pax_global_header00006660000000000000000000000064141042412430014505gustar00rootroot0000000000000052 comment=3dae79daa70c0b62e64fc6bf1b7e68757a812db0 usbredir-usbredir-0.11.0/000077500000000000000000000000001410424124300152205ustar00rootroot00000000000000usbredir-usbredir-0.11.0/.gitlab-ci.yml000066400000000000000000000034031410424124300176540ustar00rootroot00000000000000image: fedora:latest variables: DEPENDENCIES: >- gcc clang meson ninja-build valgrind git bzip2 libusb-devel glib2-devel FUZZING_DEPENDENCIES: >- clang libcxx-devel compiler-rt MINGW_DEPENDENCIES: >- mingw64-gcc mingw64-pkg-config mingw64-glib2 mingw64-libusbx # Check if can build with and without usbredirect stable: artifacts: paths: - _build/meson-logs/*.txt when: always expire_in: 1 week before_script: - dnf install -y --nogpgcheck $DEPENDENCIES script: - meson . _build -Dfuzzing=disabled -Dtools=enabled - cd _build - meson compile - meson test - meson test --wrap='valgrind --leak-check=full --error-exitcode=1' - meson dist .fuzzing_base: resource_group: fuzzing before_script: - dnf install -y --nogpgcheck $DEPENDENCIES $FUZZING_DEPENDENCIES - export CC='clang -fsanitize=fuzzer-no-link -fsanitize=address' - export CXX='clang++ -fsanitize=fuzzer-no-link -fsanitize=address' fuzzing: extends: .fuzzing_base artifacts: paths: - build/meson-logs/*.txt when: always expire_in: 1 week script: - export LIB_FUZZING_ENGINE=-fsanitize=fuzzer - export OUT=/tmp/fuzzer - ./build-aux/oss-fuzz.sh fuzzing_standalone: extends: .fuzzing_base artifacts: paths: - build-*/meson-logs/*.txt when: always expire_in: 1 week script: - export LIB_FUZZING_ENGINE=standalone - export OUT=/tmp/fuzzer-standalone - ./build-aux/oss-fuzz.sh windows: artifacts: paths: - _win_build/meson-logs/*.txt when: always expire_in: 1 week before_script: - dnf install -y --nogpgcheck $DEPENDENCIES $MINGW_DEPENDENCIES script: - mkdir _win_build && cd _win_build - mingw64-meson -Dtools=enabled - ninja usbredir-usbredir-0.11.0/COPYING000066400000000000000000000432541410424124300162630ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, 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 Lesser 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 Street, 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 Lesser General Public License instead of this License. usbredir-usbredir-0.11.0/COPYING.LIB000066400000000000000000000636311410424124300166710ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, 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 Street, 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! usbredir-usbredir-0.11.0/ChangeLog.md000066400000000000000000000211131410424124300173670ustar00rootroot00000000000000# usbredir-0.11.0 - 10 Aug 2021 - !40 Fixes use-after-free on usbredirparser serialization - !25 Fixes memory leak in usbredirparser - !32 Fixes build in MacOS related to visibility of exported symbols - !36 Adds usbredirfilter_free function - !29 Adds Fuzzing for Filters - !34 Improvements to usbredirfilter_string_to_rules() # usbredir-0.10.0 - 27 May 2021 - !23 Fixes 0.9.0 regression in bulk transfer message size - !20 Drops autotools, only meson is supported now - !15, !16, !18, !21 Improves fuzzing code base and meson builds - !17 Fixes libusbredirhost.pc when generated by meson # usbredir-0.9.0 - 02 Apr 2021 - !2 Add usbredirect tool with feature parity with usbredirserver - !6 Add fuzzer for usbredirparser - !12 Add MSI installer for usbredirect tool - !11 Add meson build: autotool will be removed in a future release - !5 Limit packet's length to 65 kB - !4 Fix wrong up-cast when checking for package's length - Require LLVM's compiler-rt (optional: for fuzzer) - Require glib2 >= 2.44 (optional: for usbredirect) - Deprecate usbredirserver in favor of usbredirect # usbredir-0.8.0 - 03 Aug 2018 - Source code and bug tracker hosted in Freedesktop's instance of Gitlab - https://gitlab.freedesktop.org/spice/usbredir - usbredirfilter - Fix busy wait due endless recursion when `interface_count` is zero - usbredirhost: - Fix leak on error - usbredirserver: - Use 'busnum-devnum' instead of 'usbbus-usbaddr' - Add support for bind specific address -4 for ipv4, -6 for ipv6 - Reject empty vendorid from command line - Enable TCP keepalive # usbredir-0.7.1 - 29 Oct 2015 - usbredirfilter - force check to device which had all interfaces skipped. This fix a bug which allow a KVM device to be redirect when it should not - usbredirparser: - allow missing capabilities from source host when loading a USB redirection stream during a qemu migration - usbredirhost: - new callback to drop isoc packets when application's pending writes buffer size is too big; The threshold calculation aims at 10fps as worst case to give at least 150ms of continuous data to application. # usbredir-0.7 - 19 May 2014 - usbredirproto: - the `usb_redir_ep_info_header` has been extended with a `max_streams` field, this is only send / received if both sides have `usb_redir_cap_bulk_streams` - Change `bulk_stream` packet definitions to allow allocating / freeing streams on multiple endpoints in one go, technically this is a protocol change, but no-one has implemented `usb_redir_cap_bulk_streams` so far, so we can safely do this - add a `USBREDIR_VERSION` define so applications can test against which version they are building - usbredirparser: - fix a bug causing parsing breakage when receiving a hello packet with 64 bit id capabiliy and another packet in succession so that they both got parsed in one `usbredirparser_do_read` call - usbredirhost: - use `libusb_set_auto_detach_kernel_driver` when available - add support for bulk streams, this only gets enabled with libusbx >= 1.0.19 - stop iso / bulk streams on reset - make cancellation handeling more robust - reset device on release - usbredirserver: - listen to both ipv4 and ipv6 addresses on ipv6 capable systems # usbredir-0.6 - 13 December 2012 - usbredirproto: - add support for bulk packets with 32 bits length - add support for buffered bulk input - usbredirparser: - add support for bulk packets with 32 bits length - add support for buffered bulk input - usbredirhost: - add support for bulk packets with 32 bits length - queue multiple transfers for interrupt receiving - add support for buffered bulk input - only apply mult to max-packet-size for isoc high speed endpoints - add a do-not-reset device blacklist, populate it with 1210:001c # usbredir-0.5.3 - 7 October 2012 - usbredirparser: - add support for bulk packets longer then 65535 bytes - usbredirhost: - add support for bulk packets longer then 65535 bytes # usbredir-0.5.2 - 25 September 2012 - usbredirparser: - rename libusbredirparser.pc to libusbredirparser-0.5.pc This should really have been done with the 0.5 release as API compatibility with previous releases was broken there! # usbredir-0.5.1 - 19 September 2012 - usbredirparser: - `usbredirparser_has_data_to_write` now returns the write queue depth, instead of only 0 or 1 - usbredirhost: - `usbredirhost_has_data_to_write` now returns the write queue depth, instead of only 0 or 1 - when the write queue gets too large, drop isochronous input packets # usbredir-0.5 - 7 September 2012 - Windows support - Add support for 64 bit packet ids - usbredirparser: - Add state serialization support - API and ABI changed to use 64 bit ids, all users need to be adjusted and recompiled for this!! - usbredirhost: - Remove bulk packets time out, this fixes various devices not working - Ack packet cancels immediately - Now supports 64 bits packet ids - Use `libusb_error_name` instead of logging raw error codes - `usbredirfilter_check` return `-ENOENT` rather then `-EPERM` if no rules match a device, this way the caller can differentiate between a deny caused by a matching rule, and one caused by there being no matching rules. # usbredir-0.4.3 - 2 April 2012 - usbredirhost: - Don't crash on devices in unconfigured state - Restore original device configuration when releasing the device - Significantly speed up reset handling - usbredirserver: - Add a manpage # usbredir-0.4.2 - 6 March 2012 - Add `usb_redir_babble` status code - usbredirparser: - extend the `usb_redir_ep_info` packet header with a `max_packet_size` field This new field is only send / received if both sides have the (new) `usb_redir_cap_ep_info_max_packet_size` capability - usbredirhost: - Ensure we always re-attach kernel drivers on cleanup - Make `set_config` handling more robust - A `set_config` or a reset can cause us to loose access to the device, if this happens this now gets reported by `usbredirhost_read_guest_data` returning `usbredirhost_read_device_lost` # usbredir-0.4.1 - 25 February 2012 - Brown paper bag release, fix a return without value in a non void function which completely breaks usbredir in some cases # usbredir-0.4 - 22 February 2012 - Add `usb_redir_filter_reject` and `usb_redir_filter_filter` packets and an `usb_redir_cap_filter` capability flag - Add an `usb_redir_device_disconnect_ack` packet and an `usb_redir_cap_device_disconnect_ack` capability flag - usbredirparser: - Add an `usbredirparser_have_peer_caps` function - usbredirhost: - Allow re-using a host instance with multiple devices, see the documentation for the new `usbredirhost_set_device()` function in usbredirhost.h - Quite a few bugfixes # usbredir-0.3.3 - 12 January 2012 - usbredirparser: - add usbredirfilter code, to help apps exclude certain devices from redirection. See usbredirfilter.h for details. - usbredirhost: - add a `usbredirhost_check_device_filter` helper function. See usbredirhost.h # usbredir-0.3.2 - 3 January 2012 - Switched to automake/autoconf/libtool based make system (Christophe Fergeau) - usbredirparser: - limited multi-thread safeness, see [multi-thread in docs](docs/multi-thread.md) - extend the device_connect packet header with a device_version field This new field is only send / received if both sides have the (new) `usb_redir_cap_connect_device_version` capability. - Add a hello_func callback which gets called after receiving the hello packet from the other side. This can be used to determine when the caps from the other side have been received and it thus is safe to call `usbredirparser_send_device_connect`. - usbredirhost: - limited multi-thread safeness, see [multi-thread in docs](docs/multi-thread.md) - properly handle clear stalls send from the usbguest - try to keep our iso buffer size near the target size - implement `usb_redir_cap_connect_device_version` # usbredir-0.3.1 - 18 August 2011 - usbredirparser: - add a `usbredirparser_fl_write_cb_owns_buffer` flag See the comment in usbredirparser.h above `usbredirparser_write` for details. - add a `usbredirparser_free_packet_data` function for symetry with `usbredirparser_free_write_buffer` - usbredirhost: - Fix device resets - Add a flags parameter to `usbredirhost_open,` this accepts the following flags: `usbredirhost_fl_write_cb_owns_buffer` - The flags parameter causes an ABI breakage, new soname: libusbredirhost.so.1 - usbredirserver: - Fix -v flag # ubsredir-0.3 - 14 July 2011 - Initial release, start at version 0.3 to match the version of the protocol desciption doc [usb-redirection-protocol.md](docs/usb-redirection-protocol.md) usbredir-usbredir-0.11.0/README.md000066400000000000000000000026041410424124300165010ustar00rootroot00000000000000# usbredir usbredir is a protocol for redirection USB traffic from a single USB device, to a different (virtual) machine then the one to which the USB device is attached. See [usb-redirection-protocol.md](docs/usb-redirection-protocol.md) for the description / definition of this protocol. This package contains a number of libraries to help implementing support for usbredir and a few simple usbredir applications: ## usbredirparser A library containing the parser for the usbredir protocol ## usbredirhost A library implementing the usb-host side of a usbredir connection, which is the side to which the actual USB device is attached. All that an application wishing to implement an usb-host needs to do is: - Provide a libusb device handle for the device - Provide write and read callbacks for the actual transport of usbredir data - Monitor for usbredir and libusb read/write events and call their handlers ## usbredirserver A simple tcp server usb-host, using usbredirhost ## usbredirtestclient A small testclient for the usbredir protocol over tcp, using usbredirparser The upstream git repository can be found at http://gitlab.freedesktop.org/spice/usbredir Bug reports can be filed against the Spice/usbredir component: https://gitlab.freedesktop.org/spice/usbredir/issues You can also send patches to the spice-devel mailing list: http://lists.freedesktop.org/mailman/listinfo/spice-devel usbredir-usbredir-0.11.0/TODO000066400000000000000000000007061410424124300157130ustar00rootroot00000000000000* do endian conversion in usbredirparser where necessary * update serialize format (comment only) to be little endian, and also do endian conversion there so be -> le and visa versa serialisation becomes possible * check length against max packet size * check caps for relevant callbacks in parser * cancel pending packets / active streams before reset? * add a queue_buf call to parser, use it in host to avoid memcpy of "in" bulk transfers usbredir-usbredir-0.11.0/build-aux/000077500000000000000000000000001410424124300171125ustar00rootroot00000000000000usbredir-usbredir-0.11.0/build-aux/msitool.py000077500000000000000000000036231410424124300211610ustar00rootroot00000000000000#!/usr/bin/python3 import os import subprocess import sys import tempfile if len(sys.argv) != 8: print("syntax: %s BUILD-DIR PREFIX WIXL-ARCH MSI-FILE WXS-FILE " \ "WIXL-HEAT-PATH WIXL-PATH" % sys.argv[0], file=sys.stderr) sys.exit(1) builddir = sys.argv[1] prefix = sys.argv[2] arch = sys.argv[3] msifile = sys.argv[4] wxs = sys.argv[5] wixl_heat = sys.argv[6] wixl = sys.argv[7] def build_msi(): manufacturer = "Usbredir project" if "DESTDIR" not in os.environ: print("$DESTDIR environment variable missing. " "Please run 'ninja install' before attempting to " "build the MSI binary, and set DESTDIR to point " "to the installation virtual root.", file=sys.stderr) sys.exit(1) if "MANUFACTURER" not in os.environ: os.environ["MANUFACTURER"] = manufacturer vroot = os.environ["DESTDIR"] manifest = [] for root, subFolder, files in os.walk(vroot): for item in files: path = str(os.path.join(root,item)) manifest.append(path) wxsfiles = subprocess.run( [ wixl_heat, "-p", vroot + prefix + "/", "--component-group", "CG.usbredirect", "--var", "var.DESTDIR", "--directory-ref", "INSTALLDIR", ], input="\n".join(manifest), encoding="utf8", check=True, capture_output=True) wxsfilelist = os.path.join(builddir, "data", "usbredirect-files.wxs") with open(wxsfilelist, "w") as fh: print(wxsfiles.stdout, file=fh) wixlenv = os.environ wixlenv["MANUFACTURER"] = manufacturer subprocess.run( [ wixl, "-D", "SourceDir=" + prefix, "-D", "DESTDIR=" + vroot + prefix, "--arch", arch, "-o", msifile, wxs, wxsfilelist, ], check=True, env=wixlenv) build_msi() usbredir-usbredir-0.11.0/build-aux/oss-fuzz.sh000077500000000000000000000034051410424124300212530ustar00rootroot00000000000000#!/usr/bin/env bash # SPDX-License-Identifier: LGPL-2.1-or-later set -eux -o pipefail export LC_CTYPE=C.UTF-8 if [[ -z "${WORK:-}" ]]; then builddir="${PWD}/build" else builddir="${WORK}/build" fi if [[ -d "$builddir" ]]; then # Meson only looks at the value of environment variables when options # are defined for the first time, i.e. not on reconfiguration (see also # https://bugs.freedesktop.org/show_bug.cgi?id=107313#c2). Consequently # object files aren't rebuilt when such options change, e.g. "c_args" from # "$CFLAGS". Removing all files in the build directory obviously solves that # problem. # # usbredir is sufficiently small to make a full rebuild acceptable. If that # were to change there'd be the following options: # # a) Use a build directory per build option set, e.g. by naming the # directory after a hash of the various relevant environment variables. # # b) Explicitly pass environment variables as their target option, e.g. # "-Dc_args=$CFLAGS". Relevant documentation: # https://mesonbuild.com/Reference-tables.html#language-arguments-parameter-names # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-flag-environment-variables # find "$builddir" -mindepth 1 -print -delete fi config=( --default-library=static -Dprefix="${OUT:?}" -Dfuzzing=enabled -Dfuzzing-engine="${LIB_FUZZING_ENGINE:?}" -Dfuzzing-install-dir="${OUT:?}" # Fails to build on Ubuntu 16.04 -Dtools=disabled # Don't use "-Wl,--no-undefined" -Db_lundef=false ) if ! meson setup "${config[@]}" -- "$builddir"; then cat "${builddir}/meson-logs/meson-log.txt" >&2 || : exit 1 fi meson compile -C "$builddir" -v meson install -C "$builddir" exit 0 usbredir-usbredir-0.11.0/data/000077500000000000000000000000001410424124300161315ustar00rootroot00000000000000usbredir-usbredir-0.11.0/data/meson.build000066400000000000000000000016471410424124300203030ustar00rootroot00000000000000with_msi=false if host_machine.system() == 'windows' wixl = find_program('wixl', required: false) wixl_heat = find_program('wixl-heat', required: false) python3 = find_program('python3', required: true) if wixl.found() and wixl_heat.found() and python3.found() with_msi=true endif endif if with_msi msi_filename = 'usbredirect-@0@-@1@.msi'.format(wixl_arch, meson.project_version()) wxsfile = configure_file( input: 'usbredirect.wxs.in', output: 'usbredirect.wxs', configuration: config ) msi = custom_target( msi_filename, input: [wxsfile], output: [msi_filename], build_by_default: false, command: [ python3, join_paths(meson.source_root(), 'build-aux', 'msitool.py'), meson.build_root(), get_option('prefix'), wixl_arch, join_paths(meson.build_root(), 'data', msi_filename), wxsfile, wixl_heat, wixl, ], ) endif usbredir-usbredir-0.11.0/data/usbredirect.wxs.in000066400000000000000000000047511410424124300216230ustar00rootroot00000000000000 NOT NEWERVERSIONDETECTED usbredir-usbredir-0.11.0/docs/000077500000000000000000000000001410424124300161505ustar00rootroot00000000000000usbredir-usbredir-0.11.0/docs/multi-thread.md000066400000000000000000000057001410424124300210730ustar00rootroot00000000000000# Multithreading libusbredirparser and libusbredirhost are *not* 100% thread-safe. They allow usage from multiple threads, but with limitations. The intended usage of the multi-threading support for libusbredirparser is to have one reader thread and allow writes / packet sends from multiple threads (including the reader thread). It is up to the app to deal with flushing writes by calling `do_write` itself. `do_write` may be called from multiple threads, libusbredirparser will serialize any calls to the write callback. The intended usage of the multi-threading support for libusbredirhost is to have one reader thread, one thread calling libusb's `handle_events` function and optionally also a separate writer thread. libusbredirhost offers setting a write flush callback, which it will call (if set) everytime it has queued some data to write. This can be used to wakeup a writer thread or it can call `usbredirhost_write_guest_data`, to directly write out the queued data from the context of its caller. Note that the flush callback may be called from both `usbredirparser_do_read` as well as from `libusb_handle_events`, so if those are done in separate threads, it may get called from multiple threads!! The above translates to some functions only allowing one caller at a time, while others allow multiple callers, see below for a detailed overview. In order to enable the multi-thread support in libusbredir[^1] the app must provide a number of locking callback functions, for libusbredirparser this is done by filling in the `usbredirparser_*_lock` funcs in the usbredirparser struct before calling `usbredirparser_init().` For libusbredirhost the locking functions (and a write-flush callback) can be specified by using `usbredirhost_open_full()` instead of `usbredirhost_open()`. [^1]: Note that the `alloc_lock_func` may not fail! If it returns NULL no locking will be done and usage from multiple threads will be unsafe. ## Overview of per function multi-thread safeness ### usbredirparser #### Only one caller allowed at a time: - `usbredirparser_create` - `usbredirparser_init` - `usbredirparser_destroy` - `usbredirparser_do_read` #### Multiple callers allowed: - `usbredirparser_get_peer_caps`[^2] - `usbredirparser_peer_has_cap`[^2] - `usbredirparser_has_data_to_write` - `usbredirparser_do_write` - `usbredirparser_free_write_buffer` - `usbredirparser_free_packet_data` - `usbredirparser_send_*` ### usbredirhost #### Only one caller allowed at a time: - `usbredirhost_open` - `usbredirhost_open_full` - `usbredirhost_close` - `usbredirhost_read_guest_data` - `usbredirhost_set_device` #### Multiple callers allowed: - `usbredirhost_has_data_to_write` - `usbredirhost_write_guest_data` - `usbredirhost_free_write_buffer` - `libusb_handle_events`[^3] # Footnotes [^2]: These only return the actual peer caps after the initial hello message has been read, as indicated by the hello_func callback. [^3]: libusb is thread safe itself, thus allowing multiple callers. usbredir-usbredir-0.11.0/docs/usb-redirection-protocol.md000066400000000000000000001145621410424124300234400ustar00rootroot00000000000000# Index [[_TOC_]] # USB Network Redirection protocol description version 0.7 (19 May 2014) ## Revisions ### Version 0.1 - Initial version (released as initial RFC without a version number) ### Version 0.2 - Version demo-ed at FOSDEM 2011 - Remove `usb_redir_report_descriptor` packet, as it is not possible to get the cached descriptors from the OS on all platforms and we can do without - Replace vm-host with usb-guest - Replace the synchroneous / asynchroneous commands nomenclature with control / data packets - Move the packet id to the main packet header shared by all packets - Add note: "All integers in the protocol are send over the pipe in least significant byte first order." - Add note: "All structs are packed" - s/data_size/length/ - Add an `usb_redir_cancel_data_packet` packet - Add `usb_redir_reset` and `usb_redir_reset_status` packets ### Version 0.3, released 14 July 2011 - First "stable" version, all later versions should be compatible with this version - Add an `usb_redir_device_connect` packet - Add an `usb_redir_device_disconnect` packet - Add an `usb_redir_interface_info` packet - Add an `usb_redir_ep_info` packet - Add support for interrupt transfers, add the following packets: `usb_redir_start_interrupt_receiving` `usb_redir_stop_interrupt_receiving` `usb_redir_interrupt_receiving_status` `usb_redir_interrupt_packet` - Add a list with the possible values for the status field - Report `usb_redir_stall` as iso status error to indicate a stream stop - Drop `usb_redir_disconnect` status, instead the usb-host should always send a `usb_redir_device_disconnect` packet on device disconnection. The reason behind this is that having to handle disconnection from data packet handlers make things unnecessarily hard for the usb-guest - Drop `usb_redir_reset_status`, instead if reconnecting to the device fails after reset the usb-host will send a `usb_redir_device_disconnect` packet ### Version 0.3.1, released 18 August 2011 - No protocol changes ### Version 0.3.2, released 3 January 2012 - The `usb_redir_device_connect_header` has been extended with a `device_version_bcd` field. This is only send / received if both sides have the `usb_redir_cap_connect_device_version` capability ### Version 0.3.3, released 12 January 2012 - No protocol changes ### Version 0.4, released 22 February 2012 - Add `usb_redir_filter_reject` and `usb_redir_filter_filter` packets and an `usb_redir_cap_filter` capability flag - Add an `usb_redir_device_disconnect_ack` packet and an `usb_redir_cap_device_disconnect_ack` capability flag ### Version 0.4.1, released 25 February 2012 - No protocol changes ### Version 0.4.2, released 6 March 2012 - Add `usb_redir_babble` status code - The `usb_redir_ep_info_header` has been extended with a `max_packet_size` field This is only send / received if both sides have the `usb_redir_cap_ep_info_max_packet_size` capability ### Version 0.5, released 7 September 2012 - Add the posibility to use 64 bits packet ids ### Version 0.5.3, released 7 October 2012 - Extend the length field in bulk packets headers to 32 bits, the extra 16 bits are only send / received if both sides have the `usb_redir_cap_32bits_bulk_length` capability ### Version 0.6, released 13 December 2012 - Add support for buffered bulk input, new packets: `usb_redir_start_bulk_receiving,` `usb_redir_stop_bulk_receiving`, `usb_redir_bulk_receiving_status,` `usb_redir_buffered_bulk_packet` New capability: `usb_redir_cap_bulk_receiving` ### Version 0.7, released 19 May 2014 - The `usb_redir_ep_info_header` has been extended with a `max_streams` field This is only send / received if both sides have the `usb_redir_cap_bulk_streams` capability. - Change bulk_stream packet definitions to allow allocating / freeing streams on multiple endpoints in one go, technically this is a protocol change, but no-one has implemented `usb_redir_cap_bulk_streams` so far, so we can safely do this # USB redirection protocol version 0.7 The protocol described in this document is meant for tunneling usb transfers to a single usb device. Note: not an entire hub, only a single device. The most significant use case for this is taking a usb device attached to some machine "a" which acts as a client / viewer to a virtual machine "v" hosted on another machine "b", and make the usb device show up inside the virtual machine as if it were attached directly to the virtual machine "v". The described protocol assumes a reliable ordered bidirectional transport is available, for example a tcp socket. All integers in the protocol are send over the pipe in least significant byte first order. All structs send over the pipe are packed (no padding). Definitions: - _usb-device_: The usb-device whose usb transfers are being tunneled. - _usb-guest_: The entity connecting to the usb-device and using it as if connected directly to it. For example a virtual machine running a guest os which accesses a usb-device over the network as if it is part of the virtual machine. - _usb-host_: The entity making the usb-device available for use by a usb-guest. For example a daemon on a machine which "exports" the usb-device over the network which then "appears" inside a virtual machine on another machine. ## Basic packet structure / communication Each packet exchanged between the usb-guest and the usb-host starts with a `usb_redir_header`, followed by an optional packet type specific header follow by optional additional data. The `usb_redir_header` each packet starts with looks as follows: ```c struct usb_redir_header { uint32_t type; uint32_t length; uint32_t id; } ``` Or, if both sides have the `usb_redir_cap_64bits_ids` capability, it looks as follows! : ```c struct usb_redir_header { uint32_t type; uint32_t length; uint64_t id; } ``` - type: This identifies the type of packet, from the type enum - length: Length of the optional type specific packet header + the optional additional data. Can be 0. - id: A unique id, generated by the usb-guest when sending a packet, the usb-host will use the same id in its response packet, allowing the usb-guest to match responses to its original requests. There are 2 types of packets: 1) control packets 2) data packets Control packets are handled synchroneously inside the usb-host, it will hand the request over to the host os and then *wait* for a response. The usb-host will thus stop processing further packets. Where as for data packets the usb-host hands them over to the host os with the request to let the usb-host process know when there is a response from the usb-device. Note that control packets should only be send to the usb-host when no data packets are pending on the device / interface / endpoint affected by the control packet. Any pending data packets will get dropped, and any active iso streams / allocated bulk streams will get stopped / free-ed. ### Packet type list #### control packets - `usb_redir_hello` - `usb_redir_device_connect` - `usb_redir_device_disconnect` - `usb_redir_reset` - `usb_redir_interface_info` - `usb_redir_ep_info` - `usb_redir_set_configuration` - `usb_redir_get_configuration` - `usb_redir_configuration_status` - `usb_redir_set_alt_setting` - `usb_redir_get_alt_setting` - `usb_redir_alt_setting_status` - `usb_redir_start_iso_stream` - `usb_redir_stop_iso_stream` - `usb_redir_iso_stream_status` - `usb_redir_start_interrupt_receiving` - `usb_redir_stop_interrupt_receiving` - `usb_redir_interrupt_receiving_status` - `usb_redir_alloc_bulk_streams` - `usb_redir_free_bulk_streams` - `usb_redir_bulk_streams_status` - `usb_redir_cancel_data_packet` - `usb_redir_filter_reject` - `usb_redir_filter_filter` - `usb_redir_device_disconnect_ack` - `usb_redir_start_bulk_receiving` - `usb_redir_stop_bulk_receiving` - `usb_redir_bulk_receiving_status` #### data packets - `usb_redir_control_packet` - `usb_redir_bulk_packet` - `usb_redir_iso_packet` - `usb_redir_interrupt_packet` - `usb_redir_buffered_bulk_packet` ### Status code list Many usb-host replies have a status field, this field can have the following values: ```c enum { usb_redir_success, usb_redir_cancelled, /* The transfer was cancelled */ usb_redir_inval, /* Invalid packet type / length / ep, etc. */ usb_redir_ioerror, /* IO error */ usb_redir_stall, /* Stalled */ usb_redir_timeout, /* Request timed out */ usb_redir_babble, /* The device has "babbled" */ }; ``` Note that in future versions there may be additional status codes to signal new / other *error* conditions. So any unknown status value should be interpreted as an error. ## usb_redir_hello ``` usb_redir_header.type: usb_redir_hello usb_redir_header.length: usb_redir_header.id: 0 (always as this is an unsolicited packet) ``` ```c struct usb_redir_hello_header { char version[64]; uint32_t capabilities[0]; } ``` No packet type specific additional data. A packet of this type is send by both sides as soon as a connection is establised. It is mandatory that this packet is the first packet send by both sides! This packet contains: - version: A free form 0 terminated version string, useful for logging should not be parsed! Suggested format: "qemu 0.13", "usb-redir-daemon 0.1", etc. - capabilities: A variable length array for announcing capabilities. Note that since the peer caps are not known until the `usb_redir_hello` packet is received, the hello packet always has 32 bits id fields! The value of the length field depends on the size of the capabilities array. If we cross the 32 capabilities count, it will go from 1 `uint32_t` to 2, etc. the value is `64 + capabilities-array-size * sizeof(uint32_t)`. Currently the following capabilities are defined: ```c enum { /* Supports USB 3 bulk streams */ usb_redir_cap_bulk_streams, /* The device_connect packet has the device_version_bcd field */ usb_redir_cap_connect_device_version, /* Supports usb_redir_filter_reject and usb_redir_filter_filter pkts */ usb_redir_cap_filter, /* Supports the usb_redir_device_disconnect_ack packet */ usb_redir_cap_device_disconnect_ack, /* The ep_info packet has the max_packet_size field */ usb_redir_cap_ep_info_max_packet_size, /* Supports 64 bits ids in usb_redir_header */ usb_redir_cap_64bits_ids, /* Supports 32 bits length in usb_redir_bulk_packet_header */ usb_redir_cap_32bits_bulk_length, /* Supports bulk receiving / buffered bulk input */ usb_redir_cap_bulk_receiving, }; ``` ## usb_redir_device_connect ``` usb_redir_header.type: usb_redir_device_connect usb_redir_header.length: sizeof(usb_redir_device_connect_header) usb_redir_header.id: 0 (always as this is an unsolicited packet) ``` ```c enum { usb_redir_speed_low, usb_redir_speed_full, usb_redir_speed_high, usb_redir_speed_super, usb_redir_speed_unknown = 255 } struct usb_redir_device_connect_header { uint8_t speed; uint8_t device_class; uint8_t device_subclass; uint8_t device_protocol; uint16_t vendor_id; uint16_t product_id; uint16_t device_version_bcd; } ``` No packet type specific additional data. This packet gets send by the usb-host when a device becomes available (it is possible for the usb-host to wait for a device to get plugged in). The `device_version_bcd` field should only be send (and expected on receive) when both sides have the `usb_redir_cap_connect_device_version` capability. If this is not the case the length of the packet will be 2 bytes less! Note that a usb-host may re-use the existing connection for a new / re-plugged device in this case this packet can be send after a `usb_redir_device_disconnect` message to notify the usb-guest that a new device is available. Note the usbredir-host *must* first send `usb_redir_ep_info` followed by `usb_redir_interface_info` before sending the `usb_redir_device_connect_info`! ## usb_redir_device_disconnect ``` usb_redir_header.type: usb_redir_device_disconnect usb_redir_header.length: 0 usb_redir_header.id: 0 (always as this is an unsolicited packet) ``` No packet type specific header. No packet type specific additional data. This packet may be send by the usb-host to indicate that the device has been disconnect (unplugged). Note on some platforms the usb-host may not become aware of the disconnection until a usb packet is send to the device. ## usb_redir_reset ``` usb_redir_header.type: usb_redir_reset usb_redir_header.length: 0 ``` No packet type specific header. No packet type specific additional data. This packet can be send by the usb-guest to cause a reset of the usb device. Note that of things go wrong the usb-host may be unable to re-connect to the device after the reset! If this happens a `usb_redir_device_disconnect` packet will be send by the usb-host. ## usb_redir_interface_info ``` usb_redir_header.type: usb_redir_interface_info usb_redir_header.length: sizeof(usb_redir_interface_info_header) usb_redir_header.id: 0 (always as this is an unsolicited packet) ``` ```c struct usb_redir_interface_info_header { uint32_t interface_count; uint8_t interface[32]; uint8_t interface_class[32]; uint8_t interface_subclass[32]; uint8_t interface_protocol[32]; } ``` No packet type specific additional data. This packet gets send by the usb-host to inform the usb-guest about the interfaces of the device. It contains the interface number, class and protocol info for `interface_count` interfaces. This gets send after a (successful) initial connection, `set_config` and `set_alt_setting`. ## usb_redir_ep_info ``` usb_redir_header.type: usb_redir_ep_info usb_redir_header.length: sizeof(usb_redir_ep_info_header) usb_redir_header.id: 0 (always as this is an unsolicited packet) ``` ```c enum { /* Note these 4 match the usb spec! */ usb_redir_type_control, usb_redir_type_iso, usb_redir_type_bulk, usb_redir_type_interrupt, usb_redir_type_invalid = 255 } struct usb_redir_ep_info_header { uint8_t type[32]; uint8_t interval[32]; uint8_t interface[32]; uint16_t max_packet_size[32]; uint32_t max_streams[32]; } ``` No packet type specific additional data. This packet gets send by the usb-host to let the usb-guest know the endpoint type, interval and interface it belongs to for all possible endpoints, first 0-15 out, then 0-15 in. This gets send after a (successful) initial connection, `set_config` and `set_alt_setting`. The `max_packet_size` field should only be send (and expected on receive) when both sides have the `usb_redir_cap_ep_info_max_packet_size` capability. If this is not the case the length of the packet will be 64 bytes less! The `max_streams` field should only be send (and expected on receive) when both sides have the `usb_redir_cap_bulk_streams` capability. If this is not the case the length of the packet will be 128 bytes less! Note implementations with the `usb_redir_cap_bulk_streams` capability must always also have the `usb_redir_cap_ep_info_max_packet_size` capability. Advertising `usb_redir_cap_bulk_streams` without `usb_redir_cap_ep_info_max_packet_size` is not allowed! ## usb_redir_set_configuration ``` usb_redir_header.type: usb_redir_set_configuration usb_redir_header.length: sizeof(usb_redir_set_configuration_header) ``` ```c struct usb_redir_set_configuration_header { uint8_t configuration; } ``` No packet type specific additional data. This packet can be send by the usb-guest to set (change) the active configuration of the usb-device. ## usb_redir_get_configuration ``` usb_redir_header.type: usb_redir_get_configuration usb_redir_header.length: 0 ``` No packet type specific header. No packet type specific additional data. This packet can be send by the usb-guest to get (query) the active configuration of the usb-device. ## usb_redir_configuration_status ``` usb_redir_header.type: usb_redir_configuration_status usb_redir_header.length: sizeof(usb_redir_configuration_status_header) ``` ```c struct usb_redir_configuration_status_header { uint8_t status; uint8_t configuration; } ``` No packet type specific additional data. This is send by the usb-host in response to a `usb_redir_set_configuration` / `usb_redir_get_configuration` packet. It reports a status code and on success the resulting / active configuration. Note that after a successful `usb_redir_set_configuration` command the usbredir-host *must* first send `usb_redir_ep_info` followed by `usb_redir_interface_info` before sending the `usb_redir_configuration_status`, to ensure the usb-guest has the new info when it starts using the new configuration. ## usb_redir_set_alt_setting ``` usb_redir_header.type: usb_redir_set_alt_setting usb_redir_header.length: sizeof(usb_redir_set_alt_setting_header) ``` ```c struct usb_redir_set_alt_setting_header { uint8_t interface; uint8_t alt; } ``` No packet type specific additional data. This packet can be send by the usb-guest to set (change) the `alt_setting` of interface `` to ``. ## usb_redir_get_alt_setting ``` usb_redir_header.type: usb_redir_get_alt_setting usb_redir_header.length: sizeof(usb_redir_get_alt_setting_header) ``` ```c struct usb_redir_get_alt_setting_header { uint8_t interface; } ``` No packet type specific additional data. This packet can be send by the usb-guest to get (query) the active `alt_setting` of an interface of the usb-device. ## usb_redir_alt_setting_status ``` usb_redir_header.type: usb_redir_alt_setting_status usb_redir_header.length: sizeof(usb_redir_alt_setting_status_header) ``` ```c struct usb_redir_alt_setting_status_header { uint8_t status; uint8_t interface; uint8_t alt; } ``` No packet type specific additional data. This is send by the usb-host in response to a `usb_redir_set_alt_setting` / `usb_redir_get_alt_setting` packet. It reports a status code, the affected interface and on success the resulting / active `alt_setting` for that interface. Note that after a successful `usb_redir_set_alt_setting` command the usbredir-host *must* first send `usb_redir_ep_info` followed by `usb_redir_interface_info` before sending the `usb_redir_alt_setting_status`, to ensure the usb-guest has the new info when it starts using the new alt setting. ## usb_redir_start_iso_stream ``` usb_redir_header.type: usb_redir_start_iso_stream usb_redir_header.length: sizeof(usb_redir_start_iso_stream_header) ``` ```c struct usb_redir_start_iso_stream_header { uint8_t endpoint; uint8_t pkts_per_urb; uint8_t no_urbs; } ``` No packet type specific additional data. This packet can be send by the usb-guest to start a iso stream on the designated endpoint of the usb-device. This function allocates `no_urbs` urbs with `pkts_per_urb` iso packets/frames per urb. For iso input endpoints these urbs will get submitted to the device *immediately*, for iso output endpoints the usb-host will wait till it has received `(pkts_per_urb * no_urbs / 2)` packets to fill its buffers, before submitting the first urb. ## usb_redir_stop_iso_stream ``` usb_redir_header.type: usb_redir_stop_iso_stream usb_redir_header.length: sizeof(struct usb_redir_start_iso_stream_header) ``` ```c struct usb_redir_stop_iso_stream_header { uint8_t endpoint; } ``` No packet type specific additional data. This packet can be send by the usb-guest to stop an iso stream on the designated endpoint. This will cancel all pending urbs, flush the usb-host's buffers and free all relevant resources. Note that the usb-guest can still receive isoc data packets from an isoc in endpoint after sending this, as some data packets may already be inside the transport pipe. ## usb_redir_iso_stream_status ``` usb_redir_header.type: usb_redir_iso_stream_status usb_redir_header.length: sizeof(usb_redir_iso_stream_status_header) ``` ```c struct usb_redir_iso_stream_status_header { uint8_t status; uint8_t endpoint; } ``` No packet type specific additional data. This packet is send by the usb-host in response to a `usb_redir_start_iso_stream` or `usb_redir_stop_iso_stream` packet. Note that for the starting of output iso streams a success status only indicates that all the buffers were successfully allocated, the actual stream is not started until enough packets are buffered. Note that this can also be send unsolicited by a usb-host in case of an error with an iso output stream, see `usb_redir_iso_packet`. To allow the usb-guest to detect if the stream was adversely stopped, the usb-host will always report `usb_redir_stall` as status if the stream was stopped for any reason other then an `usb_redir_stop_iso_stream`. ## usb_redir_start_interrupt_receiving ``` usb_redir_header.type: usb_redir_start_interrupt_receiving usb_redir_header.length: sizeof(usb_redir_start_interrupt_receiving_header) ``` ```c struct usb_redir_start_interrupt_receiving_header { uint8_t endpoint; } ``` No packet type specific additional data. This packet can be send by the usb-guest to start receiving interrupts from the designated endpoint of the usb-device. This function is for *input* interrupt endpoints only. Input interrupt endpoints need to be polled timely otherwise data may get lost. So for input interrupt endpoints the usb-host takes care of the submitting and re-submitting of urbs. On receiving this packet the usb-host will start an interrupt transfer to the endpoint using the interval and `maxPacketSize` from the descriptors. When this transfer completes, the usb-host will send an `usb_redir_interrupt_packet` to the usb-guest, and will re-submit the urb. ## usb_redir_stop_interrupt_receiving ``` usb_redir_header.type: usb_redir_stop_interrupt_receiving usb_redir_header.length: sizeof(struct usb_redir_start_interrupt_receiving_header) ``` ```c struct usb_redir_stop_interrupt_receiving_header { uint8_t endpoint; } ``` No packet type specific additional data. This packet can be send by the usb-guest to stop interrupt receiving on the designated endpoint. This will cancel the pending urb. Note that the usb-guest can still receive `usb_redir_interrupt_packet-s` after sending this, as some data packets may already be inside the transport pipe. ## usb_redir_interrupt_receiving_status ``` usb_redir_header.type: usb_redir_interrupt_receiving_status usb_redir_header.length: sizeof(usb_redir_interrupt_receiving_status_header) ``` ```c struct usb_redir_interrupt_receiving_status_header { uint8_t status; uint8_t endpoint; } ``` No packet type specific additional data. This packet is send by the usb-host in response to a `usb_redir_start_interrupt_receiving` or `usb_redir_stop_interrupt_receiving` packet. Note that this can also be send unsolicited by a usb-host in case of an error re-submitting the interrupt urb. To allow the usb-guest to detect if the stream was adversely stopped, the usb-host will always report `usb_redir_stall` as status if the stream was stopped for any reason other then an `usb_redir_stop_interrupt_receiving`. ## usb_redir_alloc_bulk_streams ``` usb_redir_header.type: usb_redir_alloc_bulk_streams usb_redir_header.length: sizeof(usb_redir_alloc_bulk_streams_header) ``` ```c struct usb_redir_alloc_bulk_streams_header { uint32_t endpoints; /* bitmask indicating on which eps to alloc streams */ uint32_t no_streams; } ``` No packet type specific additional data. This packet can be send by the usb-guest to the usb-host to request that the usb-host allocates IDs so the usb-guest can use up to `no_streams` stream IDs on the endpoints indicated by the `endpoints` bitmask. Endpoints in the bitmask are indicated by bit number (0-31) using the same numbering as in `usb_redir_ep_info_header`. ## usb_redir_free_bulk_streams ``` usb_redir_header.type: usb_redir_free_bulk_streams usb_redir_header.length: sizeof(usb_redir_free_bulk_streams_header) ``` ```c struct usb_redir_free_bulk_streams_header { uint32_t endpoints; /* bitmask indicating on which eps to free streams */ } ``` No packet type specific additional data. This packet can be send by the usb-guest to the usb-host to free any bulk streams previously allocated on the endpoints indicated by the endpoints bitmask. ## usb_redir_bulk_streams_status ``` usb_redir_header.type: usb_redir_bulk_streams_status usb_redir_header.length: sizeof(usb_redir_bulk_streams_status_header) ``` ```c struct usb_redir_bulk_streams_status_header { uint32_t endpoints; /* bitmask indicating eps this status message is for */ uint32_t no_streams; uint8_t status; } ``` No packet type specific additional data. This packet is send by the usb-host in response to a `usb_redir_alloc_bulk_streams` or `usb_redir_free_bulk_streams` packet. For `usb_redir_alloc_bulk_streams` responses `no_streams` will be the `no_streams` passed to the `usb_redir_alloc_bulk_streams` packet. usb-hosts are not allowed to return less streams then requested! For `usb_redir_free_bulk_streams` responses `no_streams` will be 0. On a success status in response to a `usb_redir_alloc_bulk_streams` the usb-guest may use stream ids 1 through `no_streams`. ## usb_redir_start_bulk_receiving ``` usb_redir_header.type: usb_redir_start_bulk_receiving usb_redir_header.length: sizeof(usb_redir_start_bulk_receiving_header) ``` ```c struct usb_redir_start_bulk_receiving_header { uint32_t stream_id; uint32_t bytes_per_transfer; uint8_t endpoint; uint8_t no_transfers; } ``` No packet type specific additional data. This packet can be send by the usb-guest to start buffered reading from a bulk endpoint. Upon receiving this packet the usb-host will submit `no_transfers` bulk in transfer of `bytes_per_transfer` each to the designated endpoint of the usb-device. Upon completion of a transfer the usb-host will send an `usb_redir_buffered_bulk_packet` with the received data to the usb-guest, and immediately re-submit the completed transfer. Note `bytes_per_transfer` must be a multiple of the endpoints `max_packet_size`. Note this packet should only be send to usb-hosts with the `usb_redir_cap_bulk_receiving` capability. ## usb_redir_stop_bulk_receiving ``` usb_redir_header.type: usb_redir_stop_bulk_receiving usb_redir_header.length: sizeof(usb_redir_stop_bulk_receiving_header) ``` ```c struct usb_redir_stop_bulk_receiving_header { uint32_t stream_id; uint8_t endpoint; } ``` No packet type specific additional data. This packet can be send by the usb-guest to stop bulk receiving on the designated endpoint. This will cancel all pending transfers. Note that the usb-guest can still receive `usb_redir_bulk_packet-s` after sending this, as some data packets may already be inside the transport pipe. Note this packet should only be send to usb-hosts with the `usb_redir_cap_bulk_receiving` capability. ## usb_redir_bulk_receiving_status ``` usb_redir_header.type: usb_redir_bulk_receiving_status usb_redir_header.length: sizeof(usb_redir_bulk_receiving_status_header) ``` ```c struct usb_redir_bulk_receiving_status_header { uint32_t stream_id; uint8_t endpoint; uint8_t status; } ``` No packet type specific additional data. This packet is send by the usb-host in response to a `usb_redir_start_bulk_receiving` or `usb_redir_stop_bulk_receiving` packet. Note that this can also be send unsolicited by an usb-host in case of an error re-submitting the bulk transfer. To allow the usb-guest to detect if the stream was adversely stopped, the usb-host will always report `usb_redir_stall` as status if the stream was stopped for any reason other then an `usb_redir_stop_interrupt_receiving`. Note this packet should only be send to usb-guests with the `usb_redir_cap_bulk_receiving` capability. ## usb_redir_cancel_data_packet ``` usb_redir_header.type: usb_redir_cancel_data_packet usb_redir_header.id usb_redir_header.length: 0 ``` No packet type specific header. No packet type specific additional data. This packet can be send by the usb-guest to cancel an earlier send data packet, the id should be set to the id used when sending the packet the guest now wishes to cancel. Note that the usb-guest will always receive back a data packet of the same type and with the same id, the usb-guest can check if the packet completed normally (before the cancel packet was processed by the usb-host), or was cancelled by looking at the return data packet's status field. ## usb_redir_filter_reject ``` usb_redir_header.type: usb_redir_filter_reject usb_redir_header.length: 0 usb_redir_header.id: 0 (always as this is an unsolicited packet) ``` No packet type specific header. No packet type specific additional data. This packet is send by the usb-guest after receiving a `usb_redir_device_connect` or `usb_redir_interface_info` packet which was rejected by an usb-guest side device filter. This packet should only be send to usb-hosts with the `usb_redir_cap_filter` capability. ## usb_redir_filter_filter ``` usb_redir_header.type: usb_redir_filter_filter usb_redir_header.length: string-length + 1 (for 0 termination) usb_redir_header.id: 0 (always as this is an unsolicited packet) ``` No packet type specific header. The additional data contains a 0 terminated usredirfilter string. This packet can be send directly after the hello packet to inform the other side that a filter is in place and some devices may be rejected. An usredirfilter consists of one or more rules, where in string form each rule has the following format: `,,,,` Values can be either in decimal format, or in hexadecimal format pre-fixed with 0x, a value of -1 can be used to allow any value. All rules of a filter are concatenated, separated by the '|' character to form a single usredirfilter string: `||` If a device matches none of the rules the result of the filter is deny and the device will be rejected. For more info on filtering see usbredirfilter.h This packet should only be send to peers with the `usb_redir_cap_filter` capability. ## usb_redir_device_disconnect_ack ``` usb_redir_header.type: usb_redir_device_disconnect_ack usb_redir_header.length: 0 usb_redir_header.id: 0 (as the id of the device_disconnect is always 0) ``` No packet type specific header. No packet type specific additional data. This packet is send by the usb-guest after having processed a `usb_redir_device_disconnect` packet send by the usb-host. This allows an usb-host which wants to re-use an existing connection to know that the usb-guest has seen the disconnect and will not send any more packets intended for the disconnected device. Without this there is a race where the usb-host may have a new device available, but it is still receiving packets intended for the old device as the usb-guest has not yet seen the disconnect. Note this packet is only send if both sides have the `usb_redir_cap_device_disconnect_ack` capability. ## usb_redir_control_packet ``` usb_redir_header.type: usb_redir_control_packet usb_redir_header.length: sizeof(usb_redir_control_packet_header) [+ length] ``` ```c struct usb_redir_control_packet_header { uint8_t endpoint; uint8_t request; uint8_t requesttype; uint8_t status; uint16_t value; uint16_t index; uint16_t length; } ``` The additional data contains the control msg data to be send / received. Packets of this type can be send by the usb-guest to the usb-host to initiate a control transfer on the usb-device. endpoint, request, requesttype, value and index have their standard meaning for usb control messages. The status field is only used in the usb-host's response. length is the amount of data the usb-guest is sending / expects to read (in the USB_DIR_IN case). Note that the length should only be added to `usb_redir_header.length` in one direction (and the actual packet length should match). When the control msg has been processed by the usb-device the usb-host sends a `usb_redir_control_packet` back to the usb-guest, with all fields unchanged except for the status field and length which get updated to match the actual results. ## usb_redir_bulk_packet ``` usb_redir_header.type: usb_redir_bulk_packet usb_redir_header.length: sizeof(usb_redir_bulk_packet_header) [+ length] ``` ```c struct usb_redir_bulk_packet_header { uint8_t endpoint; uint8_t status; uint16_t length; uint32_t stream_id; uint16_t length_high; /* High 16 bits of the packet length */ } ``` The additional data contains the bulk msg data to be send / received. Packets of this type can be send by the usb-guest to the usb-host to initiate a bulk transfer on the usb-device. `endpoint` and `stream_id` have their standard meaning for usb bulk messages. The `status` field is only used in the usb-host's response. `length` is the amount of data the usb-guest is sending / expects to read (depending on the direction of the endpoint). `length_high` contains the 16 high bits of length to allow packets larger then 65535 bytes, it is only send/received if both sides have the `usb_redir_cap_32bits_bulk_length` capability. When the bulk msg has been processed by the usb-device the usb-host sends a `usb_redir_bulk_packet` back to the usb-guest, with the `status` field and length updated to match the actual results. Note just as `usb_redir_control_packet` this packet only has additional data in one direction depending on the direction of the endpoint. Note see `usb_redir_buffered_bulk_packet` for an alternative for receiving data from bulk endpoints. ## usb_redir_iso_packet ``` usb_redir_header.type: usb_redir_iso_packet usb_redir_header.length: sizeof(usb_redir_iso_packet_header) + length ``` ```c struct usb_redir_iso_packet_header { uint8_t endpoint; uint8_t status; uint16_t length; } ``` The additional data contains the iso msg data to be send / received. Packets of this type should be send continuesly (at the endpoint interval speed) as soon as an iso stream is started using `usb_redir_start_iso_stream` the direction in which they gets send depends on the endpoints direction. The status field only has meaning for packets send from the usb-host to the usb-guest (for iso input endpoints). Due to buffering it is not possibly to timely notify the usb-guest of transfer errors for iso output packets. The usb-host will try to clear any error conditions itself. If it fails to do so it will send a `usb_redir_iso_stream_status` to the usb-guest indicating there is a problem with the iso stream. Since `usb_redir_iso_packet`s are send continuously by the usb-host once a stream is started on an iso input endpoint, the usb-host cannot set the `usb_redir_header.id` to the id of the corresponding received packet. So for `usb_redir_iso_packet's` the usb-host simply starts with an id of 0 and increments this every packet. Note that when the usb-host has recovered from a stall the id will restart at 0! ## usb_redir_interrupt_packet ``` usb_redir_header.type: usb_redir_interrupt_packet usb_redir_header.length: sizeof(usb_redir_interrupt_packet_header) [+ length] ``` ```c struct usb_redir_interrupt_packet_header { uint8_t endpoint; uint8_t status; uint16_t length; } ``` The additional data contains the interrupt msg data to be send / received. The handling of interrupt endpoints differs significantly depending on wether the endpoint is an input or output endpoint. # Input endpoints Input interrupt endpoints need to be polled timely otherwise data may get lost. So for input interrupt endpoints the usb-host takes care of the submitting and re-submitting of urbs, the usb-guest can start / stop the receiving of interrupt packets using the `usb_redir_start_interrupt_receiving` / `usb_redir_stop_interrupt_receiving` packets. Note that for an input interrupt endpoint `usb_redir_interrupt_packet-s` are only send in one direction, from the usb-host to the usb-guest! Since `usb_redir_interrupt_packet`s are send unsolicited by the usb-host once interrupt receiving has started, the usb-host cannot set the `usb_redir_header.id` to the id of the corresponding received packet. So for `usb_redir_interrupt_packet`s the usb-host simply starts with an id of 0 and increments this every packet. Note that when the usb-host has recovered from a stall the id will restart at 0! # Output endpoints For interrupt output endpoints the normal asynchroneous mechanism also used for control and bulk transfers is used: The usb-guest sends a `usb_redir_interrupt_packet` to the usb-host. When the interrupt msg has been processed by the usb-device the usb-host sends a `usb_redir_interrupt_packet` back to the usb-guest, with the status field and length updated to match the actual results. This packet only has additional data (the data to output) when send from usb-guest to usb-host. Note that since unlike with iso data there is usually no notion of a stream with interrupt data, buffering makes no sense for output interrupt packets, instead they are delivered asap. Despite this asap delivery it is likely that the timing constraints which apply to interrupt output transfers will not be met. The consequences of this will vary from device to device. ## usb_redir_buffered_bulk_packet ``` usb_redir_header.type: usb_redir_bulk_packet usb_redir_header.length: sizeof(usb_redir_bulk_packet_header) + length usb_redir_header.id: starts at 0, incremented by 1 per send packet ``` ```c struct usb_redir_buffered_bulk_packet_header { uint32_t stream_id; uint32_t length; uint8_t endpoint; uint8_t status; } ``` The additional data contains the bulk msg data received. Buffered bulk mode is intended for bulk *input* endpoints, where the data is of a streaming nature (not part of a command-response protocol). These endpoints' input buffer may overflow if data is not read quickly enough. So in buffered bulk mode the usb-host takes care of the submitting and re-submitting of bulk transfers. The usb-guest can start / stop the receiving of buffered bulk data using the `usb_redir_start_bulk_receiving` / `usb_redir_stop_bulk_receiving` packets. Note that `usb_redir_buffered_bulk_packet-s` are only send in one direction, from the usb-host to the usb-guest! Since `usb_redir_buffered_bulk_packet-s` are send unsolicited by the usb-host once bulk receiving has started, the usb-host cannot set the `usb_redir_header.id` to the id of the corresponding received packet. So for `usb_redir_buffered_bulk_packet-s` the usb-host simply starts with an id of 0 and increments this every packet. Note that when the usb-host has recovered from a stall the id will restart at 0! A typical example where buffered bulk mode should be used is with the bulk in endpoints of usb to serial convertors. Note buffered bulk mode can only be used when both sides have the `usb_redir_cap_bulk_receiving` capability. usbredir-usbredir-0.11.0/fuzzing/000077500000000000000000000000001410424124300167145ustar00rootroot00000000000000usbredir-usbredir-0.11.0/fuzzing/README.md000066400000000000000000000002551410424124300201750ustar00rootroot00000000000000This directory contains test programs compatible with [libFuzzer][libfuzzer], a library for coverage-guided fuzz testing. [libfuzzer]: https://llvm.org/docs/LibFuzzer.html usbredir-usbredir-0.11.0/fuzzing/default.options000066400000000000000000000000141410424124300217500ustar00rootroot00000000000000[libfuzzer] usbredir-usbredir-0.11.0/fuzzing/meson.build000066400000000000000000000026541410424124300210650ustar00rootroot00000000000000add_languages('cpp', required: true) cpp_compiler = meson.get_compiler('cpp') cpp_compiler.has_header('fuzzer/FuzzedDataProvider.h', required: true) link_args = [] link_with = [] fuzz_engine = get_option('fuzzing-engine') fuzz_install_dir = get_option('fuzzing-install-dir') if fuzz_engine == 'standalone' standalone_lib = static_library('standalone', sources: ['standalone.c'], install: false, ) link_with += [standalone_lib] else link_args += fuzz_engine.split() if not cpp_compiler.links(''' #include #include extern "C" int LLVMFuzzerTestOneInput(const uint8_t *, size_t) { return 0; } ''', args: link_args, ) error('Linking fuzzing test failed') endif endif fuzz_targets = { 'usbredirparserfuzz': [usbredir_parser_lib_dep], 'usbredirfilterfuzz': [usbredir_parser_lib_dep], } foreach target_name, deps : fuzz_targets executable(target_name, sources: [target_name + '.cc'], dependencies: deps, link_language: 'cpp', link_args: link_args, link_with: link_with, install: (fuzz_install_dir != ''), install_dir: fuzz_install_dir, ) if fuzz_install_dir != '' install_data('default.options', install_dir: fuzz_install_dir, rename: target_name + '.options', ) endif endforeach usbredir-usbredir-0.11.0/fuzzing/standalone.c000066400000000000000000000047261410424124300212210ustar00rootroot00000000000000/* standalone.c -- libFuzzer-compatible main function Copyright 2021 Michael Hanselmann This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, see . */ #include #include #include #include #include #include #include #include #include // Forward declare the "fuzz target" interface. We deliberately keep this // interface simple and header-free. extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); int main(int argc, char **argv) { const char *path; int fd; ssize_t length; uint8_t *buf; if (argc < 2) { fprintf(stderr, "Usage: %s \n", argv[0]); exit(2); } for (int i = 1; i < argc; i++) { path = argv[i]; fd = open(path, O_RDONLY); if (fd < 0) { fprintf(stderr, "Opening \"%s\": %s\n", path, strerror(errno)); exit(1); } length = lseek(fd, 0, SEEK_END); if (length < 0) { fprintf(stderr, "Seeking end of \"%s\": %s\n", path, strerror(errno)); exit(1); } if (lseek(fd, 0, SEEK_SET) < 0) { fprintf(stderr, "Seeking beginning of \"%s\": %s\n", path, strerror(errno)); exit(1); } fprintf(stderr, "Reading %zd bytes from \"%s\"\n", length, path); // Allocate exactly length bytes so that we reliably catch buffer // overflows. buf = malloc(length); if (read(fd, buf, length) != length) { fprintf(stderr, "Reading %zd bytes from \"%s\": %s\n", length, path, strerror(errno)); exit(1); } close(fd); LLVMFuzzerTestOneInput(buf, length); fprintf(stderr, "Execution successful\n"); free(buf); } return 0; } /* vim: set sw=4 sts=4 et : */ usbredir-usbredir-0.11.0/fuzzing/usbredirfilterfuzz.cc000066400000000000000000000064401410424124300231730ustar00rootroot00000000000000/* usbredirfilterfuzz.cc -- fuzzing for usbredirfilter Copyright 2021 Michael Hanselmann This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, see . */ #include #include #include #include #include #include "usbredirfilter.h" namespace { struct FilterDeleter { void operator()(void *ptr) { usbredirfilter_free(ptr); } }; } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { static FILE *dev_null = nullptr; if (dev_null == nullptr) { dev_null = fopen("/dev/null", "wb"); if (dev_null == nullptr) { perror("open /dev/null"); abort(); } } FuzzedDataProvider fdp{data, size}; std::unique_ptr rules; int ret, rules_count; const std::string token_sep = fdp.ConsumeBytesAsString(1), rule_sep = fdp.ConsumeBytesAsString(1); { usbredirfilter_rule *rules_ptr = nullptr; ret = usbredirfilter_string_to_rules( fdp.ConsumeRandomLengthString().c_str(), token_sep.c_str(), rule_sep.c_str(), &rules_ptr, &rules_count); if (ret != 0 || rules_ptr == nullptr) { return 1; } rules.reset(rules_ptr); } usbredirfilter_verify(rules.get(), rules_count); usbredirfilter_print(rules.get(), rules_count, dev_null); { std::unique_ptr str; str.reset(usbredirfilter_rules_to_string(rules.get(), rules_count, token_sep.c_str(), rule_sep.c_str())); } { const int interface_count = fdp.ConsumeIntegralInRange(1, 128); std::vector interface_class = fdp.ConsumeBytes(interface_count), interface_subclass = fdp.ConsumeBytes(interface_count), interface_protocol = fdp.ConsumeBytes(interface_count); // Fill with zeros up to the desired length interface_class.resize(interface_count, 0); interface_subclass.resize(interface_count, 0); interface_protocol.resize(interface_count, 0); usbredirfilter_check(rules.get(), rules_count, fdp.ConsumeIntegral(), fdp.ConsumeIntegral(), fdp.ConsumeIntegral(), &interface_class[0], &interface_subclass[0], &interface_protocol[0], interface_count, fdp.ConsumeIntegral(), fdp.ConsumeIntegral(), fdp.ConsumeIntegral(), fdp.ConsumeIntegral()); } return 0; } /* vim: set sw=4 sts=4 et : */ usbredir-usbredir-0.11.0/fuzzing/usbredirparserfuzz.cc000066400000000000000000000340441410424124300232030ustar00rootroot00000000000000/* usbredirparserfuzz.cc -- fuzzing for usbredirparser Copyright 2021 Michael Hanselmann This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include "usbredirfilter.h" #include "usbredirparser.h" namespace { struct ParserDeleter { void operator()(struct usbredirparser *p) { usbredirparser_destroy(p); } }; std::unique_ptr parser; std::unique_ptr fdp; void log(const char *format, ...) { #if 0 va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); #endif } void parser_log(void *priv, int level, const char *msg) { log("[%d] %s\n", level, msg); } int parser_read(void *priv, uint8_t *data, int count) { log("%s: %d bytes\n", __func__, count); return fdp->ConsumeData(data, count); } int parser_write(void *priv, uint8_t *data, int count) { log("%s: %d bytes\n", __func__, count); // Read over complete source buffer to detect buffer overflows on write void *buf = malloc(count); memcpy(buf, data, count); free(buf); return count; } void parser_device_connect(void *priv, struct usb_redir_device_connect_header *device_connect) { log("%s: speed=%d, class=%d, subclass=%d, protocol=%d, vendor=%04x," " product=%04x\n", __func__, device_connect->speed, device_connect->device_class, device_connect->device_subclass, device_connect->device_protocol, device_connect->vendor_id, device_connect->product_id); } void parser_device_disconnect(void *priv) { log("%s\n", __func__); } void parser_reset(void *priv) { log("%s\n", __func__); } void parser_interface_info(void *priv, struct usb_redir_interface_info_header *info) { uint32_t i; log("%s:", __func__); for (i = 0; i < info->interface_count; i++) { log(" [interface %d, class %d, subclass %d, protocol %d]", info->interface[i], info->interface_class[i], info->interface_subclass[i], info->interface_protocol[i]); } log("\n"); } void parser_ep_info(void *priv, struct usb_redir_ep_info_header *ep_info) { int i; log("%s:", __func__); for (i = 0; i < 32; i++) { if (ep_info->type[i] != usb_redir_type_invalid) { log(" [index %d, type %d, interval %d, interface %d," " max-packetsize %d]", i, (int)ep_info->type[i], (int)ep_info->interval[i], (int)ep_info->interface[i], ep_info->max_packet_size[i]); } } log("\n"); } void parser_set_configuration(void *priv, uint64_t id, struct usb_redir_set_configuration_header *set_configuration) { } void parser_get_configuration(void *priv, uint64_t id) { } void parser_configuration_status(void *priv, uint64_t id, struct usb_redir_configuration_status_header *config_status) { log("%s: id=%" PRIu64 ", status=%d, configuration=%d\n", __func__, id, config_status->status, config_status->configuration); } void parser_set_alt_setting(void *priv, uint64_t id, struct usb_redir_set_alt_setting_header *set_alt_setting) { } void parser_get_alt_setting(void *priv, uint64_t id, struct usb_redir_get_alt_setting_header *get_alt_setting) { } void parser_alt_setting_status(void *priv, uint64_t id, struct usb_redir_alt_setting_status_header *alt_setting_status) { log("%s: id=%" PRIu64 ", status=%d, interface=%d, alt=%d\n", __func__, id, alt_setting_status->status, alt_setting_status->interface, alt_setting_status->alt); } void parser_start_iso_stream(void *priv, uint64_t id, struct usb_redir_start_iso_stream_header *start_iso_stream) { } void parser_stop_iso_stream(void *priv, uint64_t id, struct usb_redir_stop_iso_stream_header *stop_iso_stream) { } void parser_iso_stream_status(void *priv, uint64_t id, struct usb_redir_iso_stream_status_header *iso_stream_status) { } void parser_start_interrupt_receiving(void *priv, uint64_t id, struct usb_redir_start_interrupt_receiving_header *start_interrupt_receiving) { } void parser_stop_interrupt_receiving(void *priv, uint64_t id, struct usb_redir_stop_interrupt_receiving_header *stop_interrupt_receiving) { } void parser_interrupt_receiving_status(void *priv, uint64_t id, struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status) { } void parser_alloc_bulk_streams(void *priv, uint64_t id, struct usb_redir_alloc_bulk_streams_header *alloc_bulk_streams) { } void parser_free_bulk_streams(void *priv, uint64_t id, struct usb_redir_free_bulk_streams_header *free_bulk_streams) { } void parser_bulk_streams_status(void *priv, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status) { } void parser_cancel_data_packet(void *priv, uint64_t id) { } void parser_filter_reject(void *priv) { } void parser_filter_filter(void *priv, struct usbredirfilter_rule *rules, int rules_count) { usbredirfilter_free(rules); } void dump_data(const uint8_t *data, const int len) { int i; if (len == 0) { return; } log(" "); for (i = 0; i < len; i++) { log(" %02X", (unsigned int)data[i]); } log("\n"); } void parser_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *control_packet, uint8_t *data, int data_len) { log("%s: id=%" PRIu64 ", endpoint=%d, request=%d, requesttype=%d," " status=%d, value=%d, index=%d, length=%d\n", __func__, id, control_packet->endpoint, control_packet->request, control_packet->requesttype, control_packet->status, control_packet->value, control_packet->index, control_packet->length); dump_data(data, data_len); usbredirparser_free_packet_data(parser.get(), data); } void parser_bulk_packet(void *priv, uint64_t id, struct usb_redir_bulk_packet_header *bulk_packet, uint8_t *data, int data_len) { log("%s: id=%" PRIu64 ", endpoint=%d, status=%d, length=%d, stream_id=%d," " length_high=%d\n", __func__, id, bulk_packet->endpoint, bulk_packet->status, bulk_packet->length, bulk_packet->stream_id, bulk_packet->length_high); dump_data(data, data_len); usbredirparser_free_packet_data(parser.get(), data); } void parser_iso_packet(void *priv, uint64_t id, struct usb_redir_iso_packet_header *iso_packet, uint8_t *data, int data_len) { log("%s: id=%" PRIu64 ", endpoint=%d, status=%d, length=%d\n", __func__, id, iso_packet->endpoint, iso_packet->status, iso_packet->length); dump_data(data, data_len); usbredirparser_free_packet_data(parser.get(), data); } void parser_interrupt_packet(void *priv, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_packet, uint8_t *data, int data_len) { log("%s: id=%" PRIu64 ", endpoint=%d, status=%d, length=%d\n", __func__, id, interrupt_packet->endpoint, interrupt_packet->status, interrupt_packet->length); dump_data(data, data_len); usbredirparser_free_packet_data(parser.get(), data); } void parser_buffered_bulk_packet(void *priv, uint64_t id, struct usb_redir_buffered_bulk_packet_header *buffered_bulk_header, uint8_t *data, int data_len) { log("%s: stream_id=%d, length=%d, endpoint=%d, status=%d\n", __func__, id, buffered_bulk_header->stream_id, buffered_bulk_header->length, buffered_bulk_header->endpoint, buffered_bulk_header->status); dump_data(data, data_len); usbredirparser_free_packet_data(parser.get(), data); } void *parser_alloc_lock() { return nullptr; } void parser_lock(void *lock) { } void parser_unlock(void *lock) { } void parser_free_lock(void *lock) { } void parser_hello(void *priv, struct usb_redir_hello_header *h) { log("%s: %s\n", __func__, h->version); } void parser_device_disconnect_ack(void *priv) { } void parser_start_bulk_receiving(void *priv, uint64_t id, struct usb_redir_start_bulk_receiving_header *start_bulk_receiving) { } void parser_stop_bulk_receiving(void *priv, uint64_t id, struct usb_redir_stop_bulk_receiving_header *stop_bulk_receiving) { } void parser_bulk_receiving_status(void *priv, uint64_t id, struct usb_redir_bulk_receiving_status_header *bulk_receiving_status) { } int try_unserialize(struct usbredirparser *parser, FuzzedDataProvider *fdp) { std::vector state; size_t len = fdp->ConsumeIntegralInRange(1, 64 * 1024); state.reserve(len); if (len >= 4) { // Could also move USBREDIRPARSER_SERIALIZE_MAGIC after moving it to // a shared header. state.insert(state.end(), {'U', 'R', 'P', '1'}); len -= 4; } if (len > 0) { const std::vector payload{fdp->ConsumeBytes(len)}; state.insert(state.end(), payload.cbegin(), payload.cend()); } if (state.empty()) { return 0; } state.shrink_to_fit(); return usbredirparser_unserialize(parser, &state[0], state.size()); } int try_serialize(struct usbredirparser *parser) { uint8_t *state = nullptr; int len = 0; int ret; ret = usbredirparser_serialize(parser, &state, &len); if (ret == 0) { free(state); } return ret; } } // namespace extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::array caps = {0}; int ret; fdp = std::make_unique(data, size); parser.reset(usbredirparser_create()); if (parser == nullptr) { return 1; } parser->log_func = parser_log; parser->read_func = parser_read; parser->write_func = parser_write; parser->device_connect_func = parser_device_connect; parser->device_disconnect_func = parser_device_disconnect; parser->reset_func = parser_reset; parser->interface_info_func = parser_interface_info; parser->ep_info_func = parser_ep_info; parser->set_configuration_func = parser_set_configuration; parser->get_configuration_func = parser_get_configuration; parser->configuration_status_func = parser_configuration_status; parser->set_alt_setting_func = parser_set_alt_setting; parser->get_alt_setting_func = parser_get_alt_setting; parser->alt_setting_status_func = parser_alt_setting_status; parser->start_iso_stream_func = parser_start_iso_stream; parser->stop_iso_stream_func = parser_stop_iso_stream; parser->iso_stream_status_func = parser_iso_stream_status; parser->start_interrupt_receiving_func = parser_start_interrupt_receiving; parser->stop_interrupt_receiving_func = parser_stop_interrupt_receiving; parser->interrupt_receiving_status_func = parser_interrupt_receiving_status; parser->alloc_bulk_streams_func = parser_alloc_bulk_streams; parser->free_bulk_streams_func = parser_free_bulk_streams; parser->bulk_streams_status_func = parser_bulk_streams_status; parser->cancel_data_packet_func = parser_cancel_data_packet; parser->control_packet_func = parser_control_packet; parser->bulk_packet_func = parser_bulk_packet; parser->iso_packet_func = parser_iso_packet; parser->interrupt_packet_func = parser_interrupt_packet; parser->alloc_lock_func = parser_alloc_lock; parser->lock_func = parser_lock; parser->unlock_func = parser_unlock; parser->free_lock_func = parser_free_lock; parser->hello_func = parser_hello; parser->filter_reject_func = parser_filter_reject; parser->filter_filter_func = parser_filter_filter; parser->device_disconnect_ack_func = parser_device_disconnect_ack; parser->start_bulk_receiving_func = parser_start_bulk_receiving; parser->stop_bulk_receiving_func = parser_stop_bulk_receiving; parser->bulk_receiving_status_func = parser_bulk_receiving_status; parser->buffered_bulk_packet_func = parser_buffered_bulk_packet; for (uint32_t &cap : caps) { cap = fdp->ConsumeIntegral(); } const int init_flags = fdp->ConsumeIntegral() & (usbredirparser_fl_usb_host | usbredirparser_fl_no_hello); usbredirparser_init(parser.get(), "fuzzer", caps.data(), caps.size(), init_flags); if (fdp->ConsumeBool() && try_unserialize(parser.get(), fdp.get()) != 0) { goto out; } while (fdp->remaining_bytes() > 0 || usbredirparser_has_data_to_write(parser.get())) { if (fdp->ConsumeBool() && try_serialize(parser.get()) != 0) { goto out; } if (fdp->remaining_bytes() > 0) { ret = usbredirparser_do_read(parser.get()); switch (ret) { case usbredirparser_read_parse_error: // Keep reading break; default: log("usbredirparser_do_read failed: %d\n", ret); goto out; } if (fdp->ConsumeBool() && try_serialize(parser.get()) != 0) { goto out; } } while (usbredirparser_has_data_to_write(parser.get())) { ret = usbredirparser_do_write(parser.get()); if (ret < 0) { log("usbredirparser_do_write failed: %d\n", ret); goto out; } } } out: parser.reset(); return 0; } /* vim: set sw=4 sts=4 et : */ usbredir-usbredir-0.11.0/meson.build000066400000000000000000000050451410424124300173660ustar00rootroot00000000000000project('usbredir', 'c', 'cpp', version: '0.11.0', license: 'LGPLv2.1+', meson_version : '>= 0.53', default_options : [ 'buildtype=debugoptimized', 'warning_level=1', ]) summary_info = {'prefix': get_option('prefix')} usbredir_include_root_dir = include_directories('.') cc_flags = [ '--param=ssp-buffer-size=4', ] if host_machine.system() != 'windows' cc_flags += [ '-Wp,-D_FORTIFY_SOURCE=2', '-fstack-protector', ] endif # Check if we are building from .git git = run_command('test', '-d', '.git').returncode() == 0 git_werror = get_option('git_werror') if git_werror.enabled() or git_werror.auto() and git cc_flags += [ '-Werror' ] endif compiler = meson.get_compiler('c') supported_cc_flags = compiler.get_supported_arguments(cc_flags) add_project_arguments(supported_cc_flags, language: 'c') config = configuration_data() config.set('USBREDIR_VISIBLE', '') foreach visibility : [ '__attribute__((visibility ("default")))', '__attribute__((dllexport))', '__declspec(dllexport)', ] code = '@0@ int func() { return 123; }'.format(visibility) if compiler.compiles(code, name : 'visibility check') config.set('USBREDIR_VISIBLE', visibility) break endif endforeach # # write config.h # proj_name = meson.project_name() proj_version = meson.project_version() config_data = { 'VERSION' : proj_version, 'PACKAGE_VERSION' : proj_version, 'PACKAGE_STRING' : '@0@ @1@'.format(proj_name, proj_version), 'PACKAGE_BUGREPORT' : 'https://gitlab.freedesktop.org/spice/usbredir/issues', } foreach key, value : config_data config.set_quoted(key, value) endforeach # # check for system headers # headers = [ 'inttypes.h', 'stdint.h', 'stdlib.h', 'strings.h', 'string.h', 'sys/stat.h', 'sys/types.h', 'unistd.h', ] foreach header : headers if compiler.has_header(header) config.set('HAVE_@0@'.format(header.underscorify().to_upper()), '1') endif endforeach if host_machine.system() == 'windows' wixl_arch = 'x64' if host_machine.cpu() != 'x86_64' wixl_arch = 'x86' endif config.set('WIXL_ARCH', wixl_arch) endif configure_file(output : 'config.h', configuration : config) subdir('usbredirparser') subdir('usbredirhost') if get_option('tools').enabled() subdir('tools') endif if host_machine.system() != 'windows' subdir('usbredirserver') subdir('usbredirtestclient') if get_option('fuzzing').enabled() subdir('fuzzing') endif endif subdir('tests') subdir('data') summary(summary_info, bool_yn: true) usbredir-usbredir-0.11.0/meson_options.txt000066400000000000000000000012071410424124300206550ustar00rootroot00000000000000option('git_werror', type: 'feature', value: 'auto', description: 'use -Werror if building from GIT') option('fuzzing', type : 'feature', value : 'disabled', description : 'Enable libFuzzer integration') option('fuzzing-engine', type : 'string', value : 'standalone', description : 'Location of prebuilt fuzzing engine library or "standalone" for built-in driver') option('fuzzing-install-dir', type : 'string', description : 'Installation directory for fuzzing binaries') option('tools', type : 'feature', value : 'enabled', description : 'Build usbredir\'s tools such as usbredirect') usbredir-usbredir-0.11.0/tests/000077500000000000000000000000001410424124300163625ustar00rootroot00000000000000usbredir-usbredir-0.11.0/tests/filter.c000066400000000000000000000152161410424124300200200ustar00rootroot00000000000000/* * Copyright 2021 Red Hat, 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, see . */ #include #include #include #include #include "usbredirfilter.h" struct test { const char *name; const char *filter; const char *token_sep; const char *rule_sep; int want_retval; int want_nrules; const char *want_serialized; }; static const struct test test_cases[] = { { .name = "empty filter", .filter = "", }, { .name = "separators only", .filter = "|||", .want_serialized = "", }, { .name = "one rule", .filter = "0x03,-1,-1,-1,0", .want_nrules = 1, }, { .name = "two rules", .filter = "0x03,-1,-1,-1,0|-1,-1,-1,-1,1", .want_nrules = 2, }, { .name = "ignore trailing rule_sep", .filter = "|0x03,-1,-1,-1,0|-1,-1,-1,-1,1|", .want_serialized = "0x03,-1,-1,-1,0|-1,-1,-1,-1,1", .want_nrules = 2, }, { .name = "ignores empty rules", .filter = "0x03,-1,-1,-1,0|||-1,-1,-1,-1,1", .want_serialized = "0x03,-1,-1,-1,0|-1,-1,-1,-1,1", .want_nrules = 2, }, { .name = "several trailing rule_sep and empty rules", .filter = "||||0x03,-1,-1,-1,0|||-1,-1,-1,-1,1||||", .want_serialized = "0x03,-1,-1,-1,0|-1,-1,-1,-1,1", .want_nrules = 2, }, { .name = "change rule separator using multiple characters", .filter = "0x03,-1,-1,-1,0", .want_nrules = 1, .token_sep = ",;", .rule_sep = " \t\n", }, { .name = "mix of different separators", .filter = "\t 0x03,-1;-1;-1,0\n\n", .want_serialized = "0x03,-1,-1,-1,0", .want_nrules = 1, .token_sep = ",;", .rule_sep = " \t\n", }, { .name = "multiple rules, separators not the first character", .filter = "\n\t0x03;-1,-1,-1,0\n\n-1,-1,-1;-1;1", .want_serialized = "0x03,-1,-1,-1,0 -1,-1,-1,-1,1", .want_nrules = 2, .token_sep = ",;", .rule_sep = " \t\n", }, { .name = "upper limit on class", .filter = "0x100,-1,-1,-1,0", .want_retval = -EINVAL, }, { .name = "lower limit on class", .filter = "-2,-1,-1,-1,0", .want_retval = -EINVAL, }, { .name = "upper limit on vendor", .filter = "0x03,,0x10000-1,-1,0", .want_retval = -EINVAL, }, { .name = "lower limit on vendor", .filter = "0x03,-2,-1,-1,0", .want_retval = -EINVAL, }, { .name = "upper limit on product", .filter = "0x03,-1,0x10000-1,,0", .want_retval = -EINVAL, }, { .name = "lower limit on product", .filter = "0x03,-1,-2,-1,0", .want_retval = -EINVAL, }, { .name = "upper limit on bcd", .filter = "0x03,-1,-1,0x10000,0", .want_retval = -EINVAL, }, { .name = "lower limit on bcd", .filter = "0x03,-1,-1,-2,0", .want_retval = -EINVAL, }, { .name = "extra argument", .filter = "0x03,-1,-1,-1,0,1", .want_retval = -EINVAL, }, { .name = "missing argument", .filter = "0x03,-1,-1,-1", .want_retval = -EINVAL, }, { .name = "missing value in argument", .filter = "0x03,-1,-1,,-1", .want_retval = -EINVAL, }, { .name = "letter as value in argument (1)", .filter = "0x03,-1,-1,a,-1", .want_retval = -EINVAL, }, { .name = "number sign as value in argument (2)", .filter = "0x03,-1,-1,#,-1", .want_retval = -EINVAL, }, { .name = "space as value in argument (3)", .filter = "0x03,-1,-1, ,-1", .want_retval = -EINVAL, }, { .name = "invalid token_sep", .filter = "0x03;-1;-1;-1;0", .want_retval = -EINVAL, }, { .name = "invalid rule_sep", .filter = "0x03,-1,-1,-1,0;-1,-1,-1,-1,1", .want_retval = -EINVAL, }, { .name = "bad rule in many", .filter = "0x03,-1,-1,-1,0|3|-1,-1,-1,-1,1", .want_retval = -EINVAL, }, { .name = "empty token separator", .filter = "0x03,-1,-1,-1,0", .token_sep = "", .want_retval = -EINVAL, }, { .name = "empty rule separator", .filter = "0x03,-1,-1,-1,0", .rule_sep = "", .want_retval = -EINVAL, }, }; static void test_check(gconstpointer private) { const struct test *const data = private; int retval, count = INT_MIN; struct usbredirfilter_rule *rules = NULL; const char *token_sep = data->token_sep ? data->token_sep : ","; const char *rule_sep = data->rule_sep ? data->rule_sep : "|"; char *const quoted_filter = g_strescape(data->filter, NULL); g_test_queue_free(quoted_filter); g_test_message("Filter: %s", quoted_filter); retval = usbredirfilter_string_to_rules(data->filter, token_sep, rule_sep, &rules, &count); g_assert_cmpint(retval, ==, data->want_retval); if (retval == 0) { const char *const serialized = data->want_serialized ? data->want_serialized : data->filter; char *filter; g_assert_cmpint(count, ==, data->want_nrules); filter = usbredirfilter_rules_to_string(rules, count, token_sep, rule_sep); g_assert_nonnull(filter); g_assert_cmpstr(serialized, ==, filter); usbredirfilter_free(filter); } usbredirfilter_free(rules); } static void add_tests(const char *prefix, const struct test items[], int count) { for (int i = 0; i < count; i++) { char *name = g_strdup_printf("%s/#%d/%s", prefix, i, items[i].name); g_test_add_data_func(name, (gconstpointer)&items[i], &test_check); g_free(name); } } int main(int argc, char **argv) { setlocale(LC_ALL, ""); g_test_init(&argc, &argv, NULL); add_tests("/filter/rules", test_cases, G_N_ELEMENTS(test_cases)); return g_test_run(); } usbredir-usbredir-0.11.0/tests/meson.build000066400000000000000000000004501410424124300205230ustar00rootroot00000000000000tests = [ 'filter', ] deps = dependency('glib-2.0') foreach t: tests source = t + '.c' runtime = 'test-' + t exe = executable(runtime, [source], install: false, dependencies: [deps, usbredir_parser_lib_dep]) test(runtime, exe, timeout:10) endforeach usbredir-usbredir-0.11.0/tools/000077500000000000000000000000001410424124300163605ustar00rootroot00000000000000usbredir-usbredir-0.11.0/tools/meson.build000066400000000000000000000011071410424124300205210ustar00rootroot00000000000000usbredirect_sources = [ 'usbredirect.c', ] usbredirect_deps = [usbredir_host_lib_dep] glib_version = '>= 2.44' deps = {'glib-2.0': glib_version} if host_machine.system() == 'windows' deps += {'gio-windows-2.0': glib_version} else deps += {'gio-unix-2.0': glib_version} endif foreach dep, version : deps usbredirect_deps += dependency(dep, version : version) endforeach executable('usbredirect', sources : usbredirect_sources, c_args : '-Wno-deprecated-declarations', install : true, dependencies : usbredirect_deps) install_man('usbredirect.1') usbredir-usbredir-0.11.0/tools/usbredirect.1000066400000000000000000000024401410424124300207550ustar00rootroot00000000000000.TH USBREDIRECT "1" "February 2021" "usbredirect" "User Commands" .SH NAME usbredirect \- exporting an USB device for use from another (virtual) machine .SH SYNOPSIS .B usbredirect [\fI--device vendor:product\fR] [\fI--to addr:port\fR] [\fI--as addr:port\fR] .SH DESCRIPTION usbredirect is an usbredir client for exporting an USB device either as TCP client or server, for use from another (virtual) machine through the usbredir protocol. .PP You can specify the USB device to export by USB id in the form of \fI:\fR. .PP Notice that an instance of usbredirect can only be used to export a single USB device and it will close once the other side closes the connection. If you want to export multiple devices you can start multiple instances listening on different TCP ports. .SH AUTHOR Written by Victor Toso .SH REPORTING BUGS You can report bugs to the spice-devel mailinglist: http://lists.freedesktop.org/mailman/listinfo/spice-devel or filing an issue at: https://gitlab.freedesktop.org/spice/usbredir/issues/new .SH COPYRIGHT Copyright 2010-2021 Red Hat, Inc. License GPLv2+: GNU GPL version 2 or later . .br This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. usbredir-usbredir-0.11.0/tools/usbredirect.c000066400000000000000000000431301410424124300210400ustar00rootroot00000000000000#include "config.h" #include #include #define G_LOG_DOMAIN "usbredirect" #define G_LOG_USE_STRUCTURED #include #include #include #include #ifdef G_OS_UNIX #include #include #include #endif #ifdef G_OS_WIN32 #include #include #include #endif struct redirect { struct { int vendor; int product; } device; bool is_client; bool keepalive; char *addr; int port; int verbosity; struct usbredirhost *usbredirhost; GSocketConnection *connection; GThread *event_thread; int event_thread_run; int watch_server_id; GMainLoop *main_loop; }; static bool parse_opt_device(const char *device, int *vendor, int *product) { if (!device) { g_warning("No device to redirect. For testing only\n"); return true; } if (g_strrstr(device, "-") != NULL) { /* Get vendor and product by bus and address number */ char **usbid = g_strsplit(device, "-", 2); if (usbid == NULL || usbid[0] == NULL || usbid[1] == NULL || usbid[2] != NULL) { g_strfreev(usbid); return false; } gint64 bus = g_ascii_strtoll(usbid[0], NULL, 10); gint64 addr = g_ascii_strtoll(usbid[1], NULL, 10); libusb_device **list = NULL; ssize_t i, n; n = libusb_get_device_list(NULL, &list); for (i = 0; i < n; i++) { if (libusb_get_bus_number(list[i]) == bus && libusb_get_device_address(list[i]) == addr) { break; } } if (i == n) { libusb_free_device_list(list, true); return false; } struct libusb_device_descriptor desc; libusb_get_device_descriptor(list[i], &desc); *vendor = desc.idVendor; *product = desc.idProduct; libusb_free_device_list(list, true); return true; } char **usbid = g_strsplit(device, ":", 2); if (usbid == NULL || usbid[0] == NULL || usbid[1] == NULL || usbid[2] != NULL) { g_strfreev(usbid); return false; } *vendor = g_ascii_strtoll(usbid[0], NULL, 16); *product = g_ascii_strtoll(usbid[1], NULL, 16); g_strfreev(usbid); if (*vendor <= 0 || *vendor > 0xffff || *product < 0 || *product > 0xffff) { g_printerr("Bad vendor:product values %04x:%04x", *vendor, *product); return false; } return true; } static bool parse_opt_uri(const char *uri, char **adr, int *port) { if (uri == NULL) { return false; } char **parts = g_strsplit(uri, ":", 2); if (parts == NULL || parts[0] == NULL || parts[1] == NULL || parts[2] != NULL) { g_printerr("Failed to parse '%s' - expected simplified uri scheme: host:port", uri); g_strfreev(parts); return false; } *adr = g_strdup(parts[0]); *port = g_ascii_strtoll(parts[1], NULL, 10); g_strfreev(parts); return true; } static struct redirect * parse_opts(int *argc, char ***argv) { char *device = NULL; char *remoteaddr = NULL; char *localaddr = NULL; gboolean keepalive = FALSE; gint verbosity = 0; /* none */ struct redirect *self = NULL; GOptionEntry entries[] = { { "device", 0, 0, G_OPTION_ARG_STRING, &device, "Local USB device to be redirected", NULL }, { "to", 0, 0, G_OPTION_ARG_STRING, &remoteaddr, "Client URI to connect to", NULL }, { "as", 0, 0, G_OPTION_ARG_STRING, &localaddr, "Server URI to be run", NULL }, { "keepalive", 'k', 0, G_OPTION_ARG_NONE, &keepalive, "If we should set SO_KEEPALIVE flag on underlying socket", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_INT, &verbosity, "Set log level between 1-5 where 5 being the most verbose", NULL }, { NULL } }; GError *err = NULL; GOptionContext *ctx = g_option_context_new(NULL); g_option_context_add_main_entries(ctx, entries, NULL); if (!g_option_context_parse(ctx, argc, argv, &err)) { g_printerr("Could not parse arguments: %s\n", err->message); g_printerr("%s", g_option_context_get_help(ctx, TRUE, NULL)); g_clear_error(&err); goto end; } /* check options */ if (!remoteaddr && !localaddr) { g_printerr("%s need to act either as client (-to) or as server (-as)\n", *argv[0]); g_printerr("%s", g_option_context_get_help(ctx, TRUE, NULL)); goto end; } self = g_new0(struct redirect, 1); if (!parse_opt_device(device, &self->device.vendor, &self->device.product)) { g_printerr("Failed to parse device: '%s' - expected: vendor:product or busnum-devnum\n", device); g_clear_pointer(&self, g_free); goto end; } if (parse_opt_uri(remoteaddr, &self->addr, &self->port)) { self->is_client = true; } else if (!parse_opt_uri(localaddr, &self->addr, &self->port)) { g_printerr("Failed to parse uri '%s' - expected: addr:port", remoteaddr ? remoteaddr : localaddr); g_clear_pointer(&self, g_free); goto end; } self->keepalive = keepalive; self->verbosity = verbosity; g_debug("options: keepalive=%s, verbosity=%d", self->keepalive ? "ON":"OFF", self->verbosity); end: if (self) { g_debug("Device: '%04x:%04x', %s addr: '%s', port: %d\n", self->device.vendor, self->device.product, self->is_client ? "client connect" : "server at", self->addr, self->port); } g_free(localaddr); g_free(remoteaddr); g_free(device); g_option_context_free(ctx); return self; } static gpointer thread_handle_libusb_events(gpointer user_data) { struct redirect *self = (struct redirect *) user_data; int res = 0; const char *desc = ""; while (g_atomic_int_get(&self->event_thread_run)) { res = libusb_handle_events(NULL); if (res && res != LIBUSB_ERROR_INTERRUPTED) { desc = libusb_strerror(res); g_warning("Error handling USB events: %s [%i]", desc, res); break; } } if (self->event_thread_run) { g_debug("%s: the thread aborted, %s(%d)", __FUNCTION__, desc, res); } return NULL; } #if LIBUSBX_API_VERSION >= 0x01000107 static void debug_libusb_cb(libusb_context *ctx, enum libusb_log_level level, const char *msg) { GLogLevelFlags glog_level; switch(level) { case LIBUSB_LOG_LEVEL_ERROR: glog_level = G_LOG_LEVEL_ERROR; break; case LIBUSB_LOG_LEVEL_WARNING: glog_level = G_LOG_LEVEL_WARNING; break; case LIBUSB_LOG_LEVEL_INFO: glog_level = G_LOG_LEVEL_INFO; break; case LIBUSB_LOG_LEVEL_DEBUG: glog_level = G_LOG_LEVEL_DEBUG; break; default: g_warn_if_reached(); return; } /* Do not print the '\n' line feed */ size_t len = strlen(msg); len = (msg[len - 1] == '\n') ? len - 1 : len; g_log_structured(G_LOG_DOMAIN, glog_level, "MESSAGE", "%.*s", len - 1, msg); } #endif static void usbredir_log_cb(void *priv, int level, const char *msg) { GLogLevelFlags glog_level; switch(level) { case usbredirparser_error: glog_level = G_LOG_LEVEL_ERROR; break; case usbredirparser_warning: glog_level = G_LOG_LEVEL_WARNING; break; case usbredirparser_info: glog_level = G_LOG_LEVEL_INFO; break; case usbredirparser_debug: case usbredirparser_debug_data: glog_level = G_LOG_LEVEL_DEBUG; break; default: g_warn_if_reached(); return; } g_log_structured(G_LOG_DOMAIN, glog_level, "MESSAGE", msg); } static int usbredir_read_cb(void *priv, uint8_t *data, int count) { struct redirect *self = (struct redirect *) priv; GIOStream *iostream = G_IO_STREAM(self->connection); GError *err = NULL; GPollableInputStream *instream = G_POLLABLE_INPUT_STREAM(g_io_stream_get_input_stream(iostream)); gssize nbytes = g_pollable_input_stream_read_nonblocking(instream, data, count, NULL, &err); if (nbytes <= 0) { if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { /* Try again later */ nbytes = 0; } else { if (err != NULL) { g_warning("Failure at %s: %s", __func__, err->message); } g_main_loop_quit(self->main_loop); } g_clear_error(&err); } return nbytes; } static int usbredir_write_cb(void *priv, uint8_t *data, int count) { struct redirect *self = (struct redirect *) priv; GIOStream *iostream = G_IO_STREAM(self->connection); GError *err = NULL; GPollableOutputStream *outstream = G_POLLABLE_OUTPUT_STREAM(g_io_stream_get_output_stream(iostream)); gssize nbytes = g_pollable_output_stream_write_nonblocking(outstream, data, count, NULL, &err); if (nbytes <= 0) { if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { /* Try again later */ nbytes = 0; } else { if (err != NULL) { g_warning("Failure at %s: %s", __func__, err->message); } g_main_loop_quit(self->main_loop); } g_clear_error(&err); } return nbytes; } static void usbredir_write_flush_cb(void *user_data) { struct redirect *self = (struct redirect *) user_data; if (!self || !self->usbredirhost) { return; } int ret = usbredirhost_write_guest_data(self->usbredirhost); if (ret < 0) { g_critical("%s: Failed to write to guest", __func__); g_main_loop_quit(self->main_loop); } } static void *usbredir_alloc_lock(void) { GMutex *mutex; mutex = g_new0(GMutex, 1); g_mutex_init(mutex); return mutex; } static void usbredir_free_lock(void *user_data) { GMutex *mutex = user_data; g_mutex_clear(mutex); g_free(mutex); } static void usbredir_lock_lock(void *user_data) { GMutex *mutex = user_data; g_mutex_lock(mutex); } static void usbredir_unlock_lock(void *user_data) { GMutex *mutex = user_data; g_mutex_unlock(mutex); } static gboolean connection_handle_io_cb(GIOChannel *source, GIOCondition condition, gpointer user_data) { struct redirect *self = (struct redirect *) user_data; if (condition & G_IO_ERR || condition & G_IO_HUP) { g_warning("Connection: err=%d, hup=%d - exiting", (condition & G_IO_ERR), (condition & G_IO_HUP)); goto end; } if (condition & G_IO_IN) { int ret = usbredirhost_read_guest_data(self->usbredirhost); if (ret < 0) { g_critical("%s: Failed to read guest", __func__); goto end; } } if (condition & G_IO_OUT) { int ret = usbredirhost_write_guest_data(self->usbredirhost); if (ret < 0) { g_critical("%s: Failed to write to guest", __func__); goto end; } } return G_SOURCE_CONTINUE; end: g_main_loop_quit(self->main_loop); return G_SOURCE_REMOVE; } #ifdef G_OS_UNIX static gboolean signal_handler(gpointer user_data) { struct redirect *self = (struct redirect *) user_data; g_main_loop_quit(self->main_loop); return G_SOURCE_REMOVE; } #endif static gboolean connection_incoming_cb(GSocketService *service, GSocketConnection *client_connection, GObject *source_object, gpointer user_data) { struct redirect *self = (struct redirect *) user_data; self->connection = g_object_ref(client_connection); /* Add a GSource watch to handle polling for us and handle IO in the callback */ GSocket *connection_socket = g_socket_connection_get_socket(self->connection); g_socket_set_keepalive(connection_socket, self->keepalive); int socket_fd = g_socket_get_fd(connection_socket); GIOChannel *io_channel = g_io_channel_unix_new(socket_fd); self->watch_server_id = g_io_add_watch(io_channel, G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR, connection_handle_io_cb, self); return G_SOURCE_REMOVE; } int main(int argc, char *argv[]) { GError *err = NULL; if (libusb_init(NULL)) { g_warning("Could not init libusb\n"); goto err_init; } struct redirect *self = parse_opts(&argc, &argv); if (!self) { /* specific issues logged in parse_opts() */ return 1; } #if LIBUSBX_API_VERSION >= 0x01000107 /* This was introduced in 1.0.23 */ libusb_set_log_cb(NULL, debug_libusb_cb, LIBUSB_LOG_CB_GLOBAL); #endif #ifdef G_OS_WIN32 /* WinUSB is the default by backwards compatibility so this is needed to * switch to USBDk backend. */ libusb_set_option(NULL, LIBUSB_OPTION_USE_USBDK); #endif #ifdef G_OS_UNIX g_unix_signal_add(SIGINT, signal_handler, self); g_unix_signal_add(SIGHUP, signal_handler, self); g_unix_signal_add(SIGTERM, signal_handler, self); #endif /* This is binary is not meant to support plugins so it is safe to pass * NULL as libusb_context here and all subsequent calls */ libusb_device_handle *device_handle = libusb_open_device_with_vid_pid(NULL, self->device.vendor, self->device.product); if (!device_handle) { g_printerr("Failed to open device!\n"); goto err_init; } /* As per doc below, we are not using hotplug so we must first call * libusb_open() and then we can start the event thread. * * http://libusb.sourceforge.net/api-1.0/group__libusb__asyncio.html#eventthread * * The event thread is a must for Windows while on Unix we would ge okay * getting the fds and polling oursevelves. */ g_atomic_int_set(&self->event_thread_run, TRUE); self->event_thread = g_thread_try_new("usbredirect-libusb-event-thread", thread_handle_libusb_events, self, &err); if (!self->event_thread) { g_warning("Error starting event thread: %s", err->message); libusb_close(device_handle); goto err_init; } self->usbredirhost = usbredirhost_open_full(NULL, device_handle, usbredir_log_cb, usbredir_read_cb, usbredir_write_cb, usbredir_write_flush_cb, usbredir_alloc_lock, usbredir_lock_lock, usbredir_unlock_lock, usbredir_free_lock, self, PACKAGE_STRING, self->verbosity, usbredirhost_fl_write_cb_owns_buffer); if (!self->usbredirhost) { g_warning("Error starting usbredirhost"); goto err_init; } /* Only allow libusb logging if log verbosity is uredirparser_debug_data * (or higher), otherwise we disable it here while keeping usbredir's logs enable. */ if (self->verbosity < usbredirparser_debug_data) { int ret = libusb_set_option(NULL, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_NONE); if (ret != LIBUSB_SUCCESS) { g_warning("error disabling libusb log level: %s", libusb_error_name(ret)); goto end; } } if (self->is_client) { /* Connect to a remote sever using usbredir to redirect the usb device */ GSocketClient *client = g_socket_client_new(); self->connection = g_socket_client_connect_to_host(client, self->addr, self->port, /* your port goes here */ NULL, &err); g_object_unref(client); if (err != NULL) { g_warning("Failed to connect to the server: %s", err->message); goto end; } GSocket *connection_socket = g_socket_connection_get_socket(self->connection); g_socket_set_keepalive(connection_socket, self->keepalive); int socket_fd = g_socket_get_fd(connection_socket); GIOChannel *io_channel = #ifdef G_OS_UNIX g_io_channel_unix_new(socket_fd); #else g_io_channel_win32_new_socket(socket_fd); #endif self->watch_server_id = g_io_add_watch(io_channel, G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR, connection_handle_io_cb, self); } else { GSocketService *socket_service; socket_service = g_socket_service_new (); GInetAddress *iaddr = g_inet_address_new_loopback(G_SOCKET_FAMILY_IPV4); GSocketAddress *saddr = g_inet_socket_address_new(iaddr, self->port); g_object_unref(iaddr); g_socket_listener_add_address(G_SOCKET_LISTENER (socket_service), saddr, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_TCP, NULL, NULL, &err); if (err != NULL) { g_warning("Failed to run as TCP server: %s", err->message); goto end; } g_signal_connect(socket_service, "incoming", G_CALLBACK (connection_incoming_cb), self); } self->main_loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(self->main_loop); g_atomic_int_set(&self->event_thread_run, FALSE); if (self->event_thread) { libusb_interrupt_event_handler(NULL); g_thread_join(self->event_thread); self->event_thread = NULL; } end: g_clear_pointer(&self->usbredirhost, usbredirhost_close); g_clear_pointer(&self->addr, g_free); g_clear_object(&self->connection); g_free(self); err_init: libusb_exit(NULL); if (err != NULL) { g_error_free(err); return 1; } return 0; } usbredir-usbredir-0.11.0/usbredirhost/000077500000000000000000000000001410424124300177355ustar00rootroot00000000000000usbredir-usbredir-0.11.0/usbredirhost/meson.build000066400000000000000000000036421410424124300221040ustar00rootroot00000000000000# so verison, see: # http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html usbredir_host_current = 1 usbredir_host_revision = 2 usbredir_host_age = 0 usbredir_host_so_version = '@0@.@1@.@2@'.format( usbredir_host_current - usbredir_host_age, usbredir_host_age, usbredir_host_revision) summary_info += {'libusbredirhost.so version': usbredir_host_so_version} usbredir_host_sources = [ 'usbredirhost.c', 'usbredirhost.h', ] usbredir_host_map_file = meson.current_source_dir() / 'usbredirhost.map' usbredir_host_link_args = compiler.get_supported_link_arguments([ '-Wl,--version-script=@0@'.format(usbredir_host_map_file), '-Wl,--no-undefined', '-Wl,-dead_strip', ]) libusb = 'libusb-1.0' libusb_version = '>= 1.0.9' libusb_required_version = '@0@ @1@'.format(libusb, libusb_version) libusb = dependency(libusb, version: libusb_version) usbredir_host_include_directories = [ include_directories('.'), usbredir_include_root_dir, ] usbredir_host_lib = library('usbredirhost', usbredir_host_sources, version : usbredir_host_so_version, install : true, include_directories: usbredir_host_include_directories, link_args : [usbredir_host_link_args], link_depends : usbredir_host_map_file, dependencies : [libusb, usbredir_parser_lib_dep], gnu_symbol_visibility : 'hidden') usbredir_host_lib_dep = declare_dependency( link_with: usbredir_host_lib, include_directories: usbredir_host_include_directories, dependencies: [libusb, usbredir_parser_lib_dep]) headers = [ 'usbredirhost.h', ] install_headers(headers) # libusbredirhost.pc pkgconfig = import('pkgconfig') pkgconfig.generate(usbredir_host_lib, name : 'libusbredirhost', description : 'usbredirhost library', requires : [libusb_required_version, 'libusbredirparser-0.5'], variables : 'exec_prefix=${prefix}') usbredir-usbredir-0.11.0/usbredirhost/usbredirhost.c000066400000000000000000002614121410424124300226240ustar00rootroot00000000000000/* usbredirhost.c usb network redirection usb host code. Copyright 2010-2012 Red Hat, Inc. Red Hat Authors: Hans de Goede This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include "usbredirhost.h" #define MAX_ENDPOINTS 32 #define MAX_INTERFACES 32 /* Max 32 endpoints and thus interfaces */ #define CTRL_TIMEOUT 5000 /* USB specifies a 5 second max timeout */ #define BULK_TIMEOUT 0 /* No timeout for bulk transfers */ #define ISO_TIMEOUT 1000 #define INTERRUPT_TIMEOUT 0 /* No timeout for interrupt transfers */ #define MAX_TRANSFER_COUNT 16 #define MAX_PACKETS_PER_TRANSFER 32 #define INTERRUPT_TRANSFER_COUNT 5 /* Special packet_idx value indicating a submitted transfer */ #define SUBMITTED_IDX -1 /* quirk flags */ #define QUIRK_DO_NOT_RESET 0x01 /* Macros to go from an endpoint address to an index for our ep array */ #define EP2I(ep_address) (((ep_address & 0x80) >> 3) | (ep_address & 0x0f)) #define I2EP(i) (((i & 0x10) << 3) | (i & 0x0f)) /* Locking convenience macros */ #define LOCK(host) \ do { \ if ((host)->lock) \ (host)->parser->lock_func((host)->lock); \ } while (0) #define UNLOCK(host) \ do { \ if ((host)->lock) \ (host)->parser->unlock_func((host)->lock); \ } while (0) #define FLUSH(host) \ do { \ if ((host)->flush_writes_func) \ (host)->flush_writes_func((host)->func_priv); \ } while (0) #define CLAMP(val, min, max) \ ((val) < (min) ? (min) : ((val) > (max) ? (max) : (val))) struct usbredirtransfer { struct usbredirhost *host; /* Back pointer to the the redirhost */ struct libusb_transfer *transfer; /* Back pointer to the libusb transfer */ uint64_t id; uint8_t cancelled; int packet_idx; union { struct usb_redir_control_packet_header control_packet; struct usb_redir_bulk_packet_header bulk_packet; struct usb_redir_iso_packet_header iso_packet; struct usb_redir_interrupt_packet_header interrupt_packet; }; struct usbredirtransfer *next; struct usbredirtransfer *prev; }; struct usbredirhost_ep { uint8_t type; uint8_t interval; uint8_t interface; uint8_t warn_on_drop; uint8_t stream_started; uint8_t pkts_per_transfer; uint8_t transfer_count; int out_idx; int drop_packets; int max_packetsize; unsigned int max_streams; struct usbredirtransfer *transfer[MAX_TRANSFER_COUNT]; }; struct usbredirhost { struct usbredirparser *parser; void *lock; void *disconnect_lock; usbredirparser_log log_func; usbredirparser_read read_func; usbredirparser_write write_func; usbredirhost_flush_writes flush_writes_func; usbredirhost_buffered_output_size buffered_output_size_func; void *func_priv; int verbose; libusb_context *ctx; libusb_device *dev; libusb_device_handle *handle; struct libusb_device_descriptor desc; struct libusb_config_descriptor *config; int quirks; int restore_config; int claimed; int reset; int disconnected; int read_status; int cancels_pending; int wait_disconnect; int connect_pending; struct usbredirhost_ep endpoint[MAX_ENDPOINTS]; uint8_t alt_setting[MAX_INTERFACES]; struct usbredirtransfer transfers_head; struct usbredirfilter_rule *filter_rules; int filter_rules_count; struct { uint64_t higher; uint64_t lower; bool dropping; } iso_threshold; }; struct usbredirhost_dev_ids { int vendor_id; int product_id; }; static const struct usbredirhost_dev_ids usbredirhost_reset_blacklist[] = { { 0x1210, 0x001c }, { 0x2798, 0x0001 }, { -1, -1 } /* Terminating Entry */ }; static void #if defined __MINGW_PRINTF_FORMAT __attribute__((format(__MINGW_PRINTF_FORMAT, 3, 4))) #elif defined __GNUC__ __attribute__((format(printf, 3, 4))) #endif va_log(struct usbredirhost *host, int level, const char *fmt, ...) { char buf[512]; va_list ap; int n; if (level > host->verbose) { return; } n = sprintf(buf, "usbredirhost: "); va_start(ap, fmt); vsnprintf(buf + n, sizeof(buf) - n, fmt, ap); va_end(ap); host->log_func(host->func_priv, level, buf); } #ifdef ERROR /* defined on WIN32 */ #undef ERROR #endif #define ERROR(...) va_log(host, usbredirparser_error, __VA_ARGS__) #define WARNING(...) va_log(host, usbredirparser_warning, __VA_ARGS__) #define INFO(...) va_log(host, usbredirparser_info, __VA_ARGS__) #define DEBUG(...) va_log(host, usbredirparser_debug, __VA_ARGS__) static void usbredirhost_hello(void *priv, struct usb_redir_hello_header *h); static void usbredirhost_reset(void *priv); static void usbredirhost_set_configuration(void *priv, uint64_t id, struct usb_redir_set_configuration_header *set_configuration); static void usbredirhost_get_configuration(void *priv, uint64_t id); static void usbredirhost_set_alt_setting(void *priv, uint64_t id, struct usb_redir_set_alt_setting_header *set_alt_setting); static void usbredirhost_get_alt_setting(void *priv, uint64_t id, struct usb_redir_get_alt_setting_header *get_alt_setting); static void usbredirhost_start_iso_stream(void *priv, uint64_t id, struct usb_redir_start_iso_stream_header *start_iso_stream); static void usbredirhost_stop_iso_stream(void *priv, uint64_t id, struct usb_redir_stop_iso_stream_header *stop_iso_stream); static void usbredirhost_start_interrupt_receiving(void *priv, uint64_t id, struct usb_redir_start_interrupt_receiving_header *start_interrupt_receiving); static void usbredirhost_stop_interrupt_receiving(void *priv, uint64_t id, struct usb_redir_stop_interrupt_receiving_header *stop_interrupt_receiving); static void usbredirhost_alloc_bulk_streams(void *priv, uint64_t id, struct usb_redir_alloc_bulk_streams_header *alloc_bulk_streams); static void usbredirhost_free_bulk_streams(void *priv, uint64_t id, struct usb_redir_free_bulk_streams_header *free_bulk_streams); static void usbredirhost_cancel_data_packet(void *priv, uint64_t id); static void usbredirhost_filter_reject(void *priv); static void usbredirhost_filter_filter(void *priv, struct usbredirfilter_rule *rules, int rules_count); static void usbredirhost_device_disconnect_ack(void *priv); static void usbredirhost_start_bulk_receiving(void *priv, uint64_t id, struct usb_redir_start_bulk_receiving_header *start_bulk_receiving); static void usbredirhost_stop_bulk_receiving(void *priv, uint64_t id, struct usb_redir_stop_bulk_receiving_header *stop_bulk_receiving); static void usbredirhost_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *control_packet, uint8_t *data, int data_len); static void usbredirhost_bulk_packet(void *priv, uint64_t id, struct usb_redir_bulk_packet_header *bulk_packet, uint8_t *data, int data_len); static void usbredirhost_iso_packet(void *priv, uint64_t id, struct usb_redir_iso_packet_header *iso_packet, uint8_t *data, int data_len); static void usbredirhost_interrupt_packet(void *priv, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_packet, uint8_t *data, int data_len); static void LIBUSB_CALL usbredirhost_iso_packet_complete( struct libusb_transfer *libusb_transfer); static void LIBUSB_CALL usbredirhost_buffered_packet_complete( struct libusb_transfer *libusb_transfer); static int usbredirhost_cancel_pending_urbs(struct usbredirhost *host, int notify_guest); static void usbredirhost_wait_for_cancel_completion(struct usbredirhost *host); static void usbredirhost_clear_device(struct usbredirhost *host); static void usbredirhost_log(void *priv, int level, const char *msg) { struct usbredirhost *host = priv; host->log_func(host->func_priv, level, msg); } static int usbredirhost_read(void *priv, uint8_t *data, int count) { struct usbredirhost *host = priv; if (host->read_status) { int ret = host->read_status; host->read_status = 0; return ret; } return host->read_func(host->func_priv, data, count); } static int usbredirhost_write(void *priv, uint8_t *data, int count) { struct usbredirhost *host = priv; return host->write_func(host->func_priv, data, count); } /* Can be called both from parser read callbacks as well as from libusb packet completion callbacks */ static void usbredirhost_handle_disconnect(struct usbredirhost *host) { /* Disconnect uses its own lock to avoid needing nesting capable locks */ if (host->disconnect_lock) { host->parser->lock_func(host->disconnect_lock); } if (!host->disconnected) { INFO("device disconnected"); usbredirparser_send_device_disconnect(host->parser); if (usbredirparser_peer_has_cap(host->parser, usb_redir_cap_device_disconnect_ack)) host->wait_disconnect = 1; host->disconnected = 1; } if (host->disconnect_lock) { host->parser->unlock_func(host->disconnect_lock); } } /* One function to convert either a transfer status code, or a libusb error code to a usb_redir status. We handle both in one conversion function so that we can pass error codes as status codes to the completion handler in case of submission error (the codes don't overlap), using the completion handler to report back the status and cleanup as it would on completion of a successfully submitted transfer. */ static int libusb_status_or_error_to_redir_status(struct usbredirhost *host, int status) { switch (status) { case LIBUSB_TRANSFER_COMPLETED: return usb_redir_success; case LIBUSB_TRANSFER_ERROR: return usb_redir_ioerror; case LIBUSB_TRANSFER_TIMED_OUT: return usb_redir_timeout; case LIBUSB_TRANSFER_CANCELLED: return usb_redir_cancelled; case LIBUSB_TRANSFER_STALL: return usb_redir_stall; case LIBUSB_TRANSFER_NO_DEVICE: usbredirhost_handle_disconnect(host); return usb_redir_ioerror; case LIBUSB_TRANSFER_OVERFLOW: return usb_redir_babble; case LIBUSB_ERROR_INVALID_PARAM: return usb_redir_inval; case LIBUSB_ERROR_NO_DEVICE: usbredirhost_handle_disconnect(host); return usb_redir_ioerror; case LIBUSB_ERROR_TIMEOUT: return usb_redir_timeout; default: return usb_redir_ioerror; } } static void usbredirhost_set_max_packetsize(struct usbredirhost *host, uint8_t ep, uint16_t wMaxPacketSize) { int maxp, mult = 1; maxp = wMaxPacketSize & 0x7ff; if (libusb_get_device_speed(host->dev) == LIBUSB_SPEED_HIGH && host->endpoint[EP2I(ep)].type == usb_redir_type_iso) { switch ((wMaxPacketSize >> 11) & 3) { case 1: mult = 2; break; case 2: mult = 3; break; default: mult = 1; break; } } host->endpoint[EP2I(ep)].max_packetsize = maxp * mult; } static void usbredirhost_set_max_streams(struct usbredirhost *host, const struct libusb_endpoint_descriptor *endp) { #if LIBUSBX_API_VERSION >= 0x01000102 struct libusb_ss_endpoint_companion_descriptor *endp_ss_comp; int max_streams, i = EP2I(endp->bEndpointAddress); host->endpoint[i].max_streams = 0; if (host->endpoint[i].type == usb_redir_type_bulk && libusb_get_ss_endpoint_companion_descriptor(host->ctx, endp, &endp_ss_comp) == LIBUSB_SUCCESS) { max_streams = endp_ss_comp->bmAttributes & 0x1f; if (max_streams) host->endpoint[i].max_streams = 1 << max_streams; libusb_free_ss_endpoint_companion_descriptor(endp_ss_comp); } #endif } /* Called from open/close and parser read callbacks */ static void usbredirhost_send_interface_n_ep_info(struct usbredirhost *host) { int i; const struct libusb_interface_descriptor *intf_desc; struct usb_redir_ep_info_header ep_info; struct usb_redir_interface_info_header interface_info = { 0, }; if (host->config) interface_info.interface_count = host->config->bNumInterfaces; for (i = 0; i < interface_info.interface_count; i++) { intf_desc = &host->config->interface[i].altsetting[host->alt_setting[i]]; interface_info.interface[i] = intf_desc->bInterfaceNumber; interface_info.interface_class[i] = intf_desc->bInterfaceClass; interface_info.interface_subclass[i] = intf_desc->bInterfaceSubClass; interface_info.interface_protocol[i] = intf_desc->bInterfaceProtocol; } usbredirparser_send_interface_info(host->parser, &interface_info); for (i = 0; i < MAX_ENDPOINTS; i++) { ep_info.type[i] = host->endpoint[i].type; ep_info.interval[i] = host->endpoint[i].interval; ep_info.interface[i] = host->endpoint[i].interface; ep_info.max_packet_size[i] = host->endpoint[i].max_packetsize; ep_info.max_streams[i] = host->endpoint[i].max_streams; } usbredirparser_send_ep_info(host->parser, &ep_info); } /* Called from open/close and parser read callbacks */ static void usbredirhost_send_device_connect(struct usbredirhost *host) { struct usb_redir_device_connect_header device_connect; enum libusb_speed speed; if (!host->disconnected) { ERROR("internal error sending device_connect but already connected"); return; } if (!usbredirparser_have_peer_caps(host->parser) || host->wait_disconnect) { host->connect_pending = 1; return; } speed = libusb_get_device_speed(host->dev); switch (speed) { case LIBUSB_SPEED_LOW: device_connect.speed = usb_redir_speed_low; break; case LIBUSB_SPEED_FULL: device_connect.speed = usb_redir_speed_full; break; case LIBUSB_SPEED_HIGH: device_connect.speed = usb_redir_speed_high; break; case LIBUSB_SPEED_SUPER: device_connect.speed = usb_redir_speed_super; break; default: device_connect.speed = usb_redir_speed_unknown; } device_connect.device_class = host->desc.bDeviceClass; device_connect.device_subclass = host->desc.bDeviceSubClass; device_connect.device_protocol = host->desc.bDeviceProtocol; device_connect.vendor_id = host->desc.idVendor; device_connect.product_id = host->desc.idProduct; device_connect.device_version_bcd = host->desc.bcdDevice; usbredirhost_send_interface_n_ep_info(host); usbredirparser_send_device_connect(host->parser, &device_connect); host->connect_pending = 0; host->disconnected = 0; /* The guest may now use the device */ FLUSH(host); } /* Called from open/close and parser read callbacks */ static void usbredirhost_parse_interface(struct usbredirhost *host, int i) { int j; const struct libusb_interface_descriptor *intf_desc; uint8_t ep_address; intf_desc = &host->config->interface[i].altsetting[host->alt_setting[i]]; for (j = 0; j < intf_desc->bNumEndpoints; j++) { ep_address = intf_desc->endpoint[j].bEndpointAddress; host->endpoint[EP2I(ep_address)].type = intf_desc->endpoint[j].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK; host->endpoint[EP2I(ep_address)].interval = intf_desc->endpoint[j].bInterval; host->endpoint[EP2I(ep_address)].interface = intf_desc->bInterfaceNumber; usbredirhost_set_max_packetsize(host, ep_address, intf_desc->endpoint[j].wMaxPacketSize); usbredirhost_set_max_streams(host, &intf_desc->endpoint[j]); host->endpoint[EP2I(ep_address)].warn_on_drop = 1; } } static void usbredirhost_parse_config(struct usbredirhost *host) { int i; for (i = 0; i < MAX_ENDPOINTS; i++) { if ((i & 0x0f) == 0) { host->endpoint[i].type = usb_redir_type_control; } else { host->endpoint[i].type = usb_redir_type_invalid; } host->endpoint[i].interval = 0; host->endpoint[i].interface = 0; host->endpoint[i].max_packetsize = 0; host->endpoint[i].max_streams = 0; } for (i = 0; host->config && i < host->config->bNumInterfaces; i++) { usbredirhost_parse_interface(host, i); } } /* Called from open/close and parser read callbacks */ static int usbredirhost_claim(struct usbredirhost *host, int initial_claim) { int i, n, r; if (host->config) { libusb_free_config_descriptor(host->config); host->config = NULL; } r = libusb_get_device_descriptor(host->dev, &host->desc); if (r < 0) { ERROR("could not get device descriptor: %s", libusb_error_name(r)); return libusb_status_or_error_to_redir_status(host, r); } r = libusb_get_active_config_descriptor(host->dev, &host->config); if (r < 0 && r != LIBUSB_ERROR_NOT_FOUND) { ERROR("could not get descriptors for active configuration: %s", libusb_error_name(r)); return libusb_status_or_error_to_redir_status(host, r); } if (host->config && host->config->bNumInterfaces > MAX_INTERFACES) { ERROR("usb decriptor has too much intefaces (%d > %d)", (int)host->config->bNumInterfaces, MAX_INTERFACES); return usb_redir_ioerror; } if (initial_claim) { if (host->config) host->restore_config = host->config->bConfigurationValue; else host->restore_config = -1; /* unconfigured */ /* If the device is unconfigured and has only 1 config, we assume this is the result of the user doing "safely remove hardware", and we try to reset the device configuration to this config when we release the device, so that it becomes usable again. */ if (host->restore_config == -1 && host->desc.bNumConfigurations == 1) { struct libusb_config_descriptor *config; r = libusb_get_config_descriptor(host->dev, 0, &config); if (r == 0) { host->restore_config = config->bConfigurationValue; libusb_free_config_descriptor(config); } } } /* All interfaces begin at alt setting 0 when (re)claimed */ memset(host->alt_setting, 0, MAX_INTERFACES); host->claimed = 1; #if LIBUSBX_API_VERSION >= 0x01000102 libusb_set_auto_detach_kernel_driver(host->handle, 1); #endif for (i = 0; host->config && i < host->config->bNumInterfaces; i++) { n = host->config->interface[i].altsetting[0].bInterfaceNumber; #if LIBUSBX_API_VERSION < 0x01000102 r = libusb_detach_kernel_driver(host->handle, n); if (r < 0 && r != LIBUSB_ERROR_NOT_FOUND && r != LIBUSB_ERROR_NOT_SUPPORTED) { ERROR("could not detach driver from interface %d (configuration %d): %s", n, host->config->bConfigurationValue, libusb_error_name(r)); return libusb_status_or_error_to_redir_status(host, r); } #endif r = libusb_claim_interface(host->handle, n); if (r < 0) { if (r == LIBUSB_ERROR_BUSY) ERROR("Device is in use by another application"); else ERROR("could not claim interface %d (configuration %d): %s", n, host->config->bConfigurationValue, libusb_error_name(r)); return libusb_status_or_error_to_redir_status(host, r); } } usbredirhost_parse_config(host); return usb_redir_success; } /* Called from open/close and parser read callbacks */ static void usbredirhost_release(struct usbredirhost *host, int attach_drivers) { int i, n, r, current_config = -1; if (!host->claimed) return; #if LIBUSBX_API_VERSION >= 0x01000102 /* We want to always do the attach ourselves because: 1) For compound interfaces such as usb-audio we must first release all interfaces before we can attach the driver; 2) When releasing interfaces before calling libusb_set_configuration, we don't want the kernel driver to get attached (our attach_drivers parameter is 0 in this case). */ libusb_set_auto_detach_kernel_driver(host->handle, 0); #endif for (i = 0; host->config && i < host->config->bNumInterfaces; i++) { n = host->config->interface[i].altsetting[0].bInterfaceNumber; r = libusb_release_interface(host->handle, n); if (r < 0 && r != LIBUSB_ERROR_NOT_FOUND && r != LIBUSB_ERROR_NO_DEVICE) { ERROR("could not release interface %d (configuration %d): %s", n, host->config->bConfigurationValue, libusb_error_name(r)); } } if (!attach_drivers) return; host->claimed = 0; /* reset the device before re-binding the kernel drivers, so that the kernel drivers get the device in a clean state. */ if (!(host->quirks & QUIRK_DO_NOT_RESET)) { r = libusb_reset_device(host->handle); if (r != 0) { /* if we're releasing the device because it was removed, resetting * will fail. Don't print a warning in this situation */ if (r != LIBUSB_ERROR_NO_DEVICE) { ERROR("error resetting device: %s", libusb_error_name(r)); } return; } } if (host->config) current_config = host->config->bConfigurationValue; if (current_config != host->restore_config) { r = libusb_set_configuration(host->handle, host->restore_config); if (r < 0) ERROR("could not restore configuration to %d: %s", host->restore_config, libusb_error_name(r)); return; /* set_config automatically binds drivers for the new config */ } for (i = 0; host->config && i < host->config->bNumInterfaces; i++) { n = host->config->interface[i].altsetting[0].bInterfaceNumber; r = libusb_attach_kernel_driver(host->handle, n); if (r < 0 && r != LIBUSB_ERROR_NOT_FOUND /* No driver */ && r != LIBUSB_ERROR_NO_DEVICE /* Device unplugged */ && r != LIBUSB_ERROR_NOT_SUPPORTED /* Not supported */ && r != LIBUSB_ERROR_BUSY /* driver rebound already */) { ERROR("could not re-attach driver to interface %d (configuration %d): %s", n, host->config->bConfigurationValue, libusb_error_name(r)); } } } USBREDIR_VISIBLE struct usbredirhost *usbredirhost_open( libusb_context *usb_ctx, libusb_device_handle *usb_dev_handle, usbredirparser_log log_func, usbredirparser_read read_guest_data_func, usbredirparser_write write_guest_data_func, void *func_priv, const char *version, int verbose, int flags) { return usbredirhost_open_full(usb_ctx, usb_dev_handle, log_func, read_guest_data_func, write_guest_data_func, NULL, NULL, NULL, NULL, NULL, func_priv, version, verbose, flags); } USBREDIR_VISIBLE struct usbredirhost *usbredirhost_open_full( libusb_context *usb_ctx, libusb_device_handle *usb_dev_handle, usbredirparser_log log_func, usbredirparser_read read_guest_data_func, usbredirparser_write write_guest_data_func, usbredirhost_flush_writes flush_writes_func, usbredirparser_alloc_lock alloc_lock_func, usbredirparser_lock lock_func, usbredirparser_unlock unlock_func, usbredirparser_free_lock free_lock_func, void *func_priv, const char *version, int verbose, int flags) { struct usbredirhost *host; int parser_flags = usbredirparser_fl_usb_host; uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0, }; host = calloc(1, sizeof(*host)); if (!host) { log_func(func_priv, usbredirparser_error, "usbredirhost error: Out of memory allocating usbredirhost"); libusb_close(usb_dev_handle); return NULL; } host->ctx = usb_ctx; host->log_func = log_func; host->read_func = read_guest_data_func; host->write_func = write_guest_data_func; host->flush_writes_func = flush_writes_func; host->func_priv = func_priv; host->verbose = verbose; host->disconnected = 1; /* No device is connected initially */ host->parser = usbredirparser_create(); if (!host->parser) { log_func(func_priv, usbredirparser_error, "usbredirhost error: Out of memory allocating usbredirparser"); libusb_close(usb_dev_handle); usbredirhost_close(host); return NULL; } host->parser->priv = host; host->parser->log_func = usbredirhost_log; host->parser->read_func = usbredirhost_read; host->parser->write_func = usbredirhost_write; host->parser->hello_func = usbredirhost_hello; host->parser->reset_func = usbredirhost_reset; host->parser->set_configuration_func = usbredirhost_set_configuration; host->parser->get_configuration_func = usbredirhost_get_configuration; host->parser->set_alt_setting_func = usbredirhost_set_alt_setting; host->parser->get_alt_setting_func = usbredirhost_get_alt_setting; host->parser->start_iso_stream_func = usbredirhost_start_iso_stream; host->parser->stop_iso_stream_func = usbredirhost_stop_iso_stream; host->parser->start_interrupt_receiving_func = usbredirhost_start_interrupt_receiving; host->parser->stop_interrupt_receiving_func = usbredirhost_stop_interrupt_receiving; host->parser->alloc_bulk_streams_func = usbredirhost_alloc_bulk_streams; host->parser->free_bulk_streams_func = usbredirhost_free_bulk_streams; host->parser->cancel_data_packet_func = usbredirhost_cancel_data_packet; host->parser->filter_reject_func = usbredirhost_filter_reject; host->parser->filter_filter_func = usbredirhost_filter_filter; host->parser->device_disconnect_ack_func = usbredirhost_device_disconnect_ack; host->parser->start_bulk_receiving_func = usbredirhost_start_bulk_receiving; host->parser->stop_bulk_receiving_func = usbredirhost_stop_bulk_receiving; host->parser->control_packet_func = usbredirhost_control_packet; host->parser->bulk_packet_func = usbredirhost_bulk_packet; host->parser->iso_packet_func = usbredirhost_iso_packet; host->parser->interrupt_packet_func = usbredirhost_interrupt_packet; host->parser->alloc_lock_func = alloc_lock_func; host->parser->lock_func = lock_func; host->parser->unlock_func = unlock_func; host->parser->free_lock_func = free_lock_func; if (host->parser->alloc_lock_func) { host->lock = host->parser->alloc_lock_func(); host->disconnect_lock = host->parser->alloc_lock_func(); } if (flags & usbredirhost_fl_write_cb_owns_buffer) { parser_flags |= usbredirparser_fl_write_cb_owns_buffer; } usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version); usbredirparser_caps_set_cap(caps, usb_redir_cap_filter); usbredirparser_caps_set_cap(caps, usb_redir_cap_device_disconnect_ack); usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size); usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids); usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length); usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_receiving); #if LIBUSBX_API_VERSION >= 0x01000103 usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_streams); #endif usbredirparser_init(host->parser, version, caps, USB_REDIR_CAPS_SIZE, parser_flags); #if LIBUSB_API_VERSION >= 0x01000106 int ret = libusb_set_option(host->ctx, LIBUSB_OPTION_LOG_LEVEL, CLAMP(host->verbose, LIBUSB_LOG_LEVEL_NONE, LIBUSB_LOG_LEVEL_DEBUG)); if (ret != LIBUSB_SUCCESS) { ERROR("error setting libusb log level: %s", libusb_error_name(ret)); usbredirhost_close(host); return NULL; } #else libusb_set_debug(host->ctx, host->verbose); #endif if (usbredirhost_set_device(host, usb_dev_handle) != usb_redir_success) { usbredirhost_close(host); return NULL; } FLUSH(host); return host; } USBREDIR_VISIBLE void usbredirhost_close(struct usbredirhost *host) { usbredirhost_clear_device(host); if (host->lock) { host->parser->free_lock_func(host->lock); } if (host->disconnect_lock) { host->parser->free_lock_func(host->disconnect_lock); } if (host->parser) { usbredirparser_destroy(host->parser); } free(host->filter_rules); free(host); } static int usbredirhost_reset_device(struct usbredirhost *host) { int r; if (host->quirks & QUIRK_DO_NOT_RESET) { return 0; } r = libusb_reset_device(host->handle); if (r != 0) { ERROR("error resetting device: %s", libusb_error_name(r)); usbredirhost_clear_device(host); return r; } host->reset = 1; return 0; } USBREDIR_VISIBLE int usbredirhost_set_device(struct usbredirhost *host, libusb_device_handle *usb_dev_handle) { int i, r, status; usbredirhost_clear_device(host); if (!usb_dev_handle) return usb_redir_success; host->dev = libusb_get_device(usb_dev_handle); host->handle = usb_dev_handle; status = usbredirhost_claim(host, 1); if (status != usb_redir_success) { usbredirhost_clear_device(host); return status; } for (i = 0; usbredirhost_reset_blacklist[i].vendor_id != -1; i++) { if (host->desc.idVendor == usbredirhost_reset_blacklist[i].vendor_id && host->desc.idProduct == usbredirhost_reset_blacklist[i].product_id) { host->quirks |= QUIRK_DO_NOT_RESET; break; } } /* The first thing almost any usb-guest does is a (slow) device-reset so lets do that before hand */ r = usbredirhost_reset_device(host); if (r != 0) { return libusb_status_or_error_to_redir_status(host, r); } usbredirhost_send_device_connect(host); return usb_redir_success; } static void usbredirhost_clear_device(struct usbredirhost *host) { if (!host->dev) return; if (usbredirhost_cancel_pending_urbs(host, 0)) usbredirhost_wait_for_cancel_completion(host); usbredirhost_release(host, 1); if (host->config) { libusb_free_config_descriptor(host->config); host->config = NULL; } if (host->handle) { libusb_close(host->handle); host->handle = NULL; } host->connect_pending = 0; host->quirks = 0; host->dev = NULL; usbredirhost_handle_disconnect(host); FLUSH(host); } USBREDIR_VISIBLE int usbredirhost_read_guest_data(struct usbredirhost *host) { return usbredirparser_do_read(host->parser); } USBREDIR_VISIBLE int usbredirhost_has_data_to_write(struct usbredirhost *host) { return usbredirparser_has_data_to_write(host->parser); } USBREDIR_VISIBLE int usbredirhost_write_guest_data(struct usbredirhost *host) { return usbredirparser_do_write(host->parser); } USBREDIR_VISIBLE void usbredirhost_free_write_buffer(struct usbredirhost *host, uint8_t *data) { usbredirparser_free_write_buffer(host->parser, data); } /**************************************************************************/ static struct usbredirtransfer *usbredirhost_alloc_transfer( struct usbredirhost *host, int iso_packets) { struct usbredirtransfer *redir_transfer; struct libusb_transfer *libusb_transfer; redir_transfer = calloc(1, sizeof(*redir_transfer)); libusb_transfer = libusb_alloc_transfer(iso_packets); if (!redir_transfer || !libusb_transfer) { ERROR("out of memory allocating usb transfer, dropping packet"); free(redir_transfer); libusb_free_transfer(libusb_transfer); return NULL; } redir_transfer->host = host; redir_transfer->transfer = libusb_transfer; libusb_transfer->user_data = redir_transfer; return redir_transfer; } static void usbredirhost_free_transfer(struct usbredirtransfer *transfer) { if (!transfer) return; /* In certain cases this should really be a usbredirparser_free_packet_data but since we use the same malloc impl. as usbredirparser this is ok. */ free(transfer->transfer->buffer); libusb_free_transfer(transfer->transfer); free(transfer); } static void usbredirhost_add_transfer(struct usbredirhost *host, struct usbredirtransfer *new_transfer) { struct usbredirtransfer *transfer = &host->transfers_head; LOCK(host); while (transfer->next) { transfer = transfer->next; } new_transfer->prev = transfer; transfer->next = new_transfer; UNLOCK(host); } /* Note caller must hold the host lock */ static void usbredirhost_remove_and_free_transfer( struct usbredirtransfer *transfer) { if (transfer->next) transfer->next->prev = transfer->prev; if (transfer->prev) transfer->prev->next = transfer->next; usbredirhost_free_transfer(transfer); } /**************************************************************************/ /* Called from both parser read and packet complete callbacks */ static void usbredirhost_cancel_stream_unlocked(struct usbredirhost *host, uint8_t ep) { int i; struct usbredirtransfer *transfer; for (i = 0; i < host->endpoint[EP2I(ep)].transfer_count; i++) { transfer = host->endpoint[EP2I(ep)].transfer[i]; if (transfer->packet_idx == SUBMITTED_IDX) { libusb_cancel_transfer(transfer->transfer); transfer->cancelled = 1; host->cancels_pending++; } else { usbredirhost_free_transfer(transfer); } host->endpoint[EP2I(ep)].transfer[i] = NULL; } host->endpoint[EP2I(ep)].out_idx = 0; host->endpoint[EP2I(ep)].stream_started = 0; host->endpoint[EP2I(ep)].drop_packets = 0; host->endpoint[EP2I(ep)].pkts_per_transfer = 0; host->endpoint[EP2I(ep)].transfer_count = 0; } static void usbredirhost_cancel_stream(struct usbredirhost *host, uint8_t ep) { LOCK(host); usbredirhost_cancel_stream_unlocked(host, ep); UNLOCK(host); } static void usbredirhost_send_stream_status(struct usbredirhost *host, uint64_t id, uint8_t ep, uint8_t status) { switch (host->endpoint[EP2I(ep)].type) { case usb_redir_type_iso: { struct usb_redir_iso_stream_status_header iso_status = { .endpoint = ep, .status = status, }; usbredirparser_send_iso_stream_status(host->parser, id, &iso_status); break; } case usb_redir_type_bulk: { struct usb_redir_bulk_receiving_status_header bulk_status = { .endpoint = ep, .status = status, }; usbredirparser_send_bulk_receiving_status(host->parser, id, &bulk_status); break; } case usb_redir_type_interrupt: { struct usb_redir_interrupt_receiving_status_header interrupt_status = { .endpoint = ep, .status = status, }; usbredirparser_send_interrupt_receiving_status(host->parser, id, &interrupt_status); break; } } } static int usbredirhost_can_write_iso_package(struct usbredirhost *host) { uint64_t size; if (!host->buffered_output_size_func) return true; size = host->buffered_output_size_func(host->func_priv); if (size >= host->iso_threshold.higher) { if (!host->iso_threshold.dropping) DEBUG("START dropping isoc packets %" PRIu64 " buffer > %" PRIu64 " hi threshold", size, host->iso_threshold.higher); host->iso_threshold.dropping = true; } else if (size < host->iso_threshold.lower) { if (host->iso_threshold.dropping) DEBUG("STOP dropping isoc packets %" PRIu64 " buffer < %" PRIu64 " low threshold", size, host->iso_threshold.lower); host->iso_threshold.dropping = false; } return !host->iso_threshold.dropping; } static void usbredirhost_send_stream_data(struct usbredirhost *host, uint64_t id, uint8_t ep, uint8_t status, uint8_t *data, int len) { /* USB-2 is max 8000 packets / sec, if we've queued up more then 0.1 sec, assume our connection is not keeping up and start dropping packets. */ if (usbredirparser_has_data_to_write(host->parser) > 800) { if (host->endpoint[EP2I(ep)].warn_on_drop) { WARNING("buffered stream on endpoint %02X, connection too slow, " "dropping packets", ep); host->endpoint[EP2I(ep)].warn_on_drop = 0; } DEBUG("buffered complete ep %02X dropping packet status %d len %d", ep, status, len); return; } DEBUG("buffered complete ep %02X status %d len %d", ep, status, len); switch (host->endpoint[EP2I(ep)].type) { case usb_redir_type_iso: { struct usb_redir_iso_packet_header iso_packet = { .endpoint = ep, .status = status, .length = len, }; if (usbredirhost_can_write_iso_package(host)) usbredirparser_send_iso_packet(host->parser, id, &iso_packet, data, len); break; } case usb_redir_type_bulk: { struct usb_redir_buffered_bulk_packet_header bulk_packet = { .endpoint = ep, .status = status, .length = len, }; usbredirparser_send_buffered_bulk_packet(host->parser, id, &bulk_packet, data, len); break; } case usb_redir_type_interrupt: { struct usb_redir_interrupt_packet_header interrupt_packet = { .endpoint = ep, .status = status, .length = len, }; usbredirparser_send_interrupt_packet(host->parser, id, &interrupt_packet, data, len); break; } } } /* Called from both parser read and packet complete callbacks */ static int usbredirhost_submit_stream_transfer_unlocked( struct usbredirhost *host, struct usbredirtransfer *transfer) { int r; host->reset = 0; r = libusb_submit_transfer(transfer->transfer); if (r < 0) { uint8_t ep = transfer->transfer->endpoint; if (r == LIBUSB_ERROR_NO_DEVICE) { usbredirhost_handle_disconnect(host); } else { ERROR("error submitting transfer on ep %02X: %s, stopping stream", ep, libusb_error_name(r)); usbredirhost_cancel_stream_unlocked(host, ep); usbredirhost_send_stream_status(host, transfer->id, ep, usb_redir_stall); } return usb_redir_stall; } transfer->packet_idx = SUBMITTED_IDX; return usb_redir_success; } /* Called from both parser read and packet complete callbacks */ static int usbredirhost_start_stream_unlocked(struct usbredirhost *host, uint8_t ep) { unsigned int i, count = host->endpoint[EP2I(ep)].transfer_count; int status; /* For out endpoints 1/2 the transfers are a buffer for usb-guest data */ if (!(ep & LIBUSB_ENDPOINT_IN)) { count /= 2; } for (i = 0; i < count; i++) { if (ep & LIBUSB_ENDPOINT_IN) { host->endpoint[EP2I(ep)].transfer[i]->id = i * host->endpoint[EP2I(ep)].pkts_per_transfer; } status = usbredirhost_submit_stream_transfer_unlocked(host, host->endpoint[EP2I(ep)].transfer[i]); if (status != usb_redir_success) { return status; } } host->endpoint[EP2I(ep)].stream_started = 1; return usb_redir_success; } static void usbredirhost_stop_stream(struct usbredirhost *host, uint64_t id, uint8_t ep) { if (host->disconnected) { return; } usbredirhost_cancel_stream(host, ep); usbredirhost_send_stream_status(host, id, ep, usb_redir_success); FLUSH(host); } static void usbredirhost_set_iso_threshold(struct usbredirhost *host, uint8_t pkts_per_transfer, uint8_t transfer_count, uint16_t max_packetsize) { uint64_t reference = pkts_per_transfer * transfer_count * max_packetsize; host->iso_threshold.lower = reference / 2; host->iso_threshold.higher = reference * 3; DEBUG("higher threshold is %" PRIu64 " bytes | lower threshold is %" PRIu64 " bytes", host->iso_threshold.higher, host->iso_threshold.lower); } /* Called from both parser read and packet complete callbacks */ static void usbredirhost_alloc_stream_unlocked(struct usbredirhost *host, uint64_t id, uint8_t ep, uint8_t type, uint8_t pkts_per_transfer, int pkt_size, uint8_t transfer_count, int send_success) { int i, buf_size, status = usb_redir_success; unsigned char *buffer; if (host->disconnected) { goto error; } if (host->endpoint[EP2I(ep)].type != type) { ERROR("error start stream type %d on type %d endpoint", type, host->endpoint[EP2I(ep)].type); goto error; } if ( pkts_per_transfer < 1 || pkts_per_transfer > MAX_PACKETS_PER_TRANSFER || transfer_count < 1 || transfer_count > MAX_TRANSFER_COUNT || host->endpoint[EP2I(ep)].max_packetsize == 0 || (pkt_size % host->endpoint[EP2I(ep)].max_packetsize) != 0) { ERROR("error start stream type %d invalid parameters", type); goto error; } if (host->endpoint[EP2I(ep)].transfer_count) { ERROR("error received start type %d for already started stream", type); usbredirhost_send_stream_status(host, id, ep, usb_redir_inval); return; } DEBUG("allocating stream ep %02X type %d packet-size %d pkts %d urbs %d", ep, type, pkt_size, pkts_per_transfer, transfer_count); for (i = 0; i < transfer_count; i++) { host->endpoint[EP2I(ep)].transfer[i] = usbredirhost_alloc_transfer(host, (type == usb_redir_type_iso) ? pkts_per_transfer : 0); if (!host->endpoint[EP2I(ep)].transfer[i]) { goto alloc_error; } buf_size = pkt_size * pkts_per_transfer; buffer = malloc(buf_size); if (!buffer) { goto alloc_error; } switch (type) { case usb_redir_type_iso: libusb_fill_iso_transfer( host->endpoint[EP2I(ep)].transfer[i]->transfer, host->handle, ep, buffer, buf_size, pkts_per_transfer, usbredirhost_iso_packet_complete, host->endpoint[EP2I(ep)].transfer[i], ISO_TIMEOUT); libusb_set_iso_packet_lengths( host->endpoint[EP2I(ep)].transfer[i]->transfer, pkt_size); usbredirhost_set_iso_threshold( host, pkts_per_transfer, transfer_count, host->endpoint[EP2I(ep)].max_packetsize); break; case usb_redir_type_bulk: libusb_fill_bulk_transfer( host->endpoint[EP2I(ep)].transfer[i]->transfer, host->handle, ep, buffer, buf_size, usbredirhost_buffered_packet_complete, host->endpoint[EP2I(ep)].transfer[i], BULK_TIMEOUT); break; case usb_redir_type_interrupt: libusb_fill_interrupt_transfer( host->endpoint[EP2I(ep)].transfer[i]->transfer, host->handle, ep, buffer, buf_size, usbredirhost_buffered_packet_complete, host->endpoint[EP2I(ep)].transfer[i], INTERRUPT_TIMEOUT); break; } } host->endpoint[EP2I(ep)].out_idx = 0; host->endpoint[EP2I(ep)].drop_packets = 0; host->endpoint[EP2I(ep)].pkts_per_transfer = pkts_per_transfer; host->endpoint[EP2I(ep)].transfer_count = transfer_count; /* For input endpoints submit the transfers now */ if (ep & LIBUSB_ENDPOINT_IN) { status = usbredirhost_start_stream_unlocked(host, ep); } if (send_success && status == usb_redir_success) { usbredirhost_send_stream_status(host, id, ep, status); } return; alloc_error: ERROR("out of memory allocating type %d stream buffers", type); do { usbredirhost_free_transfer(host->endpoint[EP2I(ep)].transfer[i]); host->endpoint[EP2I(ep)].transfer[i] = NULL; i--; } while (i >= 0); error: usbredirhost_send_stream_status(host, id, ep, usb_redir_stall); } static void usbredirhost_alloc_stream(struct usbredirhost *host, uint64_t id, uint8_t ep, uint8_t type, uint8_t pkts_per_transfer, int pkt_size, uint8_t transfer_count, int send_success) { LOCK(host); usbredirhost_alloc_stream_unlocked(host, id, ep, type, pkts_per_transfer, pkt_size, transfer_count, send_success); UNLOCK(host); } static void usbredirhost_clear_stream_stall_unlocked( struct usbredirhost *host, uint64_t id, uint8_t ep) { int r; uint8_t pkts_per_transfer = host->endpoint[EP2I(ep)].pkts_per_transfer; uint8_t transfer_count = host->endpoint[EP2I(ep)].transfer_count; int pkt_size = host->endpoint[EP2I(ep)].transfer[0]->transfer->length / pkts_per_transfer; WARNING("buffered stream on endpoint %02X stalled, clearing stall", ep); usbredirhost_cancel_stream_unlocked(host, ep); r = libusb_clear_halt(host->handle, ep); if (r < 0) { usbredirhost_send_stream_status(host, id, ep, usb_redir_stall); return; } usbredirhost_alloc_stream_unlocked(host, id, ep, host->endpoint[EP2I(ep)].type, pkts_per_transfer, pkt_size, transfer_count, 0); } /**************************************************************************/ /* Called from close and parser read callbacks */ static int usbredirhost_cancel_pending_urbs(struct usbredirhost *host, int notify_guest) { struct usbredirtransfer *t; int i, wait; LOCK(host); for (i = 0; i < MAX_ENDPOINTS; i++) { if (notify_guest && host->endpoint[i].transfer_count) usbredirhost_send_stream_status(host, 0, I2EP(i), usb_redir_stall); usbredirhost_cancel_stream_unlocked(host, I2EP(i)); } wait = host->cancels_pending; for (t = host->transfers_head.next; t; t = t->next) { libusb_cancel_transfer(t->transfer); wait = 1; } UNLOCK(host); if (notify_guest) FLUSH(host); return wait; } /* Called from close and parser read callbacks */ void usbredirhost_wait_for_cancel_completion(struct usbredirhost *host) { int wait; struct timeval tv; do { memset(&tv, 0, sizeof(tv)); tv.tv_usec = 2500; libusb_handle_events_timeout(host->ctx, &tv); LOCK(host); wait = host->cancels_pending || host->transfers_head.next; UNLOCK(host); } while (wait); } /* Only called from read callbacks */ static void usbredirhost_cancel_pending_urbs_on_interface( struct usbredirhost *host, int i) { struct usbredirtransfer *t; const struct libusb_interface_descriptor *intf_desc; LOCK(host); intf_desc = &host->config->interface[i].altsetting[host->alt_setting[i]]; for (i = 0; i < intf_desc->bNumEndpoints; i++) { uint8_t ep = intf_desc->endpoint[i].bEndpointAddress; usbredirhost_cancel_stream_unlocked(host, ep); for (t = host->transfers_head.next; t; t = t->next) { if (t->transfer->endpoint == ep) libusb_cancel_transfer(t->transfer); } } UNLOCK(host); } /* Only called from read callbacks */ static int usbredirhost_bInterfaceNumber_to_index( struct usbredirhost *host, uint8_t bInterfaceNumber) { int i, n; for (i = 0; host->config && i < host->config->bNumInterfaces; i++) { n = host->config->interface[i].altsetting[0].bInterfaceNumber; if (n == bInterfaceNumber) { return i; } } ERROR("invalid bNumInterface: %d\n", (int)bInterfaceNumber); return -1; } static void usbredirhost_log_data(struct usbredirhost *host, const char *desc, const uint8_t *data, int len) { if (usbredirparser_debug_data <= host->verbose) { int i, j, n; for (i = 0; i < len; i += j) { char buf[128]; n = sprintf(buf, "%s", desc); for (j = 0; j < 8 && i + j < len; j++){ n += sprintf(buf + n, " %02X", data[i + j]); } va_log(host, usbredirparser_debug_data, "%s", buf); } } } /**************************************************************************/ USBREDIR_VISIBLE void usbredirhost_set_buffered_output_size_cb(struct usbredirhost *host, usbredirhost_buffered_output_size buffered_output_size_func) { if (!host) { fprintf(stderr, "%s: invalid usbredirhost", __func__); return; } host->buffered_output_size_func = buffered_output_size_func; } /* Return value: 0 All ok 1 Packet borked, continue with next packet / urb 2 Stream borked, full stop, no resubmit, etc. Note in the case of a return value of 2 this function takes care of sending an iso status message to the usb-guest. */ static int usbredirhost_handle_iso_status(struct usbredirhost *host, uint64_t id, uint8_t ep, int r) { switch (r) { case LIBUSB_TRANSFER_COMPLETED: case -EXDEV: /* FIXlibusb: Passing regular error codes, bad libusb, bad! */ return 0; case LIBUSB_TRANSFER_CANCELLED: /* Stream was intentionally stopped */ return 2; case LIBUSB_TRANSFER_STALL: usbredirhost_clear_stream_stall_unlocked(host, id, ep); return 2; case LIBUSB_TRANSFER_NO_DEVICE: usbredirhost_handle_disconnect(host); return 2; case LIBUSB_TRANSFER_OVERFLOW: case LIBUSB_TRANSFER_ERROR: case LIBUSB_TRANSFER_TIMED_OUT: default: ERROR("iso stream error on endpoint %02X: %d", ep, r); return 1; } } static void LIBUSB_CALL usbredirhost_iso_packet_complete( struct libusb_transfer *libusb_transfer) { struct usbredirtransfer *transfer = libusb_transfer->user_data; uint8_t ep = libusb_transfer->endpoint; struct usbredirhost *host = transfer->host; int i, r, len, status; LOCK(host); if (transfer->cancelled) { host->cancels_pending--; usbredirhost_free_transfer(transfer); goto unlock; } /* Mark transfer completed (iow not submitted) */ transfer->packet_idx = 0; /* Check overal transfer status */ r = libusb_transfer->status; switch (usbredirhost_handle_iso_status(host, transfer->id, ep, r)) { case 0: break; case 1: status = libusb_status_or_error_to_redir_status(host, r); if (ep & LIBUSB_ENDPOINT_IN) { struct usb_redir_iso_packet_header iso_packet = { .endpoint = ep, .status = status, .length = 0 }; usbredirparser_send_iso_packet(host->parser, transfer->id, &iso_packet, NULL, 0); transfer->id += libusb_transfer->num_iso_packets; goto resubmit; } else { usbredirhost_send_stream_status(host, transfer->id, ep, status); goto unlock; } break; case 2: goto unlock; } /* Check per packet status and send ok input packets to usb-guest */ for (i = 0; i < libusb_transfer->num_iso_packets; i++) { r = libusb_transfer->iso_packet_desc[i].status; len = libusb_transfer->iso_packet_desc[i].actual_length; status = libusb_status_or_error_to_redir_status(host, r); switch (usbredirhost_handle_iso_status(host, transfer->id, ep, r)) { case 0: break; case 1: if (ep & LIBUSB_ENDPOINT_IN) { len = 0; } else { usbredirhost_send_stream_status(host, transfer->id, ep, status); goto unlock; /* We send max one iso status message per urb */ } break; case 2: goto unlock; } if (ep & LIBUSB_ENDPOINT_IN) { usbredirhost_send_stream_data(host, transfer->id, ep, status, libusb_get_iso_packet_buffer(libusb_transfer, i), len); transfer->id++; } else { DEBUG("iso-in complete ep %02X pkt %d len %d id %"PRIu64, ep, i, len, transfer->id); } } /* And for input transfers resubmit the transfer (output transfers get resubmitted when they have all their packets filled with data) */ if (ep & LIBUSB_ENDPOINT_IN) { resubmit: transfer->id += (host->endpoint[EP2I(ep)].transfer_count - 1) * libusb_transfer->num_iso_packets; usbredirhost_submit_stream_transfer_unlocked(host, transfer); } else { for (i = 0; i < host->endpoint[EP2I(ep)].transfer_count; i++) { transfer = host->endpoint[EP2I(ep)].transfer[i]; if (transfer->packet_idx == SUBMITTED_IDX) break; } if (i == host->endpoint[EP2I(ep)].transfer_count) { DEBUG("underflow of iso out queue on ep: %02X", ep); /* Re-fill buffers before submitting urbs again */ for (i = 0; i < host->endpoint[EP2I(ep)].transfer_count; i++) host->endpoint[EP2I(ep)].transfer[i]->packet_idx = 0; host->endpoint[EP2I(ep)].out_idx = 0; host->endpoint[EP2I(ep)].stream_started = 0; host->endpoint[EP2I(ep)].drop_packets = 0; } } unlock: UNLOCK(host); FLUSH(host); } /**************************************************************************/ static void LIBUSB_CALL usbredirhost_buffered_packet_complete( struct libusb_transfer *libusb_transfer) { struct usbredirtransfer *transfer = libusb_transfer->user_data; uint8_t ep = libusb_transfer->endpoint; struct usbredirhost *host = transfer->host; int r, len = libusb_transfer->actual_length; LOCK(host); if (transfer->cancelled) { host->cancels_pending--; usbredirhost_free_transfer(transfer); goto unlock; } /* Mark transfer completed (iow not submitted) */ transfer->packet_idx = 0; r = libusb_transfer->status; switch (r) { case LIBUSB_TRANSFER_COMPLETED: break; case LIBUSB_TRANSFER_STALL: usbredirhost_clear_stream_stall_unlocked(host, transfer->id, ep); goto unlock; case LIBUSB_TRANSFER_NO_DEVICE: usbredirhost_handle_disconnect(host); goto unlock; default: ERROR("buffered in error on endpoint %02X: %d", ep, r); len = 0; } usbredirhost_send_stream_data(host, transfer->id, ep, libusb_status_or_error_to_redir_status(host, r), transfer->transfer->buffer, len); usbredirhost_log_data(host, "buffered data in:", transfer->transfer->buffer, len); transfer->id += host->endpoint[EP2I(ep)].transfer_count; usbredirhost_submit_stream_transfer_unlocked(host, transfer); unlock: UNLOCK(host); FLUSH(host); } /**************************************************************************/ static void usbredirhost_hello(void *priv, struct usb_redir_hello_header *h) { struct usbredirhost *host = priv; if (host->connect_pending) usbredirhost_send_device_connect(host); } static void usbredirhost_reset(void *priv) { struct usbredirhost *host = priv; int r; if (host->disconnected || host->reset) { return; } /* * The guest should have cancelled any pending urbs already, but the * cancellations may be awaiting completion, and if we then do a reset * they will complete with an error code of LIBUSB_TRANSFER_NO_DEVICE. * * And we also need to cleanly shutdown any streams (and let the guest * know they should be restarted after the reset). */ if (usbredirhost_cancel_pending_urbs(host, 1)) usbredirhost_wait_for_cancel_completion(host); r = usbredirhost_reset_device(host); if (r != 0) { host->read_status = usbredirhost_read_device_lost; } } static void usbredirhost_set_configuration(void *priv, uint64_t id, struct usb_redir_set_configuration_header *set_config) { struct usbredirhost *host = priv; int r, claim_status; struct usb_redir_configuration_status_header status = { .status = usb_redir_success, }; if (host->disconnected) { status.status = usb_redir_ioerror; goto exit; } if (host->config && host->config->bConfigurationValue == set_config->configuration) { goto exit; } host->reset = 0; usbredirhost_cancel_pending_urbs(host, 0); usbredirhost_release(host, 0); r = libusb_set_configuration(host->handle, set_config->configuration); if (r < 0) { ERROR("could not set active configuration to %d: %s", (int)set_config->configuration, libusb_error_name(r)); status.status = usb_redir_ioerror; } claim_status = usbredirhost_claim(host, 0); if (claim_status != usb_redir_success) { usbredirhost_clear_device(host); host->read_status = usbredirhost_read_device_lost; status.status = usb_redir_ioerror; goto exit; } usbredirhost_send_interface_n_ep_info(host); exit: status.configuration = host->config ? host->config->bConfigurationValue:0; usbredirparser_send_configuration_status(host->parser, id, &status); FLUSH(host); } static void usbredirhost_get_configuration(void *priv, uint64_t id) { struct usbredirhost *host = priv; struct usb_redir_configuration_status_header status; if (host->disconnected) status.status = usb_redir_ioerror; else status.status = usb_redir_success; status.configuration = host->config ? host->config->bConfigurationValue:0; usbredirparser_send_configuration_status(host->parser, id, &status); FLUSH(host); } static void usbredirhost_set_alt_setting(void *priv, uint64_t id, struct usb_redir_set_alt_setting_header *set_alt_setting) { struct usbredirhost *host = priv; int i, j, r; struct usb_redir_alt_setting_status_header status = { .status = usb_redir_success, }; if (host->disconnected) { status.status = usb_redir_ioerror; status.alt = -1; goto exit_unknown_interface; } i = usbredirhost_bInterfaceNumber_to_index(host, set_alt_setting->interface); if (i == -1) { status.status = usb_redir_inval; status.alt = -1; goto exit_unknown_interface; } host->reset = 0; usbredirhost_cancel_pending_urbs_on_interface(host, i); r = libusb_set_interface_alt_setting(host->handle, set_alt_setting->interface, set_alt_setting->alt); if (r < 0) { ERROR("could not set alt setting for interface %d to %d: %s", set_alt_setting->interface, set_alt_setting->alt, libusb_error_name(r)); status.status = libusb_status_or_error_to_redir_status(host, r); goto exit; } /* The new alt setting may have lost endpoints compared to the old! -> Clear settings for all endpoints which used to be part of the intf. */ for (j = 0; j < MAX_ENDPOINTS; j++) { if (host->endpoint[j].interface != set_alt_setting->interface) continue; if ((j & 0x0f) == 0) { host->endpoint[j].type = usb_redir_type_control; } else { host->endpoint[j].type = usb_redir_type_invalid; } host->endpoint[j].interval = 0; host->endpoint[j].interface = 0; host->endpoint[j].max_packetsize = 0; } host->alt_setting[i] = set_alt_setting->alt; usbredirhost_parse_interface(host, i); usbredirhost_send_interface_n_ep_info(host); exit: status.alt = host->alt_setting[i]; exit_unknown_interface: status.interface = set_alt_setting->interface; usbredirparser_send_alt_setting_status(host->parser, id, &status); FLUSH(host); } static void usbredirhost_get_alt_setting(void *priv, uint64_t id, struct usb_redir_get_alt_setting_header *get_alt_setting) { struct usbredirhost *host = priv; struct usb_redir_alt_setting_status_header status; int i; if (host->disconnected) { status.status = usb_redir_ioerror; status.alt = -1; goto exit; } i = usbredirhost_bInterfaceNumber_to_index(host, get_alt_setting->interface); if (i >= 0) { status.status = usb_redir_success; status.alt = host->alt_setting[i]; } else { status.status = usb_redir_inval; status.alt = -1; } exit: status.interface = get_alt_setting->interface; usbredirparser_send_alt_setting_status(host->parser, id, &status); FLUSH(host); } static void usbredirhost_start_iso_stream(void *priv, uint64_t id, struct usb_redir_start_iso_stream_header *start_iso_stream) { struct usbredirhost *host = priv; uint8_t ep = start_iso_stream->endpoint; usbredirhost_alloc_stream(host, id, ep, usb_redir_type_iso, start_iso_stream->pkts_per_urb, host->endpoint[EP2I(ep)].max_packetsize, start_iso_stream->no_urbs, 1); FLUSH(host); } static void usbredirhost_stop_iso_stream(void *priv, uint64_t id, struct usb_redir_stop_iso_stream_header *stop_iso_stream) { usbredirhost_stop_stream(priv, id, stop_iso_stream->endpoint); } static void usbredirhost_start_interrupt_receiving(void *priv, uint64_t id, struct usb_redir_start_interrupt_receiving_header *start_interrupt_receiving) { struct usbredirhost *host = priv; uint8_t ep = start_interrupt_receiving->endpoint; usbredirhost_alloc_stream(host, id, ep, usb_redir_type_interrupt, 1, host->endpoint[EP2I(ep)].max_packetsize, INTERRUPT_TRANSFER_COUNT, 1); FLUSH(host); } static void usbredirhost_stop_interrupt_receiving(void *priv, uint64_t id, struct usb_redir_stop_interrupt_receiving_header *stop_interrupt_receiving) { usbredirhost_stop_stream(priv, id, stop_interrupt_receiving->endpoint); } #if LIBUSBX_API_VERSION >= 0x01000103 static int usbredirhost_ep_mask_to_eps(uint32_t ep_mask, unsigned char *eps) { int i, j; for (i = 0, j = 0; i < MAX_ENDPOINTS; i++) { if (ep_mask & (1 << i)) eps[j++] = I2EP(i); } return j; } #endif static void usbredirhost_alloc_bulk_streams(void *priv, uint64_t id, struct usb_redir_alloc_bulk_streams_header *alloc_bulk_streams) { #if LIBUSBX_API_VERSION >= 0x01000103 struct usbredirhost *host = priv; unsigned char eps[MAX_ENDPOINTS]; int r, no_eps; struct usb_redir_bulk_streams_status_header streams_status = { .endpoints = alloc_bulk_streams->endpoints, .no_streams = alloc_bulk_streams->no_streams, .status = usb_redir_success, }; no_eps = usbredirhost_ep_mask_to_eps(alloc_bulk_streams->endpoints, eps); r = libusb_alloc_streams(host->handle, alloc_bulk_streams->no_streams, eps, no_eps); if (r < 0) { ERROR("could not alloc bulk streams: %s", libusb_error_name(r)); streams_status.status = libusb_status_or_error_to_redir_status(host, r); } else if (r < alloc_bulk_streams->no_streams) { ERROR("tried to alloc %u bulk streams but got only %d", alloc_bulk_streams->no_streams, r); streams_status.status = usb_redir_ioerror; } usbredirparser_send_bulk_streams_status(host->parser, id, &streams_status); FLUSH(host); #endif } static void usbredirhost_free_bulk_streams(void *priv, uint64_t id, struct usb_redir_free_bulk_streams_header *free_bulk_streams) { #if LIBUSBX_API_VERSION >= 0x01000103 struct usbredirhost *host = priv; unsigned char eps[MAX_ENDPOINTS]; int r, no_eps; struct usb_redir_bulk_streams_status_header streams_status = { .endpoints = free_bulk_streams->endpoints, .no_streams = 0, .status = usb_redir_success, }; no_eps = usbredirhost_ep_mask_to_eps(free_bulk_streams->endpoints, eps); r = libusb_free_streams(host->handle, eps, no_eps); if (r < 0) { ERROR("could not free bulk streams: %s", libusb_error_name(r)); streams_status.status = libusb_status_or_error_to_redir_status(host, r); } usbredirparser_send_bulk_streams_status(host->parser, id, &streams_status); FLUSH(host); #endif } static void usbredirhost_filter_reject(void *priv) { struct usbredirhost *host = priv; if (host->disconnected) return; INFO("device rejected"); host->read_status = usbredirhost_read_device_rejected; } static void usbredirhost_filter_filter(void *priv, struct usbredirfilter_rule *rules, int rules_count) { struct usbredirhost *host = priv; free(host->filter_rules); host->filter_rules = rules; host->filter_rules_count = rules_count; } static void usbredirhost_device_disconnect_ack(void *priv) { struct usbredirhost *host = priv; if (!host->wait_disconnect) { ERROR("error received disconnect ack without sending a disconnect"); return; } host->wait_disconnect = 0; if (host->connect_pending) usbredirhost_send_device_connect(host); } static void usbredirhost_start_bulk_receiving(void *priv, uint64_t id, struct usb_redir_start_bulk_receiving_header *start_bulk_receiving) { struct usbredirhost *host = priv; uint8_t ep = start_bulk_receiving->endpoint; usbredirhost_alloc_stream(host, id, ep, usb_redir_type_bulk, 1, start_bulk_receiving->bytes_per_transfer, start_bulk_receiving->no_transfers, 1); FLUSH(host); } static void usbredirhost_stop_bulk_receiving(void *priv, uint64_t id, struct usb_redir_stop_bulk_receiving_header *stop_bulk_receiving) { usbredirhost_stop_stream(priv, id, stop_bulk_receiving->endpoint); } /**************************************************************************/ static void usbredirhost_cancel_data_packet(void *priv, uint64_t id) { struct usbredirhost *host = priv; struct usbredirtransfer *t; struct usb_redir_control_packet_header control_packet; struct usb_redir_bulk_packet_header bulk_packet; struct usb_redir_interrupt_packet_header interrupt_packet; /* * This is a bit tricky, we are run from a parser read callback, while * at the same time the packet completion callback may run from another * thread. * * Since the completion handler will remove the transfer from our list, * send it back to the usb-guest (which we don't want to do twice), * and *free* the transfer, we must do the libusb_cancel_transfer() * with the lock held to ensure that it is not freed while we try to * cancel it. * * Doing this means libusb taking the transfer lock, while * we are holding our own lock, this is ok, since libusb releases the * transfer lock before calling the packet completion callback, so there * is no deadlock here. */ LOCK(host); for (t = host->transfers_head.next; t; t = t->next) { /* After cancellation the guest may re-use the id, so skip already cancelled packets */ if (!t->cancelled && t->id == id) { break; } } /* * Note not finding the transfer is not an error, the transfer may have * completed by the time we receive the cancel. */ if (t) { t->cancelled = 1; libusb_cancel_transfer(t->transfer); switch(t->transfer->type) { case LIBUSB_TRANSFER_TYPE_CONTROL: control_packet = t->control_packet; control_packet.status = usb_redir_cancelled; control_packet.length = 0; usbredirparser_send_control_packet(host->parser, t->id, &control_packet, NULL, 0); DEBUG("cancelled control packet ep %02x id %"PRIu64, control_packet.endpoint, id); break; case LIBUSB_TRANSFER_TYPE_BULK: #if LIBUSBX_API_VERSION >= 0x01000103 case LIBUSB_TRANSFER_TYPE_BULK_STREAM: #endif bulk_packet = t->bulk_packet; bulk_packet.status = usb_redir_cancelled; bulk_packet.length = 0; bulk_packet.length_high = 0; usbredirparser_send_bulk_packet(host->parser, t->id, &bulk_packet, NULL, 0); DEBUG("cancelled bulk packet ep %02x id %"PRIu64, bulk_packet.endpoint, id); break; case LIBUSB_TRANSFER_TYPE_INTERRUPT: interrupt_packet = t->interrupt_packet; interrupt_packet.status = usb_redir_cancelled; interrupt_packet.length = 0; usbredirparser_send_interrupt_packet(host->parser, t->id, &interrupt_packet, NULL, 0); DEBUG("cancelled interrupt packet ep %02x id %"PRIu64, interrupt_packet.endpoint, id); break; } } else DEBUG("cancel packet id %"PRIu64" not found", id); UNLOCK(host); FLUSH(host); } static void LIBUSB_CALL usbredirhost_control_packet_complete( struct libusb_transfer *libusb_transfer) { struct usb_redir_control_packet_header control_packet; struct usbredirtransfer *transfer = libusb_transfer->user_data; struct usbredirhost *host = transfer->host; LOCK(host); control_packet = transfer->control_packet; control_packet.status = libusb_status_or_error_to_redir_status(host, libusb_transfer->status); control_packet.length = libusb_transfer->actual_length; DEBUG("control complete ep %02X status %d len %d id %"PRIu64, control_packet.endpoint, control_packet.status, control_packet.length, transfer->id); if (!transfer->cancelled) { if (control_packet.endpoint & LIBUSB_ENDPOINT_IN) { usbredirhost_log_data(host, "ctrl data in:", libusb_transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, libusb_transfer->actual_length); usbredirparser_send_control_packet(host->parser, transfer->id, &control_packet, libusb_transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, libusb_transfer->actual_length); } else { usbredirparser_send_control_packet(host->parser, transfer->id, &control_packet, NULL, 0); } } usbredirhost_remove_and_free_transfer(transfer); UNLOCK(host); FLUSH(host); } static void usbredirhost_send_control_status(struct usbredirhost *host, uint64_t id, struct usb_redir_control_packet_header *control_packet, uint8_t status) { control_packet->status = status; control_packet->length = 0; usbredirparser_send_control_packet(host->parser, id, control_packet, NULL, 0); } static void usbredirhost_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *control_packet, uint8_t *data, int data_len) { struct usbredirhost *host = priv; uint8_t ep = control_packet->endpoint; struct usbredirtransfer *transfer; unsigned char *buffer; int r; DEBUG("control submit ep %02X len %d id %"PRIu64, ep, control_packet->length, id); if (host->disconnected) { usbredirhost_send_control_status(host, id, control_packet, usb_redir_ioerror); usbredirparser_free_packet_data(host->parser, data); FLUSH(host); return; } /* Verify endpoint type */ if (host->endpoint[EP2I(ep)].type != usb_redir_type_control) { ERROR("error control packet on non control ep %02X", ep); usbredirhost_send_control_status(host, id, control_packet, usb_redir_inval); usbredirparser_free_packet_data(host->parser, data); FLUSH(host); return; } host->reset = 0; /* If it is a clear stall, we need to do an actual clear stall, rather then just forward the control packet, so that the usbhost usbstack knows the stall is cleared */ if (control_packet->requesttype == LIBUSB_RECIPIENT_ENDPOINT && control_packet->request == LIBUSB_REQUEST_CLEAR_FEATURE && control_packet->value == 0x00 && data_len == 0) { r = libusb_clear_halt(host->handle, control_packet->index); r = libusb_status_or_error_to_redir_status(host, r); DEBUG("clear halt ep %02X status %d", control_packet->index, r); usbredirhost_send_control_status(host, id, control_packet, r); FLUSH(host); return; } buffer = malloc(LIBUSB_CONTROL_SETUP_SIZE + control_packet->length); if (!buffer) { ERROR("out of memory allocating transfer buffer, dropping packet"); usbredirparser_free_packet_data(host->parser, data); return; } transfer = usbredirhost_alloc_transfer(host, 0); if (!transfer) { free(buffer); usbredirparser_free_packet_data(host->parser, data); return; } libusb_fill_control_setup(buffer, control_packet->requesttype, control_packet->request, control_packet->value, control_packet->index, control_packet->length); if (!(ep & LIBUSB_ENDPOINT_IN)) { usbredirhost_log_data(host, "ctrl data out:", data, data_len); memcpy(buffer + LIBUSB_CONTROL_SETUP_SIZE, data, data_len); usbredirparser_free_packet_data(host->parser, data); } libusb_fill_control_transfer(transfer->transfer, host->handle, buffer, usbredirhost_control_packet_complete, transfer, CTRL_TIMEOUT); transfer->id = id; transfer->control_packet = *control_packet; usbredirhost_add_transfer(host, transfer); r = libusb_submit_transfer(transfer->transfer); if (r < 0) { ERROR("error submitting control transfer on ep %02X: %s", ep, libusb_error_name(r)); transfer->transfer->actual_length = 0; transfer->transfer->status = r; usbredirhost_control_packet_complete(transfer->transfer); } } static void LIBUSB_CALL usbredirhost_bulk_packet_complete( struct libusb_transfer *libusb_transfer) { struct usb_redir_bulk_packet_header bulk_packet; struct usbredirtransfer *transfer = libusb_transfer->user_data; struct usbredirhost *host = transfer->host; LOCK(host); bulk_packet = transfer->bulk_packet; bulk_packet.status = libusb_status_or_error_to_redir_status(host, libusb_transfer->status); bulk_packet.length = libusb_transfer->actual_length; bulk_packet.length_high = libusb_transfer->actual_length >> 16; DEBUG("bulk complete ep %02X status %d len %d id %"PRIu64, bulk_packet.endpoint, bulk_packet.status, libusb_transfer->actual_length, transfer->id); if (!transfer->cancelled) { if (bulk_packet.endpoint & LIBUSB_ENDPOINT_IN) { usbredirhost_log_data(host, "bulk data in:", libusb_transfer->buffer, libusb_transfer->actual_length); usbredirparser_send_bulk_packet(host->parser, transfer->id, &bulk_packet, libusb_transfer->buffer, libusb_transfer->actual_length); } else { usbredirparser_send_bulk_packet(host->parser, transfer->id, &bulk_packet, NULL, 0); } } usbredirhost_remove_and_free_transfer(transfer); UNLOCK(host); FLUSH(host); } static void usbredirhost_send_bulk_status(struct usbredirhost *host, uint64_t id, struct usb_redir_bulk_packet_header *bulk_packet, uint8_t status) { bulk_packet->status = status; bulk_packet->length = 0; bulk_packet->length_high = 0; usbredirparser_send_bulk_packet(host->parser, id, bulk_packet, NULL, 0); } static void usbredirhost_bulk_packet(void *priv, uint64_t id, struct usb_redir_bulk_packet_header *bulk_packet, uint8_t *data, int data_len) { struct usbredirhost *host = priv; uint8_t ep = bulk_packet->endpoint; int len = (bulk_packet->length_high << 16) | bulk_packet->length; struct usbredirtransfer *transfer; int r; DEBUG("bulk submit ep %02X len %d id %"PRIu64, ep, len, id); if (host->disconnected) { usbredirhost_send_bulk_status(host, id, bulk_packet, usb_redir_ioerror); usbredirparser_free_packet_data(host->parser, data); FLUSH(host); return; } if (host->endpoint[EP2I(ep)].type != usb_redir_type_bulk) { ERROR("error bulk packet on non bulk ep %02X", ep); usbredirhost_send_bulk_status(host, id, bulk_packet, usb_redir_inval); usbredirparser_free_packet_data(host->parser, data); FLUSH(host); return; } if (ep & LIBUSB_ENDPOINT_IN) { data = malloc(len); if (!data) { ERROR("out of memory allocating bulk buffer, dropping packet"); return; } } else { usbredirhost_log_data(host, "bulk data out:", data, data_len); /* Note no memcpy, we can re-use the data buffer the parser malloc-ed for us and expects us to free */ } transfer = usbredirhost_alloc_transfer(host, 0); if (!transfer) { free(data); return; } host->reset = 0; if (bulk_packet->stream_id) { #if LIBUSBX_API_VERSION >= 0x01000103 libusb_fill_bulk_stream_transfer(transfer->transfer, host->handle, ep, bulk_packet->stream_id, data, len, usbredirhost_bulk_packet_complete, transfer, BULK_TIMEOUT); #else r = LIBUSB_ERROR_INVALID_PARAM; free(data); goto error; #endif } else { libusb_fill_bulk_transfer(transfer->transfer, host->handle, ep, data, len, usbredirhost_bulk_packet_complete, transfer, BULK_TIMEOUT); } transfer->id = id; transfer->bulk_packet = *bulk_packet; usbredirhost_add_transfer(host, transfer); r = libusb_submit_transfer(transfer->transfer); if (r < 0) { #if LIBUSBX_API_VERSION < 0x01000103 error: #endif ERROR("error submitting bulk transfer on ep %02X: %s", ep, libusb_error_name(r)); transfer->transfer->actual_length = 0; transfer->transfer->status = r; usbredirhost_bulk_packet_complete(transfer->transfer); } } static void usbredirhost_iso_packet(void *priv, uint64_t id, struct usb_redir_iso_packet_header *iso_packet, uint8_t *data, int data_len) { struct usbredirhost *host = priv; uint8_t ep = iso_packet->endpoint; struct usbredirtransfer *transfer; int i, j, status = usb_redir_success; LOCK(host); if (host->disconnected) { status = usb_redir_ioerror; goto leave; } if (host->endpoint[EP2I(ep)].type != usb_redir_type_iso) { ERROR("error received iso packet for non iso ep %02X", ep); status = usb_redir_inval; goto leave; } if (host->endpoint[EP2I(ep)].transfer_count == 0) { ERROR("error received iso out packet for non started iso stream"); status = usb_redir_inval; goto leave; } if (data_len > host->endpoint[EP2I(ep)].max_packetsize) { ERROR("error received iso out packet is larger than wMaxPacketSize"); status = usb_redir_inval; goto leave; } if (host->endpoint[EP2I(ep)].drop_packets) { host->endpoint[EP2I(ep)].drop_packets--; goto leave; } i = host->endpoint[EP2I(ep)].out_idx; transfer = host->endpoint[EP2I(ep)].transfer[i]; j = transfer->packet_idx; if (j == SUBMITTED_IDX) { DEBUG("overflow of iso out queue on ep: %02X, dropping packet", ep); /* Since we're interupting the stream anyways, drop enough packets to get back to our target buffer size */ host->endpoint[EP2I(ep)].drop_packets = (host->endpoint[EP2I(ep)].pkts_per_transfer * host->endpoint[EP2I(ep)].transfer_count) / 2; goto leave; } /* Store the id of the first packet in the urb */ if (j == 0) { transfer->id = id; } memcpy(libusb_get_iso_packet_buffer(transfer->transfer, j), data, data_len); transfer->transfer->iso_packet_desc[j].length = data_len; DEBUG("iso-in queue ep %02X urb %d pkt %d len %d id %"PRIu64, ep, i, j, data_len, transfer->id); j++; transfer->packet_idx = j; if (j == host->endpoint[EP2I(ep)].pkts_per_transfer) { i = (i + 1) % host->endpoint[EP2I(ep)].transfer_count; host->endpoint[EP2I(ep)].out_idx = i; j = 0; } if (host->endpoint[EP2I(ep)].stream_started) { if (transfer->packet_idx == host->endpoint[EP2I(ep)].pkts_per_transfer) { usbredirhost_submit_stream_transfer_unlocked(host, transfer); } } else { /* We've not started the stream (submitted some transfers) yet, do so once we have half our buffers filled */ int available = i * host->endpoint[EP2I(ep)].pkts_per_transfer + j; int needed = (host->endpoint[EP2I(ep)].pkts_per_transfer * host->endpoint[EP2I(ep)].transfer_count) / 2; if (available == needed) { DEBUG("iso-in starting stream on ep %02X", ep); usbredirhost_start_stream_unlocked(host, ep); } } leave: UNLOCK(host); usbredirparser_free_packet_data(host->parser, data); if (status != usb_redir_success) { usbredirhost_send_stream_status(host, id, ep, status); } FLUSH(host); } static void LIBUSB_CALL usbredirhost_interrupt_out_packet_complete( struct libusb_transfer *libusb_transfer) { struct usbredirtransfer *transfer = libusb_transfer->user_data; struct usb_redir_interrupt_packet_header interrupt_packet; struct usbredirhost *host = transfer->host; LOCK(host); interrupt_packet = transfer->interrupt_packet; interrupt_packet.status = libusb_status_or_error_to_redir_status(host, libusb_transfer->status); interrupt_packet.length = libusb_transfer->actual_length; DEBUG("interrupt out complete ep %02X status %d len %d id %"PRIu64, interrupt_packet.endpoint, interrupt_packet.status, interrupt_packet.length, transfer->id); if (!transfer->cancelled) { usbredirparser_send_interrupt_packet(host->parser, transfer->id, &interrupt_packet, NULL, 0); } usbredirhost_remove_and_free_transfer(transfer); UNLOCK(host); FLUSH(host); } static void usbredirhost_send_interrupt_status(struct usbredirhost *host, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_packet, uint8_t status) { interrupt_packet->status = status; interrupt_packet->length = 0; usbredirparser_send_interrupt_packet(host->parser, id, interrupt_packet, NULL, 0); } static void usbredirhost_interrupt_packet(void *priv, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_packet, uint8_t *data, int data_len) { struct usbredirhost *host = priv; uint8_t ep = interrupt_packet->endpoint; struct usbredirtransfer *transfer; int r; DEBUG("interrupt submit ep %02X len %d id %"PRIu64, ep, interrupt_packet->length, id); if (host->disconnected) { usbredirhost_send_interrupt_status(host, id, interrupt_packet, usb_redir_ioerror); usbredirparser_free_packet_data(host->parser, data); FLUSH(host); return; } if (host->endpoint[EP2I(ep)].type != usb_redir_type_interrupt) { ERROR("error received interrupt packet for non interrupt ep %02X", ep); usbredirhost_send_interrupt_status(host, id, interrupt_packet, usb_redir_inval); usbredirparser_free_packet_data(host->parser, data); FLUSH(host); return; } if (data_len > host->endpoint[EP2I(ep)].max_packetsize) { ERROR("error received interrupt out packet is larger than wMaxPacketSize"); usbredirhost_send_interrupt_status(host, id, interrupt_packet, usb_redir_inval); usbredirparser_free_packet_data(host->parser, data); FLUSH(host); return; } usbredirhost_log_data(host, "interrupt data out:", data, data_len); /* Note no memcpy, we can re-use the data buffer the parser malloc-ed for us and expects us to free */ transfer = usbredirhost_alloc_transfer(host, 0); if (!transfer) { usbredirparser_free_packet_data(host->parser, data); return; } host->reset = 0; libusb_fill_interrupt_transfer(transfer->transfer, host->handle, ep, data, data_len, usbredirhost_interrupt_out_packet_complete, transfer, INTERRUPT_TIMEOUT); transfer->id = id; transfer->interrupt_packet = *interrupt_packet; usbredirhost_add_transfer(host, transfer); r = libusb_submit_transfer(transfer->transfer); if (r < 0) { ERROR("error submitting interrupt transfer on ep %02X: %s", ep, libusb_error_name(r)); transfer->transfer->actual_length = 0; transfer->transfer->status = r; usbredirhost_interrupt_out_packet_complete(transfer->transfer); } } /**************************************************************************/ USBREDIR_VISIBLE void usbredirhost_get_guest_filter(struct usbredirhost *host, const struct usbredirfilter_rule **rules_ret, int *rules_count_ret) { *rules_ret = host->filter_rules; *rules_count_ret = host->filter_rules_count; } USBREDIR_VISIBLE int usbredirhost_check_device_filter(const struct usbredirfilter_rule *rules, int rules_count, libusb_device *dev, int flags) { int i, r, num_interfaces; struct libusb_device_descriptor dev_desc; struct libusb_config_descriptor *config = NULL; uint8_t interface_class[MAX_INTERFACES]; uint8_t interface_subclass[MAX_INTERFACES]; uint8_t interface_protocol[MAX_INTERFACES]; r = libusb_get_device_descriptor(dev, &dev_desc); if (r < 0) { if (r == LIBUSB_ERROR_NO_MEM) return -ENOMEM; return -EIO; } r = libusb_get_active_config_descriptor(dev, &config); if (r < 0 && r != LIBUSB_ERROR_NOT_FOUND) { if (r == LIBUSB_ERROR_NO_MEM) return -ENOMEM; return -EIO; } if (config == NULL) { return usbredirfilter_check(rules, rules_count, dev_desc.bDeviceClass, dev_desc.bDeviceSubClass, dev_desc.bDeviceProtocol, NULL, NULL, NULL, 0, dev_desc.idVendor, dev_desc.idProduct, dev_desc.bcdDevice, flags); } num_interfaces = config->bNumInterfaces; for (i = 0; i < num_interfaces; i++) { const struct libusb_interface_descriptor *intf_desc = config->interface[i].altsetting; interface_class[i] = intf_desc->bInterfaceClass; interface_subclass[i] = intf_desc->bInterfaceSubClass; interface_protocol[i] = intf_desc->bInterfaceProtocol; } libusb_free_config_descriptor(config); return usbredirfilter_check(rules, rules_count, dev_desc.bDeviceClass, dev_desc.bDeviceSubClass, dev_desc.bDeviceProtocol, interface_class, interface_subclass, interface_protocol, num_interfaces, dev_desc.idVendor, dev_desc.idProduct, dev_desc.bcdDevice, flags); } usbredir-usbredir-0.11.0/usbredirhost/usbredirhost.h000066400000000000000000000174571410424124300226410ustar00rootroot00000000000000/* usbredirhost.c usb network redirection usb host code header Copyright 2010-2012 Red Hat, Inc. Red Hat Authors: Hans de Goede This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #ifndef __USBREDIRHOST_H #define __USBREDIRHOST_H #include #include "usbredirparser.h" #include "usbredirfilter.h" #ifdef __cplusplus extern "C" { #endif struct usbredirhost; typedef void (*usbredirhost_flush_writes)(void *priv); typedef uint64_t (*usbredirhost_buffered_output_size)(void *priv); /* This function creates an usbredirhost instance, including its embedded libusbredirparser instance and sends the initial usb_redir_hello packet to the usb-guest. If usb_dev_handle is not NULL, usbredirhost_set_device will be called with the passed in usb_dev_handle after creating the instance. If usbredirhost_set_device fails, the instance will be destroyed and NULL will be returned. log_func is called by the usbredirhost to log various messages read_guest_data_func / write_guest_data_func are called by the usbredirhost to read/write data from/to the usb-guest. This function returns a pointer to the created usbredirhost object on success, or NULL on failure. Note: 1) Both the usbredirtransport_log and the usbredirtransport_write callbacks may get called before this function completes. 2) It is the responsibility of the code instantiating the usbredirhost to make sure that libusb_handle_events gets called (using the libusb_context from the passed in libusb_device_handle) when there are events waiting on the filedescriptors libusb_get_pollfds returns 3) usbredirhost is partially multi-thread safe, see docs/multi-thread.md */ enum { usbredirhost_fl_write_cb_owns_buffer = 0x01, /* See usbredirparser.h */ }; struct usbredirhost *usbredirhost_open( libusb_context *usb_ctx, libusb_device_handle *usb_dev_handle, usbredirparser_log log_func, usbredirparser_read read_guest_data_func, usbredirparser_write write_guest_data_func, void *func_priv, const char *version, int verbose, int flags); /* See docs/multi-thread.md */ struct usbredirhost *usbredirhost_open_full( libusb_context *usb_ctx, libusb_device_handle *usb_dev_handle, usbredirparser_log log_func, usbredirparser_read read_guest_data_func, usbredirparser_write write_guest_data_func, usbredirhost_flush_writes flush_writes_func, usbredirparser_alloc_lock alloc_lock_func, usbredirparser_lock lock_func, usbredirparser_unlock unlock_func, usbredirparser_free_lock free_lock_func, void *func_priv, const char *version, int verbose, int flags); /* Closes (destroys) the usbredirhost, if the usbredirhost currently is redirecting a device this function will first call usbredirhost_set_device(host, NULL); See the notes for that function! */ void usbredirhost_close(struct usbredirhost *host); /* Call this function with a valid libusb_device_handle to send the initial device info (interface_info, ep_info and device_connect packets) and make the device available to the usbredir-guest connected to the usbredir-host. Note: 1) This function *takes ownership of* the passed in libusb_device_handle. 2) The passed in libusb_device_handle is closed on failure. 3) If the host already has a device that will get disconnected and closed. Call this function with NULL as usb_dev_handle to disconnect a redirected device from the usbredir-guest and make the device available to the OS on which the usbredir-host is running again. Note when disconnecting a redirected device, this function calls libusb_handle_events to "reap" cancelled urbs before closing the libusb device handle. This means that if you are using the same libusb context for other purposes your transfer complete callbacks may get called! This function returns a usbredirproto.h status code (i.e. usb_redir_success) */ int usbredirhost_set_device(struct usbredirhost *host, libusb_device_handle *usb_dev_handle); /* Call this function to set a callback in usbredirhost. The usbredirhost_buffered_output_size callback should return the application's pending writes buffer size (in bytes). usbredirhost will set two levels of threshold based in the information provided by the usb device. In case the application's buffer is increasing too much then usbredirhost uses the threshold limits to drop isochronous packages but still send full frames whenever is possible. */ void usbredirhost_set_buffered_output_size_cb(struct usbredirhost *host, usbredirhost_buffered_output_size buffered_output_size_func); /* Call this whenever there is data ready for the usbredirhost to read from the usb-guest returns 0 on success, or an error code from the below enum on error. On an usbredirhost_read_io_error this function will continue where it left of the last time on the next call. On an usbredirhost_read_parse_error it will skip to the next packet (*). On an usbredirhost_read_device_rejected error, you are expected to call usbredirhost_set_device(host, NULL). An usbredirhost_read_device_lost error means that the host has done the equivalent of usbredirhost_set_device(host, NULL) itself because the connection to the device was lost. *) As determined by the faulty's package headers length field */ enum { usbredirhost_read_io_error = -1, usbredirhost_read_parse_error = -2, usbredirhost_read_device_rejected = -3, usbredirhost_read_device_lost = -4, }; int usbredirhost_read_guest_data(struct usbredirhost *host); /* This returns the number of usbredir packets queued up for writing */ int usbredirhost_has_data_to_write(struct usbredirhost *host); /* Call this when usbredirhost_has_data_to_write returns > 0 returns 0 on success, -1 if a write error happened. If a write error happened, this function will retry writing any queued data on the next call, and will continue doing so until it has succeeded! */ enum { usbredirhost_write_io_error = -1, }; int usbredirhost_write_guest_data(struct usbredirhost *host); /* When passing the usbredirhost_fl_write_cb_owns_buffer flag to usbredirhost_open, this function must be called to free the data buffer passed to write_guest_data_func when done with this buffer. */ void usbredirhost_free_write_buffer(struct usbredirhost *host, uint8_t *data); /* Get the *usbredir-guest's* filter, if any. If there is no filter, rules is set to NULL and rules_count to 0. */ void usbredirhost_get_guest_filter(struct usbredirhost *host, const struct usbredirfilter_rule **rules_ret, int *rules_count_ret); /* Get device and config descriptors from the USB device dev, and call usbredirfilter_check with the passed in filter rules and the needed info from the descriptors, flags gets passed to usbredirfilter_check unmodified. See the documentation of usbredirfilter_check for more details. Return value: -EIO or -ENOMEM when getting the descriptors fails, otherwise it returns the return value of the usbredirfilter_check call. */ int usbredirhost_check_device_filter(const struct usbredirfilter_rule *rules, int rules_count, libusb_device *dev, int flags); #ifdef __cplusplus } #endif #endif usbredir-usbredir-0.11.0/usbredirhost/usbredirhost.map000066400000000000000000000007321410424124300231530ustar00rootroot00000000000000USBREDIRHOST_0.8.0 { global: usbredirhost_check_device_filter; usbredirhost_close; usbredirhost_free_write_buffer; usbredirhost_get_guest_filter; usbredirhost_has_data_to_write; usbredirhost_open; usbredirhost_open_full; usbredirhost_read_guest_data; usbredirhost_set_buffered_output_size_cb; usbredirhost_set_device; usbredirhost_write_guest_data; local: *; }; # .... define new API here using predicted next version number .... usbredir-usbredir-0.11.0/usbredirparser/000077500000000000000000000000001410424124300202545ustar00rootroot00000000000000usbredir-usbredir-0.11.0/usbredirparser/meson.build000066400000000000000000000036211410424124300224200ustar00rootroot00000000000000# so verison, see: # http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html usbredir_parser_current = 2 usbredir_parser_revision = 0 usbredir_parser_age = 1 usbredir_parser_so_version = '@0@.@1@.@2@'.format( usbredir_parser_current - usbredir_parser_age, usbredir_parser_age, usbredir_parser_revision) summary_info += {'libusbredirparser.so version': usbredir_parser_so_version} usbredir_parser_sources = [ 'usbredirparser.c', 'usbredirfilter.c', 'usbredirproto-compat.h', 'usbredirparser.h', 'usbredirfilter.h', 'usbredirproto.h', ] if host_machine.system() == 'windows' usbredir_parser_sources += [ 'strtok_r.c', 'strtok_r.h', ] endif usbredir_parser_map_file = meson.current_source_dir() / 'usbredirparser.map' usbredir_parser_link_args = compiler.get_supported_link_arguments([ '-Wl,--version-script=@0@'.format(usbredir_parser_map_file), '-Wl,--no-undefined', '-Wl,-dead_strip', ]) usbredir_parser_include_directories = [ include_directories('.'), usbredir_include_root_dir, ] usbredir_parser_lib = library('usbredirparser', usbredir_parser_sources, version : usbredir_parser_so_version, install : true, include_directories: usbredir_parser_include_directories, link_args : [usbredir_parser_link_args], link_depends : usbredir_parser_map_file, gnu_symbol_visibility : 'hidden') usbredir_parser_lib_dep = declare_dependency( link_with: usbredir_parser_lib, include_directories: usbredir_parser_include_directories) headers = [ 'usbredirfilter.h', 'usbredirparser.h', 'usbredirproto.h' ] install_headers(headers) # libusbredirparser.pc pkgconfig = import('pkgconfig') pkgconfig.generate(usbredir_parser_lib, name : 'libusbredirparser-0.5', description : 'usbredirparser library', variables : 'exec_prefix=${prefix}') usbredir-usbredir-0.11.0/usbredirparser/strtok_r.c000066400000000000000000000036051410424124300222730ustar00rootroot00000000000000/* Reentrant string tokenizer. Generic version. Copyright (C) 1991,1996-1999,2001,2004 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; 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 #include /* Parse S into tokens separated by characters in DELIM. If S is NULL, the saved pointer in SAVE_PTR is used as the next starting point. For example: char s[] = "-abc-=-def"; char *sp; x = strtok_r(s, "-", &sp); // x = "abc", sp = "=-def" x = strtok_r(NULL, "-=", &sp); // x = "def", sp = NULL x = strtok_r(NULL, "=", &sp); // x = NULL // s = "abc\0-def\0" */ char * glibc_strtok_r (char *s, const char *delim, char **save_ptr) { char *token; if (s == NULL) s = *save_ptr; /* Scan leading delimiters. */ s += strspn (s, delim); if (*s == '\0') { *save_ptr = s; return NULL; } /* Find the end of the token. */ token = s; s += strcspn (token, delim); if (*s == '\0') /* This token finishes the string. */ *save_ptr = s; else { /* Terminate the token and make *SAVE_PTR point past it. */ *s = '\0'; *save_ptr = s + 1; } return token; } usbredir-usbredir-0.11.0/usbredirparser/strtok_r.h000066400000000000000000000020511410424124300222720ustar00rootroot00000000000000/* Reentrant string tokenizer. Generic version. Copyright (C) 1991,1996-1999,2001,2004 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifndef _USBREDIRPARSER_STRTOK_H_ #define _USBREDIRPARSER_STRTOK_H_ char * glibc_strtok_r(char *s, const char *delim, char **save_ptr); #endif usbredir-usbredir-0.11.0/usbredirparser/usbredirfilter.c000066400000000000000000000226701410424124300234540ustar00rootroot00000000000000/* usbredirfilter.h usb redirection filter header Copyright 2012 Red Hat, Inc. Red Hat Authors: Hans de Goede This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #include "config.h" #include #include #include #ifdef WIN32 #include "strtok_r.h" #define strtok_r glibc_strtok_r #endif #include "usbredirfilter.h" USBREDIR_VISIBLE int usbredirfilter_string_to_rules( const char *filter_str, const char *token_sep, const char *rule_sep, struct usbredirfilter_rule **rules_ret, int *rules_count_ret) { char *rule, *rule_saveptr, *token, *token_saveptr, *ep; struct usbredirfilter_rule *rules = NULL; int i, rules_count, *values, ret = 0; char *buf = NULL; const char *r; if (strlen(token_sep) == 0 || strlen(rule_sep) == 0) { return -EINVAL; } *rules_ret = NULL; *rules_count_ret = 0; /* Figure out how much rules there are in the file, so we know how much memory we must allocate for the rules array. Note this will come up with a slightly too large number if there are empty rule strings in the set. */ r = filter_str; rules_count = 0; for (;;) { r += strspn(r, rule_sep); if (!*r) { break; } rules_count++; r += strcspn(r, rule_sep); } rules = calloc(rules_count, sizeof(struct usbredirfilter_rule)); if (!rules) return -ENOMEM; /* Make a copy since strtok mangles the string */ buf = strdup(filter_str); if (!buf) { ret = -ENOMEM; goto leave; } /* And actually parse the string */ rules_count = 0; rule = strtok_r(buf, rule_sep, &rule_saveptr); while (rule) { /* We treat the filter rule as an array of ints for easier parsing */ values = (int *)&rules[rules_count]; token = strtok_r(rule, token_sep, &token_saveptr); for (i = 0; i < 5 && token; i++) { values[i] = strtol(token, &ep, 0); if (*ep) break; token = strtok_r(NULL, token_sep, &token_saveptr); } if (i != 5 || token != NULL || usbredirfilter_verify(&rules[rules_count], 1)) { ret = -EINVAL; goto leave; } rules_count++; rule = strtok_r(NULL, rule_sep, &rule_saveptr); } *rules_ret = rules; *rules_count_ret = rules_count; leave: if (ret) free(rules); free(buf); return ret; } USBREDIR_VISIBLE char *usbredirfilter_rules_to_string(const struct usbredirfilter_rule *rules, int rules_count, const char *token_sep, const char *rule_sep) { int i; char *str, *p; if (usbredirfilter_verify(rules, rules_count)) return NULL; if (strlen(token_sep) == 0 || strlen(rule_sep) == 0) { return NULL; } /* We need 28 bytes per rule in the worst case */ str = malloc(28 * rules_count + 1); if (!str) return NULL; p = str; for (i = 0; i < rules_count; i++) { if (rules[i].device_class != -1) p += sprintf(p, "0x%02x%c", rules[i].device_class, *token_sep); else p += sprintf(p, "-1%c", *token_sep); if (rules[i].vendor_id != -1) p += sprintf(p, "0x%04x%c", rules[i].vendor_id, *token_sep); else p += sprintf(p, "-1%c", *token_sep); if (rules[i].product_id != -1) p += sprintf(p, "0x%04x%c", rules[i].product_id, *token_sep); else p += sprintf(p, "-1%c", *token_sep); if (rules[i].device_version_bcd != -1) p += sprintf(p, "0x%04x%c", rules[i].device_version_bcd, *token_sep); else p += sprintf(p, "-1%c", *token_sep); p += sprintf(p, "%d", rules[i].allow ? 1:0); if (i < rules_count - 1) { p += sprintf(p, "%c", *rule_sep); } } *p = '\0'; return str; } static int usbredirfilter_check1(const struct usbredirfilter_rule *rules, int rules_count, uint8_t device_class, uint16_t vendor_id, uint16_t product_id, uint16_t device_version_bcd, int default_allow) { int i; for (i = 0; i < rules_count; i++) { if ((rules[i].device_class == -1 || rules[i].device_class == device_class) && (rules[i].vendor_id == -1 || rules[i].vendor_id == vendor_id) && (rules[i].product_id == -1 || rules[i].product_id == product_id) && (rules[i].device_version_bcd == -1 || rules[i].device_version_bcd == device_version_bcd)) { /* Found a match ! */ return rules[i].allow ? 0 : -EPERM; } } return default_allow ? 0 : -ENOENT; } USBREDIR_VISIBLE int usbredirfilter_check( const struct usbredirfilter_rule *rules, int rules_count, uint8_t device_class, uint8_t device_subclass, uint8_t device_protocol, uint8_t *interface_class, uint8_t *interface_subclass, uint8_t *interface_protocol, int interface_count, uint16_t vendor_id, uint16_t product_id, uint16_t device_version_bcd, int flags) { int i, rc, num_skipped=0; if (usbredirfilter_verify(rules, rules_count)) return -EINVAL; /* Check the device_class */ if (device_class != 0x00 && device_class != 0xef) { rc = usbredirfilter_check1(rules, rules_count, device_class, vendor_id, product_id, device_version_bcd, flags & usbredirfilter_fl_default_allow); if (rc) return rc; } /* Check the interface classes */ for (i = 0; i < interface_count; i++) { if (!(flags & usbredirfilter_fl_dont_skip_non_boot_hid) && interface_count > 1 && interface_class[i] == 0x03 && interface_subclass[i] == 0x00 && interface_protocol[i] == 0x00) { num_skipped++; continue; } rc = usbredirfilter_check1(rules, rules_count, interface_class[i], vendor_id, product_id, device_version_bcd, flags & usbredirfilter_fl_default_allow); if (rc) return rc; } /* If all interfaces were skipped, then force check on that device, * by recursively calling this function with a flag that forbids * skipping (usbredirfilter_fl_dont_skip_non_boot_hid) */ if (interface_count > 0 && num_skipped == interface_count) { rc = usbredirfilter_check(rules, rules_count, device_class, device_subclass, device_protocol, interface_class, interface_subclass, interface_protocol, interface_count, vendor_id, product_id, device_version_bcd, flags | usbredirfilter_fl_dont_skip_non_boot_hid); return rc; } return 0; } USBREDIR_VISIBLE int usbredirfilter_verify( const struct usbredirfilter_rule *rules, int rules_count) { int i; for (i = 0; i < rules_count; i++) { if (rules[i].device_class < -1 || rules[i].device_class > 255) return -EINVAL; if (rules[i].vendor_id < -1 || rules[i].vendor_id > 65535) return -EINVAL; if (rules[i].product_id < -1 || rules[i].product_id > 65535) return -EINVAL; if (rules[i].device_version_bcd < -1 || rules[i].device_version_bcd > 65535) return -EINVAL; } return 0; } USBREDIR_VISIBLE void usbredirfilter_print( const struct usbredirfilter_rule *rules, int rules_count, FILE *out) { int i; char device_class[16], vendor[16], product[16], version[16]; for (i = 0; i < rules_count; i++) { if (rules[i].device_class != -1) sprintf(device_class, " %02x", rules[i].device_class); else strcpy(device_class, "ANY"); if (rules[i].vendor_id != -1) sprintf(vendor, "%04x", rules[i].vendor_id); else strcpy(vendor, " ANY"); if (rules[i].product_id != -1) sprintf(product, "%04x", rules[i].product_id); else strcpy(product, " ANY"); if (rules[i].device_version_bcd != -1) sprintf(version, "%2d.%02d", ((rules[i].device_version_bcd & 0xf000) >> 12) * 10 + ((rules[i].device_version_bcd & 0x0f00) >> 8), ((rules[i].device_version_bcd & 0x00f0) >> 4) * 10 + ((rules[i].device_version_bcd & 0x000f))); else strcpy(version, " ANY"); fprintf(out, "Class %s ID %s:%s Version %s %s\n", device_class, vendor, product, version, rules[i].allow ? "Allow":"Block"); } } USBREDIR_VISIBLE void usbredirfilter_free(void *ptr) { /* for compatibility with 0.10.0 and older this MUST call free() */ free(ptr); } usbredir-usbredir-0.11.0/usbredirparser/usbredirfilter.h000066400000000000000000000130421410424124300234520ustar00rootroot00000000000000/* usbredirfilter.h usb redirection filter header Copyright 2012 Red Hat, Inc. Red Hat Authors: Hans de Goede This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #ifndef __USBREDIRFILTER_H #define __USBREDIRFILTER_H #include #include #ifdef __cplusplus extern "C" { #endif struct usbredirfilter_rule { int device_class; /* 0-255, -1 to match any class */ int vendor_id; /* 0-65535, -1 to match any id */ int product_id; /* 0-65535, -1 to match any id */ int device_version_bcd; /* 0-65535, -1 to match any version */ int allow; /* 0: deny redir for this device, non 0: allow */ }; /* Read a filter string and parse it into an array of usbredirfilter_rule-s. Where each rule has the form of: ,,,, Assuming "," as the specified token_sep character set. And the rules are themselves are separated by any rule_sep characters, ie: || Assuming "|" as the rule_sep character set. Note that with the separator used in this example the format matches the format as written by the RHEV-M USB filter editor tool. The separators are used as strtok use separators. On success the rules get returned in rules_ret and rules_count_ret, the returned rules array should be freed with usbredirfilter_free() when the caller is done with it. Return value: 0 on success, -ENOMEM when allocating the rules array fails, or -EINVAL when there is on parsing error. */ int usbredirfilter_string_to_rules( const char *filter_str, const char *token_sep, const char *rule_sep, struct usbredirfilter_rule **rules_ret, int *rules_count_ret); /* Convert a set of rules back to a string suitable for passing to usbredirfilter_string_to_rules(); The returned string must be usbredirfilter_free()-ed by the caller when it is done with it. Return value: The string on success, or NULL if the rules fail verification, or when allocating the string fails. */ char *usbredirfilter_rules_to_string(const struct usbredirfilter_rule *rules, int rules_count, const char *token_sep, const char *rule_sep); /* Check if redirection of a device with the passed in device info is allowed by the passed set of filter rules. Since a device has class info at both the device level and the interface level, this function does multiple passes. First the rules are checked one by one against the given device info using the device class info, if a matching rule is found, the result of the check is that of that rule. Then the same is done for each interface the device has, substituting the device class info with the class info from the interfaces. Note that under certain circumstances some passes are skipped: - For devices with a device class of 0x00 or 0xef, the pass which checks the device class is skipped. - If the usbredirfilter_fl_dont_skip_non_boot_hid flag is not passed, then for devices with more than 1 interface and an interface with an interface class of 0x03, an interface subclass of 0x00 and an interface protocol of 0x00, the check is skipped for that interface. This allows to skip i.e. checking the interface for volume buttons on some usbaudio class devices. If the result of all (not skipped) passes is allow, then 0 will be returned, which indicates that redirection should be allowed. If the result of a matching rule is deny, then processing stops and -EPERM will be returned. If a given pass does not match any rules, then processing stops and -ENOENT will be returned. This behavior can be changed with the usbredirfilter_fl_default_allow flag, if this flag is set the result of a pass with no matching rules will be allow. Return value: 0 Redirection is allowed -EINVAL Invalid parameters -EPERM Redirection is blocked by the filter rules -ENOENT None of the rules matched the device (during one of the passes) */ enum { usbredirfilter_fl_default_allow = 0x01, usbredirfilter_fl_dont_skip_non_boot_hid = 0x02, }; int usbredirfilter_check( const struct usbredirfilter_rule *rules, int rules_count, uint8_t device_class, uint8_t device_subclass, uint8_t device_protocol, uint8_t *interface_class, uint8_t *interface_subclass, uint8_t *interface_protocol, int interface_count, uint16_t vendor_id, uint16_t product_id, uint16_t device_version_bcd, int flags); /* Sanity check the passed in rules Return value: 0 on success, -EINVAL when some values are out of bound. */ int usbredirfilter_verify( const struct usbredirfilter_rule *rules, int rules_count); /* Print the passed in rules to FILE out in human readable format */ void usbredirfilter_print( const struct usbredirfilter_rule *rules, int rules_count, FILE *out); /* Free memory allocated by the library and returned */ void usbredirfilter_free(void *ptr); #ifdef __cplusplus } #endif #endif usbredir-usbredir-0.11.0/usbredirparser/usbredirparser.c000066400000000000000000001750401410424124300234630ustar00rootroot00000000000000/* usbredirparser.c usb redirection protocol parser Copyright 2010-2012 Red Hat, Inc. Red Hat Authors: Hans de Goede This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #include "config.h" #include #include #include #include #include #include #include "usbredirproto-compat.h" #include "usbredirparser.h" #include "usbredirfilter.h" /* Put *some* upper limit on bulk transfer sizes */ #define MAX_BULK_TRANSFER_SIZE (128u * 1024u * 1024u) /* Upper limit for accepted packet sizes including headers; makes the assumption * that no header is longer than 1kB */ #define MAX_PACKET_SIZE (1024u + MAX_BULK_TRANSFER_SIZE) /* Locking convenience macros */ #define LOCK(parser) \ do { \ if ((parser)->lock) \ (parser)->callb.lock_func((parser)->lock); \ } while (0) #define UNLOCK(parser) \ do { \ if ((parser)->lock) \ (parser)->callb.unlock_func((parser)->lock); \ } while (0) struct usbredirparser_buf { uint8_t *buf; int pos; int len; struct usbredirparser_buf *next; }; struct usbredirparser_priv { struct usbredirparser callb; int flags; int have_peer_caps; uint32_t our_caps[USB_REDIR_CAPS_SIZE]; uint32_t peer_caps[USB_REDIR_CAPS_SIZE]; void *lock; union { struct usb_redir_header header; struct usb_redir_header_32bit_id header_32bit_id; }; uint8_t type_header[288]; int header_read; int type_header_len; int type_header_read; uint8_t *data; int data_len; int data_read; int to_skip; struct usbredirparser_buf *write_buf; int write_buf_count; }; static void #if defined __MINGW_PRINTF_FORMAT __attribute__((format(__MINGW_PRINTF_FORMAT, 3, 4))) #elif defined __GNUC__ __attribute__((format(printf, 3, 4))) #endif va_log(struct usbredirparser_priv *parser, int verbose, const char *fmt, ...) { char buf[512]; va_list ap; int n; n = sprintf(buf, "usbredirparser: "); va_start(ap, fmt); vsnprintf(buf + n, sizeof(buf) - n, fmt, ap); va_end(ap); parser->callb.log_func(parser->callb.priv, verbose, buf); } #define ERROR(...) va_log(parser, usbredirparser_error, __VA_ARGS__) #define WARNING(...) va_log(parser, usbredirparser_warning, __VA_ARGS__) #define INFO(...) va_log(parser, usbredirparser_info, __VA_ARGS__) #define DEBUG(...) va_log(parser, usbredirparser_debug, __VA_ARGS__) #if 0 /* Can be enabled and called from random place to test serialization */ static void serialize_test(struct usbredirparser *parser_pub) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; struct usbredirparser_buf *wbuf, *next_wbuf; uint8_t *data; int len; if (usbredirparser_serialize(parser_pub, &data, &len)) return; wbuf = parser->write_buf; while (wbuf) { next_wbuf = wbuf->next; free(wbuf->buf); free(wbuf); wbuf = next_wbuf; } parser->write_buf = NULL; parser->write_buf_count = 0; free(parser->data); parser->data = NULL; parser->type_header_len = parser->data_len = parser->have_peer_caps = 0; usbredirparser_unserialize(parser_pub, data, len); free(data); } #endif static void usbredirparser_queue(struct usbredirparser *parser, uint32_t type, uint64_t id, void *type_header_in, uint8_t *data_in, int data_len); static int usbredirparser_caps_get_cap(struct usbredirparser_priv *parser, uint32_t *caps, int cap); USBREDIR_VISIBLE struct usbredirparser *usbredirparser_create(void) { return calloc(1, sizeof(struct usbredirparser_priv)); } static void usbredirparser_verify_caps(struct usbredirparser_priv *parser, uint32_t *caps, const char *desc) { if (usbredirparser_caps_get_cap(parser, caps, usb_redir_cap_bulk_streams) && !usbredirparser_caps_get_cap(parser, caps, usb_redir_cap_ep_info_max_packet_size)) { ERROR("error %s caps contains cap_bulk_streams without" "cap_ep_info_max_packet_size", desc); caps[0] &= ~(1 << usb_redir_cap_bulk_streams); } } USBREDIR_VISIBLE void usbredirparser_init(struct usbredirparser *parser_pub, const char *version, uint32_t *caps, int caps_len, int flags) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; struct usb_redir_hello_header hello = { { 0 }, }; parser->flags = (flags & ~usbredirparser_fl_no_hello); if (parser->callb.alloc_lock_func) { parser->lock = parser->callb.alloc_lock_func(); } snprintf(hello.version, sizeof(hello.version), "%s", version); if (caps_len > USB_REDIR_CAPS_SIZE) { caps_len = USB_REDIR_CAPS_SIZE; } memcpy(parser->our_caps, caps, caps_len * sizeof(uint32_t)); /* libusbredirparser handles sending the ack internally */ if (!(flags & usbredirparser_fl_usb_host)) usbredirparser_caps_set_cap(parser->our_caps, usb_redir_cap_device_disconnect_ack); usbredirparser_verify_caps(parser, parser->our_caps, "our"); if (!(flags & usbredirparser_fl_no_hello)) usbredirparser_queue(parser_pub, usb_redir_hello, 0, &hello, (uint8_t *)parser->our_caps, USB_REDIR_CAPS_SIZE * sizeof(uint32_t)); } USBREDIR_VISIBLE void usbredirparser_destroy(struct usbredirparser *parser_pub) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; struct usbredirparser_buf *wbuf, *next_wbuf; free(parser->data); parser->data = NULL; wbuf = parser->write_buf; while (wbuf) { next_wbuf = wbuf->next; free(wbuf->buf); free(wbuf); wbuf = next_wbuf; } if (parser->lock) parser->callb.free_lock_func(parser->lock); free(parser); } static int usbredirparser_caps_get_cap(struct usbredirparser_priv *parser, uint32_t *caps, int cap) { if (cap / 32 >= USB_REDIR_CAPS_SIZE) { ERROR("error request for out of bounds cap: %d", cap); return 0; } if (caps[cap / 32] & (1 << (cap % 32))) { return 1; } else { return 0; } } USBREDIR_VISIBLE void usbredirparser_caps_set_cap(uint32_t *caps, int cap) { caps[cap / 32] |= 1 << (cap % 32); } USBREDIR_VISIBLE int usbredirparser_have_peer_caps(struct usbredirparser *parser_pub) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; return parser->have_peer_caps; } USBREDIR_VISIBLE int usbredirparser_peer_has_cap(struct usbredirparser *parser_pub, int cap) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; return usbredirparser_caps_get_cap(parser, parser->peer_caps, cap); } USBREDIR_VISIBLE int usbredirparser_have_cap(struct usbredirparser *parser_pub, int cap) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; return usbredirparser_caps_get_cap(parser, parser->our_caps, cap); } static int usbredirparser_using_32bits_ids(struct usbredirparser *parser_pub) { return !usbredirparser_have_cap(parser_pub, usb_redir_cap_64bits_ids) || !usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_64bits_ids); } static void usbredirparser_handle_hello(struct usbredirparser *parser_pub, struct usb_redir_hello_header *hello, uint8_t *data, int data_len) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; uint32_t *peer_caps = (uint32_t *)data; char buf[64]; int i; if (parser->have_peer_caps) { ERROR("Received second hello message, ignoring"); return; } /* In case hello->version is not 0 terminated (which would be a protocol violation)_ */ strncpy(buf, hello->version, sizeof(buf)); buf[sizeof(buf)-1] = '\0'; memset(parser->peer_caps, 0, sizeof(parser->peer_caps)); if (data_len > sizeof(parser->peer_caps)) { data_len = sizeof(parser->peer_caps); } for (i = 0; i < data_len / sizeof(uint32_t); i++) { parser->peer_caps[i] = peer_caps[i]; } usbredirparser_verify_caps(parser, parser->peer_caps, "peer"); parser->have_peer_caps = 1; INFO("Peer version: %s, using %d-bits ids", buf, usbredirparser_using_32bits_ids(parser_pub) ? 32 : 64); /* Added in 0.3.2, so no guarantee it is there */ if (parser->callb.hello_func) parser->callb.hello_func(parser->callb.priv, hello); } static int usbredirparser_get_header_len(struct usbredirparser *parser_pub) { if (usbredirparser_using_32bits_ids(parser_pub)) return sizeof(struct usb_redir_header_32bit_id); else return sizeof(struct usb_redir_header); } static int usbredirparser_get_type_header_len( struct usbredirparser *parser_pub, int32_t type, int send) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; int command_for_host = 0; if (parser->flags & usbredirparser_fl_usb_host) { command_for_host = 1; } if (send) { command_for_host = !command_for_host; } switch (type) { case usb_redir_hello: return sizeof(struct usb_redir_hello_header); case usb_redir_device_connect: if (!command_for_host) { if (usbredirparser_have_cap(parser_pub, usb_redir_cap_connect_device_version) && usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_connect_device_version)) { return sizeof(struct usb_redir_device_connect_header); } else { return sizeof(struct usb_redir_device_connect_header_no_device_version); } } else { return -1; } case usb_redir_device_disconnect: if (!command_for_host) { return 0; } else { return -1; } case usb_redir_reset: if (command_for_host) { return 0; /* No packet type specific header */ } else { return -1; } case usb_redir_interface_info: if (!command_for_host) { return sizeof(struct usb_redir_interface_info_header); } else { return -1; } case usb_redir_ep_info: if (!command_for_host) { if (usbredirparser_have_cap(parser_pub, usb_redir_cap_bulk_streams) && usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_bulk_streams)) { return sizeof(struct usb_redir_ep_info_header); } else if (usbredirparser_have_cap(parser_pub, usb_redir_cap_ep_info_max_packet_size) && usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_ep_info_max_packet_size)) { return sizeof(struct usb_redir_ep_info_header_no_max_streams); } else { return sizeof(struct usb_redir_ep_info_header_no_max_pktsz); } } else { return -1; } case usb_redir_set_configuration: if (command_for_host) { return sizeof(struct usb_redir_set_configuration_header); } else { return -1; /* Should never be send to a guest */ } case usb_redir_get_configuration: if (command_for_host) { return 0; /* No packet type specific header */ } else { return -1; } case usb_redir_configuration_status: if (!command_for_host) { return sizeof(struct usb_redir_configuration_status_header); } else { return -1; } case usb_redir_set_alt_setting: if (command_for_host) { return sizeof(struct usb_redir_set_alt_setting_header); } else { return -1; } case usb_redir_get_alt_setting: if (command_for_host) { return sizeof(struct usb_redir_get_alt_setting_header); } else { return -1; } case usb_redir_alt_setting_status: if (!command_for_host) { return sizeof(struct usb_redir_alt_setting_status_header); } else { return -1; } case usb_redir_start_iso_stream: if (command_for_host) { return sizeof(struct usb_redir_start_iso_stream_header); } else { return -1; } case usb_redir_stop_iso_stream: if (command_for_host) { return sizeof(struct usb_redir_stop_iso_stream_header); } else { return -1; } case usb_redir_iso_stream_status: if (!command_for_host) { return sizeof(struct usb_redir_iso_stream_status_header); } else { return -1; } case usb_redir_start_interrupt_receiving: if (command_for_host) { return sizeof(struct usb_redir_start_interrupt_receiving_header); } else { return -1; } case usb_redir_stop_interrupt_receiving: if (command_for_host) { return sizeof(struct usb_redir_stop_interrupt_receiving_header); } else { return -1; } case usb_redir_interrupt_receiving_status: if (!command_for_host) { return sizeof(struct usb_redir_interrupt_receiving_status_header); } else { return -1; } case usb_redir_alloc_bulk_streams: if (command_for_host) { return sizeof(struct usb_redir_alloc_bulk_streams_header); } else { return -1; } case usb_redir_free_bulk_streams: if (command_for_host) { return sizeof(struct usb_redir_free_bulk_streams_header); } else { return -1; } case usb_redir_bulk_streams_status: if (!command_for_host) { return sizeof(struct usb_redir_bulk_streams_status_header); } else { return -1; } case usb_redir_cancel_data_packet: if (command_for_host) { return 0; /* No packet type specific header */ } else { return -1; } case usb_redir_filter_reject: if (command_for_host) { return 0; } else { return -1; } case usb_redir_filter_filter: return 0; case usb_redir_device_disconnect_ack: if (command_for_host) { return 0; } else { return -1; } case usb_redir_start_bulk_receiving: if (command_for_host) { return sizeof(struct usb_redir_start_bulk_receiving_header); } else { return -1; } case usb_redir_stop_bulk_receiving: if (command_for_host) { return sizeof(struct usb_redir_stop_bulk_receiving_header); } else { return -1; } case usb_redir_bulk_receiving_status: if (!command_for_host) { return sizeof(struct usb_redir_bulk_receiving_status_header); } else { return -1; } case usb_redir_control_packet: return sizeof(struct usb_redir_control_packet_header); case usb_redir_bulk_packet: if (usbredirparser_have_cap(parser_pub, usb_redir_cap_32bits_bulk_length) && usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_32bits_bulk_length)) { return sizeof(struct usb_redir_bulk_packet_header); } else { return sizeof(struct usb_redir_bulk_packet_header_16bit_length); } case usb_redir_iso_packet: return sizeof(struct usb_redir_iso_packet_header); case usb_redir_interrupt_packet: return sizeof(struct usb_redir_interrupt_packet_header); case usb_redir_buffered_bulk_packet: if (!command_for_host) { return sizeof(struct usb_redir_buffered_bulk_packet_header); } else { return -1; } default: return -1; } } /* Note this function only checks if extra data is allowed for the packet type being read at all, a check if it is actually allowed given the direction of the packet + ep is done in _verify_type_header */ static int usbredirparser_expect_extra_data(struct usbredirparser_priv *parser) { switch (parser->header.type) { case usb_redir_hello: /* For the variable length capabilities array */ case usb_redir_filter_filter: case usb_redir_control_packet: case usb_redir_bulk_packet: case usb_redir_iso_packet: case usb_redir_interrupt_packet: case usb_redir_buffered_bulk_packet: return 1; default: return 0; } } static int usbredirparser_verify_bulk_recv_cap( struct usbredirparser *parser_pub, int send) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; if ((send && !usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_bulk_receiving)) || (!send && !usbredirparser_have_cap(parser_pub, usb_redir_cap_bulk_receiving))) { ERROR("error bulk_receiving without cap_bulk_receiving"); return 0; } return 1; /* Verify ok */ } static int usbredirparser_verify_type_header( struct usbredirparser *parser_pub, int32_t type, void *header, uint8_t *data, int data_len, int send) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; int command_for_host = 0, expect_extra_data = 0; uint32_t length = 0; int ep = -1; if (parser->flags & usbredirparser_fl_usb_host) { command_for_host = 1; } if (send) { command_for_host = !command_for_host; } switch (type) { case usb_redir_interface_info: { struct usb_redir_interface_info_header *intf_info = header; if (intf_info->interface_count > 32) { ERROR("error interface_count > 32"); return 0; } break; } case usb_redir_start_interrupt_receiving: { struct usb_redir_start_interrupt_receiving_header *start_int = header; if (!(start_int->endpoint & 0x80)) { ERROR("start int receiving on non input ep %02x", start_int->endpoint); return 0; } break; } case usb_redir_stop_interrupt_receiving: { struct usb_redir_stop_interrupt_receiving_header *stop_int = header; if (!(stop_int->endpoint & 0x80)) { ERROR("stop int receiving on non input ep %02x", stop_int->endpoint); return 0; } break; } case usb_redir_interrupt_receiving_status: { struct usb_redir_interrupt_receiving_status_header *int_status = header; if (!(int_status->endpoint & 0x80)) { ERROR("int receiving status for non input ep %02x", int_status->endpoint); return 0; } break; } case usb_redir_filter_reject: if ((send && !usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_filter)) || (!send && !usbredirparser_have_cap(parser_pub, usb_redir_cap_filter))) { ERROR("error filter_reject without cap_filter"); return 0; } break; case usb_redir_filter_filter: if ((send && !usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_filter)) || (!send && !usbredirparser_have_cap(parser_pub, usb_redir_cap_filter))) { ERROR("error filter_filter without cap_filter"); return 0; } if (data_len < 1) { ERROR("error filter_filter without data"); return 0; } if (data[data_len - 1] != 0) { ERROR("error non 0 terminated filter_filter data"); return 0; } break; case usb_redir_device_disconnect_ack: if ((send && !usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_device_disconnect_ack)) || (!send && !usbredirparser_have_cap(parser_pub, usb_redir_cap_device_disconnect_ack))) { ERROR("error device_disconnect_ack without cap_device_disconnect_ack"); return 0; } break; case usb_redir_start_bulk_receiving: { struct usb_redir_start_bulk_receiving_header *start_bulk = header; if (!usbredirparser_verify_bulk_recv_cap(parser_pub, send)) { return 0; } if (start_bulk->bytes_per_transfer > MAX_BULK_TRANSFER_SIZE) { ERROR("start bulk receiving length exceeds limits %u > %u", start_bulk->bytes_per_transfer, MAX_BULK_TRANSFER_SIZE); return 0; } if (!(start_bulk->endpoint & 0x80)) { ERROR("start bulk receiving on non input ep %02x", start_bulk->endpoint); return 0; } break; } case usb_redir_stop_bulk_receiving: { struct usb_redir_stop_bulk_receiving_header *stop_bulk = header; if (!usbredirparser_verify_bulk_recv_cap(parser_pub, send)) { return 0; } if (!(stop_bulk->endpoint & 0x80)) { ERROR("stop bulk receiving on non input ep %02x", stop_bulk->endpoint); return 0; } break; } case usb_redir_bulk_receiving_status: { struct usb_redir_bulk_receiving_status_header *bulk_status = header; if (!usbredirparser_verify_bulk_recv_cap(parser_pub, send)) { return 0; } if (!(bulk_status->endpoint & 0x80)) { ERROR("bulk receiving status for non input ep %02x", bulk_status->endpoint); return 0; } break; } case usb_redir_control_packet: length = ((struct usb_redir_control_packet_header *)header)->length; ep = ((struct usb_redir_control_packet_header *)header)->endpoint; break; case usb_redir_bulk_packet: { struct usb_redir_bulk_packet_header *bulk_packet = header; if (usbredirparser_have_cap(parser_pub, usb_redir_cap_32bits_bulk_length) && usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_32bits_bulk_length)) { length = (((uint32_t)bulk_packet->length_high) << 16) | bulk_packet->length; } else { length = bulk_packet->length; if (!send) bulk_packet->length_high = 0; } if (length > MAX_BULK_TRANSFER_SIZE) { ERROR("bulk transfer length exceeds limits %u > %u", (uint32_t)length, MAX_BULK_TRANSFER_SIZE); return 0; } ep = bulk_packet->endpoint; break; } case usb_redir_iso_packet: length = ((struct usb_redir_iso_packet_header *)header)->length; ep = ((struct usb_redir_iso_packet_header *)header)->endpoint; break; case usb_redir_interrupt_packet: length = ((struct usb_redir_interrupt_packet_header *)header)->length; ep = ((struct usb_redir_interrupt_packet_header *)header)->endpoint; break; case usb_redir_buffered_bulk_packet: { struct usb_redir_buffered_bulk_packet_header *buf_bulk_pkt = header; length = buf_bulk_pkt->length; if (!usbredirparser_verify_bulk_recv_cap(parser_pub, send)) { return 0; } if ((uint32_t)length > MAX_BULK_TRANSFER_SIZE) { ERROR("buffered bulk transfer length exceeds limits %u > %u", (uint32_t)length, MAX_BULK_TRANSFER_SIZE); return 0; } ep = buf_bulk_pkt->endpoint; break; } } if (ep != -1) { if (((ep & 0x80) && !command_for_host) || (!(ep & 0x80) && command_for_host)) { expect_extra_data = 1; } if (expect_extra_data) { if (data_len != length) { ERROR("error data len %d != header len %d ep %02X", data_len, length, ep); return 0; } } else { if (data || data_len) { ERROR("error unexpected extra data ep %02X", ep); return 0; } switch (type) { case usb_redir_iso_packet: ERROR("error iso packet send in wrong direction"); return 0; case usb_redir_interrupt_packet: if (command_for_host) { ERROR("error interrupt packet send in wrong direction"); return 0; } break; case usb_redir_buffered_bulk_packet: ERROR("error buffered bulk packet send in wrong direction"); return 0; } } } return 1; /* Verify ok */ } static void usbredirparser_call_type_func(struct usbredirparser *parser_pub, bool *data_ownership_transferred) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; uint64_t id; if (usbredirparser_using_32bits_ids(parser_pub)) id = parser->header_32bit_id.id; else id = parser->header.id; switch (parser->header.type) { case usb_redir_hello: usbredirparser_handle_hello(parser_pub, (struct usb_redir_hello_header *)parser->type_header, parser->data, parser->data_len); break; case usb_redir_device_connect: parser->callb.device_connect_func(parser->callb.priv, (struct usb_redir_device_connect_header *)parser->type_header); break; case usb_redir_device_disconnect: parser->callb.device_disconnect_func(parser->callb.priv); if (usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_device_disconnect_ack)) usbredirparser_queue(parser_pub, usb_redir_device_disconnect_ack, 0, NULL, NULL, 0); break; case usb_redir_reset: parser->callb.reset_func(parser->callb.priv); break; case usb_redir_interface_info: parser->callb.interface_info_func(parser->callb.priv, (struct usb_redir_interface_info_header *)parser->type_header); break; case usb_redir_ep_info: parser->callb.ep_info_func(parser->callb.priv, (struct usb_redir_ep_info_header *)parser->type_header); break; case usb_redir_set_configuration: parser->callb.set_configuration_func(parser->callb.priv, id, (struct usb_redir_set_configuration_header *)parser->type_header); break; case usb_redir_get_configuration: parser->callb.get_configuration_func(parser->callb.priv, id); break; case usb_redir_configuration_status: parser->callb.configuration_status_func(parser->callb.priv, id, (struct usb_redir_configuration_status_header *)parser->type_header); break; case usb_redir_set_alt_setting: parser->callb.set_alt_setting_func(parser->callb.priv, id, (struct usb_redir_set_alt_setting_header *)parser->type_header); break; case usb_redir_get_alt_setting: parser->callb.get_alt_setting_func(parser->callb.priv, id, (struct usb_redir_get_alt_setting_header *)parser->type_header); break; case usb_redir_alt_setting_status: parser->callb.alt_setting_status_func(parser->callb.priv, id, (struct usb_redir_alt_setting_status_header *)parser->type_header); break; case usb_redir_start_iso_stream: parser->callb.start_iso_stream_func(parser->callb.priv, id, (struct usb_redir_start_iso_stream_header *)parser->type_header); break; case usb_redir_stop_iso_stream: parser->callb.stop_iso_stream_func(parser->callb.priv, id, (struct usb_redir_stop_iso_stream_header *)parser->type_header); break; case usb_redir_iso_stream_status: parser->callb.iso_stream_status_func(parser->callb.priv, id, (struct usb_redir_iso_stream_status_header *)parser->type_header); break; case usb_redir_start_interrupt_receiving: parser->callb.start_interrupt_receiving_func(parser->callb.priv, id, (struct usb_redir_start_interrupt_receiving_header *) parser->type_header); break; case usb_redir_stop_interrupt_receiving: parser->callb.stop_interrupt_receiving_func(parser->callb.priv, id, (struct usb_redir_stop_interrupt_receiving_header *) parser->type_header); break; case usb_redir_interrupt_receiving_status: parser->callb.interrupt_receiving_status_func(parser->callb.priv, id, (struct usb_redir_interrupt_receiving_status_header *) parser->type_header); break; case usb_redir_alloc_bulk_streams: parser->callb.alloc_bulk_streams_func(parser->callb.priv, id, (struct usb_redir_alloc_bulk_streams_header *)parser->type_header); break; case usb_redir_free_bulk_streams: parser->callb.free_bulk_streams_func(parser->callb.priv, id, (struct usb_redir_free_bulk_streams_header *)parser->type_header); break; case usb_redir_bulk_streams_status: parser->callb.bulk_streams_status_func(parser->callb.priv, id, (struct usb_redir_bulk_streams_status_header *)parser->type_header); break; case usb_redir_cancel_data_packet: parser->callb.cancel_data_packet_func(parser->callb.priv, id); break; case usb_redir_filter_reject: parser->callb.filter_reject_func(parser->callb.priv); break; case usb_redir_filter_filter: { struct usbredirfilter_rule *rules; int r, count; r = usbredirfilter_string_to_rules((char *)parser->data, ",", "|", &rules, &count); if (r) { ERROR("error parsing filter (%d), ignoring filter message", r); break; } parser->callb.filter_filter_func(parser->callb.priv, rules, count); break; } case usb_redir_device_disconnect_ack: parser->callb.device_disconnect_ack_func(parser->callb.priv); break; case usb_redir_start_bulk_receiving: parser->callb.start_bulk_receiving_func(parser->callb.priv, id, (struct usb_redir_start_bulk_receiving_header *) parser->type_header); break; case usb_redir_stop_bulk_receiving: parser->callb.stop_bulk_receiving_func(parser->callb.priv, id, (struct usb_redir_stop_bulk_receiving_header *) parser->type_header); break; case usb_redir_bulk_receiving_status: parser->callb.bulk_receiving_status_func(parser->callb.priv, id, (struct usb_redir_bulk_receiving_status_header *) parser->type_header); break; case usb_redir_control_packet: *data_ownership_transferred = true; parser->callb.control_packet_func(parser->callb.priv, id, (struct usb_redir_control_packet_header *)parser->type_header, parser->data, parser->data_len); break; case usb_redir_bulk_packet: *data_ownership_transferred = true; parser->callb.bulk_packet_func(parser->callb.priv, id, (struct usb_redir_bulk_packet_header *)parser->type_header, parser->data, parser->data_len); break; case usb_redir_iso_packet: *data_ownership_transferred = true; parser->callb.iso_packet_func(parser->callb.priv, id, (struct usb_redir_iso_packet_header *)parser->type_header, parser->data, parser->data_len); break; case usb_redir_interrupt_packet: *data_ownership_transferred = true; parser->callb.interrupt_packet_func(parser->callb.priv, id, (struct usb_redir_interrupt_packet_header *)parser->type_header, parser->data, parser->data_len); break; case usb_redir_buffered_bulk_packet: *data_ownership_transferred = true; parser->callb.buffered_bulk_packet_func(parser->callb.priv, id, (struct usb_redir_buffered_bulk_packet_header *)parser->type_header, parser->data, parser->data_len); break; } } USBREDIR_VISIBLE int usbredirparser_do_read(struct usbredirparser *parser_pub) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; int r, header_len, type_header_len, data_len; bool data_ownership_transferred; uint8_t *dest; header_len = usbredirparser_get_header_len(parser_pub); /* Skip forward to next packet (only used in error conditions) */ while (parser->to_skip > 0) { uint8_t buf[65536]; r = (parser->to_skip > sizeof(buf)) ? sizeof(buf) : parser->to_skip; r = parser->callb.read_func(parser->callb.priv, buf, r); if (r <= 0) return r; parser->to_skip -= r; } /* Consume data until read would block or returns an error */ while (1) { if (parser->header_read < header_len) { r = header_len - parser->header_read; dest = (uint8_t *)&parser->header + parser->header_read; } else if (parser->type_header_read < parser->type_header_len) { r = parser->type_header_len - parser->type_header_read; dest = parser->type_header + parser->type_header_read; } else { r = parser->data_len - parser->data_read; dest = parser->data + parser->data_read; } if (r > 0) { r = parser->callb.read_func(parser->callb.priv, dest, r); if (r <= 0) { return r; } } if (parser->header_read < header_len) { parser->header_read += r; if (parser->header_read == header_len) { type_header_len = usbredirparser_get_type_header_len(parser_pub, parser->header.type, 0); if (type_header_len < 0) { ERROR("error invalid usb-redir packet type: %u", parser->header.type); parser->to_skip = parser->header.length; parser->header_read = 0; return usbredirparser_read_parse_error; } /* This should never happen */ if (type_header_len > sizeof(parser->type_header)) { ERROR("error type specific header buffer too small, please report!!"); parser->to_skip = parser->header.length; parser->header_read = 0; return usbredirparser_read_parse_error; } if (parser->header.length > MAX_PACKET_SIZE) { ERROR("packet length of %d larger than permitted %d bytes", parser->header.length, MAX_PACKET_SIZE); parser->to_skip = parser->header.length; parser->header_read = 0; return usbredirparser_read_parse_error; } if ((int)parser->header.length < type_header_len || ((int)parser->header.length > type_header_len && !usbredirparser_expect_extra_data(parser))) { ERROR("error invalid packet type %u length: %u", parser->header.type, parser->header.length); parser->to_skip = parser->header.length; parser->header_read = 0; return usbredirparser_read_parse_error; } data_len = parser->header.length - type_header_len; if (data_len) { parser->data = malloc(data_len); if (!parser->data) { ERROR("Out of memory allocating data buffer"); parser->to_skip = parser->header.length; parser->header_read = 0; return usbredirparser_read_parse_error; } } parser->type_header_len = type_header_len; parser->data_len = data_len; } } else if (parser->type_header_read < parser->type_header_len) { parser->type_header_read += r; } else { parser->data_read += r; if (parser->data_read == parser->data_len) { r = usbredirparser_verify_type_header(parser_pub, parser->header.type, parser->type_header, parser->data, parser->data_len, 0); data_ownership_transferred = false; if (r) { usbredirparser_call_type_func(parser_pub, &data_ownership_transferred); } if (!data_ownership_transferred) { free(parser->data); } parser->header_read = 0; parser->type_header_len = 0; parser->type_header_read = 0; parser->data_len = 0; parser->data_read = 0; parser->data = NULL; if (!r) return usbredirparser_read_parse_error; /* header len may change if this was an hello packet */ header_len = usbredirparser_get_header_len(parser_pub); } } } } USBREDIR_VISIBLE int usbredirparser_has_data_to_write(struct usbredirparser *parser_pub) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; return parser->write_buf_count; } USBREDIR_VISIBLE int usbredirparser_do_write(struct usbredirparser *parser_pub) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; struct usbredirparser_buf* wbuf; int w, ret = 0; LOCK(parser); for (;;) { wbuf = parser->write_buf; if (!wbuf) break; w = wbuf->len - wbuf->pos; w = parser->callb.write_func(parser->callb.priv, wbuf->buf + wbuf->pos, w); if (w <= 0) { ret = w; break; } /* See usbredirparser_write documentation */ if ((parser->flags & usbredirparser_fl_write_cb_owns_buffer) && w != wbuf->len) abort(); wbuf->pos += w; if (wbuf->pos == wbuf->len) { parser->write_buf = wbuf->next; if (!(parser->flags & usbredirparser_fl_write_cb_owns_buffer)) free(wbuf->buf); free(wbuf); parser->write_buf_count--; } } UNLOCK(parser); return ret; } USBREDIR_VISIBLE void usbredirparser_free_write_buffer(struct usbredirparser *parser, uint8_t *data) { free(data); } USBREDIR_VISIBLE void usbredirparser_free_packet_data(struct usbredirparser *parser, uint8_t *data) { free(data); } static void usbredirparser_queue(struct usbredirparser *parser_pub, uint32_t type, uint64_t id, void *type_header_in, uint8_t *data_in, int data_len) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; uint8_t *buf, *type_header_out, *data_out; struct usb_redir_header *header; struct usbredirparser_buf *wbuf, *new_wbuf; int header_len, type_header_len; header_len = usbredirparser_get_header_len(parser_pub); type_header_len = usbredirparser_get_type_header_len(parser_pub, type, 1); if (type_header_len < 0) { /* This should never happen */ ERROR("error packet type unknown with internal call, please report!!"); return; } if (!usbredirparser_verify_type_header(parser_pub, type, type_header_in, data_in, data_len, 1)) { ERROR("error usbredirparser_send_* call invalid params, please report!!"); return; } new_wbuf = calloc(1, sizeof(*new_wbuf)); buf = malloc(header_len + type_header_len + data_len); if (!new_wbuf || !buf) { ERROR("Out of memory allocating buffer to send packet, dropping!"); free(new_wbuf); free(buf); return; } new_wbuf->buf = buf; new_wbuf->len = header_len + type_header_len + data_len; header = (struct usb_redir_header *)buf; type_header_out = buf + header_len; data_out = type_header_out + type_header_len; header->type = type; header->length = type_header_len + data_len; if (usbredirparser_using_32bits_ids(parser_pub)) ((struct usb_redir_header_32bit_id *)header)->id = id; else header->id = id; memcpy(type_header_out, type_header_in, type_header_len); memcpy(data_out, data_in, data_len); LOCK(parser); if (!parser->write_buf) { parser->write_buf = new_wbuf; } else { /* limiting the write_buf's stack depth is our users responsibility */ wbuf = parser->write_buf; while (wbuf->next) wbuf = wbuf->next; wbuf->next = new_wbuf; } parser->write_buf_count++; UNLOCK(parser); } USBREDIR_VISIBLE void usbredirparser_send_device_connect(struct usbredirparser *parser, struct usb_redir_device_connect_header *device_connect) { usbredirparser_queue(parser, usb_redir_device_connect, 0, device_connect, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_device_disconnect(struct usbredirparser *parser) { usbredirparser_queue(parser, usb_redir_device_disconnect, 0, NULL, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_reset(struct usbredirparser *parser) { usbredirparser_queue(parser, usb_redir_reset, 0, NULL, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_interface_info(struct usbredirparser *parser, struct usb_redir_interface_info_header *interface_info) { usbredirparser_queue(parser, usb_redir_interface_info, 0, interface_info, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_ep_info(struct usbredirparser *parser, struct usb_redir_ep_info_header *ep_info) { usbredirparser_queue(parser, usb_redir_ep_info, 0, ep_info, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_set_configuration(struct usbredirparser *parser, uint64_t id, struct usb_redir_set_configuration_header *set_configuration) { usbredirparser_queue(parser, usb_redir_set_configuration, id, set_configuration, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_get_configuration(struct usbredirparser *parser, uint64_t id) { usbredirparser_queue(parser, usb_redir_get_configuration, id, NULL, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_configuration_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_configuration_status_header *configuration_status) { usbredirparser_queue(parser, usb_redir_configuration_status, id, configuration_status, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_set_alt_setting(struct usbredirparser *parser, uint64_t id, struct usb_redir_set_alt_setting_header *set_alt_setting) { usbredirparser_queue(parser, usb_redir_set_alt_setting, id, set_alt_setting, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_get_alt_setting(struct usbredirparser *parser, uint64_t id, struct usb_redir_get_alt_setting_header *get_alt_setting) { usbredirparser_queue(parser, usb_redir_get_alt_setting, id, get_alt_setting, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_alt_setting_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_alt_setting_status_header *alt_setting_status) { usbredirparser_queue(parser, usb_redir_alt_setting_status, id, alt_setting_status, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_start_iso_stream(struct usbredirparser *parser, uint64_t id, struct usb_redir_start_iso_stream_header *start_iso_stream) { usbredirparser_queue(parser, usb_redir_start_iso_stream, id, start_iso_stream, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_stop_iso_stream(struct usbredirparser *parser, uint64_t id, struct usb_redir_stop_iso_stream_header *stop_iso_stream) { usbredirparser_queue(parser, usb_redir_stop_iso_stream, id, stop_iso_stream, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_iso_stream_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_iso_stream_status_header *iso_stream_status) { usbredirparser_queue(parser, usb_redir_iso_stream_status, id, iso_stream_status, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_start_interrupt_receiving(struct usbredirparser *parser, uint64_t id, struct usb_redir_start_interrupt_receiving_header *start_interrupt_receiving) { usbredirparser_queue(parser, usb_redir_start_interrupt_receiving, id, start_interrupt_receiving, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_stop_interrupt_receiving(struct usbredirparser *parser, uint64_t id, struct usb_redir_stop_interrupt_receiving_header *stop_interrupt_receiving) { usbredirparser_queue(parser, usb_redir_stop_interrupt_receiving, id, stop_interrupt_receiving, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_interrupt_receiving_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status) { usbredirparser_queue(parser, usb_redir_interrupt_receiving_status, id, interrupt_receiving_status, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_alloc_bulk_streams(struct usbredirparser *parser, uint64_t id, struct usb_redir_alloc_bulk_streams_header *alloc_bulk_streams) { usbredirparser_queue(parser, usb_redir_alloc_bulk_streams, id, alloc_bulk_streams, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_free_bulk_streams(struct usbredirparser *parser, uint64_t id, struct usb_redir_free_bulk_streams_header *free_bulk_streams) { usbredirparser_queue(parser, usb_redir_free_bulk_streams, id, free_bulk_streams, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_bulk_streams_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status) { usbredirparser_queue(parser, usb_redir_bulk_streams_status, id, bulk_streams_status, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_cancel_data_packet(struct usbredirparser *parser, uint64_t id) { usbredirparser_queue(parser, usb_redir_cancel_data_packet, id, NULL, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_filter_reject(struct usbredirparser *parser) { if (!usbredirparser_peer_has_cap(parser, usb_redir_cap_filter)) return; usbredirparser_queue(parser, usb_redir_filter_reject, 0, NULL, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_filter_filter(struct usbredirparser *parser_pub, const struct usbredirfilter_rule *rules, int rules_count) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; char *str; if (!usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_filter)) return; str = usbredirfilter_rules_to_string(rules, rules_count, ",", "|"); if (!str) { ERROR("error creating filter string, not sending filter"); return; } usbredirparser_queue(parser_pub, usb_redir_filter_filter, 0, NULL, (uint8_t *)str, strlen(str) + 1); free(str); } USBREDIR_VISIBLE void usbredirparser_send_start_bulk_receiving(struct usbredirparser *parser, uint64_t id, struct usb_redir_start_bulk_receiving_header *start_bulk_receiving) { usbredirparser_queue(parser, usb_redir_start_bulk_receiving, id, start_bulk_receiving, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_stop_bulk_receiving(struct usbredirparser *parser, uint64_t id, struct usb_redir_stop_bulk_receiving_header *stop_bulk_receiving) { usbredirparser_queue(parser, usb_redir_stop_bulk_receiving, id, stop_bulk_receiving, NULL, 0); } USBREDIR_VISIBLE void usbredirparser_send_bulk_receiving_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_bulk_receiving_status_header *bulk_receiving_status) { usbredirparser_queue(parser, usb_redir_bulk_receiving_status, id, bulk_receiving_status, NULL, 0); } /* Data packets: */ USBREDIR_VISIBLE void usbredirparser_send_control_packet(struct usbredirparser *parser, uint64_t id, struct usb_redir_control_packet_header *control_header, uint8_t *data, int data_len) { usbredirparser_queue(parser, usb_redir_control_packet, id, control_header, data, data_len); } USBREDIR_VISIBLE void usbredirparser_send_bulk_packet(struct usbredirparser *parser, uint64_t id, struct usb_redir_bulk_packet_header *bulk_header, uint8_t *data, int data_len) { usbredirparser_queue(parser, usb_redir_bulk_packet, id, bulk_header, data, data_len); } USBREDIR_VISIBLE void usbredirparser_send_iso_packet(struct usbredirparser *parser, uint64_t id, struct usb_redir_iso_packet_header *iso_header, uint8_t *data, int data_len) { usbredirparser_queue(parser, usb_redir_iso_packet, id, iso_header, data, data_len); } USBREDIR_VISIBLE void usbredirparser_send_interrupt_packet(struct usbredirparser *parser, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_header, uint8_t *data, int data_len) { usbredirparser_queue(parser, usb_redir_interrupt_packet, id, interrupt_header, data, data_len); } USBREDIR_VISIBLE void usbredirparser_send_buffered_bulk_packet(struct usbredirparser *parser, uint64_t id, struct usb_redir_buffered_bulk_packet_header *buffered_bulk_header, uint8_t *data, int data_len) { usbredirparser_queue(parser, usb_redir_buffered_bulk_packet, id, buffered_bulk_header, data, data_len); } /****** Serialization support ******/ #define USBREDIRPARSER_SERIALIZE_MAGIC 0x55525031 #define USBREDIRPARSER_SERIALIZE_BUF_SIZE 65536 /* Serialization format, send and receiving endian are expected to be the same! uint32 MAGIC: 0x55525031 ascii: URP1 (UsbRedirParser version 1) uint32 len: length of the entire serialized state, including MAGIC uint32 our_caps_len uint32 our_caps[our_caps_len] uint32 peer_caps_len uint32 peer_caps[peer_caps_len] uint32 to_skip uint32 header_read uint8 header[header_read] uint32 type_header_read uint8 type_header[type_header_read] uint32 data_read uint8 data[data_read] uint32 write_buf_count: followed by write_buf_count times: uint32 write_buf_len uint8 write_buf_data[write_buf_len] */ static int serialize_alloc(struct usbredirparser_priv *parser, uint8_t **state, uint8_t **pos, uint32_t *remain, uint32_t needed) { uint8_t *old_state = *state; uint32_t used, size; if (*remain >= needed) return 0; used = *pos - *state; size = (used + needed + USBREDIRPARSER_SERIALIZE_BUF_SIZE - 1) & ~(USBREDIRPARSER_SERIALIZE_BUF_SIZE - 1); *state = realloc(*state, size); if (!*state) { free(old_state); ERROR("Out of memory allocating serialization buffer"); return -1; } *pos = *state + used; *remain = size - used; return 0; } static int serialize_int(struct usbredirparser_priv *parser, uint8_t **state, uint8_t **pos, uint32_t *remain, uint32_t val, const char *desc) { DEBUG("serializing int %08x : %s", val, desc); if (serialize_alloc(parser, state, pos, remain, sizeof(uint32_t))) return -1; memcpy(*pos, &val, sizeof(uint32_t)); *pos += sizeof(uint32_t); *remain -= sizeof(uint32_t); return 0; } static int unserialize_int(struct usbredirparser_priv *parser, uint8_t **pos, uint32_t *remain, uint32_t *val, const char *desc) { if (*remain < sizeof(uint32_t)) { ERROR("error buffer underrun while unserializing state"); return -1; } memcpy(val, *pos, sizeof(uint32_t)); *pos += sizeof(uint32_t); *remain -= sizeof(uint32_t); DEBUG("unserialized int %08x : %s", *val, desc); return 0; } static int serialize_data(struct usbredirparser_priv *parser, uint8_t **state, uint8_t **pos, uint32_t *remain, uint8_t *data, uint32_t len, const char *desc) { DEBUG("serializing %d bytes of %s data", len, desc); if (len >= 8) DEBUG("First 8 bytes of %s: %02x %02x %02x %02x %02x %02x %02x %02x", desc, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); if (serialize_alloc(parser, state, pos, remain, sizeof(uint32_t) + len)) return -1; memcpy(*pos, &len, sizeof(uint32_t)); *pos += sizeof(uint32_t); *remain -= sizeof(uint32_t); memcpy(*pos, data, len); *pos += len; *remain -= len; return 0; } /* If *data == NULL, allocs buffer dynamically, else len_in_out must contain the length of the passed in buffer. */ static int unserialize_data(struct usbredirparser_priv *parser, uint8_t **pos, uint32_t *remain, uint8_t **data, uint32_t *len_in_out, const char *desc) { uint32_t len; if (*remain < sizeof(uint32_t)) { ERROR("error buffer underrun while unserializing state"); return -1; } memcpy(&len, *pos, sizeof(uint32_t)); *pos += sizeof(uint32_t); *remain -= sizeof(uint32_t); if (*remain < len) { ERROR("error buffer underrun while unserializing state"); return -1; } if (*data == NULL && len > 0) { *data = malloc(len); if (!*data) { ERROR("Out of memory allocating unserialize buffer"); return -1; } } else { if (*len_in_out < len) { ERROR("error buffer overrun while unserializing state"); return -1; } } memcpy(*data, *pos, len); *pos += len; *remain -= len; *len_in_out = len; DEBUG("unserialized %d bytes of %s data", len, desc); if (len >= 8) DEBUG("First 8 bytes of %s: %02x %02x %02x %02x %02x %02x %02x %02x", desc, (*data)[0], (*data)[1], (*data)[2], (*data)[3], (*data)[4], (*data)[5], (*data)[6], (*data)[7]); return 0; } USBREDIR_VISIBLE int usbredirparser_serialize(struct usbredirparser *parser_pub, uint8_t **state_dest, int *state_len) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; struct usbredirparser_buf *wbuf; uint8_t *state = NULL, *pos = NULL; uint32_t write_buf_count = 0, len, remain = 0; ptrdiff_t write_buf_count_pos; *state_dest = NULL; *state_len = 0; if (serialize_int(parser, &state, &pos, &remain, USBREDIRPARSER_SERIALIZE_MAGIC, "magic")) return -1; /* To be replaced with length later */ if (serialize_int(parser, &state, &pos, &remain, 0, "length")) return -1; if (serialize_data(parser, &state, &pos, &remain, (uint8_t *)parser->our_caps, USB_REDIR_CAPS_SIZE * sizeof(int32_t), "our_caps")) return -1; if (parser->have_peer_caps) { if (serialize_data(parser, &state, &pos, &remain, (uint8_t *)parser->peer_caps, USB_REDIR_CAPS_SIZE * sizeof(int32_t), "peer_caps")) return -1; } else { if (serialize_int(parser, &state, &pos, &remain, 0, "peer_caps_len")) return -1; } if (serialize_int(parser, &state, &pos, &remain, parser->to_skip, "skip")) return -1; if (serialize_data(parser, &state, &pos, &remain, (uint8_t *)&parser->header, parser->header_read, "header")) return -1; if (serialize_data(parser, &state, &pos, &remain, parser->type_header, parser->type_header_read, "type_header")) return -1; if (serialize_data(parser, &state, &pos, &remain, parser->data, parser->data_read, "packet-data")) return -1; write_buf_count_pos = pos - state; /* To be replaced with write_buf_count later */ if (serialize_int(parser, &state, &pos, &remain, 0, "write_buf_count")) return -1; wbuf = parser->write_buf; while (wbuf) { if (serialize_data(parser, &state, &pos, &remain, wbuf->buf + wbuf->pos, wbuf->len - wbuf->pos, "write-buf")) return -1; write_buf_count++; wbuf = wbuf->next; } /* Patch in write_buf_count */ memcpy(state + write_buf_count_pos, &write_buf_count, sizeof(int32_t)); /* Patch in length */ len = pos - state; memcpy(state + sizeof(int32_t), &len, sizeof(int32_t)); *state_dest = state; *state_len = len; return 0; } USBREDIR_VISIBLE int usbredirparser_unserialize(struct usbredirparser *parser_pub, uint8_t *state, int len) { struct usbredirparser_priv *parser = (struct usbredirparser_priv *)parser_pub; struct usbredirparser_buf *wbuf, **next; uint32_t orig_caps[USB_REDIR_CAPS_SIZE]; uint8_t *data; uint32_t i, l, header_len, remain = len; if (unserialize_int(parser, &state, &remain, &i, "magic")) return -1; if (i != USBREDIRPARSER_SERIALIZE_MAGIC) { ERROR("error unserialize magic mismatch"); return -1; } if (unserialize_int(parser, &state, &remain, &i, "length")) return -1; if (i != len) { ERROR("error unserialize length mismatch"); return -1; } data = (uint8_t *)parser->our_caps; i = USB_REDIR_CAPS_SIZE * sizeof(int32_t); memcpy(orig_caps, parser->our_caps, i); if (unserialize_data(parser, &state, &remain, &data, &i, "our_caps")) return -1; for (i =0; i < USB_REDIR_CAPS_SIZE; i++) { if (parser->our_caps[i] != orig_caps[i]) { /* orig_caps is our original settings * parser->our_caps is off the wire. * We want to allow reception from an older * usbredir that doesn't have all our features. */ if (parser->our_caps[i] & ~orig_caps[i]) { /* Source has a cap we don't */ ERROR("error unserialize caps mismatch ours: %x recv: %x", orig_caps[i], parser->our_caps[i]); return -1; } else { /* We've got a cap the source doesn't - that's OK */ WARNING("unserialize missing some caps; ours: %x recv: %x", orig_caps[i], parser->our_caps[i]); } } } data = (uint8_t *)parser->peer_caps; i = USB_REDIR_CAPS_SIZE * sizeof(int32_t); if (unserialize_data(parser, &state, &remain, &data, &i, "peer_caps")) return -1; if (i) parser->have_peer_caps = 1; if (unserialize_int(parser, &state, &remain, &i, "skip")) return -1; parser->to_skip = i; header_len = usbredirparser_get_header_len(parser_pub); data = (uint8_t *)&parser->header; i = header_len; if (unserialize_data(parser, &state, &remain, &data, &i, "header")) return -1; parser->header_read = i; /* Set various length field froms the header (if we've a header) */ if (parser->header_read == header_len) { int type_header_len = usbredirparser_get_type_header_len(parser_pub, parser->header.type, 0); if (type_header_len < 0 || type_header_len > sizeof(parser->type_header) || parser->header.length < type_header_len || (parser->header.length > type_header_len && !usbredirparser_expect_extra_data(parser))) { ERROR("error unserialize packet header invalid"); return -1; } parser->type_header_len = type_header_len; parser->data_len = parser->header.length - type_header_len; } data = parser->type_header; i = parser->type_header_len; if (unserialize_data(parser, &state, &remain, &data, &i, "type_header")) return -1; parser->type_header_read = i; if (parser->data_len) { parser->data = malloc(parser->data_len); if (!parser->data) { ERROR("Out of memory allocating unserialize buffer"); return -1; } } i = parser->data_len; if (unserialize_data(parser, &state, &remain, &parser->data, &i, "data")) return -1; parser->data_read = i; /* Get the write buffer count and the write buffers */ if (unserialize_int(parser, &state, &remain, &i, "write_buf_count")) return -1; next = &parser->write_buf; while (i) { wbuf = calloc(1, sizeof(*wbuf)); if (!wbuf) { ERROR("Out of memory allocating unserialize buffer"); return -1; } *next = wbuf; l = 0; if (unserialize_data(parser, &state, &remain, &wbuf->buf, &l, "wbuf")) return -1; wbuf->len = l; next = &wbuf->next; i--; } if (remain) { ERROR("error unserialize %d bytes of extraneous state data", remain); return -1; } return 0; } usbredir-usbredir-0.11.0/usbredirparser/usbredirparser.h000066400000000000000000000453001410424124300234630ustar00rootroot00000000000000/* usbredirparser.h usb redirection protocol parser header Copyright 2010-2012 Red Hat, Inc. Red Hat Authors: Hans de Goede This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #ifndef __USBREDIRPARSER_H #define __USBREDIRPARSER_H #include "usbredirproto.h" #ifdef __cplusplus extern "C" { #endif struct usbredirparser; struct usbredirfilter_rule; /* Called by a usbredirparser to log various messages */ enum { usbredirparser_none, usbredirparser_error, usbredirparser_warning, usbredirparser_info, usbredirparser_debug, usbredirparser_debug_data }; typedef void (*usbredirparser_log)(void *priv, int level, const char *msg); /* Called by a usbredirparser to read/write data to its peer. Must return the amount of bytes read/written, 0 when the read/write would block (and this is undesirable) and -1 on error. If the usbredirparser_fl_write_cb_owns_buffer flag is passed to usbredirparser_init, then the usbredirparser_write callback becomes the owner of the buffer pointed to by data and should call usbredirparser_free_write_buffer() when it is done with the buffer. In this case the callback is not allowed to return any amount of bytes written, it must either accept the entire buffer (return count), or signal blocking (return 0) or error (return -1). Returning any other value will result in a call to abort(). */ typedef int (*usbredirparser_read)(void *priv, uint8_t *data, int count); typedef int (*usbredirparser_write)(void *priv, uint8_t *data, int count); /* Locking functions for use by multithread apps */ typedef void *(*usbredirparser_alloc_lock)(void); typedef void (*usbredirparser_lock)(void *lock); typedef void (*usbredirparser_unlock)(void *lock); typedef void (*usbredirparser_free_lock)(void *lock); /* The below callbacks are called when a complete packet of the relevant type has been received. Note that the passed in packet-type-specific-header's lifetime is only guaranteed to be that of the callback. Control packets: */ typedef void (*usbredirparser_hello)(void *priv, struct usb_redir_hello_header *hello); typedef void (*usbredirparser_device_connect)(void *priv, struct usb_redir_device_connect_header *device_connect); typedef void (*usbredirparser_device_disconnect)(void *priv); typedef void (*usbredirparser_reset)(void *priv); typedef void (*usbredirparser_interface_info)(void *priv, struct usb_redir_interface_info_header *interface_info); typedef void (*usbredirparser_ep_info)(void *priv, struct usb_redir_ep_info_header *ep_info); typedef void (*usbredirparser_set_configuration)(void *priv, uint64_t id, struct usb_redir_set_configuration_header *set_configuration); typedef void (*usbredirparser_get_configuration)(void *priv, uint64_t id); typedef void (*usbredirparser_configuration_status)(void *priv, uint64_t id, struct usb_redir_configuration_status_header *configuration_status); typedef void (*usbredirparser_set_alt_setting)(void *priv, uint64_t id, struct usb_redir_set_alt_setting_header *set_alt_setting); typedef void (*usbredirparser_get_alt_setting)(void *priv, uint64_t id, struct usb_redir_get_alt_setting_header *get_alt_setting); typedef void (*usbredirparser_alt_setting_status)(void *priv, uint64_t id, struct usb_redir_alt_setting_status_header *alt_setting_status); typedef void (*usbredirparser_start_iso_stream)(void *priv, uint64_t id, struct usb_redir_start_iso_stream_header *start_iso_stream); typedef void (*usbredirparser_stop_iso_stream)(void *priv, uint64_t id, struct usb_redir_stop_iso_stream_header *stop_iso_stream); typedef void (*usbredirparser_iso_stream_status)(void *priv, uint64_t id, struct usb_redir_iso_stream_status_header *iso_stream_status); typedef void (*usbredirparser_start_interrupt_receiving)(void *priv, uint64_t id, struct usb_redir_start_interrupt_receiving_header *start_interrupt_receiving); typedef void (*usbredirparser_stop_interrupt_receiving)(void *priv, uint64_t id, struct usb_redir_stop_interrupt_receiving_header *stop_interrupt_receiving); typedef void (*usbredirparser_interrupt_receiving_status)(void *priv, uint64_t id, struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status); typedef void (*usbredirparser_alloc_bulk_streams)(void *priv, uint64_t id, struct usb_redir_alloc_bulk_streams_header *alloc_bulk_streams); typedef void (*usbredirparser_free_bulk_streams)(void *priv, uint64_t id, struct usb_redir_free_bulk_streams_header *free_bulk_streams); typedef void (*usbredirparser_bulk_streams_status)(void *priv, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status); typedef void (*usbredirparser_cancel_data_packet)(void *priv, uint64_t id); typedef void (*usbredirparser_filter_reject)(void *priv); /* Note that the ownership of the rules array is passed on to the callback. */ typedef void (*usbredirparser_filter_filter)(void *priv, struct usbredirfilter_rule *rules, int rules_count); typedef void (*usbredirparser_device_disconnect_ack)(void *priv); typedef void (*usbredirparser_start_bulk_receiving)(void *priv, uint64_t id, struct usb_redir_start_bulk_receiving_header *start_bulk_receiving); typedef void (*usbredirparser_stop_bulk_receiving)(void *priv, uint64_t id, struct usb_redir_stop_bulk_receiving_header *stop_bulk_receiving); typedef void (*usbredirparser_bulk_receiving_status)(void *priv, uint64_t id, struct usb_redir_bulk_receiving_status_header *bulk_receiving_status); /* Data packets: Note that ownership of the data buffer (if not NULL) is passed on to the callback. The callback should free it by calling usbredirparser_free_packet_data when it is done with it. */ typedef void (*usbredirparser_control_packet)(void *priv, uint64_t id, struct usb_redir_control_packet_header *control_header, uint8_t *data, int data_len); typedef void (*usbredirparser_bulk_packet)(void *priv, uint64_t id, struct usb_redir_bulk_packet_header *bulk_header, uint8_t *data, int data_len); typedef void (*usbredirparser_iso_packet)(void *priv, uint64_t id, struct usb_redir_iso_packet_header *iso_header, uint8_t *data, int data_len); typedef void (*usbredirparser_interrupt_packet)(void *priv, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_header, uint8_t *data, int data_len); typedef void (*usbredirparser_buffered_bulk_packet)(void *priv, uint64_t id, struct usb_redir_buffered_bulk_packet_header *buffered_bulk_header, uint8_t *data, int data_len); /* Public part of the data allocated by usbredirparser_alloc, *never* allocate a usbredirparser struct yourself, it may be extended in the future to add callbacks for new packet types (which will then get added at the end), *and* usbredirparser_alloc will also allocate some space behind it for private data */ struct usbredirparser { /* app private data passed into all callbacks as the priv argument */ void *priv; /* non packet callbacks */ usbredirparser_log log_func; usbredirparser_read read_func; usbredirparser_write write_func; /* usb-redir-protocol v0.3 control packet complete callbacks */ usbredirparser_device_connect device_connect_func; usbredirparser_device_disconnect device_disconnect_func; usbredirparser_reset reset_func; usbredirparser_interface_info interface_info_func; usbredirparser_ep_info ep_info_func; usbredirparser_set_configuration set_configuration_func; usbredirparser_get_configuration get_configuration_func; usbredirparser_configuration_status configuration_status_func; usbredirparser_set_alt_setting set_alt_setting_func; usbredirparser_get_alt_setting get_alt_setting_func; usbredirparser_alt_setting_status alt_setting_status_func; usbredirparser_start_iso_stream start_iso_stream_func; usbredirparser_stop_iso_stream stop_iso_stream_func; usbredirparser_iso_stream_status iso_stream_status_func; usbredirparser_start_interrupt_receiving start_interrupt_receiving_func; usbredirparser_stop_interrupt_receiving stop_interrupt_receiving_func; usbredirparser_interrupt_receiving_status interrupt_receiving_status_func; usbredirparser_alloc_bulk_streams alloc_bulk_streams_func; usbredirparser_free_bulk_streams free_bulk_streams_func; usbredirparser_bulk_streams_status bulk_streams_status_func; usbredirparser_cancel_data_packet cancel_data_packet_func; /* usb-redir-protocol v0.3 data packet complete callbacks */ usbredirparser_control_packet control_packet_func; usbredirparser_bulk_packet bulk_packet_func; usbredirparser_iso_packet iso_packet_func; usbredirparser_interrupt_packet interrupt_packet_func; /* usbredir 0.3.2 new non packet callbacks (for multi-thread locking) */ usbredirparser_alloc_lock alloc_lock_func; usbredirparser_lock lock_func; usbredirparser_unlock unlock_func; usbredirparser_free_lock free_lock_func; /* usbredir 0.3.2 new control packet complete callbacks */ usbredirparser_hello hello_func; /* usbredir 0.4 new control packet complete callbacks */ usbredirparser_filter_reject filter_reject_func; usbredirparser_filter_filter filter_filter_func; usbredirparser_device_disconnect_ack device_disconnect_ack_func; /* usbredir 0.6 new control packet complete callbacks */ usbredirparser_start_bulk_receiving start_bulk_receiving_func; usbredirparser_stop_bulk_receiving stop_bulk_receiving_func; usbredirparser_bulk_receiving_status bulk_receiving_status_func; /* usbredir 0.6 new data packet complete callbacks */ usbredirparser_buffered_bulk_packet buffered_bulk_packet_func; }; /* Allocate a usbredirparser, after this the app should set the callback app private data and all the callbacks it needs, before calling usbredirparser_init */ struct usbredirparser *usbredirparser_create(void); /* Set capability cap in the USB_REDIR_CAPS_SIZE sized caps array, this is a helper function to set capabilities in the caps array passed to usbredirparser_init(). */ void usbredirparser_caps_set_cap(uint32_t *caps, int cap); /* Init the parser, this will queue an initial usb_redir_hello packet, sending the version and caps to the peer, as well as configure the parsing according to the passed in flags. */ enum { usbredirparser_fl_usb_host = 0x01, usbredirparser_fl_write_cb_owns_buffer = 0x02, usbredirparser_fl_no_hello = 0x04, }; void usbredirparser_init(struct usbredirparser *parser, const char *version, uint32_t *caps, int caps_len, int flags); void usbredirparser_destroy(struct usbredirparser *parser); /* See if our side has a certain cap (checks the caps passed into _init) */ int usbredirparser_have_cap(struct usbredirparser *parser, int cap); /* Check if we've received the caps from the peer */ int usbredirparser_have_peer_caps(struct usbredirparser *parser); /* Check if our peer has a certain capability. Note this function should not be used before the hello_func callback has been called. */ int usbredirparser_peer_has_cap(struct usbredirparser *parser, int cap); /* Call this whenever there is data ready from the other side to parse. On an usbredirparser_read_io_error this function will continue where it left of the last time on the next call. On an usbredirparser_read_parse_error it will skip to the next packet (*). *) As determined by the faulty package's headers length field */ enum { usbredirparser_read_io_error = -1, usbredirparser_read_parse_error = -2, }; int usbredirparser_do_read(struct usbredirparser *parser); /* This returns the number of usbredir packets queued up for writing */ int usbredirparser_has_data_to_write(struct usbredirparser *parser); /* Call this when usbredirparser_has_data_to_write returns > 0 returns 0 on success, -1 if a write error happened. If a write error happened, this function will retry writing any queued data on the next call, and will continue doing so until it has succeeded! */ enum { usbredirparser_write_io_error = -1, }; int usbredirparser_do_write(struct usbredirparser *parser); /* See usbredirparser_write documentation */ void usbredirparser_free_write_buffer(struct usbredirparser *parser, uint8_t *data); /* See the data packet callbacks documentation */ void usbredirparser_free_packet_data(struct usbredirparser *parser, uint8_t *data); /* Functions to marshal and queue a packet for sending to its peer. Note: 1) it will not be actually send until usbredirparser_do_write is called 2) if their is not enough memory for buffers the packet will be dropped (and an error message will be logged */ /* Control packets: */ /* Note this function should not be used before the hello_func callback has been called (as it checks the usb_redir_cap_connect_device_version cap). */ void usbredirparser_send_device_connect(struct usbredirparser *parser, struct usb_redir_device_connect_header *device_connect); void usbredirparser_send_device_disconnect(struct usbredirparser *parser); void usbredirparser_send_reset(struct usbredirparser *parser); void usbredirparser_send_interface_info(struct usbredirparser *parser, struct usb_redir_interface_info_header *interface_info); void usbredirparser_send_ep_info(struct usbredirparser *parser, struct usb_redir_ep_info_header *ep_info); void usbredirparser_send_set_configuration(struct usbredirparser *parser, uint64_t id, struct usb_redir_set_configuration_header *set_configuration); void usbredirparser_send_get_configuration(struct usbredirparser *parser, uint64_t id); void usbredirparser_send_configuration_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_configuration_status_header *configuration_status); void usbredirparser_send_set_alt_setting(struct usbredirparser *parser, uint64_t id, struct usb_redir_set_alt_setting_header *set_alt_setting); void usbredirparser_send_get_alt_setting(struct usbredirparser *parser, uint64_t id, struct usb_redir_get_alt_setting_header *get_alt_setting); void usbredirparser_send_alt_setting_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_alt_setting_status_header *alt_setting_status); void usbredirparser_send_start_iso_stream(struct usbredirparser *parser, uint64_t id, struct usb_redir_start_iso_stream_header *start_iso_stream); void usbredirparser_send_stop_iso_stream(struct usbredirparser *parser, uint64_t id, struct usb_redir_stop_iso_stream_header *stop_iso_stream); void usbredirparser_send_iso_stream_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_iso_stream_status_header *iso_stream_status); void usbredirparser_send_start_interrupt_receiving(struct usbredirparser *parser, uint64_t id, struct usb_redir_start_interrupt_receiving_header *start_interrupt_receiving); void usbredirparser_send_stop_interrupt_receiving(struct usbredirparser *parser, uint64_t id, struct usb_redir_stop_interrupt_receiving_header *stop_interrupt_receiving); void usbredirparser_send_interrupt_receiving_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status); void usbredirparser_send_alloc_bulk_streams(struct usbredirparser *parser, uint64_t id, struct usb_redir_alloc_bulk_streams_header *alloc_bulk_streams); void usbredirparser_send_free_bulk_streams(struct usbredirparser *parser, uint64_t id, struct usb_redir_free_bulk_streams_header *free_bulk_streams); void usbredirparser_send_bulk_streams_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status); void usbredirparser_send_cancel_data_packet(struct usbredirparser *parser, uint64_t id); void usbredirparser_send_filter_reject(struct usbredirparser *parser); void usbredirparser_send_filter_filter(struct usbredirparser *parser, const struct usbredirfilter_rule *rules, int rules_count); void usbredirparser_send_start_bulk_receiving(struct usbredirparser *parser, uint64_t id, struct usb_redir_start_bulk_receiving_header *start_bulk_receiving); void usbredirparser_send_stop_bulk_receiving(struct usbredirparser *parser, uint64_t id, struct usb_redir_stop_bulk_receiving_header *stop_bulk_receiving); void usbredirparser_send_bulk_receiving_status(struct usbredirparser *parser, uint64_t id, struct usb_redir_bulk_receiving_status_header *bulk_receiving_status); /* Data packets: */ void usbredirparser_send_control_packet(struct usbredirparser *parser, uint64_t id, struct usb_redir_control_packet_header *control_header, uint8_t *data, int data_len); void usbredirparser_send_bulk_packet(struct usbredirparser *parser, uint64_t id, struct usb_redir_bulk_packet_header *bulk_header, uint8_t *data, int data_len); void usbredirparser_send_iso_packet(struct usbredirparser *parser, uint64_t id, struct usb_redir_iso_packet_header *iso_header, uint8_t *data, int data_len); void usbredirparser_send_interrupt_packet(struct usbredirparser *parser, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_header, uint8_t *data, int data_len); void usbredirparser_send_buffered_bulk_packet(struct usbredirparser *parser, uint64_t id, struct usb_redir_buffered_bulk_packet_header *buffered_bulk_header, uint8_t *data, int data_len); /* Serialization */ /* This function serializes the current usbredirparser state. It will allocate a large enough buffer for this itself and store this in state_dest, it will store the size of this buffer in state_len. Return value: 0 on success, -1 on error (out of memory). The buffer should be free-ed by the caller using free(). */ int usbredirparser_serialize(struct usbredirparser *parser, uint8_t **state_dest, int *state_len); /* This function sets the current usbredirparser state from a serialized state. This function assumes that the parser has just been initialized with the usbredirparser_fl_no_hello flag. Return value: 0 on success, -1 on error (out of memory, or invalid state data). */ int usbredirparser_unserialize(struct usbredirparser *parser_pub, uint8_t *state, int len); #ifdef __cplusplus } #endif #endif usbredir-usbredir-0.11.0/usbredirparser/usbredirparser.map000066400000000000000000000040711410424124300240110ustar00rootroot00000000000000USBREDIRPARSER_0.8.0 { global: usbredirfilter_check; usbredirfilter_print; usbredirfilter_rules_to_string; usbredirfilter_string_to_rules; usbredirfilter_verify; usbredirparser_caps_set_cap; usbredirparser_create; usbredirparser_destroy; usbredirparser_do_read; usbredirparser_do_write; usbredirparser_free_packet_data; usbredirparser_free_write_buffer; usbredirparser_has_data_to_write; usbredirparser_have_cap; usbredirparser_have_peer_caps; usbredirparser_init; usbredirparser_peer_has_cap; usbredirparser_send_alloc_bulk_streams; usbredirparser_send_alt_setting_status; usbredirparser_send_buffered_bulk_packet; usbredirparser_send_bulk_packet; usbredirparser_send_bulk_receiving_status; usbredirparser_send_bulk_streams_status; usbredirparser_send_cancel_data_packet; usbredirparser_send_configuration_status; usbredirparser_send_control_packet; usbredirparser_send_device_connect; usbredirparser_send_device_disconnect; usbredirparser_send_ep_info; usbredirparser_send_filter_filter; usbredirparser_send_filter_reject; usbredirparser_send_free_bulk_streams; usbredirparser_send_get_alt_setting; usbredirparser_send_get_configuration; usbredirparser_send_interface_info; usbredirparser_send_interrupt_packet; usbredirparser_send_interrupt_receiving_status; usbredirparser_send_iso_packet; usbredirparser_send_iso_stream_status; usbredirparser_send_reset; usbredirparser_send_set_alt_setting; usbredirparser_send_set_configuration; usbredirparser_send_start_bulk_receiving; usbredirparser_send_start_interrupt_receiving; usbredirparser_send_start_iso_stream; usbredirparser_send_stop_bulk_receiving; usbredirparser_send_stop_interrupt_receiving; usbredirparser_send_stop_iso_stream; usbredirparser_serialize; usbredirparser_unserialize; local: *; }; USBREDIRPARSER_0.10.0 { global: usbredirfilter_free; } USBREDIRPARSER_0.8.0; # .... define new API here using predicted next version number .... usbredir-usbredir-0.11.0/usbredirparser/usbredirproto-compat.h000066400000000000000000000041101410424124300246050ustar00rootroot00000000000000/* usbredirproto-compat.h usb redirection compatibility protocol definitions Copyright 2011 Red Hat, Inc. Red Hat Authors: Hans de Goede This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #ifndef __USBREDIRPROTO_COMPAT_H #define __USBREDIRPROTO_COMPAT_H /* PACK macros borrowed from spice-protocol */ #ifdef __GNUC__ #define ATTR_PACKED __attribute__ ((__packed__)) #ifdef __MINGW32__ #pragma pack(push,1) #endif #else #pragma pack(push) #pragma pack(1) #define ATTR_PACKED #pragma warning(disable:4200) #pragma warning(disable:4103) #endif #include struct usb_redir_device_connect_header_no_device_version { uint8_t speed; uint8_t device_class; uint8_t device_subclass; uint8_t device_protocol; uint16_t vendor_id; uint16_t product_id; } ATTR_PACKED; struct usb_redir_ep_info_header_no_max_pktsz { uint8_t type[32]; uint8_t interval[32]; uint8_t interface[32]; } ATTR_PACKED; struct usb_redir_ep_info_header_no_max_streams { uint8_t type[32]; uint8_t interval[32]; uint8_t interface[32]; uint16_t max_packet_size[32]; } ATTR_PACKED; struct usb_redir_header_32bit_id { uint32_t type; uint32_t length; uint32_t id; } ATTR_PACKED; struct usb_redir_bulk_packet_header_16bit_length { uint8_t endpoint; uint8_t status; uint16_t length; uint32_t stream_id; } ATTR_PACKED; #undef ATTR_PACKED #if defined(__MINGW32__) || !defined(__GNUC__) #pragma pack(pop) #endif #endif usbredir-usbredir-0.11.0/usbredirparser/usbredirproto.h000066400000000000000000000171441410424124300233370ustar00rootroot00000000000000/* usbredirproto.h usb redirection protocol definitions Copyright 2010-2011 Red Hat, Inc. Red Hat Authors: Hans de Goede This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #ifndef __USBREDIRPROTO_H #define __USBREDIRPROTO_H /* PACK macros borrowed from spice-protocol */ #ifdef __GNUC__ #define ATTR_PACKED __attribute__ ((__packed__)) #ifdef __MINGW32__ #pragma pack(push,1) #endif #else #pragma pack(push) #pragma pack(1) #define ATTR_PACKED #pragma warning(disable:4200) #pragma warning(disable:4103) #endif #include #ifdef __cplusplus extern "C" { #endif #define USBREDIR_VERSION 0x000701 /* 0.7 [.0] */ enum { usb_redir_success, usb_redir_cancelled, /* The transfer was cancelled */ usb_redir_inval, /* Invalid packet type / length / ep, etc. */ usb_redir_ioerror, /* IO error */ usb_redir_stall, /* Stalled */ usb_redir_timeout, /* Request timed out */ usb_redir_babble, /* The device has "babbled" (since 0.4.2) */ }; enum { /* Note these 4 match the usb spec! */ usb_redir_type_control, usb_redir_type_iso, usb_redir_type_bulk, usb_redir_type_interrupt, usb_redir_type_invalid = 255 }; enum { usb_redir_speed_low, usb_redir_speed_full, usb_redir_speed_high, usb_redir_speed_super, usb_redir_speed_unknown = 255 }; enum { /* Control packets */ usb_redir_hello, usb_redir_device_connect, usb_redir_device_disconnect, usb_redir_reset, usb_redir_interface_info, usb_redir_ep_info, usb_redir_set_configuration, usb_redir_get_configuration, usb_redir_configuration_status, usb_redir_set_alt_setting, usb_redir_get_alt_setting, usb_redir_alt_setting_status, usb_redir_start_iso_stream, usb_redir_stop_iso_stream, usb_redir_iso_stream_status, usb_redir_start_interrupt_receiving, usb_redir_stop_interrupt_receiving, usb_redir_interrupt_receiving_status, usb_redir_alloc_bulk_streams, usb_redir_free_bulk_streams, usb_redir_bulk_streams_status, usb_redir_cancel_data_packet, usb_redir_filter_reject, usb_redir_filter_filter, usb_redir_device_disconnect_ack, usb_redir_start_bulk_receiving, usb_redir_stop_bulk_receiving, usb_redir_bulk_receiving_status, /* Data packets */ usb_redir_control_packet = 100, usb_redir_bulk_packet, usb_redir_iso_packet, usb_redir_interrupt_packet, usb_redir_buffered_bulk_packet, }; enum { /* Supports USB 3 bulk streams */ usb_redir_cap_bulk_streams, /* The device_connect packet has the device_version_bcd field */ usb_redir_cap_connect_device_version, /* Supports usb_redir_filter_reject and usb_redir_filter_filter pkts */ usb_redir_cap_filter, /* Supports the usb_redir_device_disconnect_ack packet */ usb_redir_cap_device_disconnect_ack, /* The ep_info packet has the max_packet_size field */ usb_redir_cap_ep_info_max_packet_size, /* Supports 64 bits ids in usb_redir_header */ usb_redir_cap_64bits_ids, /* Supports 32 bits length in usb_redir_bulk_packet_header */ usb_redir_cap_32bits_bulk_length, /* Supports bulk receiving / buffered bulk input */ usb_redir_cap_bulk_receiving, }; /* Number of uint32_t-s needed to hold all (known) capabilities */ #define USB_REDIR_CAPS_SIZE 1 struct usb_redir_header { uint32_t type; uint32_t length; uint64_t id; } ATTR_PACKED; struct usb_redir_hello_header { char version[64]; uint32_t capabilities[0]; } ATTR_PACKED; struct usb_redir_device_connect_header { uint8_t speed; uint8_t device_class; uint8_t device_subclass; uint8_t device_protocol; uint16_t vendor_id; uint16_t product_id; uint16_t device_version_bcd; } ATTR_PACKED; struct usb_redir_interface_info_header { uint32_t interface_count; uint8_t interface[32]; uint8_t interface_class[32]; uint8_t interface_subclass[32]; uint8_t interface_protocol[32]; } ATTR_PACKED; struct usb_redir_ep_info_header { uint8_t type[32]; uint8_t interval[32]; uint8_t interface[32]; uint16_t max_packet_size[32]; uint32_t max_streams[32]; } ATTR_PACKED; struct usb_redir_set_configuration_header { uint8_t configuration; } ATTR_PACKED; struct usb_redir_configuration_status_header { uint8_t status; uint8_t configuration; } ATTR_PACKED; struct usb_redir_set_alt_setting_header { uint8_t interface; uint8_t alt; } ATTR_PACKED; struct usb_redir_get_alt_setting_header { uint8_t interface; } ATTR_PACKED; struct usb_redir_alt_setting_status_header { uint8_t status; uint8_t interface; uint8_t alt; } ATTR_PACKED; struct usb_redir_start_iso_stream_header { uint8_t endpoint; uint8_t pkts_per_urb; uint8_t no_urbs; } ATTR_PACKED; struct usb_redir_stop_iso_stream_header { uint8_t endpoint; } ATTR_PACKED; struct usb_redir_iso_stream_status_header { uint8_t status; uint8_t endpoint; } ATTR_PACKED; struct usb_redir_start_interrupt_receiving_header { uint8_t endpoint; } ATTR_PACKED; struct usb_redir_stop_interrupt_receiving_header { uint8_t endpoint; } ATTR_PACKED; struct usb_redir_interrupt_receiving_status_header { uint8_t status; uint8_t endpoint; } ATTR_PACKED; struct usb_redir_alloc_bulk_streams_header { uint32_t endpoints; /* bitmask indicating on which eps to alloc streams */ uint32_t no_streams; } ATTR_PACKED; struct usb_redir_free_bulk_streams_header { uint32_t endpoints; /* bitmask indicating on which eps to free streams */ } ATTR_PACKED; struct usb_redir_bulk_streams_status_header { uint32_t endpoints; /* bitmask indicating eps this status message is for */ uint32_t no_streams; uint8_t status; } ATTR_PACKED; struct usb_redir_start_bulk_receiving_header { uint32_t stream_id; uint32_t bytes_per_transfer; uint8_t endpoint; uint8_t no_transfers; } ATTR_PACKED; struct usb_redir_stop_bulk_receiving_header { uint32_t stream_id; uint8_t endpoint; } ATTR_PACKED; struct usb_redir_bulk_receiving_status_header { uint32_t stream_id; uint8_t endpoint; uint8_t status; } ATTR_PACKED; struct usb_redir_control_packet_header { uint8_t endpoint; uint8_t request; uint8_t requesttype; uint8_t status; uint16_t value; uint16_t index; uint16_t length; } ATTR_PACKED; struct usb_redir_bulk_packet_header { uint8_t endpoint; uint8_t status; uint16_t length; uint32_t stream_id; uint16_t length_high; /* High 16 bits of the packet length */ } ATTR_PACKED; struct usb_redir_iso_packet_header { uint8_t endpoint; uint8_t status; uint16_t length; } ATTR_PACKED; struct usb_redir_interrupt_packet_header { uint8_t endpoint; uint8_t status; uint16_t length; } ATTR_PACKED; struct usb_redir_buffered_bulk_packet_header { uint32_t stream_id; uint32_t length; uint8_t endpoint; uint8_t status; } ATTR_PACKED; #undef ATTR_PACKED #if defined(__MINGW32__) || !defined(__GNUC__) #pragma pack(pop) #endif #ifdef __cplusplus } #endif #endif usbredir-usbredir-0.11.0/usbredirserver/000077500000000000000000000000001410424124300202665ustar00rootroot00000000000000usbredir-usbredir-0.11.0/usbredirserver/meson.build000066400000000000000000000004551410424124300224340ustar00rootroot00000000000000usbredirserver_sources = [ 'usbredirserver.c', ] executable('usbredirserver', sources : usbredirserver_sources, c_args : '-Wno-deprecated-declarations', install : true, install_dir: get_option('sbindir'), dependencies : usbredir_host_lib_dep) install_man('usbredirserver.1') usbredir-usbredir-0.11.0/usbredirserver/usbredirserver.1000066400000000000000000000032561410424124300234240ustar00rootroot00000000000000.TH USBREDIRSERVER "1" "April 2012" "usbredirserver" "User Commands" .SH NAME usbredirserver \- exporting an USB device for use from another (virtual) machine .SH SYNOPSIS .B usbredirserver [\fI-p|--port \fR] [\fI-v|--verbose <0-5>\fR] [\fI-4 ] \fI\fR .SH DESCRIPTION usbredirserver is a small standalone server for exporting an USB device for use from another (virtual) machine through the usbredir protocol. .PP You can specify the USB device to export either by USB id in the form of \fI:\fR, or by USB bus number and device address in the form of \fI-\fR. .PP Notice that an instance of usbredirserver can only be used to export a single USB device. If you want to export multiple devices you can start multiple instances listening on different TCP ports. .SH OPTIONS .TP \fB\-p\fR, \fB\-\-port\fR=\fIPORT\fR Set the TCP port to listen on to \fIPORT\fR .TP \fB\-v\fR, \fB\-\-verbose\fR=\fIVERBOSE\fR Set usbredirserver's verbosity level to \fIVERBOSE\fR, this mostly affects USB redirection related messages. Valid values are 0-5: .br 0:Silent 1:Errors 2:Warnings 3:Info 4:Debug 5:Debug++ .SH AUTHOR Written by Hans de Goede .SH REPORTING BUGS You can report bugs to the spice-devel mailinglist: http://lists.freedesktop.org/mailman/listinfo/spice-devel or filing an issue at: https://gitlab.freedesktop.org/spice/usbredir/issues/new .SH COPYRIGHT Copyright 2010-2012 Red Hat, Inc. License GPLv2+: GNU GPL version 2 or later . .br This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. usbredir-usbredir-0.11.0/usbredirserver/usbredirserver.c000066400000000000000000000337061410424124300235110ustar00rootroot00000000000000/* usbredirserver.c simple usb network redirection tcp/ip server (host). Copyright 2010-2011 Red Hat, Inc. Red Hat Authors: Hans de Goede This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbredirhost.h" #define SERVER_VERSION "usbredirserver " PACKAGE_VERSION #if !defined(SOL_TCP) && defined(IPPROTO_TCP) #define SOL_TCP IPPROTO_TCP #endif #if !defined(TCP_KEEPIDLE) && defined(TCP_KEEPALIVE) && defined(__APPLE__) #define TCP_KEEPIDLE TCP_KEEPALIVE #endif static int verbose = usbredirparser_info; static int client_fd, running = 1; static libusb_context *ctx; static struct usbredirhost *host; static const struct option longopts[] = { { "port", required_argument, NULL, 'p' }, { "verbose", required_argument, NULL, 'v' }, { "ipv4", required_argument, NULL, '4' }, { "ipv6", required_argument, NULL, '6' }, { "keepalive", required_argument, NULL, 'k' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; static void usbredirserver_log(void *priv, int level, const char *msg) { if (level <= verbose) fprintf(stderr, "%s\n", msg); } static int usbredirserver_read(void *priv, uint8_t *data, int count) { int r = read(client_fd, data, count); if (r < 0) { if (errno == EAGAIN) return 0; return -1; } if (r == 0) { /* Client disconnected */ close(client_fd); client_fd = -1; } return r; } static int usbredirserver_write(void *priv, uint8_t *data, int count) { int r = write(client_fd, data, count); if (r < 0) { if (errno == EAGAIN) return 0; if (errno == EPIPE) { /* Client disconnected */ close(client_fd); client_fd = -1; return 0; } return -1; } return r; } static void usage(int exit_code, char *argv0) { fprintf(exit_code? stderr:stdout, "Usage: %s [-p|--port ] [-v|--verbose <0-5>] " "[[-4|--ipv4 ipaddr]|[-6|--ipv6 ipaddr]] " "[-k|--keepalive seconds] " "\n", argv0); exit(exit_code); } static void invalid_usb_device_id(char *usb_device_id, char *argv0) { fprintf(stderr, "Invalid usb device identifier: %s\n", usb_device_id); usage(1, argv0); } static void run_main_loop(void) { const struct libusb_pollfd **pollfds = NULL; fd_set readfds, writefds; int i, n, nfds; struct timeval timeout, *timeout_p; while (running && client_fd != -1) { FD_ZERO(&readfds); FD_ZERO(&writefds); FD_SET(client_fd, &readfds); if (usbredirhost_has_data_to_write(host)) { FD_SET(client_fd, &writefds); } nfds = client_fd + 1; free(pollfds); pollfds = libusb_get_pollfds(ctx); for (i = 0; pollfds && pollfds[i]; i++) { if (pollfds[i]->events & POLLIN) { FD_SET(pollfds[i]->fd, &readfds); } if (pollfds[i]->events & POLLOUT) { FD_SET(pollfds[i]->fd, &writefds); } if (pollfds[i]->fd >= nfds) nfds = pollfds[i]->fd + 1; } if (libusb_get_next_timeout(ctx, &timeout) == 1) { timeout_p = &timeout; } else { timeout_p = NULL; } n = select(nfds, &readfds, &writefds, NULL, timeout_p); if (n == -1) { if (errno == EINTR) { continue; } perror("select"); break; } memset(&timeout, 0, sizeof(timeout)); if (n == 0) { libusb_handle_events_timeout(ctx, &timeout); continue; } if (FD_ISSET(client_fd, &readfds)) { if (usbredirhost_read_guest_data(host)) { break; } } /* usbredirhost_read_guest_data may have detected client disconnect */ if (client_fd == -1) break; if (FD_ISSET(client_fd, &writefds)) { if (usbredirhost_write_guest_data(host)) { break; } } for (i = 0; pollfds && pollfds[i]; i++) { if (FD_ISSET(pollfds[i]->fd, &readfds) || FD_ISSET(pollfds[i]->fd, &writefds)) { libusb_handle_events_timeout(ctx, &timeout); break; } } } if (client_fd != -1) { /* Broken out of the loop because of an error ? */ close(client_fd); client_fd = -1; } free(pollfds); } static void quit_handler(int sig) { running = 0; } int main(int argc, char *argv[]) { int o, flags, server_fd = -1; char *endptr, *delim; int port = 4000; int usbbus = -1; int usbaddr = -1; int usbvendor = -1; int usbproduct = -1; int on = 1; int keepalive = -1; char *ipv4_addr = NULL, *ipv6_addr = NULL; union { struct sockaddr_in v4; struct sockaddr_in6 v6; } serveraddr; struct sigaction act; libusb_device_handle *handle = NULL; while ((o = getopt_long(argc, argv, "hp:v:4:6:k:", longopts, NULL)) != -1) { switch (o) { case 'p': port = strtol(optarg, &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "Invalid value for --port: '%s'\n", optarg); usage(1, argv[0]); } break; case 'v': verbose = strtol(optarg, &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "Invalid value for --verbose: '%s'\n", optarg); usage(1, argv[0]); } break; case '4': ipv4_addr = optarg; break; case '6': ipv6_addr = optarg; break; case 'k': keepalive = strtol(optarg, &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "Invalid value for -k: '%s'\n", optarg); usage(1, argv[0]); } break; case '?': case 'h': usage(o == '?', argv[0]); break; } } if (optind == argc) { fprintf(stderr, "Missing usb device identifier argument\n"); usage(1, argv[0]); } delim = strchr(argv[optind], '-'); if (delim && delim[1]) { usbbus = strtol(argv[optind], &endptr, 10); if (*endptr != '-') { invalid_usb_device_id(argv[optind], argv[0]); } usbaddr = strtol(delim + 1, &endptr, 10); if (*endptr != '\0') { invalid_usb_device_id(argv[optind], argv[0]); } } else { delim = strchr(argv[optind], ':'); if (!delim || !delim[1]) { invalid_usb_device_id(argv[optind], argv[0]); } usbvendor = strtol(argv[optind], &endptr, 16); if (*endptr != ':' || usbvendor <= 0 || usbvendor > 0xffff) { invalid_usb_device_id(argv[optind], argv[0]); } usbproduct = strtol(delim + 1, &endptr, 16); /* Product ID 0000 is valid */ if (*endptr != '\0' || usbproduct < 0 || usbproduct > 0xffff) { invalid_usb_device_id(argv[optind], argv[0]); } } optind++; if (optind != argc) { fprintf(stderr, "Excess non option arguments\n"); usage(1, argv[0]); } memset(&act, 0, sizeof(act)); act.sa_handler = quit_handler; sigaction(SIGINT, &act, NULL); sigaction(SIGHUP, &act, NULL); sigaction(SIGTERM, &act, NULL); sigaction(SIGQUIT, &act, NULL); if (libusb_init(&ctx)) { fprintf(stderr, "Could not init libusb\n"); exit(1); } #if LIBUSB_API_VERSION >= 0x01000106 libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, verbose); #else libusb_set_debug(ctx, verbose); #endif if (ipv4_addr) { server_fd = socket(AF_INET, SOCK_STREAM, 0); } else { server_fd = socket(AF_INET6, SOCK_STREAM, 0); } if (server_fd == -1) { perror("Error creating ip socket"); exit(1); } if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) { perror("Error setsockopt(SO_REUSEADDR) failed"); exit(1); } memset(&serveraddr, 0, sizeof(serveraddr)); if (ipv4_addr) { serveraddr.v4.sin_family = AF_INET; serveraddr.v4.sin_port = htons(port); if ((inet_pton(AF_INET, ipv4_addr, &serveraddr.v4.sin_addr)) != 1) { perror("Error convert ipv4 address"); exit(1); } } else { serveraddr.v6.sin6_family = AF_INET6; serveraddr.v6.sin6_port = htons(port); if (ipv6_addr) { if ((inet_pton(AF_INET6, ipv6_addr, &serveraddr.v6.sin6_addr)) != 1) { perror("Error convert ipv6 address"); exit(1); } } else { serveraddr.v6.sin6_addr = in6addr_any; } } if (bind(server_fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) { perror("Error bind"); exit(1); } if (listen(server_fd, 1)) { perror("Error listening"); exit(1); } while (running) { client_fd = accept(server_fd, NULL, 0); if (client_fd == -1) { if (errno == EINTR) { continue; } perror("accept"); break; } if (keepalive > 0) { int optval = 1; socklen_t optlen = sizeof(optval); if (setsockopt(client_fd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) == -1) { if (errno != ENOTSUP) { perror("setsockopt SO_KEEPALIVE error."); break; } } optval = keepalive; /* set default TCP_KEEPIDLE time from cmdline */ if (setsockopt(client_fd, SOL_TCP, TCP_KEEPIDLE, &optval, optlen) == -1) { if (errno != ENOTSUP) { perror("setsockopt TCP_KEEPIDLE error."); break; } } optval = 10; /* set default TCP_KEEPINTVL time as 10s */ if (setsockopt(client_fd, SOL_TCP, TCP_KEEPINTVL, &optval, optlen) == -1) { if (errno != ENOTSUP) { perror("setsockopt TCP_KEEPINTVL error."); break; } } optval = 3; /* set default TCP_KEEPCNT as 3 */ if (setsockopt(client_fd, SOL_TCP, TCP_KEEPCNT, &optval, optlen) == -1) { if (errno != ENOTSUP) { perror("setsockopt TCP_KEEPCNT error."); break; } } } flags = fcntl(client_fd, F_GETFL); if (flags == -1) { perror("fcntl F_GETFL"); break; } flags = fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); if (flags == -1) { perror("fcntl F_SETFL O_NONBLOCK"); break; } /* Try to find the specified usb device */ if (usbvendor != -1) { handle = libusb_open_device_with_vid_pid(ctx, usbvendor, usbproduct); if (!handle) { fprintf(stderr, "Could not open an usb-device with vid:pid %04x:%04x\n", usbvendor, usbproduct); } else if (verbose >= usbredirparser_info) { libusb_device *dev; dev = libusb_get_device(handle); fprintf(stderr, "Open a usb-device with vid:pid %04x:%04x on " "bus %03x device %03x\n", usbvendor, usbproduct, libusb_get_bus_number(dev), libusb_get_device_address(dev)); } } else { libusb_device **list = NULL; ssize_t i, n; n = libusb_get_device_list(ctx, &list); for (i = 0; i < n; i++) { if (libusb_get_bus_number(list[i]) == usbbus && libusb_get_device_address(list[i]) == usbaddr) break; } if (i < n) { if (libusb_open(list[i], &handle) != 0) { fprintf(stderr, "Could not open usb-device at busnum-devnum %d-%d\n", usbbus, usbaddr); } } else { fprintf(stderr, "Could not find an usb-device at busnum-devnum %d-%d\n", usbbus, usbaddr); } libusb_free_device_list(list, 1); } if (!handle) { close(client_fd); continue; } host = usbredirhost_open(ctx, handle, usbredirserver_log, usbredirserver_read, usbredirserver_write, NULL, SERVER_VERSION, verbose, 0); if (!host) exit(1); run_main_loop(); usbredirhost_close(host); handle = NULL; } close(server_fd); libusb_exit(ctx); exit(0); } usbredir-usbredir-0.11.0/usbredirtestclient/000077500000000000000000000000001410424124300211365ustar00rootroot00000000000000usbredir-usbredir-0.11.0/usbredirtestclient/meson.build000066400000000000000000000003651410424124300233040ustar00rootroot00000000000000usbredirtestclient_sources = [ 'usbredirtestclient.c', ] executable('usbredirtestclient', sources : usbredirtestclient_sources, c_args : '-Wno-deprecated-declarations', install : false, dependencies : usbredir_host_lib_dep) usbredir-usbredir-0.11.0/usbredirtestclient/usbredirtestclient.c000066400000000000000000000451531410424124300252300ustar00rootroot00000000000000/* usbredirtestclient.c simple usb network redirection test client (guest). Copyright 2010-2011 Red Hat, Inc. Red Hat Authors: Hans de Goede This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbredirparser.h" /* Macros to go from an endpoint address to an index for our ep array */ #define EP2I(ep_address) (((ep_address & 0x80) >> 3) | (ep_address & 0x0f)) #define I2EP(i) (((i & 0x10) << 3) | (i & 0x0f)) #define TESTCLIENT_VERSION "usbredirtestclient " PACKAGE_VERSION static void usbredirtestclient_device_connect(void *priv, struct usb_redir_device_connect_header *device_connect); static void usbredirtestclient_device_disconnect(void *priv); static void usbredirtestclient_interface_info(void *priv, struct usb_redir_interface_info_header *interface_info); static void usbredirtestclient_ep_info(void *priv, struct usb_redir_ep_info_header *ep_info); static void usbredirtestclient_configuration_status(void *priv, uint64_t id, struct usb_redir_configuration_status_header *configuration_status); static void usbredirtestclient_alt_setting_status(void *priv, uint64_t id, struct usb_redir_alt_setting_status_header *alt_setting_status); static void usbredirtestclient_iso_stream_status(void *priv, uint64_t id, struct usb_redir_iso_stream_status_header *iso_stream_status); static void usbredirtestclient_interrupt_receiving_status(void *priv, uint64_t id, struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status); static void usbredirtestclient_bulk_streams_status(void *priv, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status); static void usbredirtestclient_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *control_packet, uint8_t *data, int data_len); static void usbredirtestclient_bulk_packet(void *priv, uint64_t id, struct usb_redir_bulk_packet_header *bulk_packet, uint8_t *data, int data_len); static void usbredirtestclient_iso_packet(void *priv, uint64_t id, struct usb_redir_iso_packet_header *iso_packet, uint8_t *data, int data_len); static void usbredirtestclient_interrupt_packet(void *priv, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_packet, uint8_t *data, int data_len); /* id's for all the test commands we send */ enum { reset_id, get_config_id, set_config_id, get_alt_id, set_alt_id, first_cmdline_id }; static int verbose = usbredirparser_info; /* 3 */ static int client_fd, running = 1; static struct usbredirparser *parser; static int id = first_cmdline_id; static const struct option longopts[] = { { "port", required_argument, NULL, 'p' }, { "verbose", required_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; static void usbredirtestclient_log(void *priv, int level, const char *msg) { if (level <= verbose) fprintf(stderr, "%s\n", msg); } static int usbredirtestclient_read(void *priv, uint8_t *data, int count) { int r = read(client_fd, data, count); if (r < 0) { if (errno == EAGAIN) return 0; return -1; } if (r == 0) { /* Server disconnected */ close(client_fd); client_fd = -1; } return r; } static int usbredirtestclient_write(void *priv, uint8_t *data, int count) { int r = write(client_fd, data, count); if (r < 0) { if (errno == EAGAIN) return 0; if (errno == EPIPE) { /* Server disconnected */ close(client_fd); client_fd = -1; return 0; } return -1; } return r; } static void usbredirtestclient_hello(void *priv, struct usb_redir_hello_header *h) { /* Queue a reset + set config the other test commands will be send in response to the status packets of previous commands */ usbredirparser_send_reset(parser); usbredirparser_send_get_configuration(parser, get_config_id); } static void usage(int exit_code, char *argv0) { fprintf(exit_code? stderr:stdout, "Usage: %s [-p|--port ] [-v|--verbose <0-3>] \n", argv0); exit(exit_code); } static void run_main_loop(void) { fd_set readfds, writefds; int n, nfds; while (running && client_fd != -1) { FD_ZERO(&readfds); FD_ZERO(&writefds); FD_SET(client_fd, &readfds); if (usbredirparser_has_data_to_write(parser)) { FD_SET(client_fd, &writefds); } nfds = client_fd + 1; n = select(nfds, &readfds, &writefds, NULL, NULL); if (n == -1) { if (errno == EINTR) { continue; } perror("select"); break; } if (FD_ISSET(client_fd, &readfds)) { if (usbredirparser_do_read(parser)) { break; } } if (FD_ISSET(client_fd, &writefds)) { if (usbredirparser_do_write(parser)) { break; } } } if (client_fd != -1) { /* Broken out of the loop because of an error ? */ close(client_fd); client_fd = -1; } } static void quit_handler(int sig) { running = 0; } int main(int argc, char *argv[]) { int o, flags; char *endptr, *server; struct addrinfo *r, *res, hints; struct sigaction act; char port_str[16]; int port = 4000; uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0, }; while ((o = getopt_long(argc, argv, "hp:", longopts, NULL)) != -1) { switch (o) { case 'p': port = strtol(optarg, &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "Inalid value for --port: '%s'\n", optarg); usage(1, argv[0]); } break; case 'v': verbose = strtol(optarg, &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "Inalid value for --verbose: '%s'\n", optarg); usage(1, argv[0]); } break; case '?': case 'h': usage(o == '?', argv[0]); break; } } if (optind == argc) { fprintf(stderr, "Missing server argument\n"); usage(1, argv[0]); } server = argv[optind]; optind++; if (optind != argc) { fprintf(stderr, "Excess non option arguments\n"); usage(1, argv[0]); } memset(&act, 0, sizeof(act)); act.sa_handler = quit_handler; sigaction(SIGINT, &act, NULL); sigaction(SIGHUP, &act, NULL); sigaction(SIGTERM, &act, NULL); sigaction(SIGQUIT, &act, NULL); memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; sprintf(port_str, "%d", port); if (getaddrinfo(server, port_str, &hints, &res) != 0) { perror("getaddrinfo"); exit(1); } for (r = res; r != NULL; r = r->ai_next) { client_fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol); if (client_fd == -1) continue; if (connect(client_fd, r->ai_addr, r->ai_addrlen) == 0) break; close(client_fd); } freeaddrinfo(res); if (r == NULL) { fprintf(stderr, "Could not connect to: [%s]:%s\n", server, port_str); exit(1); } flags = fcntl(client_fd, F_GETFL); if (flags == -1) { perror("fcntl F_GETFL"); exit(1); } flags = fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); if (flags == -1) { perror("fcntl F_SETFL O_NONBLOCK"); exit(1); } parser = usbredirparser_create(); if (!parser) { exit(1); } parser->log_func = usbredirtestclient_log; parser->read_func = usbredirtestclient_read; parser->write_func = usbredirtestclient_write; parser->hello_func = usbredirtestclient_hello; parser->device_connect_func = usbredirtestclient_device_connect; parser->device_disconnect_func = usbredirtestclient_device_disconnect; parser->interface_info_func = usbredirtestclient_interface_info; parser->ep_info_func = usbredirtestclient_ep_info; parser->configuration_status_func = usbredirtestclient_configuration_status; parser->alt_setting_status_func = usbredirtestclient_alt_setting_status; parser->iso_stream_status_func = usbredirtestclient_iso_stream_status; parser->interrupt_receiving_status_func = usbredirtestclient_interrupt_receiving_status; parser->bulk_streams_status_func = usbredirtestclient_bulk_streams_status; parser->control_packet_func = usbredirtestclient_control_packet; parser->bulk_packet_func = usbredirtestclient_bulk_packet; parser->iso_packet_func = usbredirtestclient_iso_packet; parser->interrupt_packet_func = usbredirtestclient_interrupt_packet; usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size); usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids); usbredirparser_init(parser, TESTCLIENT_VERSION, caps, USB_REDIR_CAPS_SIZE, 0); run_main_loop(); exit(0); } static void usbredirtestclient_cmdline_help(void) { printf("Available commands:\n" "ctrl [data]\n" "quit\n" "help\n"); } static int usbredirtestclient_cmdline_ctrl(void) { struct usb_redir_control_packet_header control_packet; char *arg, *endptr = NULL; uint8_t *data = NULL; int data_len; arg = strtok(NULL, " \t\n"); if (arg) { control_packet.endpoint = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid endpoint\n"); return 0; } arg = strtok(NULL, " \t\n"); if (arg) { control_packet.request = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid request\n"); return 0; } arg = strtok(NULL, " \t\n"); if (arg) { control_packet.requesttype = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid request type\n"); return 0; } arg = strtok(NULL, " \t\n"); if (arg) { control_packet.value = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid value\n"); return 0; } arg = strtok(NULL, " \t\n"); if (arg) { control_packet.index = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid index\n"); return 0; } arg = strtok(NULL, " \t\n"); if (arg) { control_packet.length = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid length\n"); return 0; } if (!(control_packet.endpoint & 0x80)) { int i; data = malloc(control_packet.length); if (!data) { fprintf(stderr, "Out of memory!\n"); close(client_fd); client_fd = -1; return 0; } for (i = 0; i < control_packet.length; i++) { arg = strtok(NULL, " \t\n"); if (arg) { data[i] = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid data byte(s)\n"); return 0; } } data_len = control_packet.length; } else { data_len = 0; } usbredirparser_send_control_packet(parser, id, &control_packet, data, data_len); free(data); printf("Send control packet with id: %u\n", id); id++; return 1; } static void usbredirtestclient_cmdline_parse(void) { char buf[128]; char *cmd; while (running && client_fd != -1) { printf("> "); if (!fgets(buf, sizeof(buf), stdin)) { close(client_fd); client_fd = -1; return; } cmd = strtok(buf, " \t\n"); if (!cmd) continue; if (!strcmp(cmd, "help")) { usbredirtestclient_cmdline_help(); } else if (!strcmp(cmd, "quit")) { close(client_fd); client_fd = -1; return; } else if (!strcmp(cmd, "ctrl")) { if (usbredirtestclient_cmdline_ctrl()) { return; /* Run main loop until an answer is received */ } } else { printf("unknown command: '%s', type 'help' for help\n", cmd); } } } static void usbredirtestclient_device_connect(void *priv, struct usb_redir_device_connect_header *device_connect) { switch (device_connect->speed) { case usb_redir_speed_low: printf("device info: speed: low\n"); break; case usb_redir_speed_full: printf("device info: speed: full\n"); break; case usb_redir_speed_high: printf("device info: speed: high\n"); break; case usb_redir_speed_super: printf("device info: speed: super\n"); break; default: printf("device info: speed: unknown\n"); } printf(" class %2d subclass %2d protocol %2d\n", device_connect->device_class, device_connect->device_subclass, device_connect->device_protocol); printf(" vendor 0x%04x product %04x\n", device_connect->vendor_id, device_connect->product_id); } static void usbredirtestclient_device_disconnect(void *priv) { printf("device disconnected"); close(client_fd); client_fd = -1; } static void usbredirtestclient_interface_info(void *priv, struct usb_redir_interface_info_header *info) { int i; for (i = 0; i < info->interface_count; i++) { printf("interface %d class %2d subclass %2d protocol %2d\n", info->interface[i], info->interface_class[i], info->interface_subclass[i], info->interface_protocol[i]); } } static void usbredirtestclient_ep_info(void *priv, struct usb_redir_ep_info_header *ep_info) { int i; for (i = 0; i < 32; i++) { if (ep_info->type[i] != usb_redir_type_invalid) { printf("endpoint: %02X, type: %d, interval: %d, interface: %d max-packetsize: %d\n", I2EP(i), (int)ep_info->type[i], (int)ep_info->interval[i], (int)ep_info->interface[i], ep_info->max_packet_size[i]); } } } static void usbredirtestclient_configuration_status(void *priv, uint64_t id, struct usb_redir_configuration_status_header *config_status) { struct usb_redir_set_configuration_header set_config; struct usb_redir_get_alt_setting_header get_alt; switch (id) { case get_config_id: printf("Get config: %d, status: %d\n", config_status->configuration, config_status->status); set_config.configuration = config_status->configuration; usbredirparser_send_set_configuration(parser, set_config_id, &set_config); break; case set_config_id: printf("Set config: %d, status: %d\n", config_status->configuration, config_status->status); get_alt.interface = 0; /* Assume the device has an interface 0 */ usbredirparser_send_get_alt_setting(parser, get_alt_id, &get_alt); break; default: fprintf(stderr, "Unexpected configuration status packet, id: %" PRIu64"\n", id); } } static void usbredirtestclient_alt_setting_status(void *priv, uint64_t id, struct usb_redir_alt_setting_status_header *alt_setting_status) { struct usb_redir_set_alt_setting_header set_alt; switch (id) { case get_alt_id: printf("Get alt: %d, interface: %d, status: %d\n", alt_setting_status->alt, alt_setting_status->interface, alt_setting_status->status); set_alt.interface = alt_setting_status->interface; set_alt.alt = alt_setting_status->alt; usbredirparser_send_set_alt_setting(parser, set_alt_id, &set_alt); break; case set_alt_id: printf("Set alt: %d, interface: %d, status: %d\n", alt_setting_status->alt, alt_setting_status->interface, alt_setting_status->status); /* Auto tests done, go interactive */ usbredirtestclient_cmdline_parse(); break; default: fprintf(stderr, "Unexpected alt status packet, id: %"PRIu64"\n", id); } } static void usbredirtestclient_iso_stream_status(void *priv, uint64_t id, struct usb_redir_iso_stream_status_header *iso_stream_status) { } static void usbredirtestclient_interrupt_receiving_status(void *priv, uint64_t id, struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status) { } static void usbredirtestclient_bulk_streams_status(void *priv, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status) { } static void usbredirtestclient_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *control_packet, uint8_t *data, int data_len) { int i; printf("Control packet id: %"PRIu64", status: %d", id, control_packet->status); if (data_len) { printf(", data:"); } for (i = 0; i < data_len; i++) { printf(" %02X", (unsigned int)data[i]); } printf("\n"); usbredirparser_free_packet_data(parser, data); /* Ask what to send next */ usbredirtestclient_cmdline_parse(); } static void usbredirtestclient_bulk_packet(void *priv, uint64_t id, struct usb_redir_bulk_packet_header *bulk_packet, uint8_t *data, int data_len) { } static void usbredirtestclient_iso_packet(void *priv, uint64_t id, struct usb_redir_iso_packet_header *iso_packet, uint8_t *data, int data_len) { } static void usbredirtestclient_interrupt_packet(void *priv, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_packet, uint8_t *data, int data_len) { }