pax_global_header00006660000000000000000000000064140266503750014522gustar00rootroot0000000000000052 comment=42c8408936d8e30c83c3d79237350aa0d626cbca fwupd-1.2.14/000077500000000000000000000000001402665037500127345ustar00rootroot00000000000000fwupd-1.2.14/.circleci/000077500000000000000000000000001402665037500145675ustar00rootroot00000000000000fwupd-1.2.14/.circleci/config.yml000066400000000000000000000003351402665037500165600ustar00rootroot00000000000000version: 2 jobs: noop: machine: image: circleci/classic:latest steps: - run: name: "No-op" command: echo "No-op for 1_2_X" workflows: version: 2 main: jobs: - noop fwupd-1.2.14/.github/000077500000000000000000000000001402665037500142745ustar00rootroot00000000000000fwupd-1.2.14/.github/ISSUE_TEMPLATE.md000066400000000000000000000020751402665037500170050ustar00rootroot00000000000000To help us pinpoint your issue, please insert the output of the following commands when ran on the system with the issue: ```shell $ fwupdmgr --version **INSERT OUTPUT HERE** ``` Note, the switch `--version` is only present since version 0.9.6. If you use an earlier version, please use the package manager to find out the package version. For example, `dpkg -l fwupd`. ```shell $ fwupdmgr get-devices **INSERT OUTPUT HERE** ``` ```shell $ efibootmgr -v **INSERT OUTPUT HERE** **This is only required if you use the UEFI plugin** ``` ```shell $ efivar -l | grep fw **INSERT OUTPUT HERE** **This is only required if you use the UEFI plugin** ``` ```shell $ tree /boot **INSERT OUTPUT HERE** **This is only required if you use the UEFI plugin** **We're looking for any `.cap` files and the location of `fwupx64.efi`** ``` Please answer the following questions: - Operating system and version: - How did you install fwupd (ex: `from source`, `pacman`, `apt-get`, etc): - Have you tried rebooting? - Are you using an NVMe disk? - Is secure boot enabled (only for the UEFI plugin)? fwupd-1.2.14/.github/pull_request_template.md000066400000000000000000000002741402665037500212400ustar00rootroot00000000000000Type of pull request: - [ ] New plugin (Please include [new plugin checklist](https://github.com/hughsie/fwupd/wiki/New-plugin-checklist)) - [ ] Code fix - [ ] Feature - [ ] Documentation fwupd-1.2.14/.github/workflows/000077500000000000000000000000001402665037500163315ustar00rootroot00000000000000fwupd-1.2.14/.github/workflows/main.yml000066400000000000000000000013331402665037500200000ustar00rootroot00000000000000name: Continuous Integration on: push: branches: [ 1_2_X ] pull_request: branches: [ 1_2_X ] jobs: build: runs-on: ubuntu-latest strategy: matrix: os: [fedora, debian-x86_64, debian-i386] steps: - uses: actions/checkout@v2 - name: Docker login run: docker login docker.pkg.github.com -u $GITHUB_ACTOR -p $GITHUB_TOKEN env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Build in container env: CI_NETWORK: true CI: true run: | echo $GITHUB_WORKSPACE docker run --privileged -e CI=true -t -v $GITHUB_WORKSPACE:/github/workspace docker.pkg.github.com/fwupd/fwupd/fwupd-${{matrix.os}}:latest fwupd-1.2.14/.gitignore000066400000000000000000000003431402665037500147240ustar00rootroot00000000000000/build /dist /.vscode /build-dir /.flatpak-builder /repo *.flatpak *.snap /fwupd_source.tar.bz2 /parts /prime /stage /snap/.snapcraft /libxmlb /*.deb /*.ddeb /*.changes /*.buildinfo /fwupd*.build /*.dsc /*.xz /*.gz __pycache__ fwupd-1.2.14/.gitmodules000066400000000000000000000001561402665037500151130ustar00rootroot00000000000000[submodule "contrib/flatpak"] path = contrib/flatpak url = https://github.com/flathub/org.freedesktop.fwupd fwupd-1.2.14/.lgtm.yml000066400000000000000000000013141402665037500144770ustar00rootroot00000000000000extraction: python: python_setup: version: "3" cpp: prepare: packages: - python3-gi - libarchive-tools - libcogl-pango-dev - python3-pil - python3-cairo after_prepare: - "wget -O libxmlb.zip https://github.com/hughsie/libxmlb/archive/0.1.7.zip" - "mkdir -p subprojects/libxmlb" - "bsdtar --strip-components=1 -xvf libxmlb.zip -C subprojects/libxmlb" - "wget -O flashrom.zip https://github.com/hughsie/flashrom/archive/wip/hughsie/fwupd.zip" - "mkdir -p subprojects/flashrom" - "bsdtar --strip-components=1 -xvf flashrom.zip -C subprojects/flashrom" index: build_command: - "meson setup build" - "ninja -C build" fwupd-1.2.14/.tx/000077500000000000000000000000001402665037500134455ustar00rootroot00000000000000fwupd-1.2.14/.tx/config000066400000000000000000000002111402665037500146270ustar00rootroot00000000000000[main] host = https://www.transifex.com [fwupd.master] file_filter = po/.po source_file = po/fwupd.pot source_lang = en type = PO fwupd-1.2.14/AUTHORS000066400000000000000000000000451402665037500140030ustar00rootroot00000000000000Richard Hughes fwupd-1.2.14/CODE_OF_CONDUCT.md000066400000000000000000000062231402665037500155360ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fwupd@googlegroups.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ fwupd-1.2.14/COMMITMENT000066400000000000000000000037761402665037500143500ustar00rootroot00000000000000Common Cure Rights Commitment, version 1.0 Before filing or continuing to prosecute any legal proceeding or claim (other than a Defensive Action) arising from termination of a Covered License, we commit to extend to the person or entity ('you') accused of violating the Covered License the following provisions regarding cure and reinstatement, taken from GPL version 3. As used here, the term 'this License' refers to the specific Covered License being enforced. However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. We intend this Commitment to be irrevocable, and binding and enforceable against us and assignees of or successors to our copyrights. Definitions 'Covered License' means the GNU General Public License, version 2 (GPLv2), the GNU Lesser General Public License, version 2.1 (LGPLv2.1), or the GNU Library General Public License, version 2 (LGPLv2), all as published by the Free Software Foundation. 'Defensive Action' means a legal proceeding or claim that We bring against you in response to a prior proceeding or claim initiated by you or your affiliate. 'We' means each contributor to this repository as of the date of inclusion of this file, including subsidiaries of a corporate contributor. This work is available under a Creative Commons Attribution-ShareAlike 4.0 International license. fwupd-1.2.14/CONTRIBUTING.md000066400000000000000000000025111402665037500151640ustar00rootroot00000000000000Coding Style ============ The coding style to respect in this project is very similar to most GLib projects. In particular, the following rules are largely adapted from the PackageKit Coding Style. * 8-space tabs for indentation * Prefer lines of less than <= 80 columns * 1-space between function name and braces (both calls and macro declarations) * If function signature/call fits in a single line, do not break it into multiple lines * Prefer descriptive names over abbreviations (unless well-known) and shortening of names. e.g `device` not `dev` * Single statements inside if/else should not be enclosed by '{}' * Use comments to explain why something is being done, but also avoid over-documenting the obvious. Here is an example of useless comment: // Fetch the document fetch_the_document (); * Comments should not start with a capital letter or end with a full stop and should be C-style, not C++-style, e.g. `/* this */` not `// this` * Each object should go in a separate .c file and be named according to the class * Use g_autoptr() and g_autofree whenever possible, and avoid `goto out` error handling * Failing methods should return FALSE with a suitable `GError` set * Trailing whitespace is forbidden * Pointers should be checked for NULL explicitly, e.g. `foo != NULL` not `!foo` fwupd-1.2.14/COPYING000066400000000000000000000636421402665037500140020ustar00rootroot00000000000000 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! fwupd-1.2.14/MAINTAINERS000066400000000000000000000000451402665037500144300ustar00rootroot00000000000000Richard Hughes fwupd-1.2.14/README.md000066400000000000000000000104431402665037500142150ustar00rootroot00000000000000fwupd ===== [![Build Status](https://travis-ci.org/hughsie/fwupd.png?branch=master)](https://travis-ci.org/hughsie/fwupd) [![Coverity Scan Build Status](https://scan.coverity.com/projects/10744/badge.svg)](https://scan.coverity.com/projects/10744) [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/fwupd) This project aims to make updating firmware on Linux automatic, safe and reliable. Additional information is available at the website: https://fwupd.org ## Compiling The most up to date compilation instructions are available in the [Wiki](https://github.com/hughsie/fwupd/wiki/Compilation) LVFS ---- This project is configured by default to download firmware from the [Linux Vendor Firmware Service (LVFS)](https://fwupd.org/). This service is available to all OEMs and firmware creators who would like to make their firmware available to Linux users. You can find more information about the technical details of creating a firmware capsule in the hardware vendors section of the [fwupd website](https://fwupd.org). Basic usage flow (command line) ------------------------------ If you have a device with firmware supported by fwupd, this is how you will check for updates and apply them using fwupd's command line tools. `# fwupdmgr get-devices` This will display all devices detected by fwupd. `# fwupdmgr refresh` This will download the latest metadata from LVFS. `# fwupdmgr get-updates` If updates are available for any devices on the system, they'll be displayed. `# fwupdmgr update` This will download and apply all updates for your system. * Updates that can be applied live will be done immediately. * Updates that run at bootup will be staged for the next reboot. You can find more information about the update workflow in the end users section of the [fwupd website](https://fwupd.org). Reporting status --------------- fwupd will encourage users to report both successful and failed updates back to LVFS. This is an optional feature, but encouraged as it provides valuable feedback to LVFS administrators and OEM developers regarding firmware update process efficacy. The privacy policy regarding this data can be viewed on the [fwupd website](https://fwupd.org/privacy). To report the status of an update run: `# fwupdmgr report-history` To clear the local history of updates: `# fwupdmgr clear-history` Only updates that were distributed from the LVFS will be reported to the LVFS. Enterprise Use -------------- The flow of updates can be controlled in the enterprise using the "approved updates" feature. This allows the domain administrator to filter the possible updates from a central server (e.g. the LVFS, or a mirror) to only firmware that have been tested specifically in your organisation. The list of approved updates can be enabled by adding `ApprovalRequired=true` to the remote configuration file, e.g. `lvfs.conf`. Once enabled, the list of approved updates can be set in `daemon.conf` using a comma delimited list. For example: ApprovedFirmware=foo,bar Where `foo,bar` refers to the container checksums that would correspond to two updates in the metadata file. Additionally, the list of approved firmware can be supplemented using `fwupdmgr set-approved-firmware baz` or using the D-Bus interface. Other frontends ------------------- 1. [GNOME Software](https://wiki.gnome.org/Apps/Software) is the graphical frontend available. When compiled with firmware support, it will check for updates periodically and automatically download firmware in the background. After the firmware has been downloaded a popup will be displayed in Gnome Software to perform the update. 2. [KDE Discover](https://userbase.kde.org/Discover) is the software centre, generally bundled with KDE Plasma. With the release of [KDE Plasma 5.14](https://www.kde.org/announcements/plasma-5.14.0.php), a new fwupd backend has been implemented in KDE Discover for firmware updates. These firmware updates are shown with other system updates. 3. [Wyse Management Suite](https://www.dell.com/en-us/work/shop/wyse-endpoints-and-software/wyse-management-suite/spd/wyse-wms) A software suite available on Dell IoT gateways and Wyse thin clients with built-in fwupd support. The remote administration interface can be used to download and deploy firmware updates. fwupd-1.2.14/RELEASE000066400000000000000000000016451402665037500137450ustar00rootroot00000000000000fwupd Release Notes Write release entries: git log --format="%s" --cherry-pick --right-only 1.2.13... | grep -i -v trivial | grep -v Merge | sort | uniq Add any user visible changes into ../data/org.freedesktop.fwupd.metainfo.xml appstream-util appdata-to-news ../data/org.freedesktop.fwupd.metainfo.xml > NEWS 2. Commit changes to git: # MAKE SURE THIS IS CORRECT export release_ver="1.2.14" git commit -a -m "Release fwupd ${release_ver}" git tag -s -f -m "Release fwupd ${release_ver}" "${release_ver}" git push --tags git push 3. Generate the tarball: ninja dist 3a. Generate the additional verification metadata gpg -b -a meson-dist/fwupd-${release_ver}.tar.xz 4. Upload tarball: scp meson-dist/fwupd-${release_ver}.tar.* hughsient@people.freedesktop.org:~/public_html/releases 5. Do post release version bump in meson.build 6. Commit changes: git commit -a -m "trivial: post release version bump" git push fwupd-1.2.14/SECURITY.md000066400000000000000000000025601402665037500145300ustar00rootroot00000000000000# Security Policy Due to the nature of what we are doing, fwupd takes security very seriously. If you have any concerns please let us know. ## Supported Versions The `1.2.x` and `1.1.x` branches are fully supported by the upstream authors. Additonally, the `1.0.x` branch is supported for security and bug fixes. Older releases than this are unsupported by upstream but may be supported by your distributor or distribution. If you open an issue with one of these older releases the very first question from us is going to be asking if it's fixed on a supported branch. You can use the flatpak or snap packages if your distributor is unwilling to update to a supported version. | Version | Supported | | ------- | ------------------ | | 1.2.x | :heavy_check_mark: | | 1.1.x | :heavy_check_mark: | | 1.0.x | :white_check_mark: | | 0.9.x | :x: | | 0.8.x | :x: | ## Reporting a Vulnerability If you find a vulnerability in fwupd your first thing you should do is email all the maintainers, which are currently listed in the `MAINTAINERS` file in this repository. Failing that, please report the issue against the `fwupd` component in Red Hat bugzilla, with the security checkbox set. You should get a response within 3 days. We have no bug bountry program, but we're happy to credit you in updates if this is what you would like us to do. fwupd-1.2.14/contrib/000077500000000000000000000000001402665037500143745ustar00rootroot00000000000000fwupd-1.2.14/contrib/PKGBUILD000066400000000000000000000022021402665037500155140ustar00rootroot00000000000000# Maintainer: Bruno Pagani (a.k.a. ArchangeGabriel) # Contributor: Mirco Tischler pkgname=fwupd pkgver=#VERSION# pkgrel=1 pkgdesc='A simple daemon to allow session software to update firmware' arch=('i686' 'x86_64') url='https://github.com/hughsie/fwupd' license=('GPL2') depends=('libgusb' 'modemmanager') optdepends=('tpm2-abrmd' 'tpm2-tools') makedepends=('meson' 'valgrind' 'gobject-introspection' 'gtk-doc' 'python-pillow' 'git' 'python-cairo' 'noto-fonts' 'noto-fonts-cjk' 'python-gobject' 'vala' 'libsoup' 'polkit' 'gcab') build() { cd ${pkgname} if [ -n "$CI" ]; then export CI="--wrap-mode=default" fi arch-meson -D b_lto=false $CI ../build \ -Defi_sbat_distro_id="arch" \ -Defi_sbat_distro_summary="Arch Linux" \ -Defi_sbat_distro_pkgname="${pkgname}" \ -Defi_sbat_distro_version="$(pkgver)" \ -Defi_sbat_distro_url="https://archlinux.org/packages/community/x86_64/${pkgname}/" ninja -v -C ../build } check() { ninja -C build test } package() { DESTDIR="${pkgdir}" ninja -C build install } fwupd-1.2.14/contrib/README.md000066400000000000000000000042171402665037500156570ustar00rootroot00000000000000Distribution packages ===================== The relevant packaging necessary to generate *RPM*, *DEB* and *PKG* distribution packages is contained here. It is used regularly for continuous integration using [Travis CI](http://travis-ci.org). The generated packages can be used on a distribution such as Fedora, Debian, Ubuntu or Arch Linux. The build can be performed using Linux containers with [Docker](https://www.docker.com). ## RPM packages A Dockerfile for Fedora can be generated in `contrib`. To prepare the Docker container run this command: ``` OS=fedora ./generate_docker.py ``` To build the RPMs run this command (from the root of your git checkout): ``` docker run --privileged -t -v `pwd`:/build fwupd-fedora ``` RPMs will be made available in your working directory when complete. ## DEB packages A Dockerfile for Debian or Ubuntu can be generated in `contrib`. To prepare the Docker container run one of these commands: ``` OS=debian-x86_64 ./generate_docker.py OS=debian-i386 ./generate_docker.py OS=ubuntu-x86_64 ./generate_docker.py ``` To build the DEBs run one of these commands (from the root of your git checkout): ``` docker run --privileged -t -v `pwd`:/build fwupd-debian-x86_64 docker run --privileged -t -v `pwd`:/build fwupd-debian-i386 docker run --privileged -t -v `pwd`:/build fwupd-ubuntu-x86_64 ``` DEBs will be made available in your working directory when complete. ## PKG packages A Dockerfile for Arch can be generated in `contrib`. To prepare the Docker container run this command: ``` OS=arch ./generate_docker.py ``` To build the PKGs run this command (from the root of your git checkout): ``` docker run -t -v `pwd`:/build fwupd-arch ``` PKGs will be made available in your working directory when complete. ## Additional packages Submissions for generating additional packages for other distribution mechanisms are also welcome. All builds should occur in Docker containers. Please feel free to submit the following: * Dockerfile for the container for your distro * Relevant technical packaging scripts (such as ebuilds, spec file etc) * A shell script that can be launched in the container to generate distribution packages fwupd-1.2.14/contrib/add-capsule-header.py000077500000000000000000000043011402665037500203570ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2019 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import sys import uuid import argparse import struct CAPSULE_FLAGS_PERSIST_ACROSS_RESET = 0x00010000 CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE = 0x00020000 CAPSULE_FLAGS_INITIATE_RESET = 0x00040000 def main(args): # parse GUID from command line try: guid = uuid.UUID(args.guid) except ValueError as e: print(e) return 1 try: with open(args.bin, 'rb') as f: bin_data = f.read() except FileNotFoundError as e: print(e) return 1 # check if already has header hdrsz = struct.calcsize('<16sIII') if len(bin_data) >= hdrsz: hdr = struct.unpack('<16sIII', bin_data[:hdrsz]) imgsz = hdr[3] if imgsz == len(bin_data): print('Replacing existing CAPSULE_HEADER of:') guid_mixed = uuid.UUID(bytes_le=hdr[0]) hdrsz_old = hdr[1] flags = hdr[2] print('GUID: %s' % guid_mixed) print('HdrSz: 0x%04x' % hdrsz_old) print('Flags: 0x%04x' % flags) print('PayloadSz: 0x%04x' % imgsz) bin_data = bin_data[hdrsz_old:] # set header flags flags = CAPSULE_FLAGS_PERSIST_ACROSS_RESET | CAPSULE_FLAGS_INITIATE_RESET if args.flags: flags = int(args.flags, 16) # build update capsule header imgsz = hdrsz + len(bin_data) hdr = struct.pack('<16sIII', guid.bytes_le, hdrsz, flags, imgsz) with open(args.cap, 'wb') as f: f.write(hdr + bin_data) print('Wrote capsule %s' % args.cap) print('GUID: %s' % guid) print('HdrSz: 0x%04x' % hdrsz) print('Flags: 0x%04x' % flags) print('PayloadSz: 0x%04x' % imgsz) return 0 parser = argparse.ArgumentParser(description='Add capsule header on firmware') parser.add_argument('--guid', help='GUID of the device', required=True) parser.add_argument('--bin', help='Path to the .bin file', required=True) parser.add_argument('--cap', help='Output capsule file path', required=True) parser.add_argument('--flags', help='Flags, e.g. 0x40000', default=None) args = parser.parse_args() sys.exit(main(args)) fwupd-1.2.14/contrib/ci/000077500000000000000000000000001402665037500147675ustar00rootroot00000000000000fwupd-1.2.14/contrib/ci/Dockerfile-arch.in000066400000000000000000000005101402665037500202750ustar00rootroot00000000000000FROM archlinux/base %%%OS%%% ENV LANG en_US.UTF-8 ENV LC_ALL en_US.UTF-8 RUN echo fubar > /etc/machine-id RUN rm /usr/share/libalpm/hooks/package-cleanup.hook RUN echo fubar > /etc/machine-id RUN pacman -Syu --noconfirm archlinux-keyring %%%INSTALL_DEPENDENCIES_COMMAND%%% WORKDIR /github/workspace CMD ["./contrib/ci/arch.sh"] fwupd-1.2.14/contrib/ci/Dockerfile-centos.in000066400000000000000000000010611402665037500206550ustar00rootroot00000000000000FROM centos:7 %%%OS%%% ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN echo fubar > /etc/machine-id RUN yum install epel-release -y RUN echo fubar > /etc/machine-id %%%INSTALL_DEPENDENCIES_COMMAND%%% RUN pip3 install pillow pygobject RUN wget https://copr.fedorainfracloud.org/coprs/jsynacek/systemd-backports-for-centos-7/repo/epel-7/jsynacek-systemd-backports-for-centos-7-epel-7.repo -O /etc/yum.repos.d/jsynacek-systemd-centos-7.repo RUN yum --enablerepo=epel-testing -y update WORKDIR /github/workspace CMD ["./contrib/ci/centos.sh"] fwupd-1.2.14/contrib/ci/Dockerfile-debian.in000066400000000000000000000010711402665037500206050ustar00rootroot00000000000000FROM %%%ARCH_PREFIX%%%debian:testing %%%OS%%% RUN echo "deb http://ftp.us.debian.org/debian unstable main contrib non-free" >> /etc/apt/sources.list RUN echo 'Package: *\n\ Pin: release a=testing\n\ Pin-Priority: 900\n\ \n\ Package: *\n\ Pin: release a=unstable\n\ Pin-Priority: 800\n\ \n\ Package: libxmlb-dev\n\ Pin: release a=unstable\n\ Pin-Priority: 901\n'\ > /etc/apt/preferences RUN cat /etc/apt/preferences RUN echo fubar > /etc/machine-id %%%ARCH_SPECIFIC_COMMAND%%% %%%INSTALL_DEPENDENCIES_COMMAND%%% WORKDIR /github/workspace CMD ["./contrib/ci/debian.sh"] fwupd-1.2.14/contrib/ci/Dockerfile-fedora.in000066400000000000000000000006421402665037500206260ustar00rootroot00000000000000FROM fedora:29 %%%OS%%% ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN echo fubar > /etc/machine-id RUN dnf -y update RUN echo fubar > /etc/machine-id %%%INSTALL_DEPENDENCIES_COMMAND%%% RUN dnf -y update glib2 glib2-devel --releasever=32 RUN dnf -y install flashrom-devel --enablerepo=updates-testing RUN mkdir /github/workspace WORKDIR /github/workspace COPY . . CMD ["./contrib/ci/fedora.sh"] fwupd-1.2.14/contrib/ci/Dockerfile-flatpak.in000066400000000000000000000004071402665037500210070ustar00rootroot00000000000000FROM fedora:29 %%%OS%%% ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN echo fubar > /etc/machine-id %%%INSTALL_DEPENDENCIES_COMMAND%%% RUN dnf --enablerepo=updates-testing -y update WORKDIR /github/workspace CMD ["./contrib/ci/flatpak.py"] fwupd-1.2.14/contrib/ci/Dockerfile-snap.in000066400000000000000000000017651402665037500203360ustar00rootroot00000000000000FROM ubuntu:xenial RUN apt-get update && \ apt-get dist-upgrade --yes && \ apt-get install --yes \ curl sudo jq squashfs-tools && \ curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/core' | jq '.download_url' -r) --output core.snap && \ mkdir -p /snap/core && unsquashfs -d /snap/core/current core.snap && rm core.snap && \ curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/snapcraft?channel=edge' | jq '.download_url' -r) --output snapcraft.snap && \ mkdir -p /snap/snapcraft && unsquashfs -d /snap/snapcraft/current snapcraft.snap && rm snapcraft.snap && \ apt remove --yes --purge curl jq squashfs-tools && \ apt-get autoclean --yes && \ apt-get clean --yes COPY contrib/ci/snapcraft-wrapper /snap/bin/snapcraft ENV PATH=/snap/bin:$PATH LABEL maintainer="Mario Limonciello " RUN apt-get update && apt-get install -y \ curl \ git \ jq \ openssh-client \ wget WORKDIR /root/project fwupd-1.2.14/contrib/ci/Dockerfile-ubuntu.in000066400000000000000000000003051402665037500207040ustar00rootroot00000000000000FROM ubuntu:devel %%%OS%%% ENV CC clang-6.0 RUN echo fubar > /etc/machine-id %%%ARCH_SPECIFIC_COMMAND%%% %%%INSTALL_DEPENDENCIES_COMMAND%%% WORKDIR /github/workspace CMD ["./contrib/ci/ubuntu.sh"] fwupd-1.2.14/contrib/ci/README.md000066400000000000000000000104641402665037500162530ustar00rootroot00000000000000Continuous Integration ====================== Continuous integration for fwupd is provided by [Travis CI](https://travis-ci.org/hughsie/fwupd). By using Travis CI, builds are exercised across a variety of environments attempting to maximize code coverage. For every commit or pull request 6 builds are performed: Fedora (x86_64) ------ * A fully packaged RPM build with all plugins enabled * Compiled under gcc with AddressSanitizer * Tests with -Werror enabled * Tests with the built in local test suite for all plugins. * All packages are installed * An installed testing run with the "test" plugin and pulling from LVFS. * With modem manager disabled Debian testing (x86_64) ------ * A fully packaged DEB build with all plugins enabled * Compiled under gcc * Tests with -Werror enabled * Tests with the built in local test suite for all plugins. * All packages are installed * An installed testing run with the "test" plugin and pulling from LVFS. * All packages are removed Debian testing (i386) ------ * A fully packaged DEB build with all plugins enabled * Compiled under gcc * Tests with -Werror enabled * Tests with the built in local test suite for all plugins. * All packages are installed * An installed testing run with the "test" plugin and pulling from LVFS. * All packages are removed Ubuntu devel release (x86_64) ------ * A fully packaged DEB build with all plugins enabled * Compiled under clang * Tests without -Werror enabled * Tests with the built in local test suite for all plugins. * All packages are installed * An installed testing run with the "test" plugin and pulling from LVFS. * All packages are removed Debian testing (cross compile s390x) ------ * Not packaged * Tests for missing translation files * No redfish support * Compiled under gcc * Tests with -Werror enabled * Runs local test suite using qemu-user * Modem manager disabled Arch Linux (x86_64) ---------- * A fully packaged pkg build with all plugins enabled * Compiled under gcc * Tests with -Werror enabled * Compile with the deprecated USB plugin enabled * Tests with the built in local test suite for all plugins. * All packages are installed Flatpak ---------- * A flatpak bundle with all plugins enabled * Compiled under gcc with the org.gnome.Sdk/x86_64/3.28 runtime * Builds without the daemon, so only fwupdtool is available * No GPG, PKCS-7, GObjectIntrospection, systemd or ConsoleKit support * No tests Adding a new target =================== Dockerfiles are generated dynamically by the python script ```generate_dockerfile.py```. The python script will recognize the environment variable ***OS*** to determine what target to generate a Dockerfile for. dependencies.xml ---------------- Initially the python script will read in ___dependencies.xml___ to generate a dependency list for that target. The XML is organized by a top level element representing the dependencies needed for building fwupd. The child elements represent individual dependencies for all distributions. * This element has an attribute ***id*** that represents the most common package name used by distributions * This element has an attribute ***type*** that represents if the package is needed at build time or runtime. Each dependency then has a mapping to individual distributions (___distro___). * This element has an attribute ***id*** that represents the distribution. Each distribution will have ***package*** elements and ***control*** elements. ***Package*** elements represent the name of the package needed for the distribution. * An optional attribute ***variant*** represents one deviation of that distribution. For example building a specific architecture or with a different compiler. * If the ***package*** element is empty the ***id*** of the ______ element will be used. ***Control*** elements represent specific requirements associated to a dependency. They will contain elements with individual details. * ___version___ elements represent a minimum version to be installed * ___inclusive___ elements represent an inclusive list of architectures to be installed on * ___exclusive___ elements represent an exclusive list of architectures to not be installed on Dockerfile.in ------------- The ***Dockerfile.in*** file will be used as a template to build the container. No hardcoded dependencies should be put in this file. They should be stored in ***dependencies.xml***. fwupd-1.2.14/contrib/ci/arch.sh000077500000000000000000000013741402665037500162500ustar00rootroot00000000000000#!/bin/bash set -e set -x shopt -s extglob VERSION=`git describe | sed 's/-/.r/;s/-/./'` [ -z $VERSION ] && VERSION=`head meson.build | grep ' version :' | cut -d \' -f2` #install anything missing from the container ./contrib/ci/generate_dependencies.py | xargs pacman -S --noconfirm --needed # prepare the build tree rm -rf build mkdir build && pushd build cp ../contrib/PKGBUILD . sed -i "s,#VERSION#,$VERSION," PKGBUILD mkdir -p src/fwupd && pushd src/fwupd cp -R ../../../!(build|dist) . popd chown nobody . -R # build the package and install it sudo -E -u nobody makepkg -e --noconfirm pacman -U --noconfirm *.pkg.tar.xz # move the package to working dir mv *.pkg.tar.xz ../dist # no testing here because gnome-desktop-testing isn’t available in Arch fwupd-1.2.14/contrib/ci/centos.sh000077500000000000000000000005071402665037500166230ustar00rootroot00000000000000#!/bin/sh set -e set -x rm -rf build mkdir -p build cd build meson .. \ --werror \ -Dplugin_uefi=false \ -Dplugin_dell=false \ -Dplugin_modem_manager=false \ -Dplugin_synaptics=true \ -Dplugin_flashrom=true \ -Dintrospection=true \ -Dgtkdoc=true \ -Dpkcs7=false \ -Dman=true ninja-build -v ninja-build test -v cd .. fwupd-1.2.14/contrib/ci/check_missing_translations.sh000077500000000000000000000001251402665037500227330ustar00rootroot00000000000000#!/bin/sh set -e cd po intltool-update -m if [ -f missing ]; then exit 1 fi fwupd-1.2.14/contrib/ci/debian.sh000077500000000000000000000032651402665037500165560ustar00rootroot00000000000000#!/bin/bash set -e set -x #although it's debian, we don't build packages if [ "$OS" = "debian-s390x" ]; then ./contrib/ci/debian_s390x.sh exit 0 fi #prepare export DEBFULLNAME="CI Builder" export DEBEMAIL="ci@travis-ci.org" VERSION=`git describe | sed 's/-/+r/;s/-/+/'` [ -z $VERSION ] && VERSION=`head meson.build | grep ' version :' | cut -d \' -f2` rm -rf build/ mkdir -p build shopt -s extglob cp -lR !(build|dist) build/ pushd build mv contrib/debian . sed s/quilt/native/ debian/source/format -i #generate control file ./contrib/ci/generate_debian.py #check if we have all deps available #if some are missing, we're going to use subproject instead and #packaging CI will fail ./contrib/ci/generate_dependencies.py | xargs apt install -y || true if ! dpkg-checkbuilddeps; then ./contrib/ci/ubuntu.sh exit 0 fi #disable unit tests if fwupd is already installed (may cause problems) if [ -x /usr/lib/fwupd/fwupd ]; then export DEB_BUILD_OPTIONS=nocheck fi #build the package EDITOR=/bin/true dch --create --package fwupd -v $VERSION "CI Build" debuild --no-lintian --preserve-envvar CI --preserve-envvar CC #if invoked outside of CI if [ ! -f /.dockerenv ]; then echo "Not running in a container, please manually install packages" exit 0 fi #test the packages install PACKAGES=$(ls ../*.deb | grep -v 'fwupd-tests\|dbgsym') dpkg -i $PACKAGES # run the installed tests if [ "$CI" = "true" ]; then dpkg -i ../fwupd-tests*.deb service dbus restart gnome-desktop-testing-runner fwupd apt purge -y fwupd-tests fi #test the packages remove apt purge -y fwupd \ fwupd-doc \ libfwupd2 \ libfwupd-dev #place built packages in dist outside docker mkdir -p ../dist cp $PACKAGES ../dist fwupd-1.2.14/contrib/ci/debian_s390x.sh000077500000000000000000000013311402665037500175140ustar00rootroot00000000000000#!/bin/sh set -e set -x export LC_ALL=C.UTF-8 #evaluate using Debian's build flags eval "$(dpkg-buildflags --export=sh)" #filter out -Bsymbolic-functions export LDFLAGS=$(dpkg-buildflags --get LDFLAGS | sed "s/-Wl,-Bsymbolic-functions\s//") rm -rf build mkdir -p build cp contrib/ci/s390x_cross.txt build/ cd build meson .. \ --cross-file s390x_cross.txt \ --werror \ -Dplugin_flashrom=false \ -Dplugin_uefi=false \ -Dplugin_dell=false \ -Dplugin_modem_manager=false \ -Dplugin_redfish=false \ -Dintrospection=false \ -Dgtkdoc=false \ -Dlibxmlb:introspection=false \ -Dlibxmlb:gtkdoc=false \ -Dman=false ninja -v ninja test -v cd .. #test for missing translation files ./contrib/ci/check_missing_translations.sh fwupd-1.2.14/contrib/ci/dependencies.xml000066400000000000000000001022601402665037500201400ustar00rootroot00000000000000 cairo-devel cairo-devel libcairo-dev:s390x cairo-gobject-devel cairo-gobject-devel libcairo-gobject2:s390x json-glib json-glib-devel json-glib-devel (>= 1.1.1) libjson-glib-dev:s390x (>= 1.1.1) libftdi libftdi-devel libftdi-devel pciutils pciutils-devel pciutils-devel noto-fonts google-noto-sans-cjk-ttc-fonts google-noto-sans-cjk-ttc-fonts (>= 11) (>= 11) elfutils-libelf-devel elfutils-libelf-devel libelf-dev:s390x freetype freetype libfreetype6-dev:s390x (>= 0.19.8.1) (>= 0.19.8.1) gnu-efi-libs amd64 arm64 armhf i386 gnu-efi gnu-efi amd64 arm64 armhf i386 gnu-efi gnu-efi glib2-devel glib2-devel (>= 2.45.8) libglib2.0-dev:s390x (>= 2.45.8) gobject-introspection-devel gobject-introspection-devel gpgme-devel gpgme-devel libgpgme-dev:s390x gnutls-devel gnutls-devel libgnutls28-dev:s390x libgnutls28-dev gnutls-utils gnutls-utils gtk-doc gtk-doc gtk-doc libxmlb libxmlb-devel (>= 0.1.5) libxmlb-dev:s390x libarchive-devel libarchive-devel libarchive-dev:s390x efivar efivar-devel efivar-devel amd64 arm64 armhf i386 amd64 arm64 armhf i386 amd64 arm64 armhf i386 amd64 arm64 armhf i386 gcab libgcab1-devel libgcab1-devel libgcab-dev:s390x libgudev1-devel libgudev1-devel libgudev-1.0-dev:s390x libgusb libgusb-devel libgusb-devel (>= 0.2.9) libgusb-dev:s390x (>= 0.2.9) libicu-dev:s390x libidn2-0-dev:s390x libsmbios libsmbios-devel libsmbios-devel i386 amd64 i386 amd64 libsoup libsoup-devel libsoup-devel libsoup2.4-dev:s390x pango-devel pango-devel polkit polkit polkit (>> 0.105-14) (>> 0.105-14) ModemManager-glib-devel ModemManager-glib-devel modemmanager libmm-glib-dev:s390x libqmi-devel libqmi-devel libqmi libqmi-glib-dev:s390x polkit-devel polkit-devel libpolkit-gobject-1-dev:s390x python34-devel python-cairo python3-cairo python-gobject python-pillow python3-pillow qemu-user sqlite-devel sqlite-devel libsqlite3-dev:s390x (>= 231) (>= 231) umockdev-devel umockdev-devel vala vala vala valgrind-devel valgrind-devel ia64 riscv64 x32 mips sparc64 sh4 ppc64 powerpcspe hppa alpha mips64el armhf armel mipsel m68k ia64 riscv64 x32 mips sparc64 sh4 ppc64 powerpcspe hppa alpha mips64el armhf armel mipsel m68k fwupd-1.2.14/contrib/ci/fedora.sh000077500000000000000000000035341402665037500165730ustar00rootroot00000000000000#!/bin/bash set -e set -x #get any missing deps from the container ./contrib/ci/generate_dependencies.py | xargs dnf install -y #generate a tarball git config tar.tar.xz.command "xz -c" mkdir -p build && pushd build rm -rf * meson .. \ -Dgtkdoc=true \ -Dman=true \ -Dtests=true \ -Dplugin_dummy=true \ -Dplugin_flashrom=true \ -Dplugin_modem_manager=false \ -Dplugin_thunderbolt=true \ -Dplugin_uefi=true \ -Dplugin_dell=true \ -Dplugin_synaptics=true $@ ninja-build dist popd VERSION=`meson introspect build --projectinfo | jq -r .version` mkdir -p $HOME/rpmbuild/SOURCES/ mv build/meson-dist/fwupd-$VERSION.tar.xz $HOME/rpmbuild/SOURCES/ #generate a spec file sed "s,#VERSION#,$VERSION,; s,#BUILD#,1,; s,#LONGDATE#,`date '+%a %b %d %Y'`,; s,#ALPHATAG#,alpha,; s,enable_dummy 0,enable_dummy 1,; s,Source0.*,Source0:\tfwupd-$VERSION.tar.xz," \ contrib/fwupd.spec.in > build/fwupd.spec if [ -n "$CI" ]; then sed -i "s,enable_ci 0,enable_ci 1,;" build/fwupd.spec fi #build RPM packages rpmbuild -ba build/fwupd.spec #if invoked outside of CI if [ ! -f /.dockerenv ]; then echo "Not running in a container, please manually install packages" exit 0 fi #install RPM packages dnf install -y $HOME/rpmbuild/RPMS/*/*.rpm mkdir -p dist cp $HOME/rpmbuild/RPMS/*/*.rpm dist if [ "$CI" = "true" ]; then sed "s,^BlacklistPlugins=test,BlacklistPlugins=," -i /etc/fwupd/daemon.conf # set up enough PolicyKit and D-Bus to run the daemon mkdir -p /run/dbus mkdir -p /var ln -s /var/run /run dbus-daemon --system --fork /usr/lib/polkit-1/polkitd & sleep 5 # run the daemon startup to check it can start /usr/libexec/fwupd/fwupd --immediate-exit --verbose # run the installed tests whilst the daemon debugging /usr/libexec/fwupd/fwupd --verbose & sleep 10 gnome-desktop-testing-runner fwupd fi fwupd-1.2.14/contrib/ci/flatpak.py000077500000000000000000000057061402665037500167760ustar00rootroot00000000000000#!/usr/bin/python3 import subprocess import os import json import shutil def prepare (target): #clone the flatpak json cmd = ['git', 'submodule', 'update', '--remote', 'contrib/flatpak'] subprocess.run (cmd, check=True) #clone the submodules for that cmd = ['git', 'submodule', 'update', '--init', '--remote', 'shared-modules/'] subprocess.run (cmd, cwd='contrib/flatpak', check=True) #parse json if os.path.isdir ('build'): shutil.rmtree ('build') data = {} with open ('contrib/flatpak/org.freedesktop.fwupd.json', 'r') as rfd: data = json.load (rfd, strict=False) platform = 'runtime/%s/x86_64/%s' % (data['runtime'], data['runtime-version']) sdk = 'runtime/%s/x86_64/%s' % (data['sdk'], data['runtime-version']) num_modules = len (data['modules']) #update to build from master data["branch"] = "master" for index in range(0, num_modules): module = data['modules'][index] if type (module) != dict or not 'name' in module: continue name = module['name'] if not 'fwupd' in name: continue data['modules'][index]['sources'][0].pop ('url') data['modules'][index]['sources'][0].pop ('sha256') data['modules'][index]['sources'][0]['type'] = 'dir' data['modules'][index]['sources'][0]['skip'] = [".git"] data['modules'][index]['sources'][0]['path'] = ".." #write json os.mkdir('build') with open (target, 'w') as wfd: json.dump(data, wfd, indent=4) os.symlink ('../contrib/flatpak/shared-modules','build/shared-modules') # install the runtimes (parsed from json!) repo = 'flathub' repo_url = 'https://dl.flathub.org/repo/flathub.flatpakrepo' print ("Installing dependencies") cmd = ['flatpak', 'remote-add', '--if-not-exists', repo, repo_url] subprocess.run (cmd, check=True) cmd = ['flatpak', 'install', '--assumeyes', repo, sdk] subprocess.run (cmd, check=True) cmd = ['flatpak', 'install', '--assumeyes', repo, platform] subprocess.run (cmd, check=True) def build (target): cmd = ['flatpak-builder', '--repo=repo', '--force-clean', '--disable-rofiles-fuse', 'build-dir', target] subprocess.run (cmd, check=True) cmd = ['flatpak', 'build-bundle', 'repo', 'fwupd.flatpak', 'org.freedesktop.fwupd'] subprocess.run (cmd, check=True) if __name__ == '__main__': t = os.path.join ('build', 'org.freedesktop.fwupd.json') prepare (t) build (t) # to run from the builddir: # sudo flatpak-builder --run build-dir org.freedesktop.fwupd.json /app/libexec/fwupd/fwupdtool get-devices # install the single file bundle # flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo # flatpak install fwupd.flatpak # to run a shell in the same environment that flatpak sees: # flatpak run --command=sh --devel org.freedesktop.fwupd # to run fwupdtool as root: # sudo flatpak run org.freedesktop.fwupd --verbose get-devices fwupd-1.2.14/contrib/ci/generate_debian.py000077500000000000000000000123421402665037500204420ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # import os import sys import xml.etree.ElementTree as etree def parse_control_dependencies(requested_type): TARGET=os.getenv('OS') deps = [] dep = '' if TARGET == '': print("Missing OS environment variable") sys.exit(1) OS = TARGET SUBOS = '' if TARGET: split = TARGET.split('-') if len(split) >= 2: OS = split[0] SUBOS = split[1] else: import lsb_release OS = lsb_release.get_distro_information()['ID'].lower() import platform SUBOS = platform.machine() tree = etree.parse(os.path.join(os.path.dirname (sys.argv[0]), "dependencies.xml")) root = tree.getroot() for child in root: if not "type" in child.attrib or not "id" in child.attrib: continue for distro in child: if not "id" in distro.attrib: continue if distro.attrib["id"] != OS: continue control = distro.find("control") if control is None: continue packages = distro.findall("package") for package in packages: if SUBOS: if not 'variant' in package.attrib: continue if package.attrib['variant'] != SUBOS: continue if package.text: dep = package.text else: dep = child.attrib["id"] if child.attrib["type"] == requested_type and dep: version = control.find('version') if version is not None: dep = "%s %s" % (dep, version.text) inclusions = control.findall('inclusive') if inclusions: for i in range(0, len(inclusions)): prefix = '' suffix = ' ' if i == 0: prefix = " [" if i == len(inclusions) - 1: suffix = "]" dep = "%s%s%s%s" % (dep, prefix, inclusions[i].text, suffix) exclusions = control.findall('exclusive') if exclusions: for i in range(0, len(exclusions)): prefix = '!' suffix = ' ' if i == 0: prefix = " [!" if i == len(exclusions) - 1: suffix = "]" dep = "%s%s%s%s" % (dep, prefix, exclusions[i].text, suffix) deps.append(dep) return deps def update_debian_control(target): control_in = os.path.join(target, 'control.in') control_out = os.path.join(target, 'control') if not os.path.exists(control_in): print("Missing file %s" % control_in) sys.exit(1) with open(control_in, 'r') as rfd: lines = rfd.readlines() deps = parse_control_dependencies("build") deps.sort() with open(control_out, 'w') as wfd: for line in lines: if line.startswith("Build-Depends: %%%DYNAMIC%%%"): wfd.write("Build-Depends:\n") for i in range(0, len(deps)): wfd.write("\t%s,\n" % deps[i]) else: wfd.write(line) def update_debian_copyright (directory): copyright_in = os.path.join(directory, 'copyright.in') copyright_out = os.path.join(directory, 'copyright') if not os.path.exists(copyright_in): print("Missing file %s" % copyright_in) sys.exit(1) # Assume all files are remaining LGPL-2.1+ copyrights = [] for root, dirs, files in os.walk('.'): for file in files: target = os.path.join (root, file) #skip translations and license file if target.startswith('./po/') or file == "COPYING": continue try: with open(target, 'r') as rfd: #read about the first few lines of the file only lines = rfd.readlines(220) except UnicodeDecodeError: continue for line in lines: if 'Copyright (C) ' in line: parts = line.split ('Copyright (C)')[1].strip() #split out the copyright header partition = parts.partition(' ')[2] # remove the year string copyrights += ["%s" % partition] copyrights = "\n\t ".join(sorted(set(copyrights))) with open(copyright_in, 'r') as rfd: lines = rfd.readlines() with open(copyright_out, 'w') as wfd: for line in lines: if line.startswith("%%%DYNAMIC%%%"): wfd.write("Files: *\n") wfd.write("Copyright: %s\n" % copyrights) wfd.write("License: LGPL-2.1+\n") wfd.write("\n") else: wfd.write(line) directory = os.path.join (os.getcwd(), 'debian') update_debian_control(directory) update_debian_copyright(directory) fwupd-1.2.14/contrib/ci/generate_dependencies.py000077500000000000000000000037651402665037500216570ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell, Inc. # Copyright (C) 2020 Intel, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # import os import sys import argparse import xml.etree.ElementTree as etree def parse_dependencies(OS, SUBOS, requested_type): deps = [] dep = '' directory = os.path.dirname(sys.argv[0]) tree = etree.parse(os.path.join(directory, "dependencies.xml")) root = tree.getroot() for child in root: if "type" not in child.attrib or "id" not in child.attrib: continue for distro in child: if "id" not in distro.attrib: continue if distro.attrib["id"] != OS: continue packages = distro.findall("package") for package in packages: if SUBOS: if 'variant' not in package.attrib: continue if package.attrib['variant'] != SUBOS: continue if package.text: dep = package.text else: dep = child.attrib["id"] if child.attrib["type"] == requested_type and dep: deps.append(dep) return deps if __name__ == '__main__': try: import distro target = distro.linux_distribution()[0] except ModuleNotFoundError: target = None parser = argparse.ArgumentParser() parser.add_argument( "-o", "--os", default=target, choices=["fedora", "centos", "flatpak", "debian", "ubuntu", "arch"], help="dependencies for OS", ) args = parser.parse_args() target = os.getenv('OS', args.os) if target is None: print("Missing OS environment variable") sys.exit(1) _os = target.lower() _sub_os = '' split = target.split('-') if len(split) >= 2: _os, _sub_os = split[:2] dependencies = parse_dependencies(_os, _sub_os, "build") print(*dependencies, sep='\n') fwupd-1.2.14/contrib/ci/generate_docker.py000077500000000000000000000070761402665037500204770ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017-2018 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # import os import subprocess import sys import xml.etree.ElementTree as etree import tempfile def parse_dependencies(OS, SUBOS, requested_type): deps = [] dep = '' tree = etree.parse(os.path.join(directory, "dependencies.xml")) root = tree.getroot() for child in root: if not "type" in child.attrib or not "id" in child.attrib: continue for distro in child: if not "id" in distro.attrib: continue if distro.attrib["id"] != OS: continue packages = distro.findall("package") for package in packages: if SUBOS: if not 'variant' in package.attrib: continue if package.attrib['variant'] != SUBOS: continue if package.text: dep = package.text else: dep = child.attrib["id"] if child.attrib["type"] == requested_type and dep: deps.append(dep) return deps directory = os.path.dirname(sys.argv[0]) TARGET=os.getenv('OS') if TARGET is None: print("Missing OS environment variable") sys.exit(1) OS = TARGET SUBOS = '' split = TARGET.split('-') if len(split) >= 2: OS = split[0] SUBOS = split[1] deps = parse_dependencies(OS, SUBOS, "build") input = os.path.join(directory, "Dockerfile-%s.in" % OS) if not os.path.exists(input): print("Missing input file %s for %s" % (input, OS)) sys.exit(1) with open(input, 'r') as rfd: lines = rfd.readlines() with open('Dockerfile', 'w') as wfd: for line in lines: if line.startswith("FROM %%%ARCH_PREFIX%%%"): if (OS == "debian" or OS == "ubuntu") and SUBOS == "i386": replace = SUBOS + "/" else: replace = '' wfd.write(line.replace("%%%ARCH_PREFIX%%%", replace)) elif line == "%%%INSTALL_DEPENDENCIES_COMMAND%%%\n": if OS == "fedora" or OS == 'flatpak': wfd.write("RUN dnf --enablerepo=updates-testing -y install \\\n") elif OS == "centos": wfd.write("RUN yum -y install \\\n") elif OS == "debian" or OS == "ubuntu": wfd.write("RUN apt update -qq && \\\n") wfd.write("\tapt install -yq --no-install-recommends\\\n") elif OS == "arch": wfd.write("RUN pacman -Syu --noconfirm \\\n") for i in range(0, len(deps)): if i < len(deps)-1: wfd.write("\t%s \\\n" % deps[i]) else: wfd.write("\t%s \n" % deps[i]) elif line == "%%%ARCH_SPECIFIC_COMMAND%%%\n": if OS == "debian" and SUBOS == "s390x": #add sources wfd.write('RUN cat /etc/apt/sources.list | sed "s/deb/deb-src/" >> /etc/apt/sources.list\n') #add new architecture wfd.write('RUN dpkg --add-architecture %s\n' % SUBOS) elif line == "%%%OS%%%\n": wfd.write("ENV OS %s\n" % TARGET) else: wfd.write(line) wfd.flush() args = ["docker", "build", "-t", "fwupd-%s" % TARGET] if 'http_proxy' in os.environ: args += ['--build-arg=http_proxy=%s' % os.environ['http_proxy']] if 'https_proxy' in os.environ: args += ['--build-arg=https_proxy=%s' % os.environ['https_proxy']] args += ["-f", "./Dockerfile", "."] subprocess.check_call(args) fwupd-1.2.14/contrib/ci/s390x_cross.txt000066400000000000000000000004221402665037500176250ustar00rootroot00000000000000[binaries] c = 's390x-linux-gnu-gcc' cpp = 's390x-linux-gnu-cpp' ar = 's390x-linux-gnu-ar' strip = 's390x-linux-gnu-strip' pkgconfig = 's390x-linux-gnu-pkg-config' exe_wrapper = 'qemu-s390x' [host_machine] system = 'linux' cpu_family = 's390x' cpu = 's390x' endian = 'big' fwupd-1.2.14/contrib/ci/snap.sh000077500000000000000000000000371402665037500162670ustar00rootroot00000000000000#!/bin/sh cd /build snapcraft fwupd-1.2.14/contrib/ci/snapcraft-wrapper000077500000000000000000000004671402665037500203630ustar00rootroot00000000000000#!/bin/sh SNAP="/snap/snapcraft/current" SNAP_NAME="$(awk '/^name:/{print $2}' $SNAP/meta/snap.yaml)" SNAP_VERSION="$(awk '/^version:/{print $2}' $SNAP/meta/snap.yaml)" SNAP_ARCH="amd64" export SNAP export SNAP_NAME export SNAP_VERSION export SNAP_ARCH exec "$SNAP/usr/bin/python3" "$SNAP/bin/snapcraft" "$@" fwupd-1.2.14/contrib/ci/ubuntu.sh000077500000000000000000000006061402665037500166520ustar00rootroot00000000000000#!/bin/sh set -e set -x #evaluate using Ubuntu's buildflags eval "$(dpkg-buildflags --export=sh)" #filter out -Bsymbolic-functions export LDFLAGS=$(dpkg-buildflags --get LDFLAGS | sed "s/-Wl,-Bsymbolic-functions\s//") rm -rf build meson build --werror #build with clang and -Werror ninja -C build test -v #run static analysis (these mostly won't be critical) ninja -C build scan-build -v fwupd-1.2.14/contrib/debian/000077500000000000000000000000001402665037500156165ustar00rootroot00000000000000fwupd-1.2.14/contrib/debian/README.Debian000066400000000000000000000013421402665037500176570ustar00rootroot00000000000000signed vs unsigned fwupd programs ------------------------------------ fwupd 1.1.0 is configured to understand when to use a signed version of the EFI binary. If the signed version isn't installed but secure boot is turned on, it will avoid copying to the EFI system partition. This allows supporting secure boot even if not turned on at install, or changed later after install. In Ubuntu, both fwupd-signed and fwupd are seeded in the default installation. Nothing is installed to the ESP until it's needed. In Debian, the package name for the signed version is slightly different due to different infrastructure. fwupd-signed-$ARCH and fwupd should both be installed and then things will work similarly to what's described above. fwupd-1.2.14/contrib/debian/README.source000066400000000000000000000004201402665037500177710ustar00rootroot00000000000000fwupd for Debian ---------------- To build from the git tree, run: git-buildpackage -us -uc -S Then, if using sbuild, you can use something like: sbuild -s -c sid-amd64 -d unstable -- Daniel Jared Dominguez Thu, 21 May 2015 13:44:16 -0500 fwupd-1.2.14/contrib/debian/clean000066400000000000000000000000331402665037500166170ustar00rootroot00000000000000gtk-doc.make m4/gtk-doc.m4 fwupd-1.2.14/contrib/debian/compat000066400000000000000000000000031402665037500170150ustar00rootroot0000000000000011 fwupd-1.2.14/contrib/debian/control.in000066400000000000000000000124671402665037500176400ustar00rootroot00000000000000Source: fwupd Priority: optional Maintainer: Debian EFI Uploaders: Steve McIntyre <93sam@debian.org>, Daniel Jared Dominguez , Matthias Klumpp , Mario Limonciello Build-Depends: %%%DYNAMIC%%% Standards-Version: 4.3.0 Section: admin Homepage: https://github.com/hughsie/fwupd Vcs-Git: https://salsa.debian.org/efi-team/fwupd.git Vcs-Browser: https://salsa.debian.org/efi-team/fwupd Package: libfwupd2 Section: libs Architecture: linux-any Depends: ${misc:Depends}, ${shlibs:Depends} Multi-Arch: same Description: Firmware update daemon library fwupd is a daemon to allow session software to update device firmware. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the system D-Bus interface directly. Firmware updates are supported for a variety of technologies. See for details . This package provides the library used by the daemon. Package: fwupd Architecture: linux-any Depends: ${misc:Depends}, ${shlibs:Depends}, shared-mime-info Recommends: python3, bolt, tpm2-tools, tpm2-abrmd, fwupd-signed Breaks: gir1.2-dfu-1.0 (<< 0.9.7-1), libdfu1 (<< 0.9.7-1), libdfu-dev (<< 0.9.7-1) Replaces: gir1.2-dfu-1.0 (<< 0.9.7-1), libdfu1 (<< 0.9.7-1), libdfu-dev (<< 0.9.7-1) Multi-Arch: foreign Description: Firmware update daemon fwupd is a daemon to allow session software to update device firmware. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the system D-Bus interface directly. Firmware updates are supported for a variety of technologies. See for details Package: fwupd-tests Architecture: linux-any Depends: ${misc:Depends}, ${shlibs:Depends}, ca-certificates, dbus-x11, fwupd, gnome-desktop-testing, policykit-1, python3, python3-gi, python3-requests, Breaks: fwupd (<< 0.9.4-1) Replaces: fwupd (<< 0.9.4-1) Multi-Arch: foreign Description: Test suite for firmware update daemon fwupd is a daemon to allow session software to update device firmware. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the system D-Bus interface directly. Firmware updates are supported for a variety of technologies. See for details . This package provides a set of installed tests that can be run to validate the daemon in a continuous integration system. Package: fwupd-doc Section: doc Architecture: all Multi-Arch: foreign Depends: ${misc:Depends}, Description: Firmware update daemon documentation (HTML format) fwupd is a daemon to allow session software to update device firmware. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the system D-Bus interface directly. Firmware updates are supported for a variety of technologies. See for details . This package provides development documentation for creating a package that uses fwupd. Package: libfwupd-dev Architecture: linux-any Multi-Arch: same Depends: libfwupd2 (= ${binary:Version}), gir1.2-fwupd-2.0 (= ${binary:Version}), ${misc:Depends} Breaks: fwupd-dev (<< 0.5.4-2~) Replaces: fwupd-dev (<< 0.5.4-2~) Section: libdevel Description: development files for libfwupd fwupd is a daemon to allow session software to update device firmware. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the system D-Bus interface directly. Firmware updates are supported for a variety of technologies. See for details . This package provides the development files for libfwupd Package: gir1.2-fwupd-2.0 Architecture: linux-any Multi-Arch: same Depends: ${misc:Depends}, ${gir:Depends} Section: introspection Description: GObject introspection data for libfwupd This package provides the introspection data for libfwupd. . It can be used by packages using the GIRepository format to generate dynamic bindings. Package: fwupd-amd64-signed-template Architecture: amd64 Depends: ${shlibs:Depends}, ${misc:Depends}, make | build-essential | dpkg-dev Description: Template for signed fwupd package This package is used to control code signing by the Debian signing service. Package: fwupd-i386-signed-template Architecture: i386 Depends: ${shlibs:Depends}, ${misc:Depends}, make | build-essential | dpkg-dev Description: Template for signed fwupd package This package is used to control code signing by the Debian signing service. Package: fwupd-armhf-signed-template Architecture: armhf Depends: ${shlibs:Depends}, ${misc:Depends}, make | build-essential | dpkg-dev Description: Template for signed fwupd package This package is used to control code signing by the Debian signing service. Package: fwupd-arm64-signed-template Architecture: arm64 Depends: ${shlibs:Depends}, ${misc:Depends}, make | build-essential | dpkg-dev Description: Template for signed fwupd package This package is used to control code signing by the Debian signing service. fwupd-1.2.14/contrib/debian/copyright.in000066400000000000000000000201151402665037500201550ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: fwupd Source: https://github.com/hughsie/fwupd %%%DYNAMIC%%% Files: *.metainfo.xml Copyright: Richard Hughes License: CC0-1.0 Files: debian/* Copyright: 2015 Daniel Jared Dominguez 2015-2018 Mario Limonciello License: LGPL-2.1+ License: LGPL-2.1+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU Lesser General Public License version 2.1 can be found in "/usr/share/common-licenses/LGPL-2.1". License: CC0-1.0 Creative Commons CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. . Statement of Purpose . The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. . 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. . 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. . 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. . 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. fwupd-1.2.14/contrib/debian/docs000066400000000000000000000000011402665037500164600ustar00rootroot00000000000000 fwupd-1.2.14/contrib/debian/fwupd-doc.install000066400000000000000000000000221402665037500210700ustar00rootroot00000000000000usr/share/gtk-doc fwupd-1.2.14/contrib/debian/fwupd-tests.install000066400000000000000000000005511402665037500214740ustar00rootroot00000000000000#These are in a generic looking directory because #that is where gnome-desktop-testing expects to #find them. for more information see: #https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=872458 usr/share/installed-tests/* usr/lib/*/fwupd-plugins-3/libfu_plugin_test.so debian/lintian/fwupd-tests usr/share/lintian/overrides etc/fwupd/remotes.d/fwupd-tests.conf fwupd-1.2.14/contrib/debian/fwupd-tests.postinst000066400000000000000000000011141402665037500217050ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# #only enable on installation not upgrade if [ "$1" = configure ] && [ -z "$2" ]; then if [ -f /etc/fwupd/daemon.conf ]; then if [ "$CI" = "true" ]; then sed "s,^BlacklistPlugins=test,BlacklistPlugins=," -i /etc/fwupd/daemon.conf else echo "To enable test suite, modify /etc/fwupd/daemon.conf" fi fi if [ -f /etc/fwupd/remotes.d/fwupd-tests.conf ]; then if [ "$CI" = "true" ]; then sed "s,^Enabled=false,Enabled=true," -i /etc/fwupd/remotes.d/fwupd-tests.conf else echo "To enable test suite, enable fwupd-tests remote" fi fi fi fwupd-1.2.14/contrib/debian/fwupd-tests.postrm000066400000000000000000000004611402665037500213520ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# if [ "$1" = remove -o "$1" = purge ]; then if [ -f /etc/fwupd/daemon.conf ]; then if [ "$CI" = "true" ]; then sed "s,^BlacklistPlugins=,BlacklistPlugins=test," -i /etc/fwupd/daemon.conf else echo "To disable test suite, modify /etc/fwupd/daemon.conf" fi fi fi fwupd-1.2.14/contrib/debian/fwupd.dirs000066400000000000000000000000301402665037500176170ustar00rootroot00000000000000var/cache/app-info/xmls fwupd-1.2.14/contrib/debian/fwupd.install000066400000000000000000000010271402665037500203330ustar00rootroot00000000000000usr/bin/dfu-tool usr/bin/fwupdmgr etc/* usr/share/bash-completion usr/share/fwupd/* usr/share/dbus-1/* usr/share/icons/* usr/share/polkit-1/* usr/share/locale usr/share/metainfo/* usr/lib/*/fwupd usr/lib/*/fwupdagent usr/lib/*/fwupdoffline usr/lib/*/fwupdtool usr/share/man/man1/* lib/systemd/system/* lib/systemd/system-shutdown/* var/lib/fwupd lib/udev/rules.d/* data/daemon.conf etc/fwupd debian/fwupd.pkla /var/lib/polkit-1/localauthority/10-vendor.d usr/lib/*/fwupd-plugins-*/*.so debian/lintian/fwupd usr/share/lintian/overrides fwupd-1.2.14/contrib/debian/fwupd.pkla000066400000000000000000000002071402665037500176130ustar00rootroot00000000000000[Call internal fwupd actions] Identity=unix-group:admin;unix-group:sudo Action=org.freedesktop.fwupd.update-internal ResultActive=yes fwupd-1.2.14/contrib/debian/fwupd.postinst000066400000000000000000000016571402665037500205610ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/fwupd.conf 1.0.0~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/fwupd/remotes.d/fwupd.conf 1.2.7~ -- "$@" fi # Clean up from fwupdate->fwupd transition # This can be removed after bullseye and focal are released EFIDIR=$(dpkg-vendor --query vendor | awk '{ print tolower($$0) }') if [ "${DPKG_MAINTSCRIPT_ARCH}" = "amd64" ]; then EFI_NAME=x64 elif [ "${DPKG_MAINTSCRIPT_ARCH}" = "i386" ]; then EFI_NAME=ia32 elif [ "${DPKG_MAINTSCRIPT_ARCH}" = "arm64" ]; then EFI_NAME=aa64 elif [ "${DPKG_MAINTSCRIPT_ARCH}" = "armhf" ]; then EFI_NAME=arm fi rm -f /boot/efi/EFI/$EFIDIR/fwup$EFI_NAME.efi rm -f /var/lib/fwupdate/done rm -f /var/cache/fwupdate/done for dir in /var/cache/fwupdate /var/lib/fwupdate; do if [ -d $dir ]; then rmdir --ignore-fail-on-non-empty $dir || true fi done fwupd-1.2.14/contrib/debian/fwupd.postrm000066400000000000000000000004211402665037500202060ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# if [ "$1" = purge ]; then rm -rf /var/lib/fwupd/gnupg rm -f /var/cache/app-info/xmls/fwupd.xml fi if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/fwupd.conf 1.0.0~ -- "$@" fi fwupd-1.2.14/contrib/debian/fwupd.preinst000066400000000000000000000002541402665037500203520ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/fwupd.conf 1.0.0~ -- "$@" fi fwupd-1.2.14/contrib/debian/gbp.conf000066400000000000000000000001611402665037500172330ustar00rootroot00000000000000[DEFAULT] debian-branch = debian upstream-tag = %(version)s [buildpackage] sign-tags = True dist = experimental fwupd-1.2.14/contrib/debian/gen_signing_changelog000077500000000000000000000023131402665037500220410ustar00rootroot00000000000000#!/bin/sh # # Generate a changelog file for the signed fwupdate package, based on # a changelog.in file and other state DIR=$1 SOURCE=$2 ARCH=$3 IN="${DIR}/changelog.in" OUT="${DIR}/changelog" # Parse out fields from our changelg entry - want the signing-template # one to match all the important details where we can DISTRIBUTION="$(dpkg-parsechangelog | sed -ne 's/^Distribution: \(.*\)/\1/p')" URGENCY="$(dpkg-parsechangelog | sed -ne 's/^Urgency: \(.*\)/\1/p')" MAINT="$(dpkg-parsechangelog | sed -ne 's/^Maintainer: \(.*\)/\1/p')" DATE="$(dpkg-parsechangelog | sed -ne 's/^Date: \(.*\)/\1/p')" # If the version ends in "+bXXX", this is a binNMU. We don't want a new # source package to look like that, so change it to ".bXXX" instead VERSION="$(dpkg-parsechangelog | sed -ne 's/^Version: \(.*\)/\1/p')" MANGLED_VERSION="$(echo $VERSION | sed -r 's/-/\+/;s/\+(b[[:digit:]]+)$/.\1/')" printf "%s-%s-signed (%s) %s; urgency=%s\n" "${SOURCE}" "${ARCH}" "${MANGLED_VERSION}" "${DISTRIBUTION}" "${URGENCY}" > $OUT printf "\n" >> $OUT printf " * Update to %s version %s\n" "${SOURCE}" "${VERSION}" >> $OUT printf "\n" >> $OUT printf " -- %s %s\n" "${MAINT}" "${DATE}" >> $OUT printf "\n" >> $OUT cat $IN >> $OUT rm -f $IN fwupd-1.2.14/contrib/debian/gen_signing_json000077500000000000000000000013371402665037500210700ustar00rootroot00000000000000#!/bin/sh # # Generate a json file to go in the the fwupd-signed template # package. Describes exactly what needs to be signed, and how. DIR=$1 SOURCE=$2 ARCH=$3 OUT="$DIR/files.json" # What file are we looking to sign? BINARY=$(find debian/tmp -name '*.efi' | xargs basename) # Actually needs full path within the binary deb BINARY="usr/lib/${SOURCE}/efi/${BINARY}" rm -f $OUT printf '{\n' >> $OUT printf ' "packages": {\n' >> $OUT printf ' "%s": {\n' "${SOURCE}" >> $OUT printf ' "trusted_certs": [],\n' >> $OUT printf ' "files": [ \n' >> $OUT printf ' {"sig_type": "efi", "file": "%s"}\n' "${BINARY}" >> $OUT printf ' ]\n' >> $OUT printf ' }\n' >> $OUT printf ' }\n' >> $OUT printf '}\n' >> $OUT fwupd-1.2.14/contrib/debian/gir1.2-fwupd-2.0.install000066400000000000000000000000451402665037500216270ustar00rootroot00000000000000usr/lib/*/girepository-1.0/*.typelib fwupd-1.2.14/contrib/debian/libfwupd-dev.install000066400000000000000000000001421402665037500215730ustar00rootroot00000000000000usr/include/* usr/lib/*/*.so usr/lib/*/pkgconfig/*.pc usr/share/gir-1.0/*.gir usr/share/vala/vapi fwupd-1.2.14/contrib/debian/libfwupd2.install000066400000000000000000000000211402665037500210750ustar00rootroot00000000000000usr/lib/*/*.so.* fwupd-1.2.14/contrib/debian/lintian/000077500000000000000000000000001402665037500172545ustar00rootroot00000000000000fwupd-1.2.14/contrib/debian/lintian/fwupd000066400000000000000000000013571402665037500203320ustar00rootroot00000000000000#these are intended to be d-bus activated fwupd binary: systemd-service-file-missing-install-key lib/systemd/system/fwupd-offline-update.service fwupd binary: systemd-service-file-missing-install-key lib/systemd/system/fwupd.service fwupd binary: systemd-service-file-missing-install-key lib/systemd/system/system-update.target.wants/fwupd-offline-update.service #see debian bug 896012 fwupd: library-not-linked-against-libc usr/lib/*/fwupd-plugins-3/libfu_plugin_upower.so #EFI applications are PE executables fwupd: executable-not-elf-or-script usr/lib/fwupd/efi/*.efi fwupd: portable-executable-missing-security-features usr/lib/fwupd/efi/*.efi SafeSEH fwupd: library-not-linked-against-libc usr/lib/*/fwupd-plugins-3/libfu_plugin_modem_manager.so fwupd-1.2.14/contrib/debian/lintian/fwupd-tests000066400000000000000000000001631402665037500214640ustar00rootroot00000000000000#see debian bug 896012 fwupd-tests: library-not-linked-against-libc usr/lib/*/fwupd-plugins-3/libfu_plugin_test.so fwupd-1.2.14/contrib/debian/rules000077500000000000000000000107171402665037500167040ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- export LC_ALL := C.UTF-8 export DEB_BUILD_MAINT_OPTIONS = hardening=+all export DEB_LDFLAGS_MAINT_STRIP=-Wl,-Bsymbolic-functions #GPGME needs this for proper building on 32 bit archs ifeq "$(DEB_HOST_ARCH_BITS)" "32" export DEB_CFLAGS_MAINT_APPEND = -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE endif CONFARGS = ifneq ($(CI),) CONFARGS += --wrap-mode=default endif SB_STYLE := debian deb_version := $(shell dpkg-parsechangelog --show-field Version) ifeq (yes,$(shell dpkg-vendor --derives-from Ubuntu && echo yes)) SB_STYLE := ubuntu tar_name := fwupd_$(deb_version)_$(DEB_HOST_ARCH).tar.gz CONFARGS += -Dplugin_flashrom=false CONFARGS += -Defi_sbat_distro_id=ubuntu CONFARGS += -Defi_sbat_distro_summary=Ubuntu CONFARGS += -Defi_sbat_distro_pkgname=fwupd CONFARGS += -Defi_sbat_distro_version=$(deb_version) CONFARGS += -Defi_sbat_distro_url="https://launchpad.net/ubuntu/+source/fwupd" else TMPLDIR := debian/fwupd-$(DEB_HOST_ARCH)-signed-template/usr/share/code-signing/fwupd-$(DEB_HOST_ARCH)-signed-template ifneq ($(DEB_HOST_ARCH_CPU),ia64) CONFARGS += -Dplugin_flashrom=true else CONFARGS += -Dplugin_flashrom=false endif CONFARGS += -Defi_sbat_distro_id=debian CONFARGS += -Defi_sbat_distro_summary=Debian CONFARGS += -Defi_sbat_distro_pkgname=fwupd CONFARGS += -Defi_sbat_distro_version=$(deb_version) CONFARGS += -Defi_sbat_distro_url="https://tracker.debian.org/pkg/fwupd" endif ifeq (yes,$(shell pkg-config --exists libsmbios_c && echo yes)) CONFARGS += -Dplugin_dell=true else CONFARGS += -Dplugin_dell=false endif ifeq (yes,$(shell pkg-config --exists efivar && echo yes)) CONFARGS += -Dplugin_uefi=true -Dplugin_redfish=true -Dplugin_nvme=true else CONFARGS += -Dplugin_uefi=false -Dplugin_redfish=false -Dplugin_nvme=false endif CONFARGS += -Dplugin_dummy=true -Dgtkdoc=true --libexecdir=/usr/lib %: dh $@ --with gir override_dh_auto_clean: rm -fr debian/build ifeq (ubuntu,$(SB_STYLE)) rm -rf debian/fwupd-images endif override_dh_auto_configure: dh_auto_configure -- $(CONFARGS) override_dh_install: find debian/tmp/usr -type f -name "*a" -print | xargs rm -f sed -i 's,wheel,sudo,' ./debian/tmp/usr/share/polkit-1/rules.d/org.freedesktop.fwupd.rules dh_install #install the EFI binaries if needed if [ -d debian/tmp/usr/lib/fwupd/efi/ ]; then \ dh_install -pfwupd usr/lib/fwupd/efi ;\ dh_install -pfwupd usr/lib/fwupd/fwupdate; \ fi #if build with meson subproject in CI need to install this too if [ ! -z "$$CI" ] && [ -f debian/tmp/usr/lib/xb-tool ]; then \ dh_install -pfwupd usr/lib/xb-tool ;\ fi if [ ! -z "$$CI" ] && [ -f debian/tmp/usr/sbin/flashrom ]; then \ dh_install -pfwupd usr/sbin/flashrom ;\ fi dh_missing -a --fail-missing #this is placed in fwupd-tests rm -f debian/fwupd/usr/lib/*/fwupd-plugins-3/libfu_plugin_test.so rm -f debian/fwupd/etc/fwupd/remotes.d/fwupd-tests.conf ifeq (debian,$(SB_STYLE)) # Generate the template source for the Debian signing service to use mkdir -p $(TMPLDIR)/source-template/debian cp -a debian/signing-template/* $(TMPLDIR)/source-template/debian cp debian/README.Debian $(TMPLDIR)/source-template/debian find $(TMPLDIR)/source-template/debian -type f | xargs sed -i "s,SIGNARCH,$(DEB_HOST_ARCH)," find $(TMPLDIR)/source-template/debian -type f | xargs sed -i "s,SIGNVERSION,$(deb_version)," for file in $$(find $(TMPLDIR)/source-template/debian -type f -name *SIGNARCH*); do file1=$$(echo $$file | sed "s,SIGNARCH,$(DEB_HOST_ARCH),"); mv -v $$file $$file1; done install -m 0755 debian/fwupd.postinst $(TMPLDIR)/source-template/debian/fwupd-$(DEB_HOST_ARCH)-signed.postinst install -m 0755 debian/fwupd.postrm $(TMPLDIR)/source-template/debian/fwupd-$(DEB_HOST_ARCH)-signed.postrm ./debian/gen_signing_changelog $(TMPLDIR)/source-template/debian fwupd $(DEB_HOST_ARCH) ./debian/gen_signing_json $(TMPLDIR) fwupd ${DEB_HOST_ARCH} endif override_dh_strip_nondeterminism: dh_strip_nondeterminism -Xfirmware-example.xml.gz override_dh_auto_test: if [ -x /usr/bin/valgrind ] ; then \ dh_auto_test; \ fi override_dh_builddeb: dh_builddeb ifeq (ubuntu,$(SB_STYLE)) if [ -d debian/tmp/usr/lib/fwupd/efi/ ]; then \ mkdir -p debian/fwupd-images/$(deb_version) ;\ cp debian/tmp/usr/lib/fwupd/efi/fwupd*.efi debian/fwupd-images/$(deb_version) ;\ echo $(deb_version) > debian/fwupd-images/$(deb_version)/version ;\ tar -C debian/fwupd-images -czvf ../$(tar_name) . ;\ dpkg-distaddfile $(tar_name) raw-uefi - ;\ fi endif override_dh_shlibdeps: dh_shlibdeps $$DHSLIBS fwupd-1.2.14/contrib/debian/signing-template/000077500000000000000000000000001402665037500210655ustar00rootroot00000000000000fwupd-1.2.14/contrib/debian/signing-template/README.source000066400000000000000000000003311402665037500232410ustar00rootroot00000000000000This source package is generated by the Debian signing service from a template built by the fwupd package. It should never be updated directly. -- Steve McIntyre <93sam@debian.org> Sat, 07 Apr 2018 12:44:55 +0100 fwupd-1.2.14/contrib/debian/signing-template/changelog.in000066400000000000000000000002501402665037500233410ustar00rootroot00000000000000fwupd-SIGNARCH-signed (1) unstable; urgency=medium * Add template source package for signing -- Steve McIntyre <93sam@debian.org> Sat, 07 Apr 2018 12:44:55 +0100 fwupd-1.2.14/contrib/debian/signing-template/compat000066400000000000000000000000021402665037500222630ustar00rootroot000000000000009 fwupd-1.2.14/contrib/debian/signing-template/control000066400000000000000000000022461402665037500224740ustar00rootroot00000000000000Source: fwupd-SIGNARCH-signed Priority: optional Maintainer: Debian EFI Uploaders: Daniel Jared Dominguez , Steve McIntyre <93sam@debian.org>, Mario Limonciello Build-Depends: debhelper (>= 9.0.0), sbsigntool [amd64 arm64 armhf i386], fwupd (= SIGNVERSION) [SIGNARCH] Standards-Version: 4.1.3 Section: libs Homepage: https://github.com/hughsie/fwupd Vcs-Git: https://salsa.debian.org/efi-team/fwupd.git Vcs-Browser: https://salsa.debian.org/efi-team/fwupd Package: fwupd-SIGNARCH-signed Section: admin Architecture: SIGNARCH Provides: fwupd-signed Depends: ${shlibs:Depends}, ${misc:Depends}, fwupd (= SIGNVERSION) Built-Using: fwupd (= SIGNVERSION) Description: Tools to manage UEFI firmware updates (signed) fwupd provides functionality to update system firmware. It has been initially designed to update firmware using UEFI capsule updates, but it is designed to be extensible to other firmware update standards. . This package contains just the signed version of the fwupd binary, needed if your system has UEFI Secure Boot enabled. It depends on the normal fwupd package for everything else. fwupd-1.2.14/contrib/debian/signing-template/copyright000066400000000000000000000024421402665037500230220ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: fwupd Source: https://github.com/hughsie/fwupd Files: * Copyright: 2015 Richard Hughes License: LGPL-2.1+ Files: data/tests/colorhug/firmware.metainfo.xml Copyright: 2015 Richard Hughes License: CC0-1.0 Files: debian/* Copyright: 2015 Daniel Jared Dominguez 2015 Mario Limonciello License: LGPL-2.1+ License: LGPL-2.1+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU Lesser General Public License version 2.1 can be found in "/usr/share/common-licenses/LGPL-2.1". fwupd-1.2.14/contrib/debian/signing-template/fwupd-SIGNARCH-signed.install000066400000000000000000000000401402665037500262370ustar00rootroot00000000000000*.efi.signed /usr/lib/fwupd/efi fwupd-1.2.14/contrib/debian/signing-template/rules000077500000000000000000000006351402665037500221510ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- PACKAGE_NAME := fwupd SIG_PKG_NAME := fwupd-SIGNARCH-signed SIGNATURE_DIR := debian/signatures/$(PACKAGE_NAME) BINARY := $(shell find /usr/lib/fwupd/efi -name '*.efi' | xargs basename) %: dh $@ override_dh_auto_build: cp /usr/lib/fwupd/efi/$(BINARY) . sbattach --attach $(SIGNATURE_DIR)/usr/lib/fwupd/efi/$(BINARY).sig $(BINARY) mv $(BINARY) $(BINARY).signed fwupd-1.2.14/contrib/debian/signing-template/source/000077500000000000000000000000001402665037500223655ustar00rootroot00000000000000fwupd-1.2.14/contrib/debian/signing-template/source/format000066400000000000000000000000151402665037500235740ustar00rootroot000000000000003.0 (native) fwupd-1.2.14/contrib/debian/source/000077500000000000000000000000001402665037500171165ustar00rootroot00000000000000fwupd-1.2.14/contrib/debian/source/format000066400000000000000000000000141402665037500203240ustar00rootroot000000000000003.0 (quilt) fwupd-1.2.14/contrib/debian/source/include-binaries000066400000000000000000000000421402665037500222520ustar00rootroot00000000000000debian/binary-patches/example.elf fwupd-1.2.14/contrib/debian/source/lintian-overrides000066400000000000000000000002311402665037500224730ustar00rootroot00000000000000#github doesn't have these fwupd source: debian-watch-does-not-check-gpg-signature #to make CI happy until libxmlb lands fwupd source: source-is-missing fwupd-1.2.14/contrib/debian/tests/000077500000000000000000000000001402665037500167605ustar00rootroot00000000000000fwupd-1.2.14/contrib/debian/tests/ci000066400000000000000000000002001402665037500172660ustar00rootroot00000000000000#!/bin/sh set -e sed "s,^BlacklistPlugins=test,BlacklistPlugins=," -i /etc/fwupd/daemon.conf gnome-desktop-testing-runner fwupd fwupd-1.2.14/contrib/debian/tests/control000066400000000000000000000000431402665037500203600ustar00rootroot00000000000000Tests: ci Restrictions: needs-root fwupd-1.2.14/contrib/debian/watch000066400000000000000000000003551402665037500166520ustar00rootroot00000000000000# You can run the "uscan" command to check for upstream updates and more. # See uscan(1) for format version=3 opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/fwupd-$1\.tar\.gz/ \ https://github.com/hughsie/fwupd/tags .*/v?(\d\S*)\.tar\.gz fwupd-1.2.14/contrib/firmware-packager/000077500000000000000000000000001402665037500177635ustar00rootroot00000000000000fwupd-1.2.14/contrib/firmware-packager/README.md000066400000000000000000000113661402665037500212510ustar00rootroot00000000000000# Firmware Packager This script is intended to make firmware updating easier until OEMs upload their firmware packages to the LVFS. It works by extracting the firmware binary contained in a Microsoft .exe file (intended for performing the firmware update from a Windows system) and repackaging it in a cab file usable by fwupd. The cab file can then be install using `fwupdmgr install` ## Prerequisites To run this script you will need 1. Python3.5, a standard install should include all packages you need 2. 7z (for extracting .exe files) 3. gcab (for creating the cab file) ## Usage To create a firmware package, you must supply, at a minimum: 1. A string ID to name the firmware (`--firmware-id`). You are free to choose this, but [fwupd.org](http://fwupd.org/vendors.html) recommends using "a reverse-DNS prefix similar to java" and to "always use a .firmware suffix" (e.g. net.queuecumber.DellTBT.firmware) 2. A short name for the firmware package, again you are free to choose this (`--firmware-name`). 3. The unique ID of the device that the firmware is intended for (`--device-unique-id`). This *must* match the unique ID from `fwupdmgr get-devices` 4. The firmware version (`--release-version`), try to match the manufacturers versioning scheme 5. The path to the executable file to repackage (`--exe`) 6. The path *relative to the root of the exe archive* of the .bin file to package (`--bin`). Use 7z or archive-manager to inspect the .exe file and find this path. For example, if I want to package `dell-thunderbolt-firmware.exe` and I open the .exe with archive-manager and find that `Intel/tbt.bin` is the path to the bin file inside the archive, I would pass `--exe dell-thunderbolt-firmware.exe --bin Intel/tbt.bin` 7. The path to the cab file to output (`--out`). ## Documentation `--firmware-name` Short name of the firmware package can be customized (e.g. DellTBT) **REQUIRED** `--firmware-summary` One line description of the firmware package (e.g. Dell thunderbolt firmware) `--firmware-description` Longer description of the firmware package. Theoretically this can include HTML but I haven't tried it `--device-guid` GUID ID of the device this firmware will run on, this *must* match the output from `fwupdmgr get-devices` (e.g. 72533768-6a6c-5c06-994a-367374336810) **REQUIRED** `--firmware-homepage` Website for the firmware provider (e.g. http://www.dell.com) `-contact-info` Email address of the firmware developer (e.g. someone@something.net) `--developer-name` Name of the firmware developer (e.g. Dell) **REQUIRED** `--release-version` Version number of the firmware package (e.g. 4.21.01.002) **REQUIRED** `--release-description` Description of the firmware release, again this can theoretically include HTML but I didn't try it. `--exe` Executable file to extract firmware from (e.g. `dell-thunderbolt-firmware.exe`) **REQUIRED** `--bin` Path to the .bin file inside the executable to use as the firmware image', relative to the root of the archive (e.g. `Intel/tbt.bin`) **REQUIRED** `--out` Output cab file path (e.g. `updates/firmware.cab`) **REQUIRED** ## Example Let's say we downloaded `Intel_TBT3_FW_UPDATE_NVM21_318RY_A01_4.21.01.002.exe` (available [here](https://downloads.dell.com/FOLDER04421073M/1/Intel_TBT3_FW_UPDATE_NVM21_318RY_A01_4.21.01.002.exe)) containing updated firmware for Dell laptops thunderbolt controllers. Since Dell hasn't made this available on the LVFS yet, we want to package and install it ourselves. Opening the .exe with archive manager, we see it has a single folder: `Intel` and inside that, a set of firmware binaries (along with some microsoft junk). We pick the file `0x07BE_secure.bin` since we have a Dell XPS 9560 and that is its device string. Next we use `fwupdmgr` to get the device ID for the thunderbolt controller: ``` $ fwupdmgr get-devices Thunderbolt Controller Guid: 72533768-6a6c-5c06-994a-367374336810 DeviceID: 08001575 Plugin: thunderbolt Flags: internal|allow-online DeviceVendor: Intel Version: 21.00 Created: 2017-08-16 ``` The GUID field contains what we are looking for We can then run the firmware-packager with the following arguments: ``` $ firmware-packager --firmware-id net.queuecumber.DellTBT.firmware --firmware-name DellTBT --device-unique-id 72533768-6a6c-5c06-994a-367374336810 --release-version 4.21.01.002 --exe ~/Downloads/Intel_TBT3_FW_UPDATE_NVM21_318RY_A01_4.21.01.002.exe --bin Intel/0x07BE_secure.bin --out firmware.cab Using temp directory /tmp/tmpoey6_zx_ Extracting firmware exe Locating firmware bin Creating metainfo Cabbing firmware files Done ``` And we should have a firmware.cab that contains the packaged firmware. We can then install this firmware with ``` $ fwupdmgr install firmware.cab ``` fwupd-1.2.14/contrib/firmware-packager/firmware-packager000077500000000000000000000104371402665037500233050ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Max Ehrlich max.ehr@gmail.com # # SPDX-License-Identifier: LGPL-2.1+ # import argparse import subprocess import contextlib import os import shutil import tempfile import time @contextlib.contextmanager def cd(path): prev_cwd = os.getcwd() os.chdir(path) yield os.chdir(prev_cwd) firmware_metainfo_template = """ org.{developer_name}.guid{firmware_id} {firmware_name} {firmware_summary} {firmware_description} {device_guid} {firmware_homepage} CC0-1.0 proprietary {contact_info} {developer_name} {release_description} """ def make_firmware_metainfo(firmware_info, dst): local_info = vars(firmware_info) local_info["firmware_id"] = local_info["device_guid"][0:8] firmware_metainfo = firmware_metainfo_template.format(**local_info, timestamp=time.time()) with open(os.path.join(dst, 'firmware.metainfo.xml'), 'w') as f: f.write(firmware_metainfo) def extract_exe(exe, dst): command = ['7z', 'x', '-o{}'.format(dst), exe] subprocess.check_call(command, stdout=subprocess.DEVNULL) def get_firmware_bin(root, bin_path, dst): with cd(root): shutil.copy(bin_path, os.path.join(dst, 'firmware.bin')) def create_firmware_cab(exe, folder): with cd(folder): if os.name == "nt": directive = os.path.join (folder, "directive") with open (directive, 'w') as wfd: wfd.write('.OPTION EXPLICIT\r\n') wfd.write('.Set CabinetNameTemplate=firmware.cab\r\n') wfd.write('.Set DiskDirectory1=.\r\n') wfd.write('firmware.bin\r\n') wfd.write('firmware.metainfo.xml\r\n') command = ['makecab.exe', '/f', directive] else: command = ['gcab', '--create', 'firmware.cab', 'firmware.bin', 'firmware.metainfo.xml'] subprocess.check_call(command) def main(args): with tempfile.TemporaryDirectory() as dir: print('Using temp directory {}'.format(dir)) if args.exe: print('Extracting firmware exe') extract_exe(args.exe, dir) print('Locating firmware bin') get_firmware_bin(dir, args.bin, dir) print('Creating metainfo') make_firmware_metainfo(args, dir) print('Cabbing firmware files') create_firmware_cab(args, dir) print('Done') shutil.copy(os.path.join(dir, 'firmware.cab'), args.out) parser = argparse.ArgumentParser(description='Create fwupd packaged from windows executables') parser.add_argument('--firmware-name', help='Name of the firmware package can be customized (e.g. DellTBT)', required=True) parser.add_argument('--firmware-summary', help='One line description of the firmware package') parser.add_argument('--firmware-description', help='Longer description of the firmware package') parser.add_argument('--device-guid', help='GUID of the device this firmware will run on, this *must* match the output of one of the GUIDs in `fwupdmgr get-devices`', required=True) parser.add_argument('--firmware-homepage', help='Website for the firmware provider') parser.add_argument('--contact-info', help='Email address of the firmware developer') parser.add_argument('--developer-name', help='Name of the firmware developer', required=True) parser.add_argument('--release-version', help='Version number of the firmware package', required=True) parser.add_argument('--release-description', help='Description of the firmware release') parser.add_argument('--exe', help='(optional) Executable file to extract firmware from') parser.add_argument('--bin', help='Path to the .bin file (Relative if inside the executable; Absolute if outside) to use as the firmware image', required=True) parser.add_argument('--out', help='Output cab file path', required=True) args = parser.parse_args() main(args) fwupd-1.2.14/contrib/firmware-packager/meson.build000066400000000000000000000001711402665037500221240ustar00rootroot00000000000000if get_option('firmware-packager') install_data('firmware-packager', install_dir : 'share/fwupd') endif fwupd-1.2.14/contrib/fix_translations.py000077500000000000000000000023351402665037500203430ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ import sys import os import subprocess def _do_msgattrib(fn): argv = ['msgattrib', '--no-location', '--translated', '--no-wrap', '--sort-output', fn, '--output-file=' + fn] ret = subprocess.run(argv) if ret.returncode != 0: return def _do_nukeheader(fn): clean_lines = [] with open(fn) as f: lines = f.readlines() for line in lines: if line.startswith('"POT-Creation-Date:'): continue if line.startswith('"PO-Revision-Date:'): continue if line.startswith('"Last-Translator:'): continue clean_lines.append(line) with open(fn, 'w') as f: f.writelines(clean_lines) def _process_file(fn): _do_msgattrib(fn) _do_nukeheader(fn) if __name__ == '__main__': if len(sys.argv) == 1: print('path required') sys.exit(1) try: dirname = sys.argv[1] for fn in os.listdir(dirname): if fn.endswith('.po'): _process_file(os.path.join(dirname, fn)) except NotADirectoryError as _: print('path required') sys.exit(2) fwupd-1.2.14/contrib/flatpak/000077500000000000000000000000001402665037500160165ustar00rootroot00000000000000fwupd-1.2.14/contrib/fwupd.spec.in000066400000000000000000000234041402665037500170050ustar00rootroot00000000000000%global glib2_version 2.45.8 %global libxmlb_version 0.1.3 %global libgusb_version 0.2.11 %global libsoup_version 2.51.92 %global systemd_version 231 %global json_glib_version 1.1.1 %define alphatag #ALPHATAG# %global enable_ci 0 %global enable_tests 1 %global enable_dummy 1 %global __meson_wrap_mode default # fwupd.efi is only available on these arches %ifarch x86_64 aarch64 %global have_uefi 1 %endif # redfish is only available on this arch %ifarch x86_64 %global have_redfish 1 %endif # libsmbios is only available on x86 %ifarch x86_64 %global have_dell 1 %endif # only available recently %if 0%{?fedora} >= 30 %global have_modem_manager 1 %endif Summary: Firmware update daemon Name: fwupd Version: #VERSION# Release: 0.#BUILD#%{?alphatag}%{?dist} License: LGPLv2+ URL: https://github.com/hughsie/fwupd Source0: http://people.freedesktop.org/~hughsient/releases/%{name}-%{version}.tar.xz BuildRequires: gettext BuildRequires: glib2-devel >= %{glib2_version} BuildRequires: libxmlb-devel >= %{libxmlb_version} BuildRequires: libgcab1-devel BuildRequires: libgudev1-devel BuildRequires: libgusb-devel >= %{libgusb_version} BuildRequires: libsoup-devel >= %{libsoup_version} BuildRequires: polkit-devel >= 0.103 BuildRequires: sqlite-devel BuildRequires: gpgme-devel BuildRequires: systemd >= %{systemd_version} BuildRequires: libarchive-devel BuildRequires: gobject-introspection-devel BuildRequires: gcab %ifarch %{valgrind_arches} BuildRequires: valgrind BuildRequires: valgrind-devel %endif BuildRequires: elfutils-libelf-devel BuildRequires: gtk-doc BuildRequires: gnutls-devel BuildRequires: gnutls-utils BuildRequires: meson BuildRequires: help2man BuildRequires: json-glib-devel >= %{json_glib_version} BuildRequires: vala BuildRequires: bash-completion BuildRequires: git-core %if 0%{?have_modem_manager} BuildRequires: ModemManager-glib-devel >= 1.10.0 BuildRequires: libqmi-devel >= 1.22.0 %endif %if 0%{?have_redfish} BuildRequires: efivar-devel >= 33 %endif %if 0%{?have_uefi} BuildRequires: efivar-devel >= 33 BuildRequires: python3 python3-cairo python3-gobject python3-pillow BuildRequires: pango-devel BuildRequires: cairo-devel cairo-gobject-devel BuildRequires: freetype BuildRequires: fontconfig BuildRequires: google-noto-sans-cjk-ttc-fonts BuildRequires: gnu-efi-devel BuildRequires: pesign %endif %if 0%{?have_dell} BuildRequires: efivar-devel >= 33 BuildRequires: libsmbios-devel >= 2.3.0 %endif Requires(post): systemd Requires(preun): systemd Requires(postun): systemd Requires: glib2%{?_isa} >= %{glib2_version} Requires: libxmlb%{?_isa} >= %{libxmlb_version} Requires: libgusb%{?_isa} >= %{libgusb_version} Requires: libsoup%{?_isa} >= %{libsoup_version} Requires: bubblewrap Requires: shared-mime-info Recommends: python3 Recommends: tpm2-tools tpm2-abrmd Obsoletes: fwupd-sign < 0.1.6 Obsoletes: libebitdo < 0.7.5-3 Obsoletes: libdfu < 1.0.0 Obsoletes: fwupd-labels < 1.1.0-1 %description fwupd is a daemon to allow session software to update device firmware. %package devel Summary: Development package for %{name} Requires: %{name}%{?_isa} = %{version}-%{release} Obsoletes: libebitdo-devel < 0.7.5-3 Obsoletes: libdfu-devel < 1.0.0 %description devel Files for development with %{name}. %package tests Summary: Data files for installed tests BuildArch: noarch %description tests Data files for installed tests. %prep %autosetup -p1 %build %meson \ -Dgtkdoc=true \ %if 0%{?enable_tests} -Dtests=true \ %else -Dtests=false \ %endif %if 0%{?enable_dummy} -Dplugin_dummy=true \ %else -Dplugin_dummy=false \ %endif -Dplugin_flashrom=true \ -Dplugin_thunderbolt=true \ %if 0%{?have_redfish} -Dplugin_redfish=true \ %else -Dplugin_redfish=false \ %endif %if 0%{?have_uefi} -Dplugin_uefi=true \ -Dplugin_nvme=true \ -Defi_sbat_distro_id="fedora" \ -Defi_sbat_distro_summary="The Fedora Project" \ -Defi_sbat_distro_pkgname="%{name}" \ -Defi_sbat_distro_version="%{version}" \ -Defi_sbat_distro_url="https://src.fedoraproject.org/rpms/%{name}" \ %else -Dplugin_uefi=false \ -Dplugin_nvme=false \ %endif %if 0%{?have_dell} -Dplugin_dell=true \ -Dplugin_synaptics=true \ %else -Dplugin_dell=false \ -Dplugin_synaptics=false \ %endif %if 0%{?have_modem_manager} -Dplugin_modem_manager=true \ %else -Dplugin_modem_manager=false \ %endif -Dman=true %meson_build %if 0%{?enable_tests} %check %meson_test %endif %install %meson_install # sign fwupd.efi loader %if 0%{?have_uefi} %ifarch x86_64 %global efiarch x64 %endif %ifarch aarch64 %global efiarch aa64 %endif %global fwup_efi_fn $RPM_BUILD_ROOT%{_libexecdir}/fwupd/efi/fwupd%{efiarch}.efi %pesign -s -i %{fwup_efi_fn} -o %{fwup_efi_fn}.signed %endif mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg %find_lang %{name} %post %systemd_post fwupd.service %preun %systemd_preun fwupd.service %postun %systemd_postun_with_restart fwupd.service %systemd_postun_with_restart pesign.service %files -f %{name}.lang %doc README.md AUTHORS %license COPYING %config(noreplace)%{_sysconfdir}/fwupd/daemon.conf %if 0%{?have_uefi} %config(noreplace)%{_sysconfdir}/fwupd/uefi.conf %endif %if 0%{?have_redfish} %config(noreplace)%{_sysconfdir}/fwupd/redfish.conf %endif %dir %{_libexecdir}/fwupd %{_libexecdir}/fwupd/fwupd %{_libexecdir}/fwupd/fwupdtool %{_libexecdir}/fwupd/fwupdagent %{_libexecdir}/fwupd/fwupdoffline %if 0%{?have_uefi} %{_libexecdir}/fwupd/efi/*.efi %{_libexecdir}/fwupd/efi/*.efi.signed %{_libexecdir}/fwupd/fwupdate %endif %{_bindir}/dfu-tool %{_bindir}/fwupdmgr %dir %{_sysconfdir}/fwupd %dir %{_sysconfdir}/fwupd/remotes.d %if 0%{?have_dell} %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/dell-esrt.conf %endif %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/lvfs.conf %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/lvfs-testing.conf %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/vendor.conf %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/vendor-directory.conf %config(noreplace)%{_sysconfdir}/pki/fwupd %{_sysconfdir}/pki/fwupd-metadata %{_sysconfdir}/dbus-1/system.d/org.freedesktop.fwupd.conf %{_datadir}/bash-completion/completions/fwupdmgr %{_datadir}/bash-completion/completions/fwupdtool %{_datadir}/bash-completion/completions/fwupdagent %{_datadir}/fwupd/metainfo/org.freedesktop.fwupd*.metainfo.xml %if 0%{?have_dell} %{_datadir}/fwupd/remotes.d/dell-esrt/metadata.xml %endif %{_datadir}/fwupd/remotes.d/vendor/firmware/README.md %{_datadir}/dbus-1/interfaces/org.freedesktop.fwupd.xml %{_datadir}/polkit-1/actions/org.freedesktop.fwupd.policy %{_datadir}/polkit-1/rules.d/org.freedesktop.fwupd.rules %{_datadir}/dbus-1/system-services/org.freedesktop.fwupd.service %{_datadir}/man/man1/dfu-tool.1.gz %{_datadir}/man/man1/fwupdmgr.1.gz %{_datadir}/metainfo/org.freedesktop.fwupd.metainfo.xml %{_datadir}/icons/hicolor/scalable/apps/org.freedesktop.fwupd.svg %{_datadir}/fwupd/firmware-packager %{_unitdir}/fwupd-offline-update.service %{_unitdir}/fwupd.service %{_unitdir}/system-update.target.wants/ %dir %{_localstatedir}/lib/fwupd %dir %{_datadir}/fwupd/quirks.d %{_datadir}/fwupd/quirks.d/*.quirk %{_localstatedir}/lib/fwupd/builder/README.md %{_libdir}/libfwupd*.so.* %{_libdir}/girepository-1.0/Fwupd-2.0.typelib /usr/lib/udev/rules.d/*.rules /usr/lib/systemd/system-shutdown/fwupd.shutdown %dir %{_libdir}/fwupd-plugins-3 %{_libdir}/fwupd-plugins-3/libfu_plugin_altos.so %{_libdir}/fwupd-plugins-3/libfu_plugin_amt.so %{_libdir}/fwupd-plugins-3/libfu_plugin_ata.so %{_libdir}/fwupd-plugins-3/libfu_plugin_colorhug.so %{_libdir}/fwupd-plugins-3/libfu_plugin_csr.so %if 0%{?have_dell} %{_libdir}/fwupd-plugins-3/libfu_plugin_dell.so %{_libdir}/fwupd-plugins-3/libfu_plugin_dell_esrt.so %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_dell_dock.so %{_libdir}/fwupd-plugins-3/libfu_plugin_dfu.so %{_libdir}/fwupd-plugins-3/libfu_plugin_ebitdo.so %{_libdir}/fwupd-plugins-3/libfu_plugin_fastboot.so %{_libdir}/fwupd-plugins-3/libfu_plugin_flashrom.so %if 0%{?have_modem_manager} %{_libdir}/fwupd-plugins-3/libfu_plugin_modem_manager.so %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_nitrokey.so %if 0%{?have_uefi} %{_libdir}/fwupd-plugins-3/libfu_plugin_nvme.so %endif %if 0%{?have_redfish} %{_libdir}/fwupd-plugins-3/libfu_plugin_redfish.so %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_rts54hid.so %{_libdir}/fwupd-plugins-3/libfu_plugin_rts54hub.so %{_libdir}/fwupd-plugins-3/libfu_plugin_steelseries.so %{_libdir}/fwupd-plugins-3/libfu_plugin_superio.so %if 0%{?have_dell} %{_libdir}/fwupd-plugins-3/libfu_plugin_synapticsmst.so %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_synaptics_prometheus.so %if 0%{?enable_dummy} %{_libdir}/fwupd-plugins-3/libfu_plugin_test.so %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_thunderbolt.so %{_libdir}/fwupd-plugins-3/libfu_plugin_thunderbolt_power.so %{_libdir}/fwupd-plugins-3/libfu_plugin_udev.so %if 0%{?have_uefi} %{_libdir}/fwupd-plugins-3/libfu_plugin_uefi.so %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_unifying.so %{_libdir}/fwupd-plugins-3/libfu_plugin_upower.so %{_libdir}/fwupd-plugins-3/libfu_plugin_wacom_raw.so %{_libdir}/fwupd-plugins-3/libfu_plugin_wacom_usb.so %ghost %{_localstatedir}/lib/fwupd/gnupg %if 0%{?have_uefi} %{_datadir}/locale/*/LC_IMAGES/fwupd* %endif %files devel %{_datadir}/gir-1.0/Fwupd-2.0.gir %{_datadir}/gtk-doc/html/libfwupd %{_datadir}/vala/vapi %{_includedir}/fwupd-1 %{_libdir}/libfwupd*.so %{_libdir}/pkgconfig/fwupd.pc %files tests %dir %{_datadir}/installed-tests/fwupd %{_datadir}/installed-tests/fwupd/fwupd-tests.xml %{_datadir}/installed-tests/fwupd/*.test %{_datadir}/installed-tests/fwupd/*.cab %{_datadir}/installed-tests/fwupd/*.sh %{_datadir}/installed-tests/fwupd/*.py* %dir %{_sysconfdir}/fwupd/remotes.d %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/fwupd-tests.conf %changelog * #LONGDATE# Richard Hughes #VERSION#-0.#BUILD##ALPHATAG# - Update from git fwupd-1.2.14/contrib/meson.build000066400000000000000000000000341402665037500165330ustar00rootroot00000000000000subdir('firmware-packager') fwupd-1.2.14/contrib/nvme-parse.py000077500000000000000000000106421402665037500170310ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ import csv import binascii import os import struct import glob from collections import namedtuple class Record(object): def __init__(self, filename, cns): self.filename = filename self.cns = cns def load_pci_ids(): pci_vendors = {} pci_vendors[0x1987] = 'Freescale' for ln in open('/usr/share/hwdata/pci.ids').read().split('\n'): if ln.startswith('#'): continue if ln.startswith('\t'): continue data = ln.split(' ') if len(data) != 2: continue pci_vendors[int(data[0], 16)] = data[1].split(' ')[0] if data[0] == 'ffff': break return pci_vendors def _data_to_utf8(s): return s.decode('utf-8', 'replace').replace('\0', ' ') def main(): # open files records = [] for fn in glob.glob('tests/nvme/*'): blob = open(fn, 'rb').read() if len(blob) != 4096: print('WARNING: ignoring %s of size %i' % (fn, len(blob))) continue Cns = namedtuple('Cns', 'vid ssvid sn mn fr rab ieee cmic mdts cntlid ver ' \ 'rtd3r rtd3e oaes ctratt rrls rsvd102 oacs acl aerl ' \ 'frmw lpa elpe npss avscc apsta wctemp cctemp mtfa ' \ 'hmpre hmmin tnvmcap unvmcap rpmbs edstt dsto fwug ' \ 'kas hctma mntmt mxtmt sanicap hmminds hmmaxd ' \ 'nsetidmax rsvd340 anatt anacap anagrpmax nanagrpid ' \ 'rsvd352 sqes cqes maxcmd nn oncs fuses fna vwc awun ' \ 'awupf nvscc nwpc acwu rsvd534 sgls mnan rsvd544 ' \ 'subnqn rsvd1024 ioccsz iorcsz icdoff ctrattr msdbd ' \ 'rsvd1804 psd vs') try: cns = Cns._make(struct.unpack(' 0: s1ro_cnt += 1 if (r.cns.frmw & 0x10) >> 4: fawr_cnt += 1 nfws = (r.cns.frmw & 0x0e) >> 1 if nfws in nfws_map: nfws_map[nfws] += 1 continue nfws_map[nfws] = 1 print('s1ro=%i/%i' % (s1ro_cnt, len(records))) print('fawr=%i/%i' % (fawr_cnt, len(records))) nfws = sorted(nfws_map.items(), key=lambda k: k[0], reverse=True) for nfws, cnt in nfws: print('nfws[%i]=%i' % (nfws, cnt)) # vendor popularity vids = {} for r in records: if r.cns.vid not in vids: vids[r.cns.vid] = 1 continue vids[r.cns.vid] += 1 vids = sorted(vids.items(), key=lambda k: k[1], reverse=True) pci_vendors = load_pci_ids() for vid, cnt in vids: name = '0x%04x' % vid if vid in pci_vendors: name = pci_vendors[vid] print('%s,%i' % (name, cnt)) # vendor records vs_records = [] for r in records: if r.cns.vs: vs_records.append(r) print('nr_vs=%i' % len(vs_records)) main() fwupd-1.2.14/contrib/simple_client.py000077500000000000000000000110261402665037500176000ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ """A simple fwupd frontend""" import sys import os import gi from gi.repository import GLib gi.require_version('Fwupd', '2.0') from gi.repository import Fwupd #pylint: disable=wrong-import-position class Progress(): """Class to track the signal changes of progress events""" def __init__(self): self.device = None self.status = None self.percent = 0 self.erase = 0 def device_changed(self, new_device): """Indicate new device string to track""" if self.device != new_device: self.device = new_device print("\nUpdating %s" % self.device) def status_changed(self, percent, status): """Indicate new status string or % complete to track""" if self.status != status or self.percent != percent: for i in range(0, self.erase): sys.stdout.write("\b \b") self.status = status self.percent = percent status_str = "[" for i in range(0, 50): if i < percent/2: status_str += '*' else: status_str += ' ' status_str += "] %d%% %s" %(percent, status) status_str.erase = len(status_str) sys.stdout.write(status_str) sys.stdout.flush() if 'idle' in status: sys.stdout.write("\n") def parse_args(): """Parse arguments for this client""" import argparse parser = argparse.ArgumentParser(description="Interact with fwupd daemon") parser.add_argument("--allow-older", action="store_true", help="Install older payloads(default False)") parser.add_argument("--allow-reinstall", action="store_true", help="Reinstall payloads(default False)") parser.add_argument("command", choices=["get-devices", "get-details", "install"], help="What to do") parser.add_argument('cab', nargs='?', help='CAB file') parser.add_argument('deviceid', nargs='?', help='DeviceID to operate on(optional)') args = parser.parse_args() return args def get_devices(client): """Use fwupd client to fetch devices""" devices = client.get_devices() for item in devices: print(item.to_string()) def get_details(client, cab): """Use fwupd client to fetch details for a CAB file""" devices = client.get_details(cab, None) for device in devices: print(device.to_string()) def status_changed(client, spec, progress): #pylint: disable=unused-argument """Signal emitted by fwupd daemon indicating status changed""" progress.status_changed(client.get_percentage(), Fwupd.status_to_string(client.get_status())) def device_changed(client, device, progress): #pylint: disable=unused-argument """Signal emitted by fwupd daemon indicating active device changed""" progress.device_changed(device.get_name()) def install(client, cab, target, older, reinstall): """Use fwupd client to install CAB file to applicable devices""" # FWUPD_DEVICE_ID_ANY if not target: target = '*' flags = Fwupd.InstallFlags.NONE if older: flags |= Fwupd.InstallFlags.ALLOW_OLDER if reinstall: flags |= Fwupd.InstallFlags.ALLOW_REINSTALL progress = Progress() parent = super(client.__class__, client) parent.connect('device-changed', device_changed, progress) parent.connect('notify::percentage', status_changed, progress) parent.connect('notify::status', status_changed, progress) try: client.install(target, cab, flags, None) except GLib.Error as glib_err: #pylint: disable=catching-non-exception progress.status_changed(0, 'idle') print("%s" % glib_err) sys.exit(1) def check_cab(cab): """Check that CAB file exists""" if not cab: print("Need to specify payload") sys.exit(1) if not os.path.isfile(cab): print("%s doesn't exist or isn't a file" % cab) sys.exit(1) if __name__ == '__main__': ARGS = parse_args() CLIENT = Fwupd.Client() CLIENT.connect() if ARGS.command == "get-devices": get_devices(CLIENT) elif ARGS.command == "get-details": check_cab(ARGS.cab) get_details(CLIENT, ARGS.cab) elif ARGS.command == "install": check_cab(ARGS.cab) install(CLIENT, ARGS.cab, ARGS.deviceid, ARGS.allow_older, ARGS.allow_reinstall) fwupd-1.2.14/contrib/snap/000077500000000000000000000000001402665037500153355ustar00rootroot00000000000000fwupd-1.2.14/contrib/snap/README.md000066400000000000000000000015461402665037500166220ustar00rootroot00000000000000# Snap support Snaps are containerised software packages that are simple to create and install. They auto-update and are safe to run. And because they bundle their dependencies, they work on all major Linux systems without modification. ## stable vs unstable Two yaml files are distributed: * snapcraft.yaml This uses tarball releases for all dependencies and what is currently in tree for fwupd. * snapcraft-master.yaml This uses git for most dependencies and may be considered unstable. # Building Builds can be performed using snapcraft: ``` # snapcraft cleanbuild ``` # Installing A "classic" snap is produced, and locally built snaps can be installed like this: ``` # snap install fwupd_daily_amd64.snap --dangerous --classic ``` The `--dangerous` flag is because snaps built locally are not signed. Snaps distributed by a store will not need this flag. fwupd-1.2.14/contrib/snap/activate-shutdown/000077500000000000000000000000001402665037500210065ustar00rootroot00000000000000fwupd-1.2.14/contrib/snap/activate-shutdown/Makefile000066400000000000000000000005221402665037500224450ustar00rootroot00000000000000build: true install: install -d ${DESTDIR}/etc/systemd/system/ install -m0644 fwupd-activate.service ${DESTDIR}/etc/systemd/system # fixes up shutdown activation script for classic snap sed -i "s,/libexec/fwupd/,/snap/bin/fwupd.," \ ${SNAPCRAFT_STAGE}/lib/systemd/system-shutdown/fwupd.shutdown fwupd-1.2.14/contrib/snap/activate-shutdown/fwupd-activate.service000066400000000000000000000003231402665037500253110ustar00rootroot00000000000000[Unit] Description=Activate fwupd updates After=snapd.service [Service] Type=oneshot RemainAfterExit=true ExecStop=/snap/bin/fwupd.fwupdtool activate SuccessExitStatus=0 2 [Install] WantedBy=multi-user.target fwupd-1.2.14/contrib/snap/dfu-tool.wrapper000077500000000000000000000000731402665037500204730ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/dfu-tool $@ fwupd-1.2.14/contrib/snap/fix-bash-completion/000077500000000000000000000000001402665037500212055ustar00rootroot00000000000000fwupd-1.2.14/contrib/snap/fix-bash-completion/Makefile000066400000000000000000000006671402665037500226560ustar00rootroot00000000000000build: true install: #fixes up fwupdtool -> fwupd.fwupdtool sed -i "s,\(complete -F _fwupd[a-z]*\) \(fwupd.*\),\1 fwupd.\2,; \ s,\(command.*\)\(fwupdtool\),\1fwupd.\2," \ ${SNAPCRAFT_STAGE}/share/bash-completion/completions/* # fixes up dbus service for classic snap sed -i 's!SystemdService=\(.*\)!SystemdService=snap.fwupd.fwupd.service!' \ ${SNAPCRAFT_STAGE}/share/dbus-1/system-services/org.freedesktop.fwupd.service fwupd-1.2.14/contrib/snap/fwup-efi-signed/000077500000000000000000000000001402665037500203265ustar00rootroot00000000000000fwupd-1.2.14/contrib/snap/fwup-efi-signed/Makefile000066400000000000000000000010711402665037500217650ustar00rootroot00000000000000DEB_HOST_ARCH=$(shell dpkg-architecture -q DEB_HOST_ARCH) EFI_NAME := UNKNOWN-EFI-NAME ifeq ($(DEB_HOST_ARCH),amd64) EFI_NAME := x64 endif ifeq ($(DEB_HOST_ARCH),i386) EFI_NAME := ia32 endif ifeq ($(DEB_HOST_ARCH),arm64) EFI_NAME := aa64 endif ifeq ($(DEB_HOST_ARCH),armhf) EFI_NAME := arm endif SIGNED := \ fwupd$(EFI_NAME).efi.signed all: $(SIGNED) $(SIGNED): ./download-fwupd install: $(SIGNED) install -d $(DESTDIR)/libexec/fwupd/efi install -m0644 $(SIGNED) $(SIGNED).version \ $(DESTDIR)/libexec/fwupd/efi clean: rm -f $(SIGNED) $(SIGNED).version fwupd-1.2.14/contrib/snap/fwup-efi-signed/download-fwupd000077500000000000000000000020301402665037500232010ustar00rootroot00000000000000#! /usr/bin/python3 import re import shutil from urllib.parse import urlparse, urlunparse from urllib.request import urlopen import apt import apt_pkg ARCH_TO_EFI_NAME = { 'amd64': 'x64', 'i386': 'ia32', 'arm64': 'aa64', 'armhf': 'arm', } arch = apt_pkg.config['Apt::Architecture'] efi_name = ARCH_TO_EFI_NAME[arch] cache = apt.Cache() fwupd_efi = cache["fwupd"].candidate pool_parsed = urlparse(fwupd_efi.uri) dists_dir = "/dists/devel/main/uefi/fwupd-%s/current/" % ( fwupd_efi.architecture) DOWNLOAD_LIST = { "fwupd%s.efi.signed" %efi_name: "fwupd%s.efi.signed" % efi_name, "version": "fwupd%s.efi.signed.version" % efi_name } for base in DOWNLOAD_LIST: dists_parsed = list(pool_parsed) dists_parsed[2] = re.sub(r"/pool/.*", dists_dir + base, dists_parsed[2]) dists_uri = urlunparse(dists_parsed) target = DOWNLOAD_LIST[base] print("Downloading %s to %s..." % (dists_uri, target)) with urlopen(dists_uri) as dists, open(target, "wb") as out: shutil.copyfileobj(dists, out) fwupd-1.2.14/contrib/snap/fwupd-command000077500000000000000000000023621402665037500200270ustar00rootroot00000000000000#!/bin/sh export XDG_CACHE_HOME=$SNAP_USER_COMMON/.cache mkdir -p $XDG_CACHE_HOME export GIO_MODULE_DIR=$XDG_CACHE_HOME/gio-modules export XDG_DATA_DIRS="$SNAP/usr/share" #determine architecture if [ "$SNAP_ARCH" = "amd64" ]; then ARCH="x86_64-linux-gnu" elif [ "$SNAP_ARCH" = "armhf" ]; then ARCH="arm-linux-gnueabihf" elif [ "$SNAP_ARCH" = "arm64" ]; then ARCH="aarch64-linux-gnu" else ARCH="$SNAP_ARCH-linux-gnu" fi # don't update between versions, we want to preserve previous data [ ! -d "$SNAP_USER_DATA/etc" ] && cp -R "$SNAP/etc" "$SNAP_USER_DATA" [ ! -d "$SNAP_USER_DATA/var" ] && cp -R "$SNAP/var" "$SNAP_USER_DATA" # re-generate gio modules in local cache needs_update=true if [ -f $SNAP_USER_DATA/.last_revision ]; then . $SNAP_USER_DATA/.last_revision 2>/dev/null fi if [ "$SNAP_DESKTOP_LAST_REVISION" = "$SNAP_REVISION" ]; then needs_update=false fi if [ $needs_update = true ]; then if [ -f $SNAP/usr/lib/$ARCH/glib-2.0/gio-querymodules ]; then rm -rf $GIO_MODULE_DIR mkdir -p $GIO_MODULE_DIR ln -s $SNAP/usr/lib/$ARCH/gio/modules/*.so $GIO_MODULE_DIR $SNAP/usr/lib/$ARCH/glib-2.0/gio-querymodules $GIO_MODULE_DIR fi echo "SNAP_DESKTOP_LAST_REVISION=$SNAP_REVISION" > $SNAP_USER_DATA/.last_revision fi exec "$@" fwupd-1.2.14/contrib/snap/fwupd.wrapper000077500000000000000000000001021402665037500200600ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/libexec/fwupd/fwupd $@ fwupd-1.2.14/contrib/snap/fwupdmgr.wrapper000077500000000000000000000000731402665037500205750ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/fwupdmgr $@ fwupd-1.2.14/contrib/snap/fwupdtool.wrapper000077500000000000000000000001061402665037500207620ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/libexec/fwupd/fwupdtool $@ fwupd-1.2.14/contrib/snap/libefivar-fixpkgconfig/000077500000000000000000000000001402665037500217545ustar00rootroot00000000000000fwupd-1.2.14/contrib/snap/libefivar-fixpkgconfig/Makefile000066400000000000000000000011601402665037500234120ustar00rootroot00000000000000build: true install: sed -i 's!libdir=\(.*\)!libdir=${SNAPCRAFT_STAGE}\1!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efiboot.pc sed -i 's!includedir=\(.*\)!includedir=${SNAPCRAFT_STAGE}\1!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efiboot.pc sed -i 's!Cflags:\(.*\)!Cflags:\1 -L$$\{libdir\}!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efiboot.pc sed -i 's!libdir=\(.*\)!libdir=${SNAPCRAFT_STAGE}\1!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efivar.pc sed -i 's!includedir=\(.*\)!includedir=${SNAPCRAFT_STAGE}\1!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efivar.pc sed -i 's!Cflags:\(.*\)!Cflags:\1 -L$$\{libdir\}!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efivar.pc fwupd-1.2.14/contrib/snap/snapcraft-master.yaml000066400000000000000000000206131402665037500214750ustar00rootroot00000000000000name: fwupd version-script: cat $SNAPCRAFT_STAGE/version version: 'daily' summary: A standalone version of fwupd to install newer firmware updates description: | This is a tool that can be used to install firmware updates on devices not yet supported by the version of fwupd distributed with the OS. grade: devel confinement: classic architectures: - amd64 apps: dfu-tool: command: dfu-tool.wrapper fwupdtool: command: fwupdtool.wrapper completer: share/bash-completion/completions/fwupdtool fwupd: command: fwupd.wrapper daemon: simple fwupdmgr: command: fwupdmgr.wrapper completer: share/bash-completion/completions/fwupdmgr parts: libefivar-dev: plugin: make make-parameters: - prefix=/ - libdir=/lib source: https://github.com/rhboot/efivar.git source-type: git build-packages: - libpopt-dev prime: - -include - -bin - -share/man - -lib/pkgconfig #adjust the paths from libefivar libefivar-fixpkgconfig: plugin: make source: contrib/snap/libefivar-fixpkgconfig make-parameters: - SNAPCRAFT_STAGE=$SNAPCRAFT_STAGE after: [libefivar-dev] libsmbios: plugin: autotools source: https://github.com/dell/libsmbios source-type: git build-packages: - libxml2-dev - pkg-config - autoconf - automake - libtool - autopoint prime: - -include/ - -lib/pkgconfig - -lib/python3.5 - -sbin/ - -share/ - -etc/ - -lib/*.a meson: plugin: python source: https://github.com/mesonbuild/meson.git source-tag: 0.47.2 build-packages: - ninja-build prime: - -bin - -etc - -lib - -share - -usr gudev: plugin: autotools source: https://gitlab.gnome.org/GNOME/libgudev.git source-type: git configflags: - --disable-umockdev build-packages: - libglib2.0-dev - pkg-config - libudev-dev - gtk-doc-tools - gnome-common prime: - -include - -lib/girepository-1.0 - -lib/pkgconfig - -share/ # this is for the library only, we don't care about the daemon "in-snap" modemmanager: plugin: autotools source: https://gitlab.freedesktop.org/mobile-broadband/ModemManager.git #not yet tagged #source-tag: 1.10.0 after: [gudev, gettext] # build without these; system daemon needs them configflags: - --without-mbim - --without-qmi prime: - -include - -etc - -sbin - -bin - -share - -lib/*.*a - -lib/pkgconfig - -lib/ModemManager - -lib/systemd - -lib/udev - -lib/girepository-1.0 libqmi: plugin: autotools source: https://gitlab.freedesktop.org/mobile-broadband/libqmi.git #not yet tagged #source-tag: 1.10.0 after: [gudev, gettext, modemmanager] # build without these; system daemon needs them configflags: - --without-udev prime: - -include - -etc - -sbin - -bin - -share - -lib/*.*a - -lib/pkgconfig - -lib/ModemManager - -lib/systemd - -lib/udev - -lib/girepository-1.0 libusb: plugin: autotools source: https://github.com/libusb/libusb/releases/download/v1.0.22/libusb-1.0.22.tar.bz2 configflags: - --disable-static prime: - -include/ - -lib/pkgconfig gusb: plugin: meson source: https://github.com/hughsie/libgusb/archive/0.3.0.tar.gz meson-parameters: [--prefix=/, -Dtests=false, -Dvapi=false, -Ddocs=false] build-packages: - libgirepository1.0-dev prime: - -bin/ - -include - -share - -lib/*/pkgconfig - -lib/*/girepository-1.0 after: [meson, libusb] gnu-efi: plugin: make source: http://superb-dca2.dl.sourceforge.net/project/gnu-efi/gnu-efi-3.0.5.tar.bz2 make-parameters: - PREFIX=/usr make-install-var: INSTALLROOT prime: - -usr/include/ - -usr/lib #fetch the latest version of the signed bootloader #this might not match our fwupdx64.efi, but it's better than nothing fwup-efi-signed: build-packages: - python3-apt plugin: make source: contrib/snap/fwup-efi-signed #needed for UEFI plugin to build UX labels build-introspection: plugin: nil stage-packages: - python3-gi - python3-gi-cairo - python3-pil prime: - -etc - -usr - -lib - -var #0.19.8.1 adds support for GETTEXTDATADIRS which is needed by meson's msgfmthelper gettext: source: https://ftp.gnu.org/pub/gnu/gettext/gettext-0.19.8.1.tar.xz plugin: autotools build-packages: - bison - libunistring-dev - libxml2-dev configflags: - --prefix=/usr - --disable-static - --disable-curses - --disable-java - --enable-relocatable - --without-emacs - --without-included-glib - --without-included-libunistring - --without-included-libxml stage-packages: - libunistring0 - libxml2 - libgomp1 prime: - -**/*.a - -**/*.la - -usr/bin - -usr/include - -usr/lib/gettext - -usr/share fwupd: plugin: meson meson-parameters: [--prefix=/, -Defi-includedir=$SNAPCRAFT_STAGE/usr/include/efi, -Defi-ldsdir=$SNAPCRAFT_STAGE/usr/lib, -Defi-libdir=$SNAPCRAFT_STAGE/usr/lib, -Dtests=false, -Ddaemon=true, -Dgtkdoc=false, -Dintrospection=false, -Dman=false, -Dplugin_modem_manager=true, -Dudevdir=$SNAPCRAFT_STAGE/lib/udev, -Dlibxmlb:gtkdoc=false, -Dlibxmlb:introspection=false, -Dpkcs7=false] source: . source-type: git override-build: | snapcraftctl build echo $(git describe HEAD --always) > $SNAPCRAFT_STAGE/version build-packages: - bash-completion - gcab - gnutls-dev - libarchive-dev - libcairo-dev - libelf-dev - libgcab-dev - libglib2.0-dev - libgpgme11-dev - libjson-glib-dev - libpango1.0-dev - libpolkit-gobject-1-dev - libsoup2.4-dev - libsqlite3-dev - locales - pkg-config - uuid-dev stage-packages: - libgcab-1.0-0 - libarchive13 - libassuan0 - liblcms2-2 - libelf1 - libgpgme11 - libjson-glib-1.0-0 - libpolkit-gobject-1-0 - libsoup2.4-1 - glib-networking - libglib2.0-bin prime: # we explicitly don't want /usr/bin/gpgconf # this will cause gpgme to error finding it # but that also avoids trying to use non-existent # /usr/bin/gpg2 - -usr/bin - -usr/sbin - -usr/share/man - -usr/share/GConf - -etc/X11 - -etc/ldap - -etc/logcheck - -usr/lib/dconf - -usr/lib/gcc - -usr/lib/glib-networking - -usr/lib/gnupg2 - -usr/lib/sasl2 - -usr/lib/systemd - -usr/lib/*/audit - -usr/share/glib-2.0/schemas - -usr/share/X11 - -include - -lib/udev - -lib/*/pkgconfig - -usr/share/lintian - -usr/share/pkgconfig - -usr/share/installed-tests - -usr/share/polkit-1 - -usr/share/vala - -usr/share/doc - -usr/share/gnupg2 - -usr/share/info - -usr/share/gir-1.0 - -usr/share/upstart - -usr/lib/*/pkgconfig after: [gudev, gusb, gnu-efi, libefivar-fixpkgconfig, libsmbios, build-introspection, gettext, modemmanager, libqmi] fix-bash-completion: plugin: make source: contrib/snap/fix-bash-completion after: [fwupd] activate-shutdown: plugin: make source: contrib/snap/activate-shutdown after: [fwupd] update-mime: plugin: make source: contrib/snap/update-mime stage-packages: - shared-mime-info - gsettings-desktop-schemas - libxml2 prime: - -usr/bin - -usr/share/doc - -usr/share/doc-base - -usr/share/man - -usr/share/lintian - -usr/share/pkgconfig - -usr/share/GConf after: [fwupd] fwupd-wrappers: plugin: dump source: contrib/snap stage: - dfu-tool.wrapper - fwupd-command - fwupdtool.wrapper - fwupd.wrapper - fwupdmgr.wrapper fwupd-1.2.14/contrib/snap/update-mime/000077500000000000000000000000001402665037500175445ustar00rootroot00000000000000fwupd-1.2.14/contrib/snap/update-mime/Makefile000066400000000000000000000002021402665037500211760ustar00rootroot00000000000000build: true install: update-mime-database ../install/usr/share/mime glib-compile-schemas ../install/usr/share/glib-2.0/schemas fwupd-1.2.14/contrib/standalone-installer/000077500000000000000000000000001402665037500205175ustar00rootroot00000000000000fwupd-1.2.14/contrib/standalone-installer/README.md000066400000000000000000000004301402665037500217730ustar00rootroot00000000000000# Standalone installer This is a script that will build a standalone installer around the fwupd snap or flatpak. This can be used for distributing updates that use fwupd on machines without networking and the needed tools. For usage instructions, view: ``` ./make.py --help ``` fwupd-1.2.14/contrib/standalone-installer/assets/000077500000000000000000000000001402665037500220215ustar00rootroot00000000000000fwupd-1.2.14/contrib/standalone-installer/assets/header.py000066400000000000000000000241311402665037500236240ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # from base64 import b64decode import io import os import subprocess import sys import shutil import tempfile import zipfile TAG = b'#\x00' def parse_args(): import argparse parser = argparse.ArgumentParser(description="Self extracting firmware updater") parser.add_argument("--directory", help="Directory to extract to") parser.add_argument("--cleanup", action='store_true', help="Remove tools when done with installation") parser.add_argument("--verbose", action='store_true', help="Run the tool in verbose mode") parser.add_argument("--allow-reinstall", action='store_true', help="Allow re-installing existing firmware versions") parser.add_argument("--allow-older", action='store_true', help="Allow downgrading firmware versions") parser.add_argument("command", choices=["install", "extract"], help="Command to run") args = parser.parse_args() return args def error (msg): print(msg) sys.exit(1) def bytes_slicer(length, source): start = 0 stop = length while start < len(source): yield source[start:stop] start = stop stop += length def get_zip(): script = os.path.realpath (__file__) bytes_out = io.BytesIO() with open(script, 'rb') as source: for line in source: if not line.startswith(TAG): continue bytes_out.write(b64decode(line[len(TAG):-1])) return bytes_out def unzip (destination): zipf = get_zip () source = zipfile.ZipFile (zipf, 'r') for item in source.namelist(): # extract handles the sanitization source.extract (item, destination) def copy_cabs (source, target): if not os.path.exists (target): os.makedirs (target) cabs = [] for root, dirs, files in os.walk (source): for f in files: if (f.endswith ('.cab')): origf = os.path.join(root, f) shutil.copy (origf, target) cabs.append (os.path.join (target, f)) return cabs def install_snap (directory, verbose, allow_reinstall, allow_older, uninstall): app = 'fwupd' common = '/root/snap/%s/common' % app #check if snap is installed with open(os.devnull, 'w') as devnull: subprocess.run (['snap'], check=True, stdout=devnull, stderr=devnull) #check existing installed cmd = ['snap', 'list', app] with open(os.devnull, 'w') as devnull: if verbose: print(cmd) ret = subprocess.run (cmd, stdout=devnull, stderr=devnull) if ret.returncode == 0: cmd = ['snap', 'remove', app] if verbose: print(cmd) subprocess.run (cmd, check=True) # install the snap cmd = ['snap', 'ack', os.path.join (directory, 'fwupd.assert')] if verbose: print(cmd) subprocess.run (cmd, check=True) cmd = ['snap', 'install', '--classic', os.path.join (directory, 'fwupd.snap')] if verbose: print(cmd) subprocess.run (cmd, check=True) # copy the CAB files cabs = copy_cabs (directory, common) # run the snap for cab in cabs: cmd = ["%s.fwupdmgr" % app, 'install', cab] if allow_reinstall: cmd += ["--allow-reinstall"] if allow_older: cmd += ["--allow-older"] if verbose: cmd += ["--verbose"] print(cmd) subprocess.run (cmd) #remove copied cabs for f in cabs: os.remove(f) #cleanup if uninstall: cmd = ['snap', 'remove', app] if verbose: print(cmd) subprocess.run (cmd) def install_flatpak (directory, verbose, allow_reinstall, allow_older, uninstall): app = 'org.freedesktop.fwupd' common = '%s/.var/app/%s' % (os.getenv ('HOME'), app) with open(os.devnull, 'w') as devnull: if not verbose: output = devnull else: output = None #look for dependencies dep = 'org.gnome.Platform/x86_64/3.30' repo = 'flathub' repo_url = 'https://flathub.org/repo/flathub.flatpakrepo' cmd = ['flatpak', 'info', dep] if verbose: print(cmd) ret = subprocess.run (cmd, stdout=output, stderr=output) #not installed if ret.returncode != 0: #look for remotes cmd = ['flatpak', 'remote-info', repo, dep] if verbose: print(cmd) ret = subprocess.run (cmd, stdout=output, stderr=output) #not enabled, enable it if ret.returncode != 0: cmd = ['flatpak', 'remote-add', repo, repo_url] if verbose: print(cmd) ret = subprocess.run (cmd, stderr=output) # install dep cmd = ['flatpak', 'install', repo, dep] if verbose: print(cmd) ret = subprocess.run (cmd) #check existing installed cmd = ['flatpak', 'info', app] if verbose: print(cmd) ret = subprocess.run (cmd, stdout=output, stderr=output) if ret.returncode == 0: cmd = ['flatpak', 'remove', app] if verbose: print(cmd) subprocess.run (cmd, check=True) #install the flatpak cmd = ['flatpak', 'install', os.path.join (directory, 'fwupd.flatpak')] if verbose: print(cmd) subprocess.run (cmd, check=True) # copy the CAB files cabs = copy_cabs (directory, common) #run command for cab in cabs: cmd = ['flatpak', 'run', app, 'install', cab] if allow_reinstall: cmd += ["--allow-reinstall"] if allow_older: cmd += ["--allow-older"] if verbose: cmd += ["--verbose"] print(cmd) subprocess.run (cmd) #remove copied cabs for f in cabs: os.remove(f) #cleanup if uninstall: cmd = ['flatpak', 'remove', app] if verbose: print(cmd) subprocess.run (cmd) # Check which package to use # - return False to use packaged version # - return True for snap/flatpak def use_included_version(minimum_version): try: import apt except ModuleNotFoundError: return True cache = apt.Cache() pkg = cache.get("fwupd") version = pkg.installed if not version: return True if minimum_version: if minimum_version > version: print("fwupd %s is already installed but this package requires %s" % (version.version, minimum_version)) else: print("Using existing fwupd version %s already installed on system." % version.version) return False else: print("fwupd %s is installed and must be removed" % version.version) return remove_packaged_version(pkg, cache) def remove_packaged_version(pkg, cache): res = False while True: res = input("Remove now (Y/N)? ") if res.lower() == 'n': res = False break if res.lower() == 'y': res = True break if res: pkg.mark_delete() res = cache.commit() if not res: raise Exception("Need to remove packaged version") return True def install_builtin(directory, verbose, allow_reinstall, allow_older): cabs = [] for root, dirs, files in os.walk (directory): for f in files: if f.endswith('.cab'): cabs.append(os.path.join(root, f)) #run command for cab in cabs: cmd = ['fwupdmgr', 'install', cab] if allow_reinstall: cmd += ["--allow-reinstall"] if allow_older: cmd += ["--allow-older"] if verbose: cmd += ["--verbose"] print(cmd) subprocess.run(cmd) def run_installation (directory, verbose, allow_reinstall, allow_older, uninstall): try_snap = False try_flatpak = False #determine if a minimum version was specified minimum_path = os.path.join(directory, "minimum") minimum = None if os.path.exists(minimum_path): with open(minimum_path, "r") as rfd: minimum = rfd.read() if not use_included_version(minimum): install_builtin(directory, verbose, allow_reinstall, allow_older) return # determine what self extracting binary has if os.path.exists (os.path.join (directory, 'fwupd.snap')) and \ os.path.exists (os.path.join (directory, 'fwupd.assert')): try_snap = True if os.path.exists (os.path.join (directory, 'fwupd.flatpak')): try_flatpak = True if try_snap: try: install_snap (directory, verbose, allow_reinstall, allow_older, uninstall) return True except Exception as _: if verbose: print ("Snap installation failed") if not try_flatpak: error ("Snap installation failed") if try_flatpak: install_flatpak (directory, verbose, allow_reinstall, allow_older, uninstall) if __name__ == '__main__': args = parse_args() if 'extract' in args.command: if args.allow_reinstall: error ("allow-reinstall argument doesn't make sense with command %s" % args.command) if args.allow_older: error ("allow-older argument doesn't make sense with command %s" % args.command) if args.cleanup: error ("Cleanup argument doesn't make sense with command %s" % args.command) if args.directory is None: error ("No directory specified") if not os.path.exists (args.directory): print ("Creating %s" % args.directory) os.makedirs (args.directory) unzip (args.directory) else: if args.directory: error ("Directory argument %s doesn't make sense with command %s" % (args.directory, args.command)) if os.getuid() != 0: error ("This tool must be run as root") with tempfile.TemporaryDirectory (prefix='fwupd') as target: unzip (target) run_installation (target, args.verbose, args.allow_reinstall, args.allow_older, args.cleanup) fwupd-1.2.14/contrib/standalone-installer/make.py000077500000000000000000000112301402665037500220060ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # from base64 import b64encode import io import os import subprocess import shutil import sys import tempfile import zipfile from assets.header import TAG def error (msg): print(msg) sys.exit(1) def parse_args(): import argparse parser = argparse.ArgumentParser(description="Generate a standalone firmware updater") parser.add_argument("--disable-snap-download", action='store_true', help="Don't download support for snap") parser.add_argument("--disable-flatpak-download", action='store_true', help="Don't download support for flatpak") parser.add_argument("--snap-channel", help="Channel to download snap from (optional)") parser.add_argument("--minimum", help="Use already installed fwupd version if at least this version") parser.add_argument("cab", help="CAB file or directory containing CAB files to automatically install") parser.add_argument('target', help='target file to create') args = parser.parse_args() return args def bytes_slicer(length, source): start = 0 stop = length while start < len(source): yield source[start:stop] start = stop stop += length def generate_installer (directory, target): asset_base = os.path.join (os.path.dirname(os.path.realpath(__file__)), "assets") #header shutil.copy (os.path.join (asset_base, "header.py"), target) #zip file buffer = io.BytesIO() archive = zipfile.ZipFile(buffer, "a") for root, dirs, files in os.walk (directory): for f in files: source = os.path.join(root, f) archive_fname = source.split (directory) [1] archive.write(source, archive_fname) if 'DEBUG' in os.environ: print (archive.namelist()) archive.close() with open (target, 'ab') as bytes_out: encoded = b64encode(buffer.getvalue()) for section in bytes_slicer(64, encoded): bytes_out.write(TAG) bytes_out.write(section) bytes_out.write(b'\n') def download_snap (directory, channel): cmd = ['snap', 'download', 'fwupd'] if channel is not None: cmd += ['--channel', channel] if 'DEBUG' in os.environ: print(cmd) subprocess.run (cmd, cwd=directory, check=True) for f in os.listdir (directory): # the signatures associated with the snap if f.endswith(".assert"): shutil.move (os.path.join(directory, f), os.path.join(directory, 'fwupd.assert')) # the snap binary itself elif f.endswith(".snap"): shutil.move (os.path.join(directory, f), os.path.join(directory, 'fwupd.snap')) def download_cab_file (directory, uri): cmd = ['wget', uri] if 'DEBUG' in os.environ: print(cmd) subprocess.run (cmd, cwd=directory, check=True) def download_flatpak (directory): dep = 'org.freedesktop.fwupd' flatpak_dir = os.path.join(os.getenv('HOME'),'.local', 'share', 'flatpak') verbose = 'DEBUG' in os.environ #check if we have installed locally already or not if not os.path.exists (os.path.join (flatpak_dir, 'app', dep)): # install into local user's repo cmd = ['flatpak', 'install', '--user', 'https://www.flathub.org/repo/appstream/org.freedesktop.fwupd.flatpakref', '--no-deps', '-y'] if verbose: print(cmd) subprocess.run (cmd, cwd=directory, check=True) # generate a bundle repo = os.path.join(flatpak_dir, 'repo') cmd = ['flatpak', 'build-bundle', repo, 'fwupd.flatpak', dep, 'stable'] if verbose: print(cmd) subprocess.run (cmd, cwd=directory, check=True) if __name__ == '__main__': args = parse_args() if not args.cab.startswith("http"): local = args.cab with tempfile.TemporaryDirectory (prefix='fwupd') as directory: if local: if not os.path.exists (local): error ("%s doesn't exist" % local) if not os.path.isdir(local): shutil.copy (local, directory) else: for root, dirs, files in os.walk(local): for f in files: shutil.copy (os.path.join(root, f), directory) else: download_cab_file (directory, args.cab) if not args.disable_snap_download: download_snap (directory, args.snap_channel) if not args.disable_flatpak_download: download_flatpak (directory) if args.minimum: with open(os.path.join(directory, "minimum"), "w") as wfd: wfd.write(args.minimum) generate_installer (directory, args.target) fwupd-1.2.14/contrib/vscode/000077500000000000000000000000001402665037500156575ustar00rootroot00000000000000fwupd-1.2.14/contrib/vscode/README.md000066400000000000000000000030071402665037500171360ustar00rootroot00000000000000# Using Visual Studio Code to debug This directory contains a collection of scripts and assets to make debugging using Visual Studio Code easier. ## Preparing First install the following applications locally: * GDB Server * GDB * Visual Studio Code In Visual Studio code, visit the extension store and install *C/C++* which is an extension provided by Microsoft. Configure Visual Studio code to open the folder representing the root of the fwupd checkout. ## Building Run `./contrib/debugging/build.sh` to build fwupd with all default options and create helper scripts pre-configured for debugger use. The application will be placed into `./dist` and helper scripts will be created for `fwupdtool`, `fwupdmgr`, and `fwupd`. ## Running To run any of the applications, execute the appropriate helper script in `./dist`. ## Debugging To debug any of the applications, launch the helper script with the environment variable `DEBUG` set. For example to debug `fwupdtool get-devices` the command to launch would be: ``` sudo DEBUG=1 ./dist/fwupdtool.sh get-devices ``` This will configure `gdbserver` to listen on a local port waiting for a debugger to connect. ## Using Visual Studio code During build time a set of launch targets will have been created for use with Visual Studio Code. Press the debugging button on the left and 3 targets will be listed at the top. * gdbserver (fwupdtool) * gdbserver (fwupd) * gdbserver (fwupdmgr) Select the appropriate target and press the green arrow to connect to `gdbserver` and start debugging. fwupd-1.2.14/contrib/vscode/build.sh000077500000000000000000000015731402665037500173230ustar00rootroot00000000000000#!/bin/sh # Copyright (C) 2018 Dell, Inc. SOURCE=$(dirname $0) ROOT=$1 if [ -z "$ROOT" ]; then ROOT=`pwd` fi # build in tree rm -rf build ${ROOT}/dist meson build --prefix=${ROOT}/dist -Dsystemd=false -Dudevdir=${ROOT}/dist ninja -C build install #create helper scripts TEMPLATE=${SOURCE}/launcher.sh sed "s,#ROOT#,${ROOT},; s,#EXECUTABLE#,libexec/fwupd/fwupd," \ ${TEMPLATE} > ${ROOT}/dist/fwupd.sh sed "s,#ROOT#,${ROOT},; s,#EXECUTABLE#,libexec/fwupd/fwupdtool," \ ${TEMPLATE} > ${ROOT}/dist/fwupdtool.sh sed "s,#ROOT#,${ROOT},; s,#EXECUTABLE#,bin/fwupdmgr," \ ${TEMPLATE} > ${ROOT}/dist/fwupdmgr.sh chmod +x ${ROOT}/dist/*.sh #create debugging targets TARGET=${ROOT}/.vscode mkdir -p ${TARGET} if [ -f ${TARGET}/launch.json ]; then echo "${TARGET}/launch.json already exists, not overwriting" else cp ${SOURCE}/launch.json ${TARGET} fi fwupd-1.2.14/contrib/vscode/launch.json000066400000000000000000000060021402665037500200220ustar00rootroot00000000000000{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "gdbserver (fwupdtool)", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/dist/libexec/fwupd/fwupdtool", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "miDebuggerServerAddress": "localhost:9091", "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] }, { "name": "gdbserver (fwupd)", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/dist/libexec/fwupd/fwupd", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "miDebuggerServerAddress": "localhost:9091", "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] }, { "name": "gdbserver (fwupdmgr)", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/dist/bin/fwupdmgr", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "miDebuggerServerAddress": "localhost:9091", "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] }, ] } fwupd-1.2.14/contrib/vscode/launcher.sh000077500000000000000000000003351402665037500200200ustar00rootroot00000000000000#!/bin/sh export ROOT=#ROOT# export FWUPD_LOCALSTATEDIR=${ROOT}/dist export FWUPD_SYSCONFDIR=${ROOT}/dist/etc if [ -n "${DEBUG}" ]; then DEBUG="gdbserver localhost:9091" fi ${DEBUG} ${ROOT}/dist/#EXECUTABLE# "$@" fwupd-1.2.14/data/000077500000000000000000000000001402665037500136455ustar00rootroot00000000000000fwupd-1.2.14/data/90-fwupd-devices.rules000066400000000000000000000027001402665037500177130ustar00rootroot00000000000000######################################################################## # Copyright (C) 2015 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # VIA USB 3.0 VL811 Hub SUBSYSTEM=="usb", DRIVER=="hub", ATTRS{idVendor}=="2109", ATTRS{idProduct}=="0810", ENV{FWUPD_GUID}="adbb9034-b577-42c2-a661-1ee4f49ef64c", ENV{FWUPD_VENDOR}="VIA", ENV{FWUPD_MODEL}="USB 3.0 VL811 Hub" # VIA USB 3.0 VL811+ Hub SUBSYSTEM=="usb", DRIVER=="hub", ATTRS{idVendor}=="2109", ATTRS{idProduct}=="0811", ENV{FWUPD_GUID}="54f84d05-c917-4c50-8b35-44feabaaa323", ENV{FWUPD_VENDOR}="VIA", ENV{FWUPD_MODEL}="USB 3.0 VL811+ Hub" # VIA USB 3.0 VL812 Hub SUBSYSTEM=="usb", DRIVER=="hub", ATTRS{idVendor}=="2109", ATTRS{idProduct}=="0812", ENV{FWUPD_GUID}="cd0314ec-b80f-4d1a-a24f-c409183a8b2d", ENV{FWUPD_VENDOR}="VIA", ENV{FWUPD_MODEL}="USB 3.0 VL812 Hub" # VIA USB 3.0 VL812 B2 Hub SUBSYSTEM=="usb", DRIVER=="hub", ATTRS{idVendor}=="2109", ATTRS{idProduct}=="2812", ENV{FWUPD_GUID}="26470009-97a8-4028-867a-bbbac6ee7bf0", ENV{FWUPD_VENDOR}="VIA", ENV{FWUPD_MODEL}="USB 3.0 VL812 B2 Hub" ENV{FWUPD_GUID}=="*?", ENV{ID_MODEL}=="", IMPORT{builtin}="usb_id" ENV{FWUPD_GUID}=="*?", ENV{ID_MODEL_FROM_DATABASE}=="", IMPORT{builtin}="hwdb --subsystem=usb" # PCI cards with ROM SUBSYSTEM=="pci", TEST=="/sys$devpath/rom", ENV{FWUPD_GUID}="$attr{vendor}:$attr{device}" # NVMe hardware SUBSYSTEM=="nvme", ENV{ID_VENDOR_FROM_DATABASE}=="", IMPORT{builtin}="hwdb --subsystem=pci" fwupd-1.2.14/data/bash-completion/000077500000000000000000000000001402665037500167315ustar00rootroot00000000000000fwupd-1.2.14/data/bash-completion/fwupdagent000066400000000000000000000010671402665037500210240ustar00rootroot00000000000000_fwupdagent_cmd_list=( 'get-devices' ) _fwupdagent_opts=( '--verbose' ) _show_modifiers() { COMPREPLY+=( $(compgen -W '${_fwupdagent_opts[@]}' -- "$cur") ) } _fwupdagent() { local cur prev command COMPREPLY=() cur=`_get_cword` prev=${COMP_WORDS[COMP_CWORD-1]} command=${COMP_WORDS[1]} case $command in *) #find first command if [[ ${COMP_CWORD} = 1 ]]; then COMPREPLY=( $(compgen -W '${_fwupdagent_cmd_list[@]}' -- "$cur") ) #modifiers for all commands else _show_modifiers fi ;; esac return 0 } complete -F _fwupdagent fwupdagent fwupd-1.2.14/data/bash-completion/fwupdmgr000066400000000000000000000060361402665037500205140ustar00rootroot00000000000000_fwupdmgr_cmd_list=( 'activate' 'clear-history' 'clear-offline' 'clear-results' 'disable-remote' 'downgrade' 'enable-remote' 'get-approved-firmware' 'get-details' 'get-devices' 'get-history' 'get-releases' 'get-remotes' 'get-results' 'get-topology' 'get-updates' 'install' 'modify-config' 'modify-remote' 'refresh' 'report-history' 'set-approved-firmware' 'unlock' 'update' 'verify' 'verify-update' '--version' ) _fwupdmgr_opts=( '--verbose' '--offline' '--allow-reinstall' '--allow-older' '--force' '--assume-yes' '--no-history' '--no-unreported-check' '--no-metadata-check' '--no-reboot-check' '--show-all-devices' '--sign' ) _show_modifiers() { COMPREPLY+=( $(compgen -W '${_fwupdmgr_opts[@]}' -- "$cur") ) } _show_device_ids() { local description OLDIFS=$IFS IFS=$'\n' description="$(command fwupdmgr get-devices | command awk '!/DeviceId/ { line = $0 }; /DeviceId/ { print $2 " {" line "}"}')" COMPREPLY+=( $(compgen -W "${description}" -- "$cur") ) IFS=$OLDIFS } _show_remotes() { local remotes remotes="$(command fwupdmgr get-remotes | command awk '/Remote ID/ { print $3 }')" COMPREPLY+=( $(compgen -W "${remotes}" -- "$cur") ) } _fwupdmgr() { local cur prev command COMPREPLY=() cur=`_get_cword` prev=${COMP_WORDS[COMP_CWORD-1]} command=${COMP_WORDS[1]} case $command in activate|clear-results|downgrade|get-releases|get-results|unlock|verify|verify-update) if [[ "$prev" = "$command" ]]; then _show_device_ids else _show_modifiers fi ;; get-details) #browse for file if [[ "$prev" = "$command" ]]; then _filedir #modifiers else _show_modifiers fi ;; install) #find files if [[ "$prev" = "$command" ]]; then _filedir #device ID or modifiers elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; then _show_device_ids _show_modifiers #modifiers else _show_modifiers fi ;; modify-remote) #find remotes if [[ "$prev" = "$command" ]]; then _show_remotes #add key elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; then local keys keys="$(command fwupdmgr get-remotes | command awk -v pattern="Remote ID:.*${prev}$" '$0~pattern{show=1; next}/Remote/{show=0}{gsub(/:.*/,"")}show')" COMPREPLY+=( $(compgen -W "${keys}" -- "$cur") ) #modifiers else _show_modifiers fi ;; enable-remote) #find remotes if [[ "$prev" = "$command" ]]; then _show_remotes #modifiers else _show_modifiers fi ;; disable-remote) #find remotes if [[ "$prev" = "$command" ]]; then _show_remotes #modifiers else _show_modifiers fi ;; refresh) #find first file if [[ "$prev" = "$command" ]]; then _filedir #find second file elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; then _filedir #find remote ID elif [[ "$prev" = "${COMP_WORDS[3]}" ]]; then _show_remotes else _show_modifiers fi ;; *) #find first command if [[ ${COMP_CWORD} = 1 ]]; then COMPREPLY=( $(compgen -W '${_fwupdmgr_cmd_list[@]}' -- "$cur") ) #modifiers for all commands else _show_modifiers fi ;; esac return 0 } complete -F _fwupdmgr fwupdmgr fwupd-1.2.14/data/bash-completion/fwupdtool.in000066400000000000000000000034001402665037500213010ustar00rootroot00000000000000_fwupdtool_cmd_list=( 'activate' 'build-firmware' 'get-updates' 'get-details' 'get-devices' 'get-history' 'get-plugins' 'get-topology' 'hwids' 'update' 'install' 'install-blob' 'monitor' 'self-sign' 'smbios-dump' 'attach' 'detach' 'verify-update' 'watch' ) _fwupdtool_opts=( '--verbose' '--enable-json-state' '--allow-reinstall' '--allow-older' '--force' '--show-all-devices' '--plugin-whitelist' '--prepare' '--cleanup' ) _show_plugins() { local plugins plugins="$(command @libexecdir@/fwupdtool get-plugins 2>/dev/null)" COMPREPLY+=( $(compgen -W "${plugins}" -- "$cur") ) } _show_modifiers() { COMPREPLY+=( $(compgen -W '${_fwupdtool_opts[@]}' -- "$cur") ) } _fwupdtool() { local cur prev command COMPREPLY=() cur=`_get_cword` prev=${COMP_WORDS[COMP_CWORD-1]} command=${COMP_WORDS[1]} case $prev in --plugin-whitelist) _show_plugins return 0 ;; esac case $command in get-details|install|install-blob) #find files if [[ "$prev" = "$command" ]]; then _filedir #modifiers else _show_modifiers fi ;; attach|detach|activate|verify-update) if [[ "$prev" = "$command" ]]; then _show_device_ids #modifiers else _show_modifiers fi ;; build-firmware) #file in if [[ "$prev" = "$command" ]]; then _filedir #file out elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; then _filedir #script elif [[ "$prev" = "${COMP_WORDS[3]}" ]]; then _filedir #output elif [[ "$prev" = "${COMP_WORDS[4]}" ]]; then _filedir else _show_modifiers fi ;; *) #find first command if [[ ${COMP_CWORD} = 1 ]]; then COMPREPLY=( $(compgen -W '${_fwupdtool_cmd_list[@]}' -- "$cur") ) #modifiers for all commands else _show_modifiers fi ;; esac return 0 } complete -F _fwupdtool fwupdtool fwupd-1.2.14/data/bash-completion/meson.build000066400000000000000000000011601402665037500210710ustar00rootroot00000000000000if bashcomp.found() tgt = bashcomp.get_pkgconfig_variable('completionsdir', define_variable: [ 'prefix', prefix ], ) if get_option('daemon') install_data(['fwupdmgr'], install_dir : tgt, ) endif if get_option('agent') install_data(['fwupdagent'], install_dir : tgt, ) endif # replace @libexecdir@ fwupdtool_path = join_paths(libexecdir, 'fwupd') con2 = configuration_data() con2.set('libexecdir', fwupdtool_path) configure_file( input : 'fwupdtool.in', output : 'fwupdtool', configuration : con2, install: true, install_dir: tgt) endif fwupd-1.2.14/data/builder/000077500000000000000000000000001402665037500152735ustar00rootroot00000000000000fwupd-1.2.14/data/builder/README.md000066400000000000000000000045761402665037500165660ustar00rootroot00000000000000Building Firmware ================= Most of the time when you’re distributing firmware you have permission from the OEM or ODM to redistribute the non-free parts of the system firmware, e.g. Dell can re-distribute the proprietary Intel Management Engine as part as the firmware capsule that gets flashed onto the hardware. In some cases that’s not possible, for example for smaller vendors or people selling OpenHardware. For reasons (IFD, FMAP and CBFS…) you need to actually build the target firmware on the system you’re deploying onto, where build means executing random low-level tools to push random blobs of specific sizes into specific unnecessarily complex partition formats rather than actually compiling .c into executable code. The solution of a manually updated interactive bash script isn’t awesome from a user-experience or security point of view. The other things that might be required is a way to `dd` a few bytes of randomness into the target image at a specific offset and also to copy the old network MAC address into the new firmware. The firmware-builder functionality allows you to ship an archive (typically in `.tar` format, as the `.cab` file will be compressed already) within the `.cab` file as the main “release”. Within the `.tar` archive will be a startup.sh file and all the utilities or scripts needed to run the build operation, statically linked if required. At firmware deploy time fwupd will explode the tar file into a newly-created temp directory, create a bubblewrap container which has no network and limited file-system access and then run the startup.sh script. Once complete, fwupd will copy out just the `firmware.bin` file and then destroy the bubblewrap container and the temporary directory. This is the directory that is available to the bubble-wrap confined script. If, for instance, a plugin needs the old system firmware blob (for a bsdiff) then the plugin can write to this directory and the startup.sh script will be able to access it as the chroot-ed `/boot`. Firmware `.cab` files using this functionality should list the `.tar` file: and also should include the name of the script to run as additional metadata: startup.sh firmware.bin fwupd-1.2.14/data/builder/meson.build000066400000000000000000000001411402665037500174310ustar00rootroot00000000000000install_data('README.md', install_dir : join_paths(localstatedir, 'lib', 'fwupd', 'builder') ) fwupd-1.2.14/data/daemon.conf000066400000000000000000000012611402665037500157570ustar00rootroot00000000000000[fwupd] # Allow blacklisting specific devices by their GUID # Uses semicolons as delimiter BlacklistDevices= # Allow blacklisting specific plugins # Uses semicolons as delimiter BlacklistPlugins=test # Maximum archive size that can be loaded in Mb, with 0 for the default ArchiveSizeMax=0 # Idle time in seconds to shut down the daemon -- note some plugins might # inhibit the auto-shutdown, for instance thunderbolt. # # A value of 0 specifies 'never' IdleTimeout=7200 # Comma separated list of domains to log in verbose mode # If unset, no domains # If set to FuValue, FuValue domain (same as --domain-verbose=FuValue) # If set to *, all domains (same as --verbose) VerboseDomains= fwupd-1.2.14/data/fwupd-offline-update.service.in000066400000000000000000000006411402665037500216620ustar00rootroot00000000000000[Unit] Description=Updates device firmware whilst offline Documentation=man:fwupdmgr ConditionPathExists=@localstatedir@/lib/fwupd/pending.db DefaultDependencies=false Requires=sysinit.target dbus.socket After=sysinit.target system-update-pre.target dbus.socket systemd-journald.socket Before=shutdown.target system-update.target [Service] Type=oneshot ExecStart=@libexecdir@/fwupd/fwupdoffline FailureAction=reboot fwupd-1.2.14/data/fwupd.service.in000066400000000000000000000006751402665037500167710ustar00rootroot00000000000000[Unit] Description=Firmware update daemon Documentation=https://fwupd.org/ After=dbus.service Before=display-manager.service [Service] Type=dbus BusName=org.freedesktop.fwupd ExecStart=@libexecdir@/fwupd/fwupd StateDirectory=@package_name@ CacheDirectory=@package_name@ ConfigurationDirectory=@package_name@ PrivateTmp=yes ProtectHome=yes ProtectSystem=full RestrictAddressFamilies=AF_NETLINK AF_UNIX SystemCallFilter=~@mount @dynamic_options@ fwupd-1.2.14/data/fwupd.shutdown.in000077500000000000000000000002751402665037500172030ustar00rootroot00000000000000#!/bin/sh # no history database exists [ -f @localstatedir@/lib/fwupd/pending.db ] || exit 0 # activate firmware when we have a read-only filesysten @libexecdir@/fwupd/fwupdtool activate fwupd-1.2.14/data/installed-tests/000077500000000000000000000000001402665037500167645ustar00rootroot00000000000000fwupd-1.2.14/data/installed-tests/README.md000066400000000000000000000054751402665037500202560ustar00rootroot00000000000000Installed tests ========= A test suite that can be used to interact with a fake device is installed when configured with `-Ddaemon=true` and `-Dtests=true`. By default this test suite is disabled. Enabling ======= To enable the test suite: 1. Modify `/etc/fwupd/daemon.conf` to remove the `test` plugin from `BlacklistPlugins` ``` # sed "s,^Enabled=false,Enabled=true," -i /etc/fwupd/remotes.d/fwupd-tests.conf ``` 2. Enable the `fwupd-tests` remote for local CAB files. ``` # fwupdmgr enable-remote fwupd-tests ``` Using test suite ===== When the daemon is started with the test suite enabled a fake webcam device will be created with a pending update. ``` Integrated Webcam™ DeviceId: 08d460be0f1f9f128413f816022a6439e0078018 Guid: b585990a-003e-5270-89d5-3705a17f9a43 Summary: A fake webcam Plugin: test Flags: updatable|supported|registered Vendor: ACME Corp. VendorId: USB:0x046D Version: 1.2.2 VersionLowest: 1.2.0 VersionBootloader: 0.1.2 Icon: preferences-desktop-keyboard Created: 2018-11-29 ``` ## Upgrading This can be upgraded to a firmware version `1.2.4` by using `fwupdmgr update` or any fwupd frontend. ``` $ fwupdmgr get-updates Integrated Webcam™ has firmware updates: GUID: b585990a-003e-5270-89d5-3705a17f9a43 ID: fakedevice.firmware Update Version: 1.2.4 Update Name: FakeDevice Firmware Update Summary: Firmware for the ACME Corp Integrated Webcam Update Remote ID: fwupd-tests Update Checksum: SHA1(fc0aabcf98bf3546c91270f2941f0acd0395dd79) Update Location: ./fakedevice124.cab Update Description: Fixes another bug with the flux capacitor to prevent time going backwards. $ fwupdmgr update Decompressing… [***************************************] Authenticating… [***************************************] Updating Integrated Webcam™… ] Verifying… [***************************************] Less than one minute remaining… ``` ## Downgrading It can also be downgraded to firmware version `1.2.3`. ``` $ fwupdmgr downgrade Choose a device: 0. Cancel 1. 08d460be0f1f9f128413f816022a6439e0078018 (Integrated Webcam™) 2. 8a21cacfb0a8d2b30c5ee9290eb71db021619f8b (XPS 13 9370 System Firmware) 3. d10c5f0ed12c6dc773f596b8ac51f8ace4355380 (XPS 13 9370 Thunderbolt Controller) 1 Decompressing… [***************************************] Authenticating… [***************************************] Downgrading Integrated Webcam™… \ ] Verifying… [***************************************] Less than one minute remaining… ``` fwupd-1.2.14/data/installed-tests/fakedevice123.bin000066400000000000000000000000211402665037500217630ustar00rootroot00000000000000fakedevice123 -n fwupd-1.2.14/data/installed-tests/fakedevice123.bin.asc000066400000000000000000000007521402665037500225430ustar00rootroot00000000000000-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.14 (GNU/Linux) iQEcBAABAgAGBQJZQqynAAoJEEim2A5FOLrCVcsH/3Vn56wSeRCol0rOeXvoupg2 qpTAmqUvlubv2vX1IDbcL/lHIIEAHAlN/4LRHUh+Om0T7bMKX1uSfmcgCyUTBxl0 fm3TfXRhybi9VtZ5ZpwWxGsFsCNC9eOU0i8tB1zp9e9KjDPiYnluFkTRQ+Aw3u1u tKBMTk6Z+VQlIUFrsveFYmPMGDkvn8AWbJCz6E4jc8can/lP/9djSi91mCqtEq/j YTBz4OwfU80MRrSgoxykHgcB1RiT43ywfKlpHQzcO+rqCV7rv7LkXIEzBdWRZstk XmboCnEKuMxtr+vXlGqU4n+upQkYur3Vs+07ut1OewQnJT3eeZbAH0mr42MVf7c= =MQJe -----END PGP SIGNATURE----- fwupd-1.2.14/data/installed-tests/fakedevice123.metainfo.xml000066400000000000000000000017571402665037500236350ustar00rootroot00000000000000 fakedevice.firmware FakeDevice Firmware Firmware for the ACME Corp Integrated Webcam

Updating the firmware on your webcam device improves performance and adds new features.

b585990a-003e-5270-89d5-3705a17f9a43 http://www.acme.com/ CC0-1.0 GPL-2.0+ ACME Corp

Fixes a bug with the flux capacitor to avoid year 2038 overflow.

fwupd-1.2.14/data/installed-tests/fakedevice124.bin000066400000000000000000000000211402665037500217640ustar00rootroot00000000000000fakedevice124 -n fwupd-1.2.14/data/installed-tests/fakedevice124.bin.asc000066400000000000000000000007521402665037500225440ustar00rootroot00000000000000-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.14 (GNU/Linux) iQEcBAABAgAGBQJZQqy9AAoJEEim2A5FOLrCRLEH/27k0IfUtfGS8T5CPTvwW8kF Cf6EIzw+2HgjbxLdeMNHwiHCBdIR58z44O1I9Xy6gY1vF3H4kKft6oBAUFDH0Ja5 YpQHXMZVSNdnwdg57cyC67kLOycHTSDlLXKB74tU3R4J8xntA1cY+DSYmCs2uAjq 3T3ExfjrX6PGbRhbNr8vBUQckCxcGvEZNOws2081mTosEQNpIxFyJ2tbbKLR60d0 5O/UDjNEYfUFCGy7MycXePEIOR+rO6KuEQ3vjJnv80UKE8msFxJTM1iKwct+B2HI JNecCsx14BGDXCiE0Xc0heunfWiBHmNS2lymrHsU2Z82VrFqP0obD2cm64PBf0Y= =Wsq/ -----END PGP SIGNATURE----- fwupd-1.2.14/data/installed-tests/fakedevice124.metainfo.xml000066400000000000000000000017711402665037500236320ustar00rootroot00000000000000 fakedevice.firmware FakeDevice Firmware Firmware for the ACME Corp Integrated Webcam

Updating the firmware on your webcam device improves performance and adds new features.

b585990a-003e-5270-89d5-3705a17f9a43 http://www.acme.com/ CC0-1.0 GPL-2.0+ ACME Corp

Fixes another bug with the flux capacitor to prevent time going backwards.

fwupd-1.2.14/data/installed-tests/fwupd-tests.xml000066400000000000000000000105421402665037500217750ustar00rootroot00000000000000 fakedevice.firmware FakeDevice Firmware Firmware for the ACME Corp Integrated Webcam ACME Corp GPL-2.0+

Updating the firmware on your webcam device improves performance and adds new features.

http://www.acme.com/ 17 1163 ./fakedevice124.cab fc0aabcf98bf3546c91270f2941f0acd0395dd79 2b8546ba805ad10bf8a2e5ad539d53f303812ba5

Fixes another bug with the flux capacitor to prevent time going backwards.

17 1153 ./fakedevice123.cab bc3c32f42cf33fe5aade64f999417251fd8208d3 7998cd212721e068b2411135e1f90d0ad436d730

Fixes a bug with the flux capacitor to avoid year 2038 overflow.

b585990a-003e-5270-89d5-3705a17f9a43
com.hughski.ColorHug2.firmware ColorHug2 Firmware for the Hughski ColorHug2 Colorimeter Hughski Limited GPL-2.0+

Updating the firmware on your ColorHug2 device improves performance and adds new features.

http://www.hughski.com/ 16384 19592 hughski-colorhug2-2.0.7.cab 490be5c0b13ca4a3f169bf8bc682ba127b8f7b96 658851e6f27c4d87de19cd66b97b610d100efe09

This release fixes prevents the firmware returning an error when the remote SHA1 hash was never sent.

2082b5e0-7a64-478a-b1b2-e3404fab6dad
com.hughski.ColorHug.firmware ColorHug Firmware for the Hughski ColorHug Colorimeter Hughski Limited GPL-2.0+

Updating the firmware on your ColorHug device improves performance and adds new features.

http://www.hughski.com/ 16384 18054 hughski-colorhug-1.2.6.cab 570a4259af0c7670f3883e84d2f4e6ff7de572c2 111784ffadfd5dd43f05655b266b5142230195b6

This release fixes prevents the firmware returning an error when the remote SHA1 hash was never sent.

40338ceb-b966-4eae-adae-9c32edfcc484
fwupd-1.2.14/data/installed-tests/fwupdmgr.sh000077500000000000000000000040701402665037500211570ustar00rootroot00000000000000#!/bin/bash exec 2>&1 dirname=`dirname $0` # --- echo "Getting the list of remotes..." fwupdmgr get-remotes rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Enabling fwupd-tests remote..." fwupdmgr enable-remote fwupd-tests rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Update the device hash database..." fwupdmgr verify-update rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Getting devices (should be one)..." fwupdmgr get-devices --no-unreported-check rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Testing the verification of firmware..." fwupdmgr verify rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Getting updates (should be one)..." fwupdmgr --no-unreported-check --no-metadata-check get-updates rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Installing test firmware..." fwupdmgr install ${dirname}/fakedevice124.cab rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Getting updates (should be none)..." fwupdmgr --no-unreported-check --no-metadata-check get-updates rc=$?; if [[ $rc != 2 ]]; then exit $rc; fi # --- echo "Testing the verification of firmware (again)..." fwupdmgr verify rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Downgrading to older release (requires network access)" fwupdmgr downgrade rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Downgrading to older release (should be none)" fwupdmgr downgrade rc=$?; if [[ $rc != 2 ]]; then exit $rc; fi # --- echo "Updating all devices to latest release (requires network access)" fwupdmgr --no-unreported-check --no-metadata-check --no-reboot-check update rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Getting updates (should be none)..." fwupdmgr --no-unreported-check --no-metadata-check get-updates rc=$?; if [[ $rc != 2 ]]; then exit $rc; fi # --- echo "Refreshing from the LVFS (requires network access)..." fwupdmgr refresh rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # --- echo "Flashing actual devices (requires specific hardware)" ${dirname}/hardware.py rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi # success! exit 0 fwupd-1.2.14/data/installed-tests/fwupdmgr.test.in000066400000000000000000000001011402665037500221150ustar00rootroot00000000000000[Test] Type=session Exec=sh -c "@installedtestsdir@/fwupdmgr.sh" fwupd-1.2.14/data/installed-tests/hardware.py000077500000000000000000000160411402665037500211400ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=wrong-import-position,too-many-locals,unused-argument,wrong-import-order # # Copyright (C) 2017 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import gi import os import requests import time gi.require_version('Fwupd', '2.0') from gi.repository import Fwupd from gi.repository import Gio from gi.repository import GLib def _get_by_device_guid(client, guid): cancellable = Gio.Cancellable.new() devices = client.get_devices(cancellable) for d in devices: if d.has_guid(guid): return d return None def _get_cache_file(fn): cachedir = os.path.expanduser('~/.cache/fwupdmgr') if not os.path.exists(cachedir): os.makedirs(cachedir) cachefn = os.path.join(cachedir, fn) if not os.path.exists(cachefn): url = 'https://fwupd.org/downloads/' + fn print("Downloading", url) r = requests.get(url) r.raise_for_status() f = open(cachefn, 'wb') f.write(r.content) f.close() return cachefn class Test: def __init__(self, name, guid, has_runtime=True): self.files = [] self.name = name self.guid = guid self.has_runtime = has_runtime def run(self): # connect to fwupd client = Fwupd.Client.new() dev = _get_by_device_guid(client, self.guid) if not dev: print("Skipping hardware test, no", self.name, "attached") return print(dev.get_name(), "is currently version", dev.get_version()) # apply each file for fn, ver in self.files: fn_cache = _get_cache_file(fn) if dev.get_version() == ver: flags = Fwupd.InstallFlags.ALLOW_REINSTALL else: flags = Fwupd.InstallFlags.ALLOW_OLDER cancellable = Gio.Cancellable.new() print("Installing", fn_cache) client.install(dev.get_id(), fn_cache, flags, cancellable) # verify version if self.has_runtime: dev = _get_by_device_guid(client, self.guid) if not dev: raise GLib.Error('Device did not come back: ' + self.name) if not dev.get_version(): raise GLib.Error('No version set after flash for: ' + self.name) if dev.get_version() != ver: raise GLib.Error('Got: ' + dev.get_version() + ', expected: ' + ver) # FIXME: wait for device to settle? time.sleep(2) def add_file(self, fn, ver): self.files.append((fn, ver)) if __name__ == '__main__': tests = [] # DFU A3BU XPLAINED Mouse test = Test('DfuXmegaA3BU-Xplained', '80478b9a-3643-5e47-ab0f-ed28abe1019d') test.add_file('f5bbeaba1037dce31dd12f349e8148ae35f98b61-a3bu-xplained123.cab', '1.23') test.add_file('24d838541efe0340bf67e1cc5a9b95526e4d3702-a3bu-xplained124.cab', '1.24') tests.append(test) # DFU AT90USBKEY Mouse test = Test('DfuAT90USBKEY', 'c1874c52-5f6a-5864-926d-ea84bcdc82ea') test.add_file('b6bef375597e848971f230cf992c9740f7bf5b92-at90usbkey123.cab', '1.23') test.add_file('47807fd4a94a4d5514ac6bf7a73038e00ed63225-at90usbkey124.cab', '1.24') tests.append(test) # Logitech K780 Keyboard test = Test('LogitechMPK01', '3932ba15-2bbe-5bbb-817e-6c74e7088509') test.add_file('d81a81e13952e871ca2eb86cba7e66199e576a38-Logitech-K780-MPK01.02_B0021.cab', 'MPK01.02_B0021') test.add_file('b0dffe84c6d3681e7ae5f27509781bc1cf924dd7-Logitech-K780-MPK01.03_B0024.cab', 'MPK01.03_B0024') tests.append(test) # Hughski ColorHug (a special variant) using 'dfu' test = Test('ColorHugDFU', 'dfbaaded-754b-5214-a5f2-46aa3331e8ce') test.add_file('77b315dcaa7edc1d5fbb77016b94d8a0c0133838-fakedevice01_dfu.cab', '0.1') test.add_file('8bc3afd07a0af3baaab8b19893791dd3972e8305-fakedevice02_dfu.cab', '0.2') tests.append(test) # Hughski ColorHug2 using 'colorhug' test = Test('ColorHug2', '2082b5e0-7a64-478a-b1b2-e3404fab6dad') test.add_file('170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab', '2.0.6') test.add_file('0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab', '2.0.7') tests.append(test) # Logitech Unifying Receiver (RQR12) using 'unifying' test = Test('UnifyingRQR12', '9d131a0c-a606-580f-8eda-80587250b8d6') test.add_file('6e5ab5961ec4c577bff198ebb465106e979cf686-Logitech-Unifying-RQR12.05_B0028.cab', 'RQR12.05_B0028') test.add_file('938fec082652c603a1cdafde7cd25d76baadc70d-Logitech-Unifying-RQR12.07_B0029.cab', 'RQR12.07_B0029') tests.append(test) # Logitech Unifying Receiver (RQR24) using 'unifying' test = Test('UnifyingRQR24', 'cc4cbfa9-bf9d-540b-b92b-172ce31013c1') test.add_file('82b90b2614a9a4d0aced1ab8a4a99e228c95585c-Logitech-Unifying-RQ024.03_B0027.cab', 'RQR24.03_B0027') test.add_file('4511b9b0d123bdbe8a2007233318ab215a59dfe6-Logitech-Unifying-RQR24.05_B0029.cab', 'RQR24.05_B0029') tests.append(test) # Jabra Speak 510 test = Test('JabraSpeak510', '443b9b32-7603-5c3a-bb30-291a7d8d6dbd') test.add_file('45f88c50e79cfd30b6599df463463578d52f2fe9-Jabra-SPEAK_510-2.10.cab', '2.10') test.add_file('c0523a98ef72508b5c7ddd687418b915ad5f4eb9-Jabra-SPEAK_510-2.14.cab', '2.14') tests.append(test) # Jabra Speak 410 test = Test('JabraSpeak410', '1764c519-4723-5514-baf9-3b42970de487') test.add_file('eab97d7e745e372e435dbd76404c3929730ac082-Jabra-SPEAK_410-1.8.cab', '1.8') test.add_file('50a03efc5df333a948e159854ea40e1a3786c34c-Jabra-SPEAK_410-1.11.cab', '1.11') tests.append(test) # Jabra Speak 710 test = Test('JabraSpeak710', '0c503ad9-4969-5668-81e5-a3748682fc16') test.add_file('d2910cdbc45cf172767d05e60d9e39a07a10d242-Jabra-SPEAK_710-1.10.cab', '1.10') test.add_file('a5c627ae42de4e5c3ae3df28977f480624f96f66-Jabra-SPEAK_710-1.28.cab', '1.28') tests.append(test) # 8Bitdo SFC30 Gamepad test = Test('8BitdoSFC30', 'a7fcfbaf-e9e8-59f4-920d-7691dc6c8699') test.add_file('fe066b57c69265f4cce8a999a5f8ab90d1c13b24-8Bitdo-SFC30_NES30_SFC30_SNES30-4.01.cab', '4.01') tests.append(test) # 8Bitdo NES30Pro Gamepad test = Test('8BitdoNES30Pro', 'c6566b1b-0c6e-5d2e-9376-78c23ab57bf2') test.add_file('1cb9a0277f536ecd81ca1cea6fd80d60cdbbdcd8-8Bitdo-SFC30PRO_NES30PRO-4.01.cab', '4.01') tests.append(test) # 8Bitdo SF30 Pro Gamepad test = Test('8BitdoSF30Pro', '269b3121-097b-50d8-b9ba-d1f64f9cd241') test.add_file('3d3a65ee2e8581647fb09d752fa7e21ee1566481-8Bitdo-SF30_Pro-SN30_Pro-1.26.cab', '1.26') tests.append(test) # AIAIAI H05 test = Test('AIAIAI-H05', '7e8318e1-27ae-55e4-a7a7-a35eff60e9bf', has_runtime=False) test.add_file('84279d6bab52262080531acac701523604f3e649-AIAIAI-H05-1.6.cab', '1.6') tests.append(test) # run each test rc = 0 for test in tests: try: test.run() except GLib.Error as e: print(str(e)) rc = 1 except requests.exceptions.HTTPError as e: print(str(e)) rc = 1 fwupd-1.2.14/data/installed-tests/meson.build000066400000000000000000000024571402665037500211360ustar00rootroot00000000000000con2 = configuration_data() con2.set('installedtestsdir', join_paths(datadir, 'installed-tests', 'fwupd')) con2.set('bindir', bindir) configure_file( input : 'fwupdmgr.test.in', output : 'fwupdmgr.test', configuration : con2, install: true, install_dir: join_paths('share', 'installed-tests', 'fwupd'), ) install_data([ 'fwupdmgr.sh', 'fwupd-tests.xml', 'hardware.py', ], install_dir : 'share/installed-tests/fwupd', ) custom_target('installed-cab123', input : [ 'fakedevice123.bin', 'fakedevice123.bin.asc', 'fakedevice123.metainfo.xml', ], output : 'fakedevice123.cab', command : [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], install: true, install_dir: join_paths('share', 'installed-tests', 'fwupd'), ) custom_target('installed-cab124', input : [ 'fakedevice124.bin', 'fakedevice124.bin.asc', 'fakedevice124.metainfo.xml', ], output : 'fakedevice124.cab', command : [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], install: true, install_dir: join_paths('share', 'installed-tests', 'fwupd'), ) # replace @installedtestsdir@ configure_file( input : 'remote.conf.in', output : 'fwupd-tests.conf', configuration : con2, install: true, install_dir: join_paths(sysconfdir, 'fwupd', 'remotes.d'), ) fwupd-1.2.14/data/installed-tests/remote.conf.in000066400000000000000000000004161402665037500215340ustar00rootroot00000000000000[fwupd Remote] # This is a local fwupd remote that is used only for installed tests # either from continuous integration or for fake devices from fwupd # frontends Enabled=false Title=fwupd test suite Keyring=none MetadataURI=file://@installedtestsdir@/fwupd-tests.xml fwupd-1.2.14/data/meson.build000066400000000000000000000052321402665037500160110ustar00rootroot00000000000000subdir('builder') subdir('pki') subdir('remotes.d') subdir('bash-completion') if get_option('tests') subdir('tests') endif if get_option('daemon') subdir('installed-tests') endif install_data(['daemon.conf'], install_dir : join_paths(sysconfdir, 'fwupd') ) install_data(['org.freedesktop.fwupd.metainfo.xml'], install_dir: join_paths(datadir, 'metainfo') ) install_data(['org.freedesktop.fwupd.svg'], install_dir : join_paths(datadir, 'icons', 'hicolor', 'scalable', 'apps') ) install_data(['org.freedesktop.fwupd.conf'], install_dir : join_paths(sysconfdir, 'dbus-1', 'system.d') ) if get_option('daemon') install_data(['90-fwupd-devices.rules'], install_dir : join_paths(udevdir, 'rules.d') ) endif if get_option('systemd') con2 = configuration_data() con2.set('libexecdir', libexecdir) con2.set('bindir', bindir) con2.set('datadir', datadir) con2.set('localstatedir', localstatedir) con2.set('package_name', meson.project_name()) rw_directories = [] if get_option('plugin_uefi') rw_directories += ['-/boot/efi', '-/efi/EFI', '-/boot/EFI'] endif dynamic_options = [] if systemd.version().version_compare('>= 232') dynamic_options += 'ProtectControlGroups=yes' dynamic_options += 'ProtectKernelModules=yes' endif if systemd.version().version_compare('>= 231') dynamic_options += 'RestrictRealtime=yes' # dynamic_options += 'MemoryDenyWriteExecute=yes' dynamic_options += ['ReadWritePaths=' + ' '.join(rw_directories)] else dynamic_options += ['ReadWriteDirectories=' + ' '.join(rw_directories)] endif con2.set('dynamic_options', '\n'.join(dynamic_options)) # replace @bindir@ configure_file( input : 'fwupd-offline-update.service.in', output : 'fwupd-offline-update.service', configuration : con2, install: true, install_dir: systemdunitdir, ) # replace @dynamic_options@ configure_file( input : 'fwupd.service.in', output : 'fwupd.service', configuration : con2, install: true, install_dir: systemdunitdir, ) # for activation configure_file( input : 'fwupd.shutdown.in', output : 'fwupd.shutdown', configuration : con2, install: true, install_dir: systemd.get_pkgconfig_variable('systemdshutdowndir'), ) endif if get_option('systemd') or get_option('elogind') con2 = configuration_data() con2.set('libexecdir', libexecdir) # replace @libexecdir@ configure_file( input : 'org.freedesktop.fwupd.service.in', output : 'org.freedesktop.fwupd.service', configuration : con2, install: true, install_dir: join_paths(datadir, 'dbus-1', 'system-services'), ) endif fwupd-1.2.14/data/org.freedesktop.fwupd.conf000066400000000000000000000020231402665037500207360ustar00rootroot00000000000000 fwupd-1.2.14/data/org.freedesktop.fwupd.metainfo.xml000066400000000000000000002017111402665037500224170ustar00rootroot00000000000000 org.freedesktop.fwupd CC0-1.0 LGPL-2.0+ fwupd Update device firmware on Linux

This project aims to make updating firmware on Linux automatic, safe and reliable. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the D-Bus interface directly.

The fwupd process is a simple daemon to allow session software to update device firmware on your local machine. It is designed for desktops, but this project is also usable on phones, tablets and on headless servers.

https://github.com/hughsie/fwupd/issues https://fwupd.org/ https://www.transifex.com/freedesktop/fwupd/ richard_at_hughsie.com fwupd moderate fwupdmgr

This release fixes the following bugs:

  • Add SBAT metadata to the fwupd EFI binary
  • Fix using a very new libxmlb with a very old fwupd daemon
  • Use the system provided flashrom on Fedora

This release fixes the following bugs:

  • Check version was updated by checking version
  • Correctly import PKCS-7 remote metadata
  • Decrease minimum battery requirement to 10%
  • Disable the battery percentage checks if UPower is unavailable
  • Do not do semver conversion in fu_common_vercmp()
  • Fix the DeviceID set by GetDetails
  • Force the synaptics-prometheus minor version from 0x02 to 0x01
  • Prevent Dell updates to occur via synaptics-mst
  • Read all releases and convert versions when comparing
  • Use the correct timeout for unifying IO channel writes
  • Validate that gpgme_op_verify_result() returned at least one signature

This release adds the following features:

  • Add support for the DW5821e/eSIM

This release fixes the following bugs:

  • Avoid checking for bolt support when not required
  • Correct HWID support in wacom-raw
  • Fix offset of vendor id of hidraw devices
  • Make loading vendor/product/serial strings non-fatal
  • Only check the vendor ID if the device has one set
  • Use more systemd directives for directories

This release adds the following features:

  • Add support for tpm2-tools 4.X
  • Allow specifying a firmware GUID to check any version exists

This release fixes the following bugs:

  • Actually write the new device path if different than before
  • Add a SynapticsMSTBoardID for a few Lenovo docks
  • Add the counterpart GUID for the DW5821e
  • Be more accepting when trying to recover a failed database migration
  • Do not ask the user to upload a report if ReportURI is not set
  • Do not segfault when trying to quit the downgrade selection
  • Fix a crash when stopping the fwupd service
  • Never show AppStream markup on the console
  • Relax the certificate time checks in the self tests for the legacy certificate
  • Reload metadata store when configuration changes
  • Remove replug flag after the device comes back from reboot
  • Update device_modified in sql database during updates
  • Work properly with ICL thunderbolt controller

This release adds the following features:

  • Add a new experimental plugin that supports libflashrom
  • Add a specific error code for the low battery case
  • Add support for 8bitdo USB Retro Receiver
  • Export new API to build objects from GVariant blobs
  • Show a warning when running in UEFI legacy mode
  • Support a UEFI quirk to disable the use of the UX capsule

This release fixes the following bugs:

  • Fix installing synaptics-prometheus config updates
  • Fix the supported list of Wacom tablets
  • Never set an empty device name
  • Prompt for reboot when unlocking on the command line if applicable
  • Show devices with an UpdateError in get-devices output
  • Support empty proxy server strings
  • Try harder to find duplicate UEFI boot entries

This release adds the following features:

  • Add support for Synaptics Prometheus fingerprint readers
  • Check if VersionFormat is ambiguous when adding devices
  • Check the daemon version is at least the client version
  • Export the version-format used by devices to clients
  • Set the version format for more device types

This release fixes the following bugs:

  • Allow using --force to trigger a duplicate offline update
  • Be smarter about existing installed fwupd when using standalone-installer
  • Correctly identify DFU firmware that starts at offset zero
  • Display the remote warning on the console in an easy-to-read way
  • Fix a libasan failure when reading a UEFI variable
  • Never guess the version format from the version string
  • Only use class-based instance IDs for quirk matching
  • Prompt the user to shutdown if requried when installing by ID
  • Reset the forced version during DFU attach and detach

This release adds the following features:

  • Allow the fwupdmgr tool to modify the daemon config

This release fixes the following bugs:

  • Correctly parse DFU interfaces with extra vendor-specific data
  • Do not report transient or invalid system failures
  • Fix problems with the version format checking for some updates

This release adds the following features:

  • Add a component categories to express the firmware type
  • Add support for 8BitDo M30
  • Add support for the not-child extension from Logitech
  • Shut down the daemon if the on-disk binary is replaced

This release fixes the following bugs:

  • Blacklist the synapticsmst plugin when using amdgpu
  • Correct ATA activation functionality to work for all vendors
  • Implement QMI PDC active config selection for modems
  • Make an error message clearer when there are no updates available
  • Match the old or new version number when setting NEEDS_REBOOT
  • More carefully check the output from tpm2_pcrlist
  • Recreate the history database if migration failed
  • Require AC power when updating Thunderbolt devices
  • Require --force to install a release with a different version format
  • Save history from firmware installed with fwupdtool

This release adds the following features:

  • Add a plugin to support modem hardware
  • Add support for delayed activation of docks and ATA devices
  • Add support for reading the SuperIO device checksum and writing to e-flash
  • Add the fwupdagent binary for use in shell scripts
  • Allow restricting firmware updates for enterprise use
  • Allow signing the fwupd report with a client certificate
  • Use Plymouth when updating offline firmware

This release fixes the following bugs:

  • Allow forcing an offline-only update on a live system using --force
  • Allow running offline updates when in system-update.target
  • Ask to reboot after scheduling an offline firmware update
  • Correctly check the new version for devices that replug
  • Do not fail to start the daemon if tpm2_pcrlist hangs
  • Do not fail when scheduling more than one update to be run offline
  • Do not let failing to find DBus prevent fwuptool from starting
  • Do not schedule an update on battery power if it requires AC power
  • Include all device checksums in the LVFS report
  • Rename the shimx64.efi binary for known broken firmware
  • Upload the UPDATE_INFO entry for the UEFI UX capsule

This release adds the following features:

  • Allow a device to be updated using more than one plugin
  • Report the DeviceInstanceIDs from fwupdmgr when run as root

This release fixes the following bugs:

  • Add an extra check for Dell NVMe drives to avoid false positives
  • Call composite prepare and cleanup using fwupdtool
  • Correct handling of CAB files with nested directories
  • Detect and special case Dell ATA hardware
  • Do not fail fwupdtool if dbus is unavailable
  • Do not unconditionally enable Werror for the EFI binary
  • Fill holes when reading SREC files
  • Filter the last supported payloads of certain Dell docks
  • Fix flashing failure with latest Intuos Pro tablet
  • Fix potential segfault when applying UEFI updates
  • Fix unifying regression when recovering from failed flash

This release adds the following features:

  • Add a directory remote that generates metadata
  • Add a new remote type "directory"
  • Add a plugin to update Wacom embedded EMR and AES panels
  • Add a plugin to upgrade firmware on ATA-ATAPI hardware
  • Add a quirk to use the legacy bootmgr description
  • Add flag to support manually aligning the NVMe firmware to the FWUG value
  • Add SuperIO IT89xx device support
  • Add support for Dell dock passive flow
  • Add 'update' and 'get-updates' commands to fwupdtool
  • Allow Dell dock flashing Thunderbolt over I2C
  • Check the battery percentage before flashing
  • Show a per-release source and details URL
  • Show a `UpdateMessage` and display it in tools

This release fixes the following bugs:

  • Add the needs-shutdown quirk to Phison NVMe drives
  • Correct Nitrokey Storage invalid firmware version read
  • Do not check the BGRT status before uploading a UX capsule
  • Do the UEFI UX checksum calculation in fwupd
  • Fix flashing various Jabra devices
  • Fix the parser to support extended segment addresses
  • Flash the fastboot partition after downloading the file
  • Show a console warning if loading an out-of-tree plugin
  • Support FGUID to get the SKU GUID for NVMe hardware

This release fixes the following bug:

  • Correctly migrate the history database

This release adds the following features:

  • Add support for devices that support fastboot
  • Add more standard USB identifier GUIDs
  • Add new API to get the release protocol from the metadata
  • Add the PCR0 value as the device checksum for system firmware
  • Include the device firmware checksum and update protocol in the report

This release fixes the following bugs:

  • Add Dell TB18DC to the supported devices list
  • Allow replacing the last byte in the image when using 'dfu-tool replace-data'
  • Append the UEFI capsule header in userspace rather than in the loader
  • Check the device checksum as well as the content checksum during verify
  • Correctly parse format the version numbers correctly using old metadata
  • Fix a crash if AMT returns an empty response
  • Fix a regression when doing GetReleases on unsupported hardware
  • Fix the 8bitdo version number if the daemon locale is not C.UTF-8
  • Remove the Wacom DTH generation hardware from the whitelist
  • Sanitize the version if the version format has been specified

This release adds the following features:

  • Add per-release install duration values
  • Shut down the daemon after 2h of inactivity when possible

This release fixes the following bugs:

  • Fix a use-after-free when using --immediate-exit
  • Fix flashing the 8bitdo SF30
  • Fix showing the custom remote agreements
  • Include the os-release information in the release metadata
  • Speed up startup by loading less thunderbolt firmware
  • Speed up startup by using a silo index for GUID queries
  • Use less memory and fragment the heap less when starting

This release adds the following features:

  • Add a plugin for an upcoming Dell USB-C dock
  • Add a standalone installer creation script
  • Add support for devices to show an estimated flash time
  • Add support for some new Realtek USB devices
  • Allow firmware files to depend on versions from other devices
  • Allow setting the version format from a quirk entry
  • Port from libappstream-glib to libxmlb for a large reduction in RSS
  • Stop any running daemon over dbus when using fu-tool
  • Support the Intel ME version format

This release fixes the following bugs:

  • Add version format quirks for several Lenovo machines
  • Adjust panamera ESM update routine for some reported issues
  • Adjust synapticsmst EVB board handling
  • Check the amount of free space on the ESP
  • Don't show devices pending a reboot in GetUpgrades
  • Ensure that parent ID is created before creating quirked children
  • Optionally wait for replug before updating a device
  • Set the full AMT device version including the BuildNum
  • Sort the firmware sack by component priority
  • Stop showing errors when no Dell dock plugged in
  • Stop showing the current release during updates in fwupdmgr
  • Update all sub-devices for a composite update
  • Use HTTPS_PROXY if set

This release adds the following features:

  • Add a new device flag 'ignore-validation' that will override checks
  • Add a new plugin to enumerate EC firmware
  • Add a new plugin to update NVMe hardware
  • Add a plugin for updating using the flashrom command line tool
  • Allow the device list to take care of waiting for the device replug
  • Allow updating just one specific device from the command line
  • Allow upgrades using a self-signed fwupd.efi binary
  • Download firmware if the user specifies a URI
  • Include serial number in daemon device output when trusted
  • Notify all plugins of device removals through a new vfunc
  • Use boltd force power API if available

This release fixes the following bugs:

  • Add an install hook for classic snap
  • Allow forcing installation even if no AC power is applied
  • Allow using --force to ignore version_lowest
  • Always use the same HardwareIDs as Windows
  • Check the device state before assuming a fake DFU runtime
  • Copy over parent GUIDs from other plugin donors
  • Detect location of python3 interpreter
  • Do not add udev devices after a small delay
  • Don't fail to run if compiled without GPG/PKCS7
  • Fix a segfault in fwupdtool caused by cleanup of USB plugins
  • Implement the systemd recommendations for offline updates
  • Improve performance when reading keys from the quirk database
  • Remove children of devices when the parent is removed
  • Rewrite synapticsmst to use modern error handling
  • Rewrite the unifying plugin to use the new daemon-provided functionality
  • Show a time estimate on the progressbar after an update has started

This release adds the following features:

  • Add support for the Synaptics Panamera hardware
  • Add validation for Alpine and Titan Ridge
  • Improve the Redfish plugin to actually work with real hardware

This release fixes the following bugs:

  • Allow different plugins to add the same device
  • Allow flashing unifying devices in recovery mode
  • Allow running synapticsmst on non-Dell hardware
  • Check the ESP for sanity at startup
  • Do not hold hidraw devices open forever
  • Don't override _FORTIFY_SOURCE when building the EFI binary
  • Don't show passwords in fwupdmgr
  • Fix a potential segfault in smbios data parsing
  • Fix encoding the GUID into the capsule EFI variable
  • Fix various bugs when reading the thunderbolt version number
  • Reboot synapticsmst devices at the end of flash cycle
  • Show status messages when the daemon is initializing
  • Show the correct title when updating devices
  • Show the reasons that plugins are not run on the CLI
  • Use localedir in po/make-images

This release adds the following features:

  • Add a initial Redfish support
  • Add a tool to mimic the original fwupdate CLI interface
  • Allow devices to assign a plugin from the quirk subsystem
  • Change the quirk file structure to be more efficient
  • Merge fwupdate functionality into fwupd
  • Run a plugin vfunc before and after all the composite devices are updated
  • Support more Wacom tablets

This release fixes the following bugs:

  • Add release information for locked devices
  • Allow building with older meson
  • Detect the EFI system partition location at runtime
  • Do not use 8bitdo bootloader commands after a successful flash
  • Enable accessing downloaded files in flatpak and snap
  • Fix a potential buffer overflow when applying a DFU patch
  • Fix downgrading older releases to devices
  • Fix flashing devices that require a manual replug
  • Fix several small memory leaks in various places
  • Fix the retrieval of Redfish version
  • Fix unifying failure to detach when using a slow host controller
  • Set the Wacom device status when erasing and writing firmware
  • Show errors in the CLI if unable to access directory
  • Use the parent device name for Wacom sub-modules

This release adds the following features:

  • Add a plugin to update some future Wacom tablets
  • Add 'fwupdmgr get-topology' to show logical device tree
  • Add support for creating a flatpak
  • Add support for creating a snap
  • Add support for Motorola S-record files
  • Add the Linux Foundation public GPG keys for firmware and metadata
  • Show a translated warning when the server is limiting downloads

This release fixes the following bugs:

  • Add a firmware diagnostic tool called fwupdtool
  • Adjust all licensing to LGPL 2.1+
  • Allow installing more than one firmware using 'fwupdmgr install'
  • Allow specifying hwids with OR relationships
  • Do not call fu_plugin_init() on blacklisted plugins
  • Do not require libcolorhug to build
  • Fix a crash in libfwupd where no device ID is set
  • Fix a potential DoS in libdfu by limiting holes to 1MiB
  • Fix a segfault that sometimes occurs during cleanup of USB plugins
  • Fix Hardware-ID{0,1,2,12} compatibility with Microsoft
  • Hide devices that aren't updatable by default in fwupdmgr
  • Search all UEFI GUIDs when matching hardware
  • Stop matching Nintendo Switch Pro in the 8bitdo plugin

This release adds the following features:

  • Add enable-remote and disable-remote commands to fwupdmgr
  • Add fu_plugin_add_compile_version() for libraries to use
  • Allow requiring specific versions of libraries for firmware updates
  • If no remotes are enabled try to enable the LVFS
  • Show a warning with interactive prompt when enabling a remote

This release fixes the following bugs:

  • Check that EFI system partition is mounted before update
  • Disable synapticsmst remote control on failure
  • Don't recoldplug thunderbolt to fix a flashing failure
  • Fix SQL error when running 'fwupdmgr clear-offline'
  • Improve the update report message
  • Only enumerate Dell Docks if the type is known
  • Only run certtool if a new enough gnutls is present
  • Prevent a client crash if the daemon somehow sends invalid data
  • Reboot after scheduling using logind not systemd
  • Use the right encoding for the label in make-images

This release adds the following features:

  • Add bash completion for fwupdmgr
  • Add support for newest Thunderbolt chips
  • Allow all functions that take device arguments to be prompted
  • Allow devices to use the runtime version when in bootloader mode
  • Allow overriding ESP mount point via conf file
  • Delete any old fwupdate capsules and efivars when launching fwupd
  • Generate Vala bindings

This release fixes the following bugs:

  • Allow ctrl-d out of the prompt for devices
  • Allow to create package out of provided binary
  • Correct handling of unknown Thunderbolt devices
  • Correctly detect new remotes that are manually copied
  • Fix a crash related to when passing device to downgrade in CLI
  • Fix running the self tests when no fwupd is installed
  • Fix Unifying signature writing and parsing for Texas bootloader
  • Only send success and failure reports to the server
  • Use a CNAME to redirect to the correct CDN for metadata
  • Use a longer timeout when powering back the Thunderbolt device

This release adds the following features:

  • Offer to reboot when processing an offline update
  • Report the efivar, libsmbios and fwupdate library versions
  • Report Thunderbolt safe mode and SecureBoot status
  • Show the user a URL when they report a known problem
  • Support split cabinet archives as produced by Windows Update

This release fixes the following bugs:

  • Be more careful deleting and modifying device history
  • Clarify which devices don't have upgrades
  • Ensure the Thunderbolt version is xx.yy
  • Fix a daemon warning when using fwupdmgr get-results
  • Fix crash with MST flashing
  • Fix DFU detach with newer releases of libusb
  • Include the device VID and PID when generating the device-id
  • Set the RemoteId when using GetDetails
  • Stop matching 8bitdo DS4 controller VID/PID
  • Use help2man for dfu-tool and drop docbook dependencies
  • Use ngettext for any strings with plurals
  • Use the default value if ArchiveSizeMax is unspecified

This release adds the following features:

  • Add D-Bus methods to get and modify the history information
  • Allow the user to share firmware update success or failure
  • Ask the user to refresh metadata when it is very old
  • Store firmware update success and failure to a local database

This release fixes the following bugs:

  • Add a device name for locked UEFI devices
  • Allow each plugin to opt-in to the recoldplug action
  • Fix firmware downloading using gnome-software
  • Fix UX capsule reference to the one specified in efivar
  • Never add two devices to the daemon with the same ID
  • Rescan supported flags when refreshing metadata

This release adds the following features:

  • Add a new plugin to add support for CSR 'Driverless DFU'
  • Add initial SF30/SN30 Pro support
  • Support AppStream metadata with relative <location> URLs

This release fixes the following bugs:

  • Add more metadata to the user-agent string
  • Block owned Dell TPM updates
  • Choose the correct component from provides matches using requirements
  • Do not try to parse huge compressed archive files
  • Fix a double-free bug in the Udev code
  • Handle Thunderbolt 'native' mode
  • Use the new functionality in libgcab >= 1.0 to avoid writing temp files

This release adds the following features:

  • Add a plugin for the Nitrokey Storage device
  • Add support for the original AVR DFU protocol
  • Allow different plugins to claim the same device
  • Allow quirks to set common USB properties
  • Move a common plugin functionality out to a new shared object
  • Optionally delay the device removal for better replugging
  • Set environment variables to allow easy per-plugin debugging
  • Use a SHA1 hash for the internal DeviceID

This release fixes the following bugs:

  • Add quirk for AT32UC3B1256 as used in the RubberDucky
  • Disable the dell plugin if libsmbios fails
  • Don't register for USB UDev events to later ignore them
  • Fix a possible buffer overflow when debugging ebitdo devices
  • Fix critical warning when more than one remote fails to load
  • Fix DFU attaching AVR32 devices like the XMEGA
  • Ignore useless Thunderbolt device types
  • Refactor ColorHug into a much more modern plugin
  • Release the Steelseries interface if getting the version failed
  • Remove autoconf-isms from the meson configure options
  • Show a nicer error message if the requirement fails
  • Sort the output of GetUpgrades correctly

This release adds the following features:

  • Add support for HWID requirements
  • Add support for programming various AVR32 and XMEGA parts using DFU
  • Add the various DFU quirks for the Jabra Speak devices
  • Allow specifying the output file type for 'dfu-tool read'
  • Move the database of supported devices out into runtime loaded files
  • Support the IHEX record type 0x05
  • Use help2man to generate the man page at build time
  • Use the new quirk infrastructure for version numbers

This release fixes the following bugs:

  • Catch invalid Dell dock component requests
  • Correctly output Intel HEX files with > 16bit offset addresses
  • Do not try to verify the element write if upload is unsupported
  • Fix a double-unref when updating any 8Bitdo device
  • Fix crash when enumerating with Dell dock connected but with no UEFI
  • Fix uploading large firmware files over DFU
  • Format the BCD USB revision numbers correctly
  • Guess the DFU transfer size if it is not specified
  • Include the reset timeout as wValue to fix some DFU bootloaders
  • Make the error message clearer when sans fonts are missing
  • Support devices with truncated DFU interface data
  • Use the correct remote-specified username and passord when using fwupdmgr
  • Use the correct wDetachTimeOut when writing DFU firmware
  • Verify devices with legacy VIDs are actually 8Bitdo controllers

This release breaks API and ABI to remove deprecated symbols!

This release adds the following features:

  • Add a human-readable title for each remote
  • Add a method to return a list of upgrades for a specific device
  • Add an 'Summary' and 'Icons' properties to each device
  • Add FuDeviceLocker to simplify device open/close lifecycles
  • Add functionality to blacklist Dell HW with problems
  • Add fu_plugin_check_supported()
  • Add fwupd_remote_get_checksum() to use in client programs
  • Add ModifyRemote as an easy way to enable and disable remotes
  • Add the plugin documentation to the main gtk-doc
  • Allow plugins to depend on each other
  • Disable the fallback USB plugin
  • Parse the SMBIOS v2 and v3 DMI tables directly
  • Support uploading the UEFI firmware splash image
  • Use the intel-wmi-thunderbolt kernel module to force power

This release fixes the following bugs:

  • Only run SMI to toggle host MST GPIO on Dell systems with host MST
  • Disable unifying support if no CONFIG_HIDRAW support
  • Do not auto-open all USB devices at startup
  • Do not fail to load the daemon if cached metadata is invalid
  • Do not use system-specific information for UEFI PCI devices
  • Fix a crash when using fu_plugin_device_add_delay()
  • Fix the libdfu self test failure on s390 and ppc64
  • Fix various printing issues with the progressbar
  • Generate the LD script from the GObject introspection data
  • Never fallback to an offline update from client code
  • Only set the Dell coldplug delay when we know we need it
  • Prefer to use HWIDs to get DMI keys and DE table

This release adds the following features:

  • Add a configure switch for the LVFS remotes
  • Add a FirmwareBaseURI parameter to the remote config
  • Add a firmware builder that uses bubblewrap
  • Add a python script to create fwupd compatible cab files from Microsoft .exe files
  • Add a thunderbolt plugin for new kernel interface
  • Allow plugins to get DMI data from the hardware in a safe way
  • Allow plugins to set metadata on devices created by other plugins
  • Optionally install the LVFS PKCS7 root certificate
  • Optionally use GnuTLS to verify PKCS7 certificates

This release fixes the following bugs:

  • Add back options for HAVE_SYNAPTICS and HAVE_THUNDERBOLT
  • Allow configuring systemd and udev directories
  • Enable C99 support in meson.build
  • Fix an incomplete cipher when using XTEA on data not in 4 byte chunks
  • Fix minor const-correctness issues
  • Implement thunderbolt image validation
  • Remove the confusing ALLOW_OFFLINE and ALLOW_ONLINE flags
  • Show a bouncing progress bar if the percentage remains at zero
  • Use a hwid to match supported systems for synapticsmst
  • Use the new bootloader PIDs for Unifying pico receivers
  • When thunderbolt is in safe mode on a Dell recover using SMBIOS

This release adds the following features:

  • Add DfuPatch to support forward-only firmware patching
  • Add --version option to fwupdmgr
  • Display all errors recorded by efi_error tracing
  • Make building introspection optional
  • Support embedded devices with local firmware metadata

This release fixes the following bugs:

  • Check all the device GUIDs against the blacklist when added
  • Correct a memory leak in Dell plugin
  • Default to 'en' for UEFI capsule graphics
  • Don't log a warning when an unknown unifying report is parsed
  • Enable test suite via /etc/fwupd.conf
  • Fix a hang on 32 bit computers
  • Fix compilation of the policy on a variety of configurations
  • Fix UEFI crash when the product name is NULL
  • Make flashing ebitdo devices work with fu-ebitdo-tool
  • Make messages from installing capsules useful
  • Make sure the unifying percentage completion goes from 0% to 100%
  • Run the plugin coldplug methods in a predictable order
  • Test UEFI for kernel support during coldplug
  • Use new GUsb functionality to fix flashing Unifying devices

This release adds the following features:

  • Add a get-remotes command to fwupdmgr
  • Add a plugin to get the version of the AMT ME interface
  • Add Arch Linux to CI
  • Add some installed tests flashing actual hardware
  • Allow flashing Unifying devices in bootloader modes
  • Allow ordering the metadata remotes

This release fixes the following bugs:

  • Do not check the runtime if the DFU device is in bootloader mode
  • Do not unlock devices when doing VerifyUpdate
  • Filter by Unifying SwId when making HID++2.0 requests
  • Fix downgrades when version_lowest is set
  • Fix the self tests when running on PPC64 big endian
  • Move the remotes parsing from the client to the server
  • Split up the Unifying HID++2.0 and HID++1.0 functionality
  • Store the metadata files rather than merging to one store
  • Use a longer timeout for some Unifying operations
  • Use the UFY DeviceID prefix for Unifying devices

This release adds the following features:

  • Add installed tests that use the daemon
  • Add the ability to restrict firmware to specific vendors
  • Enable Travis CI for Fedora and Debian
  • Export some more API for dealing with checksums
  • Generate a images for status messages during system firmware update
  • Show progress download when refreshing metadata

This release fixes the following bugs:

  • Compile with newer versions of meson
  • Ensure that firmware provides are legal GUIDs
  • Fix a common crash when refreshing metadata
  • Use the correct type signature in the D-Bus introspection file

This release adds the following features:

  • Add a 'downgrade' command to fwupdmgr
  • Add a 'get-releases' command to fwupdmgr
  • Add support for ConsoleKit2
  • Add support for Microsoft HardwareIDs
  • Allow downloading metadata from more than just the LVFS
  • Allow multiple checksums on devices and releases

This release fixes the following bugs:

  • Allow to specify bindir
  • Correctly open Unifying devices with original factory firmware
  • Deprecate some of the old FwupdResult API
  • Do not copy the origin from the new metadata file
  • Do not expect a Unifying reply when issuing a REBOOT command
  • Do not re-download firmware that exists in the cache
  • Fix a problem when testing for a Dell system
  • Fix flashing new firmware to 8bitdo controllers
  • Increase minimum required AppStream-Glib version to 0.6.13
  • Make documentation and man pages optional
  • Make systemd dependency at least version 231
  • Only decompress the firmware after the signature check
  • Remove 'lib' prefix when looking for libraries
  • Return the remote ID when getting updates about hardware
  • Send the daemon the remote ID when sending firmware metadata

This release adds the following feature:

  • Add support for Unifying DFU features

This release fixes the following bugs:

  • Do not spew a critial warning when parsing an invalid URI
  • Ensure device is closed if did not complete setup
  • Ensure steelseries device is closed if it returns an invalid packet
  • Fix man page installation location
  • Ignore spaces in the Unifying version prefix
  • Set HAVE_POLKIT_0_114 when polkit is newer than 0.114

This release adds the following features:

  • Add a config option to allow runtime disabling plugins by name
  • Add the Meson build system and remove autotools
  • Support signed Intel HEX files

This release fixes the following bugs:

  • Add DFU quirk for OpenPICC and SIMtrace
  • Create directories in /var/cache as required
  • Refactor the unifying plugin now we know more about the hardware
  • Set the source origin when saving metadata
  • Support proxy servers in fwupdmgr
  • Use a 60 second timeout on all client downloads

This release fixes the following bugs:

  • Adjust systemd confinement restrictions
  • Do not hardcode docbook2man path
  • Don't initialize libsmbios on unsupported systems
  • Fix a crash when enumerating devices on a Dell WLD15
  • Fix compiler warnings
  • Fix fwupdmgr timeout with missing pending database

This release adds the following features:

  • Add a set of vfuncs that are run before and after a device update
  • Add Dell-specific functionality to allow other plugins turn on TBT/GPIO
  • Add support for Intel Thunderbolt devices
  • Add support for Logitech Unifying devices
  • Add support for Synaptics MST cascades hubs
  • Add support for the Altus-Metrum ChaosKey device
  • Add VerifyUpdate to update the device checksums server-side
  • Allow the metadata to match a version of fwupd and the existing fw version

This release fixes the following bugs:

  • Add a new method for forcing a controller to flash mode
  • Always make sure we're getting a C99 compiler
  • Close USB devices before error returns
  • Don't read data from some DfuSe targets
  • Include all debug messages when run with --verbose
  • Return the pending UEFI update when not on AC power
  • Use a heuristic for the start address if the firmware has no DfuSe footer
  • Use more restrictive settings when running under systemd

This release adds the following features:

  • Add a 'replace-data' command to dfu-tool
  • Use an animated progress bar when performing DFU operations

This release fixes the following bugs:

  • Add quirks for HydraBus as it does not have a DFU runtime
  • Don't create the UEFI dummy device if the unlock will happen on next boot
  • Enable hardening flags on more binaries
  • Fix an assert when unlocking the dummy ESRT device
  • Fix writing firmware to devices using the ST reference bootloader
  • Match the Dell TB16 device
  • Re-get the quirks when the DfuDevice gets a new GUsbDevice
  • Show the nicely formatted target name for DfuSe devices
  • Verify devices support updating in mode they are called

This release adds the following features:

  • Add dfu_firmware_add_symbol()
  • Allow the argument to 'dfu-tool set-release' be major.minor
  • Load the Altos USB descriptor from ELF files
  • Support writing the IHEX symbol table

This release fixes the following bugs:

  • Add a fallback for older appstream-glib releases
  • Fix a possible crash when uploading firmware files using libdfu
  • Fix libfwupd self tests when a host-provided fwupd is not available
  • Show the human-readable version in the 'dfu-tool dump' output
  • Write the ELF files with the correct section type

This release adds the following features:

  • Add a set-address and set-target-size commands to dfu-util
  • Add a small library for talking with 0bitdo hardware
  • Add Dell TPM and TB15/WD15 support via new Dell provider
  • Add FU_DEVICE_FLAG_NEEDS_BOOTLOADER
  • Add fwupd_client_get_status()
  • Add fwupd_result_get_unique_id()
  • Add initial ELF reading and writing support to libdfu
  • Add support for installing multiple devices from a CAB file
  • Allow providers to export percentage completion
  • Show a progress notification when installing firmware
  • Show the vendor flashing instructions when installing

This release fixes the following bugs:

  • Add XPS 9250 to Dell TPM modeswitch blacklist
  • Allow blacklisting devices by their GUID
  • Conditionally enable all providers based upon installed
  • Display flashes left in results output when it gets low
  • Do not attempt to add DFU devices not in runtime mode
  • Do not use the deprecated GNOME_COMPILE_WARNINGS
  • Don't fail while checking versions or locked state
  • Embed fwupd version in generated documentation
  • Ensure the ID is set when getting local firmware details
  • Fix gtk-doc build when srcdir != builddir
  • Fix libdfu hang when parsing corrupt IHEX files
  • Ignore devices that do not add at least one GUID
  • In get-details output, display the blob filename
  • Save the unique ID in the pending database
  • Support the 'DEVO' cipher kind in libdfu
  • Switch to the Amazon S3 CDN for firmware metadata
  • Update fwupdmgr manpage for new commands and arguments
  • Use a private gnupg key store
  • Use the correct firmware when installing a composite device
  • Use the SHA1 hash of the local file data as the origin

This release adds the following features:

  • Add a GetDetailsLocal() method to eventually replace GetDetails()
  • Add fu_device_get_alternate()
  • Allow devices to have multiple assigned GUIDs
  • Allow metainfo files to match only specific revisions of devices
  • Show the DFU protocol version in 'dfu-tool list'

This release fixes the following bugs:

  • Enforce allowing providers to take away flash abilities
  • Only claim the DFU interface when required
  • Only return updatable devices from GetDevices()

This release adds the following features:

  • Add a --force flag to override provider warnings
  • Add device-added, device-removed and device-changed signals
  • Add dfu_image_get_element_default()
  • Add for a new device field 'Flashes Left'
  • Add fwupd_client_connect()
  • Add the 'monitor' debugging command for fwupdmgr
  • Add the 'supported' flag to the FuDevice

This release fixes the following bugs:

  • Add summary and name field for Rival SteelSeries
  • Fix a critical warning when restarting the daemon
  • Fix BE issues when reading and writing DFU files
  • Make the device display name nicer
  • Match the AppStream metadata after a device has been added
  • Remove non-interactive pinentry setting from fu-keyring
  • Return all update descriptions newer than the installed version
  • Set the device description when parsing local firmware files

This release adds the following features:

  • Add a version plugin for SteelSeries hardware
  • Add FwupdClient and FwupdResult to libfwupd
  • Generate gtk-doc documentation for libfwupd
  • Return the device flags when getting firmware details
  • Support other checksum kinds

This release fixes the following bugs:

  • Add Alienware to the version quirk table
  • Allow the test suite to run in %check
  • Do not return updates that require AC when on battery
  • Do not use /tmp for downloaded files
  • Test that GPG key import actually was successful

This release adds the following features:

  • Add an unlock method for devices
  • Add a simple plugin infrastructure
  • Add ESRT enable method into UEFI provider
  • Install the hardcoded firmware AppStream file

This release fixes the following bugs:

  • Correct the BCD version number for DFU 1.1
  • Do not use deprecated API from libappstream-glib
  • Ignore the DFU runtime on the DW1820A
  • Only read PCI OptionROM firmware when devices are manually unlocked
  • Require AC power before scheduling some types of firmware update
  • Show ignored DFU devices in dfu-util, but not in fwupd

This release adds the following feature:

  • Add 'Created' and 'Modified' properties on managed devices

This release fixes the following bugs:

  • Fix get-results for UEFI provider
  • Support vendor-specific UEFI version encodings

This release fixes the following bugs:

  • Always persist ColorHug devices after replug
  • Do not misdetect different ColorHug devices
  • Only dump the profiling data when run with --verbose

This release adds a new GObject library called libdfu and a command line client called dfu-tool. This is a low-level tool used to upgrade USB device firmware and can either be shipped in the same package as fwupd or split off as separate subpackages.

This release adds the following feature:

  • Add support for automatically updating USB DFU-capable devices

This release fixes the following bugs:

  • Emit the changed signal after doing an update
  • Export the AppStream ID when returning device results
  • Fix compile with --disable-shared
  • Use new API available in fwup 0.5
  • Use the same device identification string format as Microsoft

This release fixes the following bugs:

  • Avoid seeking when reading the file magic during refresh
  • Do not assume that the compressed XML data will be NUL terminated
  • Use the correct user agent string for fwupdmgr

This release adds the following features:

  • Add profiling data to debug slow startup times
  • Support cabinet archives files with more than one firmware

This release fixes the following bugs:

  • Add the update description to the GetDetails results
  • Clear the in-memory firmware store only after parsing a valid XML file
  • Ensure D-Bus remote errors are registered at fwupdmgr startup
  • Fix verify-update to produce components with the correct provide values
  • Require appstream-glib 0.5.1
  • Show the dotted-decimal representation of the UEFI version number
  • When the version is from the 'FW' extension do not cache the device

This release fixes the following bugs:

  • Fix the error message when no devices can be updated
  • Fix reading symlink to prevent crash with some compilers

This release adds the following feature:

  • Raise the dep on GLib to support and use g_autoptr()

This release fixes the following bugs:

  • Do not merge existing firmware metadata
  • Do not reboot if racing with the PackageKit offline update mechanism

This release adds the following feature:

  • Remove fwsignd, we have the LVFS now

This release fixes the following bugs:

  • Add application metadata when getting the updates list
  • Depend on appstream-glib >= 0.5.0
  • Don't apply firmware if something else is processing the update
  • Install fwupd into /usr/lib/$(triplet)/fwupd instead
  • Simplify the version properties on devices to avoid complexity
  • Update the offline update service to invoke right command
  • Use the new secure metadata URI

For the device verification code to work correctly you need at least libappstream-glib 0.5.0 installed.

This release adds the following features:

  • Add a Raspberry Pi firmware provider
  • Add a simple config file to store the correct LVFS download URI
  • Make parsing the option ROM runtime optional

This release fixes the following bugs:

  • Allow fwupd to be autostarted by systemd
  • Allow no arguments to 'fwupdmgr verify-update' and use sane defaults
  • Devices with option ROM are always internal
  • Do not pre-convert the update description from AppStream XML
  • Fix validation of written firmware
  • Move the verification and metadata matching phase to the daemon
  • Sign the test binary with the correct key
  • Use the AppStream 0.9 firmware specification by default

In this release we've moved the LVFS website to the fwupd project and made them work really well together. To update all the firmware on your system is now just a case of 'fwupdmgr refresh && fwupdmgr update'. We've also added verification of BIOS and PCI ROM firmware, which may be useful for forensics or to verify that system updates have been applied.

This release adds the following features:

  • Actually parse the complete PCI option ROM
  • Add a 'fwupdmgr update' command to update all devices to latest versions
  • Add a simple signing server that operates on .cab files
  • Add a 'verify' command that verifies the cryptographic hash of device firmware
  • Allow clients to add new firmware metadata to the system cache
  • Move GetUpdates to the daemon
  • Move the LVFS website to the fwupd project

This release fixes the following bugs:

  • Accept multiple files at one time when using fwupdmgr dump-rom
  • Automatically download metadata using fwupdmgr if required
  • Do not return NULL as a gboolean
  • Don't call efibootmgr after fwupdate
  • Fallback to offline install when calling the update argument
  • Fix Intel VBIOS detection on Dell hardware
  • Reload appstream data after refreshing
  • Use the new LVFS GPG key
  • Fix build: libgusb is required even without colorhug support

This release adds the following features:

  • Get the firmware version from the device descriptors
  • Run the offline actions using systemd when required
  • Support OpenHardware devices using the fwupd vendor extensions

This release fixes the following bugs:

  • Add an UNKNOWN status so we can return meaningful enum values
  • Coldplug the devices before acquiring the well known name

This release adds the following features:

  • Add a 'get-updates' command to fwupdmgr
  • Add and document the offline-update lifecycle
  • Create a libfwupd shared library

This release fixes the following bugs:

  • Create runtime directories if they do not exist
  • Do not crash when there are no devices to return

fwupd is a simple daemon to allow session software to update firmware.

fwupd-1.2.14/data/org.freedesktop.fwupd.service.in000066400000000000000000000002211402665037500220540ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.fwupd Documentation=https://fwupd.org/ Exec=@libexecdir@/fwupd/fwupd User=root SystemdService=fwupd.service fwupd-1.2.14/data/org.freedesktop.fwupd.svg000066400000000000000000000243251402665037500206210ustar00rootroot00000000000000 Adwaita Icon Template image/svg+xml GNOME Design Team Adwaita Icon Template fwupd firmwareupdater fwupd-1.2.14/data/pki/000077500000000000000000000000001402665037500144305ustar00rootroot00000000000000fwupd-1.2.14/data/pki/GPG-KEY-Hughski-Limited000066400000000000000000000032461402665037500204100ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 mQENBFUr0UoBCACsdOLuTJ81dICrSvUhyznBsL4WgEa2RUbEjJuaXwrEyPMikHE1 Clda2YI7VbpCgIVq8Zy63CGJ4Xqs2T6pyetaXnbX8J0C+7wg2IfPv7pUyCsP7/JR HRB2GNelCWrsGArN1cOPI0ESH4yHWKF9KCGlpsLfSHmvF7D8vcKlKQUlO4T6lxOP SNjMSXkMsxfDDhl1mzqrwxfU4V6nnPcuMwU7tvg+39PioP4Ny1tKP4SSpBfh7qwz XXRd505dqNLOubxmOPZ5rznVkKmW2cwahO6fr5zVA8/2TDZQ79mdbfvSJVlW06qs C5PYmLnBjyzE5uQ4oxSIuUEiMfqrn3Qs6PhhABEBAAG0Ikh1Z2hza2kgTGltaXRl ZCA8aW5mb0BodWdoc2tpLmNvbT6JATgEEwECACIFAlUr0UoCGwMGCwkIBwMCBhUI AgkKCwQWAgMBAh4BAheAAAoJEK2KUo/sRIge/fUH/Rblgzh5GeB0Zp2U9W+r26iJ t1AD5a/fKxQahz/pwMkevQCCMzI1vpX12P3HtACZOD3Zjh9RXY6Z3033YZjrRApe FkOVfcyUF1nP/z2Ox3jE3+B8v1u0UzH/MqtF/1095mqvR7gllE288KDqu7bvd5l3 z4IETk5qqoeCe9LYc8aob973dbocyS/gou/FLCKxoXVEe8DPRwv8qmXlXOujxdxd FcslpYqtjj4fgUswQ/cY/a1UcAX5zCnVqFbU7oJH2uTNewKuaZ2wgPbnzvwx8JYl VfFdPN7GZ0NMrZDLeJ0SLXer/9+qAKNH4UpQS9axXQL+VKOzsZCXuv31VDCj5Jy5 AQ0EVSvRSgEIAMgVrZP3LmA9bx7B8l+agVh5DNXrMixX9jhZ0Yfn8+UIMMNTZziD ZV3nXxswKPrcsqQ+KP9iUwq3V2oio46bvHiMMoZSGCaTv4yiKOliFOMYr9NAOSTZ 8mOI24dNXI9XqQ7ZA8m4uKmgHZQUIUUlx693uRI2Wmk/Y5XEBoL2+XdA5KalO+36 27YXpdyU3GiMCOtSBLWNfBxXw6oKdNUp+8o/fYrmQnBxuGgmVlcZEmjhrIGXaCH1 iDeWIFqaM/S+DXMF3bgqvqRZq1U2RwT2oxapAuaG/0I5JaKKpb3HqMCXfOUxpFPk zgUYpHatUcePG/94K8N8CRjnJ+l83H5PewcAEQEAAYkBHwQYAQIACQUCVSvRSgIb DAAKCRCtilKP7ESIHrrcCACc6UTZzVGbVq9pXSz2Bw2xQpAEAhnnedPgfXwEJMM0 24bMUNsyJcQZAW1d5KfJYNAihOfse3oDQ/hJAycTK3GAHsPfljEQjWGn27eC8Fxu mHpfNpxbTirChfepCNctZG818Hp2v+K4X/PjyQMQ6J5H9oinnlasVQ6wzdZifnWm 7E5OL0NV/ni9xqq4fC5y5qxNBeYVmHUF4H0E3VOuCbESAOnUDpCo998Dc68eZEmV f3IMukvvnxM9VOZQSnp7J/kkhPB5fim2z2qrlJK9N+tBjAMugxtnAV2fIaZYTiba SnN2hheFd9Y0nMmWbwRqFtwMG1m/tS3JlD52Rpwzk59B =WFoi -----END PGP PUBLIC KEY BLOCK----- fwupd-1.2.14/data/pki/GPG-KEY-Linux-Foundation-Firmware000066400000000000000000000041711402665037500223740ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 mQENBFsDBO0BCACjkrMuRgaWxP88Ubc1Xar5mxLMNXAJUzeYQVh/LnkEwytO3Ekh GDH8Ch78269MiezkJmUGUUGyjKhqZECtZaKGp4LSl6gTPFDFHS/xKaq8L+8G/v4K LEtZE03PKSnY2XYnf+3Kc6tmIZBB67yRg/79p3OpFd95wqyu+2c1cVkjCA1Q8XpO bgCDfNacU3Yag6GXYlKpLmlVkYaAptjV0FrbLLBjaHvFeGAXgRUlv0PRyDjKD2XT PEBtbg2+qTxPJIOlFgGNsJjkFL7R3mWwn00yF4jt9JMYGkpNAuFg1c/TZ1v64wlP N6i2DsDwIMQ9S/ahJWdX/zP4JMTdpYNP91o9ABEBAAG0WkxWRlMgYSBTZXJpZXMg b2YgTEYgUHJvamVjdHMsIExMQyAoTFZGUyBhIFNlcmllcyBvZiBMRiBQcm9qZWN0 cywgTExDKSA8ZmlybXdhcmVAZnd1cGQub3JnPokBOAQTAQIAIgUCWwME7QIbAwYL CQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQxjYXh6CoSeE3pQf+MlPyQzkVhdRU fqjzu3Ba9dZ+hmsol2ooFmytd058AIO1eKie2LxnkQw4P5prFOnVWbbFi79vUEzQ KK5xpW46nEglU14xYgv+4cMlVNWBYIVsXIIKKs2z4gM8oCA20JpVunnVbaViWTna YwiUbTniIvLQH4RLo66Qzd0b3Z8ycK0bVbCl4RazSbWAGHMAnqm0xSQqsWRQwaHk vxXEbjb0hfO4P/PZui5vFGr82tZUdGVKift1JlOzDMjVcmFIuYITHQyGaZ5Xsh2h Pu9gI9Vp7OQoA7dJ+Qc5LBk3rVxN5Zx3jUSWOd7Dtvrm+ArBy/y0MCVyv9fcSvAH vEv9T4zhE7kBDQRbAwTtAQgAwjiRDT3qinYz7b1s9SM2Y7aZG9JhWi/Zp2qGzGVX QpVV2EK3PnAfZpyt99I63N7d/QDPqLFmTyjlv5cMb3QxVyXGBCGGz3OiWYY0NaDd s1sp3J+TT6bHNG/Lo+vgcTRvzHvq7HbbeNssIMLr6MDAj0fZSh5UlAfQdC3qz90A qIPGcx5AwgCwXLDqzCusz17Erc3IK/TG4r0AbRFtGx0hl2w5EOn7funw8BhnJ59w OMsq7sXDmFff4hQjgQoDezMkA1EgzFokRY7pToLG3X1KdDXKR0edQ3+1mlJTf9XN Zaz+ortKsugmTmzsF4DlRhq0Ok0VWuq0rzWndpFyFvIewwARAQABiQI+BBgBAgAJ BQJbAwTtAhsuASkJEMY2F4egqEnhwF0gBBkBAgAGBQJbAwTtAAoJENTAcNuxNA6+ 1T4H/jsWVrANLKElBwZJpPOVy4Haw7UG+zk7lfwck0H8J9ShDtwhNTg03BiOONP/ JuR8XvOxjqdUexEmAJdQCtxJgLTlI40xSlcmSEIneCamOhA/I/T+nkXFAfV65FKF +OR3Ee122sUMXvLCcNvcbM23GIWiN/YmYFlK1PGNe3oOn4MWJ/28dbCLuEOPP4Tu VJM/RpZ65qCnojc1meMcPJxI5iNWZtG9SmmGWDI3f7mDK+dtD06VLmPd3uc8P23t YN0o/Jkgz2oV5GKD9t2+Ne2C5H5xZ0aE7dDVM8ErEPd3wTS+bC8GhPxHjj4A6HyA MNZAEZSAJ4TVpzbfyOMcpSRK4yVLTAgAmTVV79EH/14s60Ya0LCtpifpvpZimbbo xBGFaymvX7doxZyITC66JNTzT4Eixp8FNRloKWkEo6gPwA6qshlhc0HpmiqNmC+k QNYIVeanrflV2bVzYSdsIzrZLUTd6P835YYxD1nsQGwnCqeeD0gJlV+alo0LYTRt lFwNYxHU7BM09wu7sUEvYW5wt4TXPUrZ9jV+BM9UQLatW+S5vO41wqfTmPKGEqct doW2ZYUgCc4aFGTOj3fA5hoK6EjAQpVdkcA7fiRYLn5AIvM4FGxIqI5khjsZUEPa 9Gpui69Y4a+x4QEIDj/WAHOOMSIg/n96e+uRdGXN4c8nr7JSASxlow== =RFA4 -----END PGP PUBLIC KEY BLOCK----- fwupd-1.2.14/data/pki/GPG-KEY-Linux-Foundation-Metadata000066400000000000000000000041711402665037500223400ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 mQENBFsDA8IBCACgSd0NAJFEUjqcyv38If9f/FCQi2C2MQbzNt05DblHAg6eBk/V eYM/GI+Cr9sPwxs8ZWtN0IRoQp/d7MRxe43zFT4IH2N4RVaBTgWCoRerPn09k4K/ 2fk6GWIY8lgxlKV/LinM5XkFDXv6Zf/o8Nv/i9bVO9Dv1bVh1ThgA3xy8WIzUQge cVviEjEYG10TX+NENGgdA+aD/fMk4Wzwz6L48D+ryTiXGFnwoizifr9DIn4yIp6i b4vTQY96VoXHSgU6JRvYjzPPME+NmmcLgW0hGJlVvi8RL+7wJPVeS0ioqPzMtonS evrwVv5E5k87i+LS/vdVu3SIUzR9JLIXtvNNABEBAAG0WkxWRlMgYSBTZXJpZXMg b2YgTEYgUHJvamVjdHMsIExMQyAoTFZGUyBhIFNlcmllcyBvZiBMRiBQcm9qZWN0 cywgTExDKSA8bWV0YWRhdGFAZnd1cGQub3JnPokBOAQTAQIAIgUCWwMDwgIbAwYL CQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQCm3O9rRvPb/wsQf7BGEkgT08Bx1v 657l//B/yniB7VGH+4plrX2OkWVzDxn+z6APcgqDMnzwatddxM7J6mm4yxA0nFKs D5BueTItYv5zQCzX8e4TM1oaUWBr8nACK7/WSxNIC+GRUKl68v+dIbp1bhdmAYzj wTn/uDW47Z7gtQYDJJit2MsKfu5Lwar7w+divH+6KWdaMctm2injYYIlpKCjffl8 RZ4PgX7lN5C0s9kWDkH9iI2i5aqJaI8gZHK6EKwitmsHMJBczekymlXh4MheYCKm IWJLh8tvK6LaVoddwfle4orhq4b7doA57H8BgJDDz+MxyjZn+GAireCMaJjtCSWv q5bnsdnMH7kBDQRbAwPCAQgAq6hTejZZcIXnfA4Z/1c03USKnK8SeG/yqlggvpyZ 9C3hAvJITtQ7iZmz0VKOjwQtds52qnZYbOn/Fr+4Ef+cbFZRNxamZ4kf0XSAVXSv EODMFj+BpdoDwAWhJcvyijoMV6N+gbCG+UedMNnpe25tlCRjouBSEF5KWJabejdh iZ8ikN+HNJa5uQ68F76aDP1BOE0XiLrOZC0MZ7mvbyOi9LPXFyV2EuVj+gt7r+2x OlSMmor7RTEAJcBK9tiew5LhNHeaqbe3xnOcpWrAaoVdIed7h5YbbetTFMWHCPGJ raGGRSv3OrZDfQXZvOi+k6I6wWEQrsUCIiVKPefU+5xSpwARAQABiQI+BBgBAgAJ BQJbAwPCAhsuASkJEAptzva0bz2/wF0gBBkBAgAGBQJbAwPCAAoJEL4e3StH4Ita hvAIAIlZlrAJrbj7aQ3VkFcTJJC+68BaGNqte2S3Zv7ONLjT1kc0xSflf4c4MHef q7WmLLsjUHocD0a8SUsR1V/Fp36qG1Yr7mfhf7dY3TwwUw9VXQoEdpEWZES/IJ4d oPXSowk8eSbb72g4dvt9p+wlDKlsT7YHjmfn9Zct5FqcQ2kV9+900DtWlvPK8hRR N0FibLR9GMorSFfHotFQ8AjdiXQUo+6GTb2HDJ1+aI4fpYo8NnqUs0wvCVtxrqn9 JBzSgKkFAjHOiz/C6a0JOBtmH6tL41U7ZtUI2idQWsHiufyCTVOHRbyHoOxxFEpX Xu3l2vckZaENNyNOGCqrIo8npFQczAf/REhK0Z8UumttRm2CQkJnkqRgLnIoJq3i l7ljKjFi06XLJxOjLLpIFBH/yAJ5YbsYZt+XuT3WgORfHPDU3xepWGfQj4QFcsny GWStnu6Ej/PogJyyvZ1hFpKu8g2cIp04vEebWeYHAMorvwty/p+MJnH6NeSQq5Pe n22AuKKENtgYwulgJH0VQ0lJ5k1CcFuEuZqnXk5CUIC4tStdUS6hgn+vEkaJ5dX8 nj/X9etEj2nnQGIL+7Dh2Z+UGaZqxSa1tjF5+7uC85K0NTq6Cpc+Bnd7Gqb+afnn /iFHG21+hpjQmdSvpbGTabrM9gxd0iuDFXSFSttMtSC+gR5VcNQpUA== =7Jrd -----END PGP PUBLIC KEY BLOCK----- fwupd-1.2.14/data/pki/GPG-KEY-Linux-Vendor-Firmware-Service000066400000000000000000000016771402665037500231310ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 mQENBFWt/98BCADZ4+lUHSp4OMlzVf4HlJNLJ7Ks5QxGwL/hy2wChoNLuA/j4GNM 9mBZutKynYmphD0Mi4XjXn7JNXyuJa8Qutz98/Iyhsjq4LeiL9ayaKMXT+3pKlTm Gd/Fzo3QEOqTJ5s2RamrfwFIVuvwoj+rNmzj5fUCgoDOZeqVl6gxb7ZPzL8sWTOU iLeGMSzZBGE0ioJ82PZzsHelrrObDP1mMre1jQ6zxLlnYUlLvtJpydAfeBxU+6yL fgPeoFeuCE6JIszyWuyAgpBpYSGgj1bpt9Sxc2+MoZ0BjDzoijZqt4O48gYuEaLf iqYzQybe1JF0McO4C0dmjdKQz2qm0XrQyNhVABEBAAG0LkxpbnV4IFZlbmRvciBG aXJtd2FyZSBTZXJ2aWNlIDxzaWduQGZ3dXBkLm9yZz6JATcEEwEIACEFAlWt/98C GwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQSKbYDkU4usJjjQgAzmTcA8qH s+1kieEZvsUzH4wun2Hlz7R5FRc/7BijgIQAA9TTrJnwbJmEBzEvHv7FKQLiBN3a 0lQIZgahmcUt1qm6VW94VAio+SDCdqTx73wUsgM3t9sAwKxkEdJQQoO8PqYHV3uK rq0t2YjXglIBHRDiJlOTAR3if37OCDKCcHOOODqYrsN7wNleez+ulkDyP7C7ZTbm /A7Xec73t2OQUnejU0uvRvc7VSnQDRFBHA9TPiBhbruMw+ZX+z/wfPd7x2RCqoOE vHh+QofE41Ya2QOkT96fAKfcJ+gvIbmwp3w7h+Hus1h3xDrykCG9cCxuH0HxooVI XL3IlFx/6OUpBA== =6Dz2 -----END PGP PUBLIC KEY BLOCK----- fwupd-1.2.14/data/pki/LVFS-CA.pem000066400000000000000000000032171402665037500161710ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEqjCCAxKgAwIBAgIBATANBgkqhkiG9w0BAQsFADA6MRAwDgYDVQQDEwdMVkZT IENBMSYwJAYDVQQKEx1MaW51eCBWZW5kb3IgRmlybXdhcmUgUHJvamVjdDAeFw0x NzA4MDEwMDAwMDBaFw00NzA4MDEwMDAwMDBaMDoxEDAOBgNVBAMTB0xWRlMgQ0Ex JjAkBgNVBAoTHUxpbnV4IFZlbmRvciBGaXJtd2FyZSBQcm9qZWN0MIIBojANBgkq hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAtfUXH3NwDJzWyhkPyPcFI899+tPZ/SMp OkDtRr9dJjgQkSO9jKCue4DVq8Bd9RcL76F7XnEKG0LiuKnr+D7+x86TtDAPCbkP WAS7fAaetLtiNFU96cokhjeALB3hyamkMQnCw+5Ov+sHJfGI9Bor9UaIIbIB4r8v oU1WpE7N6Ix2qsS5b88+Z6EIV6CX8RbciOC/TfyYVnpF1cd4l7LH7TtL+ERpsPwv rk0JgVoRzG3BT5yYfuxHIe4H4Axh95tW9i6urzyQkXRz14twwwcEDvl5ALrBLNJJ 8EDz9oR8HBPbxbd4i2dBfziY7TW4o/VgZKTGWA39JfwWNc5RxaYzBhBmg5nRcVFs E7PlovhyFH/0RNm/3E6vZQCeM+FNps0ovVq8Yqg8whL/yZ0iNlavCGTWhaxisVHG 7mQopV4jZlafxvrcBFzK8RPe8Gi04FFn4ugZtJnOuMel+AiADhgtWZCENiyWV+V7 WF1SFF4HaHuS8qqna/p9lrpVq6TBr0WRAgMBAAGjgbowgbcwEgYDVR0TAQH/BAgw BgEB/wIBATAwBgNVHREEKTAnhhVodHRwOi8vd3d3LmZ3dXBkLm9yZy+BDnNpZ25A Znd1cGQub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA8GA1UdDwEB/wQFAwMHBgAw HQYDVR0OBBYEFLGN6uQjp34JjrXuMeBq3Z40N2WsMCoGA1UdHwQjMCEwH6AdoBuG GWh0dHA6Ly93d3cuZnd1cGQub3JnL3BraS8wDQYJKoZIhvcNAQELBQADggGBABNK mC4AcqsBCVRGpwJeUymh5G6uUpzkoEDw+y9TEoWzfldV0epU7ruqI2p8B8YshDK6 +D4CFmCnW8cc+Jb6jrJ2ZcjUqWE/c+uwZhwsUHNdk6ummPPKfMhRSbduk1ngdQe5 meIgWGkoCfJ48GUAVVD6MlrMTNFsot1GN9x3ALMqhSU49+X43yikcc9WY2F8JOY8 xYpGpgUQV1hBSPOGK4XhgztpFLqw0GxJiLrOfKjtJwSTkxGCpPi2dLS0huk/mreT NAQ5FnMLkoqfR1RGga3tiP5w13gqDBV7a6MYMdmMfAAZhfRtlDu6SiAmjEmlSkOK PNhdoCNVDQLQpGaKZUI5hjMfR90U8Cm/6e0ondwjV4J6f4CS4wkQ5zzITGWptagE 01tpgTXf7TLaFGtzR8cl8XgV+UO3T4DQjEQkXUaS7n72ZCGv/s4LraLunhBrVHSq glEXpU/V/JNptgArIiRFZOrto52cUnnlNEfgqIzAHv/LMFRIkMo8ZMGTgScFrA== -----END CERTIFICATE----- fwupd-1.2.14/data/pki/meson.build000066400000000000000000000012401402665037500165670ustar00rootroot00000000000000if get_option('gpg') install_data([ 'GPG-KEY-Hughski-Limited', 'GPG-KEY-Linux-Foundation-Firmware', 'GPG-KEY-Linux-Vendor-Firmware-Service', ], install_dir : join_paths(sysconfdir, 'pki', 'fwupd') ) install_data([ 'GPG-KEY-Linux-Foundation-Metadata', 'GPG-KEY-Linux-Vendor-Firmware-Service', ], install_dir : join_paths(sysconfdir, 'pki', 'fwupd-metadata') ) endif if get_option('pkcs7') install_data([ 'LVFS-CA.pem', ], install_dir : join_paths(sysconfdir, 'pki', 'fwupd') ) install_data([ 'LVFS-CA.pem', ], install_dir : join_paths(sysconfdir, 'pki', 'fwupd-metadata') ) endif fwupd-1.2.14/data/remotes.d/000077500000000000000000000000001402665037500155455ustar00rootroot00000000000000fwupd-1.2.14/data/remotes.d/README.md000066400000000000000000000070231402665037500170260ustar00rootroot00000000000000Vendor Firmware =============== These are the steps to add vendor firmware that is installed as part of an embedded image such as an OSTree or ChromeOS image: * Change `/etc/fwupd/remotes.d/vendor.conf` to have `Enabled=true` * Change `/etc/fwupd/remotes.d/vendor.conf` to have the correct `Title` * Deploy the firmware to `/usr/share/fwupd/remotes.d/vendor/firmware` * Deploy the metadata to `/usr/share/fwupd/remotes.d/vendor/vendor.xml.gz` The metadata should be of the form: FIXME.firmware FIXME FIXME FIXME FIXME

FIXME

http://FIXME 86406 firmware/FIXME.cab 96a92915c9ebaf3dd232cfc7dcc41c1c6f942877

FIXME.

FIXME
Ideally, the metadata and firmware should be signed by either GPG or a PKCS7 certificate. If this is the case also change `Keyring=gpg` or `Keyring=pkcs7` in `/etc/fwupd/remotes.d/vendor.conf` and ensure the correct public key or signing certificate is installed in the `/etc/pki/fwupd` location. Automatic metadata generation ============================= `fwupd` and `fwupdtool` support automatically generating metadata for a remote by configuring it to be a *directory* type. This is very convenient if you want to dynamically add firmware from multiple packages while generating the image but there are a few deficiencies: * There will be a performance impact of starting the daemon or tool measured by O(# CAB files) * It's not possible to verify metadata signature and any file validation should be part of the image validation. To enable this: * Change `/etc/fwupd/remotes.d/vendor-directory.conf` to have `Enabled=true` * Change `/etc/fwupd/remotes.d/vendor.conf-directory` to have the correct `Title` * Deploy the firmware to `/usr/share/fwupd/remotes.d/vendor/firmware` * Change `MetadataURI` to that of the directory (Eg `/usr/share/fwupd/remotes.d/vendor/`) Mirroring a Repository ====================== The LVFS currently outputs XML with absolute URI locations, e.g. `http://foo/bar.cab` rather than `bar.cab` This makes mirroring the master LVFS (or other private instance) somewhat tricky. To work around this issue client remotes can specify `FirmwareBaseURI` to replace the URI of the firmware before it is downloaded. For mirroring the LVFS content to a new CDN, you could use: [fwupd Remote] Enabled=true Type=download Keyring=gpg MetadataURI=https://my.new.cdn/mirror/firmware.xml.gz FirmwareBaseURI=https://my.new.cdn/mirror New instances of the LVFS can actually output a relative URL for firmware files, e.g. `bar.cab` and when downloading the `MetadataURI` name and path prefix is used in this case. This is not enabled for the "upstream" LVFS instance as versions of fwupd older than 1.0.3 are unable to automatically use the `MetadataURI` value for firmware downloads. fwupd-1.2.14/data/remotes.d/lvfs-testing.conf000066400000000000000000000005361402665037500210450ustar00rootroot00000000000000[fwupd Remote] # this remote provides metadata and firmware marked as 'testing' from the LVFS Enabled=false Title=Linux Vendor Firmware Service (testing) Keyring=gpg MetadataURI=https://cdn.fwupd.org/downloads/firmware-testing.xml.gz ReportURI=https://fwupd.org/lvfs/firmware/report Username= Password= OrderBefore=lvfs,fwupd ApprovalRequired=false fwupd-1.2.14/data/remotes.d/lvfs-testing.metainfo.xml000066400000000000000000000027461402665037500225260ustar00rootroot00000000000000 org.freedesktop.fwupd.remotes.lvfs-testing Linux Vendor Firmware Service (testing firmware) CC0-1.0

The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer.

This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails.

Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$.

fwupd-1.2.14/data/remotes.d/lvfs.conf000066400000000000000000000004611402665037500173670ustar00rootroot00000000000000[fwupd Remote] # this remote provides metadata and firmware marked as 'stable' from the LVFS Enabled=true Title=Linux Vendor Firmware Service Keyring=gpg MetadataURI=https://cdn.fwupd.org/downloads/firmware.xml.gz ReportURI=https://fwupd.org/lvfs/firmware/report OrderBefore=fwupd ApprovalRequired=false fwupd-1.2.14/data/remotes.d/lvfs.metainfo.xml000066400000000000000000000023221402665037500210410ustar00rootroot00000000000000 org.freedesktop.fwupd.remotes.lvfs Linux Vendor Firmware Service (stable firmware) CC0-1.0

The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer.

Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$.

fwupd-1.2.14/data/remotes.d/meson.build000066400000000000000000000026111402665037500177070ustar00rootroot00000000000000if get_option('daemon') and get_option('lvfs') install_data([ 'lvfs.conf', 'lvfs-testing.conf', ], install_dir : join_paths(sysconfdir, 'fwupd', 'remotes.d') ) i18n.merge_file( input: 'lvfs.metainfo.xml', output: 'org.freedesktop.fwupd.remotes.lvfs.metainfo.xml', type: 'xml', po_dir: join_paths(meson.source_root(), 'po'), data_dirs: join_paths(meson.source_root(), 'po'), install: true, install_dir: join_paths(get_option('datadir'), 'fwupd', 'metainfo') ) i18n.merge_file( input: 'lvfs-testing.metainfo.xml', output: 'org.freedesktop.fwupd.remotes.lvfs-testing.metainfo.xml', type: 'xml', po_dir: join_paths(meson.source_root(), 'po'), data_dirs: join_paths(meson.source_root(), 'po'), install: true, install_dir: join_paths(get_option('datadir'), 'fwupd', 'metainfo') ) endif install_data('README.md', install_dir : join_paths(datadir, 'fwupd', 'remotes.d', 'vendor', 'firmware') ) # replace @datadir@ con2 = configuration_data() con2.set('datadir', datadir) configure_file( input : 'vendor.conf', output : 'vendor.conf', configuration : con2, install: true, install_dir: join_paths(sysconfdir, 'fwupd', 'remotes.d'), ) configure_file( input : 'vendor-directory.conf', output : 'vendor-directory.conf', configuration : con2, install: true, install_dir: join_paths(sysconfdir, 'fwupd', 'remotes.d'), ) fwupd-1.2.14/data/remotes.d/vendor-directory.conf000066400000000000000000000004461402665037500217170ustar00rootroot00000000000000[fwupd Remote] # this remote provides dynamically generated metadata shipped by the OS vendor and can # be found in @datadir@/fwupd/remotes.d/vendor/firmware Enabled=false Title=Vendor (Automatic) Keyring=none MetadataURI=file://@datadir@/fwupd/remotes.d/vendor/firmware ApprovalRequired=false fwupd-1.2.14/data/remotes.d/vendor.conf000066400000000000000000000004721402665037500177140ustar00rootroot00000000000000[fwupd Remote] # this remote provides metadata shipped by the OS vendor and can be found in # @datadir@/fwupd/remotes.d/vendor and firmware in @datadir@/fwupd/remotes.d/vendor/firmware Enabled=false Title=Vendor Keyring=none MetadataURI=file://@datadir@/fwupd/remotes.d/vendor/vendor.xml.gz ApprovalRequired=false fwupd-1.2.14/data/tests/000077500000000000000000000000001402665037500150075ustar00rootroot00000000000000fwupd-1.2.14/data/tests/builder/000077500000000000000000000000001402665037500164355ustar00rootroot00000000000000fwupd-1.2.14/data/tests/builder/meson.build000066400000000000000000000005341402665037500206010ustar00rootroot00000000000000if get_option('tests') tar = find_program('tar') builder_test_firmware = custom_target('builder-test-firmware', input : [ 'source.bin', 'startup.sh', ], output : 'firmware.tar', command : [ tar, '--xform', 's,.*/,,', '--absolute-names', '--create', '--file', '@OUTPUT@', '@INPUT@', ], ) endif fwupd-1.2.14/data/tests/builder/source.bin000066400000000000000000000000261402665037500204250ustar00rootroot00000000000000running in the sandboxfwupd-1.2.14/data/tests/builder/startup.sh000077500000000000000000000000551402665037500204760ustar00rootroot00000000000000#/bin/sh cat source.bin | rev > firmware.bin fwupd-1.2.14/data/tests/colorhug/000077500000000000000000000000001402665037500166315ustar00rootroot00000000000000fwupd-1.2.14/data/tests/colorhug/README.md000066400000000000000000000004371402665037500201140ustar00rootroot00000000000000# Generating the p7b file manually: certtool --p7-detached-sign --p7-time \ --load-privkey LVFS/pkcs7/secure-lvfs.rhcloud.com.key \ --load-certificate LVFS/pkcs7/secure-lvfs.rhcloud.com_signed.pem \ --infile firmware.bin \ --outfile firmware.bin.p7b fwupd-1.2.14/data/tests/colorhug/firmware.bin000066400000000000000000000175001402665037500211420ustar00rootroot000000000000001 (~1 p~ 1(00'012!1 00@012!100@012!1@000001,!~ 1*! 02=!4W(4  9GHY(KJj(0G91H!!!3{(3  9GH}(ML(0G")! 0000@001!1# !Gw)0(1T!1(#!1!1)1'!1(#!18!1)1p!1!G")#!1!1)0 >000! ()00>00! ()0 >000! ()0!0000 >000! ))!^ !_ 1#1 W! V! U! T!0 >000! 5))$0!G)#!:N)!:N) 0!Gr)0?0@001x#1!GU)0?0000G1$1!H) )T:(:(:(:(:(:(:( :(:(:(/: )::) :@):(t)!3)3  9GH)ON)UG"!TG" 0!G@0H011#1!I0!G@0H011#!I B:*@98*! A:)! *! B@9:D9:D90[!2 : ,*D9>[D9>8*D9j>[D9>[_E*0[F*`N*`D9>S*`D9j>[A:t*`l*f*D9>i*D9>0`[0[`*0`[`00`[001$1* `0 [*`**D9>*D9>;0`[00`[001$1*;0 `[ D9>0=[\0]^[!*=00{0**! 2:+=+0 g!=+0 g=1!1=!+ 3+1%10 g!0 g=?+1!10 g=V+P+s0 0001$10 g=\+1!1=o+0 0001$1=0!2w+ =+ + go g 9h!0 g= o+!8>+!8>9 0g9g!+1%1+r0 0o0g001$10 g0i+=}+ 0 000 X0000Wc+Vb+Ua+T`,d + , X00=0=0=+Wc,Vb,Ua,T` ,00000000Wc6,Vb6,Ua6,T`b,d Y, U,00=0=0=hgfeU,0000 X00=0=0=(,1T!1 Xd >0>[ZYXhgfe1C"1t srqh_,g^,f],e\,0000hgfe ,! H!.:, ,!,! ,D9>,D9j>qr,r,0q,@9:,:,:,,!H00@0q44W44q4484f404644444444 44)4444444 444444444 4!44444"4444444@4444444@444444 444444)4@444&444u444@44444)4@444444H44u44g44h44s44k44i44 44L44t44d44.4444C44o44l44o44r44H44u44g44A44L44S4444444444?4'44444444444 44!=00u-st00=0qu-0=0{00v-v05>-q5 >0?0???0q0v-==-0q!-0q!0q0v!.vj>v>v>v>0q0v.(0q 0=@00q r00q! nA?vt!$L.s#R.#s0>v  @?sA?t=vu}.uH> s0s0se.0>.0| sH00|>s?t? n.0|s0|s0| s@00|>s?t?0|s! 1p% @9:DA:./!C:/#00 0.:0!0000q/ C!:.:.:// @ 9:@/0!0000q0q! Cq!0!0000q0q! Bq! A:U/: /:&/ :U/:/:4/U/!0 0]q/[\00=0{]a/ 0000 001!1= 0^/^>^>0{0^/0000001!1=(0{ 0{n{B{!/0{0 00{001$ 0{! x/R1u"1/ R1u" R>0=yzPQx1,1,1,1,1,1m,1v,1,1|,1y,1,1,1,  9 -)4d1 3)4q 44444444K)Q)!9$:I)Q)!  905 Z)9q 00 f) f)20b)  905 v) r 9qr9 q9qr69 q9q!t0 0001$=0{=0{! u0 0001$r9 q9qr69 q9qrq00;0v)0uwxs00=)=* n:*j:*0nq=0q m: *1<& !A*0| w@00|>w?x?0|w0 000qW*u v=w=x=05 X*06 _*tsrqL* SRQP 0rq05 }*v05>*s5r05>*t5uw*wv>*wvj>rww>=*01a!1018!101!101!11%1!0W*018!1_01a!1018!1_01a!10!V0W*d1"11= 1*! ! 0jb0b0jb0b0|b!1e$10 0001$11&q8+vj>:+v>tE+0r0w>t?u?sw t@0wt0wtqn+0vj>r+0v>tw@0r0q+0?0q0rvu+0?0s0tvu+006 +tx+sw+#wqxr=uvU00 00=+#0! m:+0| tH00|>t?u?0|t!B G+!A F+G! F!19&H00 j>t?u?0jtJ,1p!1 X!01!1018!!;1!0 X01'10 X@0Y011# Z!1& T:P,d,S:+,:G,:d,w:d,:&,:,d, @ 9:,!0 a!1#'1V'0!0@ 1$1! D>!0@ ! BD>a00001$ A 015>@0r0q,006 ,ty,sx-#rq00=xuv0x>0y=tw,sv-x>uv0U0000=,#0 j000t!2:3- B=.-0/-0t! m:j-H00j>t?u?19&1!=:U-0 jta-0 jt0 jt! !0q0q m:-j 0jqm:!0 n q@00n>q?r?0nq! 0| q@00|>q?r?0|q o9..o6?905>-c5 >dnc0cn 9 :+.0f.n>@?A?cdf@>en>0A1 0c0f-@00n>c?d?1"1"0 oc:1%0q0!B0AW.AqO. W.=:W.0q0>s  @?qA?r=s j qH0q!@..>?<q00=0q0y.>?<q00=0q0.=!..0v !.1#!0| v@00|>v?w?0|v0 jv . !.1#1! 1p%0v 0| v@00|>v?w?0|v! 1p% @:0q!/0!000 B5 0q0r=?!??>0qrr s?>0qrr s0 B/B5 0q0r=?!?>?qr!! C:./:8/:h//fwupd-1.2.14/data/tests/colorhug/firmware.bin.asc000066400000000000000000000007311402665037500217050ustar00rootroot00000000000000-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJVLPMHAAoJEK2KUo/sRIgeAxAH/jPY7c2qrG4UEsZXgUFxMUQe QEufh3cK9cv8kA7SAzpSHy6M0rNanC2vCqcc/fTJI/yBRfBjPPZYEsQgwpB/8m9y wiTPRuQySwCKsH+ZXNh3j6x8Oaf3DTiO7bJI/M3sOb4fdvb0Csp910g67Nt+HtMw I5EUM0uvMquZTUygp9B6BBJv8xRKtCNgqvPhyoDZKxKrPzaFwvb7BY50Q03LymU6 hQUIkjHIvMcTljNocOZNvTBHvEGB2BiBb60QhAXYyNfDrS58pm2JHfw/pgOuQTzT 3Lw9qmedRXbWR95u/piUmyUsY5ey75lD08U/2aE9RLBZ9xR17u1mAgyLGoIMYEk= =ZdoH -----END PGP SIGNATURE----- fwupd-1.2.14/data/tests/colorhug/firmware.bin.p7b000066400000000000000000000043251402665037500216320ustar00rootroot00000000000000-----BEGIN PKCS7----- MIIGYAYJKoZIhvcNAQcCoIIGUTCCBk0CAQExDTALBglghkgBZQMEAgEwCwYJKoZI hvcNAQcBoIIESDCCBEQwggKsoAMCAQICDFmdjlgcgXiV33RlVTANBgkqhkiG9w0B AQsFADA6MRAwDgYDVQQDEwdMVkZTIENBMSYwJAYDVQQKEx1MaW51eCBWZW5kb3Ig RmlybXdhcmUgUHJvamVjdDAeFw0xNzA4MDEwMDAwMDBaFw0xOTA4MDEwMDAwMDBa MBkxFzAVBgNVBAMTDlJpY2hhcmQgSHVnaGVzMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA5XlsYGdD5isOAEim4tRR9usJa8C4Gs3TUPfe5EfXcIT44dJr plVcXpH2Wau/Pbcvc/2cY/bZmgcRMgw8O/4HoJyCCCKfjCfT6yN9BlLnxAgZVLSw QT2d2JW0m5bY/VgZNwdNZWb+fMnPDx7JMCjtdpUpwQ0R6hwrryRt+6zFyhDayCCL GOsxpmo7Fc9ix/nP5DEcPjU6Bofz0jFFMesod8babaQSWm2b/QN7aTgkrPjslC+p BkTLq7IrndgQzLKI9bXn++LFKE2Srm0nHZ6DapKCgsSE3UOqDGtKTUf86aT2IGnV 5JzTZ/HZk/sGqAyS2wb5m13rJfbzkKnf9c14qwIDAQABo4HqMIHnMAwGA1UdEwEB /wQCMAAwRQYDVR0RBD4wPIYlaHR0cHM6Ly9zZWN1cmUtbHZmcy5yaGNsb3VkLmNv bS9sdmZzL4ETcmljaGFyZEBodWdoc2llLmNvbTATBgNVHSUEDDAKBggrBgEFBQcD AzAPBgNVHQ8BAf8EBQMDB4AAMB0GA1UdDgQWBBSZpooSP4z6IVWsXkoUjpByAh5D fzAfBgNVHSMEGDAWgBSxjerkI6d+CY617jHgat2eNDdlrDAqBgNVHR8EIzAhMB+g HaAbhhlodHRwOi8vd3d3LmZ3dXBkLm9yZy9wa2kvMA0GCSqGSIb3DQEBCwUAA4IB gQBSXRGZB6YR8wTyuOdEelRcJj45Mz5tiuuCfei8ZOTyFzkGjnRno0Dl57tnmUmX ufN2Rb9yzXBGHmSTXT6j2uVg+U1xevPAVCWlIslhwxJcqncfpALxL7TwVL5PpJls /Ao7y/KkS5Bxd8u45A2/wIFkawxn/X0nRmwNh6jF9m3+NSwCv3QxYdgGcfhzD96p 6hG+DuXT97h0lJ3gJJDPbVkWTvuhoNo+iEz8fAfSmlk12HDQ+oQIGRgpFZYHREFr 2/A2HoBfAPFVdmRfYWNrxODrVg3tQEHmtxG7HIHocyRSVzqd31yJKgkwh4I9meUY rCOf0hhMjWmxiviPKJx4SEcNg7Ye8Ib2OtXxcQbZ71ax57dUyVZZXEcfR3KjBuFp vY6QnVF5D3NsyV5q3M1VV8XRh9ELRafruX+Ygx8NLkDPKqFGZh0xKDzr55gJF9q8 rfuHjQ/cd5tokRMI1qlGymbQ/bWgsLBO2MOWeZezITBO1ZVbz6QMJ4YnvHug8nsZ /SkxggHeMIIB2gIBATBKMDoxEDAOBgNVBAMTB0xWRlMgQ0ExJjAkBgNVBAoTHUxp bnV4IFZlbmRvciBGaXJtd2FyZSBQcm9qZWN0AgxZnY5YHIF4ld90ZVUwCwYJYIZI AWUDBAIBoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx DxcNMTcwODIzMTQzNTQ1WjAvBgkqhkiG9w0BCQQxIgQgoZZQTQmHHaT32DuHS1AP jubgYZq3mfB0gUsxbYj5b38wDQYJKoZIhvcNAQEBBQAEggEAWc7kxSri1v+c+N8h S8cerVmAPBm150DjB58F3gxSl91gs/z8d1uWOx88eX0DjOU4C7sQj7E9WiZSPcvb z2KvXqg7MJy+ev9wXPwDqqPtsVZdLKd665JqF7kfSXxpMFzutu/NxW7UUUrKot4v d93NlAEXmjjuQ8V6STtYapxzyuWGXThI/K89kXaMvzmqTYQ4S9+98sXG1PMX69zm z00PT+rL2QGMsZCSUcnE/u38s0q7uCEfBB9uoq5QIECYch65ezX3H2GqVcKPG4M3 6Ttko+W01+2IIPN02ZHPqXqEw8diTiMYS5HVRD7nVs5TTxNNB+rAIBR+mJJBkxin 7MLHjQ== -----END PKCS7----- fwupd-1.2.14/data/tests/colorhug/firmware.metainfo.xml000066400000000000000000000023361402665037500227740ustar00rootroot00000000000000 com.hughski.ColorHugALS.firmware ColorHugALS Firmware Firmware for the ColorHugALS Ambient Light Sensor

Updating the firmware on your ColorHugALS device improves performance and adds new features.

84f40464-9272-4ef7-9399-cd95f12da696 12345678-1234-1234-1234-123456789012 http://www.hughski.com/ CC0-1.0 GPL-2.0+ richard_at_hughsie.com Hughski Limited

This stable release fixes the following bugs:

  • Fix the return code from GetHardwareVersion
  • Scale the output of TakeReadingRaw by the datasheet values
fwupd-1.2.14/data/tests/colorhug/meson.build000066400000000000000000000014041402665037500207720ustar00rootroot00000000000000colorhug_test_firmware = custom_target('colorhug-test-firmware', input : [ 'firmware.bin', 'firmware.bin.asc', 'firmware.metainfo.xml', ], output : 'colorhug-als-3.0.2.cab', command : [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], ) if get_option('pkcs7') # generate self-signed detached signature colorhug_pkcs7_signature = custom_target('firmware.bin.p7c', input: 'firmware.bin', output: 'firmware.bin.p7c', command: [certtool, '--p7-detached-sign', '--p7-time', '--load-privkey', pkcs7_privkey, '--load-certificate', pkcs7_certificate, '--infile', '@INPUT@', '--outfile', '@OUTPUT@'], ) endif fwupd-1.2.14/data/tests/dmi/000077500000000000000000000000001402665037500155605ustar00rootroot00000000000000fwupd-1.2.14/data/tests/dmi/tables/000077500000000000000000000000001402665037500170325ustar00rootroot00000000000000fwupd-1.2.14/data/tests/dmi/tables/DMI000066400000000000000000000047331402665037500173750ustar00rootroot00000000000000*Qd44A!Intel(R) Core(TM) i7-4600U CPU @ 2.10GHzIntel(R) CorporationNoneCPU Socket - U3E1NoneNone @@L1-Cache @@L1-Cache@@L2-Cache@@L3-Cache"@@@@ChannelABANK 0ElpidaNoneNoneEDJ8416E6MB-GN-F " @@ChannelB-DIMM0BANK 2Samsung15AF7001NoneM471B1G73QH0-YK0  Intel_ASFIntel_ASF_001   ZS RNLENOVO20ARS19C0CThinkPad T440sPF01VVCALENOVO_MT_20AR_BU_Think_FM_ThinkPad T440sThinkPad T440s   LENOVO20ARS19C0CNot Defined1ZSUK45C1DZNot AvailableNot Available LENOVONot AvailablePF01VVCANo Asset InformationLENOVO_MT_20AR_BU_Think_FM_ThinkPad T440s Not AvailableUSB 1 Not AvailableUSB 2 Not AvailableUSB 3 Not AvailableUSB 4 Not AvailableUSB 5 Not AvailableUSB 6 Not AvailableUSB 7 Not AvailableUSB 8  Not AvailableEthernet Not AvailableExternal Monitor Not AvailableMini DisplayPort Not AvailableDisplayPort/DVI-D Not AvailableDisplayPort/HDMI Not AvailableHeadphone/Microphone Combo Jack1 Not AvailableHeadphone/Microphone Combo Jack2 Media Card Slot~SmartCard Slot  SimCard Slot !IBM Embedded Security hardware " #en-US$ \+;wD FrontSONY45N111103.01LiP%0*fD RearSANYO45N177703.01LION&'()TVT-Enablement*ZZ+STM TPM INFOSystem Reserved,$AMT@-5 C  Z&vPro.KHOIHGIUCCHHIIS/TPBAY I/O @0  LENOVOGJET75WW (2.25 )03/28/2014!1  C2LENOVO ca,tR)D/3LENOVO (uu#/ i"_8 ?4LENOVO 5LENOVO 6LENOVO MS 7LENOVO 8LENOVO 9":6;TP<LENOVO GJHT25WW11/07/2013+=LENOVO /fwupd-1.2.14/data/tests/dmi/tables/smbios_entry_point000066400000000000000000000000371402665037500227030ustar00rootroot00000000000000_SM__DMI_ Ӽ>'fwupd-1.2.14/data/tests/dmi/tables64/000077500000000000000000000000001402665037500172045ustar00rootroot00000000000000fwupd-1.2.14/data/tests/dmi/tables64/DMI000066400000000000000000000133051402665037500175420ustar00rootroot00000000000000ڲ@""##(())**++,,--..@@AABBCCPPUUWW\\]]eeffmmnn}}ڲ@  ++,,--..558899DDEEFFGGJJKKLLMMRRSSuuvv{{||ڲ@--..22335566JJKKLLddeeffgghhiillmmnnڲ@  %%&&))**++,,8899::;;AAڲ@BBCCFFGGHHIIJJMMNNOOPPWWXX[[\\]]^^__``aabbffggiijjkkllmmnnttuuvvwwxxyyڲ@11223366778899::;;@@ڲ@AABBCCDDEEFFGGPPQQRRaabb{{||}}~~J@J@K@K@L@L@ Yڲ@ ""0022@@BBPPRR?cDell Inc.99.01.2108/21/2017DELLR0QO2G2XPSDell Inc.XPS 13 9365077A2R0Q2G2 Dell Inc.0DVT6MA00/2R0Q2G2/CN1296374E0065/Dell Inc.2R0Q2G2Convertible0dl A5CPU 1Intel(R) CorporationIntel(R) Core(TM) i7-7Y75 CPU @ 1.30GHzTo Be Filled By O.E.M.To Be Filled By O.E.M.To Be Filled By O.E.M. L1 Cache L2 Cache L3 Cache  JKBTP1 - KeyboardNone J1A2BVideo J3A2HDMI JUSB1USB1 JUSB2USB2 JTypeCUSB3 JSD1Cardreader JHP1Audio Jack JeDP1-eDPNone JNGFF1 - WLAN/BT/Wigig CONNNone JNGFF2 - HDDNone JSPK1 - SpeakerNone JAPS1 - Automatic PowerNone JDEG1 - Debug PORTNone JRTC1 - RTCNone   PCI-Express 0  PCI-Express 4  PCI-Express 5  PCI-Express 8  "Intel HD Graphics"  Dell System1[077A]3[1.0]12[www.dell.com]14[1]15[0]  en-USIntel(R) Silicon View TechnologyFirmware Version Info$MEI(@@ @KKSystem Board Memory (UD2)0Micron000000000000000000MT52L1G32D4PG-107 (@@ @KKSystem Board Memory (UD2)0Micron000000000000000000MT52L1G32D4PG-107 nptald IG0 tald IGptali dCPU Thermal Sensora dOther Thermal Sensora dOther Thermal Sensord dHdd Thermal Sensorg dAmbient Thermal Sensorh dMemory Thermal Sensor 1 ) )Onboard IGD) )Onboard LAN) )Onboard SOUND) ) Onboard SATA CONTROLLERd #J Sys. Battery BaySMP01/03/20170CE4DELL HMPFH621.0LiPCN0HMPFHSLW00714I2DDA00 ~ Dell Inc.2R0Q2G2Docking Station$AMT@5 V &vPro T   ; A$BHP !"D QE0@CMEI1MEI2MEI3^Reference Code - CPUuCode VersionTXT ACM version   Reference Code - ME 11.0MEBx versionME Firmware VersionCorporate SKUK !! >4 > 4Reference Code - SKL PCHPCH-CRID StatusDisabledPCH-CRID Original ValuePCH-CRID New ValueOPROM - RST - RAIDSKL PCH H Bx Hsio VersionSKL PCH H Dx Hsio VersionKBL PCH H Ax Hsio VersionSKL PCH LP Bx Hsio VersionSKL PCH LP Cx Hsio Version6Reference Code - SA - System AgentReference Code - MRCSA - PCIe VersionSA-CRID StatusDisabledSA-CRID Original ValueSA-CRID New ValueOPROM - VBIOSg f Lan Phy VersionSensor Firmware VersionDebug Mode StatusDisabledPerformance Mode StatusDisabledDebug Use USB(Disabled:Serial)DisabledICC Overclocking VersionUNDI VersionEC FW VersionGOP VersionBIOS Guard VersionBase EC FW VersionEC-EC Protocol VersionRoyal Park VersionBP1.3.3.0_RP02Platform Version 0Memory Init CompleteEnd of DXE PhaseBIOS Boot Complete z2017041420170426 "Intel Corp.""2089" "02@BPR"Y_SIDARMy7y63nZgZ077Afwupd-1.2.14/data/tests/dmi/tables64/smbios_entry_point000066400000000000000000000000301402665037500230460ustar00rootroot00000000000000_SM3_ jfwupd-1.2.14/data/tests/firmware-base-uri.conf000066400000000000000000000002461402665037500212010ustar00rootroot00000000000000[fwupd Remote] Enabled=true Type=download Keyring=gpg MetadataURI=https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz FirmwareBaseURI=https://my.fancy.cdn/ fwupd-1.2.14/data/tests/firmware-nopath.conf000066400000000000000000000002001402665037500207510ustar00rootroot00000000000000[fwupd Remote] Enabled=true Type=download Keyring=gpg MetadataURI=https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz fwupd-1.2.14/data/tests/fwupd/000077500000000000000000000000001402665037500161345ustar00rootroot00000000000000fwupd-1.2.14/data/tests/fwupd/daemon.conf000066400000000000000000000000631402665037500202450ustar00rootroot00000000000000[fwupd] ArchiveSizeMax=5 ApprovedFirmware=deadbeef fwupd-1.2.14/data/tests/history_v1.db000066400000000000000000000300001402665037500174160ustar00rootroot00000000000000SQLite format 3@ . == itablehistoryhistoryCREATE TABLE history ( device_id TEXT PRIMARY KEY, update_state INTEGER DEFAULT 0, update_error TEXT, filename TEXT, display_name TEXT, plugin TEXT, device_created INTEGER DEFAULT 0, device_modified INTEGER DEFAULT 0, checksum TEXT DEFAULT NULL, flags INTEGER DEFAULT 0, metadata TEXT DEFAULT NULL, guid_default TEXT DEFAULT NULL, version_old TEXT, version_new TEXT)-Aindexsqlite_autoindex_history_1history 7]2ba16d10df45823dd4494ff10a0bfccfef512c9d +] 2ba16d10df45823dd4494ff10a0bfccfef512c9dfwupd-1.2.14/data/tests/hwids/000077500000000000000000000000001402665037500161255ustar00rootroot00000000000000fwupd-1.2.14/data/tests/hwids/bios_major_release000066400000000000000000000000031402665037500216650ustar00rootroot0000000000000004 fwupd-1.2.14/data/tests/hwids/bios_minor_release000066400000000000000000000000031402665037500217010ustar00rootroot0000000000000006 fwupd-1.2.14/data/tests/hwids/bios_vendor000066400000000000000000000000311402665037500203530ustar00rootroot00000000000000American Megatrends Inc. fwupd-1.2.14/data/tests/hwids/bios_version000066400000000000000000000000051402665037500205440ustar00rootroot000000000000001201 fwupd-1.2.14/data/tests/hwids/board_name000066400000000000000000000000271402665037500201360ustar00rootroot00000000000000To be filled by O.E.M. fwupd-1.2.14/data/tests/hwids/board_vendor000066400000000000000000000000271402665037500205130ustar00rootroot00000000000000To be filled by O.E.M. fwupd-1.2.14/data/tests/hwids/chassis_type000066400000000000000000000000021402665037500205360ustar00rootroot000000000000003 fwupd-1.2.14/data/tests/hwids/product_family000066400000000000000000000000271402665037500210700ustar00rootroot00000000000000To be filled by O.E.M. fwupd-1.2.14/data/tests/hwids/product_name000066400000000000000000000000271402665037500205270ustar00rootroot00000000000000To be filled by O.E.M. fwupd-1.2.14/data/tests/hwids/product_sku000066400000000000000000000000041402665037500204040ustar00rootroot00000000000000SKU fwupd-1.2.14/data/tests/hwids/sys_vendor000066400000000000000000000000271402665037500202420ustar00rootroot00000000000000To be filled by O.E.M. fwupd-1.2.14/data/tests/meson.build000066400000000000000000000004441402665037500171530ustar00rootroot00000000000000# generate private PKCS7 key certtool = find_program('certtool') pkcs7_privkey = custom_target('test-privkey.pem', output: 'test-privkey.pem', command: [certtool, '--generate-privkey', '--outfile', '@OUTPUT@'], ) subdir('builder') subdir('pki') subdir('colorhug') subdir('missing-hwid') fwupd-1.2.14/data/tests/metadata.xml000066400000000000000000000007731402665037500173200ustar00rootroot00000000000000 org.fwupd.8330a096d9f1af8567c7374cb8403e1ce9cf3163.device 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e

Applying will enable UEFI firmware reporting

fwupd-1.2.14/data/tests/missing-hwid/000077500000000000000000000000001402665037500174115ustar00rootroot00000000000000fwupd-1.2.14/data/tests/missing-hwid/firmware.bin000066400000000000000000000175001402665037500217220ustar00rootroot000000000000001 (~1 p~ 1(00'012!1 00@012!100@012!1@000001,!~ 1*! 02=!4W(4  9GHY(KJj(0G91H!!!3{(3  9GH}(ML(0G")! 0000@001!1# !Gw)0(1T!1(#!1!1)1'!1(#!18!1)1p!1!G")#!1!1)0 >000! ()00>00! ()0 >000! ()0!0000 >000! ))!^ !_ 1#1 W! V! U! T!0 >000! 5))$0!G)#!:N)!:N) 0!Gr)0?0@001x#1!GU)0?0000G1$1!H) )T:(:(:(:(:(:(:( :(:(:(/: )::) :@):(t)!3)3  9GH)ON)UG"!TG" 0!G@0H011#1!I0!G@0H011#!I B:*@98*! A:)! *! B@9:D9:D90[!2 : ,*D9>[D9>8*D9j>[D9>[_E*0[F*`N*`D9>S*`D9j>[A:t*`l*f*D9>i*D9>0`[0[`*0`[`00`[001$1* `0 [*`**D9>*D9>;0`[00`[001$1*;0 `[ D9>0=[\0]^[!*=00{0**! 2:+=+0 g!=+0 g=1!1=!+ 3+1%10 g!0 g=?+1!10 g=V+P+s0 0001$10 g=\+1!1=o+0 0001$1=0!2w+ =+ + go g 9h!0 g= o+!8>+!8>9 0g9g!+1%1+r0 0o0g001$10 g0i+=}+ 0 000 X0000Wc+Vb+Ua+T`,d + , X00=0=0=+Wc,Vb,Ua,T` ,00000000Wc6,Vb6,Ua6,T`b,d Y, U,00=0=0=hgfeU,0000 X00=0=0=(,1T!1 Xd >0>[ZYXhgfe1C"1t srqh_,g^,f],e\,0000hgfe ,! H!.:, ,!,! ,D9>,D9j>qr,r,0q,@9:,:,:,,!H00@0q44W44q4484f404644444444 44)4444444 444444444 4!44444"4444444@4444444@444444 444444)4@444&444u444@44444)4@444444H44u44g44h44s44k44i44 44L44t44d44.4444C44o44l44o44r44H44u44g44A44L44S4444444444?4'44444444444 44!=00u-st00=0qu-0=0{00v-v05>-q5 >0?0???0q0v-==-0q!-0q!0q0v!.vj>v>v>v>0q0v.(0q 0=@00q r00q! nA?vt!$L.s#R.#s0>v  @?sA?t=vu}.uH> s0s0se.0>.0| sH00|>s?t? n.0|s0|s0| s@00|>s?t?0|s! 1p% @9:DA:./!C:/#00 0.:0!0000q/ C!:.:.:// @ 9:@/0!0000q0q! Cq!0!0000q0q! Bq! A:U/: /:&/ :U/:/:4/U/!0 0]q/[\00=0{]a/ 0000 001!1= 0^/^>^>0{0^/0000001!1=(0{ 0{n{B{!/0{0 00{001$ 0{! x/R1u"1/ R1u" R>0=yzPQx1,1,1,1,1,1m,1v,1,1|,1y,1,1,1,  9 -)4d1 3)4q 44444444K)Q)!9$:I)Q)!  905 Z)9q 00 f) f)20b)  905 v) r 9qr9 q9qr69 q9q!t0 0001$=0{=0{! u0 0001$r9 q9qr69 q9qrq00;0v)0uwxs00=)=* n:*j:*0nq=0q m: *1<& !A*0| w@00|>w?x?0|w0 000qW*u v=w=x=05 X*06 _*tsrqL* SRQP 0rq05 }*v05>*s5r05>*t5uw*wv>*wvj>rww>=*01a!1018!101!101!11%1!0W*018!1_01a!1018!1_01a!10!V0W*d1"11= 1*! ! 0jb0b0jb0b0|b!1e$10 0001$11&q8+vj>:+v>tE+0r0w>t?u?sw t@0wt0wtqn+0vj>r+0v>tw@0r0q+0?0q0rvu+0?0s0tvu+006 +tx+sw+#wqxr=uvU00 00=+#0! m:+0| tH00|>t?u?0|t!B G+!A F+G! F!19&H00 j>t?u?0jtJ,1p!1 X!01!1018!!;1!0 X01'10 X@0Y011# Z!1& T:P,d,S:+,:G,:d,w:d,:&,:,d, @ 9:,!0 a!1#'1V'0!0@ 1$1! D>!0@ ! BD>a00001$ A 015>@0r0q,006 ,ty,sx-#rq00=xuv0x>0y=tw,sv-x>uv0U0000=,#0 j000t!2:3- B=.-0/-0t! m:j-H00j>t?u?19&1!=:U-0 jta-0 jt0 jt! !0q0q m:-j 0jqm:!0 n q@00n>q?r?0nq! 0| q@00|>q?r?0|q o9..o6?905>-c5 >dnc0cn 9 :+.0f.n>@?A?cdf@>en>0A1 0c0f-@00n>c?d?1"1"0 oc:1%0q0!B0AW.AqO. W.=:W.0q0>s  @?qA?r=s j qH0q!@..>?<q00=0q0y.>?<q00=0q0.=!..0v !.1#!0| v@00|>v?w?0|v0 jv . !.1#1! 1p%0v 0| v@00|>v?w?0|v! 1p% @:0q!/0!000 B5 0q0r=?!??>0qrr s?>0qrr s0 B/B5 0q0r=?!?>?qr!! C:./:8/:h//fwupd-1.2.14/data/tests/missing-hwid/firmware.metainfo.xml000066400000000000000000000007041402665037500235510ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 9342d47a-1bab-5709-9869-c840b2eac501 fwupd-1.2.14/data/tests/missing-hwid/firmware2.metainfo.xml000066400000000000000000000005531402665037500236350ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 fwupd-1.2.14/data/tests/missing-hwid/meson.build000066400000000000000000000007231402665037500215550ustar00rootroot00000000000000hwid_test_firmware = custom_target('hwid-test-firmware', input : [ 'firmware.bin', 'firmware.metainfo.xml', ], output : 'hwid-1.2.3.cab', command : [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], ) noreqs_test_firmware = custom_target('noreqs-test-firmware', input : [ 'firmware.bin', 'firmware2.metainfo.xml', ], output : 'noreqs-1.2.3.cab', command : [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], ) fwupd-1.2.14/data/tests/pki/000077500000000000000000000000001402665037500155725ustar00rootroot00000000000000fwupd-1.2.14/data/tests/pki/GPG-KEY-Linux-Vendor-Firmware-Service000077700000000000000000000000001402665037500341522../../pki/GPG-KEY-Linux-Vendor-Firmware-Serviceustar00rootroot00000000000000fwupd-1.2.14/data/tests/pki/LVFS-CA.pem000077700000000000000000000000001402665037500222722../../pki/LVFS-CA.pemustar00rootroot00000000000000fwupd-1.2.14/data/tests/pki/meson.build000066400000000000000000000005741402665037500177420ustar00rootroot00000000000000# generate certificate pkcs7_config = join_paths(meson.current_source_dir(), 'test.cfg') pkcs7_certificate = custom_target('test.pem', input: pkcs7_privkey, output: 'test.pem', command: [certtool, '--generate-self-signed', '--template', pkcs7_config, '--load-privkey', '@INPUT@', '--outfile', '@OUTPUT@'], ) fwupd-1.2.14/data/tests/pki/test.cfg000066400000000000000000000001351402665037500172310ustar00rootroot00000000000000organization = "Hughski Limited" expiration_days = -1 email = "info@hughski.com" signing_key fwupd-1.2.14/data/tests/quirks.d/000077500000000000000000000000001402665037500165475ustar00rootroot00000000000000fwupd-1.2.14/data/tests/quirks.d/merged.quirk000066400000000000000000000000511402665037500210630ustar00rootroot00000000000000[USB\VID_0A5C&PID_6412] Flags = MERGE_ME fwupd-1.2.14/data/tests/quirks.d/tests.quirk000066400000000000000000000005221402665037500207650ustar00rootroot00000000000000[USB\VID_0A5C&PID_6412] Flags= ignore-runtime [USB\VID_FFFF&PID_FFFF] Flags = [ACME Inc.=True] Test = awesome [CORP*] Test = town [DeviceInstanceId=USB\VID_0BDA&PID_1100] Flags = clever Name = Hub Children = FuDevice|USB\VID_0763&PID_2806&I2C_01 [DeviceInstanceId=USB\VID_0763&PID_2806&I2C_01] Name = HDMI Flags = updatable,internal fwupd-1.2.14/data/tests/remotes.d/000077500000000000000000000000001402665037500167075ustar00rootroot00000000000000fwupd-1.2.14/data/tests/remotes.d/broken.conf000066400000000000000000000001221402665037500210310ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/broken.xml.gz fwupd-1.2.14/data/tests/remotes.d/directory.conf000066400000000000000000000001411402665037500215560ustar00rootroot00000000000000[fwupd Remote] Enabled=true Keyring=none MetadataURI=file:///tmp/fwupd-self-test/var/cache/fwupd fwupd-1.2.14/data/tests/remotes.d/stable.conf000066400000000000000000000001171402665037500210270ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/stable.xml fwupd-1.2.14/data/tests/remotes.d/testing.conf000066400000000000000000000001461402665037500212340ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/testing.xml ApprovalRequired=true fwupd-1.2.14/data/tests/spawn.sh000077500000000000000000000002671402665037500165030ustar00rootroot00000000000000#/bin/sh echo "this is a test" sleep 1 echo "this is another line1" echo "this is another line2" echo "this is another line3" echo "this is another line4" sleep 1 echo "done!" exit 0 fwupd-1.2.14/data/tests/thunderbolt/000077500000000000000000000000001402665037500173415ustar00rootroot00000000000000fwupd-1.2.14/data/tests/thunderbolt/COPYING000066400000000000000000000027321402665037500204000ustar00rootroot00000000000000Copyright(c) 2017 Intel Corporation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fwupd-1.2.14/data/tests/thunderbolt/minimal-fw-controller.bin000066400000000000000000000004771402665037500242640ustar00rootroot00000000000000$40`fwupd-1.2.14/data/tests/thunderbolt/minimal-fw.bin000066400000000000000000000005001402665037500220660ustar00rootroot00000000000000$40`fwupd-1.2.14/docs/000077500000000000000000000000001402665037500136645ustar00rootroot00000000000000fwupd-1.2.14/docs/architecture-plan.svg000066400000000000000000001667301402665037500200340ustar00rootroot00000000000000 image/svg+xml fwupd ESRT customplugins udev systemd pending.db internet system downloadcache CDN sqlite gnome-softwarefwupdmgr UpdateMetadata() GetDevices() sysfs only metadata firmware AppStream XML LVFS session embargoed metadata fwupd-1.2.14/docs/dfu-metadata-store.md000066400000000000000000000152451402665037500177030ustar00rootroot00000000000000 DFU File Format - Metadata Store Proposal ========================================= Introduction ------------ The DFU specification version 1.1 defines some target-specific data that can optionally be included in the DFU file to ease firmware deployment. These include items such as the runtime vendor, product and device release, but nothing else and with no provision for extra metadata. The DFU file specification does not specify any additional data structures allowing vendors to include additional metadata, although does provide the chance to include future additional data fields in the header in a backwards and forwards compatible way by increasing the `header_len` value. All software reading and writing DFU-format files should already be reading the footer length from the file, rather than assuming a fixed footer length of 0x10 bytes. This ensures that only the raw device firmware is sent to the device and not any additional data fields added in future versions of the DFU specification. There are valid reasons why we would want to add additional metadata into the distributed DFU file. Reasons are listed as follows: * Legal compliance, to tag a file with copyright and licensing information * Business-specific metadata, for instance the SHA-1 git commit for the source * Cryptographic information such as a SHA-256 hash or a detached GPG signature Although the original authors of the specification allowed for future additions to the specification, they only allowed us to extend the footer by 239 bytes as the `header_len` value is specified as just one byte, and 16 bytes are already specified by the specification. This would explain why some vendors are using vendor-specific file prefix data segments, for instance the DfuSe prefix specification from ST. This specification is not aiming to expand or standardize the various incompatible vendor-specific prefix specifications, but tries to squeeze the additional metadata into the existing DFU footer space which is compatible with all existing DFU-compliant software. Specification ------------- An additional structure would be present after the binary firmware data, and notionally contained within the DFU footer itself, although specified as a seporate object. The representation in memory and on disk would be as follows: uint16 signature='MD' uint8 number_of_keys uint8 key(n)_length ... key(n) (no NUL) uint8 value(n)_length ... value(n) (no NUL) If there are no metadata keys being set, it is expected that the metadata table signature is not be written to the file, and that the footer should be again 0x10 bytes in length. The signature of `MD` should also be checked before attempting to parse the metadata store structure to ensure other vendor-specific extensions are not already in use. The key and value fields should be parsed as UTF-8, although in the pursuit of space minimisation ASCII values are preferred where possible. Example ------- The following table shows an example firmware file with the payload 'DATA' set with vendor 0x1234, product 0xABCD and no metadata table. neg. offset description byte ---------------------------- 13 firmware'D' 0x44 12 firmware'A' 0x41 11 firmware'T' 0x54 10 firmware'A' 0x44 0f bcdDevice 0xFF 0e bcdDevice 0xFF 0d idProduct 0xCD 0c idProduct 0xAB 0b idVendor 0x34 0a idVendor 0x12 09 bcdDFU 0x00 08 bcdDFU 0x01 07 ucDfuSig'U' 0x55 06 ucDfuSig'F' 0x46 05 ucDfuSig'D' 0x44 04 bLength 0x10 03 dwCRC 0x52 02 dwCRC 0xB4 01 dwCRC 0xE5 00 dwCRC 0xCE The following table shows a second firmware file with the same payload but with the addition of a metadata table with a single metadata pair of `test=val`: neg. offset description byte ---------------------------- 1f firmware'D' 0x44 1e firmware'A' 0x41 1d firmware'T' 0x54 1c firmware'A' 0x44 1b ucMdSig'M' 0x4D 1a ucMdSig'D' 0x44 19 bMdLength 0x01 18 bKeyLen 0x04 17 KeyData't' 0x74 16 KeyData'e' 0x65 15 KeyData's' 0x73 14 KeyData't' 0x74 13 bValueLen 0x03 12 ValueData'v' 0x76 11 ValueData'a' 0x61 10 ValueData'l' 0x6c 0f bcdDevice 0xFF 0e bcdDevice 0xFF 0d idProduct 0xCD 0c idProduct 0xAB 0b idVendor 0x34 0a idVendor 0x12 09 bcdDFU 0x00 08 bcdDFU 0x01 07 ucDfuSig'U' 0x55 06 ucDfuSig'F' 0x46 05 ucDfuSig'D' 0x44 04 bLength 0x1C 03 dwCRC 0x1B 02 dwCRC 0x25 01 dwCRC 0x6D 00 dwCRC 0xF5 Conclusions ----------- The metadata store proposal allows us to store a small amount of metadata inside the DFU file footer. If the original specification had included just one more byte for the footer length (for instance a `uint16`, rather than a `uint8`) type then I would have proposed a key type allowing integers, IEEE floating point, and strings, and also made the number of keys and the length of keys much larger. Working with what we've been given, we can support a useful easy-to-parse extension that allows us to solve some of the real-world problems vendors are facing when trying to distribute firmware files for devices that support in-the-field device firmware upgrading. Several deliberate compomises have been made to this proposal due to the restricted space available: * The metadata table signature is just two bytes * The number of keys is limited to just 59 pairs * Keys are limited to just 233 chars maximum * Values are limited to just 233 chars maximum * Types are limited to just strings (which can includes empty strings) * Strings are written without a `NUL` trailing byte * The metadata table uses variable offsets rather than fixed sizes The key-value length in particular leads to some other best practices: * A value for the 'License' key should be in SPDX format, NOT the full licence * A value for the 'Copyright' key should just be the company name The author is not already aware of any vendors using this additional data area, but would be willing to work with any vendors who have implemented a similar proprietary extension already or are planning to do so. fwupd-1.2.14/docs/libfwupd/000077500000000000000000000000001402665037500155005ustar00rootroot00000000000000fwupd-1.2.14/docs/libfwupd/clean.sh000066400000000000000000000001001402665037500171050ustar00rootroot00000000000000rm -f *.txt rm -rf html/ rm -rf xml/ rm -rf tmpl/ rm -f *.stamp fwupd-1.2.14/docs/libfwupd/libfwupd-docs.xml000066400000000000000000000371331402665037500207730ustar00rootroot00000000000000 ]> fwupd Reference Manual About fwupd fwupd is a daemon for updating firmware. libfwupd Functionality exported by libfwupd for client applications. Plugin Reference Functionality available to plugins. Plugin Tutorial
Introduction At the heart of fwupd is a plugin loader that gets run at startup, when devices get hotplugged and when updates are done. The idea is we have lots of small plugins that each do one thing, and are ordered by dependencies against each other at runtime. Using plugins we can add support for new hardware or new policies without making big changes all over the source tree. There are broadly 3 types of plugin methods: Mechanism: Upload binary data into a specific hardware device. Policy: Control the system when updates are happening, e.g. preventing the user from powering-off. Helpers: Providing more metadata about devices, for instance handling device quirks. In general, building things out-of-tree isn't something that we think is a very good idea; the API and ABI internal to fwupd is still changing and there's a huge benefit to getting plugins upstream where they can undergo review and be ported as the API adapts. For this reason we don't install the plugin headers onto the system, although you can of course just install the .so binary file manually. A plugin only needs to define the vfuncs that are required, and the plugin name is taken automatically from the suffix of the .so file. A sample plugin /* * Copyright (C) 2017 Richard Hughes */ #include <fu-plugin.h> #include <fu-plugin-vfuncs.h> struct FuPluginData { gpointer proxy; }; void fu_plugin_initialize (FuPlugin *plugin) { fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_BEFORE, "dfu"); fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); destroy_proxy (data->proxy); } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); data->proxy = create_proxy (); if (data->proxy == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to create proxy"); return FALSE; } return TRUE; } We have to define when our plugin is run in reference to other plugins, in this case, making sure we run before the dfu plugin. For most plugins it does not matter in what order they are run and this information is not required.
Creating an abstract device This section shows how you would create a device which is exported to the daemon and thus can be queried and updated by the client software. The example here is all hardcoded, and a true plugin would have to derive the details about the FuDevice from the hardware, for example reading data from sysfs or /dev. Example adding a custom device #include <fu-plugin.h> gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { g_autoptr(FuDevice) dev = NULL; fu_device_set_id (dev, "dummy-1:2:3"); fu_device_add_guid (dev, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_set_version (dev, "1.2.3"); fu_device_get_version_lowest (dev, "1.2.2"); fu_device_get_version_bootloader (dev, "0.1.2"); fu_device_add_icon (dev, "computer"); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_plugin_device_add (plugin, dev); return TRUE; } This shows a lot of the plugin architecture in action. Some notable points: The device ID (dummy-1:2:3) has to be unique on the system between all plugins, so including the plugin name as a prefix is probably a good idea. The GUID value can be generated automatically using fu_device_add_guid(dev,"some-identifier") but is quoted here explicitly. The GUID value has to match the provides value in the .metainfo.xml file for the firmware update to succeed. Setting a display name and an icon is a good idea in case the GUI software needs to display the device to the user. Icons can be specified using a full path, although icon theme names should be preferred for most devices. The FWUPD_DEVICE_FLAG_UPDATABLE flag tells the client code that the device is in a state where it can be updated. If the device needs to be in a special mode (e.g. a bootloader) then the FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER flag can also be used. If the update should only be allowed when there is AC power available to the computer (i.e. not on battery) then FWUPD_DEVICE_FLAG_REQUIRE_AC should be used as well. There are other flags and the API documentation should be used when choosing what flags to use for each kind of device. Setting the lowest allows client software to refuse downgrading the device to specific versions. This is required in case the upgrade migrates some kind of data-store so as to be incompatible with previous versions. Similarly, setting the version of the bootloader (if known) allows the firmware to depend on a specific bootloader version, for instance allowing signed firmware to only be installable on hardware with a bootloader new enough to deploy it
Mechanism Plugins Although it would be a wonderful world if we could update all hardware using a standard shared protocol this is not the universe we live in. Using a mechanism like DFU or UpdateCapsule means that fwupd will just work without requiring any special code, but for the real world we need to support vendor-specific update protocols with layers of backwards compatibility. When a plugin has created a device that is FWUPD_DEVICE_FLAG_UPDATABLE we can ask the daemon to update the device with a suitable .cab file. When this is done the daemon checks the update for compatibility with the device, and then calls the vfuncs to update the device. Updating a device gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { gsize sz = 0; guint8 *buf = g_bytes_get_data (blob_fw, &sz); /* write 'buf' of size 'sz' to the hardware */ return TRUE; } It's important to note that the blob_fw is the binary firmware file (e.g. .dfu) and not the .cab binary data. If FWUPD_INSTALL_FLAG_FORCE is used then the usual checks done by the flashing process can be relaxed (e.g. checking for quirks), but please don't brick the users hardware even if they ask you to.
Policy Helpers For some hardware, we might want to do an action before or after the actual firmware is squirted into the device. This could be something as simple as checking the system battery level is over a certain threshold, or it could be as complicated as ensuring a vendor-specific GPIO is asserted when specific types of hardware are updated. Running before a device update gboolean fu_plugin_update_prepare (FuPlugin *plugin, FuDevice *device, GError **error) { if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REQUIRE_AC && !on_ac_power ()) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_AC_POWER_REQUIRED, "Cannot install update " "when not on AC power"); return FALSE; } return TRUE; } Running after a device update gboolean fu_plugin_update_cleanup (FuPlugin *plugin, FuDevice *device, GError **error) { return g_file_set_contents ("/var/lib/fwupd/something", fu_device_get_id (device), -1, error); }
Detaching to bootloader mode Some hardware can only be updated in a special bootloader mode, which for most devices can be switched to automatically. In some cases the user to do something manually, for instance re-inserting the hardware with a secret button pressed. Before the device update is performed the fwupd daemon runs an optional update_detach() vfunc which switches the device to bootloader mode. After the update (or if the update fails) an the daemon runs an optional update_attach() vfunc which should switch the hardware back to runtime mode. Finally an optional update_reload() vfunc is run to get the new firmware version from the hardware. The optional vfuncs are only run on the plugin currently registered to handle the device ID, although the registered plugin can change during the attach and detach phases. Running before a device update gboolean fu_plugin_update_detach (FuPlugin *plugin, FuDevice *device, GError **error) { if (hardware_in_bootloader) return TRUE; return _device_detach(device, error); } Running after a device update gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error) { if (!hardware_in_bootloader) return TRUE; return _device_attach(device, error); } Running after a device update on success gboolean fu_plugin_update_reload (FuPlugin *plugin, FuDevice *device, GError **error) { g_autofree gchar *version = _get_version(plugin, device, error); if (version == NULL) return FALSE; fu_device_set_version(device, version); return TRUE; }
The Plugin Object Cache The fwupd daemon provides a per-plugin cache which allows objects to be added, removed and queried using a specified key. Objects added to the cache must be GObjects to enable the cache objects to be properly refcounted.
Debugging a Plugin If the fwupd daemon is started with --plugin-verbose=$plugin then the environment variable FWUPD_$PLUGIN_VERBOSE is set process-wide. This allows plugins to detect when they should output detailed debugging information that would normally be too verbose to keep in the journal. For example, using --plugin-verbose=unifying would set FWUPD_UNIFYING_VERBOSE=1.
API Index Index of deprecated API
fwupd-1.2.14/docs/libfwupd/libfwupd.types000066400000000000000000000002051402665037500203770ustar00rootroot00000000000000fwupd_client_get_type fwupd_device_get_type fwupd_quirks_get_type fwupd_release_get_type fwupd_remote_get_type fwupd_result_get_type fwupd-1.2.14/docs/libfwupd/meson.build000066400000000000000000000004341402665037500176430ustar00rootroot00000000000000gnome.gtkdoc( 'libfwupd', src_dir : [ join_paths(meson.source_root(), 'libfwupd'), join_paths(meson.source_root(), 'src'), join_paths(meson.build_root(), 'libfwupd'), join_paths(meson.build_root(), 'src'), ], main_xml : 'libfwupd-docs.xml', install : true ) fwupd-1.2.14/docs/meson.build000066400000000000000000000000231402665037500160210ustar00rootroot00000000000000subdir('libfwupd') fwupd-1.2.14/docs/version-format.md000066400000000000000000000045101402665037500171610ustar00rootroot00000000000000Version Formats =============== In some circumstances fwupd has to convert from a unsigned integer version number into something that has either been used in documentation or has been defined in some specification. A good example here is the UEFI ESRT table, which specifies a `uint32_t` for the version but does not specify how this should be formatted for the user. As is typical in underspecified specifications, vendors have converted the integer in different ways. For instance, Dell uses version strings like 1.2.3 and Microsoft use versions like 1.2.3.4. The fwudp daemon can match specific devices and apply the correct version style using quirk files. The version format can also be specified in the firmware `metainfo.xml` file so that the new version is correctly shown, and so that it matches on the LVFS website. The current version formats supported by fwupd and the LVFS are: * `plain`: Use plain integer version numbers with no dots, e.g. `AABBCCDD` * `quad`: Use Dell-style `AA.BB.CC.DD` version numbers * `triplet`: Use Microsoft-style `AA.BB.CCDD` version numbers * `pair`: Use two `AABB.CCDD` version numbers * `bcd`: Use binary coded decimal notation * `intel-me`: Use Intel ME-style notation (`aaa+11.bbbbb.CC.DDDD`) * `intel-me2`: Use alternate Intel ME-style-style `A.B.CC.DDDD` notation These can be specified in quirk files like this: # Vendor Modelname [Guid=5b92717b-2cad-4a96-a13b-9d65781df8bf] VersionFormat = intel-me2 ...or in metainfo.xml files like this: intel-me2 Runtime requirements -------------------- Versions of fwupd `< 1.2.0` can only support firmware updates with key values `LVFS::VersionFormat` of `quad` and `triplet`. Additionally, on older versions no quirk `VersionFormat` device fixups are supported. If want to use one of the additional version formats you should depend on a specific version of fwupd in the firmware file: org.freedesktop.fwupd This is not *strictly* required, as the integer value can be used for update calculations if the version is specified in hex (e.g. `0x12345678`) in the `` tag, although the user might get a bit confused if the update version does not match the update description. fwupd-1.2.14/libfwupd/000077500000000000000000000000001402665037500145505ustar00rootroot00000000000000fwupd-1.2.14/libfwupd/README.md000066400000000000000000000020651402665037500160320ustar00rootroot00000000000000Migration from Version 0.9.x ============================ * Rename FU_DEVICE_FLAG -> FWUPD_DEVICE_FLAG * Rename FWUPD_DEVICE_FLAG_ALLOW_ONLINE -> FWUPD_DEVICE_FLAG_UPDATABLE * Rename FWUPD_DEVICE_FLAG_ALLOW_OFFLINE -> FWUPD_DEVICE_FLAG_ONLY_OFFLINE * Rename fwupd_client_get_devices_simple -> fwupd_client_get_devices * Rename fwupd_client_get_details_local -> fwupd_client_get_details * Rename fwupd_client_update_metadata_with_id -> fwupd_client_update_metadata * Rename fwupd_remote_get_uri -> fwupd_remote_get_metadata_uri * Rename fwupd_remote_get_uri_asc -> fwupd_remote_get_metadata_uri_sig * Rename fwupd_remote_build_uri -> fwupd_remote_build_firmware_uri * Switch FWUPD_RESULT_KEY_DEVICE_CHECKSUM_KIND to fwupd_checksum_guess_kind() * Rename fwupd_result_update_*() to fwupd_release_*() * Rename fwupd_result_*() to fwupd_device_*() * Convert FwupdResult to FwupdDevice in all callbacks * Rename fwupd_device_*_provider -> fwupd_device_*_plugin * Convert hash types sa{sv} -> a{sv} * Convert fwupd_client_get_updates() -> fwupd_client_get_upgrades() fwupd-1.2.14/libfwupd/fwupd-client.c000066400000000000000000001447241402665037500173310ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include "fwupd-client.h" #include "fwupd-common.h" #include "fwupd-deprecated.h" #include "fwupd-enums.h" #include "fwupd-error.h" #include "fwupd-device-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" /** * SECTION:fwupd-client * @short_description: a way of interfacing with the daemon * * An object that allows client code to call the daemon methods synchronously. * * See also: #FwupdDevice */ static void fwupd_client_finalize (GObject *object); typedef struct { FwupdStatus status; gboolean tainted; guint percentage; gchar *daemon_version; GDBusConnection *conn; GDBusProxy *proxy; } FwupdClientPrivate; enum { SIGNAL_CHANGED, SIGNAL_STATUS_CHANGED, SIGNAL_DEVICE_ADDED, SIGNAL_DEVICE_REMOVED, SIGNAL_DEVICE_CHANGED, SIGNAL_LAST }; enum { PROP_0, PROP_STATUS, PROP_PERCENTAGE, PROP_DAEMON_VERSION, PROP_TAINTED, PROP_LAST }; static guint signals [SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE_WITH_PRIVATE (FwupdClient, fwupd_client, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_client_get_instance_private (o)) typedef struct { gboolean ret; GError *error; GMainLoop *loop; GVariant *val; GDBusMessage *message; } FwupdClientHelper; static void fwupd_client_helper_free (FwupdClientHelper *helper) { if (helper->message != NULL) g_object_unref (helper->message); if (helper->val != NULL) g_variant_unref (helper->val); if (helper->error != NULL) g_error_free (helper->error); g_main_loop_unref (helper->loop); g_free (helper); } static FwupdClientHelper * fwupd_client_helper_new (void) { FwupdClientHelper *helper; helper = g_new0 (FwupdClientHelper, 1); helper->loop = g_main_loop_new (NULL, FALSE); return helper; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FwupdClientHelper, fwupd_client_helper_free) #pragma clang diagnostic pop static void fwupd_client_set_daemon_version (FwupdClient *client, const gchar *daemon_version) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_free (priv->daemon_version); priv->daemon_version = g_strdup (daemon_version); g_object_notify (G_OBJECT (client), "daemon-version"); } static void fwupd_client_properties_changed_cb (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, FwupdClient *client) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariantDict) dict = NULL; /* print to the console */ dict = g_variant_dict_new (changed_properties); if (g_variant_dict_contains (dict, "Status")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property (proxy, "Status"); if (val != NULL) { priv->status = g_variant_get_uint32 (val); g_debug ("Emitting ::status-changed() [%s]", fwupd_status_to_string (priv->status)); g_signal_emit (client, signals[SIGNAL_STATUS_CHANGED], 0, priv->status); g_object_notify (G_OBJECT (client), "status"); } } if (g_variant_dict_contains (dict, "Tainted")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property (proxy, "Tainted"); if (val != NULL) { priv->tainted = g_variant_get_boolean (val); g_object_notify (G_OBJECT (client), "tainted"); } } if (g_variant_dict_contains (dict, "Percentage")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property (proxy, "Percentage"); if (val != NULL) { priv->percentage = g_variant_get_uint32 (val); g_object_notify (G_OBJECT (client), "percentage"); } } if (g_variant_dict_contains (dict, "DaemonVersion")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property (proxy, "DaemonVersion"); if (val != NULL) fwupd_client_set_daemon_version (client, g_variant_get_string (val, NULL)); } } static void fwupd_client_signal_cb (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, FwupdClient *client) { g_autoptr(FwupdDevice) dev = NULL; if (g_strcmp0 (signal_name, "Changed") == 0) { g_debug ("Emitting ::changed()"); g_signal_emit (client, signals[SIGNAL_CHANGED], 0); return; } if (g_strcmp0 (signal_name, "DeviceAdded") == 0) { dev = fwupd_device_from_variant (parameters); g_debug ("Emitting ::device-added(%s)", fwupd_device_get_id (dev)); g_signal_emit (client, signals[SIGNAL_DEVICE_ADDED], 0, dev); return; } if (g_strcmp0 (signal_name, "DeviceRemoved") == 0) { dev = fwupd_device_from_variant (parameters); g_signal_emit (client, signals[SIGNAL_DEVICE_REMOVED], 0, dev); g_debug ("Emitting ::device-removed(%s)", fwupd_device_get_id (dev)); return; } if (g_strcmp0 (signal_name, "DeviceChanged") == 0) { dev = fwupd_device_from_variant (parameters); g_signal_emit (client, signals[SIGNAL_DEVICE_CHANGED], 0, dev); g_debug ("Emitting ::device-changed(%s)", fwupd_device_get_id (dev)); return; } g_debug ("Unknown signal name '%s' from %s", signal_name, sender_name); } /** * fwupd_client_connect: * @client: A #FwupdClient * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Sets up the client ready for use. Most other methods call this * for you, and do you only need to call this if you are just watching * the client. * * Returns: %TRUE for success * * Since: 0.7.1 **/ gboolean fwupd_client_connect (FwupdClient *client, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; g_autoptr(GVariant) val2 = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* nothing to do */ if (priv->proxy != NULL) return TRUE; /* connect to the daemon */ priv->conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); if (priv->conn == NULL) { g_prefix_error (error, "Failed to connect to system D-Bus: "); return FALSE; } priv->proxy = g_dbus_proxy_new_sync (priv->conn, G_DBUS_PROXY_FLAGS_NONE, NULL, FWUPD_DBUS_SERVICE, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, NULL, error); if (priv->proxy == NULL) return FALSE; g_signal_connect (priv->proxy, "g-properties-changed", G_CALLBACK (fwupd_client_properties_changed_cb), client); g_signal_connect (priv->proxy, "g-signal", G_CALLBACK (fwupd_client_signal_cb), client); val = g_dbus_proxy_get_cached_property (priv->proxy, "DaemonVersion"); if (val != NULL) fwupd_client_set_daemon_version (client, g_variant_get_string (val, NULL)); val2 = g_dbus_proxy_get_cached_property (priv->proxy, "Tainted"); if (val2 != NULL) priv->tainted = g_variant_get_boolean (val2); return TRUE; } static void fwupd_client_fixup_dbus_error (GError *error) { g_autofree gchar *name = NULL; g_return_if_fail (error != NULL); /* is a remote error? */ if (!g_dbus_error_is_remote_error (error)) return; /* parse the remote error */ name = g_dbus_error_get_remote_error (error); if (g_str_has_prefix (name, FWUPD_DBUS_INTERFACE)) { error->domain = FWUPD_ERROR; error->code = fwupd_error_from_string (name); } else if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) { error->domain = FWUPD_ERROR; error->code = FWUPD_ERROR_NOT_SUPPORTED; } else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR)) { error->domain = FWUPD_ERROR; error->code = FWUPD_ERROR_NOT_SUPPORTED; } else { error->domain = FWUPD_ERROR; error->code = FWUPD_ERROR_INTERNAL; } g_dbus_error_strip_remote_error (error); } /** * fwupd_client_get_devices: * @client: A #FwupdClient * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets all the devices registered with the daemon. * * Returns: (element-type FwupdDevice) (transfer container): results * * Since: 0.9.2 **/ GPtrArray * fwupd_client_get_devices (FwupdClient *client, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return NULL; /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "GetDevices", NULL, G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return NULL; } return fwupd_device_array_from_variant (val); } /** * fwupd_client_get_history: * @client: A #FwupdClient * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets all the history. * * Returns: (element-type FwupdDevice) (transfer container): results * * Since: 1.0.4 **/ GPtrArray * fwupd_client_get_history (FwupdClient *client, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return NULL; /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "GetHistory", NULL, G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return NULL; } return fwupd_device_array_from_variant (val); } /** * fwupd_client_get_device_by_id: * @client: A #FwupdClient * @device_id: the device ID, e.g. `usb:00:01:03:03` * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets a device by it's device ID. * * Returns: (transfer full): a #FwupdDevice or %NULL * * Since: 0.9.3 **/ FwupdDevice * fwupd_client_get_device_by_id (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* get all the devices */ devices = fwupd_client_get_devices (client, cancellable, error); if (devices == NULL) return NULL; /* find the device by ID (client side) */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); if (g_strcmp0 (fwupd_device_get_id (dev), device_id) == 0) return g_object_ref (dev); } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to find %s", device_id); return NULL; } /** * fwupd_client_get_releases: * @client: A #FwupdClient * @device_id: the device ID * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets all the releases for a specific device * * Returns: (element-type FwupdRelease) (transfer container): results * * Since: 0.9.3 **/ GPtrArray * fwupd_client_get_releases (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return NULL; /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "GetReleases", g_variant_new ("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return NULL; } return fwupd_release_array_from_variant (val); } /** * fwupd_client_get_downgrades: * @client: A #FwupdClient * @device_id: the device ID * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets all the downgrades for a specific device. * * Returns: (element-type FwupdRelease) (transfer container): results * * Since: 0.9.8 **/ GPtrArray * fwupd_client_get_downgrades (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return NULL; /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "GetDowngrades", g_variant_new ("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return NULL; } return fwupd_release_array_from_variant (val); } /** * fwupd_client_get_upgrades: * @client: A #FwupdClient * @device_id: the device ID * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets all the upgrades for a specific device. * * Returns: (element-type FwupdRelease) (transfer container): results * * Since: 0.9.8 **/ GPtrArray * fwupd_client_get_upgrades (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return NULL; /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "GetUpgrades", g_variant_new ("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return NULL; } return fwupd_release_array_from_variant (val); } static void fwupd_client_proxy_call_cb (GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *) user_data; helper->val = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &helper->error); if (helper->val != NULL) helper->ret = TRUE; if (helper->error != NULL) fwupd_client_fixup_dbus_error (helper->error); g_main_loop_quit (helper->loop); } /** * fwupd_client_modify_config * @client: A #FwupdClient * @key: key, e.g. `BlacklistPlugins` * @value: value, e.g. `*` * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Modifies a daemon config option. * The daemon will only respond to this request with proper permissions * * Returns: %TRUE for success * * Since: 1.2.8 **/ gboolean fwupd_client_modify_config (FwupdClient *client, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* call into daemon */ helper = fwupd_client_helper_new (); g_dbus_proxy_call (priv->proxy, "ModifyConfig", g_variant_new ("(ss)", key, value), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, fwupd_client_proxy_call_cb, helper); g_main_loop_run (helper->loop); if (!helper->ret) { g_propagate_error (error, helper->error); helper->error = NULL; return FALSE; } return TRUE; } /** * fwupd_client_activate: * @client: A #FwupdClient * @cancellable: the #GCancellable, or %NULL * @device_id: a device * @error: the #GError, or %NULL * * Activates up a device, which normally means the device switches to a new * firmware version. This should only be called when data loss cannot occur. * * Returns: %TRUE for success * * Since: 1.2.6 **/ gboolean fwupd_client_activate (FwupdClient *client, GCancellable *cancellable, const gchar *device_id, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* call into daemon */ helper = fwupd_client_helper_new (); g_dbus_proxy_call (priv->proxy, "Activate", g_variant_new ("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, fwupd_client_proxy_call_cb, helper); g_main_loop_run (helper->loop); if (!helper->ret) { g_propagate_error (error, helper->error); helper->error = NULL; return FALSE; } return TRUE; } /** * fwupd_client_verify: * @client: A #FwupdClient * @device_id: the device ID * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Verify a specific device. * * Returns: %TRUE for verification success * * Since: 0.7.0 **/ gboolean fwupd_client_verify (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* call into daemon */ helper = fwupd_client_helper_new (); g_dbus_proxy_call (priv->proxy, "Verify", g_variant_new ("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, fwupd_client_proxy_call_cb, helper); g_main_loop_run (helper->loop); if (!helper->ret) { g_propagate_error (error, helper->error); helper->error = NULL; return FALSE; } return TRUE; } /** * fwupd_client_verify_update: * @client: A #FwupdClient * @device_id: the device ID * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Update the verification record for a specific device. * * Returns: %TRUE for verification success * * Since: 0.8.0 **/ gboolean fwupd_client_verify_update (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* call into daemon */ helper = fwupd_client_helper_new (); g_dbus_proxy_call (priv->proxy, "VerifyUpdate", g_variant_new ("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, fwupd_client_proxy_call_cb, helper); g_main_loop_run (helper->loop); if (!helper->ret) { g_propagate_error (error, helper->error); helper->error = NULL; return FALSE; } return TRUE; } /** * fwupd_client_unlock: * @client: A #FwupdClient * @device_id: the device ID * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Unlocks a specific device so firmware can be read or wrote. * * Returns: %TRUE for success * * Since: 0.7.0 **/ gboolean fwupd_client_unlock (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* call into daemon */ helper = fwupd_client_helper_new (); g_dbus_proxy_call (priv->proxy, "Unlock", g_variant_new ("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, fwupd_client_proxy_call_cb, helper); g_main_loop_run (helper->loop); if (!helper->ret) { g_propagate_error (error, helper->error); helper->error = NULL; return FALSE; } return TRUE; } /** * fwupd_client_clear_results: * @client: A #FwupdClient * @device_id: the device ID * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Clears the results for a specific device. * * Returns: %TRUE for success * * Since: 0.7.0 **/ gboolean fwupd_client_clear_results (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* call into daemon */ helper = fwupd_client_helper_new (); g_dbus_proxy_call (priv->proxy, "ClearResults", g_variant_new ("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, fwupd_client_proxy_call_cb, helper); g_main_loop_run (helper->loop); if (!helper->ret) { g_propagate_error (error, helper->error); helper->error = NULL; return FALSE; } return TRUE; } /** * fwupd_client_get_results: * @client: A #FwupdClient * @device_id: the device ID * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets the results of a previous firmware update for a specific device. * * Returns: (transfer full): a #FwupdDevice, or %NULL for failure * * Since: 0.7.0 **/ FwupdDevice * fwupd_client_get_results (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return NULL; /* call into daemon */ helper = fwupd_client_helper_new (); g_dbus_proxy_call (priv->proxy, "GetResults", g_variant_new ("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, fwupd_client_proxy_call_cb, helper); g_main_loop_run (helper->loop); if (!helper->ret) { g_propagate_error (error, helper->error); helper->error = NULL; return NULL; } return fwupd_device_from_variant (helper->val); } static void fwupd_client_send_message_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *) user_data; GDBusConnection *con = G_DBUS_CONNECTION (source_object); helper->message = g_dbus_connection_send_message_with_reply_finish (con, res, &helper->error); if (helper->message && !g_dbus_message_to_gerror (helper->message, &helper->error)) { helper->ret = TRUE; helper->val = g_dbus_message_get_body (helper->message); if (helper->val != NULL) g_variant_ref (helper->val); } if (helper->error != NULL) fwupd_client_fixup_dbus_error (helper->error); g_main_loop_quit (helper->loop); } /** * fwupd_client_install: * @client: A #FwupdClient * @device_id: the device ID * @filename: the filename to install * @install_flags: the #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_REINSTALL * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Install a file onto a specific device. * * Returns: %TRUE for success * * Since: 0.7.0 **/ gboolean fwupd_client_install (FwupdClient *client, const gchar *device_id, const gchar *filename, FwupdInstallFlags install_flags, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); GVariant *body; GVariantBuilder builder; gint retval; gint fd; g_autoptr(FwupdClientHelper) helper = NULL; g_autoptr(GDBusMessage) request = NULL; g_autoptr(GUnixFDList) fd_list = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (filename != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* set options */ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add (&builder, "{sv}", "reason", g_variant_new_string ("user-action")); g_variant_builder_add (&builder, "{sv}", "filename", g_variant_new_string (filename)); if (install_flags & FWUPD_INSTALL_FLAG_OFFLINE) { g_variant_builder_add (&builder, "{sv}", "offline", g_variant_new_boolean (TRUE)); } if (install_flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) { g_variant_builder_add (&builder, "{sv}", "allow-older", g_variant_new_boolean (TRUE)); } if (install_flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) { g_variant_builder_add (&builder, "{sv}", "allow-reinstall", g_variant_new_boolean (TRUE)); } if (install_flags & FWUPD_INSTALL_FLAG_FORCE) { g_variant_builder_add (&builder, "{sv}", "force", g_variant_new_boolean (TRUE)); } if (install_flags & FWUPD_INSTALL_FLAG_NO_HISTORY) { g_variant_builder_add (&builder, "{sv}", "no-history", g_variant_new_boolean (TRUE)); } /* open file */ fd = open (filename, O_RDONLY); if (fd < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to open %s", filename); return FALSE; } /* set out of band file descriptor */ fd_list = g_unix_fd_list_new (); retval = g_unix_fd_list_append (fd_list, fd, NULL); g_assert (retval != -1); request = g_dbus_message_new_method_call (FWUPD_DBUS_SERVICE, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "Install"); g_dbus_message_set_unix_fd_list (request, fd_list); /* g_unix_fd_list_append did a dup() already */ close (fd); /* call into daemon */ helper = fwupd_client_helper_new (); body = g_variant_new ("(sha{sv})", device_id, fd, &builder); g_dbus_message_set_body (request, body); g_dbus_connection_send_message_with_reply (priv->conn, request, G_DBUS_SEND_MESSAGE_FLAGS_NONE, G_MAXINT, NULL, cancellable, fwupd_client_send_message_cb, helper); g_main_loop_run (helper->loop); if (!helper->ret) { g_propagate_error (error, helper->error); helper->error = NULL; return FALSE; } return TRUE; } /** * fwupd_client_get_details: * @client: A #FwupdClient * @filename: the firmware filename, e.g. `firmware.cab` * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets details about a specific firmware file. * * Returns: (transfer container) (element-type FwupdDevice): an array of results * * Since: 1.0.0 **/ GPtrArray * fwupd_client_get_details (FwupdClient *client, const gchar *filename, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); GVariant *body; gint fd; gint retval; g_autoptr(FwupdClientHelper) helper = NULL; g_autoptr(GDBusMessage) request = NULL; g_autoptr(GUnixFDList) fd_list = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (filename != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return NULL; /* open file */ fd = open (filename, O_RDONLY); if (fd < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to open %s", filename); return NULL; } /* set out of band file descriptor */ fd_list = g_unix_fd_list_new (); retval = g_unix_fd_list_append (fd_list, fd, NULL); g_assert (retval != -1); request = g_dbus_message_new_method_call (FWUPD_DBUS_SERVICE, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "GetDetails"); g_dbus_message_set_unix_fd_list (request, fd_list); /* g_unix_fd_list_append did a dup() already */ close (fd); /* call into daemon */ helper = fwupd_client_helper_new (); body = g_variant_new ("(h)", fd); g_dbus_message_set_body (request, body); g_dbus_connection_send_message_with_reply (priv->conn, request, G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, cancellable, fwupd_client_send_message_cb, helper); g_main_loop_run (helper->loop); if (!helper->ret) { g_propagate_error (error, helper->error); helper->error = NULL; return NULL; } /* return results */ return fwupd_device_array_from_variant (helper->val); } /** * fwupd_client_get_percentage: * @client: A #FwupdClient * * Gets the last returned percentage value. * * Returns: a percentage, or 0 for unknown. * * Since: 0.7.3 **/ guint fwupd_client_get_percentage (FwupdClient *client) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_return_val_if_fail (FWUPD_IS_CLIENT (client), 0); return priv->percentage; } /** * fwupd_client_get_daemon_version: * @client: A #FwupdClient * * Gets the daemon version number. * * Returns: a string, or %NULL for unknown. * * Since: 0.9.6 **/ const gchar * fwupd_client_get_daemon_version (FwupdClient *client) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); return priv->daemon_version; } /** * fwupd_client_get_status: * @client: A #FwupdClient * * Gets the last returned status value. * * Returns: a #FwupdStatus, or %FWUPD_STATUS_UNKNOWN for unknown. * * Since: 0.7.3 **/ FwupdStatus fwupd_client_get_status (FwupdClient *client) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_return_val_if_fail (FWUPD_IS_CLIENT (client), FWUPD_STATUS_UNKNOWN); return priv->status; } /** * fwupd_client_get_tainted: * @client: A #FwupdClient * * Gets if the daemon has been tainted by 3rd party code. * * Returns: %TRUE if the daemon is unsupported * * Since: 1.2.4 **/ gboolean fwupd_client_get_tainted (FwupdClient *client) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); return priv->tainted; } /** * fwupd_client_update_metadata: * @client: A #FwupdClient * @remote_id: the remote ID, e.g. `lvfs-testing` * @metadata_fn: the XML metadata filename * @signature_fn: the GPG signature file * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Updates the metadata. This allows a session process to download the metadata * and metadata signing file to be passed into the daemon to be checked and * parsed. * * The @remote_id allows the firmware to be tagged so that the remote can be * matched when the firmware is downloaded. * * Returns: %TRUE for success * * Since: 1.0.0 **/ gboolean fwupd_client_update_metadata (FwupdClient *client, const gchar *remote_id, const gchar *metadata_fn, const gchar *signature_fn, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); GVariant *body; gint fd; gint fd_sig; g_autoptr(FwupdClientHelper) helper = NULL; g_autoptr(GDBusMessage) request = NULL; g_autoptr(GUnixFDList) fd_list = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (remote_id != NULL, FALSE); g_return_val_if_fail (metadata_fn != NULL, FALSE); g_return_val_if_fail (signature_fn != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* open file */ fd = open (metadata_fn, O_RDONLY); if (fd < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to open %s", metadata_fn); return FALSE; } fd_sig = open (signature_fn, O_RDONLY); if (fd_sig < 0) { close (fd); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to open %s", signature_fn); return FALSE; } /* set out of band file descriptor */ fd_list = g_unix_fd_list_new (); g_unix_fd_list_append (fd_list, fd, NULL); g_unix_fd_list_append (fd_list, fd_sig, NULL); request = g_dbus_message_new_method_call (FWUPD_DBUS_SERVICE, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "UpdateMetadata"); g_dbus_message_set_unix_fd_list (request, fd_list); /* g_unix_fd_list_append did a dup() already */ close (fd); close (fd_sig); /* call into daemon */ body = g_variant_new ("(shh)", remote_id, fd, fd_sig); g_dbus_message_set_body (request, body); helper = fwupd_client_helper_new (); g_dbus_connection_send_message_with_reply (priv->conn, request, G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, cancellable, fwupd_client_send_message_cb, helper); g_main_loop_run (helper->loop); if (!helper->ret) { g_propagate_error (error, helper->error); helper->error = NULL; return FALSE; } return TRUE; } /** * fwupd_client_get_remotes: * @client: A #FwupdClient * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets the list of remotes that have been configured for the system. * * Returns: (element-type FwupdRemote) (transfer container): list of remotes, or %NULL * * Since: 0.9.3 **/ GPtrArray * fwupd_client_get_remotes (FwupdClient *client, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return NULL; /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "GetRemotes", NULL, G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return NULL; } return fwupd_remote_array_from_variant (val); } /** * fwupd_client_get_approved_firmware: * @client: A #FwupdClient * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets the list of approved firmware. * * Returns: (transfer full): list of remotes, or %NULL * * Since: 1.2.6 **/ gchar ** fwupd_client_get_approved_firmware (FwupdClient *client, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; gchar **retval = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return NULL; /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "GetApprovedFirmware", NULL, G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return NULL; } g_variant_get (val, "(^as)", &retval); return retval; } /** * fwupd_client_set_approved_firmware: * @client: A #FwupdClient * @checksums: Array of checksums * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Sets the list of approved firmware. * * Returns: %TRUE for success * * Since: 1.2.6 **/ gboolean fwupd_client_set_approved_firmware (FwupdClient *client, gchar **checksums, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "SetApprovedFirmware", g_variant_new ("(^as)", checksums), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return FALSE; } return TRUE; } /** * fwupd_client_self_sign: * @client: A #FwupdClient * @value: A string to sign, typically a JSON blob * @flags: #FwupdSelfSignFlags, e.g. %FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Signs the data using the client self-signed certificate. * * Returns: %TRUE for success * * Since: 1.2.6 **/ gchar * fwupd_client_self_sign (FwupdClient *client, const gchar *value, FwupdSelfSignFlags flags, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); GVariantBuilder builder; g_autoptr(GVariant) val = NULL; gchar *retval = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return NULL; /* set options */ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); if (flags & FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP) { g_variant_builder_add (&builder, "{sv}", "add-timestamp", g_variant_new_boolean (TRUE)); } if (flags & FWUPD_SELF_SIGN_FLAG_ADD_CERT) { g_variant_builder_add (&builder, "{sv}", "add-cert", g_variant_new_boolean (TRUE)); } /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "SelfSign", g_variant_new ("(sa{sv})", value, &builder), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return NULL; } g_variant_get (val, "(s)", &retval); return retval; } /** * fwupd_client_modify_remote: * @client: A #FwupdClient * @remote_id: the remote ID, e.g. `lvfs-testing` * @key: the key, e.g. `Enabled` * @value: the key, e.g. `true` * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Modifies a system remote in a specific way. * * NOTE: User authentication may be required to complete this action. * * Returns: %TRUE for success * * Since: 0.9.8 **/ gboolean fwupd_client_modify_remote (FwupdClient *client, const gchar *remote_id, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (remote_id != NULL, FALSE); g_return_val_if_fail (key != NULL, FALSE); g_return_val_if_fail (value != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "ModifyRemote", g_variant_new ("(sss)", remote_id, key, value), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return FALSE; } return TRUE; } /** * fwupd_client_modify_device: * @client: A #FwupdClient * @device_id: the device ID * @key: the key, e.g. `Flags` * @value: the key, e.g. `reported` * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Modifies a device in a specific way. Not all properties on the #FwupdDevice * are settable by the client, and some may have other restrictions on @value. * * NOTE: User authentication may be required to complete this action. * * Returns: %TRUE for success * * Since: 1.0.4 **/ gboolean fwupd_client_modify_device (FwupdClient *client, const gchar *remote_id, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_autoptr(GVariant) val = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); g_return_val_if_fail (remote_id != NULL, FALSE); g_return_val_if_fail (key != NULL, FALSE); g_return_val_if_fail (value != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect (client, cancellable, error)) return FALSE; /* call into daemon */ val = g_dbus_proxy_call_sync (priv->proxy, "ModifyDevice", g_variant_new ("(sss)", remote_id, key, value), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (val == NULL) { if (error != NULL) fwupd_client_fixup_dbus_error (*error); return FALSE; } return TRUE; } static FwupdRemote * fwupd_client_get_remote_by_id_noref (GPtrArray *remotes, const gchar *remote_id) { for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); if (g_strcmp0 (remote_id, fwupd_remote_get_id (remote)) == 0) return remote; } return NULL; } /** * fwupd_client_get_remote_by_id: * @client: A #FwupdClient * @remote_id: the remote ID, e.g. `lvfs-testing` * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Gets a specific remote that has been configured for the system. * * Returns: (transfer full): a #FwupdRemote, or %NULL if not found * * Since: 0.9.3 **/ FwupdRemote * fwupd_client_get_remote_by_id (FwupdClient *client, const gchar *remote_id, GCancellable *cancellable, GError **error) { FwupdRemote *remote; g_autoptr(GPtrArray) remotes = NULL; g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); g_return_val_if_fail (remote_id != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* find remote in list */ remotes = fwupd_client_get_remotes (client, cancellable, error); if (remotes == NULL) return NULL; remote = fwupd_client_get_remote_by_id_noref (remotes, remote_id); if (remote == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No remote '%s' found in search paths", remote_id); return NULL; } /* success */ return g_object_ref (remote); } static void fwupd_client_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FwupdClient *client = FWUPD_CLIENT (object); FwupdClientPrivate *priv = GET_PRIVATE (client); switch (prop_id) { case PROP_STATUS: g_value_set_uint (value, priv->status); break; case PROP_TAINTED: g_value_set_boolean (value, priv->tainted); break; case PROP_PERCENTAGE: g_value_set_uint (value, priv->percentage); break; case PROP_DAEMON_VERSION: g_value_set_string (value, priv->daemon_version); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fwupd_client_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FwupdClient *client = FWUPD_CLIENT (object); FwupdClientPrivate *priv = GET_PRIVATE (client); switch (prop_id) { case PROP_STATUS: priv->status = g_value_get_uint (value); break; case PROP_PERCENTAGE: priv->percentage = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fwupd_client_class_init (FwupdClientClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fwupd_client_finalize; object_class->get_property = fwupd_client_get_property; object_class->set_property = fwupd_client_set_property; /** * FwupdClient::changed: * @client: the #FwupdClient instance that emitted the signal * * The ::changed signal is emitted when the daemon internal has * changed, for instance when a device has been added or removed. * * Since: 0.7.0 **/ signals [SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FwupdClientClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * FwupdClient::state-changed: * @client: the #FwupdClient instance that emitted the signal * @status: the #FwupdStatus * * The ::state-changed signal is emitted when the daemon status has * changed, e.g. going from %FWUPD_STATUS_IDLE to %FWUPD_STATUS_DEVICE_WRITE. * * Since: 0.7.0 **/ signals [SIGNAL_STATUS_CHANGED] = g_signal_new ("status-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FwupdClientClass, status_changed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); /** * FwupdClient::device-added: * @client: the #FwupdClient instance that emitted the signal * @result: the #FwupdDevice * * The ::device-added signal is emitted when a device has been * added. * * Since: 0.7.1 **/ signals [SIGNAL_DEVICE_ADDED] = g_signal_new ("device-added", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FwupdClientClass, device_added), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, FWUPD_TYPE_DEVICE); /** * FwupdClient::device-removed: * @client: the #FwupdClient instance that emitted the signal * @result: the #FwupdDevice * * The ::device-removed signal is emitted when a device has been * removed. * * Since: 0.7.1 **/ signals [SIGNAL_DEVICE_REMOVED] = g_signal_new ("device-removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FwupdClientClass, device_removed), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, FWUPD_TYPE_DEVICE); /** * FwupdClient::device-changed: * @client: the #FwupdClient instance that emitted the signal * @result: the #FwupdDevice * * The ::device-changed signal is emitted when a device has been * changed in some way, e.g. the version number is updated. * * Since: 0.7.1 **/ signals [SIGNAL_DEVICE_CHANGED] = g_signal_new ("device-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FwupdClientClass, device_changed), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, FWUPD_TYPE_DEVICE); /** * FwupdClient:status: * * The last-reported status of the daemon. * * Since: 0.7.0 */ pspec = g_param_spec_uint ("status", NULL, NULL, 0, FWUPD_STATUS_LAST, FWUPD_STATUS_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_STATUS, pspec); /** * FwupdClient:tainted: * * If the daemon is tainted by 3rd party code. * * Since: 1.2.4 */ pspec = g_param_spec_boolean ("tainted", NULL, NULL, FALSE, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_TAINTED, pspec); /** * FwupdClient:percentage: * * The last-reported percentage of the daemon. * * Since: 0.7.3 */ pspec = g_param_spec_uint ("percentage", NULL, NULL, 0, 100, 0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_PERCENTAGE, pspec); /** * FwupdClient:daemon-version: * * The daemon version number. * * Since: 0.9.6 */ pspec = g_param_spec_string ("daemon-version", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_DAEMON_VERSION, pspec); } static void fwupd_client_init (FwupdClient *client) { } static void fwupd_client_finalize (GObject *object) { FwupdClient *client = FWUPD_CLIENT (object); FwupdClientPrivate *priv = GET_PRIVATE (client); g_free (priv->daemon_version); if (priv->conn != NULL) g_object_unref (priv->conn); if (priv->proxy != NULL) g_object_unref (priv->proxy); G_OBJECT_CLASS (fwupd_client_parent_class)->finalize (object); } /** * fwupd_client_new: * * Creates a new client. * * Returns: a new #FwupdClient * * Since: 0.7.0 **/ FwupdClient * fwupd_client_new (void) { FwupdClient *client; client = g_object_new (FWUPD_TYPE_CLIENT, NULL); return FWUPD_CLIENT (client); } fwupd-1.2.14/libfwupd/fwupd-client.h000066400000000000000000000120661402665037500173270ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fwupd-enums.h" #include "fwupd-device.h" #include "fwupd-remote.h" G_BEGIN_DECLS #define FWUPD_TYPE_CLIENT (fwupd_client_get_type ()) G_DECLARE_DERIVABLE_TYPE (FwupdClient, fwupd_client, FWUPD, CLIENT, GObject) struct _FwupdClientClass { GObjectClass parent_class; void (*changed) (FwupdClient *client); void (*status_changed) (FwupdClient *client, FwupdStatus status); void (*device_added) (FwupdClient *client, FwupdDevice *result); void (*device_removed) (FwupdClient *client, FwupdDevice *result); void (*device_changed) (FwupdClient *client, FwupdDevice *result); /*< private >*/ void (*_fwupd_reserved1) (void); void (*_fwupd_reserved2) (void); void (*_fwupd_reserved3) (void); void (*_fwupd_reserved4) (void); void (*_fwupd_reserved5) (void); void (*_fwupd_reserved6) (void); void (*_fwupd_reserved7) (void); }; FwupdClient *fwupd_client_new (void); gboolean fwupd_client_connect (FwupdClient *client, GCancellable *cancellable, GError **error); GPtrArray *fwupd_client_get_devices (FwupdClient *client, GCancellable *cancellable, GError **error); GPtrArray *fwupd_client_get_history (FwupdClient *client, GCancellable *cancellable, GError **error); GPtrArray *fwupd_client_get_releases (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error); GPtrArray *fwupd_client_get_downgrades (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error); GPtrArray *fwupd_client_get_upgrades (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error); GPtrArray *fwupd_client_get_details (FwupdClient *client, const gchar *filename, GCancellable *cancellable, GError **error); gboolean fwupd_client_verify (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error); gboolean fwupd_client_verify_update (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error); gboolean fwupd_client_unlock (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error); gboolean fwupd_client_modify_config (FwupdClient *client, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error); gboolean fwupd_client_activate (FwupdClient *client, GCancellable *cancellable, const gchar *device_id, GError **error); gboolean fwupd_client_clear_results (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error); FwupdDevice *fwupd_client_get_results (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error); FwupdDevice *fwupd_client_get_device_by_id (FwupdClient *client, const gchar *device_id, GCancellable *cancellable, GError **error); gboolean fwupd_client_install (FwupdClient *client, const gchar *device_id, const gchar *filename, FwupdInstallFlags install_flags, GCancellable *cancellable, GError **error); gboolean fwupd_client_update_metadata (FwupdClient *client, const gchar *remote_id, const gchar *metadata_fn, const gchar *signature_fn, GCancellable *cancellable, GError **error); gboolean fwupd_client_modify_remote (FwupdClient *client, const gchar *remote_id, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error); gboolean fwupd_client_modify_device (FwupdClient *client, const gchar *device_id, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error); FwupdStatus fwupd_client_get_status (FwupdClient *client); gboolean fwupd_client_get_tainted (FwupdClient *client); guint fwupd_client_get_percentage (FwupdClient *client); const gchar *fwupd_client_get_daemon_version (FwupdClient *client); GPtrArray *fwupd_client_get_remotes (FwupdClient *client, GCancellable *cancellable, GError **error); FwupdRemote *fwupd_client_get_remote_by_id (FwupdClient *client, const gchar *remote_id, GCancellable *cancellable, GError **error); gchar **fwupd_client_get_approved_firmware (FwupdClient *client, GCancellable *cancellable, GError **error); gboolean fwupd_client_set_approved_firmware (FwupdClient *client, gchar **checksums, GCancellable *cancellable, GError **error); gchar *fwupd_client_self_sign (FwupdClient *client, const gchar *value, FwupdSelfSignFlags flags, GCancellable *cancellable, GError **error); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-common-private.h000066400000000000000000000004071402665037500210050ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-common.h" G_BEGIN_DECLS gchar *fwupd_checksum_format_for_display (const gchar *checksum); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-common.c000066400000000000000000000563611402665037500173420ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-common-private.h" #include "fwupd-device.h" #include "fwupd-error.h" #include "fwupd-release.h" #include #include #include #include #if !GLIB_CHECK_VERSION(2,54,0) #include #endif /** * fwupd_checksum_guess_kind: * @checksum: A checksum * * Guesses the checksum kind based on the length of the hash. * * Returns: a #GChecksumType, e.g. %G_CHECKSUM_SHA1 * * Since: 0.9.3 **/ GChecksumType fwupd_checksum_guess_kind (const gchar *checksum) { guint len; if (checksum == NULL) return G_CHECKSUM_SHA1; len = strlen (checksum); if (len == 32) return G_CHECKSUM_MD5; if (len == 40) return G_CHECKSUM_SHA1; if (len == 64) return G_CHECKSUM_SHA256; if (len == 128) return G_CHECKSUM_SHA512; return G_CHECKSUM_SHA1; } static const gchar * fwupd_checksum_type_to_string_display (GChecksumType checksum_type) { if (checksum_type == G_CHECKSUM_MD5) return "MD5"; if (checksum_type == G_CHECKSUM_SHA1) return "SHA1"; if (checksum_type == G_CHECKSUM_SHA256) return "SHA256"; if (checksum_type == G_CHECKSUM_SHA512) return "SHA512"; return NULL; } /** * fwupd_checksum_format_for_display: * @checksum: A checksum * * Formats a checksum for display. * * Returns: text, or %NULL for invalid * * Since: 0.9.3 **/ gchar * fwupd_checksum_format_for_display (const gchar *checksum) { GChecksumType kind = fwupd_checksum_guess_kind (checksum); return g_strdup_printf ("%s(%s)", fwupd_checksum_type_to_string_display (kind), checksum); } /** * fwupd_checksum_get_by_kind: * @checksums: (element-type utf8): checksums * @kind: a #GChecksumType, e.g. %G_CHECKSUM_SHA512 * * Gets a specific checksum kind. * * Returns: a checksum from the array, or %NULL if not found * * Since: 0.9.4 **/ const gchar * fwupd_checksum_get_by_kind (GPtrArray *checksums, GChecksumType kind) { for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index (checksums, i); if (fwupd_checksum_guess_kind (checksum) == kind) return checksum; } return NULL; } /** * fwupd_checksum_get_best: * @checksums: (element-type utf8): checksums * * Gets a the best possible checksum kind. * * Returns: a checksum from the array, or %NULL if nothing was suitable * * Since: 0.9.4 **/ const gchar * fwupd_checksum_get_best (GPtrArray *checksums) { GChecksumType checksum_types[] = { G_CHECKSUM_SHA512, G_CHECKSUM_SHA256, G_CHECKSUM_SHA1, 0 }; for (guint i = 0; checksum_types[i] != 0; i++) { for (guint j = 0; j < checksums->len; j++) { const gchar *checksum = g_ptr_array_index (checksums, j); if (fwupd_checksum_guess_kind (checksum) == checksum_types[i]) return checksum; } } return NULL; } /** * fwupd_get_os_release: * @error: A #GError or %NULL * * Loads information from the system os-release file. * * Returns: (transfer container) (element-type utf8 utf8): keys from os-release * * Since: 1.0.7 **/ GHashTable * fwupd_get_os_release (GError **error) { GHashTable *hash; const gchar *filename = NULL; const gchar *paths[] = { "/etc/os-release", "/usr/lib/os-release", NULL }; g_autofree gchar *buf = NULL; g_auto(GStrv) lines = NULL; /* find the correct file */ for (guint i = 0; paths[i] != NULL; i++) { g_debug ("looking for os-release at %s", paths[i]); if (g_file_test (paths[i], G_FILE_TEST_EXISTS)) { filename = paths[i]; break; } } if (filename == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "No os-release found"); return NULL; } /* load each line */ if (!g_file_get_contents (filename, &buf, NULL, error)) return NULL; hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); lines = g_strsplit (buf, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { gsize len, off = 0; g_auto(GStrv) split = NULL; /* split up into sections */ split = g_strsplit (lines[i], "=", 2); if (g_strv_length (split) < 2) continue; /* remove double quotes if set both ends */ len = strlen (split[1]); if (len == 0) continue; if (split[1][0] == '\"' && split[1][len-1] == '\"') { off++; len -= 2; } g_hash_table_insert (hash, g_strdup (split[0]), g_strndup (split[1] + off, len)); } return hash; } static gchar * fwupd_build_user_agent_os_release (void) { const gchar *keys[] = { "NAME", "VERSION_ID", "VARIANT", NULL }; g_autoptr(GHashTable) hash = NULL; g_autoptr(GPtrArray) ids_os = g_ptr_array_new (); /* get all keys */ hash = fwupd_get_os_release (NULL); if (hash == NULL) return NULL; /* create an array of the keys that exist */ for (guint i = 0; keys[i] != NULL; i++) { const gchar *value = g_hash_table_lookup (hash, keys[i]); if (value != NULL) g_ptr_array_add (ids_os, (gpointer) value); } if (ids_os->len == 0) return NULL; g_ptr_array_add (ids_os, NULL); return g_strjoinv (" ", (gchar **) ids_os->pdata); } static gchar * fwupd_build_user_agent_system (void) { struct utsname name_tmp; g_autofree gchar *locale = NULL; g_autofree gchar *os_release = NULL; g_autoptr(GPtrArray) ids = g_ptr_array_new_with_free_func (g_free); /* system, architecture and kernel, e.g. "Linux i686 4.14.5" */ memset (&name_tmp, 0, sizeof(struct utsname)); if (uname (&name_tmp) >= 0) { g_ptr_array_add (ids, g_strdup_printf ("%s %s %s", name_tmp.sysname, name_tmp.machine, name_tmp.release)); } /* current locale, e.g. "en-gb" */ locale = g_strdup (setlocale (LC_MESSAGES, NULL)); if (locale != NULL) { g_strdelimit (locale, ".", '\0'); g_strdelimit (locale, "_", '-'); g_ptr_array_add (ids, g_steal_pointer (&locale)); } /* OS release, e.g. "Fedora 27 Workstation" */ os_release = fwupd_build_user_agent_os_release (); if (os_release != NULL) g_ptr_array_add (ids, g_steal_pointer (&os_release)); /* convert to string */ if (ids->len == 0) return NULL; g_ptr_array_add (ids, NULL); return g_strjoinv ("; ", (gchar **) ids->pdata); } /** * fwupd_build_user_agent: * @package_name: client program name, e.g. "gnome-software" * @package_version: client program version, e.g. "3.28.1" * * Builds a user-agent to use for the download. * * Supplying harmless details to the server means it knows more about each * client. This allows the web service to respond in a different way, for * instance sending a different metadata file for old versions of fwupd, or * returning an error for Solaris machines. * * Before freaking out about theoretical privacy implications, much more data * than this is sent to each and every website you visit. * * Returns: a string, e.g. `foo/0.1 (Linux i386 4.14.5; en; Fedora 27) fwupd/1.0.3` * * Since: 1.0.3 **/ gchar * fwupd_build_user_agent (const gchar *package_name, const gchar *package_version) { GString *str = g_string_new (NULL); g_autofree gchar *system = NULL; /* application name and version */ g_string_append_printf (str, "%s/%s", package_name, package_version); /* system information */ system = fwupd_build_user_agent_system (); if (system != NULL) g_string_append_printf (str, " (%s)", system); /* platform, which in our case is just fwupd */ if (g_strcmp0 (package_name, "fwupd") != 0) g_string_append_printf (str, " fwupd/%s", PACKAGE_VERSION); /* success */ return g_string_free (str, FALSE); } /** * fwupd_build_machine_id: * @salt: The salt, or %NULL for none * @error: A #GError or %NULL * * Gets a salted hash of the /etc/machine-id contents. This can be used to * identify a specific machine. It is not possible to recover the original * machine-id from the machine-hash. * * Returns: the SHA256 machine hash, or %NULL if the ID is not present * * Since: 1.0.4 **/ gchar * fwupd_build_machine_id (const gchar *salt, GError **error) { g_autofree gchar *buf = NULL; g_autoptr(GChecksum) csum = NULL; gsize sz = 0; /* this has to exist */ if (!g_file_get_contents ("/etc/machine-id", &buf, &sz, error)) return NULL; if (sz == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "The machine-id is present but unset"); return NULL; } csum = g_checksum_new (G_CHECKSUM_SHA256); if (salt != NULL) g_checksum_update (csum, (const guchar *) salt, (gssize) strlen (salt)); g_checksum_update (csum, (const guchar *) buf, (gssize) sz); return g_strdup (g_checksum_get_string (csum)); } static void fwupd_build_history_report_json_metadata_device (JsonBuilder *builder, FwupdDevice *dev) { FwupdRelease *rel = fwupd_device_get_release_default (dev); GHashTable *metadata = fwupd_release_get_metadata (rel); g_autoptr(GList) keys = NULL; /* add each metadata value */ keys = g_hash_table_get_keys (metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup (metadata, key); json_builder_set_member_name (builder, key); json_builder_add_string_value (builder, value); } } static void fwupd_build_history_report_json_device (JsonBuilder *builder, FwupdDevice *dev) { FwupdRelease *rel = fwupd_device_get_release_default (dev); GPtrArray *checksums; /* identify the firmware used */ json_builder_set_member_name (builder, "Checksum"); checksums = fwupd_release_get_checksums (rel); json_builder_add_string_value (builder, fwupd_checksum_get_by_kind (checksums, G_CHECKSUM_SHA1)); /* identify the firmware written */ checksums = fwupd_device_get_checksums (dev); if (checksums->len > 0) { json_builder_set_member_name (builder, "ChecksumDevice"); json_builder_begin_array (builder); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index (checksums, i); json_builder_add_string_value (builder, checksum); } json_builder_end_array (builder); } /* include the protocol used */ if (fwupd_release_get_protocol (rel) != NULL) { json_builder_set_member_name (builder, "Protocol"); json_builder_add_string_value (builder, fwupd_release_get_protocol (rel)); } /* set the error state of the report */ json_builder_set_member_name (builder, "UpdateState"); json_builder_add_int_value (builder, fwupd_device_get_update_state (dev)); if (fwupd_device_get_update_error (dev) != NULL) { json_builder_set_member_name (builder, "UpdateError"); json_builder_add_string_value (builder, fwupd_device_get_update_error (dev)); } if (fwupd_release_get_update_message (rel) != NULL) { json_builder_set_member_name (builder, "UpdateMessage"); json_builder_add_string_value (builder, fwupd_release_get_update_message (rel)); } /* map back to the dev type on the LVFS */ json_builder_set_member_name (builder, "Guid"); json_builder_add_string_value (builder, fwupd_device_get_guid_default (dev)); json_builder_set_member_name (builder, "Plugin"); json_builder_add_string_value (builder, fwupd_device_get_plugin (dev)); /* report what we're trying to update *from* and *to* */ json_builder_set_member_name (builder, "VersionOld"); json_builder_add_string_value (builder, fwupd_device_get_version (dev)); json_builder_set_member_name (builder, "VersionNew"); json_builder_add_string_value (builder, fwupd_release_get_version (rel)); /* to know the state of the dev we're trying to update */ json_builder_set_member_name (builder, "Flags"); json_builder_add_int_value (builder, fwupd_device_get_flags (dev)); /* to know when the update tried to happen, and how soon after boot */ json_builder_set_member_name (builder, "Created"); json_builder_add_int_value (builder, fwupd_device_get_created (dev)); json_builder_set_member_name (builder, "Modified"); json_builder_add_int_value (builder, fwupd_device_get_modified (dev)); /* add saved metadata to the report */ json_builder_set_member_name (builder, "Metadata"); json_builder_begin_object (builder); fwupd_build_history_report_json_metadata_device (builder, dev); json_builder_end_object (builder); } static gboolean fwupd_build_history_report_json_metadata (JsonBuilder *builder, GError **error) { g_autoptr(GHashTable) hash = NULL; struct { const gchar *key; const gchar *val; } distro_kv[] = { { "ID", "DistroId" }, { "VERSION_ID", "DistroVersion" }, { "VARIANT_ID", "DistroVariant" }, { NULL, NULL } }; /* get all required os-release keys */ hash = fwupd_get_os_release (error); if (hash == NULL) return FALSE; for (guint i = 0; distro_kv[i].key != NULL; i++) { const gchar *tmp = g_hash_table_lookup (hash, distro_kv[i].key); if (tmp != NULL) { json_builder_set_member_name (builder, distro_kv[i].val); json_builder_add_string_value (builder, tmp); } } return TRUE; } /** * fwupd_build_history_report_json: * @devices: (element-type FwupdDevice): devices * @error: A #GError or %NULL * * Builds a JSON report for the list of devices. No filtering is done on the * @devices array, and it is expected that the caller will filter to something * sane, e.g. %FWUPD_DEVICE_FLAG_REPORTED at the bare minimum. * * Returns: a string, or %NULL if the ID is not present * * Since: 1.0.4 **/ gchar * fwupd_build_history_report_json (GPtrArray *devices, GError **error) { gchar *data; g_autofree gchar *machine_id = NULL; g_autoptr(JsonBuilder) builder = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonNode) json_root = NULL; /* get a hash that represents the machine */ machine_id = fwupd_build_machine_id ("fwupd", error); if (machine_id == NULL) return NULL; /* create header */ builder = json_builder_new (); json_builder_begin_object (builder); json_builder_set_member_name (builder, "ReportVersion"); json_builder_add_int_value (builder, 2); json_builder_set_member_name (builder, "MachineId"); json_builder_add_string_value (builder, machine_id); /* this is system metadata not stored in the database */ json_builder_set_member_name (builder, "Metadata"); json_builder_begin_object (builder); if (!fwupd_build_history_report_json_metadata (builder, error)) return NULL; json_builder_end_object (builder); /* add each device */ json_builder_set_member_name (builder, "Reports"); json_builder_begin_array (builder); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); json_builder_begin_object (builder); fwupd_build_history_report_json_device (builder, dev); json_builder_end_object (builder); } json_builder_end_array (builder); json_builder_end_object (builder); /* export as a string */ json_root = json_builder_get_root (builder); json_generator = json_generator_new (); json_generator_set_pretty (json_generator, TRUE); json_generator_set_root (json_generator, json_root); data = json_generator_to_data (json_generator, NULL); if (data == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert to JSON string"); return NULL; } return data; } #define FWUPD_GUID_NAMESPACE_DEFAULT "6ba7b810-9dad-11d1-80b4-00c04fd430c8" #define FWUPD_GUID_NAMESPACE_MICROSOFT "70ffd812-4c7f-4c7d-0000-000000000000" typedef struct __attribute__((packed)) { guint32 a; guint16 b; guint16 c; guint16 d; guint8 e[6]; } fwupd_guid_native_t; /** * fwupd_guid_to_string: * @guid: a #fwupd_guid_t to read * @flags: some %FwupdGuidFlags, e.g. %FWUPD_GUID_FLAG_MIXED_ENDIAN * * Returns a text GUID of mixed or BE endian for a packed buffer. * * Returns: A new GUID * * Since: 1.2.5 **/ gchar * fwupd_guid_to_string (const fwupd_guid_t *guid, FwupdGuidFlags flags) { fwupd_guid_native_t gnat; g_return_val_if_fail (guid != NULL, NULL); /* copy to avoid issues with aligning */ memcpy (&gnat, guid, sizeof(gnat)); /* mixed is bizaar, but specified as the DCE encoding */ if (flags & FWUPD_GUID_FLAG_MIXED_ENDIAN) { return g_strdup_printf ("%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", GUINT32_FROM_LE(gnat.a), GUINT16_FROM_LE(gnat.b), GUINT16_FROM_LE(gnat.c), GUINT16_FROM_BE(gnat.d), gnat.e[0], gnat.e[1], gnat.e[2], gnat.e[3], gnat.e[4], gnat.e[5]); } return g_strdup_printf ("%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", GUINT32_FROM_BE(gnat.a), GUINT16_FROM_BE(gnat.b), GUINT16_FROM_BE(gnat.c), GUINT16_FROM_BE(gnat.d), gnat.e[0], gnat.e[1], gnat.e[2], gnat.e[3], gnat.e[4], gnat.e[5]); } #if !GLIB_CHECK_VERSION(2,54,0) static gboolean str_has_sign (const gchar *str) { return str[0] == '-' || str[0] == '+'; } static gboolean str_has_hex_prefix (const gchar *str) { return str[0] == '0' && g_ascii_tolower (str[1]) == 'x'; } static gboolean g_ascii_string_to_unsigned (const gchar *str, guint base, guint64 min, guint64 max, guint64 *out_num, GError **error) { const gchar *end_ptr = NULL; gint saved_errno = 0; guint64 number; g_return_val_if_fail (str != NULL, FALSE); g_return_val_if_fail (base >= 2 && base <= 36, FALSE); g_return_val_if_fail (min <= max, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (str[0] == '\0') { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Empty string is not a number"); return FALSE; } errno = 0; number = g_ascii_strtoull (str, (gchar **)&end_ptr, base); saved_errno = errno; if (g_ascii_isspace (str[0]) || str_has_sign (str) || (base == 16 && str_has_hex_prefix (str)) || (saved_errno != 0 && saved_errno != ERANGE) || end_ptr == NULL || *end_ptr != '\0') { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "“%s” is not an unsigned number", str); return FALSE; } if (saved_errno == ERANGE || number < min || number > max) { g_autofree gchar *min_str = g_strdup_printf ("%" G_GUINT64_FORMAT, min); g_autofree gchar *max_str = g_strdup_printf ("%" G_GUINT64_FORMAT, max); g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Number “%s” is out of bounds [%s, %s]", str, min_str, max_str); return FALSE; } if (out_num != NULL) *out_num = number; return TRUE; } #endif /* GLIB_CHECK_VERSION(2,54,0) */ /** * fwupd_guid_from_string: * @guidstr: (nullable): a GUID, e.g. `00112233-4455-6677-8899-aabbccddeeff` * @guid: a #fwupd_guid_t, or NULL to just check the GUID * @flags: some %FwupdGuidFlags, e.g. %FWUPD_GUID_FLAG_MIXED_ENDIAN * @error: A #GError or %NULL * * Converts a string GUID into its binary encoding. All string GUIDs are * formatted as big endian but on-disk can be encoded in different ways. * * Returns: %TRUE for success * * Since: 1.2.5 **/ gboolean fwupd_guid_from_string (const gchar *guidstr, fwupd_guid_t *guid, FwupdGuidFlags flags, GError **error) { fwupd_guid_native_t gu = { 0x0 }; gboolean mixed_endian = flags & FWUPD_GUID_FLAG_MIXED_ENDIAN; guint64 tmp; g_auto(GStrv) split = NULL; g_return_val_if_fail (guidstr != NULL, FALSE); /* split into sections */ if (strlen (guidstr) != 36) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "is not valid format"); return FALSE; } split = g_strsplit (guidstr, "-", 5); if (g_strv_length (split) != 5) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "is not valid format, no dashes"); return FALSE; } if (strlen (split[0]) != 8 && strlen (split[1]) != 4 && strlen (split[2]) != 4 && strlen (split[3]) != 4 && strlen (split[4]) != 12) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "is not valid format, not GUID"); return FALSE; } /* parse */ if (!g_ascii_string_to_unsigned (split[0], 16, 0, 0xffffffff, &tmp, error)) return FALSE; gu.a = mixed_endian ? GUINT32_TO_LE(tmp) : GUINT32_TO_BE(tmp); if (!g_ascii_string_to_unsigned (split[1], 16, 0, 0xffff, &tmp, error)) return FALSE; gu.b = mixed_endian ? GUINT16_TO_LE(tmp) : GUINT16_TO_BE(tmp); if (!g_ascii_string_to_unsigned (split[2], 16, 0, 0xffff, &tmp, error)) return FALSE; gu.c = mixed_endian ? GUINT16_TO_LE(tmp) : GUINT16_TO_BE(tmp); if (!g_ascii_string_to_unsigned (split[3], 16, 0, 0xffff, &tmp, error)) return FALSE; gu.d = GUINT16_TO_BE(tmp); for (guint i = 0; i < 6; i++) { gchar buffer[3] = { 0x0 }; memcpy (buffer, split[4] + (i * 2), 2); if (!g_ascii_string_to_unsigned (buffer, 16, 0, 0xff, &tmp, error)) return FALSE; gu.e[i] = tmp; } if (guid != NULL) memcpy (guid, &gu, sizeof(gu)); /* success */ return TRUE; } /** * fwupd_guid_hash_data: * @data: data to hash * @datasz: length of @data * @flags: some %FwupdGuidFlags, e.g. %FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT * * Returns a GUID for some data. This uses a hash and so even small * differences in the @data will produce radically different return values. * * The implementation is taken from RFC4122, Section 4.1.3; specifically * using a type-5 SHA-1 hash. * * Returns: A new GUID, or %NULL for internal error * * Since: 1.2.5 **/ gchar * fwupd_guid_hash_data (const guint8 *data, gsize datasz, FwupdGuidFlags flags) { const gchar *namespace_id = FWUPD_GUID_NAMESPACE_DEFAULT; gsize digestlen = 20; guint8 hash[20]; fwupd_guid_t uu_namespace; fwupd_guid_t uu_new; g_autoptr(GChecksum) csum = NULL; g_return_val_if_fail (namespace_id != NULL, NULL); g_return_val_if_fail (data != NULL, NULL); g_return_val_if_fail (datasz != 0, NULL); /* old MS GUID */ if (flags & FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT) namespace_id = FWUPD_GUID_NAMESPACE_MICROSOFT; /* convert the namespace to binary: hardcoded BE, not @flags */ if (!fwupd_guid_from_string (namespace_id, &uu_namespace, FWUPD_GUID_FLAG_NONE, NULL)) return NULL; /* hash the namespace and then the string */ csum = g_checksum_new (G_CHECKSUM_SHA1); g_checksum_update (csum, (guchar *) &uu_namespace, sizeof(uu_namespace)); g_checksum_update (csum, (guchar *) data, (gssize) datasz); g_checksum_get_digest (csum, hash, &digestlen); /* copy most parts of the hash 1:1 */ memcpy (uu_new, hash, sizeof(uu_new)); /* set specific bits according to Section 4.1.3 */ uu_new[6] = (guint8) ((uu_new[6] & 0x0f) | (5 << 4)); uu_new[8] = (guint8) ((uu_new[8] & 0x3f) | 0x80); return fwupd_guid_to_string ((const fwupd_guid_t *) &uu_new, flags); } /** * fwupd_guid_is_valid: * @guid: string to check, e.g. `00112233-4455-6677-8899-aabbccddeeff` * * Checks the string is a valid GUID. * * Returns: %TRUE if @guid was a valid GUID, %FALSE otherwise * * Since: 1.2.5 **/ gboolean fwupd_guid_is_valid (const gchar *guid) { if (guid == NULL) return FALSE; if (!fwupd_guid_from_string (guid, NULL, FWUPD_GUID_FLAG_NONE, NULL)) return FALSE; if (g_strcmp0 (guid, "00000000-0000-0000-0000-000000000000") == 0) return FALSE; return TRUE; } /** * fwupd_guid_hash_string: * @str: A source string to use as a key * * Returns a GUID for a given string. This uses a hash and so even small * differences in the @str will produce radically different return values. * * The default implementation is taken from RFC4122, Section 4.1.3; specifically * using a type-5 SHA-1 hash with a DNS namespace. * The same result can be obtained with this simple python program: * * #!/usr/bin/python * import uuid * print uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org') * * Returns: A new GUID, or %NULL if the string was invalid * * Since: 1.2.5 **/ gchar * fwupd_guid_hash_string (const gchar *str) { if (str == NULL || str[0] == '\0') return NULL; return fwupd_guid_hash_data ((const guint8 *) str, strlen (str), FWUPD_GUID_FLAG_NONE); } fwupd-1.2.14/libfwupd/fwupd-common.h000066400000000000000000000043201402665037500173330ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FWUPD_DBUS_PATH "/" #define FWUPD_DBUS_SERVICE "org.freedesktop.fwupd" #define FWUPD_DBUS_INTERFACE "org.freedesktop.fwupd" #define FWUPD_DEVICE_ID_ANY "*" /** * FwupdGuidFlags: * @FWUPD_GUID_FLAG_NONE: No trust * @FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT: Use the Microsoft-compatible namespace * @FWUPD_GUID_FLAG_MIXED_ENDIAN: Use EFI mixed endian representation * * The flags to show how the data should be converted. **/ typedef enum { FWUPD_GUID_FLAG_NONE = 0, /* Since: 1.2.5 */ FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT = 1 << 0, /* Since: 1.2.5 */ FWUPD_GUID_FLAG_MIXED_ENDIAN = 1 << 1, /* Since: 1.2.5 */ /*< private >*/ FWUPD_GUID_FLAG_LAST } FwupdGuidFlags; /* GObject Introspection does not understand typedefs with sizes */ #ifndef __GI_SCANNER__ typedef guint8 fwupd_guid_t[16]; #endif const gchar *fwupd_checksum_get_best (GPtrArray *checksums); const gchar *fwupd_checksum_get_by_kind (GPtrArray *checksums, GChecksumType kind); GChecksumType fwupd_checksum_guess_kind (const gchar *checksum); gchar *fwupd_build_user_agent (const gchar *package_name, const gchar *package_version); gchar *fwupd_build_machine_id (const gchar *salt, GError **error); GHashTable *fwupd_get_os_release (GError **error); gchar *fwupd_build_history_report_json (GPtrArray *devices, GError **error); #ifndef __GI_SCANNER__ gchar *fwupd_guid_to_string (const fwupd_guid_t *guid, FwupdGuidFlags flags); gboolean fwupd_guid_from_string (const gchar *guidstr, fwupd_guid_t *guid, FwupdGuidFlags flags, GError **error); #else gchar *fwupd_guid_to_string (const guint8 guid[16], FwupdGuidFlags flags); gboolean fwupd_guid_from_string (const gchar *guidstr, guint8 guid[16], FwupdGuidFlags flags, GError **error); #endif gboolean fwupd_guid_is_valid (const gchar *guid); gchar *fwupd_guid_hash_string (const gchar *str); gchar *fwupd_guid_hash_data (const guint8 *data, gsize datasz, FwupdGuidFlags flags); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-deprecated.h000066400000000000000000000002541402665037500201450ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once G_BEGIN_DECLS /* indeed, nothing */ G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-device-private.h000066400000000000000000000010531402665037500207520ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fwupd-device.h" G_BEGIN_DECLS GVariant *fwupd_device_to_variant (FwupdDevice *device); GVariant *fwupd_device_to_variant_full (FwupdDevice *device, FwupdDeviceFlags flags); void fwupd_device_incorporate (FwupdDevice *self, FwupdDevice *donor); void fwupd_device_to_json (FwupdDevice *device, JsonBuilder *builder); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-device.c000066400000000000000000001566151402665037500173140ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-device-private.h" #include "fwupd-release-private.h" /** * SECTION:fwupd-device * @short_description: a hardware device * * An object that represents a physical device on the host. * * See also: #FwupdRelease */ static void fwupd_device_finalize (GObject *object); typedef struct { gchar *id; gchar *parent_id; guint64 created; guint64 modified; guint64 flags; GPtrArray *guids; GPtrArray *instance_ids; GPtrArray *icons; gchar *name; gchar *serial; gchar *summary; gchar *description; gchar *vendor; gchar *vendor_id; gchar *homepage; gchar *plugin; gchar *version; gchar *version_lowest; gchar *version_bootloader; FwupdVersionFormat version_format; GPtrArray *checksums; guint32 flashes_left; guint32 install_duration; FwupdUpdateState update_state; gchar *update_error; gchar *update_message; GPtrArray *releases; FwupdDevice *parent; } FwupdDevicePrivate; enum { PROP_0, PROP_VERSION_FORMAT, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE (FwupdDevice, fwupd_device, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_device_get_instance_private (o)) /** * fwupd_device_get_checksums: * @device: A #FwupdDevice * * Gets the device checksums. * * Returns: (element-type utf8) (transfer none): the checksums, which may be empty * * Since: 0.9.3 **/ GPtrArray * fwupd_device_get_checksums (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->checksums; } /** * fwupd_device_add_checksum: * @device: A #FwupdDevice * @checksum: the device checksum * * Sets the device checksum. * * Since: 0.9.3 **/ void fwupd_device_add_checksum (FwupdDevice *device, const gchar *checksum) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_return_if_fail (checksum != NULL); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum_tmp = g_ptr_array_index (priv->checksums, i); if (g_strcmp0 (checksum_tmp, checksum) == 0) return; } g_ptr_array_add (priv->checksums, g_strdup (checksum)); } /** * fwupd_device_get_summary: * @device: A #FwupdDevice * * Gets the device summary. * * Returns: the device summary, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_summary (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->summary; } /** * fwupd_device_set_summary: * @device: A #FwupdDevice * @summary: the device one line summary * * Sets the device summary. * * Since: 0.9.3 **/ void fwupd_device_set_summary (FwupdDevice *device, const gchar *summary) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->summary); priv->summary = g_strdup (summary); } /** * fwupd_device_get_serial: * @device: A #FwupdDevice * * Gets the serial number for the device. * * Returns: a string value, or %NULL if never set. * * Since: 1.1.2 **/ const gchar * fwupd_device_get_serial (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->serial; } /** * fwupd_device_set_serial: * @device: A #FwupdDevice * @serial: the device serial number * * Sets the serial number for the device. * * Since: 1.1.2 **/ void fwupd_device_set_serial (FwupdDevice *device, const gchar *serial) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->serial); priv->serial = g_strdup (serial); } /** * fwupd_device_get_id: * @device: A #FwupdDevice * * Gets the ID. * * Returns: the ID, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_id (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->id; } /** * fwupd_device_set_id: * @device: A #FwupdDevice * @id: the device ID, e.g. `USB:foo` * * Sets the ID. * * Since: 0.9.3 **/ void fwupd_device_set_id (FwupdDevice *device, const gchar *id) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->id); priv->id = g_strdup (id); } /** * fwupd_device_get_parent_id: * @device: A #FwupdDevice * * Gets the ID. * * Returns: the parent ID, or %NULL if unset * * Since: 1.0.8 **/ const gchar * fwupd_device_get_parent_id (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->parent_id; } /** * fwupd_device_set_parent_id: * @device: A #FwupdDevice * @parent_id: the device ID, e.g. `USB:foo` * * Sets the parent ID. * * Since: 1.0.8 **/ void fwupd_device_set_parent_id (FwupdDevice *device, const gchar *parent_id) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->parent_id); priv->parent_id = g_strdup (parent_id); } /** * fwupd_device_get_parent: * @device: A #FwupdDevice * * Gets the parent. * * Returns: (transfer none): the parent device, or %NULL if unset * * Since: 1.0.8 **/ FwupdDevice * fwupd_device_get_parent (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->parent; } /** * fwupd_device_set_parent: * @device: A #FwupdDevice * @parent: another #FwupdDevice, or %NULL * * Sets the parent. Only used internally. * * Since: 1.0.8 **/ void fwupd_device_set_parent (FwupdDevice *device, FwupdDevice *parent) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_set_object (&priv->parent, parent); } /** * fwupd_device_get_guids: * @device: A #FwupdDevice * * Gets the GUIDs. * * Returns: (element-type utf8) (transfer none): the GUIDs * * Since: 0.9.3 **/ GPtrArray * fwupd_device_get_guids (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->guids; } /** * fwupd_device_has_guid: * @device: A #FwupdDevice * @guid: the GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad` * * Finds out if the device has this specific GUID. * * Returns: %TRUE if the GUID is found * * Since: 0.9.3 **/ gboolean fwupd_device_has_guid (FwupdDevice *device, const gchar *guid) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), FALSE); for (guint i = 0; i < priv->guids->len; i++) { const gchar *guid_tmp = g_ptr_array_index (priv->guids, i); if (g_strcmp0 (guid, guid_tmp) == 0) return TRUE; } return FALSE; } /** * fwupd_device_add_guid: * @device: A #FwupdDevice * @guid: the GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad` * * Adds the GUID if it does not already exist. * * Since: 0.9.3 **/ void fwupd_device_add_guid (FwupdDevice *device, const gchar *guid) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); if (fwupd_device_has_guid (device, guid)) return; g_ptr_array_add (priv->guids, g_strdup (guid)); } /** * fwupd_device_get_guid_default: * @device: A #FwupdDevice * * Gets the default GUID. * * Returns: the GUID, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_guid_default (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); if (priv->guids->len == 0) return NULL; return g_ptr_array_index (priv->guids, 0); } /** * fwupd_device_get_instance_ids: * @device: A #FwupdDevice * * Gets the InstanceIDs. * * Returns: (element-type utf8) (transfer none): the InstanceID * * Since: 1.2.5 **/ GPtrArray * fwupd_device_get_instance_ids (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->instance_ids; } /** * fwupd_device_has_instance_id: * @device: A #FwupdDevice * @instance_id: the InstanceID, e.g. `PCI\VEN_10EC&DEV_525A` * * Finds out if the device has this specific InstanceID. * * Returns: %TRUE if the InstanceID is found * * Since: 1.2.5 **/ gboolean fwupd_device_has_instance_id (FwupdDevice *device, const gchar *instance_id) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), FALSE); for (guint i = 0; i < priv->instance_ids->len; i++) { const gchar *instance_id_tmp = g_ptr_array_index (priv->instance_ids, i); if (g_strcmp0 (instance_id, instance_id_tmp) == 0) return TRUE; } return FALSE; } /** * fwupd_device_add_instance_id: * @device: A #FwupdDevice * @instance_id: the GUID, e.g. `PCI\VEN_10EC&DEV_525A` * * Adds the InstanceID if it does not already exist. * * Since: 1.2.5 **/ void fwupd_device_add_instance_id (FwupdDevice *device, const gchar *instance_id) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); if (fwupd_device_has_instance_id (device, instance_id)) return; g_ptr_array_add (priv->instance_ids, g_strdup (instance_id)); } /** * fwupd_device_get_icons: * @device: A #FwupdDevice * * Gets the icon names to use for the device. * * NOTE: Icons specified without a full path are stock icons and should * be loaded from the users icon theme. * * Returns: (element-type utf8) (transfer none): an array of icon names * * Since: 0.9.8 **/ GPtrArray * fwupd_device_get_icons (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->icons; } static gboolean fwupd_device_has_icon (FwupdDevice *device, const gchar *icon) { FwupdDevicePrivate *priv = GET_PRIVATE (device); for (guint i = 0; i < priv->icons->len; i++) { const gchar *icon_tmp = g_ptr_array_index (priv->icons, i); if (g_strcmp0 (icon, icon_tmp) == 0) return TRUE; } return FALSE; } /** * fwupd_device_add_icon: * @device: A #FwupdDevice * @icon: the name, e.g. `input-mouse` or `/usr/share/icons/foo.png` * * Adds the icon name if it does not already exist. * * Since: 0.9.8 **/ void fwupd_device_add_icon (FwupdDevice *device, const gchar *icon) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); if (fwupd_device_has_icon (device, icon)) return; g_ptr_array_add (priv->icons, g_strdup (icon)); } /** * fwupd_device_get_name: * @device: A #FwupdDevice * * Gets the device name. * * Returns: the device name, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_name (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->name; } /** * fwupd_device_set_name: * @device: A #FwupdDevice * @name: the device name, e.g. `ColorHug2` * * Sets the device name. * * Since: 0.9.3 **/ void fwupd_device_set_name (FwupdDevice *device, const gchar *name) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->name); priv->name = g_strdup (name); } /** * fwupd_device_get_vendor: * @device: A #FwupdDevice * * Gets the device vendor. * * Returns: the device vendor, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_vendor (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->vendor; } /** * fwupd_device_set_vendor: * @device: A #FwupdDevice * @vendor: the description * * Sets the device vendor. * * Since: 0.9.3 **/ void fwupd_device_set_vendor (FwupdDevice *device, const gchar *vendor) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->vendor); priv->vendor = g_strdup (vendor); } /** * fwupd_device_get_vendor_id: * @device: A #FwupdDevice * * Gets the device vendor ID. * * Returns: the device vendor, e.g. 'USB:0x1234', or %NULL if unset * * Since: 0.9.4 **/ const gchar * fwupd_device_get_vendor_id (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->vendor_id; } /** * fwupd_device_set_vendor_id: * @device: A #FwupdDevice * @vendor_id: the ID, e.g. 'USB:0x1234' * * Sets the device vendor ID. * * Since: 0.9.4 **/ void fwupd_device_set_vendor_id (FwupdDevice *device, const gchar *vendor_id) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->vendor_id); priv->vendor_id = g_strdup (vendor_id); } /** * fwupd_device_get_description: * @device: A #FwupdDevice * * Gets the device description in AppStream markup format. * * Returns: the device description, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_description (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->description; } /** * fwupd_device_set_description: * @device: A #FwupdDevice * @description: the description in AppStream markup format * * Sets the device description. * * Since: 0.9.3 **/ void fwupd_device_set_description (FwupdDevice *device, const gchar *description) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->description); priv->description = g_strdup (description); } /** * fwupd_device_get_version: * @device: A #FwupdDevice * * Gets the device version. * * Returns: the device version, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_version (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->version; } /** * fwupd_device_set_version: * @device: A #FwupdDevice * @version: the device version, e.g. `1.2.3` * * Sets the device version. * * Since: 0.9.3 **/ void fwupd_device_set_version (FwupdDevice *device, const gchar *version) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->version); priv->version = g_strdup (version); } /** * fwupd_device_get_version_lowest: * @device: A #FwupdDevice * * Gets the lowest version of firmware the device will accept. * * Returns: the device version_lowest, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_version_lowest (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->version_lowest; } /** * fwupd_device_set_version_lowest: * @device: A #FwupdDevice * @version_lowest: the description * * Sets the lowest version of firmware the device will accept. * * Since: 0.9.3 **/ void fwupd_device_set_version_lowest (FwupdDevice *device, const gchar *version_lowest) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->version_lowest); priv->version_lowest = g_strdup (version_lowest); } /** * fwupd_device_get_version_bootloader: * @device: A #FwupdDevice * * Gets the version of the bootloader. * * Returns: the device version_bootloader, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_version_bootloader (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->version_bootloader; } /** * fwupd_device_set_version_bootloader: * @device: A #FwupdDevice * @version_bootloader: the description * * Sets the bootloader version. * * Since: 0.9.3 **/ void fwupd_device_set_version_bootloader (FwupdDevice *device, const gchar *version_bootloader) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->version_bootloader); priv->version_bootloader = g_strdup (version_bootloader); } /** * fwupd_device_get_flashes_left: * @device: A #FwupdDevice * * Gets the number of flash cycles left on the device * * Returns: the flash cycles left, or %NULL if unset * * Since: 0.9.3 **/ guint32 fwupd_device_get_flashes_left (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), 0); return priv->flashes_left; } /** * fwupd_device_set_flashes_left: * @device: A #FwupdDevice * @flashes_left: the description * * Sets the number of flash cycles left on the device * * Since: 0.9.3 **/ void fwupd_device_set_flashes_left (FwupdDevice *device, guint32 flashes_left) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); priv->flashes_left = flashes_left; } /** * fwupd_device_get_install_duration: * @device: A #FwupdDevice * * Gets the time estimate for firmware installation (in seconds) * * Returns: the estimated time to flash this device (or 0 if unset) * * Since: 1.1.3 **/ guint32 fwupd_device_get_install_duration (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), 0); return priv->install_duration; } /** * fwupd_device_set_install_duration: * @device: A #FwupdDevice * @duration: The amount of time * * Sets the time estimate for firmware installation (in seconds) * * Since: 1.1.3 **/ void fwupd_device_set_install_duration (FwupdDevice *device, guint32 duration) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); priv->install_duration = duration; } /** * fwupd_device_get_plugin: * @device: A #FwupdDevice * * Gets the plugin that created the device. * * Returns: the plugin name, or %NULL if unset * * Since: 1.0.0 **/ const gchar * fwupd_device_get_plugin (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->plugin; } /** * fwupd_device_set_plugin: * @device: A #FwupdDevice * @plugin: the plugin name, e.g. `colorhug` * * Sets the plugin that created the device. * * Since: 1.0.0 **/ void fwupd_device_set_plugin (FwupdDevice *device, const gchar *plugin) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->plugin); priv->plugin = g_strdup (plugin); } /** * fwupd_device_get_flags: * @device: A #FwupdDevice * * Gets the device flags. * * Returns: the device flags, or 0 if unset * * Since: 0.9.3 **/ guint64 fwupd_device_get_flags (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), 0); return priv->flags; } /** * fwupd_device_set_flags: * @device: A #FwupdDevice * @flags: the device flags, e.g. %FWUPD_DEVICE_FLAG_REQUIRE_AC * * Sets the device flags. * * Since: 0.9.3 **/ void fwupd_device_set_flags (FwupdDevice *device, guint64 flags) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); priv->flags = flags; } /** * fwupd_device_add_flag: * @device: A #FwupdDevice * @flag: the #FwupdDeviceFlags * * Adds a specific device flag to the device. * * Since: 0.9.3 **/ void fwupd_device_add_flag (FwupdDevice *device, FwupdDeviceFlags flag) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); priv->flags |= flag; } /** * fwupd_device_remove_flag: * @device: A #FwupdDevice * @flag: the #FwupdDeviceFlags * * Removes a specific device flag from the device. * * Since: 0.9.3 **/ void fwupd_device_remove_flag (FwupdDevice *device, FwupdDeviceFlags flag) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); priv->flags &= ~flag; } /** * fwupd_device_has_flag: * @device: A #FwupdDevice * @flag: the #FwupdDeviceFlags * * Finds if the device has a specific device flag. * * Returns: %TRUE if the flag is set * * Since: 0.9.3 **/ gboolean fwupd_device_has_flag (FwupdDevice *device, FwupdDeviceFlags flag) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), FALSE); return (priv->flags & flag) > 0; } /** * fwupd_device_get_created: * @device: A #FwupdDevice * * Gets when the device was created. * * Returns: the UNIX time, or 0 if unset * * Since: 0.9.3 **/ guint64 fwupd_device_get_created (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), 0); return priv->created; } /** * fwupd_device_set_created: * @device: A #FwupdDevice * @created: the UNIX time * * Sets when the device was created. * * Since: 0.9.3 **/ void fwupd_device_set_created (FwupdDevice *device, guint64 created) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); priv->created = created; } /** * fwupd_device_get_modified: * @device: A #FwupdDevice * * Gets when the device was modified. * * Returns: the UNIX time, or 0 if unset * * Since: 0.9.3 **/ guint64 fwupd_device_get_modified (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), 0); return priv->modified; } /** * fwupd_device_set_modified: * @device: A #FwupdDevice * @modified: the UNIX time * * Sets when the device was modified. * * Since: 0.9.3 **/ void fwupd_device_set_modified (FwupdDevice *device, guint64 modified) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); priv->modified = modified; } /** * fwupd_device_incorporate: * @self: A #FwupdDevice * @donor: Another #FwupdDevice * * Copy all properties from the donor object if they have not already been set. * * Since: 1.1.0 **/ void fwupd_device_incorporate (FwupdDevice *self, FwupdDevice *donor) { FwupdDevicePrivate *priv = GET_PRIVATE (self); FwupdDevicePrivate *priv_donor = GET_PRIVATE (donor); if (priv->flags == 0) fwupd_device_add_flag (self, priv_donor->flags); if (priv->created == 0) fwupd_device_set_created (self, priv_donor->created); if (priv->modified == 0) fwupd_device_set_modified (self, priv_donor->modified); if (priv->flashes_left == 0) fwupd_device_set_flashes_left (self, priv_donor->flashes_left); if (priv->install_duration == 0) fwupd_device_set_install_duration (self, priv_donor->install_duration); if (priv->update_state == 0) fwupd_device_set_update_state (self, priv_donor->update_state); if (priv->description == NULL) fwupd_device_set_description (self, priv_donor->description); if (priv->id == NULL) fwupd_device_set_id (self, priv_donor->id); if (priv->parent_id == NULL) fwupd_device_set_parent_id (self, priv_donor->parent_id); if (priv->name == NULL) fwupd_device_set_name (self, priv_donor->name); if (priv->serial == NULL) fwupd_device_set_serial (self, priv_donor->serial); if (priv->summary == NULL) fwupd_device_set_summary (self, priv_donor->summary); if (priv->vendor == NULL) fwupd_device_set_vendor (self, priv_donor->vendor); if (priv->vendor_id == NULL) fwupd_device_set_vendor_id (self, priv_donor->vendor_id); if (priv->plugin == NULL) fwupd_device_set_plugin (self, priv_donor->plugin); if (priv->update_error == NULL) fwupd_device_set_update_error (self, priv_donor->update_error); if (priv->update_message == NULL) fwupd_device_set_update_message (self, priv_donor->update_message); if (priv->version == NULL) fwupd_device_set_version (self, priv_donor->version); if (priv->version_lowest == NULL) fwupd_device_set_version_lowest (self, priv_donor->version_lowest); if (priv->version_bootloader == NULL) fwupd_device_set_version_bootloader (self, priv_donor->version_bootloader); if (priv->version_format == FWUPD_VERSION_FORMAT_UNKNOWN) fwupd_device_set_version_format (self, priv_donor->version_format); for (guint i = 0; i < priv_donor->guids->len; i++) { const gchar *tmp = g_ptr_array_index (priv_donor->guids, i); fwupd_device_add_guid (self, tmp); } for (guint i = 0; i < priv_donor->instance_ids->len; i++) { const gchar *tmp = g_ptr_array_index (priv_donor->instance_ids, i); fwupd_device_add_instance_id (self, tmp); } for (guint i = 0; i < priv_donor->icons->len; i++) { const gchar *tmp = g_ptr_array_index (priv_donor->icons, i); fwupd_device_add_icon (self, tmp); } for (guint i = 0; i < priv_donor->checksums->len; i++) { const gchar *tmp = g_ptr_array_index (priv_donor->checksums, i); fwupd_device_add_checksum (self, tmp); } for (guint i = 0; i < priv_donor->releases->len; i++) { FwupdRelease *tmp = g_ptr_array_index (priv_donor->releases, i); fwupd_device_add_release (self, tmp); } } /** * fwupd_device_to_variant_full: * @device: A #FwupdDevice * @flags: #FwupdDeviceFlags for the call * * Creates a GVariant from the device data. * Optionally provides additional data based upon flags * * Returns: the GVariant, or %NULL for error * * Since: 1.1.2 **/ GVariant * fwupd_device_to_variant_full (FwupdDevice *device, FwupdDeviceFlags flags) { FwupdDevicePrivate *priv = GET_PRIVATE (device); GVariantBuilder builder; g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); /* create an array with all the metadata in */ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); if (priv->id != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_DEVICE_ID, g_variant_new_string (priv->id)); } if (priv->parent_id != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_PARENT_DEVICE_ID, g_variant_new_string (priv->parent_id)); } if (priv->guids->len > 0) { const gchar * const *tmp = (const gchar * const *) priv->guids->pdata; g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_GUID, g_variant_new_strv (tmp, priv->guids->len)); } if (priv->icons->len > 0) { const gchar * const *tmp = (const gchar * const *) priv->icons->pdata; g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_ICON, g_variant_new_strv (tmp, priv->icons->len)); } if (priv->name != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_NAME, g_variant_new_string (priv->name)); } if (priv->vendor != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_VENDOR, g_variant_new_string (priv->vendor)); } if (priv->vendor_id != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_VENDOR_ID, g_variant_new_string (priv->vendor_id)); } if (priv->flags > 0) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_FLAGS, g_variant_new_uint64 (priv->flags)); } if (priv->created > 0) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_CREATED, g_variant_new_uint64 (priv->created)); } if (priv->modified > 0) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_MODIFIED, g_variant_new_uint64 (priv->modified)); } if (priv->description != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_DESCRIPTION, g_variant_new_string (priv->description)); } if (priv->summary != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_SUMMARY, g_variant_new_string (priv->summary)); } if (priv->checksums->len > 0) { g_autoptr(GString) str = g_string_new (""); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index (priv->checksums, i); g_string_append_printf (str, "%s,", checksum); } if (str->len > 0) g_string_truncate (str, str->len - 1); g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_CHECKSUM, g_variant_new_string (str->str)); } if (priv->plugin != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_PLUGIN, g_variant_new_string (priv->plugin)); } if (priv->version != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_VERSION, g_variant_new_string (priv->version)); } if (priv->version_lowest != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_LOWEST, g_variant_new_string (priv->version_lowest)); } if (priv->version_bootloader != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_BOOTLOADER, g_variant_new_string (priv->version_bootloader)); } if (priv->flashes_left > 0) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_FLASHES_LEFT, g_variant_new_uint32 (priv->flashes_left)); } if (priv->install_duration > 0) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_INSTALL_DURATION, g_variant_new_uint32 (priv->install_duration)); } if (priv->update_error != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_ERROR, g_variant_new_string (priv->update_error)); } if (priv->update_message != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_MESSAGE, g_variant_new_string (priv->update_message)); } if (priv->update_state != FWUPD_UPDATE_STATE_UNKNOWN) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_STATE, g_variant_new_uint32 (priv->update_state)); } if (priv->version_format != FWUPD_VERSION_FORMAT_UNKNOWN) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_FORMAT, g_variant_new_uint32 (priv->version_format)); } if (flags & FWUPD_DEVICE_FLAG_TRUSTED) { if (priv->serial != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_SERIAL, g_variant_new_string (priv->serial)); } if (priv->instance_ids->len > 0) { const gchar * const *tmp = (const gchar * const *) priv->instance_ids->pdata; g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_INSTANCE_IDS, g_variant_new_strv (tmp, priv->instance_ids->len)); } } /* create an array with all the metadata in */ if (priv->releases->len > 0) { g_autofree GVariant **children = NULL; children = g_new0 (GVariant *, priv->releases->len); for (guint i = 0; i < priv->releases->len; i++) { FwupdRelease *release = g_ptr_array_index (priv->releases, i); children[i] = fwupd_release_to_variant (release); } g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_RELEASE, g_variant_new_array (G_VARIANT_TYPE ("a{sv}"), children, priv->releases->len)); } return g_variant_new ("a{sv}", &builder); } /** * fwupd_device_to_variant: * @device: A #FwupdDevice * * Creates a GVariant from the device data omitting sensitive fields * * Returns: the GVariant, or %NULL for error * * Since: 1.0.0 **/ GVariant * fwupd_device_to_variant (FwupdDevice *device) { return fwupd_device_to_variant_full (device, FWUPD_DEVICE_FLAG_NONE); } static void fwupd_device_from_key_value (FwupdDevice *device, const gchar *key, GVariant *value) { if (g_strcmp0 (key, FWUPD_RESULT_KEY_RELEASE) == 0) { GVariantIter iter; GVariant *child; g_variant_iter_init (&iter, value); while ((child = g_variant_iter_next_value (&iter))) { g_autoptr(FwupdRelease) release = fwupd_release_from_variant (child); if (release != NULL) fwupd_device_add_release (device, release); g_variant_unref (child); } return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_DEVICE_ID) == 0) { fwupd_device_set_id (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_PARENT_DEVICE_ID) == 0) { fwupd_device_set_parent_id (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_FLAGS) == 0) { fwupd_device_set_flags (device, g_variant_get_uint64 (value)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_CREATED) == 0) { fwupd_device_set_created (device, g_variant_get_uint64 (value)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_MODIFIED) == 0) { fwupd_device_set_modified (device, g_variant_get_uint64 (value)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_GUID) == 0) { g_autofree const gchar **guids = g_variant_get_strv (value, NULL); for (guint i = 0; guids != NULL && guids[i] != NULL; i++) fwupd_device_add_guid (device, guids[i]); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_INSTANCE_IDS) == 0) { g_autofree const gchar **instance_ids = g_variant_get_strv (value, NULL); for (guint i = 0; instance_ids != NULL && instance_ids[i] != NULL; i++) fwupd_device_add_instance_id (device, instance_ids[i]); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_ICON) == 0) { g_autofree const gchar **icons = g_variant_get_strv (value, NULL); for (guint i = 0; icons != NULL && icons[i] != NULL; i++) fwupd_device_add_icon (device, icons[i]); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_NAME) == 0) { fwupd_device_set_name (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_VENDOR) == 0) { fwupd_device_set_vendor (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_VENDOR_ID) == 0) { fwupd_device_set_vendor_id (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_SERIAL) == 0) { fwupd_device_set_serial (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_SUMMARY) == 0) { fwupd_device_set_summary (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_DESCRIPTION) == 0) { fwupd_device_set_description (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_CHECKSUM) == 0) { const gchar *checksums = g_variant_get_string (value, NULL); if (checksums != NULL) { g_auto(GStrv) split = g_strsplit (checksums, ",", -1); for (guint i = 0; split[i] != NULL; i++) fwupd_device_add_checksum (device, split[i]); } return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_PLUGIN) == 0) { fwupd_device_set_plugin (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_VERSION) == 0) { fwupd_device_set_version (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_VERSION_LOWEST) == 0) { fwupd_device_set_version_lowest (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_VERSION_BOOTLOADER) == 0) { fwupd_device_set_version_bootloader (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_FLASHES_LEFT) == 0) { fwupd_device_set_flashes_left (device, g_variant_get_uint32 (value)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_INSTALL_DURATION) == 0) { fwupd_device_set_install_duration (device, g_variant_get_uint32 (value)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_UPDATE_ERROR) == 0) { fwupd_device_set_update_error (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_UPDATE_MESSAGE) == 0) { fwupd_device_set_update_message (device, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_UPDATE_STATE) == 0) { fwupd_device_set_update_state (device, g_variant_get_uint32 (value)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_VERSION_FORMAT) == 0) { fwupd_device_set_version_format (device, g_variant_get_uint32 (value)); return; } } static void fwupd_pad_kv_str (GString *str, const gchar *key, const gchar *value) { /* ignore */ if (key == NULL || value == NULL) return; g_string_append_printf (str, " %s: ", key); for (gsize i = strlen (key); i < 20; i++) g_string_append (str, " "); g_string_append_printf (str, "%s\n", value); } static void fwupd_pad_kv_unx (GString *str, const gchar *key, guint64 value) { g_autoptr(GDateTime) date = NULL; g_autofree gchar *tmp = NULL; /* ignore */ if (value == 0) return; date = g_date_time_new_from_unix_utc ((gint64) value); tmp = g_date_time_format (date, "%F"); fwupd_pad_kv_str (str, key, tmp); } static void fwupd_pad_kv_dfl (GString *str, const gchar *key, guint64 device_flags) { g_autoptr(GString) tmp = g_string_new (""); for (guint i = 0; i < 64; i++) { if ((device_flags & ((guint64) 1 << i)) == 0) continue; g_string_append_printf (tmp, "%s|", fwupd_device_flag_to_string ((guint64) 1 << i)); } if (tmp->len == 0) { g_string_append (tmp, fwupd_device_flag_to_string (0)); } else { g_string_truncate (tmp, tmp->len - 1); } fwupd_pad_kv_str (str, key, tmp->str); } static void fwupd_pad_kv_int (GString *str, const gchar *key, guint32 value) { g_autofree gchar *tmp = NULL; /* ignore */ if (value == 0) return; tmp = g_strdup_printf("%" G_GUINT32_FORMAT, value); fwupd_pad_kv_str (str, key, tmp); } /** * fwupd_device_get_update_state: * @device: A #FwupdDevice * * Gets the update state. * * Returns: the update state, or %FWUPD_UPDATE_STATE_UNKNOWN if unset * * Since: 0.9.8 **/ FwupdUpdateState fwupd_device_get_update_state (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), FWUPD_UPDATE_STATE_UNKNOWN); return priv->update_state; } /** * fwupd_device_set_update_state: * @device: A #FwupdDevice * @update_state: the state, e.g. %FWUPD_UPDATE_STATE_PENDING * * Sets the update state. * * Since: 0.9.8 **/ void fwupd_device_set_update_state (FwupdDevice *device, FwupdUpdateState update_state) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); priv->update_state = update_state; } /** * fwupd_device_get_version_format: * @device: A #FwupdDevice * * Gets the update state. * * Returns: the update state, or %FWUPD_VERSION_FORMAT_UNKNOWN if unset * * Since: 1.2.9 **/ FwupdVersionFormat fwupd_device_get_version_format (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), FWUPD_VERSION_FORMAT_UNKNOWN); return priv->version_format; } /** * fwupd_device_set_version_format: * @device: A #FwupdDevice * @version_format: the state, e.g. %FWUPD_VERSION_FORMAT_PENDING * * Sets the update state. * * Since: 1.2.9 **/ void fwupd_device_set_version_format (FwupdDevice *device, FwupdVersionFormat version_format) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); priv->version_format = version_format; } /** * fwupd_device_get_update_message: * @device: A #FwupdDevice * * Gets the update message. * * Returns: the update message, or %NULL if unset * * Since: 1.2.4 **/ const gchar * fwupd_device_get_update_message (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->update_message; } /** * fwupd_device_set_update_message: * @device: A #FwupdDevice * @update_message: the update message string * * Sets the update message. * * Since: 1.2.4 **/ void fwupd_device_set_update_message (FwupdDevice *device, const gchar *update_message) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->update_message); priv->update_message = g_strdup (update_message); } /** * fwupd_device_get_update_error: * @device: A #FwupdDevice * * Gets the update error. * * Returns: the update error, or %NULL if unset * * Since: 0.9.8 **/ const gchar * fwupd_device_get_update_error (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->update_error; } /** * fwupd_device_set_update_error: * @device: A #FwupdDevice * @update_error: the update error string * * Sets the update error. * * Since: 0.9.8 **/ void fwupd_device_set_update_error (FwupdDevice *device, const gchar *update_error) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->update_error); priv->update_error = g_strdup (update_error); } /** * fwupd_device_get_release_default: * @device: A #FwupdDevice * * Gets the default release for this device. * * Returns: (transfer none): the #FwupdRelease, or %NULL if not set * * Since: 0.9.8 **/ FwupdRelease * fwupd_device_get_release_default (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); if (priv->releases->len == 0) return NULL; return FWUPD_RELEASE (g_ptr_array_index (priv->releases, 0)); } /** * fwupd_device_get_releases: * @device: A #FwupdDevice * * Gets all the releases for this device. * * Returns: (transfer none) (element-type FwupdRelease): array of releases * * Since: 0.9.8 **/ GPtrArray * fwupd_device_get_releases (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->releases; } /** * fwupd_device_add_release: * @device: A #FwupdDevice * @release: a #FwupdRelease * * Adds a release for this device. * * Since: 0.9.8 **/ void fwupd_device_add_release (FwupdDevice *device, FwupdRelease *release) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_ptr_array_add (priv->releases, g_object_ref (release)); } static void fwupd_pad_kv_ups (GString *str, const gchar *key, FwupdUpdateState value) { if (value == FWUPD_UPDATE_STATE_UNKNOWN) return; fwupd_pad_kv_str (str, key, fwupd_update_state_to_string (value)); } static void fwupd_device_json_add_string (JsonBuilder *builder, const gchar *key, const gchar *str) { if (str == NULL) return; json_builder_set_member_name (builder, key); json_builder_add_string_value (builder, str); } static void fwupd_device_json_add_int (JsonBuilder *builder, const gchar *key, guint64 num) { if (num == 0) return; json_builder_set_member_name (builder, key); json_builder_add_int_value (builder, num); } /** * fwupd_device_to_json: * @device: A #FwupdDevice * @builder: A #JsonBuilder * * Adds a fwupd device to a JSON builder * * Since: 1.2.6 **/ void fwupd_device_to_json (FwupdDevice *device, JsonBuilder *builder) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_return_if_fail (builder != NULL); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_NAME, priv->name); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_DEVICE_ID, priv->id); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_PARENT_DEVICE_ID, priv->parent_id); if (priv->guids->len > 0) { json_builder_set_member_name (builder, FWUPD_RESULT_KEY_GUID); json_builder_begin_array (builder); for (guint i = 0; i < priv->guids->len; i++) { const gchar *guid = g_ptr_array_index (priv->guids, i); json_builder_add_string_value (builder, guid); } json_builder_end_array (builder); } fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_SERIAL, priv->serial); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_SUMMARY, priv->summary); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_PLUGIN, priv->plugin); if (priv->flags != FWUPD_DEVICE_FLAG_NONE) { json_builder_set_member_name (builder, FWUPD_RESULT_KEY_FLAGS); json_builder_begin_array (builder); for (guint i = 0; i < 64; i++) { const gchar *tmp; if ((priv->flags & ((guint64) 1 << i)) == 0) continue; tmp = fwupd_device_flag_to_string ((guint64) 1 << i); json_builder_add_string_value (builder, tmp); } json_builder_end_array (builder); } if (priv->checksums->len > 0) { json_builder_set_member_name (builder, "Checksums"); json_builder_begin_array (builder); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index (priv->checksums, i); json_builder_add_string_value (builder, checksum); } json_builder_end_array (builder); } fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_VENDOR, priv->vendor); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_VENDOR_ID, priv->vendor_id); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_VERSION, priv->version); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_VERSION_LOWEST, priv->version_lowest); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_VERSION_BOOTLOADER, priv->version_bootloader); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_VERSION_FORMAT, fwupd_version_format_to_string (priv->version_format)); fwupd_device_json_add_int (builder, FWUPD_RESULT_KEY_FLASHES_LEFT, priv->flashes_left); if (priv->icons->len > 0) { json_builder_set_member_name (builder, "Icons"); json_builder_begin_array (builder); for (guint i = 0; i < priv->icons->len; i++) { const gchar *icon = g_ptr_array_index (priv->icons, i); json_builder_add_string_value (builder, icon); } json_builder_end_array (builder); } fwupd_device_json_add_int (builder, FWUPD_RESULT_KEY_INSTALL_DURATION, priv->install_duration); fwupd_device_json_add_int (builder, FWUPD_RESULT_KEY_CREATED, priv->created); fwupd_device_json_add_int (builder, FWUPD_RESULT_KEY_MODIFIED, priv->modified); fwupd_device_json_add_int (builder, FWUPD_RESULT_KEY_UPDATE_STATE, priv->update_state); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_UPDATE_ERROR, priv->update_error); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_UPDATE_MESSAGE, priv->update_message); if (priv->releases->len > 0) { json_builder_set_member_name (builder, "Releases"); json_builder_begin_array (builder); for (guint i = 0; i < priv->releases->len; i++) { FwupdRelease *release = g_ptr_array_index (priv->releases, i); json_builder_begin_object (builder); fwupd_release_to_json (release, builder); json_builder_end_object (builder); } json_builder_end_array (builder); } } /** * fwupd_device_to_string: * @device: A #FwupdDevice * * Builds a text representation of the object. * * Returns: text, or %NULL for invalid * * Since: 0.9.3 **/ gchar * fwupd_device_to_string (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); GString *str; g_autoptr(GHashTable) ids = NULL; g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); str = g_string_new (""); if (priv->name != NULL) g_string_append_printf (str, "%s\n", priv->name); else str = g_string_append (str, "Unknown Device\n"); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_DEVICE_ID, priv->id); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_PARENT_DEVICE_ID, priv->parent_id); ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); for (guint i = 0; i < priv->instance_ids->len; i++) { const gchar *instance_id = g_ptr_array_index (priv->instance_ids, i); g_hash_table_insert (ids, fwupd_guid_hash_string (instance_id), g_strdup (instance_id)); } for (guint i = 0; i < priv->guids->len; i++) { const gchar *guid = g_ptr_array_index (priv->guids, i); const gchar *instance_id = g_hash_table_lookup (ids, guid); if (instance_id == NULL) { fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_GUID, guid); } else { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf ("%s <- %s", guid, instance_id); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_GUID, tmp); } } fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_SERIAL, priv->serial); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_SUMMARY, priv->summary); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_PLUGIN, priv->plugin); fwupd_pad_kv_dfl (str, FWUPD_RESULT_KEY_FLAGS, priv->flags); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index (priv->checksums, i); g_autofree gchar *checksum_display = fwupd_checksum_format_for_display (checksum); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_CHECKSUM, checksum_display); } fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_VENDOR, priv->vendor); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_VENDOR_ID, priv->vendor_id); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_VERSION, priv->version); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_VERSION_LOWEST, priv->version_lowest); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_VERSION_BOOTLOADER, priv->version_bootloader); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_VERSION_FORMAT, fwupd_version_format_to_string (priv->version_format)); if (priv->flashes_left < 2) fwupd_pad_kv_int (str, FWUPD_RESULT_KEY_FLASHES_LEFT, priv->flashes_left); if (priv->icons->len > 0) { g_autoptr(GString) tmp = g_string_new (NULL); for (guint i = 0; i < priv->icons->len; i++) { const gchar *icon = g_ptr_array_index (priv->icons, i); g_string_append_printf (tmp, "%s,", icon); } if (tmp->len > 1) g_string_truncate (tmp, tmp->len - 1); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_ICON, tmp->str); } fwupd_pad_kv_int (str, FWUPD_RESULT_KEY_INSTALL_DURATION, priv->install_duration); fwupd_pad_kv_unx (str, FWUPD_RESULT_KEY_CREATED, priv->created); fwupd_pad_kv_unx (str, FWUPD_RESULT_KEY_MODIFIED, priv->modified); fwupd_pad_kv_ups (str, FWUPD_RESULT_KEY_UPDATE_STATE, priv->update_state); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_UPDATE_ERROR, priv->update_error); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_UPDATE_MESSAGE, priv->update_message); for (guint i = 0; i < priv->releases->len; i++) { FwupdRelease *release = g_ptr_array_index (priv->releases, i); g_autofree gchar *tmp = fwupd_release_to_string (release); g_string_append_printf (str, " \n [%s]\n%s", FWUPD_RESULT_KEY_RELEASE, tmp); } return g_string_free (str, FALSE); } static void fwupd_device_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FwupdDevice *self = FWUPD_DEVICE (object); FwupdDevicePrivate *priv = GET_PRIVATE (self); switch (prop_id) { case PROP_VERSION_FORMAT: g_value_set_uint (value, priv->version_format); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fwupd_device_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FwupdDevice *self = FWUPD_DEVICE (object); switch (prop_id) { case PROP_VERSION_FORMAT: fwupd_device_set_version_format (self, g_value_get_uint (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fwupd_device_class_init (FwupdDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; object_class->finalize = fwupd_device_finalize; object_class->get_property = fwupd_device_get_property; object_class->set_property = fwupd_device_set_property; pspec = g_param_spec_uint ("version-format", NULL, NULL, FWUPD_VERSION_FORMAT_UNKNOWN, FWUPD_VERSION_FORMAT_LAST, FWUPD_VERSION_FORMAT_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_VERSION_FORMAT, pspec); } static void fwupd_device_init (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); priv->guids = g_ptr_array_new_with_free_func (g_free); priv->instance_ids = g_ptr_array_new_with_free_func (g_free); priv->icons = g_ptr_array_new_with_free_func (g_free); priv->checksums = g_ptr_array_new_with_free_func (g_free); priv->releases = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); } static void fwupd_device_finalize (GObject *object) { FwupdDevice *device = FWUPD_DEVICE (object); FwupdDevicePrivate *priv = GET_PRIVATE (device); if (priv->parent != NULL) g_object_unref (priv->parent); g_free (priv->description); g_free (priv->id); g_free (priv->parent_id); g_free (priv->name); g_free (priv->serial); g_free (priv->summary); g_free (priv->vendor); g_free (priv->vendor_id); g_free (priv->plugin); g_free (priv->update_error); g_free (priv->update_message); g_free (priv->version); g_free (priv->version_lowest); g_free (priv->version_bootloader); g_ptr_array_unref (priv->guids); g_ptr_array_unref (priv->instance_ids); g_ptr_array_unref (priv->icons); g_ptr_array_unref (priv->checksums); g_ptr_array_unref (priv->releases); G_OBJECT_CLASS (fwupd_device_parent_class)->finalize (object); } static void fwupd_device_set_from_variant_iter (FwupdDevice *device, GVariantIter *iter) { GVariant *value; const gchar *key; while (g_variant_iter_next (iter, "{&sv}", &key, &value)) { fwupd_device_from_key_value (device, key, value); g_variant_unref (value); } } /** * fwupd_device_from_variant: * @value: a #GVariant * * Creates a new device using packed data. * * Returns: (transfer full): a new #FwupdDevice, or %NULL if @value was invalid * * Since: 1.0.0 **/ FwupdDevice * fwupd_device_from_variant (GVariant *value) { FwupdDevice *dev = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; /* format from GetDetails */ type_string = g_variant_get_type_string (value); if (g_strcmp0 (type_string, "(a{sv})") == 0) { dev = fwupd_device_new (); g_variant_get (value, "(a{sv})", &iter); fwupd_device_set_from_variant_iter (dev, iter); } else if (g_strcmp0 (type_string, "a{sv}") == 0) { dev = fwupd_device_new (); g_variant_get (value, "a{sv}", &iter); fwupd_device_set_from_variant_iter (dev, iter); } else { g_warning ("type %s not known", type_string); } return dev; } /** * fwupd_device_array_from_variant: * @value: a #GVariant * * Creates an array of new devices using packed data. * * Returns: (transfer container) (element-type FwupdDevice): devices, or %NULL if @value was invalid * * Since: 1.2.10 **/ GPtrArray * fwupd_device_array_from_variant (GVariant *value) { GPtrArray *array = NULL; gsize sz; g_autoptr(GVariant) untuple = NULL; g_autoptr(GHashTable) devices_by_id = NULL; array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); devices_by_id = g_hash_table_new (g_str_hash, g_str_equal); untuple = g_variant_get_child_value (value, 0); sz = g_variant_n_children (untuple); for (guint i = 0; i < sz; i++) { FwupdDevice *dev; g_autoptr(GVariant) data = NULL; data = g_variant_get_child_value (untuple, i); dev = fwupd_device_from_variant (data); if (dev == NULL) continue; g_ptr_array_add (array, dev); if (fwupd_device_get_id (dev) != NULL) { g_hash_table_insert (devices_by_id, (gpointer) fwupd_device_get_id (dev), (gpointer) dev); } } /* set the parent on each child */ for (guint i = 0; i < array->len; i++) { FwupdDevice *dev = g_ptr_array_index (array, i); const gchar *parent_id = fwupd_device_get_parent_id (dev); if (parent_id != NULL) { FwupdDevice *dev_tmp; dev_tmp = g_hash_table_lookup (devices_by_id, parent_id); fwupd_device_set_parent (dev, dev_tmp); } } return array; } /** * fwupd_device_compare: * @device1: a #FwupdDevice * @device2: a #FwupdDevice * * Comparison function for comparing two FwupdDevice objects. * * Returns: negative, 0 or positive * * Since: 1.1.1 **/ gint fwupd_device_compare (FwupdDevice *device1, FwupdDevice *device2) { FwupdDevicePrivate *priv1 = GET_PRIVATE (device1); FwupdDevicePrivate *priv2 = GET_PRIVATE (device2); g_return_val_if_fail (FWUPD_IS_DEVICE (device1), 0); g_return_val_if_fail (FWUPD_IS_DEVICE (device2), 0); return g_strcmp0 (priv1->id, priv2->id); } /** * fwupd_device_new: * * Creates a new device. * * Returns: a new #FwupdDevice * * Since: 0.9.3 **/ FwupdDevice * fwupd_device_new (void) { FwupdDevice *device; device = g_object_new (FWUPD_TYPE_DEVICE, NULL); return FWUPD_DEVICE (device); } fwupd-1.2.14/libfwupd/fwupd-device.h000066400000000000000000000130621402665037500173050ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-enums.h" #include "fwupd-release.h" G_BEGIN_DECLS #define FWUPD_TYPE_DEVICE (fwupd_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FwupdDevice, fwupd_device, FWUPD, DEVICE, GObject) struct _FwupdDeviceClass { GObjectClass parent_class; /*< private >*/ void (*_fwupd_reserved1) (void); void (*_fwupd_reserved2) (void); void (*_fwupd_reserved3) (void); void (*_fwupd_reserved4) (void); void (*_fwupd_reserved5) (void); void (*_fwupd_reserved6) (void); void (*_fwupd_reserved7) (void); }; FwupdDevice *fwupd_device_new (void); gchar *fwupd_device_to_string (FwupdDevice *device); const gchar *fwupd_device_get_id (FwupdDevice *device); void fwupd_device_set_id (FwupdDevice *device, const gchar *id); const gchar *fwupd_device_get_parent_id (FwupdDevice *device); void fwupd_device_set_parent_id (FwupdDevice *device, const gchar *parent_id); FwupdDevice *fwupd_device_get_parent (FwupdDevice *device); void fwupd_device_set_parent (FwupdDevice *device, FwupdDevice *parent); const gchar *fwupd_device_get_name (FwupdDevice *device); void fwupd_device_set_name (FwupdDevice *device, const gchar *name); const gchar *fwupd_device_get_serial (FwupdDevice *device); void fwupd_device_set_serial (FwupdDevice *device, const gchar *serial); const gchar *fwupd_device_get_summary (FwupdDevice *device); void fwupd_device_set_summary (FwupdDevice *device, const gchar *summary); const gchar *fwupd_device_get_description (FwupdDevice *device); void fwupd_device_set_description (FwupdDevice *device, const gchar *description); const gchar *fwupd_device_get_version (FwupdDevice *device); void fwupd_device_set_version (FwupdDevice *device, const gchar *version); const gchar *fwupd_device_get_version_lowest (FwupdDevice *device); void fwupd_device_set_version_lowest (FwupdDevice *device, const gchar *version_lowest); const gchar *fwupd_device_get_version_bootloader (FwupdDevice *device); void fwupd_device_set_version_bootloader (FwupdDevice *device, const gchar *version_bootloader); FwupdVersionFormat fwupd_device_get_version_format (FwupdDevice *device); void fwupd_device_set_version_format (FwupdDevice *device, FwupdVersionFormat version_format); guint32 fwupd_device_get_flashes_left (FwupdDevice *device); void fwupd_device_set_flashes_left (FwupdDevice *device, guint32 flashes_left); guint32 fwupd_device_get_install_duration (FwupdDevice *device); void fwupd_device_set_install_duration (FwupdDevice *device, guint32 duration); guint64 fwupd_device_get_flags (FwupdDevice *device); void fwupd_device_set_flags (FwupdDevice *device, guint64 flags); void fwupd_device_add_flag (FwupdDevice *device, FwupdDeviceFlags flag); void fwupd_device_remove_flag (FwupdDevice *device, FwupdDeviceFlags flag); gboolean fwupd_device_has_flag (FwupdDevice *device, FwupdDeviceFlags flag); guint64 fwupd_device_get_created (FwupdDevice *device); void fwupd_device_set_created (FwupdDevice *device, guint64 created); guint64 fwupd_device_get_modified (FwupdDevice *device); void fwupd_device_set_modified (FwupdDevice *device, guint64 modified); GPtrArray *fwupd_device_get_checksums (FwupdDevice *device); void fwupd_device_add_checksum (FwupdDevice *device, const gchar *checksum); const gchar *fwupd_device_get_plugin (FwupdDevice *device); void fwupd_device_set_plugin (FwupdDevice *device, const gchar *plugin); const gchar *fwupd_device_get_vendor (FwupdDevice *device); void fwupd_device_set_vendor (FwupdDevice *device, const gchar *vendor); const gchar *fwupd_device_get_vendor_id (FwupdDevice *device); void fwupd_device_set_vendor_id (FwupdDevice *device, const gchar *vendor_id); void fwupd_device_add_guid (FwupdDevice *device, const gchar *guid); gboolean fwupd_device_has_guid (FwupdDevice *device, const gchar *guid); GPtrArray *fwupd_device_get_guids (FwupdDevice *device); const gchar *fwupd_device_get_guid_default (FwupdDevice *device); void fwupd_device_add_instance_id (FwupdDevice *device, const gchar *instance_id); gboolean fwupd_device_has_instance_id (FwupdDevice *device, const gchar *instance_id); GPtrArray *fwupd_device_get_instance_ids (FwupdDevice *device); void fwupd_device_add_icon (FwupdDevice *device, const gchar *icon); GPtrArray *fwupd_device_get_icons (FwupdDevice *device); FwupdUpdateState fwupd_device_get_update_state (FwupdDevice *device); void fwupd_device_set_update_state (FwupdDevice *device, FwupdUpdateState update_state); const gchar *fwupd_device_get_update_error (FwupdDevice *device); void fwupd_device_set_update_error (FwupdDevice *device, const gchar *update_error); const gchar *fwupd_device_get_update_message (FwupdDevice *device); void fwupd_device_set_update_message (FwupdDevice *device, const gchar *update_message); void fwupd_device_add_release (FwupdDevice *device, FwupdRelease *release); GPtrArray *fwupd_device_get_releases (FwupdDevice *device); FwupdRelease *fwupd_device_get_release_default (FwupdDevice *device); gint fwupd_device_compare (FwupdDevice *device1, FwupdDevice *device2); FwupdDevice *fwupd_device_from_variant (GVariant *value); GPtrArray *fwupd_device_array_from_variant (GVariant *value); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-enums-private.h000066400000000000000000000045501402665037500206470ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once G_BEGIN_DECLS #define FWUPD_RESULT_KEY_APPSTREAM_ID "AppstreamId" /* s */ #define FWUPD_RESULT_KEY_CHECKSUM "Checksum" /* as */ #define FWUPD_RESULT_KEY_CREATED "Created" /* t */ #define FWUPD_RESULT_KEY_DESCRIPTION "Description" /* s */ #define FWUPD_RESULT_KEY_DEVICE_ID "DeviceId" /* s */ #define FWUPD_RESULT_KEY_PARENT_DEVICE_ID "ParentDeviceId"/* s */ #define FWUPD_RESULT_KEY_FILENAME "Filename" /* s */ #define FWUPD_RESULT_KEY_PROTOCOL "Protocol" /* s */ #define FWUPD_RESULT_KEY_CATEGORIES "Categories" /* as */ #define FWUPD_RESULT_KEY_FLAGS "Flags" /* t */ #define FWUPD_RESULT_KEY_FLASHES_LEFT "FlashesLeft" /* u */ #define FWUPD_RESULT_KEY_INSTALL_DURATION "InstallDuration" /* u */ #define FWUPD_RESULT_KEY_GUID "Guid" /* as */ #define FWUPD_RESULT_KEY_INSTANCE_IDS "InstanceIds" /* as */ #define FWUPD_RESULT_KEY_HOMEPAGE "Homepage" /* s */ #define FWUPD_RESULT_KEY_DETAILS_URL "DetailsUrl" /* s */ #define FWUPD_RESULT_KEY_SOURCE_URL "SourceUrl" /* s */ #define FWUPD_RESULT_KEY_ICON "Icon" /* as */ #define FWUPD_RESULT_KEY_LICENSE "License" /* s */ #define FWUPD_RESULT_KEY_MODIFIED "Modified" /* t */ #define FWUPD_RESULT_KEY_METADATA "Metadata" /* a{ss} */ #define FWUPD_RESULT_KEY_NAME "Name" /* s */ #define FWUPD_RESULT_KEY_PLUGIN "Plugin" /* s */ #define FWUPD_RESULT_KEY_RELEASE "Release" /* a{sv} */ #define FWUPD_RESULT_KEY_REMOTE_ID "RemoteId" /* s */ #define FWUPD_RESULT_KEY_SERIAL "Serial" /* s */ #define FWUPD_RESULT_KEY_SIZE "Size" /* t */ #define FWUPD_RESULT_KEY_SUMMARY "Summary" /* s */ #define FWUPD_RESULT_KEY_TRUST_FLAGS "TrustFlags" /* t */ #define FWUPD_RESULT_KEY_UPDATE_MESSAGE "UpdateMessage" /* s */ #define FWUPD_RESULT_KEY_UPDATE_ERROR "UpdateError" /* s */ #define FWUPD_RESULT_KEY_UPDATE_STATE "UpdateState" /* u */ #define FWUPD_RESULT_KEY_URI "Uri" /* s */ #define FWUPD_RESULT_KEY_VENDOR_ID "VendorId" /* s */ #define FWUPD_RESULT_KEY_VENDOR "Vendor" /* s */ #define FWUPD_RESULT_KEY_VENDOR "Vendor" /* s */ #define FWUPD_RESULT_KEY_VERSION_BOOTLOADER "VersionBootloader" /* s */ #define FWUPD_RESULT_KEY_VERSION_FORMAT "VersionFormat" /* u */ #define FWUPD_RESULT_KEY_VERSION_LOWEST "VersionLowest" /* s */ #define FWUPD_RESULT_KEY_VERSION "Version" /* s */ G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-enums.c000066400000000000000000000346161402665037500172000ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-enums.h" /** * SECTION:fwupd-enums * @short_description: enumerated values shared by the daemon and library * * This file also provides helper functions to map enums to strings and back * again. * * See also: #fwupd-error */ /** * fwupd_status_to_string: * @status: A #FwupdStatus, e.g. %FWUPD_STATUS_DECOMPRESSING * * Converts a #FwupdStatus to a string. * * Return value: identifier string * * Since: 0.1.1 **/ const gchar * fwupd_status_to_string (FwupdStatus status) { if (status == FWUPD_STATUS_UNKNOWN) return "unknown"; if (status == FWUPD_STATUS_IDLE) return "idle"; if (status == FWUPD_STATUS_DECOMPRESSING) return "decompressing"; if (status == FWUPD_STATUS_LOADING) return "loading"; if (status == FWUPD_STATUS_DEVICE_RESTART) return "device-restart"; if (status == FWUPD_STATUS_DEVICE_WRITE) return "device-write"; if (status == FWUPD_STATUS_DEVICE_READ) return "device-read"; if (status == FWUPD_STATUS_DEVICE_ERASE) return "device-erase"; if (status == FWUPD_STATUS_DEVICE_VERIFY) return "device-verify"; if (status == FWUPD_STATUS_DEVICE_BUSY) return "device-busy"; if (status == FWUPD_STATUS_SCHEDULING) return "scheduling"; if (status == FWUPD_STATUS_DOWNLOADING) return "downloading"; if (status == FWUPD_STATUS_WAITING_FOR_AUTH) return "waiting-for-auth"; if (status == FWUPD_STATUS_SHUTDOWN) return "shutdown"; return NULL; } /** * fwupd_status_from_string: * @status: A string, e.g. `decompressing` * * Converts a string to a #FwupdStatus. * * Return value: enumerated value * * Since: 0.1.1 **/ FwupdStatus fwupd_status_from_string (const gchar *status) { if (g_strcmp0 (status, "unknown") == 0) return FWUPD_STATUS_UNKNOWN; if (g_strcmp0 (status, "idle") == 0) return FWUPD_STATUS_IDLE; if (g_strcmp0 (status, "decompressing") == 0) return FWUPD_STATUS_DECOMPRESSING; if (g_strcmp0 (status, "loading") == 0) return FWUPD_STATUS_LOADING; if (g_strcmp0 (status, "device-restart") == 0) return FWUPD_STATUS_DEVICE_RESTART; if (g_strcmp0 (status, "device-write") == 0) return FWUPD_STATUS_DEVICE_WRITE; if (g_strcmp0 (status, "device-verify") == 0) return FWUPD_STATUS_DEVICE_VERIFY; if (g_strcmp0 (status, "scheduling") == 0) return FWUPD_STATUS_SCHEDULING; if (g_strcmp0 (status, "downloading") == 0) return FWUPD_STATUS_DOWNLOADING; if (g_strcmp0 (status, "device-read") == 0) return FWUPD_STATUS_DEVICE_READ; if (g_strcmp0 (status, "device-erase") == 0) return FWUPD_STATUS_DEVICE_ERASE; if (g_strcmp0 (status, "device-busy") == 0) return FWUPD_STATUS_DEVICE_BUSY; if (g_strcmp0 (status, "waiting-for-auth") == 0) return FWUPD_STATUS_WAITING_FOR_AUTH; if (g_strcmp0 (status, "shutdown") == 0) return FWUPD_STATUS_SHUTDOWN; return FWUPD_STATUS_LAST; } /** * fwupd_device_flag_to_string: * @device_flag: A #FwupdDeviceFlags, e.g. %FWUPD_DEVICE_FLAG_REQUIRE_AC * * Converts a #FwupdDeviceFlags to a string. * * Return value: identifier string * * Since: 0.7.0 **/ const gchar * fwupd_device_flag_to_string (FwupdDeviceFlags device_flag) { if (device_flag == FWUPD_DEVICE_FLAG_NONE) return "none"; if (device_flag == FWUPD_DEVICE_FLAG_INTERNAL) return "internal"; if (device_flag == FWUPD_DEVICE_FLAG_UPDATABLE) return "updatable"; if (device_flag == FWUPD_DEVICE_FLAG_ONLY_OFFLINE) return "only-offline"; if (device_flag == FWUPD_DEVICE_FLAG_REQUIRE_AC) return "require-ac"; if (device_flag == FWUPD_DEVICE_FLAG_LOCKED) return "locked"; if (device_flag == FWUPD_DEVICE_FLAG_SUPPORTED) return "supported"; if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER) return "needs-bootloader"; if (device_flag == FWUPD_DEVICE_FLAG_REGISTERED) return "registered"; if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_REBOOT) return "needs-reboot"; if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) return "needs-shutdown"; if (device_flag == FWUPD_DEVICE_FLAG_REPORTED) return "reported"; if (device_flag == FWUPD_DEVICE_FLAG_NOTIFIED) return "notified"; if (device_flag == FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION) return "use-runtime-version"; if (device_flag == FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST) return "install-parent-first"; if (device_flag == FWUPD_DEVICE_FLAG_IS_BOOTLOADER) return "is-bootloader"; if (device_flag == FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) return "wait-for-replug"; if (device_flag == FWUPD_DEVICE_FLAG_IGNORE_VALIDATION) return "ignore-validation"; if (device_flag == FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED) return "another-write-required"; if (device_flag == FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS) return "no-auto-instance-ids"; if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION) return "needs-activation"; if (device_flag == FWUPD_DEVICE_FLAG_ENSURE_SEMVER) return "ensure-semver"; if (device_flag == FWUPD_DEVICE_FLAG_UNKNOWN) return "unknown"; return NULL; } /** * fwupd_device_flag_from_string: * @device_flag: A string, e.g. `require-ac` * * Converts a string to a #FwupdDeviceFlags. * * Return value: enumerated value * * Since: 0.7.0 **/ FwupdDeviceFlags fwupd_device_flag_from_string (const gchar *device_flag) { if (g_strcmp0 (device_flag, "none") == 0) return FWUPD_DEVICE_FLAG_NONE; if (g_strcmp0 (device_flag, "internal") == 0) return FWUPD_DEVICE_FLAG_INTERNAL; if (g_strcmp0 (device_flag, "updatable") == 0 || g_strcmp0 (device_flag, "allow-online") == 0) return FWUPD_DEVICE_FLAG_UPDATABLE; if (g_strcmp0 (device_flag, "only-offline") == 0 || g_strcmp0 (device_flag, "allow-offline") == 0) return FWUPD_DEVICE_FLAG_ONLY_OFFLINE; if (g_strcmp0 (device_flag, "require-ac") == 0) return FWUPD_DEVICE_FLAG_REQUIRE_AC; if (g_strcmp0 (device_flag, "locked") == 0) return FWUPD_DEVICE_FLAG_LOCKED; if (g_strcmp0 (device_flag, "supported") == 0) return FWUPD_DEVICE_FLAG_SUPPORTED; if (g_strcmp0 (device_flag, "needs-bootloader") == 0) return FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER; if (g_strcmp0 (device_flag, "registered") == 0) return FWUPD_DEVICE_FLAG_REGISTERED; if (g_strcmp0 (device_flag, "needs-reboot") == 0) return FWUPD_DEVICE_FLAG_NEEDS_REBOOT; if (g_strcmp0 (device_flag, "needs-shutdown") == 0) return FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; if (g_strcmp0 (device_flag, "reported") == 0) return FWUPD_DEVICE_FLAG_REPORTED; if (g_strcmp0 (device_flag, "notified") == 0) return FWUPD_DEVICE_FLAG_NOTIFIED; if (g_strcmp0 (device_flag, "use-runtime-version") == 0) return FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION; if (g_strcmp0 (device_flag, "install-parent-first") == 0) return FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST; if (g_strcmp0 (device_flag, "is-bootloader") == 0) return FWUPD_DEVICE_FLAG_IS_BOOTLOADER; if (g_strcmp0 (device_flag, "wait-for-replug") == 0) return FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG; if (g_strcmp0 (device_flag, "ignore-validation") == 0) return FWUPD_DEVICE_FLAG_IGNORE_VALIDATION; if (g_strcmp0 (device_flag, "another-write-required") == 0) return FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED; if (g_strcmp0 (device_flag, "no-auto-instance-ids") == 0) return FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS; if (g_strcmp0 (device_flag, "needs-activation") == 0) return FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION; if (g_strcmp0 (device_flag, "ensure-semver") == 0) return FWUPD_DEVICE_FLAG_ENSURE_SEMVER; return FWUPD_DEVICE_FLAG_UNKNOWN; } /** * fwupd_update_state_to_string: * @update_state: A #FwupdUpdateState, e.g. %FWUPD_UPDATE_STATE_PENDING * * Converts a #FwupdUpdateState to a string. * * Return value: identifier string * * Since: 0.7.0 **/ const gchar * fwupd_update_state_to_string (FwupdUpdateState update_state) { if (update_state == FWUPD_UPDATE_STATE_UNKNOWN) return "unknown"; if (update_state == FWUPD_UPDATE_STATE_PENDING) return "pending"; if (update_state == FWUPD_UPDATE_STATE_SUCCESS) return "success"; if (update_state == FWUPD_UPDATE_STATE_FAILED) return "failed"; if (update_state == FWUPD_UPDATE_STATE_FAILED_TRANSIENT) return "failed-transient"; if (update_state == FWUPD_UPDATE_STATE_NEEDS_REBOOT) return "needs-reboot"; return NULL; } /** * fwupd_update_state_from_string: * @update_state: A string, e.g. `pending` * * Converts a string to a #FwupdUpdateState. * * Return value: enumerated value * * Since: 0.7.0 **/ FwupdUpdateState fwupd_update_state_from_string (const gchar *update_state) { if (g_strcmp0 (update_state, "unknown") == 0) return FWUPD_UPDATE_STATE_UNKNOWN; if (g_strcmp0 (update_state, "pending") == 0) return FWUPD_UPDATE_STATE_PENDING; if (g_strcmp0 (update_state, "success") == 0) return FWUPD_UPDATE_STATE_SUCCESS; if (g_strcmp0 (update_state, "failed") == 0) return FWUPD_UPDATE_STATE_FAILED; if (g_strcmp0 (update_state, "failed-transient") == 0) return FWUPD_UPDATE_STATE_FAILED_TRANSIENT; if (g_strcmp0 (update_state, "needs-reboot") == 0) return FWUPD_UPDATE_STATE_NEEDS_REBOOT; return FWUPD_UPDATE_STATE_UNKNOWN; } /** * fwupd_trust_flag_to_string: * @trust_flag: A #FwupdTrustFlags, e.g. %FWUPD_TRUST_FLAG_PAYLOAD * * Converts a #FwupdTrustFlags to a string. * * Return value: identifier string * * Since: 0.7.0 **/ const gchar * fwupd_trust_flag_to_string (FwupdTrustFlags trust_flag) { if (trust_flag == FWUPD_TRUST_FLAG_NONE) return "none"; if (trust_flag == FWUPD_TRUST_FLAG_PAYLOAD) return "payload"; if (trust_flag == FWUPD_TRUST_FLAG_METADATA) return "metadata"; return NULL; } /** * fwupd_trust_flag_from_string: * @trust_flag: A string, e.g. `payload` * * Converts a string to a #FwupdTrustFlags. * * Return value: enumerated value * * Since: 0.7.0 **/ FwupdTrustFlags fwupd_trust_flag_from_string (const gchar *trust_flag) { if (g_strcmp0 (trust_flag, "none") == 0) return FWUPD_TRUST_FLAG_NONE; if (g_strcmp0 (trust_flag, "payload") == 0) return FWUPD_TRUST_FLAG_PAYLOAD; if (g_strcmp0 (trust_flag, "metadata") == 0) return FWUPD_TRUST_FLAG_METADATA; return FWUPD_TRUST_FLAG_LAST; } /** * fwupd_keyring_kind_from_string: * @keyring_kind: a string, e.g. `gpg` * * Converts an printable string to an enumerated type. * * Returns: a #FwupdKeyringKind, e.g. %FWUPD_KEYRING_KIND_GPG * * Since: 0.9.7 **/ FwupdKeyringKind fwupd_keyring_kind_from_string (const gchar *keyring_kind) { if (g_strcmp0 (keyring_kind, "none") == 0) return FWUPD_KEYRING_KIND_NONE; if (g_strcmp0 (keyring_kind, "gpg") == 0) return FWUPD_KEYRING_KIND_GPG; if (g_strcmp0 (keyring_kind, "pkcs7") == 0) return FWUPD_KEYRING_KIND_PKCS7; return FWUPD_KEYRING_KIND_UNKNOWN; } /** * fwupd_keyring_kind_to_string: * @keyring_kind: a #FwupdKeyringKind, e.g. %FWUPD_KEYRING_KIND_GPG * * Converts an enumerated type to a printable string. * * Returns: a string, e.g. `gpg` * * Since: 0.9.7 **/ const gchar * fwupd_keyring_kind_to_string (FwupdKeyringKind keyring_kind) { if (keyring_kind == FWUPD_KEYRING_KIND_NONE) return "none"; if (keyring_kind == FWUPD_KEYRING_KIND_GPG) return "gpg"; if (keyring_kind == FWUPD_KEYRING_KIND_PKCS7) return "pkcs7"; return NULL; } /** * fwupd_release_flag_to_string: * @release_flag: A #FwupdReleaseFlags, e.g. %FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD * * Converts a #FwupdReleaseFlags to a string. * * Return value: identifier string * * Since: 1.2.6 **/ const gchar * fwupd_release_flag_to_string (FwupdReleaseFlags release_flag) { if (release_flag == FWUPD_RELEASE_FLAG_NONE) return "none"; if (release_flag == FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD) return "trusted-payload"; if (release_flag == FWUPD_RELEASE_FLAG_TRUSTED_METADATA) return "trusted-metadata"; if (release_flag == FWUPD_RELEASE_FLAG_IS_UPGRADE) return "is-upgrade"; if (release_flag == FWUPD_RELEASE_FLAG_IS_DOWNGRADE) return "is-downgrade"; if (release_flag == FWUPD_RELEASE_FLAG_BLOCKED_VERSION) return "blocked-version"; if (release_flag == FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL) return "blocked-approval"; return NULL; } /** * fwupd_release_flag_from_string: * @release_flag: A string, e.g. `trusted-payload` * * Converts a string to a #FwupdReleaseFlags. * * Return value: enumerated value * * Since: 1.2.6 **/ FwupdReleaseFlags fwupd_release_flag_from_string (const gchar *release_flag) { if (g_strcmp0 (release_flag, "trusted-payload") == 0) return FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD; if (g_strcmp0 (release_flag, "trusted-metadata") == 0) return FWUPD_RELEASE_FLAG_TRUSTED_METADATA; if (g_strcmp0 (release_flag, "is-upgrade") == 0) return FWUPD_RELEASE_FLAG_IS_UPGRADE; if (g_strcmp0 (release_flag, "is-downgrade") == 0) return FWUPD_RELEASE_FLAG_IS_DOWNGRADE; if (g_strcmp0 (release_flag, "blocked-version") == 0) return FWUPD_RELEASE_FLAG_BLOCKED_VERSION; if (g_strcmp0 (release_flag, "blocked-approval") == 0) return FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL; return FWUPD_RELEASE_FLAG_NONE; } /** * fwupd_version_format_from_string: * @str: A string, e.g. `quad` * * Converts text to a display version type. * * Returns: A #FwupdVersionFormat, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Since: 1.2.9 **/ FwupdVersionFormat fwupd_version_format_from_string (const gchar *str) { if (g_strcmp0 (str, "plain") == 0) return FWUPD_VERSION_FORMAT_PLAIN; if (g_strcmp0 (str, "pair") == 0) return FWUPD_VERSION_FORMAT_PAIR; if (g_strcmp0 (str, "number") == 0) return FWUPD_VERSION_FORMAT_NUMBER; if (g_strcmp0 (str, "triplet") == 0) return FWUPD_VERSION_FORMAT_TRIPLET; if (g_strcmp0 (str, "quad") == 0) return FWUPD_VERSION_FORMAT_QUAD; if (g_strcmp0 (str, "bcd") == 0) return FWUPD_VERSION_FORMAT_BCD; if (g_strcmp0 (str, "intel-me") == 0) return FWUPD_VERSION_FORMAT_INTEL_ME; if (g_strcmp0 (str, "intel-me2") == 0) return FWUPD_VERSION_FORMAT_INTEL_ME2; return FWUPD_VERSION_FORMAT_UNKNOWN; } /** * fwupd_version_format_to_string: * @kind: A #FwupdVersionFormat, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Converts a display version type to text. * * Returns: A string, e.g. `quad`, or %NULL if not known * * Since: 1.2.9 **/ const gchar * fwupd_version_format_to_string (FwupdVersionFormat kind) { if (kind == FWUPD_VERSION_FORMAT_PLAIN) return "plain"; if (kind == FWUPD_VERSION_FORMAT_NUMBER) return "number"; if (kind == FWUPD_VERSION_FORMAT_PAIR) return "pair"; if (kind == FWUPD_VERSION_FORMAT_TRIPLET) return "triplet"; if (kind == FWUPD_VERSION_FORMAT_QUAD) return "quad"; if (kind == FWUPD_VERSION_FORMAT_BCD) return "bcd"; if (kind == FWUPD_VERSION_FORMAT_INTEL_ME) return "intel-me"; if (kind == FWUPD_VERSION_FORMAT_INTEL_ME2) return "intel-me2"; return NULL; } fwupd-1.2.14/libfwupd/fwupd-enums.h000066400000000000000000000275551402665037500172110ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS /** * FwupdStatus: * @FWUPD_STATUS_UNKNOWN: Unknown state * @FWUPD_STATUS_IDLE: Idle * @FWUPD_STATUS_LOADING: Loading a resource * @FWUPD_STATUS_DECOMPRESSING: Decompressing firmware * @FWUPD_STATUS_DEVICE_RESTART: Restarting the device * @FWUPD_STATUS_DEVICE_WRITE: Writing to a device * @FWUPD_STATUS_DEVICE_VERIFY: Verifying (reading) a device * @FWUPD_STATUS_SCHEDULING: Scheduling an offline update * @FWUPD_STATUS_DOWNLOADING: A file is downloading * @FWUPD_STATUS_DEVICE_READ: Reading from a device * @FWUPD_STATUS_DEVICE_ERASE: Erasing a device * @FWUPD_STATUS_WAITING_FOR_AUTH: Waiting for authentication * @FWUPD_STATUS_DEVICE_BUSY: The device is busy * @FWUPD_STATUS_SHUTDOWN: The daemon is shutting down * * The flags to show daemon status. **/ typedef enum { FWUPD_STATUS_UNKNOWN, /* Since: 0.1.1 */ FWUPD_STATUS_IDLE, /* Since: 0.1.1 */ FWUPD_STATUS_LOADING, /* Since: 0.1.1 */ FWUPD_STATUS_DECOMPRESSING, /* Since: 0.1.1 */ FWUPD_STATUS_DEVICE_RESTART, /* Since: 0.1.1 */ FWUPD_STATUS_DEVICE_WRITE, /* Since: 0.1.1 */ FWUPD_STATUS_DEVICE_VERIFY, /* Since: 0.1.1 */ FWUPD_STATUS_SCHEDULING, /* Since: 0.1.1 */ FWUPD_STATUS_DOWNLOADING, /* Since: 0.9.4 */ FWUPD_STATUS_DEVICE_READ, /* Since: 1.0.0 */ FWUPD_STATUS_DEVICE_ERASE, /* Since: 1.0.0 */ FWUPD_STATUS_WAITING_FOR_AUTH, /* Since: 1.0.0 */ FWUPD_STATUS_DEVICE_BUSY, /* Since: 1.0.1 */ FWUPD_STATUS_SHUTDOWN, /* Since: 1.2.1 */ /*< private >*/ FWUPD_STATUS_LAST } FwupdStatus; /** * FwupdTrustFlags: * @FWUPD_TRUST_FLAG_NONE: No trust * @FWUPD_TRUST_FLAG_PAYLOAD: The firmware is trusted * @FWUPD_TRUST_FLAG_METADATA: The metadata is trusted * * The flags to show the level of trust. **/ typedef enum { FWUPD_TRUST_FLAG_NONE = 0, /* Since: 0.1.2 */ FWUPD_TRUST_FLAG_PAYLOAD = 1 << 0, /* Since: 0.1.2 */ FWUPD_TRUST_FLAG_METADATA = 1 << 1, /* Since: 0.1.2 */ /*< private >*/ FWUPD_TRUST_FLAG_LAST } FwupdTrustFlags; /** * FwupdDeviceFlags: * @FWUPD_DEVICE_FLAG_NONE: No flags set * @FWUPD_DEVICE_FLAG_INTERNAL: Device cannot be removed easily * @FWUPD_DEVICE_FLAG_UPDATABLE: Device is updatable in this or any other mode * @FWUPD_DEVICE_FLAG_ONLY_OFFLINE: Update can only be done from offline mode * @FWUPD_DEVICE_FLAG_REQUIRE_AC: Requires AC power * @FWUPD_DEVICE_FLAG_LOCKED: Is locked and can be unlocked * @FWUPD_DEVICE_FLAG_SUPPORTED: Is found in current metadata * @FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER: Requires a bootloader mode to be manually enabled by the user * @FWUPD_DEVICE_FLAG_REGISTERED: Has been registered with other plugins * @FWUPD_DEVICE_FLAG_NEEDS_REBOOT: Requires a reboot to apply firmware or to reload hardware * @FWUPD_DEVICE_FLAG_REPORTED: Has been reported to a metadata server * @FWUPD_DEVICE_FLAG_NOTIFIED: User has been notified * @FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION: Always use the runtime version rather than the bootloader * @FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST: Install composite firmware on the parent before the child * @FWUPD_DEVICE_FLAG_IS_BOOTLOADER: Is currently in bootloader mode * @FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG: The hardware is waiting to be replugged * @FWUPD_DEVICE_FLAG_IGNORE_VALIDATION: Ignore validation safety checks when flashing this device * @FWUPD_DEVICE_FLAG_TRUSTED: Extra metadata can be exposed about this device * @FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN: Requires system shutdown to apply firmware * @FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED: Requires the update to be retried with a new plugin * @FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS: Do not add instance IDs from the device baseclass * @FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION: Device update needs to be separately activated * @FWUPD_DEVICE_FLAG_ENSURE_SEMVER: Ensure the version is a valid semantic version, e.g. numbers separated with dots * * The device flags. **/ #define FWUPD_DEVICE_FLAG_NONE (0u) /* Since: 0.1.3 */ #define FWUPD_DEVICE_FLAG_INTERNAL (1u << 0) /* Since: 0.1.3 */ #define FWUPD_DEVICE_FLAG_UPDATABLE (1u << 1) /* Since: 0.9.7 */ #define FWUPD_DEVICE_FLAG_ONLY_OFFLINE (1u << 2) /* Since: 0.9.7 */ #define FWUPD_DEVICE_FLAG_REQUIRE_AC (1u << 3) /* Since: 0.6.3 */ #define FWUPD_DEVICE_FLAG_LOCKED (1u << 4) /* Since: 0.6.3 */ #define FWUPD_DEVICE_FLAG_SUPPORTED (1u << 5) /* Since: 0.7.1 */ #define FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER (1u << 6) /* Since: 0.7.3 */ #define FWUPD_DEVICE_FLAG_REGISTERED (1u << 7) /* Since: 0.9.7 */ #define FWUPD_DEVICE_FLAG_NEEDS_REBOOT (1u << 8) /* Since: 0.9.7 */ #define FWUPD_DEVICE_FLAG_REPORTED (1u << 9) /* Since: 1.0.4 */ #define FWUPD_DEVICE_FLAG_NOTIFIED (1u << 10) /* Since: 1.0.5 */ #define FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION (1u << 11) /* Since: 1.0.6 */ #define FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST (1u << 12) /* Since: 1.0.8 */ #define FWUPD_DEVICE_FLAG_IS_BOOTLOADER (1u << 13) /* Since: 1.0.8 */ #define FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG (1u << 14) /* Since: 1.1.2 */ #define FWUPD_DEVICE_FLAG_IGNORE_VALIDATION (1u << 15) /* Since: 1.1.2 */ #define FWUPD_DEVICE_FLAG_TRUSTED (1u << 16) /* Since: 1.1.2 */ #define FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN (1u << 17) /* Since: 1.2.4 */ #define FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED (1u << 18) /* Since: 1.2.5 */ #define FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS (1u << 19) /* Since: 1.2.5 */ #define FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION (1u << 20) /* Since: 1.2.6 */ #define FWUPD_DEVICE_FLAG_ENSURE_SEMVER (1u << 21) /* Since: 1.2.9 */ #define FWUPD_DEVICE_FLAG_UNKNOWN G_MAXUINT64 /* Since: 0.7.3 */ typedef guint64 FwupdDeviceFlags; /** * FwupdReleaseFlags: * @FWUPD_RELEASE_FLAG_NONE: No flags set * @FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD: The payload binary is trusted * @FWUPD_RELEASE_FLAG_TRUSTED_METADATA: The payload metadata is trusted * @FWUPD_RELEASE_FLAG_IS_UPGRADE: Is newer than the device version * @FWUPD_RELEASE_FLAG_IS_DOWNGRADE: Is older than the device version * @FWUPD_RELEASE_FLAG_BLOCKED_VERSION: Blocked as below device version-lowest * @FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL: Blocked as release not approved * * The release flags. **/ #define FWUPD_RELEASE_FLAG_NONE (0u) /* Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD (1u << 0) /* Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_TRUSTED_METADATA (1u << 1) /* Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_IS_UPGRADE (1u << 2) /* Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_IS_DOWNGRADE (1u << 3) /* Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_BLOCKED_VERSION (1u << 4) /* Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL (1u << 5) /* Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_UNKNOWN G_MAXUINT64 /* Since: 1.2.6 */ typedef guint64 FwupdReleaseFlags; /** * FwupdInstallFlags: * @FWUPD_INSTALL_FLAG_NONE: No flags set * @FWUPD_INSTALL_FLAG_OFFLINE: Schedule this for next boot * @FWUPD_INSTALL_FLAG_ALLOW_REINSTALL: Allow reinstalling the same version * @FWUPD_INSTALL_FLAG_ALLOW_OLDER: Allow downgrading firmware * @FWUPD_INSTALL_FLAG_FORCE: Force the update even if not a good idea * @FWUPD_INSTALL_FLAG_NO_HISTORY: Do not write to the history database * * Flags to set when performing the firwmare update or install. **/ typedef enum { FWUPD_INSTALL_FLAG_NONE = 0, /* Since: 0.7.0 */ FWUPD_INSTALL_FLAG_OFFLINE = 1 << 0, /* Since: 0.7.0 */ FWUPD_INSTALL_FLAG_ALLOW_REINSTALL = 1 << 1, /* Since: 0.7.0 */ FWUPD_INSTALL_FLAG_ALLOW_OLDER = 1 << 2, /* Since: 0.7.0 */ FWUPD_INSTALL_FLAG_FORCE = 1 << 3, /* Since: 0.7.1 */ FWUPD_INSTALL_FLAG_NO_HISTORY = 1 << 4, /* Since: 1.0.8 */ /*< private >*/ FWUPD_INSTALL_FLAG_LAST } FwupdInstallFlags; /** * FwupdSelfSignFlags: * @FWUPD_SELF_SIGN_FLAG_NONE: No flags set * @FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP: Add the timestamp to the detached signature * @FWUPD_SELF_SIGN_FLAG_ADD_CERT: Add the certificate to the detached signature * * Flags to set when performing the firwmare update or install. **/ typedef enum { FWUPD_SELF_SIGN_FLAG_NONE = 0, /* Since: 1.2.6 */ FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP = 1 << 0, /* Since: 1.2.6 */ FWUPD_SELF_SIGN_FLAG_ADD_CERT = 1 << 1, /* Since: 1.2.6 */ /*< private >*/ FWUPD_SELF_SIGN_FLAG_LAST } FwupdSelfSignFlags; /** * FwupdUpdateState: * @FWUPD_UPDATE_STATE_UNKNOWN: Unknown * @FWUPD_UPDATE_STATE_PENDING: Update is pending * @FWUPD_UPDATE_STATE_SUCCESS: Update was successful * @FWUPD_UPDATE_STATE_FAILED: Update failed * @FWUPD_UPDATE_STATE_NEEDS_REBOOT: Waiting for a reboot to apply * @FWUPD_UPDATE_STATE_FAILED_TRANSIENT: Update failed due to transient issue, e.g. AC power required * * The update state. **/ typedef enum { FWUPD_UPDATE_STATE_UNKNOWN, /* Since: 0.7.0 */ FWUPD_UPDATE_STATE_PENDING, /* Since: 0.7.0 */ FWUPD_UPDATE_STATE_SUCCESS, /* Since: 0.7.0 */ FWUPD_UPDATE_STATE_FAILED, /* Since: 0.7.0 */ FWUPD_UPDATE_STATE_NEEDS_REBOOT, /* Since: 1.0.4 */ FWUPD_UPDATE_STATE_FAILED_TRANSIENT, /* Since: 1.2.7 */ /*< private >*/ FWUPD_UPDATE_STATE_LAST } FwupdUpdateState; /** * FwupdKeyringKind: * @FWUPD_KEYRING_KIND_UNKNOWN: Unknown * @FWUPD_KEYRING_KIND_NONE: No verification * @FWUPD_KEYRING_KIND_GPG: Verification using GPG * @FWUPD_KEYRING_KIND_PKCS7: Verification using PKCS7 * * The update state. **/ typedef enum { FWUPD_KEYRING_KIND_UNKNOWN, /* Since: 0.9.7 */ FWUPD_KEYRING_KIND_NONE, /* Since: 0.9.7 */ FWUPD_KEYRING_KIND_GPG, /* Since: 0.9.7 */ FWUPD_KEYRING_KIND_PKCS7, /* Since: 0.9.7 */ /*< private >*/ FWUPD_KEYRING_KIND_LAST } FwupdKeyringKind; /** * FwupdVersionFormat: * @FWUPD_VERSION_FORMAT_UNKNOWN: Unknown version format * @FWUPD_VERSION_FORMAT_PLAIN: An unidentified format text string * @FWUPD_VERSION_FORMAT_NUMBER: A single integer version number * @FWUPD_VERSION_FORMAT_PAIR: Two AABB.CCDD version numbers * @FWUPD_VERSION_FORMAT_TRIPLET: Microsoft-style AA.BB.CCDD version numbers * @FWUPD_VERSION_FORMAT_QUAD: Dell-style AA.BB.CC.DD version numbers * @FWUPD_VERSION_FORMAT_BCD: Binary coded decimal notation * @FWUPD_VERSION_FORMAT_INTEL_ME: Intel ME-style bitshifted notation * @FWUPD_VERSION_FORMAT_INTEL_ME2: Intel ME-style A.B.CC.DDDD notation notation * * The flags used when parsing version numbers. * * If no verification is required then %FWUPD_VERSION_FORMAT_PLAIN should * be used to signify an unparsable text string. **/ typedef enum { FWUPD_VERSION_FORMAT_UNKNOWN, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_PLAIN, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_NUMBER, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_PAIR, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_TRIPLET, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_QUAD, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_BCD, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_INTEL_ME, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_INTEL_ME2, /* Since: 1.2.9 */ /*< private >*/ FWUPD_VERSION_FORMAT_LAST } FwupdVersionFormat; const gchar *fwupd_status_to_string (FwupdStatus status); FwupdStatus fwupd_status_from_string (const gchar *status); const gchar *fwupd_device_flag_to_string (FwupdDeviceFlags device_flag); FwupdDeviceFlags fwupd_device_flag_from_string (const gchar *device_flag); const gchar *fwupd_release_flag_to_string (FwupdReleaseFlags release_flag); FwupdReleaseFlags fwupd_release_flag_from_string (const gchar *release_flag); const gchar *fwupd_update_state_to_string (FwupdUpdateState update_state); FwupdUpdateState fwupd_update_state_from_string (const gchar *update_state); const gchar *fwupd_trust_flag_to_string (FwupdTrustFlags trust_flag); FwupdTrustFlags fwupd_trust_flag_from_string (const gchar *trust_flag); FwupdKeyringKind fwupd_keyring_kind_from_string (const gchar *keyring_kind); const gchar *fwupd_keyring_kind_to_string (FwupdKeyringKind keyring_kind); FwupdVersionFormat fwupd_version_format_from_string (const gchar *str); const gchar *fwupd_version_format_to_string (FwupdVersionFormat kind); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-error.c000066400000000000000000000104551402665037500171750ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fwupd-common.h" #include "fwupd-enums.h" #include "fwupd-error.h" /** * SECTION:fwupd-error * @short_description: an error domain shared by the daemon and library * * This file also provides helper functions to map errors to strings and back * again. * * See also: #fwupd-enums */ /** * fwupd_error_to_string: * @error: A #FwupdError, e.g. %FWUPD_ERROR_VERSION_NEWER * * Converts a #FwupdError to a string. * * Return value: identifier string * * Since: 0.7.0 **/ const gchar * fwupd_error_to_string (FwupdError error) { if (error == FWUPD_ERROR_INTERNAL) return FWUPD_DBUS_INTERFACE ".Internal"; if (error == FWUPD_ERROR_VERSION_NEWER) return FWUPD_DBUS_INTERFACE ".VersionNewer"; if (error == FWUPD_ERROR_VERSION_SAME) return FWUPD_DBUS_INTERFACE ".VersionSame"; if (error == FWUPD_ERROR_ALREADY_PENDING) return FWUPD_DBUS_INTERFACE ".AlreadyPending"; if (error == FWUPD_ERROR_AUTH_FAILED) return FWUPD_DBUS_INTERFACE ".AuthFailed"; if (error == FWUPD_ERROR_READ) return FWUPD_DBUS_INTERFACE ".Read"; if (error == FWUPD_ERROR_WRITE) return FWUPD_DBUS_INTERFACE ".Write"; if (error == FWUPD_ERROR_INVALID_FILE) return FWUPD_DBUS_INTERFACE ".InvalidFile"; if (error == FWUPD_ERROR_NOT_FOUND) return FWUPD_DBUS_INTERFACE ".NotFound"; if (error == FWUPD_ERROR_NOTHING_TO_DO) return FWUPD_DBUS_INTERFACE ".NothingToDo"; if (error == FWUPD_ERROR_NOT_SUPPORTED) return FWUPD_DBUS_INTERFACE ".NotSupported"; if (error == FWUPD_ERROR_SIGNATURE_INVALID) return FWUPD_DBUS_INTERFACE ".SignatureInvalid"; if (error == FWUPD_ERROR_AC_POWER_REQUIRED) return FWUPD_DBUS_INTERFACE ".AcPowerRequired"; if (error == FWUPD_ERROR_PERMISSION_DENIED) return FWUPD_DBUS_INTERFACE ".PermissionDenied"; if (error == FWUPD_ERROR_BROKEN_SYSTEM) return FWUPD_DBUS_INTERFACE ".BrokenSystem"; if (error == FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW) return FWUPD_DBUS_INTERFACE ".BatteryLevelTooLow"; return NULL; } /** * fwupd_error_from_string: * @error: A string, e.g. `org.freedesktop.fwupd.VersionNewer` * * Converts a string to a #FwupdError. * * Return value: enumerated value * * Since: 0.7.0 **/ FwupdError fwupd_error_from_string (const gchar *error) { if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".Internal") == 0) return FWUPD_ERROR_INTERNAL; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".VersionNewer") == 0) return FWUPD_ERROR_VERSION_NEWER; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".VersionSame") == 0) return FWUPD_ERROR_VERSION_SAME; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".AlreadyPending") == 0) return FWUPD_ERROR_ALREADY_PENDING; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".AuthFailed") == 0) return FWUPD_ERROR_AUTH_FAILED; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".Read") == 0) return FWUPD_ERROR_READ; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".Write") == 0) return FWUPD_ERROR_WRITE; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".InvalidFile") == 0) return FWUPD_ERROR_INVALID_FILE; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".NotFound") == 0) return FWUPD_ERROR_NOT_FOUND; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".NothingToDo") == 0) return FWUPD_ERROR_NOTHING_TO_DO; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".NotSupported") == 0) return FWUPD_ERROR_NOT_SUPPORTED; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".SignatureInvalid") == 0) return FWUPD_ERROR_SIGNATURE_INVALID; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".AcPowerRequired") == 0) return FWUPD_ERROR_AC_POWER_REQUIRED; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".PermissionDenied") == 0) return FWUPD_ERROR_PERMISSION_DENIED; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".BrokenSystem") == 0) return FWUPD_ERROR_BROKEN_SYSTEM; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".BatteryLevelTooLow") == 0) return FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW; return FWUPD_ERROR_LAST; } /** * fwupd_error_quark: * * Return value: An error quark. * * Since: 0.1.1 **/ GQuark fwupd_error_quark (void) { static GQuark quark = 0; if (!quark) { quark = g_quark_from_static_string ("FwupdError"); for (gint i = 0; i < FWUPD_ERROR_LAST; i++) { g_dbus_error_register_error (quark, i, fwupd_error_to_string (i)); } } return quark; } fwupd-1.2.14/libfwupd/fwupd-error.h000066400000000000000000000042301402665037500171740ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FWUPD_ERROR fwupd_error_quark() /** * FwupdError: * @FWUPD_ERROR_INTERNAL: Internal error * @FWUPD_ERROR_VERSION_NEWER: Installed newer firmware version * @FWUPD_ERROR_VERSION_SAME: Installed same firmware version * @FWUPD_ERROR_ALREADY_PENDING: Already set be be installed offline * @FWUPD_ERROR_AUTH_FAILED: Failed to get authentication * @FWUPD_ERROR_READ: Failed to read from device * @FWUPD_ERROR_WRITE: Failed to write to the device * @FWUPD_ERROR_INVALID_FILE: Invalid file format * @FWUPD_ERROR_NOT_FOUND: No matching device exists * @FWUPD_ERROR_NOTHING_TO_DO: Nothing to do * @FWUPD_ERROR_NOT_SUPPORTED: Action was not possible * @FWUPD_ERROR_SIGNATURE_INVALID: Signature was invalid * @FWUPD_ERROR_AC_POWER_REQUIRED: AC power was required * @FWUPD_ERROR_PERMISSION_DENIED: Permission was denied * @FWUPD_ERROR_BROKEN_SYSTEM: User has configured their system in a broken way * @FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW: The system battery level is too low * * The error code. **/ typedef enum { FWUPD_ERROR_INTERNAL, /* Since: 0.1.1 */ FWUPD_ERROR_VERSION_NEWER, /* Since: 0.1.1 */ FWUPD_ERROR_VERSION_SAME, /* Since: 0.1.1 */ FWUPD_ERROR_ALREADY_PENDING, /* Since: 0.1.1 */ FWUPD_ERROR_AUTH_FAILED, /* Since: 0.1.1 */ FWUPD_ERROR_READ, /* Since: 0.1.1 */ FWUPD_ERROR_WRITE, /* Since: 0.1.1 */ FWUPD_ERROR_INVALID_FILE, /* Since: 0.1.1 */ FWUPD_ERROR_NOT_FOUND, /* Since: 0.1.1 */ FWUPD_ERROR_NOTHING_TO_DO, /* Since: 0.1.1 */ FWUPD_ERROR_NOT_SUPPORTED, /* Since: 0.1.1 */ FWUPD_ERROR_SIGNATURE_INVALID, /* Since: 0.1.2 */ FWUPD_ERROR_AC_POWER_REQUIRED, /* Since: 0.8.0 */ FWUPD_ERROR_PERMISSION_DENIED, /* Since: 0.9.8 */ FWUPD_ERROR_BROKEN_SYSTEM, /* Since: 1.2.8 */ FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW, /* Since: 1.2.10 */ /*< private >*/ FWUPD_ERROR_LAST } FwupdError; GQuark fwupd_error_quark (void); const gchar *fwupd_error_to_string (FwupdError error); FwupdError fwupd_error_from_string (const gchar *error); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-release-private.h000066400000000000000000000006011402665037500211310ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fwupd-release.h" G_BEGIN_DECLS GVariant *fwupd_release_to_variant (FwupdRelease *release); void fwupd_release_to_json (FwupdRelease *release, JsonBuilder *builder); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-release.c000066400000000000000000001236331402665037500174670ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-release-private.h" /** * SECTION:fwupd-release * @short_description: a firmware release * * An object that represents a firmware release with a specific version. * Devices can have more than one release, and the releases are typically * ordered by their version. * * See also: #FwupdDevice */ static void fwupd_release_finalize (GObject *object); typedef struct { GPtrArray *checksums; GPtrArray *categories; GHashTable *metadata; gchar *description; gchar *filename; gchar *protocol; gchar *homepage; gchar *details_url; gchar *source_url; gchar *appstream_id; gchar *license; gchar *name; gchar *summary; gchar *uri; gchar *vendor; gchar *version; gchar *remote_id; guint64 size; guint32 install_duration; FwupdReleaseFlags flags; gchar *update_message; } FwupdReleasePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FwupdRelease, fwupd_release, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_release_get_instance_private (o)) /* the deprecated fwupd_release_get_trust_flags() function should only * return the last two bits of the #FwupdReleaseFlags */ #define FWUPD_RELEASE_TRUST_FLAGS_MASK 0x3 /** * fwupd_release_get_remote_id: * @release: A #FwupdRelease * * Gets the remote ID that can be used for downloading. * * Returns: the ID, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_remote_id (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->remote_id; } /** * fwupd_release_set_remote_id: * @release: A #FwupdRelease * @remote_id: the release ID, e.g. `USB:foo` * * Sets the remote ID that can be used for downloading. * * Since: 0.9.3 **/ void fwupd_release_set_remote_id (FwupdRelease *release, const gchar *remote_id) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->remote_id); priv->remote_id = g_strdup (remote_id); } /** * fwupd_release_get_version: * @release: A #FwupdRelease * * Gets the update version. * * Returns: the update version, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_version (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->version; } /** * fwupd_release_set_version: * @release: A #FwupdRelease * @version: the update version, e.g. `1.2.4` * * Sets the update version. * * Since: 0.9.3 **/ void fwupd_release_set_version (FwupdRelease *release, const gchar *version) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->version); priv->version = g_strdup (version); } /** * fwupd_release_get_filename: * @release: A #FwupdRelease * * Gets the update filename. * * Returns: the update filename, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_filename (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->filename; } /** * fwupd_release_set_filename: * @release: A #FwupdRelease * @filename: the update filename on disk * * Sets the update filename. * * Since: 0.9.3 **/ void fwupd_release_set_filename (FwupdRelease *release, const gchar *filename) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->filename); priv->filename = g_strdup (filename); } /** * fwupd_release_get_update_message: * @release: A #FwupdRelease * * Gets the update message. * * Returns: the update message, or %NULL if unset * * Since: 1.2.4 **/ const gchar * fwupd_release_get_update_message (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->update_message; } /** * fwupd_release_set_update_message: * @release: A #FwupdRelease * @update_message: the update message string * * Sets the update message. * * Since: 1.2.4 **/ void fwupd_release_set_update_message (FwupdRelease *release, const gchar *update_message) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->update_message); priv->update_message = g_strdup (update_message); } /** * fwupd_release_get_protocol: * @release: A #FwupdRelease * * Gets the update protocol. * * Returns: the update protocol, or %NULL if unset * * Since: 1.2.2 **/ const gchar * fwupd_release_get_protocol (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->protocol; } /** * fwupd_release_set_protocol: * @release: A #FwupdRelease * @protocol: the update protocol, e.g. `org.usb.dfu` * * Sets the update protocol. * * Since: 1.2.2 **/ void fwupd_release_set_protocol (FwupdRelease *release, const gchar *protocol) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->protocol); priv->protocol = g_strdup (protocol); } /** * fwupd_release_get_categories: * @release: A #FwupdRelease * * Gets the release categories. * * Returns: (element-type utf8) (transfer none): the categories, which may be empty * * Since: 1.2.7 **/ GPtrArray * fwupd_release_get_categories (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->categories; } /** * fwupd_release_add_category: * @release: A #FwupdRelease * @category: the update category, e.g. `X-EmbeddedController` * * Adds the update category. * * Since: 1.2.7 **/ void fwupd_release_add_category (FwupdRelease *release, const gchar *category) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_return_if_fail (category != NULL); for (guint i = 0; i < priv->categories->len; i++) { const gchar *category_tmp = g_ptr_array_index (priv->categories, i); if (g_strcmp0 (category_tmp, category) == 0) return; } g_ptr_array_add (priv->categories, g_strdup (category)); } /** * fwupd_release_has_category: * @release: A #FwupdRelease * @category: the update category, e.g. `X-EmbeddedController` * * Finds out if the release has the update category. * * Returns: %TRUE if the release matches * * Since: 1.2.7 **/ gboolean fwupd_release_has_category (FwupdRelease *release, const gchar *category) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), FALSE); g_return_val_if_fail (category != NULL, FALSE); for (guint i = 0; i < priv->categories->len; i++) { const gchar *category_tmp = g_ptr_array_index (priv->categories, i); if (g_strcmp0 (category_tmp, category) == 0) return TRUE; } return FALSE; } /** * fwupd_release_get_checksums: * @release: A #FwupdRelease * * Gets the release checksums. * * Returns: (element-type utf8) (transfer none): the checksums, which may be empty * * Since: 0.9.3 **/ GPtrArray * fwupd_release_get_checksums (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->checksums; } /** * fwupd_release_add_checksum: * @release: A #FwupdRelease * @checksum: the update checksum * * Sets the update checksum. * * Since: 0.9.3 **/ void fwupd_release_add_checksum (FwupdRelease *release, const gchar *checksum) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_return_if_fail (checksum != NULL); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum_tmp = g_ptr_array_index (priv->checksums, i); if (g_strcmp0 (checksum_tmp, checksum) == 0) return; } g_ptr_array_add (priv->checksums, g_strdup (checksum)); } /** * fwupd_release_has_checksum: * @release: A #FwupdRelease * @checksum: the update checksum * * Finds out if the release has the update checksum. * * Returns: %TRUE if the release matches * * Since: 1.2.6 **/ gboolean fwupd_release_has_checksum (FwupdRelease *release, const gchar *checksum) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), FALSE); g_return_val_if_fail (checksum != NULL, FALSE); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum_tmp = g_ptr_array_index (priv->checksums, i); if (g_strcmp0 (checksum_tmp, checksum) == 0) return TRUE; } return FALSE; } /** * fwupd_release_get_metadata: * @release: A #FwupdRelease * * Gets the release metadata. * * Returns: (transfer none): the metadata, which may be empty * * Since: 1.0.4 **/ GHashTable * fwupd_release_get_metadata (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->metadata; } /** * fwupd_release_add_metadata_item: * @release: A #FwupdRelease * @key: the key * @value: the value * * Sets a release metadata item. * * Since: 1.0.4 **/ void fwupd_release_add_metadata_item (FwupdRelease *release, const gchar *key, const gchar *value) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_return_if_fail (key != NULL); g_return_if_fail (value != NULL); g_hash_table_insert (priv->metadata, g_strdup (key), g_strdup (value)); } /** * fwupd_release_add_metadata: * @release: A #FwupdRelease * @hash: the key-values * * Sets multiple release metadata items. * * Since: 1.0.4 **/ void fwupd_release_add_metadata (FwupdRelease *release, GHashTable *hash) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_autoptr(GList) keys = NULL; g_return_if_fail (FWUPD_IS_RELEASE (release)); g_return_if_fail (hash != NULL); /* deep copy the whole map */ keys = g_hash_table_get_keys (hash); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup (hash, key); g_hash_table_insert (priv->metadata, g_strdup (key), g_strdup (value)); } } /** * fwupd_release_get_metadata_item: * @release: A #FwupdRelease * @key: the key * * Gets a release metadata item. * * Returns: the value, or %NULL if unset * * Since: 1.0.4 **/ const gchar * fwupd_release_get_metadata_item (FwupdRelease *release, const gchar *key) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); g_return_val_if_fail (key != NULL, NULL); return g_hash_table_lookup (priv->metadata, key); } /** * fwupd_release_get_uri: * @release: A #FwupdRelease * * Gets the update uri. * * Returns: the update uri, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_uri (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->uri; } /** * fwupd_release_set_uri: * @release: A #FwupdRelease * @uri: the update URI * * Sets the update uri, i.e. where you can download the firmware from. * * Since: 0.9.3 **/ void fwupd_release_set_uri (FwupdRelease *release, const gchar *uri) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->uri); priv->uri = g_strdup (uri); } /** * fwupd_release_get_homepage: * @release: A #FwupdRelease * * Gets the update homepage. * * Returns: the update homepage, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_homepage (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->homepage; } /** * fwupd_release_set_homepage: * @release: A #FwupdRelease * @homepage: the description * * Sets the update homepage. * * Since: 0.9.3 **/ void fwupd_release_set_homepage (FwupdRelease *release, const gchar *homepage) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->homepage); priv->homepage = g_strdup (homepage); } /** * fwupd_release_get_details_url: * @release: A #FwupdRelease * * Gets the URL for the online update notes. * * Returns: the update URL, or %NULL if unset * * Since: 1.2.4 **/ const gchar * fwupd_release_get_details_url (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->details_url; } /** * fwupd_release_set_details_url: * @release: A #FwupdRelease * @details_url: the URL * * Sets the URL for the online update notes. * * Since: 1.2.4 **/ void fwupd_release_set_details_url (FwupdRelease *release, const gchar *details_url) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->details_url); priv->details_url = g_strdup (details_url); } /** * fwupd_release_get_source_url: * @release: A #FwupdRelease * * Gets the URL of the source code used to build this release. * * Returns: the update source_url, or %NULL if unset * * Since: 1.2.4 **/ const gchar * fwupd_release_get_source_url (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->source_url; } /** * fwupd_release_set_source_url: * @release: A #FwupdRelease * @source_url: the URL * * Sets the URL of the source code used to build this release. * * Since: 1.2.4 **/ void fwupd_release_set_source_url (FwupdRelease *release, const gchar *source_url) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->source_url); priv->source_url = g_strdup (source_url); } /** * fwupd_release_get_description: * @release: A #FwupdRelease * * Gets the update description in AppStream markup format. * * Returns: the update description, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_description (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->description; } /** * fwupd_release_set_description: * @release: A #FwupdRelease * @description: the update description in AppStream markup format * * Sets the update description. * * Since: 0.9.3 **/ void fwupd_release_set_description (FwupdRelease *release, const gchar *description) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->description); priv->description = g_strdup (description); } /** * fwupd_release_get_appstream_id: * @release: A #FwupdRelease * * Gets the AppStream ID. * * Returns: the AppStream ID, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_appstream_id (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->appstream_id; } /** * fwupd_release_set_appstream_id: * @release: A #FwupdRelease * @appstream_id: the AppStream component ID, e.g. `org.hughski.ColorHug2.firmware` * * Sets the AppStream ID. * * Since: 0.9.3 **/ void fwupd_release_set_appstream_id (FwupdRelease *release, const gchar *appstream_id) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->appstream_id); priv->appstream_id = g_strdup (appstream_id); } /** * fwupd_release_get_size: * @release: A #FwupdRelease * * Gets the update size. * * Returns: the update size in bytes, or 0 if unset * * Since: 0.9.3 **/ guint64 fwupd_release_get_size (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), 0); return priv->size; } /** * fwupd_release_set_size: * @release: A #FwupdRelease * @size: the update size in bytes * * Sets the update size. * * Since: 0.9.3 **/ void fwupd_release_set_size (FwupdRelease *release, guint64 size) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); priv->size = size; } /** * fwupd_release_get_summary: * @release: A #FwupdRelease * * Gets the update summary. * * Returns: the update summary, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_summary (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->summary; } /** * fwupd_release_set_summary: * @release: A #FwupdRelease * @summary: the update one line summary * * Sets the update summary. * * Since: 0.9.3 **/ void fwupd_release_set_summary (FwupdRelease *release, const gchar *summary) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->summary); priv->summary = g_strdup (summary); } /** * fwupd_release_get_vendor: * @release: A #FwupdRelease * * Gets the update vendor. * * Returns: the update vendor, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_vendor (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->vendor; } /** * fwupd_release_set_vendor: * @release: A #FwupdRelease * @vendor: the vendor name, e.g. `Hughski Limited` * * Sets the update vendor. * * Since: 0.9.3 **/ void fwupd_release_set_vendor (FwupdRelease *release, const gchar *vendor) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->vendor); priv->vendor = g_strdup (vendor); } /** * fwupd_release_get_license: * @release: A #FwupdRelease * * Gets the update license. * * Returns: the update license, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_license (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->license; } /** * fwupd_release_set_license: * @release: A #FwupdRelease * @license: the description * * Sets the update license. * * Since: 0.9.3 **/ void fwupd_release_set_license (FwupdRelease *release, const gchar *license) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->license); priv->license = g_strdup (license); } /** * fwupd_release_get_name: * @release: A #FwupdRelease * * Gets the update name. * * Returns: the update name, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_name (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->name; } /** * fwupd_release_set_name: * @release: A #FwupdRelease * @name: the description * * Sets the update name. * * Since: 0.9.3 **/ void fwupd_release_set_name (FwupdRelease *release, const gchar *name) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->name); priv->name = g_strdup (name); } /** * fwupd_release_get_trust_flags: * @release: A #FwupdRelease * * Gets the trust level of the release. * * Returns: the trust bitfield, e.g. #FWUPD_TRUST_FLAG_PAYLOAD * * Since: 0.9.8 **/ FwupdTrustFlags fwupd_release_get_trust_flags (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), 0); return priv->flags & FWUPD_RELEASE_TRUST_FLAGS_MASK; } /** * fwupd_release_set_trust_flags: * @release: A #FwupdRelease * @trust_flags: the bitfield, e.g. #FWUPD_TRUST_FLAG_PAYLOAD * * Sets the trust level of the release. * * Since: 0.9.8 **/ void fwupd_release_set_trust_flags (FwupdRelease *release, FwupdTrustFlags trust_flags) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); /* only overwrite the last two bits of the flags */ priv->flags &= ~FWUPD_RELEASE_TRUST_FLAGS_MASK; priv->flags |= trust_flags; } /** * fwupd_release_get_flags: * @release: A #FwupdRelease * * Gets the release flags. * * Returns: the release flags, or 0 if unset * * Since: 1.2.6 **/ FwupdReleaseFlags fwupd_release_get_flags (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), 0); return priv->flags; } /** * fwupd_release_set_flags: * @release: A #FwupdRelease * @flags: the release flags, e.g. %FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD * * Sets the release flags. * * Since: 1.2.6 **/ void fwupd_release_set_flags (FwupdRelease *release, FwupdReleaseFlags flags) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); priv->flags = flags; } /** * fwupd_release_add_flag: * @release: A #FwupdRelease * @flag: the #FwupdReleaseFlags * * Adds a specific release flag to the release. * * Since: 1.2.6 **/ void fwupd_release_add_flag (FwupdRelease *release, FwupdReleaseFlags flag) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); priv->flags |= flag; } /** * fwupd_release_remove_flag: * @release: A #FwupdRelease * @flag: the #FwupdReleaseFlags * * Removes a specific release flag from the release. * * Since: 1.2.6 **/ void fwupd_release_remove_flag (FwupdRelease *release, FwupdReleaseFlags flag) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); priv->flags &= ~flag; } /** * fwupd_release_has_flag: * @release: A #FwupdRelease * @flag: the #FwupdReleaseFlags * * Finds if the release has a specific release flag. * * Returns: %TRUE if the flag is set * * Since: 1.2.6 **/ gboolean fwupd_release_has_flag (FwupdRelease *release, FwupdReleaseFlags flag) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), FALSE); return (priv->flags & flag) > 0; } /** * fwupd_release_get_install_duration: * @release: A #FwupdRelease * * Gets the time estimate for firmware installation (in seconds) * * Returns: the estimated time to flash this release (or 0 if unset) * * Since: 1.2.1 **/ guint32 fwupd_release_get_install_duration (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), 0); return priv->install_duration; } /** * fwupd_release_set_install_duration: * @release: A #FwupdRelease * @duration: The amount of time * * Sets the time estimate for firmware installation (in seconds) * * Since: 1.2.1 **/ void fwupd_release_set_install_duration (FwupdRelease *release, guint32 duration) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); priv->install_duration = duration; } static GVariant * _hash_kv_to_variant (GHashTable *hash) { GVariantBuilder builder; g_autoptr(GList) keys = g_hash_table_get_keys (hash); g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup (hash, key); g_variant_builder_add (&builder, "{ss}", key, value); } return g_variant_builder_end (&builder); } static GHashTable * _variant_to_hash_kv (GVariant *dict) { GHashTable *hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); GVariantIter iter; const gchar *key; const gchar *value; g_variant_iter_init (&iter, dict); while (g_variant_iter_loop (&iter, "{ss}", &key, &value)) g_hash_table_insert (hash, g_strdup (key), g_strdup (value)); return hash; } /** * fwupd_release_to_variant: * @release: A #FwupdRelease * * Creates a GVariant from the release data. * * Returns: the GVariant, or %NULL for error * * Since: 1.0.0 **/ GVariant * fwupd_release_to_variant (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); GVariantBuilder builder; g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); /* create an array with all the metadata in */ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); if (priv->remote_id != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_REMOTE_ID, g_variant_new_string (priv->remote_id)); } if (priv->appstream_id != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_APPSTREAM_ID, g_variant_new_string (priv->appstream_id)); } if (priv->filename != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_FILENAME, g_variant_new_string (priv->filename)); } if (priv->protocol != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_PROTOCOL, g_variant_new_string (priv->protocol)); } if (priv->license != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_LICENSE, g_variant_new_string (priv->license)); } if (priv->name != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_NAME, g_variant_new_string (priv->name)); } if (priv->size != 0) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_SIZE, g_variant_new_uint64 (priv->size)); } if (priv->summary != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_SUMMARY, g_variant_new_string (priv->summary)); } if (priv->description != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_DESCRIPTION, g_variant_new_string (priv->description)); } if (priv->categories->len > 0) { g_autofree const gchar **strv = g_new0 (const gchar *, priv->categories->len + 1); for (guint i = 0; i < priv->categories->len; i++) strv[i] = (const gchar *) g_ptr_array_index (priv->categories, i); g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_CATEGORIES, g_variant_new_strv (strv, -1)); } if (priv->checksums->len > 0) { g_autoptr(GString) str = g_string_new (""); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index (priv->checksums, i); g_string_append_printf (str, "%s,", checksum); } if (str->len > 0) g_string_truncate (str, str->len - 1); g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_CHECKSUM, g_variant_new_string (str->str)); } if (priv->uri != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_URI, g_variant_new_string (priv->uri)); } if (priv->homepage != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_HOMEPAGE, g_variant_new_string (priv->homepage)); } if (priv->details_url != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_DETAILS_URL, g_variant_new_string (priv->details_url)); } if (priv->source_url != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_SOURCE_URL, g_variant_new_string (priv->source_url)); } if (priv->version != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_VERSION, g_variant_new_string (priv->version)); } if (priv->vendor != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_VENDOR, g_variant_new_string (priv->vendor)); } if (priv->flags != 0) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_TRUST_FLAGS, g_variant_new_uint64 (priv->flags)); } if (g_hash_table_size (priv->metadata) > 0) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_METADATA, _hash_kv_to_variant (priv->metadata)); } if (priv->install_duration > 0) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_INSTALL_DURATION, g_variant_new_uint32 (priv->install_duration)); } return g_variant_new ("a{sv}", &builder); } static void fwupd_release_from_key_value (FwupdRelease *release, const gchar *key, GVariant *value) { FwupdReleasePrivate *priv = GET_PRIVATE (release); if (g_strcmp0 (key, FWUPD_RESULT_KEY_REMOTE_ID) == 0) { fwupd_release_set_remote_id (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_APPSTREAM_ID) == 0) { fwupd_release_set_appstream_id (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_FILENAME) == 0) { fwupd_release_set_filename (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_PROTOCOL) == 0) { fwupd_release_set_protocol (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_LICENSE) == 0) { fwupd_release_set_license (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_NAME) == 0) { fwupd_release_set_name (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_SIZE) == 0) { fwupd_release_set_size (release, g_variant_get_uint64 (value)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_SUMMARY) == 0) { fwupd_release_set_summary (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_DESCRIPTION) == 0) { fwupd_release_set_description (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_CATEGORIES) == 0) { g_autofree const gchar **strv = g_variant_get_strv (value, NULL); for (guint i = 0; strv[i] != NULL; i++) fwupd_release_add_category (release, strv[i]); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_CHECKSUM) == 0) { const gchar *checksums = g_variant_get_string (value, NULL); g_auto(GStrv) split = g_strsplit (checksums, ",", -1); for (guint i = 0; split[i] != NULL; i++) fwupd_release_add_checksum (release, split[i]); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_URI) == 0) { fwupd_release_set_uri (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_HOMEPAGE) == 0) { fwupd_release_set_homepage (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_DETAILS_URL) == 0) { fwupd_release_set_details_url (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_SOURCE_URL) == 0) { fwupd_release_set_source_url (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_VERSION) == 0) { fwupd_release_set_version (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_VENDOR) == 0) { fwupd_release_set_vendor (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_TRUST_FLAGS) == 0) { fwupd_release_set_flags (release, g_variant_get_uint64 (value)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_INSTALL_DURATION) == 0) { fwupd_release_set_install_duration (release, g_variant_get_uint32 (value)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_UPDATE_MESSAGE) == 0) { fwupd_release_set_update_message (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_METADATA) == 0) { g_hash_table_unref (priv->metadata); priv->metadata = _variant_to_hash_kv (value); return; } } static void fwupd_pad_kv_str (GString *str, const gchar *key, const gchar *value) { /* ignore */ if (key == NULL || value == NULL) return; g_string_append_printf (str, " %s: ", key); for (gsize i = strlen (key); i < 20; i++) g_string_append (str, " "); g_string_append_printf (str, "%s\n", value); } static void fwupd_pad_kv_siz (GString *str, const gchar *key, guint64 value) { g_autofree gchar *tmp = NULL; /* ignore */ if (value == 0) return; tmp = g_format_size (value); fwupd_pad_kv_str (str, key, tmp); } static void fwupd_pad_kv_tfl (GString *str, const gchar *key, FwupdReleaseFlags release_flags) { g_autoptr(GString) tmp = g_string_new (""); for (guint i = 0; i < 64; i++) { if ((release_flags & ((guint64) 1 << i)) == 0) continue; g_string_append_printf (tmp, "%s|", fwupd_release_flag_to_string ((guint64) 1 << i)); } if (tmp->len == 0) { g_string_append (tmp, fwupd_release_flag_to_string (0)); } else { g_string_truncate (tmp, tmp->len - 1); } fwupd_pad_kv_str (str, key, tmp->str); } static void fwupd_pad_kv_int (GString *str, const gchar *key, guint32 value) { g_autofree gchar *tmp = NULL; /* ignore */ if (value == 0) return; tmp = g_strdup_printf("%" G_GUINT32_FORMAT, value); fwupd_pad_kv_str (str, key, tmp); } static void fwupd_release_json_add_string (JsonBuilder *builder, const gchar *key, const gchar *str) { if (str == NULL) return; json_builder_set_member_name (builder, key); json_builder_add_string_value (builder, str); } static void fwupd_release_json_add_int (JsonBuilder *builder, const gchar *key, guint64 num) { if (num == 0) return; json_builder_set_member_name (builder, key); json_builder_add_int_value (builder, num); } /** * fwupd_release_to_json: * @release: A #FwupdRelease * @builder: A #JsonBuilder * * Adds a fwupd release to a JSON builder * * Since: 1.2.6 **/ void fwupd_release_to_json (FwupdRelease *release, JsonBuilder *builder) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_autoptr(GList) keys = NULL; g_return_if_fail (FWUPD_IS_RELEASE (release)); g_return_if_fail (builder != NULL); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_APPSTREAM_ID, priv->appstream_id); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_REMOTE_ID, priv->remote_id); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_SUMMARY, priv->summary); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_VERSION, priv->version); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_FILENAME, priv->filename); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_PROTOCOL, priv->protocol); if (priv->categories->len > 0) { json_builder_set_member_name (builder, FWUPD_RESULT_KEY_CATEGORIES); json_builder_begin_array (builder); for (guint i = 0; i < priv->categories->len; i++) { const gchar *tmp = g_ptr_array_index (priv->categories, i); json_builder_add_string_value (builder, tmp); } json_builder_end_array (builder); } if (priv->checksums->len > 0) { json_builder_set_member_name (builder, FWUPD_RESULT_KEY_CHECKSUM); json_builder_begin_array (builder); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index (priv->checksums, i); json_builder_add_string_value (builder, checksum); } json_builder_end_array (builder); } fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_LICENSE, priv->license); fwupd_release_json_add_int (builder, FWUPD_RESULT_KEY_SIZE, priv->size); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_URI, priv->uri); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_HOMEPAGE, priv->homepage); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_DETAILS_URL, priv->details_url); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_SOURCE_URL, priv->source_url); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_VENDOR, priv->vendor); if (priv->flags != FWUPD_RELEASE_FLAG_NONE) { json_builder_set_member_name (builder, FWUPD_RESULT_KEY_FLAGS); json_builder_begin_array (builder); for (guint i = 0; i < 64; i++) { const gchar *tmp; if ((priv->flags & ((guint64) 1 << i)) == 0) continue; tmp = fwupd_release_flag_to_string ((guint64) 1 << i); json_builder_add_string_value (builder, tmp); } json_builder_end_array (builder); } fwupd_release_json_add_int (builder, FWUPD_RESULT_KEY_INSTALL_DURATION, priv->install_duration); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_UPDATE_MESSAGE, priv->update_message); /* metadata */ keys = g_hash_table_get_keys (priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup (priv->metadata, key); fwupd_release_json_add_string (builder, key, value); } } /** * fwupd_release_to_string: * @release: A #FwupdRelease * * Builds a text representation of the object. * * Returns: text, or %NULL for invalid * * Since: 0.9.3 **/ gchar * fwupd_release_to_string (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); GString *str; g_autoptr(GList) keys = NULL; g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); str = g_string_new (""); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_APPSTREAM_ID, priv->appstream_id); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_REMOTE_ID, priv->remote_id); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_SUMMARY, priv->summary); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_VERSION, priv->version); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_FILENAME, priv->filename); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_PROTOCOL, priv->protocol); for (guint i = 0; i < priv->categories->len; i++) { const gchar *tmp = g_ptr_array_index (priv->categories, i); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_CATEGORIES, tmp); } for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index (priv->checksums, i); g_autofree gchar *checksum_display = fwupd_checksum_format_for_display (checksum); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_CHECKSUM, checksum_display); } fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_LICENSE, priv->license); fwupd_pad_kv_siz (str, FWUPD_RESULT_KEY_SIZE, priv->size); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_URI, priv->uri); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_HOMEPAGE, priv->homepage); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_DETAILS_URL, priv->details_url); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_SOURCE_URL, priv->source_url); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_VENDOR, priv->vendor); fwupd_pad_kv_tfl (str, FWUPD_RESULT_KEY_FLAGS, priv->flags); fwupd_pad_kv_int (str, FWUPD_RESULT_KEY_INSTALL_DURATION, priv->install_duration); if (priv->update_message != NULL) fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_UPDATE_MESSAGE, priv->update_message); /* metadata */ keys = g_hash_table_get_keys (priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup (priv->metadata, key); fwupd_pad_kv_str (str, key, value); } return g_string_free (str, FALSE); } static void fwupd_release_class_init (FwupdReleaseClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fwupd_release_finalize; } static void fwupd_release_init (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); priv->categories = g_ptr_array_new_with_free_func (g_free); priv->checksums = g_ptr_array_new_with_free_func (g_free); priv->metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } static void fwupd_release_finalize (GObject *object) { FwupdRelease *release = FWUPD_RELEASE (object); FwupdReleasePrivate *priv = GET_PRIVATE (release); g_free (priv->description); g_free (priv->filename); g_free (priv->protocol); g_free (priv->appstream_id); g_free (priv->license); g_free (priv->name); g_free (priv->summary); g_free (priv->uri); g_free (priv->homepage); g_free (priv->details_url); g_free (priv->source_url); g_free (priv->vendor); g_free (priv->version); g_free (priv->remote_id); g_free (priv->update_message); g_ptr_array_unref (priv->categories); g_ptr_array_unref (priv->checksums); g_hash_table_unref (priv->metadata); G_OBJECT_CLASS (fwupd_release_parent_class)->finalize (object); } static void fwupd_release_set_from_variant_iter (FwupdRelease *release, GVariantIter *iter) { GVariant *value; const gchar *key; while (g_variant_iter_next (iter, "{&sv}", &key, &value)) { fwupd_release_from_key_value (release, key, value); g_variant_unref (value); } } /** * fwupd_release_from_variant: * @value: a #GVariant * * Creates a new release using packed data. * * Returns: (transfer full): a new #FwupdRelease, or %NULL if @value was invalid * * Since: 1.0.0 **/ FwupdRelease * fwupd_release_from_variant (GVariant *value) { FwupdRelease *rel = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; /* format from GetDetails */ type_string = g_variant_get_type_string (value); if (g_strcmp0 (type_string, "(a{sv})") == 0) { rel = fwupd_release_new (); g_variant_get (value, "(a{sv})", &iter); fwupd_release_set_from_variant_iter (rel, iter); } else if (g_strcmp0 (type_string, "a{sv}") == 0) { rel = fwupd_release_new (); g_variant_get (value, "a{sv}", &iter); fwupd_release_set_from_variant_iter (rel, iter); } else { g_warning ("type %s not known", type_string); } return rel; } /** * fwupd_release_array_from_variant: * @value: a #GVariant * * Creates an array of new releases using packed data. * * Returns: (transfer container) (element-type FwupdRelease): releases, or %NULL if @value was invalid * * Since: 1.2.10 **/ GPtrArray * fwupd_release_array_from_variant (GVariant *value) { GPtrArray *array = NULL; gsize sz; g_autoptr(GVariant) untuple = NULL; array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); untuple = g_variant_get_child_value (value, 0); sz = g_variant_n_children (untuple); for (guint i = 0; i < sz; i++) { FwupdRelease *rel; g_autoptr(GVariant) data = NULL; data = g_variant_get_child_value (untuple, i); rel = fwupd_release_from_variant (data); if (rel == NULL) continue; g_ptr_array_add (array, rel); } return array; } /** * fwupd_release_new: * * Creates a new release. * * Returns: a new #FwupdRelease * * Since: 0.9.3 **/ FwupdRelease * fwupd_release_new (void) { FwupdRelease *release; release = g_object_new (FWUPD_TYPE_RELEASE, NULL); return FWUPD_RELEASE (release); } fwupd-1.2.14/libfwupd/fwupd-release.h000066400000000000000000000117761402665037500175000ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-enums.h" G_BEGIN_DECLS #define FWUPD_TYPE_RELEASE (fwupd_release_get_type ()) G_DECLARE_DERIVABLE_TYPE (FwupdRelease, fwupd_release, FWUPD, RELEASE, GObject) struct _FwupdReleaseClass { GObjectClass parent_class; /*< private >*/ void (*_fwupd_reserved1) (void); void (*_fwupd_reserved2) (void); void (*_fwupd_reserved3) (void); void (*_fwupd_reserved4) (void); void (*_fwupd_reserved5) (void); void (*_fwupd_reserved6) (void); void (*_fwupd_reserved7) (void); }; FwupdRelease *fwupd_release_new (void); gchar *fwupd_release_to_string (FwupdRelease *release); const gchar *fwupd_release_get_version (FwupdRelease *release); void fwupd_release_set_version (FwupdRelease *release, const gchar *version); const gchar *fwupd_release_get_uri (FwupdRelease *release); void fwupd_release_set_uri (FwupdRelease *release, const gchar *uri); GPtrArray *fwupd_release_get_categories (FwupdRelease *release); void fwupd_release_add_category (FwupdRelease *release, const gchar *category); gboolean fwupd_release_has_category (FwupdRelease *release, const gchar *category); GPtrArray *fwupd_release_get_checksums (FwupdRelease *release); void fwupd_release_add_checksum (FwupdRelease *release, const gchar *checksum); gboolean fwupd_release_has_checksum (FwupdRelease *release, const gchar *checksum); GHashTable *fwupd_release_get_metadata (FwupdRelease *release); void fwupd_release_add_metadata (FwupdRelease *release, GHashTable *hash); void fwupd_release_add_metadata_item (FwupdRelease *release, const gchar *key, const gchar *value); const gchar *fwupd_release_get_metadata_item (FwupdRelease *release, const gchar *key); const gchar *fwupd_release_get_filename (FwupdRelease *release); void fwupd_release_set_filename (FwupdRelease *release, const gchar *filename); const gchar *fwupd_release_get_protocol (FwupdRelease *release); void fwupd_release_set_protocol (FwupdRelease *release, const gchar *protocol); const gchar *fwupd_release_get_appstream_id (FwupdRelease *release); void fwupd_release_set_appstream_id (FwupdRelease *release, const gchar *appstream_id); const gchar *fwupd_release_get_remote_id (FwupdRelease *release); void fwupd_release_set_remote_id (FwupdRelease *release, const gchar *remote_id); const gchar *fwupd_release_get_vendor (FwupdRelease *release); void fwupd_release_set_vendor (FwupdRelease *release, const gchar *vendor); const gchar *fwupd_release_get_name (FwupdRelease *release); void fwupd_release_set_name (FwupdRelease *release, const gchar *name); const gchar *fwupd_release_get_summary (FwupdRelease *release); void fwupd_release_set_summary (FwupdRelease *release, const gchar *summary); const gchar *fwupd_release_get_description (FwupdRelease *release); void fwupd_release_set_description (FwupdRelease *release, const gchar *description); const gchar *fwupd_release_get_homepage (FwupdRelease *release); void fwupd_release_set_homepage (FwupdRelease *release, const gchar *homepage); const gchar *fwupd_release_get_details_url (FwupdRelease *release); void fwupd_release_set_details_url (FwupdRelease *release, const gchar *details_url); const gchar *fwupd_release_get_source_url (FwupdRelease *release); void fwupd_release_set_source_url (FwupdRelease *release, const gchar *source_url); guint64 fwupd_release_get_size (FwupdRelease *release); void fwupd_release_set_size (FwupdRelease *release, guint64 size); const gchar *fwupd_release_get_license (FwupdRelease *release); void fwupd_release_set_license (FwupdRelease *release, const gchar *license); FwupdTrustFlags fwupd_release_get_trust_flags (FwupdRelease *release) G_DEPRECATED_FOR(fwupd_release_get_flags); void fwupd_release_set_trust_flags (FwupdRelease *release, FwupdTrustFlags trust_flags) G_DEPRECATED_FOR(fwupd_release_set_flags); FwupdReleaseFlags fwupd_release_get_flags (FwupdRelease *release); void fwupd_release_set_flags (FwupdRelease *release, FwupdReleaseFlags flags); void fwupd_release_add_flag (FwupdRelease *release, FwupdReleaseFlags flag); void fwupd_release_remove_flag (FwupdRelease *release, FwupdReleaseFlags flag); gboolean fwupd_release_has_flag (FwupdRelease *release, FwupdReleaseFlags flag); guint32 fwupd_release_get_install_duration (FwupdRelease *release); void fwupd_release_set_install_duration (FwupdRelease *release, guint32 duration); const gchar *fwupd_release_get_update_message (FwupdRelease *release); void fwupd_release_set_update_message (FwupdRelease *release, const gchar *update_message); FwupdRelease *fwupd_release_from_variant (GVariant *value); GPtrArray *fwupd_release_array_from_variant (GVariant *value); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-remote-private.h000066400000000000000000000013611402665037500210100ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fwupd-remote.h" G_BEGIN_DECLS GVariant *fwupd_remote_to_variant (FwupdRemote *self); gboolean fwupd_remote_load_from_filename (FwupdRemote *self, const gchar *filename, GCancellable *cancellable, GError **error); void fwupd_remote_set_priority (FwupdRemote *self, gint priority); void fwupd_remote_set_agreement (FwupdRemote *self, const gchar *agreement); void fwupd_remote_set_mtime (FwupdRemote *self, guint64 mtime); gchar **fwupd_remote_get_order_after (FwupdRemote *self); gchar **fwupd_remote_get_order_before (FwupdRemote *self); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-remote.c000066400000000000000000001031161402665037500173340ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fwupd-deprecated.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-remote-private.h" /** * SECTION:fwupd-remote * @short_description: a source of firmware * * An object that represents a source of metadata that provides firmware. * * See also: #FwupdClient */ static void fwupd_remote_finalize (GObject *obj); typedef struct { GObject parent_instance; FwupdRemoteKind kind; FwupdKeyringKind keyring_kind; gchar *id; gchar *firmware_base_uri; gchar *report_uri; gchar *metadata_uri; gchar *metadata_uri_sig; gchar *username; gchar *password; gchar *title; gchar *agreement; gchar *checksum; gchar *filename_cache; gchar *filename_cache_sig; gchar *filename_source; gboolean enabled; gboolean approval_required; gint priority; guint64 mtime; gchar **order_after; gchar **order_before; } FwupdRemotePrivate; enum { PROP_0, PROP_ID, PROP_ENABLED, PROP_APPROVAL_REQUIRED, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE (FwupdRemote, fwupd_remote, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_remote_get_instance_private (o)) static void fwupd_remote_set_username (FwupdRemote *self, const gchar *username) { FwupdRemotePrivate *priv = GET_PRIVATE (self); if (username != NULL && username[0] == '\0') username = NULL; priv->username = g_strdup (username); } static void fwupd_remote_set_title (FwupdRemote *self, const gchar *title) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_free (priv->title); priv->title = g_strdup (title); } /** * fwupd_remote_set_agreement: * @self: A #FwupdRemote * @agreement: Agreement markup * * Sets the remote agreement in AppStream markup format * * Since: 1.0.7 **/ void fwupd_remote_set_agreement (FwupdRemote *self, const gchar *agreement) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_free (priv->agreement); priv->agreement = g_strdup (agreement); } static void fwupd_remote_set_checksum (FwupdRemote *self, const gchar *checksum) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_free (priv->checksum); priv->checksum = g_strdup (checksum); } static void fwupd_remote_set_password (FwupdRemote *self, const gchar *password) { FwupdRemotePrivate *priv = GET_PRIVATE (self); if (password != NULL && password[0] == '\0') password = NULL; priv->password = g_strdup (password); } static void fwupd_remote_set_kind (FwupdRemote *self, FwupdRemoteKind kind) { FwupdRemotePrivate *priv = GET_PRIVATE (self); priv->kind = kind; } static void fwupd_remote_set_keyring_kind (FwupdRemote *self, FwupdKeyringKind keyring_kind) { FwupdRemotePrivate *priv = GET_PRIVATE (self); priv->keyring_kind = keyring_kind; } /* note, this has to be set before url */ static void fwupd_remote_set_id (FwupdRemote *self, const gchar *id) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_free (priv->id); priv->id = g_strdup (id); g_strdelimit (priv->id, ".", '\0'); } static void fwupd_remote_set_filename_source (FwupdRemote *self, const gchar *filename_source) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_free (priv->filename_source); priv->filename_source = g_strdup (filename_source); } static const gchar * fwupd_remote_get_suffix_for_keyring_kind (FwupdKeyringKind keyring_kind) { if (keyring_kind == FWUPD_KEYRING_KIND_GPG) return ".asc"; if (keyring_kind == FWUPD_KEYRING_KIND_PKCS7) return ".p7b"; return NULL; } static SoupURI * fwupd_remote_build_uri (FwupdRemote *self, const gchar *url, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE (self); SoupURI *uri; g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); g_return_val_if_fail (url != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* create URI, substituting if required */ if (priv->firmware_base_uri != NULL) { g_autoptr(SoupURI) uri_tmp = NULL; g_autofree gchar *basename = NULL; g_autofree gchar *url2 = NULL; uri_tmp = soup_uri_new (url); if (uri_tmp == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse URI '%s'", url); return NULL; } basename = g_path_get_basename (soup_uri_get_path (uri_tmp)); url2 = g_build_filename (priv->firmware_base_uri, basename, NULL); uri = soup_uri_new (url2); if (uri == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse URI '%s'", url2); return NULL; } /* use the base URI of the metadata to build the full path */ } else if (g_strstr_len (url, -1, "/") == NULL) { g_autofree gchar *basename = NULL; g_autofree gchar *path = NULL; uri = soup_uri_new (priv->metadata_uri); if (uri == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse metadata URI '%s'", url); return NULL; } basename = g_path_get_dirname (soup_uri_get_path (uri)); path = g_build_filename (basename, url, NULL); soup_uri_set_path (uri, path); /* a normal URI */ } else { uri = soup_uri_new (url); if (uri == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse URI '%s'", url); return NULL; } } /* set the username and password */ if (priv->username != NULL) soup_uri_set_user (uri, priv->username); if (priv->password != NULL) soup_uri_set_password (uri, priv->password); return uri; } /* note, this has to be set before username and password */ static void fwupd_remote_set_metadata_uri (FwupdRemote *self, const gchar *metadata_uri) { FwupdRemotePrivate *priv = GET_PRIVATE (self); const gchar *suffix; g_autoptr(SoupURI) uri = NULL; /* build the URI */ uri = soup_uri_new (metadata_uri); if (uri == NULL) return; /* save this so we can export the object as a GVariant */ priv->metadata_uri = g_strdup (metadata_uri); /* generate the signature URI too */ suffix = fwupd_remote_get_suffix_for_keyring_kind (priv->keyring_kind); if (suffix != NULL) priv->metadata_uri_sig = g_strconcat (metadata_uri, suffix, NULL); } /* note, this has to be set after MetadataURI */ static void fwupd_remote_set_firmware_base_uri (FwupdRemote *self, const gchar *firmware_base_uri) { FwupdRemotePrivate *priv = GET_PRIVATE (self); priv->firmware_base_uri = g_strdup (firmware_base_uri); } static void fwupd_remote_set_report_uri (FwupdRemote *self, const gchar *report_uri) { FwupdRemotePrivate *priv = GET_PRIVATE (self); priv->report_uri = g_strdup (report_uri); } /** * fwupd_remote_kind_from_string: * @kind: a string, e.g. `download` * * Converts an printable string to an enumerated type. * * Returns: a #FwupdRemoteKind, e.g. %FWUPD_REMOTE_KIND_DOWNLOAD * * Since: 0.9.6 **/ FwupdRemoteKind fwupd_remote_kind_from_string (const gchar *kind) { if (g_strcmp0 (kind, "download") == 0) return FWUPD_REMOTE_KIND_DOWNLOAD; if (g_strcmp0 (kind, "local") == 0) return FWUPD_REMOTE_KIND_LOCAL; if (g_strcmp0 (kind, "directory") == 0) return FWUPD_REMOTE_KIND_DIRECTORY; return FWUPD_REMOTE_KIND_UNKNOWN; } /** * fwupd_remote_kind_to_string: * @kind: a #FwupdRemoteKind, e.g. %FWUPD_REMOTE_KIND_DOWNLOAD * * Converts an enumerated type to a printable string. * * Returns: a string, e.g. `download` * * Since: 0.9.6 **/ const gchar * fwupd_remote_kind_to_string (FwupdRemoteKind kind) { if (kind == FWUPD_REMOTE_KIND_DOWNLOAD) return "download"; if (kind == FWUPD_REMOTE_KIND_LOCAL) return "local"; if (kind == FWUPD_REMOTE_KIND_DIRECTORY) return "directory"; return NULL; } static void fwupd_remote_set_filename_cache (FwupdRemote *self, const gchar *filename) { FwupdRemotePrivate *priv = GET_PRIVATE (self); const gchar *suffix; g_return_if_fail (FWUPD_IS_REMOTE (self)); g_free (priv->filename_cache); priv->filename_cache = g_strdup (filename); /* create for all remote types */ suffix = fwupd_remote_get_suffix_for_keyring_kind (priv->keyring_kind); if (suffix != NULL) { g_free (priv->filename_cache_sig); priv->filename_cache_sig = g_strconcat (filename, suffix, NULL); } } /** * fwupd_remote_load_from_filename: * @self: A #FwupdRemote * @filename: A filename * @cancellable: the #GCancellable, or %NULL * @error: the #GError, or %NULL * * Sets up the remote ready for use. Most other methods call this * for you, and do you only need to call this if you are just watching * the self. * * Returns: %TRUE for success * * Since: 0.9.3 **/ gboolean fwupd_remote_load_from_filename (FwupdRemote *self, const gchar *filename, GCancellable *cancellable, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE (self); const gchar *group = "fwupd Remote"; g_autofree gchar *firmware_base_uri = NULL; g_autofree gchar *id = NULL; g_autofree gchar *keyring_kind = NULL; g_autofree gchar *metadata_uri = NULL; g_autofree gchar *order_after = NULL; g_autofree gchar *order_before = NULL; g_autofree gchar *report_uri = NULL; g_autoptr(GKeyFile) kf = NULL; g_return_val_if_fail (FWUPD_IS_REMOTE (self), FALSE); g_return_val_if_fail (filename != NULL, FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* set ID */ id = g_path_get_basename (filename); fwupd_remote_set_id (self, id); /* load file */ kf = g_key_file_new (); if (!g_key_file_load_from_file (kf, filename, G_KEY_FILE_NONE, error)) return FALSE; /* get verification type, falling back to GPG */ keyring_kind = g_key_file_get_string (kf, group, "Keyring", NULL); if (keyring_kind == NULL) { priv->keyring_kind = FWUPD_KEYRING_KIND_GPG; } else { priv->keyring_kind = fwupd_keyring_kind_from_string (keyring_kind); if (priv->keyring_kind == FWUPD_KEYRING_KIND_UNKNOWN) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse type '%s'", keyring_kind); return FALSE; } } /* all remotes need a URI, even if it's file:// to the cache */ metadata_uri = g_key_file_get_string (kf, group, "MetadataURI", error); if (metadata_uri == NULL) return FALSE; if (g_str_has_prefix (metadata_uri, "file://")) { const gchar *filename_cache = metadata_uri; if (g_str_has_prefix (filename_cache, "file://")) filename_cache += 7; fwupd_remote_set_filename_cache (self, filename_cache); if (g_file_test (filename_cache, G_FILE_TEST_IS_DIR)) priv->kind = FWUPD_REMOTE_KIND_DIRECTORY; else priv->kind = FWUPD_REMOTE_KIND_LOCAL; } else if (g_str_has_prefix (metadata_uri, "http://") || g_str_has_prefix (metadata_uri, "https://")) { priv->kind = FWUPD_REMOTE_KIND_DOWNLOAD; } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse MetadataURI type '%s'", metadata_uri); return FALSE; } /* extract data */ priv->enabled = g_key_file_get_boolean (kf, group, "Enabled", NULL); priv->approval_required = g_key_file_get_boolean (kf, group, "ApprovalRequired", NULL); priv->title = g_key_file_get_string (kf, group, "Title", NULL); /* reporting is optional */ report_uri = g_key_file_get_string (kf, group, "ReportURI", NULL); if (report_uri != NULL && report_uri[0] != '\0') fwupd_remote_set_report_uri (self, report_uri); /* DOWNLOAD-type remotes */ if (priv->kind == FWUPD_REMOTE_KIND_DOWNLOAD) { g_autofree gchar *filename_cache = NULL; g_autofree gchar *username = NULL; g_autofree gchar *password = NULL; /* the client has to download this and the signature */ fwupd_remote_set_metadata_uri (self, metadata_uri); /* check the URI was valid */ if (priv->metadata_uri == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse URI '%s' in %s", metadata_uri, filename); return FALSE; } /* username and password are optional */ username = g_key_file_get_string (kf, group, "Username", NULL); if (username != NULL) fwupd_remote_set_username (self, username); password = g_key_file_get_string (kf, group, "Password", NULL); if (password != NULL) fwupd_remote_set_password (self, password); /* set cache to /var/lib... */ filename_cache = g_build_filename (LOCALSTATEDIR, "lib", "fwupd", "remotes.d", priv->id, "metadata.xml.gz", NULL); fwupd_remote_set_filename_cache (self, filename_cache); } /* load the checksum */ if (priv->filename_cache_sig != NULL && g_file_test (priv->filename_cache_sig, G_FILE_TEST_EXISTS)) { gsize sz = 0; g_autofree gchar *buf = NULL; g_autoptr(GChecksum) checksum = g_checksum_new (G_CHECKSUM_SHA256); if (!g_file_get_contents (priv->filename_cache_sig, &buf, &sz, error)) { g_prefix_error (error, "failed to get checksum: "); return FALSE; } g_checksum_update (checksum, (guchar *) buf, (gssize) sz); fwupd_remote_set_checksum (self, g_checksum_get_string (checksum)); } else { fwupd_remote_set_checksum (self, NULL); } /* the base URI is optional */ firmware_base_uri = g_key_file_get_string (kf, group, "FirmwareBaseURI", NULL); if (firmware_base_uri != NULL) fwupd_remote_set_firmware_base_uri (self, firmware_base_uri); /* some validation around DIRECTORY types */ if (priv->kind == FWUPD_REMOTE_KIND_DIRECTORY) { if (priv->keyring_kind != FWUPD_KEYRING_KIND_NONE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Keyring kind %s is not supported with directory remote", fwupd_keyring_kind_to_string (priv->keyring_kind)); return FALSE; } if (firmware_base_uri != NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Directory remotes don't support firmware base URI"); return FALSE; } } /* dep logic */ order_before = g_key_file_get_string (kf, group, "OrderBefore", NULL); if (order_before != NULL) priv->order_before = g_strsplit_set (order_before, ",:;", -1); order_after = g_key_file_get_string (kf, group, "OrderAfter", NULL); if (order_after != NULL) priv->order_after = g_strsplit_set (order_after, ",:;", -1); /* success */ fwupd_remote_set_filename_source (self, filename); return TRUE; } /** * fwupd_remote_get_order_after: * @self: A #FwupdRemote * * Gets the list of remotes this plugin should be ordered after. * * Returns: (transfer none): an array * * Since: 0.9.5 **/ gchar ** fwupd_remote_get_order_after (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->order_after; } /** * fwupd_remote_get_order_before: * @self: A #FwupdRemote * * Gets the list of remotes this plugin should be ordered before. * * Returns: (transfer none): an array * * Since: 0.9.5 **/ gchar ** fwupd_remote_get_order_before (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->order_before; } /** * fwupd_remote_get_filename_cache: * @self: A #FwupdRemote * * Gets the path and filename that the remote is using for a cache. * * Returns: a string, or %NULL for unset * * Since: 0.9.6 **/ const gchar * fwupd_remote_get_filename_cache (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->filename_cache; } /** * fwupd_remote_get_filename_cache_sig: * @self: A #FwupdRemote * * Gets the path and filename that the remote is using for a signature cache. * * Returns: a string, or %NULL for unset * * Since: 0.9.7 **/ const gchar * fwupd_remote_get_filename_cache_sig (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->filename_cache_sig; } /** * fwupd_remote_get_filename_source: * @self: A #FwupdRemote * * Gets the path and filename of the remote itself, typically a `.conf` file. * * Returns: a string, or %NULL for unset * * Since: 0.9.8 **/ const gchar * fwupd_remote_get_filename_source (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->filename_source; } /** * fwupd_remote_get_priority: * @self: A #FwupdRemote * * Gets the priority of the remote, where bigger numbers are better. * * Returns: a priority, or 0 for the default value * * Since: 0.9.5 **/ gint fwupd_remote_get_priority (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), 0); return priv->priority; } /** * fwupd_remote_get_kind: * @self: A #FwupdRemote * * Gets the kind of the remote. * * Returns: a #FwupdRemoteKind, e.g. #FWUPD_REMOTE_KIND_LOCAL * * Since: 0.9.6 **/ FwupdRemoteKind fwupd_remote_get_kind (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), 0); return priv->kind; } /** * fwupd_remote_get_keyring_kind: * @self: A #FwupdRemote * * Gets the keyring kind of the remote. * * Returns: a #FwupdKeyringKind, e.g. #FWUPD_KEYRING_KIND_GPG * * Since: 0.9.7 **/ FwupdKeyringKind fwupd_remote_get_keyring_kind (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), 0); return priv->keyring_kind; } /** * fwupd_remote_get_age: * @self: A #FwupdRemote * * Gets the age of the remote in seconds. * * Returns: a age, or %G_MAXUINT64 for unavailable * * Since: 0.9.5 **/ guint64 fwupd_remote_get_age (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); guint64 now; g_return_val_if_fail (FWUPD_IS_REMOTE (self), 0); now = (guint64) g_get_real_time () / G_USEC_PER_SEC; if (priv->mtime > now) return G_MAXUINT64; return now - priv->mtime; } /** * fwupd_remote_set_priority: * @self: A #FwupdRemote * @priority: an integer, where 1 is better * * Sets the plugin priority. * * Since: 0.9.5 **/ void fwupd_remote_set_priority (FwupdRemote *self, gint priority) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FWUPD_IS_REMOTE (self)); priv->priority = priority; } /** * fwupd_remote_set_mtime: * @self: A #FwupdRemote * @mtime: a UNIX itmestamp * * Sets the plugin modification time. * * Since: 0.9.5 **/ void fwupd_remote_set_mtime (FwupdRemote *self, guint64 mtime) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FWUPD_IS_REMOTE (self)); priv->mtime = mtime; } /** * fwupd_remote_get_username: * @self: A #FwupdRemote * * Gets the username configured for the remote. * * Returns: a string, or %NULL for unset * * Since: 0.9.5 **/ const gchar * fwupd_remote_get_username (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->username; } /** * fwupd_remote_get_password: * @self: A #FwupdRemote * * Gets the password configured for the remote. * * Returns: a string, or %NULL for unset * * Since: 0.9.5 **/ const gchar * fwupd_remote_get_password (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->password; } /** * fwupd_remote_get_title: * @self: A #FwupdRemote * * Gets the remote title, e.g. `Linux Vendor Firmware Service`. * * Returns: a string, or %NULL if unset * * Since: 0.9.8 **/ const gchar * fwupd_remote_get_title (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->title; } /** * fwupd_remote_get_agreement: * @self: A #FwupdRemote * * Gets the remote agreement in AppStream markup format * * Returns: a string, or %NULL if unset * * Since: 1.0.7 **/ const gchar * fwupd_remote_get_agreement (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->agreement; } /** * fwupd_remote_get_checksum: * @self: A #FwupdRemote * * Gets the remote checksum. * * Returns: a string, or %NULL if unset * * Since: 1.0.0 **/ const gchar * fwupd_remote_get_checksum (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->checksum; } /** * fwupd_remote_build_firmware_uri: * @self: A #FwupdRemote * @url: the URL to use * @error: the #GError, or %NULL * * Builds a URI for the URL using the username and password set for the remote, * including any basename URI substitution. * * Returns: (transfer full): a URI, or %NULL for error * * Since: 0.9.7 **/ gchar * fwupd_remote_build_firmware_uri (FwupdRemote *self, const gchar *url, GError **error) { g_autoptr(SoupURI) uri = fwupd_remote_build_uri (self, url, error); if (uri == NULL) return NULL; return soup_uri_to_string (uri, FALSE); } /** * fwupd_remote_get_report_uri: * @self: A #FwupdRemote * * Gets the URI for the remote reporting. * * Returns: (transfer none): a URI, or %NULL for invalid. * * Since: 1.0.4 **/ const gchar * fwupd_remote_get_report_uri (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->report_uri; } /** * fwupd_remote_get_metadata_uri: * @self: A #FwupdRemote * * Gets the URI for the remote metadata. * * Returns: (transfer none): a URI, or %NULL for invalid. * * Since: 0.9.7 **/ const gchar * fwupd_remote_get_metadata_uri (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->metadata_uri; } /** * fwupd_remote_get_metadata_uri_sig: * @self: A #FwupdRemote * * Gets the URI for the remote metadata signature. * * Returns: (transfer none): a URI, or %NULL for invalid. * * Since: 0.9.7 **/ const gchar * fwupd_remote_get_metadata_uri_sig (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->metadata_uri_sig; } /** * fwupd_remote_get_firmware_base_uri: * @self: A #FwupdRemote * * Gets the base URI for firmware. * * Returns: (transfer none): a URI, or %NULL for unset. * * Since: 0.9.7 **/ const gchar * fwupd_remote_get_firmware_base_uri (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->firmware_base_uri; } /** * fwupd_remote_get_enabled: * @self: A #FwupdRemote * * Gets if the remote is enabled and should be used. * * Returns: a #TRUE if the remote is enabled * * Since: 0.9.3 **/ gboolean fwupd_remote_get_enabled (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), FALSE); return priv->enabled; } /** * fwupd_remote_get_approval_required: * @self: A #FwupdRemote * * Gets if firmware from the remote should be checked against the list * of a approved checksums. * * Returns: a #TRUE if the remote is restricted * * Since: 1.2.6 **/ gboolean fwupd_remote_get_approval_required (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), FALSE); return priv->approval_required; } /** * fwupd_remote_get_id: * @self: A #FwupdRemote * * Gets the remote ID, e.g. `lvfs-testing`. * * Returns: a string, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_remote_get_id (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->id; } static void fwupd_remote_set_from_variant_iter (FwupdRemote *self, GVariantIter *iter) { FwupdRemotePrivate *priv = GET_PRIVATE (self); GVariant *value; const gchar *key; g_autoptr(GVariantIter) iter2 = g_variant_iter_copy (iter); g_autoptr(GVariantIter) iter3 = g_variant_iter_copy (iter); /* three passes, as we have to construct Id -> Url -> * */ while (g_variant_iter_loop (iter, "{sv}", &key, &value)) { if (g_strcmp0 (key, FWUPD_RESULT_KEY_REMOTE_ID) == 0) fwupd_remote_set_id (self, g_variant_get_string (value, NULL)); if (g_strcmp0 (key, "Type") == 0) fwupd_remote_set_kind (self, g_variant_get_uint32 (value)); if (g_strcmp0 (key, "Keyring") == 0) fwupd_remote_set_keyring_kind (self, g_variant_get_uint32 (value)); } while (g_variant_iter_loop (iter2, "{sv}", &key, &value)) { if (g_strcmp0 (key, FWUPD_RESULT_KEY_URI) == 0) fwupd_remote_set_metadata_uri (self, g_variant_get_string (value, NULL)); if (g_strcmp0 (key, "FilenameCache") == 0) fwupd_remote_set_filename_cache (self, g_variant_get_string (value, NULL)); if (g_strcmp0 (key, "FilenameSource") == 0) fwupd_remote_set_filename_source (self, g_variant_get_string (value, NULL)); if (g_strcmp0 (key, "ReportUri") == 0) fwupd_remote_set_report_uri (self, g_variant_get_string (value, NULL)); } while (g_variant_iter_loop (iter3, "{sv}", &key, &value)) { if (g_strcmp0 (key, "Username") == 0) { fwupd_remote_set_username (self, g_variant_get_string (value, NULL)); } else if (g_strcmp0 (key, "Password") == 0) { fwupd_remote_set_password (self, g_variant_get_string (value, NULL)); } else if (g_strcmp0 (key, "Title") == 0) { fwupd_remote_set_title (self, g_variant_get_string (value, NULL)); } else if (g_strcmp0 (key, "Agreement") == 0) { fwupd_remote_set_agreement (self, g_variant_get_string (value, NULL)); } else if (g_strcmp0 (key, FWUPD_RESULT_KEY_CHECKSUM) == 0) { fwupd_remote_set_checksum (self, g_variant_get_string (value, NULL)); } else if (g_strcmp0 (key, "Enabled") == 0) { priv->enabled = g_variant_get_boolean (value); } else if (g_strcmp0 (key, "ApprovalRequired") == 0) { priv->approval_required = g_variant_get_boolean (value); } else if (g_strcmp0 (key, "Priority") == 0) { priv->priority = g_variant_get_int32 (value); } else if (g_strcmp0 (key, "ModificationTime") == 0) { priv->mtime = g_variant_get_uint64 (value); } else if (g_strcmp0 (key, "FirmwareBaseUri") == 0) { fwupd_remote_set_firmware_base_uri (self, g_variant_get_string (value, NULL)); } } } /** * fwupd_remote_to_variant: * @self: A #FwupdRemote * * Creates a GVariant from the remote data. * * Returns: the GVariant, or %NULL for error * * Since: 1.0.0 **/ GVariant * fwupd_remote_to_variant (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); GVariantBuilder builder; g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); /* create an array with all the metadata in */ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); if (priv->id != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_REMOTE_ID, g_variant_new_string (priv->id)); } if (priv->username != NULL) { g_variant_builder_add (&builder, "{sv}", "Username", g_variant_new_string (priv->username)); } if (priv->password != NULL) { g_variant_builder_add (&builder, "{sv}", "Password", g_variant_new_string (priv->password)); } if (priv->title != NULL) { g_variant_builder_add (&builder, "{sv}", "Title", g_variant_new_string (priv->title)); } if (priv->agreement != NULL) { g_variant_builder_add (&builder, "{sv}", "Agreement", g_variant_new_string (priv->agreement)); } if (priv->checksum != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_CHECKSUM, g_variant_new_string (priv->checksum)); } if (priv->metadata_uri != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_URI, g_variant_new_string (priv->metadata_uri)); } if (priv->report_uri != NULL) { g_variant_builder_add (&builder, "{sv}", "ReportUri", g_variant_new_string (priv->report_uri)); } if (priv->firmware_base_uri != NULL) { g_variant_builder_add (&builder, "{sv}", "FirmwareBaseUri", g_variant_new_string (priv->firmware_base_uri)); } if (priv->priority != 0) { g_variant_builder_add (&builder, "{sv}", "Priority", g_variant_new_int32 (priv->priority)); } if (priv->kind != FWUPD_REMOTE_KIND_UNKNOWN) { g_variant_builder_add (&builder, "{sv}", "Type", g_variant_new_uint32 (priv->kind)); } if (priv->keyring_kind != FWUPD_KEYRING_KIND_UNKNOWN) { g_variant_builder_add (&builder, "{sv}", "Keyring", g_variant_new_uint32 (priv->keyring_kind)); } if (priv->mtime != 0) { g_variant_builder_add (&builder, "{sv}", "ModificationTime", g_variant_new_uint64 (priv->mtime)); } if (priv->filename_cache != NULL) { g_variant_builder_add (&builder, "{sv}", "FilenameCache", g_variant_new_string (priv->filename_cache)); } if (priv->filename_source != NULL) { g_variant_builder_add (&builder, "{sv}", "FilenameSource", g_variant_new_string (priv->filename_source)); } g_variant_builder_add (&builder, "{sv}", "Enabled", g_variant_new_boolean (priv->enabled)); g_variant_builder_add (&builder, "{sv}", "ApprovalRequired", g_variant_new_boolean (priv->approval_required)); return g_variant_new ("a{sv}", &builder); } static void fwupd_remote_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { FwupdRemote *self = FWUPD_REMOTE (obj); FwupdRemotePrivate *priv = GET_PRIVATE (self); switch (prop_id) { case PROP_ENABLED: g_value_set_boolean (value, priv->enabled); break; case PROP_APPROVAL_REQUIRED: g_value_set_boolean (value, priv->approval_required); break; case PROP_ID: g_value_set_string (value, priv->id); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static void fwupd_remote_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { FwupdRemote *self = FWUPD_REMOTE (obj); FwupdRemotePrivate *priv = GET_PRIVATE (self); switch (prop_id) { case PROP_ENABLED: priv->enabled = g_value_get_boolean (value); break; case PROP_APPROVAL_REQUIRED: priv->approval_required = g_value_get_boolean (value); break; case PROP_ID: fwupd_remote_set_id (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static void fwupd_remote_class_init (FwupdRemoteClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fwupd_remote_finalize; object_class->get_property = fwupd_remote_get_property; object_class->set_property = fwupd_remote_set_property; /** * FwupdRemote:id: * * The remote ID. * * Since: 0.9.3 */ pspec = g_param_spec_string ("id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_ID, pspec); /** * FwupdRemote:enabled: * * If the remote is enabled and should be used. * * Since: 0.9.3 */ pspec = g_param_spec_boolean ("enabled", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_ENABLED, pspec); /** * FwupdRemote:approval-required: * * If firmware from the remote should be checked against the system * list of approved firmware. * * Since: 1.2.6 */ pspec = g_param_spec_boolean ("approval-required", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_APPROVAL_REQUIRED, pspec); } static void fwupd_remote_init (FwupdRemote *self) { } static void fwupd_remote_finalize (GObject *obj) { FwupdRemote *self = FWUPD_REMOTE (obj); FwupdRemotePrivate *priv = GET_PRIVATE (self); g_free (priv->id); g_free (priv->metadata_uri); g_free (priv->metadata_uri_sig); g_free (priv->firmware_base_uri); g_free (priv->report_uri); g_free (priv->username); g_free (priv->password); g_free (priv->title); g_free (priv->agreement); g_free (priv->checksum); g_free (priv->filename_cache); g_free (priv->filename_cache_sig); g_free (priv->filename_source); g_strfreev (priv->order_after); g_strfreev (priv->order_before); G_OBJECT_CLASS (fwupd_remote_parent_class)->finalize (obj); } /** * fwupd_remote_from_variant: * @value: a #GVariant * * Creates a new remote using packed data. * * Returns: (transfer full): a new #FwupdRemote, or %NULL if @value was invalid * * Since: 1.0.0 **/ FwupdRemote * fwupd_remote_from_variant (GVariant *value) { FwupdRemote *rel = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; type_string = g_variant_get_type_string (value); if (g_strcmp0 (type_string, "(a{sv})") == 0) { rel = fwupd_remote_new (); g_variant_get (value, "(a{sv})", &iter); fwupd_remote_set_from_variant_iter (rel, iter); fwupd_remote_set_from_variant_iter (rel, iter); } else if (g_strcmp0 (type_string, "a{sv}") == 0) { rel = fwupd_remote_new (); g_variant_get (value, "a{sv}", &iter); fwupd_remote_set_from_variant_iter (rel, iter); } else { g_warning ("type %s not known", type_string); } return rel; } /** * fwupd_remote_array_from_variant: * @value: a #GVariant * * Creates an array of new devices using packed data. * * Returns: (transfer container) (element-type FwupdRemote): remotes, or %NULL if @value was invalid * * Since: 1.2.10 **/ GPtrArray * fwupd_remote_array_from_variant (GVariant *value) { GPtrArray *remotes = NULL; gsize sz; g_autoptr(GVariant) untuple = NULL; remotes = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); untuple = g_variant_get_child_value (value, 0); sz = g_variant_n_children (untuple); for (guint i = 0; i < sz; i++) { g_autoptr(GVariant) data = g_variant_get_child_value (untuple, i); FwupdRemote *remote = fwupd_remote_from_variant (data); g_ptr_array_add (remotes, remote); } return remotes; } /** * fwupd_remote_new: * * Creates a new fwupd remote. * * Returns: a new #FwupdRemote * * Since: 0.9.3 **/ FwupdRemote * fwupd_remote_new (void) { FwupdRemote *self; self = g_object_new (FWUPD_TYPE_REMOTE, NULL); return FWUPD_REMOTE (self); } fwupd-1.2.14/libfwupd/fwupd-remote.h000066400000000000000000000052711402665037500173440ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fwupd-enums.h" G_BEGIN_DECLS #define FWUPD_TYPE_REMOTE (fwupd_remote_get_type ()) G_DECLARE_DERIVABLE_TYPE (FwupdRemote, fwupd_remote, FWUPD, REMOTE, GObject) struct _FwupdRemoteClass { GObjectClass parent_class; /*< private >*/ void (*_fwupd_reserved1) (void); void (*_fwupd_reserved2) (void); void (*_fwupd_reserved3) (void); void (*_fwupd_reserved4) (void); void (*_fwupd_reserved5) (void); void (*_fwupd_reserved6) (void); void (*_fwupd_reserved7) (void); }; /** * FwupdRemoteKind: * @FWUPD_REMOTE_KIND_UNKNOWN: Unknown kind * @FWUPD_REMOTE_KIND_DOWNLOAD: Requires files to be downloaded * @FWUPD_REMOTE_KIND_LOCAL: Reads files from the local machine * @FWUPD_REMOTE_KIND_DIRECTORY: Reads directory from the local machine * * The kind of remote. **/ typedef enum { FWUPD_REMOTE_KIND_UNKNOWN, FWUPD_REMOTE_KIND_DOWNLOAD, FWUPD_REMOTE_KIND_LOCAL, FWUPD_REMOTE_KIND_DIRECTORY, /* Since: 1.2.4 */ /*< private >*/ FWUPD_REMOTE_KIND_LAST } FwupdRemoteKind; FwupdRemoteKind fwupd_remote_kind_from_string (const gchar *kind); const gchar *fwupd_remote_kind_to_string (FwupdRemoteKind kind); FwupdRemote *fwupd_remote_new (void); const gchar *fwupd_remote_get_id (FwupdRemote *self); const gchar *fwupd_remote_get_title (FwupdRemote *self); const gchar *fwupd_remote_get_agreement (FwupdRemote *self); const gchar *fwupd_remote_get_checksum (FwupdRemote *self); const gchar *fwupd_remote_get_username (FwupdRemote *self); const gchar *fwupd_remote_get_password (FwupdRemote *self); const gchar *fwupd_remote_get_filename_cache (FwupdRemote *self); const gchar *fwupd_remote_get_filename_cache_sig (FwupdRemote *self); const gchar *fwupd_remote_get_filename_source (FwupdRemote *self); const gchar *fwupd_remote_get_firmware_base_uri (FwupdRemote *self); const gchar *fwupd_remote_get_report_uri (FwupdRemote *self); const gchar *fwupd_remote_get_metadata_uri (FwupdRemote *self); const gchar *fwupd_remote_get_metadata_uri_sig (FwupdRemote *self); gboolean fwupd_remote_get_enabled (FwupdRemote *self); gboolean fwupd_remote_get_approval_required (FwupdRemote *self); gint fwupd_remote_get_priority (FwupdRemote *self); guint64 fwupd_remote_get_age (FwupdRemote *self); FwupdRemoteKind fwupd_remote_get_kind (FwupdRemote *self); FwupdKeyringKind fwupd_remote_get_keyring_kind (FwupdRemote *self); gchar *fwupd_remote_build_firmware_uri (FwupdRemote *self, const gchar *url, GError **error); FwupdRemote *fwupd_remote_from_variant (GVariant *value); GPtrArray *fwupd_remote_array_from_variant (GVariant *value); G_END_DECLS fwupd-1.2.14/libfwupd/fwupd-self-test.c000066400000000000000000000511771402665037500177600ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fwupd-client.h" #include "fwupd-common.h" #include "fwupd-enums.h" #include "fwupd-error.h" #include "fwupd-device-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" static gboolean fu_test_compare_lines (const gchar *txt1, const gchar *txt2, GError **error) { g_autofree gchar *output = NULL; /* exactly the same */ if (g_strcmp0 (txt1, txt2) == 0) return TRUE; /* matches a pattern */ if (fnmatch (txt2, txt1, FNM_NOESCAPE) == 0) return TRUE; /* save temp files and diff them */ if (!g_file_set_contents ("/tmp/a", txt1, -1, error)) return FALSE; if (!g_file_set_contents ("/tmp/b", txt2, -1, error)) return FALSE; if (!g_spawn_command_line_sync ("diff -urNp /tmp/b /tmp/a", &output, NULL, NULL, error)) return FALSE; /* just output the diff */ g_set_error_literal (error, 1, 0, output); return FALSE; } /* https://gitlab.gnome.org/GNOME/glib/issues/225 */ static guint _g_string_replace (GString *string, const gchar *search, const gchar *replace) { gchar *tmp; guint count = 0; gsize search_idx = 0; gsize replace_len; gsize search_len; g_return_val_if_fail (string != NULL, 0); g_return_val_if_fail (search != NULL, 0); g_return_val_if_fail (replace != NULL, 0); /* nothing to do */ if (string->len == 0) return 0; search_len = strlen (search); replace_len = strlen (replace); do { tmp = g_strstr_len (string->str + search_idx, -1, search); if (tmp == NULL) break; /* advance the counter in case @replace contains @search */ search_idx = (gsize) (tmp - string->str); /* reallocate the string if required */ if (search_len > replace_len) { g_string_erase (string, (gssize) search_idx, (gssize) (search_len - replace_len)); memcpy (tmp, replace, replace_len); } else if (search_len < replace_len) { g_string_insert_len (string, (gssize) search_idx, replace, (gssize) (replace_len - search_len)); /* we have to treat this specially as it could have * been reallocated when the insertion happened */ memcpy (string->str + search_idx, replace, replace_len); } else { /* just memcmp in the new string */ memcpy (tmp, replace, replace_len); } search_idx += replace_len; count++; } while (TRUE); return count; } static void fwupd_enums_func (void) { /* enums */ for (guint i = 0; i < FWUPD_ERROR_LAST; i++) { const gchar *tmp = fwupd_error_to_string (i); g_assert_cmpstr (tmp, !=, NULL); g_assert_cmpint (fwupd_error_from_string (tmp), ==, i); } for (guint i = 0; i < FWUPD_STATUS_LAST; i++) { const gchar *tmp = fwupd_status_to_string (i); g_assert_cmpstr (tmp, !=, NULL); g_assert_cmpint (fwupd_status_from_string (tmp), ==, i); } for (guint i = 0; i < FWUPD_UPDATE_STATE_LAST; i++) { const gchar *tmp = fwupd_update_state_to_string (i); g_assert_cmpstr (tmp, !=, NULL); g_assert_cmpint (fwupd_update_state_from_string (tmp), ==, i); } for (guint i = 0; i < FWUPD_TRUST_FLAG_LAST; i++) { const gchar *tmp = fwupd_trust_flag_to_string (i); g_assert_cmpstr (tmp, !=, NULL); g_assert_cmpint (fwupd_trust_flag_from_string (tmp), ==, i); } for (guint i = 1; i < FWUPD_VERSION_FORMAT_LAST; i++) { const gchar *tmp = fwupd_version_format_to_string (i); g_assert_cmpstr (tmp, !=, NULL); g_assert_cmpint (fwupd_version_format_from_string (tmp), ==, i); } /* bitfield */ for (guint64 i = 1; i < FWUPD_DEVICE_FLAG_UNKNOWN; i *= 2) { const gchar *tmp = fwupd_device_flag_to_string (i); if (tmp == NULL) break; g_assert_cmpint (fwupd_device_flag_from_string (tmp), ==, i); } } static void fwupd_remote_download_func (void) { gboolean ret; g_autofree gchar *fn = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error = NULL; remote = fwupd_remote_new (); fn = g_build_filename (FU_SELF_TEST_REMOTES_DIR, "remotes.d", "lvfs.conf", NULL); ret = fwupd_remote_load_from_filename (remote, fn, NULL, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fwupd_remote_get_kind (remote), ==, FWUPD_REMOTE_KIND_DOWNLOAD); g_assert_cmpint (fwupd_remote_get_keyring_kind (remote), ==, FWUPD_KEYRING_KIND_GPG); g_assert_cmpint (fwupd_remote_get_priority (remote), ==, 0); g_assert (fwupd_remote_get_enabled (remote)); g_assert (fwupd_remote_get_metadata_uri (remote) != NULL); g_assert (fwupd_remote_get_metadata_uri_sig (remote) != NULL); g_assert_cmpstr (fwupd_remote_get_title (remote), ==, "Linux Vendor Firmware Service"); g_assert_cmpstr (fwupd_remote_get_report_uri (remote), ==, "https://fwupd.org/lvfs/firmware/report"); g_assert_cmpstr (fwupd_remote_get_filename_cache (remote), ==, LOCALSTATEDIR "/lib/fwupd/remotes.d/lvfs/metadata.xml.gz"); g_assert_cmpstr (fwupd_remote_get_filename_cache_sig (remote), ==, LOCALSTATEDIR "/lib/fwupd/remotes.d/lvfs/metadata.xml.gz.asc"); } /* verify we used the FirmwareBaseURI just for firmware */ static void fwupd_remote_baseuri_func (void) { gboolean ret; g_autofree gchar *firmware_uri = NULL; g_autofree gchar *fn = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error = NULL; remote = fwupd_remote_new (); fn = g_build_filename (TESTDATADIR, "tests", "firmware-base-uri.conf", NULL); ret = fwupd_remote_load_from_filename (remote, fn, NULL, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fwupd_remote_get_kind (remote), ==, FWUPD_REMOTE_KIND_DOWNLOAD); g_assert_cmpint (fwupd_remote_get_keyring_kind (remote), ==, FWUPD_KEYRING_KIND_GPG); g_assert_cmpint (fwupd_remote_get_priority (remote), ==, 0); g_assert (fwupd_remote_get_enabled (remote)); g_assert_cmpstr (fwupd_remote_get_checksum (remote), ==, NULL); g_assert_cmpstr (fwupd_remote_get_metadata_uri (remote), ==, "https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz"); g_assert_cmpstr (fwupd_remote_get_metadata_uri_sig (remote), ==, "https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz.asc"); firmware_uri = fwupd_remote_build_firmware_uri (remote, "http://bbc.co.uk/firmware.cab", &error); g_assert_no_error (error); g_assert_cmpstr (firmware_uri, ==, "https://my.fancy.cdn/firmware.cab"); } /* verify we used the metadata path for firmware */ static void fwupd_remote_nopath_func (void) { gboolean ret; g_autofree gchar *firmware_uri = NULL; g_autofree gchar *fn = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error = NULL; remote = fwupd_remote_new (); fn = g_build_filename (TESTDATADIR, "tests", "firmware-nopath.conf", NULL); ret = fwupd_remote_load_from_filename (remote, fn, NULL, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fwupd_remote_get_kind (remote), ==, FWUPD_REMOTE_KIND_DOWNLOAD); g_assert_cmpint (fwupd_remote_get_keyring_kind (remote), ==, FWUPD_KEYRING_KIND_GPG); g_assert_cmpint (fwupd_remote_get_priority (remote), ==, 0); g_assert (fwupd_remote_get_enabled (remote)); g_assert_cmpstr (fwupd_remote_get_checksum (remote), ==, NULL); g_assert_cmpstr (fwupd_remote_get_metadata_uri (remote), ==, "https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz"); g_assert_cmpstr (fwupd_remote_get_metadata_uri_sig (remote), ==, "https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz.asc"); firmware_uri = fwupd_remote_build_firmware_uri (remote, "firmware.cab", &error); g_assert_no_error (error); g_assert_cmpstr (firmware_uri, ==, "https://s3.amazonaws.com/lvfsbucket/downloads/firmware.cab"); } static void fwupd_remote_local_func (void) { gboolean ret; g_autofree gchar *fn = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error = NULL; remote = fwupd_remote_new (); fn = g_build_filename (FU_LOCAL_REMOTE_DIR, "dell-esrt.conf", NULL); ret = fwupd_remote_load_from_filename (remote, fn, NULL, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fwupd_remote_get_kind (remote), ==, FWUPD_REMOTE_KIND_LOCAL); g_assert_cmpint (fwupd_remote_get_keyring_kind (remote), ==, FWUPD_KEYRING_KIND_NONE); g_assert (fwupd_remote_get_enabled (remote)); g_assert (fwupd_remote_get_metadata_uri (remote) == NULL); g_assert (fwupd_remote_get_metadata_uri_sig (remote) == NULL); g_assert (fwupd_remote_get_report_uri (remote) == NULL); g_assert_cmpstr (fwupd_remote_get_title (remote), ==, "Enable UEFI capsule updates on Dell systems"); g_assert_cmpstr (fwupd_remote_get_filename_cache (remote), ==, "@datadir@/fwupd/remotes.d/dell-esrt/metadata.xml"); g_assert_cmpstr (fwupd_remote_get_filename_cache_sig (remote), ==, NULL); g_assert_cmpstr (fwupd_remote_get_checksum (remote), ==, NULL); } static void fwupd_release_func (void) { g_autoptr(FwupdRelease) release1 = NULL; g_autoptr(FwupdRelease) release2 = NULL; g_autoptr(GVariant) data = NULL; release1 = fwupd_release_new (); fwupd_release_add_metadata_item (release1, "foo", "bar"); fwupd_release_add_metadata_item (release1, "baz", "bam"); data = fwupd_release_to_variant (release1); release2 = fwupd_release_from_variant (data); g_assert_cmpstr (fwupd_release_get_metadata_item (release2, "foo"), ==, "bar"); g_assert_cmpstr (fwupd_release_get_metadata_item (release2, "baz"), ==, "bam"); } static void fwupd_device_func (void) { gboolean ret; g_autofree gchar *data = NULL; g_autofree gchar *str = NULL; g_autoptr(FwupdDevice) dev = NULL; g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GError) error = NULL; g_autoptr(GString) str_ascii = NULL; g_autoptr(JsonBuilder) builder = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonNode) json_root = NULL; /* create dummy object */ dev = fwupd_device_new (); fwupd_device_add_checksum (dev, "beefdead"); fwupd_device_set_created (dev, 1); fwupd_device_set_flags (dev, FWUPD_DEVICE_FLAG_UPDATABLE); fwupd_device_set_id (dev, "USB:foo"); fwupd_device_set_modified (dev, 60 * 60 * 24); fwupd_device_set_name (dev, "ColorHug2"); fwupd_device_add_guid (dev, "2082b5e0-7a64-478a-b1b2-e3404fab6dad"); fwupd_device_add_guid (dev, "00000000-0000-0000-0000-000000000000"); fwupd_device_add_icon (dev, "input-gaming"); fwupd_device_add_icon (dev, "input-mouse"); fwupd_device_add_flag (dev, FWUPD_DEVICE_FLAG_REQUIRE_AC); rel = fwupd_release_new (); fwupd_release_add_flag (rel, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD); fwupd_release_add_checksum (rel, "deadbeef"); fwupd_release_set_description (rel, "

Hi there!

"); fwupd_release_set_filename (rel, "firmware.bin"); fwupd_release_set_appstream_id (rel, "org.dave.ColorHug.firmware"); fwupd_release_set_size (rel, 1024); fwupd_release_set_uri (rel, "http://foo.com"); fwupd_release_set_version (rel, "1.2.3"); fwupd_device_add_release (dev, rel); str = fwupd_device_to_string (dev); g_print ("\n%s", str); /* check GUIDs */ g_assert (fwupd_device_has_guid (dev, "2082b5e0-7a64-478a-b1b2-e3404fab6dad")); g_assert (fwupd_device_has_guid (dev, "00000000-0000-0000-0000-000000000000")); g_assert (!fwupd_device_has_guid (dev, "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")); /* convert the new non-breaking space back into a normal space: * https://gitlab.gnome.org/GNOME/glib/commit/76af5dabb4a25956a6c41a75c0c7feeee74496da */ str_ascii = g_string_new (str); _g_string_replace (str_ascii, " ", " "); ret = fu_test_compare_lines (str_ascii->str, "ColorHug2\n" " DeviceId: USB:foo\n" " Guid: 2082b5e0-7a64-478a-b1b2-e3404fab6dad\n" " Guid: 00000000-0000-0000-0000-000000000000\n" " Flags: updatable|require-ac\n" " Checksum: SHA1(beefdead)\n" " Icon: input-gaming,input-mouse\n" " Created: 1970-01-01\n" " Modified: 1970-01-02\n" " \n" " [Release]\n" " AppstreamId: org.dave.ColorHug.firmware\n" " Description:

Hi there!

\n" " Version: 1.2.3\n" " Filename: firmware.bin\n" " Checksum: SHA1(deadbeef)\n" " Size: 1.0 kB\n" " Uri: http://foo.com\n" " Flags: trusted-payload\n", &error); g_assert_no_error (error); g_assert (ret); /* export to json */ builder = json_builder_new (); json_builder_begin_object (builder); fwupd_device_to_json (dev, builder); json_builder_end_object (builder); json_root = json_builder_get_root (builder); json_generator = json_generator_new (); json_generator_set_pretty (json_generator, TRUE); json_generator_set_root (json_generator, json_root); data = json_generator_to_data (json_generator, NULL); g_assert_nonnull (data); ret = fu_test_compare_lines (data, "{\n" " \"Name\" : \"ColorHug2\",\n" " \"DeviceId\" : \"USB:foo\",\n" " \"Guid\" : [\n" " \"2082b5e0-7a64-478a-b1b2-e3404fab6dad\",\n" " \"00000000-0000-0000-0000-000000000000\"\n" " ],\n" " \"Flags\" : [\n" " \"updatable\",\n" " \"require-ac\"\n" " ],\n" " \"Checksums\" : [\n" " \"beefdead\"\n" " ],\n" " \"Icons\" : [\n" " \"input-gaming\",\n" " \"input-mouse\"\n" " ],\n" " \"Created\" : 1,\n" " \"Modified\" : 86400,\n" " \"Releases\" : [\n" " {\n" " \"AppstreamId\" : \"org.dave.ColorHug.firmware\",\n" " \"Description\" : \"

Hi there!

\",\n" " \"Version\" : \"1.2.3\",\n" " \"Filename\" : \"firmware.bin\",\n" " \"Checksum\" : [\n" " \"deadbeef\"\n" " ],\n" " \"Size\" : 1024,\n" " \"Uri\" : \"http://foo.com\",\n" " \"Flags\" : [\n" " \"trusted-payload\"\n" " ]\n" " }\n" " ]\n" "}", &error); g_assert_no_error (error); g_assert (ret); } static void fwupd_client_devices_func (void) { FwupdDevice *dev; gboolean ret; g_autoptr(FwupdClient) client = NULL; g_autoptr(GPtrArray) array = NULL; g_autoptr(GError) error = NULL; client = fwupd_client_new (); /* only run if running fwupd is new enough */ ret = fwupd_client_connect (client, NULL, &error); g_assert_no_error (error); g_assert_true (ret); if (fwupd_client_get_daemon_version (client) == NULL) { g_test_skip ("no enabled fwupd daemon"); return; } if (!g_str_has_prefix (fwupd_client_get_daemon_version (client), "1.")) { g_test_skip ("running fwupd is too old"); return; } array = fwupd_client_get_devices (client, NULL, &error); if (array == NULL && g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_test_skip ("no available fwupd devices"); return; } if (array == NULL && g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip ("no available fwupd daemon"); return; } g_assert_no_error (error); g_assert (array != NULL); g_assert_cmpint (array->len, >, 0); /* check device */ dev = g_ptr_array_index (array, 0); g_assert (FWUPD_IS_DEVICE (dev)); g_assert_cmpstr (fwupd_device_get_guid_default (dev), !=, NULL); g_assert_cmpstr (fwupd_device_get_id (dev), !=, NULL); } static void fwupd_client_remotes_func (void) { gboolean ret; g_autoptr(FwupdClient) client = NULL; g_autoptr(FwupdRemote) remote2 = NULL; g_autoptr(FwupdRemote) remote3 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) array = NULL; g_setenv ("FU_SELF_TEST_REMOTES_DIR", FU_SELF_TEST_REMOTES_DIR, TRUE); client = fwupd_client_new (); /* only run if running fwupd is new enough */ ret = fwupd_client_connect (client, NULL, &error); g_assert_no_error (error); g_assert_true (ret); if (fwupd_client_get_daemon_version (client) == NULL) { g_test_skip ("no enabled fwupd daemon"); return; } if (!g_str_has_prefix (fwupd_client_get_daemon_version (client), "1.")) { g_test_skip ("running fwupd is too old"); return; } array = fwupd_client_get_remotes (client, NULL, &error); if (array == NULL && g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_test_skip ("no available fwupd remotes"); return; } if (array == NULL && g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip ("no available fwupd daemon"); return; } g_assert_no_error (error); g_assert (array != NULL); g_assert_cmpint (array->len, >, 0); /* check we can find the right thing */ remote2 = fwupd_client_get_remote_by_id (client, "lvfs", NULL, &error); g_assert_no_error (error); g_assert (remote2 != NULL); g_assert_cmpstr (fwupd_remote_get_id (remote2), ==, "lvfs"); g_assert (fwupd_remote_get_enabled (remote2)); g_assert (fwupd_remote_get_metadata_uri (remote2) != NULL); /* check we set an error when unfound */ remote3 = fwupd_client_get_remote_by_id (client, "XXXX", NULL, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert (remote3 == NULL); } static gboolean fwupd_has_system_bus (void) { g_autoptr(GDBusConnection) conn = NULL; conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); if (conn != NULL) return TRUE; g_debug ("D-Bus system bus unavailable, skipping tests."); return FALSE; } static void fwupd_common_machine_hash_func (void) { gsize sz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *mhash1 = NULL; g_autofree gchar *mhash2 = NULL; g_autoptr(GError) error = NULL; if (!g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS)) { g_test_skip ("Missing /etc/machine-id"); return; } if (!g_file_get_contents ("/etc/machine-id", &buf, &sz, &error)) { g_test_skip ("/etc/machine-id is unreadable"); return; } if (sz == 0) { g_test_skip ("Empty /etc/machine-id"); return; } mhash1 = fwupd_build_machine_id ("salt1", &error); g_assert_no_error (error); g_assert_cmpstr (mhash1, !=, NULL); mhash2 = fwupd_build_machine_id ("salt2", &error); g_assert_no_error (error); g_assert_cmpstr (mhash2, !=, NULL); g_assert_cmpstr (mhash2, !=, mhash1); } static void fwupd_common_guid_func (void) { g_autofree gchar *guid1 = NULL; g_autofree gchar *guid2 = NULL; g_autofree gchar *guid_be = NULL; g_autofree gchar *guid_me = NULL; fwupd_guid_t buf = { 0x0 }; gboolean ret; g_autoptr(GError) error = NULL; /* invalid */ g_assert (!fwupd_guid_is_valid (NULL)); g_assert (!fwupd_guid_is_valid ("")); g_assert (!fwupd_guid_is_valid ("1ff60ab2-3905-06a1-b476")); g_assert (!fwupd_guid_is_valid ("1ff60ab2-XXXX-XXXX-XXXX-0371f00c9e9b")); g_assert (!fwupd_guid_is_valid (" 1ff60ab2-3905-06a1-b476-0371f00c9e9b")); g_assert (!fwupd_guid_is_valid ("00000000-0000-0000-0000-000000000000")); /* valid */ g_assert (fwupd_guid_is_valid ("1ff60ab2-3905-06a1-b476-0371f00c9e9b")); /* make valid */ guid1 = fwupd_guid_hash_string ("python.org"); g_assert_cmpstr (guid1, ==, "886313e1-3b8a-5372-9b90-0c9aee199e5d"); guid2 = fwupd_guid_hash_string ("8086:0406"); g_assert_cmpstr (guid2, ==, "1fbd1f2c-80f4-5d7c-a6ad-35c7b9bd5486"); /* round-trip BE */ ret = fwupd_guid_from_string ("00112233-4455-6677-8899-aabbccddeeff", &buf, FWUPD_GUID_FLAG_NONE, &error); g_assert_true (ret); g_assert_no_error (error); g_assert (memcmp (buf, "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff", sizeof(buf)) == 0); guid_be = fwupd_guid_to_string ((const fwupd_guid_t *) &buf, FWUPD_GUID_FLAG_NONE); g_assert_cmpstr (guid_be, ==, "00112233-4455-6677-8899-aabbccddeeff"); /* round-trip mixed encoding */ ret = fwupd_guid_from_string ("00112233-4455-6677-8899-aabbccddeeff", &buf, FWUPD_GUID_FLAG_MIXED_ENDIAN, &error); g_assert_true (ret); g_assert_no_error (error); g_assert (memcmp (buf, "\x33\x22\x11\x00\x55\x44\x77\x66\x88\x99\xaa\xbb\xcc\xdd\xee\xff", sizeof(buf)) == 0); guid_me = fwupd_guid_to_string ((const fwupd_guid_t *) &buf, FWUPD_GUID_FLAG_MIXED_ENDIAN); g_assert_cmpstr (guid_me, ==, "00112233-4455-6677-8899-aabbccddeeff"); /* check failure */ g_assert_false (fwupd_guid_from_string ("001122334455-6677-8899-aabbccddeeff", NULL, 0, NULL)); g_assert_false (fwupd_guid_from_string ("0112233-4455-6677-8899-aabbccddeeff", NULL, 0, NULL)); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func ("/fwupd/enums", fwupd_enums_func); g_test_add_func ("/fwupd/common{machine-hash}", fwupd_common_machine_hash_func); g_test_add_func ("/fwupd/common{guid}", fwupd_common_guid_func); g_test_add_func ("/fwupd/release", fwupd_release_func); g_test_add_func ("/fwupd/device", fwupd_device_func); g_test_add_func ("/fwupd/remote{download}", fwupd_remote_download_func); g_test_add_func ("/fwupd/remote{base-uri}", fwupd_remote_baseuri_func); g_test_add_func ("/fwupd/remote{no-path}", fwupd_remote_nopath_func); g_test_add_func ("/fwupd/remote{local}", fwupd_remote_local_func); if (fwupd_has_system_bus ()) { g_test_add_func ("/fwupd/client{remotes}", fwupd_client_remotes_func); g_test_add_func ("/fwupd/client{devices}", fwupd_client_devices_func); } return g_test_run (); } fwupd-1.2.14/libfwupd/fwupd-version.h.in000066400000000000000000000026521402665037500201430ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once /** * SECTION:fwupd-version * @short_description: Obtains the version for the installed fwupd * * These compile time macros allow the user to enable parts of client code * depending on the version of libfwupd installed. */ #if !defined (__FWUPD_H_INSIDE__) && !defined (FWUPD_COMPILATION) #error "Only can be included directly." #endif /** * FWUPD_MAJOR_VERSION: * * The compile-time major version */ #ifndef FWUPD_MAJOR_VERSION #define FWUPD_MAJOR_VERSION (@FWUPD_MAJOR_VERSION@) #endif /** * FWUPD_MINOR_VERSION: * * The compile-time minor version */ #ifndef FWUPD_MINOR_VERSION #define FWUPD_MINOR_VERSION (@FWUPD_MINOR_VERSION@) #endif /** * FWUPD_MICRO_VERSION: * * The compile-time micro version */ #ifndef FWUPD_MICRO_VERSION #define FWUPD_MICRO_VERSION (@FWUPD_MICRO_VERSION@) #endif /** * FWUPD_CHECK_VERSION: * @major: Major version number * @minor: Minor version number * @micro: Micro version number * * Check whether a fwupd version equal to or greater than * major.minor.micro. */ #define FWUPD_CHECK_VERSION(major,minor,micro) \ (FWUPD_MAJOR_VERSION > (major) || \ (FWUPD_MAJOR_VERSION == (major) && FWUPD_MINOR_VERSION > (minor)) || \ (FWUPD_MAJOR_VERSION == (major) && FWUPD_MINOR_VERSION == (minor) && \ FWUPD_MICRO_VERSION >= (micro))) fwupd-1.2.14/libfwupd/fwupd.h000066400000000000000000000011551402665037500160500ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once /** * SECTION:fwupd * @short_description: Helper objects for accessing fwupd */ #define __FWUPD_H_INSIDE__ #include #include #include #include #include #include #include #include #ifndef FWUPD_DISABLE_DEPRECATED #include #endif #undef __FWUPD_H_INSIDE__ fwupd-1.2.14/libfwupd/fwupd.map000066400000000000000000000211401402665037500163720ustar00rootroot00000000000000# generated automatically, do not edit! LIBFWUPD_0.1.1 { global: fwupd_error_quark; fwupd_status_from_string; fwupd_status_to_string; local: *; }; LIBFWUPD_0.7.0 { global: fwupd_client_clear_results; fwupd_client_get_results; fwupd_client_get_type; fwupd_client_install; fwupd_client_new; fwupd_client_unlock; fwupd_client_verify; fwupd_device_flag_from_string; fwupd_device_flag_to_string; fwupd_error_from_string; fwupd_error_to_string; fwupd_trust_flag_from_string; fwupd_trust_flag_to_string; fwupd_update_state_from_string; fwupd_update_state_to_string; local: *; } LIBFWUPD_0.1.1; LIBFWUPD_0.7.1 { global: fwupd_client_connect; local: *; } LIBFWUPD_0.7.0; LIBFWUPD_0.7.3 { global: fwupd_client_get_percentage; fwupd_client_get_status; local: *; } LIBFWUPD_0.7.1; LIBFWUPD_0.8.0 { global: fwupd_client_verify_update; local: *; } LIBFWUPD_0.7.3; LIBFWUPD_0.9.2 { global: fwupd_client_get_devices; local: *; } LIBFWUPD_0.8.0; LIBFWUPD_0.9.3 { global: fwupd_checksum_format_for_display; fwupd_checksum_guess_kind; fwupd_client_get_device_by_id; fwupd_client_get_releases; fwupd_client_get_remote_by_id; fwupd_client_get_remotes; fwupd_device_add_checksum; fwupd_device_add_flag; fwupd_device_add_guid; fwupd_device_get_checksums; fwupd_device_get_created; fwupd_device_get_description; fwupd_device_get_flags; fwupd_device_get_flashes_left; fwupd_device_get_guid_default; fwupd_device_get_guids; fwupd_device_get_id; fwupd_device_get_modified; fwupd_device_get_name; fwupd_device_get_summary; fwupd_device_get_type; fwupd_device_get_vendor; fwupd_device_get_version; fwupd_device_get_version_bootloader; fwupd_device_get_version_lowest; fwupd_device_has_flag; fwupd_device_has_guid; fwupd_device_new; fwupd_device_remove_flag; fwupd_device_set_created; fwupd_device_set_description; fwupd_device_set_flags; fwupd_device_set_flashes_left; fwupd_device_set_id; fwupd_device_set_modified; fwupd_device_set_name; fwupd_device_set_summary; fwupd_device_set_vendor; fwupd_device_set_version; fwupd_device_set_version_bootloader; fwupd_device_set_version_lowest; fwupd_device_to_string; fwupd_release_add_checksum; fwupd_release_get_appstream_id; fwupd_release_get_checksums; fwupd_release_get_description; fwupd_release_get_filename; fwupd_release_get_homepage; fwupd_release_get_license; fwupd_release_get_name; fwupd_release_get_remote_id; fwupd_release_get_size; fwupd_release_get_summary; fwupd_release_get_type; fwupd_release_get_uri; fwupd_release_get_vendor; fwupd_release_get_version; fwupd_release_new; fwupd_release_set_appstream_id; fwupd_release_set_description; fwupd_release_set_filename; fwupd_release_set_homepage; fwupd_release_set_license; fwupd_release_set_name; fwupd_release_set_remote_id; fwupd_release_set_size; fwupd_release_set_summary; fwupd_release_set_uri; fwupd_release_set_vendor; fwupd_release_set_version; fwupd_release_to_string; fwupd_remote_get_enabled; fwupd_remote_get_id; fwupd_remote_get_type; fwupd_remote_load_from_filename; fwupd_remote_new; local: *; } LIBFWUPD_0.9.2; LIBFWUPD_0.9.4 { global: fwupd_checksum_get_best; fwupd_checksum_get_by_kind; fwupd_device_get_vendor_id; fwupd_device_set_vendor_id; local: *; } LIBFWUPD_0.9.3; LIBFWUPD_0.9.5 { global: fwupd_remote_get_age; fwupd_remote_get_order_after; fwupd_remote_get_order_before; fwupd_remote_get_password; fwupd_remote_get_priority; fwupd_remote_get_username; fwupd_remote_set_mtime; fwupd_remote_set_priority; local: *; } LIBFWUPD_0.9.4; LIBFWUPD_0.9.6 { global: fwupd_client_get_daemon_version; fwupd_remote_get_filename_cache; fwupd_remote_get_kind; fwupd_remote_kind_from_string; fwupd_remote_kind_to_string; local: *; } LIBFWUPD_0.9.5; LIBFWUPD_0.9.7 { global: fwupd_keyring_kind_from_string; fwupd_keyring_kind_to_string; fwupd_remote_build_firmware_uri; fwupd_remote_get_filename_cache_sig; fwupd_remote_get_firmware_base_uri; fwupd_remote_get_keyring_kind; fwupd_remote_get_metadata_uri; fwupd_remote_get_metadata_uri_sig; local: *; } LIBFWUPD_0.9.6; LIBFWUPD_0.9.8 { global: fwupd_client_get_downgrades; fwupd_client_get_upgrades; fwupd_client_modify_remote; fwupd_device_add_icon; fwupd_device_add_release; fwupd_device_get_icons; fwupd_device_get_release_default; fwupd_device_get_releases; fwupd_device_get_update_error; fwupd_device_get_update_state; fwupd_device_set_update_error; fwupd_device_set_update_state; fwupd_release_get_trust_flags; fwupd_release_set_trust_flags; fwupd_remote_get_filename_source; fwupd_remote_get_title; local: *; } LIBFWUPD_0.9.7; LIBFWUPD_1.0.0 { global: fwupd_client_get_details; fwupd_client_update_metadata; fwupd_device_from_variant; fwupd_device_get_plugin; fwupd_device_set_plugin; fwupd_device_to_variant; fwupd_release_from_variant; fwupd_release_to_variant; fwupd_remote_from_variant; fwupd_remote_get_checksum; fwupd_remote_to_variant; local: *; } LIBFWUPD_0.9.8; LIBFWUPD_1.0.3 { global: fwupd_build_user_agent; local: *; } LIBFWUPD_1.0.0; LIBFWUPD_1.0.4 { global: fwupd_build_history_report_json; fwupd_build_machine_id; fwupd_client_get_history; fwupd_client_modify_device; fwupd_release_add_metadata; fwupd_release_add_metadata_item; fwupd_release_get_metadata; fwupd_release_get_metadata_item; fwupd_remote_get_report_uri; local: *; } LIBFWUPD_1.0.3; LIBFWUPD_1.0.7 { global: fwupd_get_os_release; fwupd_remote_get_agreement; fwupd_remote_set_agreement; local: *; } LIBFWUPD_1.0.4; LIBFWUPD_1.0.8 { global: fwupd_device_get_parent; fwupd_device_get_parent_id; fwupd_device_set_parent; fwupd_device_set_parent_id; local: *; } LIBFWUPD_1.0.7; LIBFWUPD_1.1.0 { global: fwupd_device_incorporate; local: *; } LIBFWUPD_1.0.8; LIBFWUPD_1.1.1 { global: fwupd_device_compare; local: *; } LIBFWUPD_1.1.0; LIBFWUPD_1.1.2 { global: fwupd_device_get_serial; fwupd_device_set_serial; fwupd_device_to_variant_full; local: *; } LIBFWUPD_1.1.1; LIBFWUPD_1.1.3 { global: fwupd_device_get_install_duration; fwupd_device_set_install_duration; local: *; } LIBFWUPD_1.1.2; LIBFWUPD_1.2.1 { global: fwupd_release_get_install_duration; fwupd_release_set_install_duration; local: *; } LIBFWUPD_1.1.3; LIBFWUPD_1.2.2 { global: fwupd_release_get_protocol; fwupd_release_set_protocol; local: *; } LIBFWUPD_1.2.1; LIBFWUPD_1.2.4 { global: fwupd_client_get_tainted; fwupd_device_get_update_message; fwupd_device_set_update_message; fwupd_release_get_details_url; fwupd_release_get_source_url; fwupd_release_get_update_message; fwupd_release_set_details_url; fwupd_release_set_source_url; fwupd_release_set_update_message; local: *; } LIBFWUPD_1.2.2; LIBFWUPD_1.2.5 { global: fwupd_device_add_instance_id; fwupd_device_get_instance_ids; fwupd_device_has_instance_id; fwupd_guid_from_string; fwupd_guid_hash_data; fwupd_guid_hash_string; fwupd_guid_is_valid; fwupd_guid_to_string; local: *; } LIBFWUPD_1.2.4; LIBFWUPD_1.2.6 { global: fwupd_client_activate; fwupd_client_get_approved_firmware; fwupd_client_self_sign; fwupd_client_set_approved_firmware; fwupd_device_to_json; fwupd_release_add_flag; fwupd_release_flag_from_string; fwupd_release_flag_to_string; fwupd_release_get_flags; fwupd_release_has_checksum; fwupd_release_has_flag; fwupd_release_remove_flag; fwupd_release_set_flags; fwupd_release_to_json; fwupd_remote_get_approval_required; local: *; } LIBFWUPD_1.2.5; LIBFWUPD_1.2.7 { global: fwupd_release_add_category; fwupd_release_get_categories; fwupd_release_has_category; local: *; } LIBFWUPD_1.2.6; LIBFWUPD_1.2.8 { global: fwupd_client_modify_config; local: *; } LIBFWUPD_1.2.7; LIBFWUPD_1.2.9 { global: fwupd_device_get_version_format; fwupd_device_set_version_format; fwupd_version_format_from_string; fwupd_version_format_to_string; local: *; } LIBFWUPD_1.2.8; LIBFWUPD_1.2.10 { global: fwupd_device_array_from_variant; fwupd_release_array_from_variant; fwupd_remote_array_from_variant; local: *; } LIBFWUPD_1.2.9; fwupd-1.2.14/libfwupd/generate-version-script.py000077500000000000000000000072221402665037500217070ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2017 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import sys import xml.etree.ElementTree as ET from pkg_resources import parse_version XMLNS = '{http://www.gtk.org/introspection/core/1.0}' XMLNS_C = '{http://www.gtk.org/introspection/c/1.0}' def usage(return_code): """ print usage and exit with the supplied return code """ if return_code == 0: out = sys.stdout else: out = sys.stderr out.write("usage: %s \n" % sys.argv[0]) sys.exit(return_code) class LdVersionScript: """ Rasterize some text """ def __init__(self, library_name): self.library_name = library_name self.releases = {} def _add_node(self, node): identifier = node.attrib[XMLNS_C + 'identifier'] if 'version' not in node.attrib: print('No version for', identifier) sys.exit(1) version = node.attrib['version'] if version not in self.releases: self.releases[version] = [] release = self.releases[version] release.append(identifier) return version def _add_cls(self, cls): # add all class functions for node in cls.findall(XMLNS + 'function'): self._add_node(node) # add the constructor for node in cls.findall(XMLNS + 'constructor'): self._add_node(node) # choose the lowest version method for the _get_type symbol version_lowest = None if '{http://www.gtk.org/introspection/glib/1.0}get-type' not in cls.attrib: return type_name = cls.attrib['{http://www.gtk.org/introspection/glib/1.0}get-type'] # add all class methods for node in cls.findall(XMLNS + 'method'): version_tmp = self._add_node(node) if version_tmp: if not version_lowest or version_tmp < version_lowest: version_lowest = version_tmp # finally add the get_type symbol if version_lowest: self.releases[version_lowest].append(type_name) def import_gir(self, filename): tree = ET.parse(filename) root = tree.getroot() for ns in root.findall(XMLNS + 'namespace'): for node in ns.findall(XMLNS + 'function'): self._add_node(node) for cls in ns.findall(XMLNS + 'record'): self._add_cls(cls) for cls in ns.findall(XMLNS + 'class'): self._add_cls(cls) def render(self): # get a sorted list of all the versions versions = [] for version in self.releases: versions.append(version) # output the version data to a file verout = '# generated automatically, do not edit!\n' oldversion = None for version in sorted(versions, key=parse_version): symbols = sorted(self.releases[version]) verout += '\n%s_%s {\n' % (self.library_name, version) verout += ' global:\n' for symbol in symbols: verout += ' %s;\n' % symbol verout += ' local: *;\n' if oldversion: verout += '} %s_%s;\n' % (self.library_name, oldversion) else: verout += '};\n' oldversion = version return verout if __name__ == '__main__': if {'-?', '--help', '--usage'}.intersection(set(sys.argv)): usage(0) if len(sys.argv) != 4: usage(1) ld = LdVersionScript(library_name=sys.argv[1]) ld.import_gir(sys.argv[2]) open(sys.argv[3], 'w').write(ld.render()) fwupd-1.2.14/libfwupd/meson.build000066400000000000000000000075471402665037500167270ustar00rootroot00000000000000cargs = [ '-DG_LOG_DOMAIN="Fwupd"', ] fwupd_version_h = configure_file( input : 'fwupd-version.h.in', output : 'fwupd-version.h', configuration : conf ) install_headers( 'fwupd.h', subdir : 'fwupd-1', ) install_headers([ 'fwupd-client.h', 'fwupd-common.h', 'fwupd-deprecated.h', 'fwupd-device.h', 'fwupd-enums.h', 'fwupd-error.h', 'fwupd-remote.h', 'fwupd-release.h', fwupd_version_h, ], subdir : 'fwupd-1/libfwupd', ) mapfile = 'fwupd.map' vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) fwupd = shared_library( 'fwupd', sources : [ 'fwupd-client.c', 'fwupd-common.c', 'fwupd-device.c', 'fwupd-enums.c', 'fwupd-error.c', 'fwupd-release.c', 'fwupd-remote.c', ], soversion : lt_current, version : lt_version, dependencies : [ giounix, soup, libjsonglib, ], c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], include_directories : include_directories('..'), link_args : vflag, link_depends : mapfile, install : true ) pkgg = import('pkgconfig') pkgg.generate( libraries : fwupd, requires : [ 'gio-2.0' ], subdirs : 'fwupd-1', version : meson.project_version(), name : 'fwupd', filebase : 'fwupd', description : 'fwupd is a system daemon for installing device firmware', ) if get_option('introspection') gir = gnome.generate_gir(fwupd, sources : [ 'fwupd-client.c', 'fwupd-client.h', 'fwupd-common.c', 'fwupd-common.h', 'fwupd-common-private.h', 'fwupd-device.c', 'fwupd-device.h', 'fwupd-device-private.h', 'fwupd-enums.c', 'fwupd-enums.h', 'fwupd-enums-private.h', 'fwupd-error.c', 'fwupd-error.h', 'fwupd-release.c', 'fwupd-release.h', 'fwupd-release-private.h', 'fwupd-remote.c', 'fwupd-remote.h', 'fwupd-remote-private.h', ], nsversion : '2.0', namespace : 'Fwupd', symbol_prefix : 'fwupd', identifier_prefix : 'Fwupd', export_packages : 'fwupd', header : 'fwupd.h', dependencies : [ giounix, soup, ], includes : [ 'Gio-2.0', 'GObject-2.0', 'Soup-2.4', ], install : true ) gnome.generate_vapi('fwupd', sources : gir[0], packages : ['gio-2.0', 'libsoup-2.4'], install : true, ) # Verify the map file is correct -- note we can't actually use the generated # file for two reasons: # # 1. We don't hard depend on GObject Introspection # 2. The map file is required to build the lib that the GIR is built from # # To avoid the circular dep, and to ensure we don't change exported API # accidentally actually check in a version of the version script to git. mapfile_target = custom_target('mapfile', input: gir[0], output: 'fwupd.map', command: [ join_paths(meson.current_source_dir(), 'generate-version-script.py'), 'LIBFWUPD', '@INPUT@', '@OUTPUT@', ], ) diffcmd = find_program('diff') test('fwupd-exported-api', diffcmd, args : [ '-urNp', join_paths(meson.current_source_dir(), 'fwupd.map'), mapfile_target, ], ) endif if get_option('tests') testdatadir = join_paths(meson.source_root(), 'data') localremotetestdir = join_paths(meson.source_root(), 'plugins', 'dell-esrt') e = executable( 'fwupd-self-test', sources : [ 'fwupd-self-test.c' ], include_directories : [ include_directories('..'), ], dependencies : [ gio, soup, libjsonglib, ], link_with : fwupd, c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', '-DTESTDATADIR="' + testdatadir + '"', '-DFU_SELF_TEST_REMOTES_DIR="' + testdatadir + '"', '-DFU_LOCAL_REMOTE_DIR="' + localremotetestdir + '"', ], ) test('fwupd-self-test', e) endif fwupd-1.2.14/meson.build000066400000000000000000000241641402665037500151050ustar00rootroot00000000000000project('fwupd', 'c', version : '1.2.14', license : 'LGPL-2.1+', meson_version : '>=0.47.0', default_options : ['warning_level=2', 'c_std=c99'], ) fwupd_version = meson.project_version() varr = fwupd_version.split('.') fwupd_major_version = varr[0] fwupd_minor_version = varr[1] fwupd_micro_version = varr[2] conf = configuration_data() conf.set('FWUPD_MAJOR_VERSION', fwupd_major_version) conf.set('FWUPD_MINOR_VERSION', fwupd_minor_version) conf.set('FWUPD_MICRO_VERSION', fwupd_micro_version) conf.set_quoted('PACKAGE_VERSION', fwupd_version) archiver = find_program('git', required : false) if archiver.found() result = run_command('git', 'describe') if result.returncode() == 0 describe = result.stdout().strip() conf.set_quoted('FWUPD_GIT_DESCRIBE', describe) endif endif # libtool versioning - this applies to libfwupd # # See http://sources.redhat.com/autobook/autobook/autobook_91.html#SEC91 for details # # - If interfaces have been changed or added, but binary compatibility # has been preserved, change: # CURRENT += 1 # REVISION = 0 # AGE += 1 # - If binary compatibility has been broken (eg removed or changed # interfaces), change: # CURRENT += 1 # REVISION = 0 # AGE = 0 # - If the interface is the same as the previous version, but bugs are # fixed, change: # REVISION += 1 lt_current = '2' lt_revision = '0' lt_age = '0' lt_version = '@0@.@1@.@2@'.format(lt_current, lt_age, lt_revision) # get supported warning flags warning_flags = [ '-fstack-protector-strong', '-Waggregate-return', '-Wunused', '-Warray-bounds', '-Wcast-align', '-Wclobbered', '-Wdeclaration-after-statement', '-Wduplicated-branches', '-Wduplicated-cond', '-Wempty-body', '-Wformat=2', '-Wformat-nonliteral', '-Wformat-security', '-Wformat-signedness', '-Wignored-qualifiers', '-Wimplicit-function-declaration', '-Wincompatible-pointer-types-discards-qualifiers', '-Winit-self', '-Wlogical-op', '-Wmissing-declarations', '-Wmissing-format-attribute', '-Wmissing-include-dirs', '-Wmissing-noreturn', '-Wmissing-parameter-type', '-Wmissing-prototypes', '-Wnested-externs', '-Wno-cast-function-type', '-Wno-address-of-packed-member', # incompatible with g_autoptr() '-Wno-unknown-pragmas', '-Wno-deprecated-declarations', '-Wno-discarded-qualifiers', '-Wno-missing-field-initializers', '-Wno-strict-aliasing', '-Wno-suggest-attribute=format', '-Wno-unused-parameter', '-Wnull-dereference', '-Wold-style-definition', '-Woverride-init', '-Wpointer-arith', '-Wredundant-decls', '-Wreturn-type', '-Wshadow', '-Wsign-compare', '-Wstrict-aliasing', '-Wstrict-prototypes', '-Wswitch-default', '-Wtype-limits', '-Wundef', '-Wuninitialized', '-Wunused-but-set-variable', '-Wunused-variable', '-Wvla', '-Wwrite-strings' ] cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments(warning_flags), language : 'c') # enable full RELRO where possible # FIXME: until https://github.com/mesonbuild/meson/issues/1140 is fixed global_link_args = [] test_link_args = [ '-Wl,-z,relro', '-Wl,-z,defs', '-Wl,-z,now', ] foreach arg: test_link_args if cc.has_link_argument(arg) global_link_args += arg endif endforeach add_global_link_arguments( global_link_args, language: 'c' ) # Needed for realpath(), syscall(), cfmakeraw(), etc. add_project_arguments('-D_DEFAULT_SOURCE', language : 'c') # do not use deprecated symbols or defines internally add_project_arguments('-DFWUPD_DISABLE_DEPRECATED', language : 'c') # needed for symlink() and BYTE_ORDER add_project_arguments('-D_BSD_SOURCE', language : 'c') add_project_arguments('-D_XOPEN_SOURCE=700', language : 'c') prefix = get_option('prefix') bindir = join_paths(prefix, get_option('bindir')) libdir = join_paths(prefix, get_option('libdir')) datadir = join_paths(prefix, get_option('datadir')) libexecdir = join_paths(prefix, get_option('libexecdir')) sysconfdir = join_paths(prefix, get_option('sysconfdir')) localstatedir = join_paths(prefix, get_option('localstatedir')) mandir = join_paths(prefix, get_option('mandir')) localedir = join_paths(prefix, get_option('localedir')) gio = dependency('gio-2.0', version : '>= 2.45.8') if gio.version().version_compare ('>= 2.55.0') conf.set('HAVE_GIO_2_55_0', '1') endif gmodule = dependency('gmodule-2.0') giounix = dependency('gio-unix-2.0', version : '>= 2.45.8') gudev = dependency('gudev-1.0') if gudev.version().version_compare('>= 232') conf.set('HAVE_GUDEV_232', '1') endif libxmlb = dependency('xmlb', version : '>= 0.1.7', fallback : ['libxmlb', 'libxmlb_dep']) gusb = dependency('gusb', version : '>= 0.2.9') sqlite = dependency('sqlite3') libarchive = dependency('libarchive') libjsonglib = dependency('json-glib-1.0', version : '>= 1.1.1') valgrind = dependency('valgrind', required: false) soup = dependency('libsoup-2.4', version : '>= 2.51.92') if get_option('daemon') polkit = dependency('polkit-gobject-1', version : '>= 0.103') if polkit.version().version_compare('>= 0.114') conf.set('HAVE_POLKIT_0_114', '1') endif conf.set_quoted ('POLKIT_ACTIONDIR', polkit.get_pkgconfig_variable('actiondir')) udevdir = get_option('udevdir') if udevdir == '' udev = dependency('udev') udevdir = udev.get_pkgconfig_variable('udevdir') endif endif if get_option('pkcs7') gnutls = dependency('gnutls', version : '>= 3.4.4.1') if gnutls.version().version_compare('>= 3.6.0') conf.set('HAVE_GNUTLS_3_6_0', '1') endif conf.set('ENABLE_PKCS7', '1') endif if get_option('gpg') gpgme = cc.find_library('gpgme') gpgerror = cc.find_library('gpg-error') conf.set('ENABLE_GPG', '1') endif libm = cc.find_library('m', required: false) libgcab = dependency('libgcab-1.0') if libgcab.version().version_compare('>= 0.8') conf.set('HAVE_GCAB_0_8', '1') endif if libgcab.version().version_compare('>= 1.0') conf.set('HAVE_GCAB_1_0', '1') endif gcab = find_program('gcab', required : true) bashcomp = dependency('bash-completion', required: false) python3 = find_program('python3') if valgrind.found() conf.set('HAVE_VALGRIND', '1') endif if get_option('plugin_redfish') efivar = dependency('efivar') endif if get_option('plugin_altos') libelf = dependency('libelf') endif if get_option('plugin_uefi') cairo = dependency('cairo') fontconfig = cc.find_library('fontconfig') freetype = cc.find_library('freetype') efivar = dependency('efivar', version : '>= 33') conf.set_quoted('EFIVAR_LIBRARY_VERSION', efivar.version()) efiboot = dependency('efiboot') objcopy = find_program ('objcopy') readelf = find_program ('readelf') genpeimg = find_program ('genpeimg', required: false) efi_app_location = join_paths(libexecdir, 'fwupd', 'efi') conf.set_quoted ('EFI_APP_LOCATION', efi_app_location) conf.set('EFI_OBJCOPY', get_option('efi-objcopy')) efi_arch = host_machine.cpu_family() if efi_arch == 'x86' EFI_MACHINE_TYPE_NAME = 'ia32' gnu_efi_arch = 'ia32' elif efi_arch == 'x86_64' EFI_MACHINE_TYPE_NAME = 'x64' gnu_efi_arch = 'x86_64' elif efi_arch == 'arm' EFI_MACHINE_TYPE_NAME = 'arm' gnu_efi_arch = 'arm' elif efi_arch == 'aarch64' EFI_MACHINE_TYPE_NAME = 'aa64' gnu_efi_arch = 'aarch64' else EFI_MACHINE_TYPE_NAME = '' gnu_efi_arch = '' endif conf.set_quoted('EFI_MACHINE_TYPE_NAME', EFI_MACHINE_TYPE_NAME) r = run_command([python3, 'po/test-deps']) if r.returncode() != 0 error(r.stderr()) endif endif if get_option('plugin_dell') libsmbios_c = dependency('libsmbios_c', version : '>= 2.4.0') efivar = dependency('efivar') conf.set('HAVE_DELL', '1') if not get_option('plugin_uefi') error('plugin_dell also needs plugin_uefi to work') endif endif if get_option('plugin_modem_manager') libmm_glib = dependency('mm-glib', version : '>= 1.10.0') add_project_arguments('-DMM_REQUIRED_VERSION="1.10.0"', language : 'c') libqmi_glib = dependency('qmi-glib', version : '>= 1.22.0') add_project_arguments('-DQMI_REQUIRED_VERSION="1.23.1"', language : 'c') endif if get_option('plugin_nvme') if not cc.has_header('linux/nvme_ioctl.h') error('NVMe support requires kernel >= 4.4') endif endif if get_option('plugin_synaptics') conf.set('HAVE_SYNAPTICS', '1') endif if get_option('plugin_thunderbolt') umockdev = dependency('umockdev-1.0', required: false) conf.set('HAVE_THUNDERBOLT', '1') endif if get_option('plugin_flashrom') libflashrom = dependency('flashrom', fallback : ['flashrom', 'flashrom_dep']) endif if get_option('systemd') systemd = dependency('systemd', version : '>= 211') conf.set('HAVE_SYSTEMD' , '1') conf.set('HAVE_LOGIND' , '1') endif if get_option('elogind') elogind = dependency('libelogind', version : '>= 211') conf.set('HAVE_LOGIND' , '1') endif if get_option('consolekit') conf.set('HAVE_CONSOLEKIT' , '1') endif systemdunitdir = get_option('systemdunitdir') if systemdunitdir == '' and get_option('systemd') systemdunitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir') endif gnome = import('gnome') i18n = import('i18n') plugin_dir = join_paths(libdir, 'fwupd-plugins-3') conf.set_quoted('BINDIR', bindir) conf.set_quoted('LIBEXECDIR', libexecdir) conf.set_quoted('DATADIR', datadir) conf.set_quoted('LOCALSTATEDIR', localstatedir) conf.set_quoted('SYSCONFDIR', sysconfdir) conf.set_quoted('PLUGINDIR', plugin_dir) conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) conf.set_quoted('PACKAGE_NAME', meson.project_name()) conf.set_quoted('VERSION', meson.project_version()) conf.set_quoted('LOCALEDIR', localedir) configure_file( output : 'config.h', configuration : conf ) plugin_deps = [] plugin_deps += libxmlb plugin_deps += gio plugin_deps += giounix plugin_deps += gmodule plugin_deps += gusb plugin_deps += soup plugin_deps += libarchive plugin_deps += gudev subdir('data') if get_option('gtkdoc') gtkdocscan = find_program('gtkdoc-scan', required : true) subdir('docs') endif subdir('libfwupd') subdir('po') if get_option('daemon') subdir('policy') endif subdir('src') subdir('plugins') subdir('contrib') if get_option('systemd') and get_option('daemon') meson.add_install_script('meson_post_install.sh', systemdunitdir, localstatedir) endif fwupd-1.2.14/meson_options.txt000066400000000000000000000075411402665037500164000ustar00rootroot00000000000000option('daemon', type : 'boolean', value : true, description : 'enable the fwupd daemon') option('agent', type : 'boolean', value : true, description : 'enable the fwupd agent') option('consolekit', type : 'boolean', value : true, description : 'enable ConsoleKit support') option('firmware-packager', type : 'boolean', value : true, description : 'enable firmware-packager installation') option('gpg', type : 'boolean', value : true, description : 'enable the GPG verification support') option('gtkdoc', type : 'boolean', value : true, description : 'enable developer documentation') option('introspection', type : 'boolean', value : true, description : 'generate GObject Introspection data') option('lvfs', type : 'boolean', value : true, description : 'enable LVFS remotes') option('man', type : 'boolean', value : true, description : 'enable man pages') option('pkcs7', type : 'boolean', value : true, description : 'enable the PKCS7 verification support') option('plugin_altos', type : 'boolean', value : true, description : 'enable altos support') option('plugin_amt', type : 'boolean', value : true, description : 'enable Intel AMT support') option('plugin_dell', type : 'boolean', value : true, description : 'enable Dell-specific support') option('plugin_dummy', type : 'boolean', value : false, description : 'enable the dummy device') option('plugin_synaptics', type: 'boolean', value: true, description : 'enable Synaptics MST hub support') option('plugin_thunderbolt', type : 'boolean', value : true, description : 'enable Thunderbolt support') option('plugin_redfish', type : 'boolean', value : true, description : 'enable Redfish support') option('plugin_uefi', type : 'boolean', value : true, description : 'enable UEFI support') option('plugin_nvme', type : 'boolean', value : true, description : 'enable NVMe support') option('plugin_modem_manager', type : 'boolean', value : false, description : 'enable ModemManager support') option('plugin_flashrom', type : 'boolean', value : false, description : 'enable libflashrom support') option('systemd', type : 'boolean', value : true, description : 'enable systemd support') option('systemdunitdir', type: 'string', value: '', description: 'Directory for systemd units') option('elogind', type : 'boolean', value : false, description : 'enable elogind support') option('tests', type : 'boolean', value : true, description : 'enable tests') option('udevdir', type: 'string', value: '', description: 'Directory for udev rules') option('efi-cc', type : 'string', value : 'gcc', description : 'the compiler to use for EFI modules') option('efi-ld', type : 'string', value : 'ld', description : 'the linker to use for EFI modules') option('efi-objcopy', type : 'string', value : 'objcopy', description : 'the objcopy utility to use for EFI modules') option('efi-libdir', type : 'string', description : 'path to the EFI lib directory') option('efi-ldsdir', type : 'string', description : 'path to the EFI lds directory') option('efi-includedir', type : 'string', value : '/usr/include/efi', description : 'path to the EFI header directory') option('efi_os_dir', type: 'string', description : 'the hardcoded name of OS directory in ESP, e.g. fedora') option('efi_sbat_fwupd_generation', type : 'integer', value : 1, description : 'SBAT fwupd generation') option('efi_sbat_distro_id', type : 'string', value : '', description : 'SBAT distribution ID, e.g. fedora') option('efi_sbat_distro_summary', type : 'string', value : '', description : 'SBAT distribution summary, e.g. Fedora') option('efi_sbat_distro_pkgname', type : 'string', value : '', description : 'SBAT distribution package name, e.g. fwupd') option('efi_sbat_distro_version', type : 'string', value : '', description : 'SBAT distribution version, e.g. fwupd-1.5.6.fc33') option('efi_sbat_distro_url', type : 'string', value : '', description : 'SBAT distribution URL, e.g. https://src.fedoraproject.org/rpms/fwupd') fwupd-1.2.14/meson_post_install.sh000077500000000000000000000006311402665037500172070ustar00rootroot00000000000000#!/bin/sh if [ -z $MESON_INSTALL_PREFIX ]; then echo 'This is meant to be ran from Meson only!' exit 1 fi SYSTEMDUNITDIR=$1 LOCALSTATEDIR=$2 #if [ -z $DESTDIR ]; then echo 'Updating systemd deps' mkdir -p ${DESTDIR}${SYSTEMDUNITDIR}/system-update.target.wants ln -sf ../fwupd-offline-update.service ${DESTDIR}${SYSTEMDUNITDIR}/system-update.target.wants/fwupd-offline-update.service #fi fwupd-1.2.14/plugins/000077500000000000000000000000001402665037500144155ustar00rootroot00000000000000fwupd-1.2.14/plugins/README.md000066400000000000000000000020141402665037500156710ustar00rootroot00000000000000Adding a new plugin ------------------- An extensible architecture allows for providing new plugin types (for reading and writing different firmware) as well as ways quirk their behavior. You can find more information about the architecture in the developers section of the [fwupd website](https://fwupd.org). If you have a firmware specification and would like to see support in this project, please file an issue and share the spec. Patches are also welcome. We will not accept plugins that upgrade hardware using a proprietary Linux executable, proprietary UEFI executable, proprietary library, or DBus interface. Plugin interaction ------------------ Some plugins may be able to influence the behavior of other plugins. This includes things like one plugin turning on a device, or providing missing metadata to another plugin. The ABI for these interactions is defined in: https://github.com/hughsie/fwupd/blob/master/src/fu-device-metadata.h All interactions between plugins should have the interface defined in that file. fwupd-1.2.14/plugins/altos/000077500000000000000000000000001402665037500155375ustar00rootroot00000000000000fwupd-1.2.14/plugins/altos/README.md000066400000000000000000000035001402665037500170140ustar00rootroot00000000000000Altos Support ============= Introduction ------------ Altos is a 8051 operating system for Altus-Metrum projects. The ChaosKey is a hardware random number generator that attaches via USB. When the ChaosKey when inserted it appears as a device handled by the kernel with VID 0x1d50 and PID 0x60c6. If pins 1 and 5 are shorted as the device is connected then the bootloader is run, which presents VID 0xfffe and PID 0x000a. The bootloader communication is not handled in the kernel, and a tty device is created so userspace can communicate with the hardware. Commands the bootloader accept are as follows: Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in ELF file format. The firmware image is inserted into the `.text` section. This plugin supports the following protocol ID: * org.altusmetrum.altos GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1D50&PID_60C6&REV_0001` * `USB\VID_1D50&PID_60C6` * `USB\VID_1D50` ### List Information Command: `l\n` Several lines of text about the device are transferred to the host, e.g. altos-loader manufacturer altusmetrum.org product AltosFlash flash-range 08001000 08008000 software-version 1.6.8 There doesn't appear to be any kind of end-of-message signal. ### Read Flash Command: `R $addr\n` where `$addr` is a memory address `0x8001000->0x8008000`. 256 bytes of raw data are then transferred to the host. ### Write Flash Command: `W $addr\n` where `$addr` is a memory address `0x8001000->0x8008000`. 256 bytes of raw data are then transferred to the device. ### Application Mode Command: `v\n` The device will reboot into application mode. This is typically performed after flashing firmware completes successfully. fwupd-1.2.14/plugins/altos/altos.quirk000066400000000000000000000002361402665037500177370ustar00rootroot00000000000000# ChaosKey [DeviceInstanceId=USB\VID_1D50&PID_60C6] Plugin = altos Flags = none [DeviceInstanceId=USB\VID_FFFE&PID_000A] Plugin = altos Flags = is-bootloader fwupd-1.2.14/plugins/altos/data/000077500000000000000000000000001402665037500164505ustar00rootroot00000000000000fwupd-1.2.14/plugins/altos/data/lsusb-bootloader.txt000066400000000000000000000056651402665037500225050ustar00rootroot00000000000000Bus 001 Device 037: ID fffe:000a Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 2 Communications bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0xfffe idProduct 0x000a bcdDevice 1.00 iManufacturer 1 altusmetrum.org iProduct 2 AltosFlash iSerial 3 000001 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 67 bNumInterfaces 2 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 2 Communications bInterfaceSubClass 2 Abstract (modem) bInterfaceProtocol 1 AT-commands (v.25ter) iInterface 0 CDC Header: bcdCDC 1.10 CDC Call Management: bmCapabilities 0x01 call management bDataInterface 1 CDC ACM: bmCapabilities 0x02 line coding and serial state CDC Union: bMasterInterface 0 bSlaveInterface 1 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 255 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 10 CDC Data bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x04 EP 4 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x85 EP 5 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/altos/data/lsusb-runtime.txt000066400000000000000000000037001402665037500220220ustar00rootroot00000000000000Bus 001 Device 036: ID 1d50:60c6 OpenMoko, Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 255 Vendor Specific Class bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x1d50 OpenMoko, Inc. idProduct 0x60c6 bcdDevice 1.00 iManufacturer 1 altusmetrum.org iProduct 2 ChaosKey-hw-1.0-sw-1.6.7 iSerial 3 001b002f5346430b20333632 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 32 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x85 EP 5 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x86 EP 6 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/altos/fu-altos-device.c000066400000000000000000000420601402665037500206740ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "fu-io-channel.h" #include "fu-altos-device.h" #include "fu-altos-firmware.h" struct _FuAltosDevice { FuUsbDevice parent_instance; FuAltosDeviceKind kind; guint32 serial[9]; gchar *guid; gchar *tty; gchar *version; guint64 addr_base; guint64 addr_bound; struct termios tty_termios; FuIOChannel *io_channel; }; G_DEFINE_TYPE (FuAltosDevice, fu_altos_device, FU_TYPE_USB_DEVICE) #ifndef HAVE_GUDEV_232 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevClient, g_object_unref) #pragma clang diagnostic pop #endif /** * fu_altos_device_kind_from_string: * @kind: the string. * * Converts the text representation to an enumerated value. * * Returns: (transfer full): a #FuAltosDeviceKind, or %FU_ALTOS_DEVICE_KIND_UNKNOWN for unknown. * * Since: 0.1.0 **/ FuAltosDeviceKind fu_altos_device_kind_from_string (const gchar *kind) { if (g_strcmp0 (kind, "BOOTLOADER") == 0) return FU_ALTOS_DEVICE_KIND_BOOTLOADER; if (g_strcmp0 (kind, "CHAOSKEY") == 0) return FU_ALTOS_DEVICE_KIND_CHAOSKEY; return FU_ALTOS_DEVICE_KIND_UNKNOWN; } /** * fu_altos_device_kind_to_string: * @kind: the #FuAltosDeviceKind. * * Converts the enumerated value to an text representation. * * Returns: string version of @kind * * Since: 0.1.0 **/ const gchar * fu_altos_device_kind_to_string (FuAltosDeviceKind kind) { if (kind == FU_ALTOS_DEVICE_KIND_BOOTLOADER) return "BOOTLOADER"; if (kind == FU_ALTOS_DEVICE_KIND_CHAOSKEY) return "CHAOSKEY"; return NULL; } static void fu_altos_device_finalize (GObject *object) { FuAltosDevice *self = FU_ALTOS_DEVICE (object); g_free (self->guid); g_free (self->tty); g_free (self->version); G_OBJECT_CLASS (fu_altos_device_parent_class)->finalize (object); } FuAltosDeviceKind fu_altos_device_get_kind (FuAltosDevice *self) { return self->kind; } static gboolean fu_altos_device_find_tty (FuAltosDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); g_autoptr(GList) devices = NULL; g_autoptr(GUdevClient) gudev_client = g_udev_client_new (NULL); /* find all tty devices */ devices = g_udev_client_query_by_subsystem (gudev_client, "tty"); for (GList *l = devices; l != NULL; l = l->next) { GUdevDevice *dev = G_UDEV_DEVICE (l->data); /* get the tty device */ const gchar *dev_file = g_udev_device_get_device_file (dev); if (dev_file == NULL) continue; /* get grandparent */ dev = g_udev_device_get_parent (dev); if (dev == NULL) continue; dev = g_udev_device_get_parent (dev); if (dev == NULL) continue; /* check correct device */ if (g_udev_device_get_sysfs_attr_as_int (dev, "busnum") != g_usb_device_get_bus (usb_device)) continue; if (g_udev_device_get_sysfs_attr_as_int (dev, "devnum") != g_usb_device_get_address (usb_device)) continue; /* success */ self->tty = g_strdup (dev_file); return TRUE; } /* failure */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find tty for %u:%u", g_usb_device_get_bus (usb_device), g_usb_device_get_address (usb_device)); return FALSE; } static gboolean fu_altos_device_tty_write (FuAltosDevice *self, const gchar *data, gssize data_len, GError **error) { /* lets assume this is text */ if (data_len < 0) data_len = strlen (data); return fu_io_channel_write_raw (self->io_channel, (const guint8 *) data, (gsize) data_len, 500, /* ms */ FU_IO_CHANNEL_FLAG_NONE, error); } static GString * fu_altos_device_tty_read (FuAltosDevice *self, guint timeout_ms, gssize max_size, GError **error) { g_autoptr(GBytes) buf = NULL; buf = fu_io_channel_read_bytes (self->io_channel, max_size, timeout_ms, FU_IO_CHANNEL_FLAG_NONE, error); if (buf == NULL) return NULL; return g_string_new_len (g_bytes_get_data (buf, NULL), g_bytes_get_size (buf)); } static gboolean fu_altos_device_tty_open (FuAltosDevice *self, GError **error) { struct termios termios; g_autoptr(GString) str = NULL; /* open device */ self->io_channel = fu_io_channel_new_file (self->tty, error); if (self->io_channel == NULL) return FALSE; /* get the old termios settings so we can restore later */ if (tcgetattr (fu_io_channel_unix_get_fd (self->io_channel), &termios) < 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get attributes from fd"); return FALSE; } self->tty_termios = termios; cfmakeraw (&termios); /* set speed */ if (cfsetspeed (&termios, B9600) < 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to set terminal speed"); return FALSE; } /* one input byte is enough to return * inter-character timer off */ termios.c_cc[VMIN] = 1; termios.c_cc[VTIME] = 0; /* set all new data */ if (tcsetattr (fu_io_channel_unix_get_fd (self->io_channel), TCSAFLUSH, &termios) < 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to set attributes on fd"); return FALSE; } /* dump any pending input */ str = fu_altos_device_tty_read (self, 50, -1, NULL); if (str != NULL) g_debug ("dumping pending buffer: %s", str->str); return TRUE; } static gboolean fu_altos_device_tty_close (FuAltosDevice *self, GError **error) { tcsetattr (fu_io_channel_unix_get_fd (self->io_channel), TCSAFLUSH, &self->tty_termios); if (!fu_io_channel_shutdown (self->io_channel, error)) return FALSE; g_clear_object (&self->io_channel); return TRUE; } static GString * fu_altos_device_read_page (FuAltosDevice *self, guint address, GError **error) { g_autoptr(GString) str = NULL; g_autofree gchar *cmd = g_strdup_printf ("R %x\n", address); if (!fu_altos_device_tty_write (self, cmd, -1, error)) return NULL; str = fu_altos_device_tty_read (self, 1500, 256, error); if (str == NULL) return NULL; return g_steal_pointer (&str); } static gboolean fu_altos_device_write_page (FuAltosDevice *self, guint address, const guint8 *data, guint data_len, GError **error) { g_autofree gchar *cmd = g_strdup_printf ("W %x\n", address); if (!fu_altos_device_tty_write (self, cmd, -1, error)) return FALSE; if (!fu_altos_device_tty_write (self, (const gchar *) data, data_len, error)) return FALSE; return TRUE; } static gboolean fu_altos_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuAltosDevice *self = FU_ALTOS_DEVICE (device); GBytes *fw_blob; const gchar *data; const gsize data_len; guint flash_len; g_autoptr(FuAltosFirmware) altos_firmware = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GString) buf = g_string_new (NULL); /* check kind */ if (self->kind != FU_ALTOS_DEVICE_KIND_BOOTLOADER) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "verification only supported in bootloader"); return FALSE; } /* check sizes */ if (self->addr_base == 0x0 || self->addr_bound == 0x0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "address base and bound are unset"); return FALSE; } /* read in blocks of 256 bytes */ flash_len = self->addr_bound - self->addr_base; if (flash_len == 0x0 || flash_len > 0x100000) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "address range was icorrect"); return FALSE; } /* load ihex blob */ altos_firmware = fu_altos_firmware_new (); if (!fu_altos_firmware_parse (altos_firmware, fw, error)) return FALSE; /* check the start address */ if (fu_altos_firmware_get_address (altos_firmware) != self->addr_base) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "start address not correct %" G_GUINT64_FORMAT ":" "%" G_GUINT64_FORMAT, fu_altos_firmware_get_address (altos_firmware), self->addr_base); return FALSE; } /* check firmware will fit */ fw_blob = fu_altos_firmware_get_data (altos_firmware); data = g_bytes_get_data (fw_blob, (gsize *) &data_len); if (data_len > flash_len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too large for device %" G_GSIZE_FORMAT ":%u", data_len, flash_len); return FALSE; } /* open tty for download */ locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) fu_altos_device_tty_open, (FuDeviceLockerFunc) fu_altos_device_tty_close, error); if (locker == NULL) return FALSE; for (guint i = 0; i < flash_len; i+= 0x100) { g_autoptr(GString) str = NULL; guint8 buf_tmp[0x100]; /* copy remaining data into buf if required */ memset (buf_tmp, 0xff, sizeof (buf)); if (i < data_len) { gsize chunk_len = 0x100; if (i + 0x100 > data_len) chunk_len = data_len - i; memcpy (buf_tmp, data + i, chunk_len); } /* verify data from device */ if (!fu_altos_device_write_page (self, self->addr_base + i, buf_tmp, 0x100, error)) return FALSE; /* verify data written on device */ str = fu_altos_device_read_page (self, self->addr_base + i, error); if (str == NULL) return FALSE; if (str->len != 0x100) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to verify @%x, " "not enough data returned", (guint) (self->addr_base + i)); return FALSE; } if (memcmp (str->str, buf_tmp, 0x100) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to verify @%x", (guint) (self->addr_base + i)); return FALSE; } /* progress */ fu_device_set_progress_full (device, i, flash_len); g_string_append_len (buf, str->str, str->len); } /* go to application mode */ if (!fu_altos_device_tty_write (self, "a\n", -1, error)) return FALSE; /* progress complete */ fu_device_set_progress_full (device, flash_len, flash_len); /* success */ return TRUE; } static GBytes * fu_altos_device_read_firmware (FuDevice *device, GError **error) { FuAltosDevice *self = FU_ALTOS_DEVICE (device); guint flash_len; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GString) buf = g_string_new (NULL); /* check kind */ if (self->kind != FU_ALTOS_DEVICE_KIND_BOOTLOADER) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "verification only supported in bootloader"); return NULL; } /* check sizes */ if (self->addr_base == 0x0 || self->addr_bound == 0x0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "address base and bound are unset"); return NULL; } /* read in blocks of 256 bytes */ flash_len = self->addr_bound - self->addr_base; if (flash_len == 0x0 || flash_len > 0x100000) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "address range was icorrect"); return NULL; } /* open tty for download */ locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) fu_altos_device_tty_open, (FuDeviceLockerFunc) fu_altos_device_tty_close, error); if (locker == NULL) return NULL; for (guint i = self->addr_base; i < self->addr_bound; i+= 0x100) { g_autoptr(GString) str = NULL; /* request data from device */ str = fu_altos_device_read_page (self, i, error); if (str == NULL) return NULL; /* progress */ fu_device_set_progress_full (device, i - self->addr_base, self->addr_bound - self->addr_base); g_string_append_len (buf, str->str, str->len); } /* success */ return g_bytes_new (buf->str, buf->len); } static gboolean fu_altos_device_probe_bootloader (FuAltosDevice *self, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_auto(GStrv) lines = NULL; g_autoptr(GString) str = NULL; /* get tty for upload */ if (!fu_altos_device_find_tty (self, error)) return FALSE; locker = fu_device_locker_new_full (self, (FuDeviceLockerFunc) fu_altos_device_tty_open, (FuDeviceLockerFunc) fu_altos_device_tty_close, error); if (locker == NULL) return FALSE; /* get the version information */ if (!fu_altos_device_tty_write (self, "v\n", -1, error)) return FALSE; str = fu_altos_device_tty_read (self, 100, -1, error); if (str == NULL) return FALSE; /* parse each line */ lines = g_strsplit_set (str->str, "\n\r", -1); for (guint i = 0; lines[i] != NULL; i++) { /* ignore */ if (lines[i][0] == '\0') continue; if (g_str_has_prefix (lines[i], "manufacturer ")) continue; if (g_str_has_prefix (lines[i], "product ")) continue; /* we can flash firmware */ if (g_strcmp0 (lines[i], "altos-loader") == 0) { fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); continue; } /* version number */ if (g_str_has_prefix (lines[i], "software-version ")) { fu_device_set_version (FU_DEVICE (self), lines[i] + 17, FWUPD_VERSION_FORMAT_TRIPLET); continue; } /* address base and bound */ if (g_str_has_prefix (lines[i], "flash-range ")) { g_auto(GStrv) addrs = g_strsplit (lines[i] + 17, " ", -1); self->addr_base = g_ascii_strtoull (addrs[0], NULL, 16); self->addr_bound = g_ascii_strtoull (addrs[1], NULL, 16); g_debug ("base: %x, bound: %x", (guint) self->addr_base, (guint) self->addr_bound); continue; } /* unknown line */ g_debug ("unknown data: '%s'", lines[i]); } return TRUE; } static gboolean fu_altos_device_probe (FuDevice *device, GError **error) { FuAltosDevice *self = FU_ALTOS_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); /* bootloader uses tty */ if (self->kind == FU_ALTOS_DEVICE_KIND_BOOTLOADER) return fu_altos_device_probe_bootloader (self, error); /* get version */ if (self->kind == FU_ALTOS_DEVICE_KIND_CHAOSKEY) { const gchar *version_prefix = "ChaosKey-hw-1.0-sw-"; guint8 version_idx; g_autofree gchar *version = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* open */ locker = fu_device_locker_new (usb_device, error); if (locker == NULL) return FALSE; /* get string */ version_idx = g_usb_device_get_product_index (usb_device); version = g_usb_device_get_string_descriptor (usb_device, version_idx, error); if (version == NULL) return FALSE; if (!g_str_has_prefix (version, version_prefix)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not a ChaosKey v1.0 device: %s", version); return FALSE; } fu_device_set_version (FU_DEVICE (self), version + 19, FWUPD_VERSION_FORMAT_TRIPLET); } /* success */ return TRUE; } /* now with kind and usb_device set */ static void fu_altos_device_init_real (FuAltosDevice *self) { /* allowed, but requires manual bootloader step */ fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); /* set default vendor */ fu_device_set_vendor (FU_DEVICE (self), "altusmetrum.org"); /* set name */ switch (self->kind) { case FU_ALTOS_DEVICE_KIND_BOOTLOADER: fu_device_set_name (FU_DEVICE (self), "Altos [bootloader]"); break; case FU_ALTOS_DEVICE_KIND_CHAOSKEY: fu_device_set_name (FU_DEVICE (self), "Altos ChaosKey"); break; default: g_assert_not_reached (); break; } /* set one line summary */ fu_device_set_summary (FU_DEVICE (self), "A USB hardware random number generator"); /* only the bootloader can do the update */ if (self->kind != FU_ALTOS_DEVICE_KIND_BOOTLOADER) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); } } static void fu_altos_device_init (FuAltosDevice *self) { } static void fu_altos_device_class_init (FuAltosDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->probe = fu_altos_device_probe; klass_device->write_firmware = fu_altos_device_write_firmware; klass_device->read_firmware = fu_altos_device_read_firmware; object_class->finalize = fu_altos_device_finalize; } typedef struct { guint16 vid; guint16 pid; FuAltosDeviceKind kind; } FuAltosDeviceVidPid; FuAltosDevice * fu_altos_device_new (FuUsbDevice *device) { const FuAltosDeviceVidPid vidpids[] = { { 0xfffe, 0x000a, FU_ALTOS_DEVICE_KIND_BOOTLOADER }, { 0x1d50, 0x60c6, FU_ALTOS_DEVICE_KIND_CHAOSKEY }, { 0x0000, 0x0000, FU_ALTOS_DEVICE_KIND_UNKNOWN } }; /* set kind */ for (guint j = 0; vidpids[j].vid != 0x0000; j++) { if (fu_usb_device_get_vid (device) == vidpids[j].vid && fu_usb_device_get_pid (device) == vidpids[j].pid) { FuAltosDevice *self = g_object_new (FU_TYPE_ALTOS_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); self->kind = vidpids[j].kind; fu_altos_device_init_real (self); return self; } } return NULL; } fwupd-1.2.14/plugins/altos/fu-altos-device.h000066400000000000000000000017301402665037500207000ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_ALTOS_DEVICE (fu_altos_device_get_type ()) G_DECLARE_FINAL_TYPE (FuAltosDevice, fu_altos_device, FU, ALTOS_DEVICE, FuUsbDevice) typedef enum { FU_ALTOS_DEVICE_KIND_UNKNOWN, FU_ALTOS_DEVICE_KIND_BOOTLOADER, FU_ALTOS_DEVICE_KIND_CHAOSKEY, /*< private >*/ FU_ALTOS_DEVICE_KIND_LAST } FuAltosDeviceKind; typedef enum { FU_ALTOS_DEVICE_WRITE_FIRMWARE_FLAG_NONE = 0, FU_ALTOS_DEVICE_WRITE_FIRMWARE_FLAG_REBOOT = 1 << 0, /*< private >*/ FU_ALTOS_DEVICE_WRITE_FIRMWARE_FLAG_LAST } FuAltosDeviceWriteFirmwareFlag; FuAltosDevice *fu_altos_device_new (FuUsbDevice *device); FuAltosDeviceKind fu_altos_device_kind_from_string (const gchar *kind); const gchar *fu_altos_device_kind_to_string (FuAltosDeviceKind kind); FuAltosDeviceKind fu_altos_device_get_kind (FuAltosDevice *device); G_END_DECLS fwupd-1.2.14/plugins/altos/fu-altos-firmware.c000066400000000000000000000061651402665037500212570ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-altos-firmware.h" #include "fwupd-error.h" struct _FuAltosFirmware { GObject parent_instance; GBytes *data; guint64 address; }; G_DEFINE_TYPE (FuAltosFirmware, fu_altos_firmware, G_TYPE_OBJECT) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(Elf, elf_end); #pragma clang diagnostic pop GBytes * fu_altos_firmware_get_data (FuAltosFirmware *self) { return self->data; } guint64 fu_altos_firmware_get_address (FuAltosFirmware *self) { return self->address; } gboolean fu_altos_firmware_parse (FuAltosFirmware *self, GBytes *blob, GError **error) { const gchar *name; Elf_Scn *scn = NULL; GElf_Shdr shdr; size_t shstrndx; g_autoptr(Elf) e = NULL; /* load library */ if (elf_version (EV_CURRENT) == EV_NONE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ELF library init failed: %s", elf_errmsg (-1)); return FALSE; } /* parse data */ e = elf_memory ((gchar *) g_bytes_get_data (blob, NULL), g_bytes_get_size (blob)); if (e == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to load data as ELF: %s", elf_errmsg (-1)); return FALSE; } if (elf_kind (e) != ELF_K_ELF) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "not a supported ELF format: %s", elf_errmsg (-1)); return FALSE; } /* add interesting section */ if (elf_getshdrstrndx (e, &shstrndx) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid ELF file: %s", elf_errmsg (-1)); return FALSE; } while ((scn = elf_nextscn (e, scn)) != NULL ) { if (gelf_getshdr (scn, &shdr) != & shdr) continue; /* not program data with the same section name */ if (shdr.sh_type != SHT_PROGBITS) continue; if ((name = elf_strptr (e, shstrndx, shdr.sh_name)) == NULL) continue; if (g_strcmp0 (name, ".text") == 0) { Elf_Data *data = elf_getdata (scn, NULL); if (data != NULL && data->d_buf != NULL) { self->data = g_bytes_new (data->d_buf, data->d_size); self->address = shdr.sh_addr; } return TRUE; } } g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no firmware found in ELF file"); return FALSE; } static void fu_altos_firmware_finalize (GObject *object) { FuAltosFirmware *self = FU_ALTOS_FIRMWARE (object); if (self->data != NULL) g_bytes_unref (self->data); G_OBJECT_CLASS (fu_altos_firmware_parent_class)->finalize (object); } static void fu_altos_firmware_class_init (FuAltosFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_altos_firmware_finalize; } static void fu_altos_firmware_init (FuAltosFirmware *self) { } FuAltosFirmware * fu_altos_firmware_new (void) { FuAltosFirmware *self; self = g_object_new (FU_TYPE_ALTOS_FIRMWARE, NULL); return FU_ALTOS_FIRMWARE (self); } fwupd-1.2.14/plugins/altos/fu-altos-firmware.h000066400000000000000000000011071402665037500212530ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once G_BEGIN_DECLS #define FU_TYPE_ALTOS_FIRMWARE (fu_altos_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuAltosFirmware, fu_altos_firmware, FU, ALTOS_FIRMWARE, GObject) FuAltosFirmware *fu_altos_firmware_new (void); GBytes *fu_altos_firmware_get_data (FuAltosFirmware *self); guint64 fu_altos_firmware_get_address (FuAltosFirmware *self); gboolean fu_altos_firmware_parse (FuAltosFirmware *self, GBytes *blob, GError **error); G_END_DECLS fwupd-1.2.14/plugins/altos/fu-plugin-altos.c000066400000000000000000000050301402665037500207270ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-altos-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "org.altusmetrum.altos"); } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); const gchar *platform_id = NULL; g_autofree gchar *runtime_id = NULL; g_autoptr(FuAltosDevice) dev = NULL; /* get kind */ dev = fu_altos_device_new (device); if (dev == NULL) return TRUE; /* get device properties */ if (!fu_device_probe (FU_DEVICE (dev), error)) return FALSE; /* only the bootloader can do the update */ platform_id = g_usb_device_get_platform_id (usb_device); runtime_id = g_strdup_printf ("%s-runtime", platform_id); if (fu_altos_device_get_kind (dev) == FU_ALTOS_DEVICE_KIND_BOOTLOADER) { FuDevice *dev_runtime; dev_runtime = fu_plugin_cache_lookup (plugin, runtime_id); if (dev_runtime != NULL) { const gchar *guid = fu_device_get_guid_default (dev_runtime); g_debug ("adding runtime GUID of %s", guid); fu_device_add_counterpart_guid (FU_DEVICE (dev), guid); fu_device_set_version (FU_DEVICE (dev), fu_device_get_version (dev_runtime), fu_device_get_version_format (dev_runtime)); } } else { fu_plugin_cache_add (plugin, runtime_id, dev); } /* success */ fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } gboolean fu_plugin_verify (FuPlugin *plugin, FuDevice *dev, FuPluginVerifyFlags flags, GError **error) { g_autoptr(GBytes) blob_fw = NULL; GChecksumType checksum_types[] = { G_CHECKSUM_SHA1, G_CHECKSUM_SHA256, 0 }; /* get data */ fu_device_set_status (dev, FWUPD_STATUS_DEVICE_VERIFY); blob_fw = fu_device_read_firmware (dev, error); if (blob_fw == NULL) return FALSE; for (guint i = 0; checksum_types[i] != 0; i++) { g_autofree gchar *hash = NULL; hash = g_compute_checksum_for_bytes (checksum_types[i], blob_fw); fu_device_add_checksum (dev, hash); } return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { fu_device_set_status (dev, FWUPD_STATUS_DEVICE_WRITE); return fu_device_write_firmware (dev, blob_fw, flags, error); } fwupd-1.2.14/plugins/altos/meson.build000066400000000000000000000011031402665037500176740ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginAltos"'] install_data(['altos.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_altos', fu_hash, sources : [ 'fu-altos-device.c', 'fu-altos-firmware.c', 'fu-plugin-altos.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ libelf, plugin_deps, ], ) fwupd-1.2.14/plugins/amt/000077500000000000000000000000001402665037500151765ustar00rootroot00000000000000fwupd-1.2.14/plugins/amt/README.md000066400000000000000000000013431402665037500164560ustar00rootroot00000000000000Intel Management Engine ======================= Introduction ------------ This plugin is used to get the version number on the Intel Management Engine. If AMT is enabled and provisioned and the AMT version is between 6.0 and 11.2, and you have not upgraded your firmware, you are vulnerable to CVE-2017-5689 and you should disable AMT in your system firmware. This code is inspired by 'AMT status checker for Linux' by Matthew Garrett which can be found here: https://github.com/mjg59/mei-amt-check That tool in turn is heavily based on mei-amt-version from samples/mei in the Linux source tree and copyright Intel Corporation. GUID Generation --------------- These devices use the existing GUID provided by the AMT host interface. fwupd-1.2.14/plugins/amt/fu-plugin-amt.c000066400000000000000000000346611402665037500200410ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation. * Copyright (C) 2017 Google, Inc. * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "fu-plugin-vfuncs.h" typedef struct { uuid_le guid; guint buf_size; guchar prot_ver; gint fd; } mei_context; static void mei_context_free (mei_context *cl) { if (cl->fd != -1) close(cl->fd); g_free (cl); } static gboolean mei_context_new (mei_context *ctx, const uuid_le *guid, guchar req_protocol_version, GError **error) { gint result; struct mei_client *cl; struct mei_connect_client_data data; ctx->fd = open ("/dev/mei0", O_RDWR); if (ctx->fd == -1 && errno == ENOENT) ctx->fd = open ("/dev/mei", O_RDWR); if (ctx->fd == -1) { if (errno == ENOENT) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Unable to find a ME interface"); } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Cannot open /dev/mei0"); } return FALSE; } memcpy (&ctx->guid, guid, sizeof(*guid)); memset (&data, 0, sizeof(data)); memcpy (&data.in_client_uuid, &ctx->guid, sizeof(ctx->guid)); result = ioctl (ctx->fd, IOCTL_MEI_CONNECT_CLIENT, &data); if (result != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ME refused connection"); return FALSE; } cl = &data.out_client_properties; if ((req_protocol_version > 0) && (cl->protocol_version != req_protocol_version)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Intel MEI protocol version not supported %i", cl->protocol_version); return FALSE; } ctx->buf_size = cl->max_msg_length; ctx->prot_ver = cl->protocol_version; return TRUE; } static gboolean mei_recv_msg (mei_context *ctx, guchar *buffer, gssize len, guint32 *readsz, unsigned long timeout, GError **error) { gssize rc; rc = read (ctx->fd, buffer, len); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "read failed with status %zd %s", rc, strerror(errno)); return FALSE; } if (readsz != NULL) *readsz = rc; return TRUE; } static gboolean mei_send_msg (mei_context *ctx, const guchar *buffer, gssize len, unsigned long timeout, GError **error) { struct timeval tv; gssize written; gssize rc; fd_set set; tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000000; written = write (ctx->fd, buffer, len); if (written < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "write failed with status %zd %s", written, strerror(errno)); return FALSE; } if (written != len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "only wrote %" G_GSSIZE_FORMAT " of %" G_GSSIZE_FORMAT, written, len); return FALSE; } FD_ZERO(&set); FD_SET(ctx->fd, &set); rc = select (ctx->fd + 1 , &set, NULL, NULL, &tv); if (rc > 0 && FD_ISSET(ctx->fd, &set)) return TRUE; /* timed out */ if (rc == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "write failed on timeout with status"); return FALSE; } /* rc < 0 */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "write failed on select with status %zd", rc); return FALSE; } /*************************************************************************** * Intel Advanced Management Technology ME Client ***************************************************************************/ #define AMT_MAJOR_VERSION 1 #define AMT_MINOR_VERSION 1 #define AMT_STATUS_SUCCESS 0x0 #define AMT_STATUS_INTERNAL_ERROR 0x1 #define AMT_STATUS_NOT_READY 0x2 #define AMT_STATUS_INVALID_AMT_MODE 0x3 #define AMT_STATUS_INVALID_MESSAGE_LENGTH 0x4 #define AMT_STATUS_HOST_IF_EMPTY_RESPONSE 0x4000 #define AMT_STATUS_SDK_RESOURCES 0x1004 #define AMT_BIOS_VERSION_LEN 65 #define AMT_VERSIONS_NUMBER 50 #define AMT_UNICODE_STRING_LEN 20 struct amt_unicode_string { guint16 length; char string[AMT_UNICODE_STRING_LEN]; } __attribute__((packed)); struct amt_version_type { struct amt_unicode_string description; struct amt_unicode_string version; } __attribute__((packed)); struct amt_version { guint8 major; guint8 minor; } __attribute__((packed)); struct amt_code_versions { guint8 bios[AMT_BIOS_VERSION_LEN]; guint32 count; struct amt_version_type versions[AMT_VERSIONS_NUMBER]; } __attribute__((packed)); struct amt_provisioning_state { guint8 bios[AMT_BIOS_VERSION_LEN]; guint32 count; guint8 state; } __attribute__((packed)); /*************************************************************************** * Intel Advanced Management Technology Host Interface ***************************************************************************/ struct amt_host_if_msg_header { struct amt_version version; guint16 _reserved; guint32 command; guint32 length; } __attribute__((packed)); struct amt_host_if_resp_header { struct amt_host_if_msg_header header; guint32 status; guchar data[0]; } __attribute__((packed)); #define AMT_HOST_IF_CODE_VERSIONS_REQUEST 0x0400001A #define AMT_HOST_IF_CODE_VERSIONS_RESPONSE 0x0480001A const struct amt_host_if_msg_header CODE_VERSION_REQ = { .version = {AMT_MAJOR_VERSION, AMT_MINOR_VERSION}, ._reserved = 0, .command = AMT_HOST_IF_CODE_VERSIONS_REQUEST, .length = 0 }; #define AMT_HOST_IF_PROVISIONING_MODE_REQUEST 0x04000008 #define AMT_HOST_IF_PROVISIONING_MODE_RESPONSE 0x04800008 const struct amt_host_if_msg_header PROVISIONING_MODE_REQUEST = { .version = {AMT_MAJOR_VERSION, AMT_MINOR_VERSION}, ._reserved = 0, .command = AMT_HOST_IF_PROVISIONING_MODE_REQUEST, .length = 0 }; #define AMT_HOST_IF_PROVISIONING_STATE_REQUEST 0x04000011 #define AMT_HOST_IF_PROVISIONING_STATE_RESPONSE 0x04800011 const struct amt_host_if_msg_header PROVISIONING_STATE_REQUEST = { .version = {AMT_MAJOR_VERSION, AMT_MINOR_VERSION}, ._reserved = 0, .command = AMT_HOST_IF_PROVISIONING_STATE_REQUEST, .length = 0 }; struct amt_host_if { mei_context mei_cl; }; static gboolean amt_verify_code_versions (const struct amt_host_if_resp_header *resp, GError **error) { struct amt_code_versions *code_ver = (struct amt_code_versions *)resp->data; gsize code_ver_len = resp->header.length - sizeof(guint32); guint32 ver_type_cnt = code_ver_len - sizeof(code_ver->bios) - sizeof(code_ver->count); if (code_ver->count != ver_type_cnt / sizeof(struct amt_version_type)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid offset"); return FALSE; } for (guint32 i = 0; i < code_ver->count; i++) { guint32 len = code_ver->versions[i].description.length; if (len > AMT_UNICODE_STRING_LEN) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "string too large"); return FALSE; } len = code_ver->versions[i].version.length; if (code_ver->versions[i].version.string[len] != '\0' || len != strlen(code_ver->versions[i].version.string)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "string was invalid size"); return FALSE; } } return TRUE; } static gboolean amt_status_set_error (guint32 status, GError **error) { if (status == AMT_STATUS_SUCCESS) return TRUE; if (status == AMT_STATUS_INTERNAL_ERROR) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "internal error"); return FALSE; } if (status == AMT_STATUS_NOT_READY) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "not ready"); return FALSE; } if (status == AMT_STATUS_INVALID_AMT_MODE) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid AMT mode"); return FALSE; } if (status == AMT_STATUS_INVALID_MESSAGE_LENGTH) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid message length"); return FALSE; } if (status == AMT_STATUS_HOST_IF_EMPTY_RESPONSE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Intel AMT is disabled"); return FALSE; } g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unknown error"); return FALSE; } static gboolean amt_host_if_call (mei_context *mei_cl, const guchar *command, gssize command_sz, guint8 **read_buf, guint32 rcmd, guint expected_sz, unsigned long send_timeout, GError **error) { guint32 in_buf_sz; guint32 out_buf_sz; struct amt_host_if_resp_header *msg_hdr; in_buf_sz = mei_cl->buf_size; *read_buf = (guint8 *) g_malloc0 (in_buf_sz); msg_hdr = (struct amt_host_if_resp_header *) *read_buf; if (!mei_send_msg (mei_cl, command, command_sz, send_timeout, error)) return FALSE; if (!mei_recv_msg (mei_cl, *read_buf, in_buf_sz, &out_buf_sz, 2000, error)) return FALSE; if (out_buf_sz <= 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "empty response"); return FALSE; } if (expected_sz && expected_sz != out_buf_sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "expected %u but got %" G_GUINT32_FORMAT, expected_sz, out_buf_sz); return FALSE; } if (!amt_status_set_error (msg_hdr->status, error)) return FALSE; if (out_buf_sz < sizeof(struct amt_host_if_resp_header)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid response: too small"); return FALSE; } if (out_buf_sz != (msg_hdr->header.length + sizeof(struct amt_host_if_msg_header))) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid response: headerlen"); return FALSE; } if (msg_hdr->header.command != rcmd) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid response: rcmd"); return FALSE; } if (msg_hdr->header._reserved != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid response: reserved"); return FALSE; } if (msg_hdr->header.version.major != AMT_MAJOR_VERSION || msg_hdr->header.version.minor < AMT_MINOR_VERSION) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid response: version"); return FALSE; } return TRUE; } static gboolean amt_get_provisioning_state (mei_context *mei_cl, guint8 *state, GError **error) { g_autofree struct amt_host_if_resp_header *response = NULL; if (!amt_host_if_call (mei_cl, (const guchar *)&PROVISIONING_STATE_REQUEST, sizeof(PROVISIONING_STATE_REQUEST), (guint8 **)&response, AMT_HOST_IF_PROVISIONING_STATE_RESPONSE, 0, 5000, error)) { g_prefix_error (error, "unable to get provisioning state: "); return FALSE; } *state = response->data[0]; return TRUE; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(mei_context, mei_context_free) #pragma clang diagnostic pop static FuDevice * fu_plugin_amt_create_device (GError **error) { guint8 state; struct amt_code_versions ver; fwupd_guid_t uu; g_autofree gchar *guid_buf = NULL; g_autofree struct amt_host_if_resp_header *response = NULL; g_autoptr(FuDevice) dev = NULL; g_autoptr(GString) version_bl = g_string_new (NULL); g_autoptr(GString) version_fw = g_string_new (NULL); g_autoptr(mei_context) ctx = g_new0 (mei_context, 1); const uuid_le MEI_IAMTHIF = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, \ 0xac, 0xa8, 0x46, 0xe0, 0xff, 0x65, 0x81, 0x4c); /* create context */ if (!mei_context_new (ctx, &MEI_IAMTHIF, 0, error)) return NULL; /* check version */ if (!amt_host_if_call (ctx, (const guchar *) &CODE_VERSION_REQ, sizeof(CODE_VERSION_REQ), (guint8 **) &response, AMT_HOST_IF_CODE_VERSIONS_RESPONSE, 0, 5000, error)) { g_prefix_error (error, "Failed to check version: "); return NULL; } if (!amt_verify_code_versions (response, error)) { g_prefix_error (error, "failed to verify code versions: "); return NULL; } memcpy (&ver, response->data, sizeof(struct amt_code_versions)); dev = fu_device_new (); fu_device_set_id (dev, "/dev/mei0"); fu_device_set_vendor (dev, "Intel Corporation"); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon (dev, "computer"); fu_device_add_parent_guid (dev, "main-system-firmware"); if (!amt_get_provisioning_state (ctx, &state, error)) return NULL; switch (state) { case 0: fu_device_set_name (dev, "Intel AMT [unprovisioned]"); break; case 1: fu_device_set_name (dev, "Intel AMT [being provisioned]"); break; case 2: fu_device_set_name (dev, "Intel AMT [provisioned]"); break; default: fu_device_set_name (dev, "Intel AMT [unknown]"); break; } fu_device_set_summary (dev, "Hardware and firmware technology for remote " "out-of-band management"); /* add guid */ memcpy (&uu, &ctx->guid, 16); guid_buf = fwupd_guid_to_string ((const fwupd_guid_t *) &uu, FWUPD_GUID_FLAG_NONE); fu_device_add_guid (dev, guid_buf); /* get version numbers */ for (guint i = 0; i < ver.count; i++) { if (g_strcmp0 (ver.versions[i].description.string, "AMT") == 0) { g_string_append (version_fw, ver.versions[i].version.string); continue; } if (g_strcmp0 (ver.versions[i].description.string, "Recovery Version") == 0) { g_string_append (version_bl, ver.versions[i].version.string); continue; } if (g_strcmp0 (ver.versions[i].description.string, "Build Number") == 0) { g_string_append_printf (version_fw, ".%s", ver.versions[i].version.string); continue; } if (g_strcmp0 (ver.versions[i].description.string, "Recovery Build Num") == 0) { g_string_append_printf (version_bl, ".%s", ver.versions[i].version.string); continue; } } if (version_fw->len > 0) fu_device_set_version (dev, version_fw->str, FWUPD_VERSION_FORMAT_INTEL_ME); if (version_bl->len > 0) fu_device_set_version_bootloader (dev, version_bl->str); return g_steal_pointer (&dev); } void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { g_autoptr(FuDevice) dev = NULL; dev = fu_plugin_amt_create_device (error); if (dev == NULL) return FALSE; fu_plugin_device_add (plugin, dev); return TRUE; } fwupd-1.2.14/plugins/amt/meson.build000066400000000000000000000006441402665037500173440ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginAmt"'] shared_module('fu_plugin_amt', fu_hash, sources : [ 'fu-plugin-amt.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/ata/000077500000000000000000000000001402665037500151625ustar00rootroot00000000000000fwupd-1.2.14/plugins/ata/README.md000066400000000000000000000025341402665037500164450ustar00rootroot00000000000000ATA === Introduction ------------ This plugin allows updating ATA/ATAPI storage hardware. Devices are enumerated from the block devices and if ID_ATA_DOWNLOAD_MICROCODE is supported they can be updated with appropriate firmware file. Updating ATA devices is more dangerous than other hardware such as DFU or NVMe and should be tested carefully with the help of the drive vendor. The device GUID is read from the trimmed model string. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * org.t13.ata GUID Generation --------------- These device use the Microsoft DeviceInstanceId values, e.g. * `IDE\VENDOR[40]REVISION[8]` * `IDE\0VENDOR[40]` See https://docs.microsoft.com/en-us/windows-hardware/drivers/install/identifiers-for-ide-devices for more details. Quirk use --------- This plugin uses the following plugin-specific quirks: | Quirk | Description | Minimum fwupd version | |------------------------|-------------------------------------------|-----------------------| | `AtaTransferBlocks` | Blocks to transfer, or `0xffff` for max | 1.2.4 | | `AtaTransferMode` | The transfer mode, `0x3`, `0x7` or `0xe` | 1.2.4 | fwupd-1.2.14/plugins/ata/ata.quirk000066400000000000000000000000011402665037500167730ustar00rootroot00000000000000 fwupd-1.2.14/plugins/ata/fu-ata-device.c000066400000000000000000000502441402665037500177450ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include "fu-ata-device.h" #include "fu-chunk.h" #define FU_ATA_IDENTIFY_SIZE 512 /* bytes */ #define FU_ATA_BLOCK_SIZE 512 /* bytes */ struct ata_tf { guint8 dev; guint8 command; guint8 error; guint8 status; guint8 feat; guint8 nsect; guint8 lbal; guint8 lbam; guint8 lbah; }; #define ATA_USING_LBA (1 << 6) #define ATA_STAT_DRQ (1 << 3) #define ATA_STAT_ERR (1 << 0) #define ATA_OP_IDENTIFY 0xec #define ATA_OP_FLUSH_CACHE 0xe7 #define ATA_OP_DOWNLOAD_MICROCODE 0x92 #define ATA_OP_STANDBY_IMMEDIATE 0xe0 #define ATA_SUBCMD_MICROCODE_OBSOLETE 0x01 #define ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS_ACTIVATE 0x03 #define ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNK 0x07 #define ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS 0x0e #define ATA_SUBCMD_MICROCODE_ACTIVATE 0x0f #define SG_CHECK_CONDITION 0x02 #define SG_DRIVER_SENSE 0x08 #define SG_ATA_12 0xa1 #define SG_ATA_12_LEN 12 #define SG_ATA_PROTO_NON_DATA (3 << 1) #define SG_ATA_PROTO_PIO_IN (4 << 1) #define SG_ATA_PROTO_PIO_OUT (5 << 1) enum { SG_CDB2_TLEN_NODATA = 0 << 0, SG_CDB2_TLEN_FEAT = 1 << 0, SG_CDB2_TLEN_NSECT = 2 << 0, SG_CDB2_TLEN_BYTES = 0 << 2, SG_CDB2_TLEN_SECTORS = 1 << 2, SG_CDB2_TDIR_TO_DEV = 0 << 3, SG_CDB2_TDIR_FROM_DEV = 1 << 3, SG_CDB2_CHECK_COND = 1 << 5, }; struct _FuAtaDevice { FuUdevDevice parent_instance; guint pci_depth; guint usb_depth; gint fd; guint16 transfer_blocks; guint8 transfer_mode; }; G_DEFINE_TYPE (FuAtaDevice, fu_ata_device, FU_TYPE_UDEV_DEVICE) #ifndef HAVE_GUDEV_232 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref) #pragma clang diagnostic pop #endif guint8 fu_ata_device_get_transfer_mode (FuAtaDevice *self) { return self->transfer_mode; } guint16 fu_ata_device_get_transfer_blocks (FuAtaDevice *self) { return self->transfer_blocks; } static gchar * fu_ata_device_get_string (const guint16 *buf, guint start, guint end) { g_autoptr(GString) str = g_string_new (NULL); for (guint i = start; i <= end; i++) { g_string_append_c (str, (gchar) (buf[i] >> 8)); g_string_append_c (str, (gchar) (buf[i] & 0xff)); } /* remove whitespace before returning */ if (str->len > 0) { g_strstrip (str->str); if (str->str[0] == '\0') return NULL; } return g_string_free (g_steal_pointer (&str), FALSE); } static void fu_ata_device_to_string (FuDevice *device, GString *str) { FuAtaDevice *self = FU_ATA_DEVICE (device); g_string_append (str, " FuAtaDevice:\n"); g_string_append_printf (str, " fd:\t\t\t%i\n", self->fd); g_string_append_printf (str, " transfer-mode:\t0x%x\n", (guint) self->transfer_mode); g_string_append_printf (str, " transfer-size:\t0x%x\n", (guint) self->transfer_blocks); g_string_append_printf (str, " pci-depth:\t\t%u\n", self->pci_depth); g_string_append_printf (str, " usb-depth:\t\t%u\n", self->usb_depth); } /* https://docs.microsoft.com/en-us/windows-hardware/drivers/install/identifiers-for-ide-devices */ static gchar * fu_ata_device_pad_string_for_id (const gchar *name) { GString *str = g_string_new (name); fu_common_string_replace (str, " ", "_"); for (guint i = str->len; i < 40; i++) g_string_append_c (str, '_'); return g_string_free (str, FALSE); } static gchar * fu_ata_device_get_guid_safe (const guint16 *buf, guint16 addr_start) { if (!fu_common_guid_is_plausible ((guint8 *) (buf + addr_start))) return NULL; return fwupd_guid_to_string ((const fwupd_guid_t *) (buf + addr_start), FWUPD_GUID_FLAG_MIXED_ENDIAN); } static void fu_ata_device_parse_id_maybe_dell (FuAtaDevice *self, const guint16 *buf) { g_autofree gchar *component_id = NULL; g_autofree gchar *guid_efi = NULL; g_autofree gchar *guid_id = NULL; g_autofree gchar *guid = NULL; /* add extra component ID if set */ component_id = fu_ata_device_get_string (buf, 137, 140); if (component_id == NULL || !g_str_is_ascii (component_id) || strlen (component_id) < 6) { g_debug ("invalid component ID, skipping"); return; } /* do not add the FuUdevDevice instance IDs as generic firmware * should not be used on these OEM-specific devices */ fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS); /* add instance ID *and* GUID as using no-auto-instance-ids */ guid_id = g_strdup_printf ("STORAGE-DELL-%s", component_id); fu_device_add_instance_id (FU_DEVICE (self), guid_id); guid = fwupd_guid_hash_string (guid_id); fu_device_add_guid (FU_DEVICE (self), guid); /* also add the EFI GUID */ guid_efi = fu_ata_device_get_guid_safe (buf, 129); if (guid_efi != NULL) fu_device_add_guid (FU_DEVICE (self), guid_efi); } static gboolean fu_ata_device_parse_id (FuAtaDevice *self, const guint8 *buf, gsize sz, GError **error) { FuDevice *device = FU_DEVICE (self); guint16 xfer_min = 1; guint16 xfer_max = 0xffff; guint16 id[FU_ATA_IDENTIFY_SIZE/2]; g_autofree gchar *name_pad = NULL; g_autofree gchar *sku = NULL; /* check size */ if (sz != FU_ATA_IDENTIFY_SIZE) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "ID incorrect size, got 0x%02x", (guint) sz); return FALSE; } /* read LE buffer */ for (guint i = 0; i < sz / 2; i++) id[i] = fu_common_read_uint16 (buf + (i * 2), G_LITTLE_ENDIAN); /* verify drive correctly supports DOWNLOAD_MICROCODE */ if (!(id[83] & 1 && id[86] & 1)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "DOWNLOAD_MICROCODE not supported by device"); return FALSE; } fu_ata_device_parse_id_maybe_dell (self, id); /* firmware will be applied when the device restarts */ if (self->transfer_mode == ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS) fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); /* the newer, segmented transfer mode */ if (self->transfer_mode == ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS_ACTIVATE || self->transfer_mode == ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS) { xfer_min = id[234]; if (xfer_min == 0x0 || xfer_min == 0xffff) xfer_min = 1; xfer_max = id[235]; if (xfer_max == 0x0 || xfer_max == 0xffff) xfer_max = xfer_min; } /* fall back to a sane block size */ if (self->transfer_blocks == 0x0) self->transfer_blocks = xfer_min; else if (self->transfer_blocks == 0xffff) self->transfer_blocks = xfer_max; /* get values in case the kernel didn't */ if (fu_device_get_serial (device) == NULL) { g_autofree gchar *tmp = NULL; tmp = fu_ata_device_get_string (id, 10, 19); if (tmp != NULL) fu_device_set_serial (device, tmp); } if (fu_device_get_name (device) == NULL) { g_autofree gchar *tmp = NULL; tmp = fu_ata_device_get_string (id, 27, 46); if (tmp != NULL) fu_device_set_name (device, tmp); } if (fu_device_get_version (device) == NULL) { g_autofree gchar *tmp = NULL; tmp = fu_ata_device_get_string (id, 23, 26); if (tmp != NULL) fu_device_set_version (device, tmp, FWUPD_VERSION_FORMAT_PLAIN); } else { fu_device_set_version_format (device, FWUPD_VERSION_FORMAT_PLAIN); } /* 8 byte additional product identifier == SKU? */ sku = fu_ata_device_get_string (id, 170, 173); if (sku != NULL) g_debug ("SKU=%s", sku); /* if we have vendor defined identify blocks don't add generic GUID */ if (fu_device_get_guids (device)->len != 0) return TRUE; /* add extra GUIDs if none detected from identify block */ name_pad = fu_ata_device_pad_string_for_id (fu_device_get_name (device)); if (name_pad != NULL && fu_device_get_version (device) != NULL) { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf ("IDE\\%s%s", name_pad, fu_device_get_version (device)); fu_device_add_instance_id (device, tmp); } if (name_pad != NULL) { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf ("IDE\\0%s", name_pad); fu_device_add_instance_id (device, tmp); } /* add the name fallback */ fu_device_add_instance_id (device, fu_device_get_name (device)); return TRUE; } static gboolean fu_ata_device_open (FuDevice *device, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE (device); GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device)); /* open device */ self->fd = g_open (g_udev_device_get_device_file (udev_device), O_RDONLY); if (self->fd < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to open %s: %s", g_udev_device_get_device_file (udev_device), strerror (errno)); return FALSE; } /* success */ return TRUE; } static gboolean fu_ata_device_probe (FuUdevDevice *device, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE (device); /* set the physical ID */ if (!fu_udev_device_set_physical_id (device, "scsi", error)) return FALSE; /* look at the PCI and USB depth to work out if in an external enclosure */ self->pci_depth = fu_udev_device_get_slot_depth (device, "pci"); self->usb_depth = fu_udev_device_get_slot_depth (device, "usb"); if (self->pci_depth <= 2 && self->usb_depth <= 2) fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL); return TRUE; } static guint64 fu_ata_device_tf_to_pack_id (struct ata_tf *tf) { guint32 lba24 = (tf->lbah << 16) | (tf->lbam << 8) | (tf->lbal); guint32 lbah = tf->dev & 0x0f; return (((guint64) lbah) << 24) | (guint64) lba24; } static gboolean fu_ata_device_command (FuAtaDevice *self, struct ata_tf *tf, gint dxfer_direction, guint timeout_ms, guint8 *dxferp, gsize dxfer_len, GError **error) { guint8 cdb[SG_ATA_12_LEN] = { 0x0 }; guint8 sb[32] = { 0x0 }; sg_io_hdr_t io_hdr = { 0x0 }; /* map _TO_DEV to PIO mode */ if (dxfer_direction == SG_DXFER_TO_DEV) cdb[1] = SG_ATA_PROTO_PIO_OUT; else if (dxfer_direction == SG_DXFER_FROM_DEV) cdb[1] = SG_ATA_PROTO_PIO_IN; else cdb[1] = SG_ATA_PROTO_NON_DATA; /* libata workaround: don't demand sense data for IDENTIFY */ if (dxfer_len > 0) { cdb[2] |= SG_CDB2_TLEN_NSECT | SG_CDB2_TLEN_SECTORS; cdb[2] |= dxfer_direction == SG_DXFER_TO_DEV ? SG_CDB2_TDIR_TO_DEV : SG_CDB2_TDIR_FROM_DEV; } else { cdb[2] = SG_CDB2_CHECK_COND; } /* populate non-LBA48 CDB */ cdb[0] = SG_ATA_12; cdb[3] = tf->feat; cdb[4] = tf->nsect; cdb[5] = tf->lbal; cdb[6] = tf->lbam; cdb[7] = tf->lbah; cdb[8] = tf->dev; cdb[9] = tf->command; fu_common_dump_raw (G_LOG_DOMAIN, "CBD", cdb, sizeof(cdb)); if (dxfer_direction == SG_DXFER_TO_DEV && dxferp != NULL) { fu_common_dump_raw (G_LOG_DOMAIN, "outgoing_data", dxferp, dxfer_len); } /* hit hardware */ io_hdr.interface_id = 'S'; io_hdr.mx_sb_len = sizeof(sb); io_hdr.dxfer_direction = dxfer_direction; io_hdr.dxfer_len = dxfer_len; io_hdr.dxferp = dxferp; io_hdr.cmdp = cdb; io_hdr.cmd_len = SG_ATA_12_LEN; io_hdr.sbp = sb; io_hdr.pack_id = fu_ata_device_tf_to_pack_id (tf); io_hdr.timeout = timeout_ms; if (ioctl (self->fd, SG_IO, &io_hdr) == -1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SG_IO not supported: %s", strerror (errno)); return FALSE; } g_debug ("ATA_%u status=0x%x, host_status=0x%x, driver_status=0x%x", io_hdr.cmd_len, io_hdr.status, io_hdr.host_status, io_hdr.driver_status); fu_common_dump_raw (G_LOG_DOMAIN, "SB", sb, sizeof(sb)); /* error check */ if (io_hdr.status && io_hdr.status != SG_CHECK_CONDITION) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad status: 0x%x", io_hdr.status); return FALSE; } if (io_hdr.host_status) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad host status: 0x%x", io_hdr.host_status); return FALSE; } if (io_hdr.driver_status && (io_hdr.driver_status != SG_DRIVER_SENSE)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad driver status: 0x%x", io_hdr.driver_status); return FALSE; } /* repopulate ata_tf */ tf->error = sb[8 + 3]; tf->nsect = sb[8 + 5]; tf->lbal = sb[8 + 7]; tf->lbam = sb[8 + 9]; tf->lbah = sb[8 + 11]; tf->dev = sb[8 + 12]; tf->status = sb[8 + 13]; g_debug ("ATA_%u stat=%02x err=%02x nsect=%02x lbal=%02x lbam=%02x lbah=%02x dev=%02x", io_hdr.cmd_len, tf->status, tf->error, tf->nsect, tf->lbal, tf->lbam, tf->lbah, tf->dev); /* io error */ if (tf->status & (ATA_STAT_ERR | ATA_STAT_DRQ)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "I/O error, ata_op=0x%02x ata_status=0x%02x ata_error=0x%02x", tf->command, tf->status, tf->error); return FALSE; } /* success */ return TRUE; } static gboolean fu_ata_device_setup (FuDevice *device, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE (device); struct ata_tf tf = { 0x0 }; guint8 id[FU_ATA_IDENTIFY_SIZE]; /* get ID block */ tf.dev = ATA_USING_LBA; tf.command = ATA_OP_IDENTIFY; tf.nsect = 1; /* 512 bytes */ if (!fu_ata_device_command (self, &tf, SG_DXFER_FROM_DEV, 1000, id, sizeof(id), error)) { g_prefix_error (error, "failed to IDENTIFY"); return FALSE; } if (!fu_ata_device_parse_id (self, id, sizeof(id), error)) return FALSE; /* success */ return TRUE; } static gboolean fu_ata_device_activate (FuDevice *device, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE (device); struct ata_tf tf = { 0x0 }; /* flush cache and put drive in standby to prepare to activate */ tf.dev = ATA_USING_LBA; tf.command = ATA_OP_FLUSH_CACHE; if (!fu_ata_device_command (self, &tf, SG_DXFER_NONE, 120 * 1000, /* a long time! */ NULL, 0, error)) { g_prefix_error (error, "failed to flush cache immediate: "); return FALSE; } tf.command = ATA_OP_STANDBY_IMMEDIATE; if (!fu_ata_device_command (self, &tf, SG_DXFER_NONE, 120 * 1000, /* a long time! */ NULL, 0, error)) { g_prefix_error (error, "failed to standby immediate: "); return FALSE; } /* load the new firmware */ tf.dev = 0xa0 | ATA_USING_LBA; tf.command = ATA_OP_DOWNLOAD_MICROCODE; tf.feat = ATA_SUBCMD_MICROCODE_ACTIVATE; if (!fu_ata_device_command (self, &tf, SG_DXFER_NONE, 120 * 1000, /* a long time! */ NULL, 0, error)) { g_prefix_error (error, "failed to activate firmware: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_ata_device_close (FuDevice *device, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE (device); if (!g_close (self->fd, error)) return FALSE; self->fd = 0; return TRUE; } static gboolean fu_ata_device_fw_download (FuAtaDevice *self, guint32 idx, guint32 addr, const guint8 *data, guint32 data_sz, GError **error) { struct ata_tf tf = { 0x0 }; guint32 block_count = data_sz / FU_ATA_BLOCK_SIZE; guint32 buffer_offset = addr / FU_ATA_BLOCK_SIZE; /* write block */ tf.dev = 0xa0 | ATA_USING_LBA; tf.command = ATA_OP_DOWNLOAD_MICROCODE; tf.feat = self->transfer_mode; tf.nsect = block_count & 0xff; tf.lbal = block_count >> 8; tf.lbam = buffer_offset & 0xff; tf.lbah = buffer_offset >> 8; if (!fu_ata_device_command (self, &tf, SG_DXFER_TO_DEV, 120 * 1000, /* a long time! */ (guint8 *) data, data_sz, error)) { g_prefix_error (error, "failed to write firmware @0x%0x", (guint) addr); return FALSE; } /* check drive status */ if (tf.nsect == 0x0) return TRUE; /* drive wants more data, or thinks it is all done */ if (tf.nsect == 0x1 || tf.nsect == 0x2) return TRUE; /* the offset was set up incorrectly */ if (tf.nsect == 0x4) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "alignment error"); return FALSE; } /* other error */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unknown return code 0x%02x", tf.nsect); return FALSE; } static gboolean fu_ata_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE (device); guint32 chunksz = (guint32) self->transfer_blocks * FU_ATA_BLOCK_SIZE; guint max_size = 0xffff * FU_ATA_BLOCK_SIZE; g_autoptr(GPtrArray) chunks = NULL; /* only one block allowed */ if (self->transfer_mode == ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNK) max_size = 0xffff; /* check is valid */ if (g_bytes_get_size (fw) > max_size) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware is too large, maximum size is %u", max_size); return FALSE; } if (g_bytes_get_size (fw) % FU_ATA_BLOCK_SIZE != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware is not multiple of block size %i", FU_ATA_BLOCK_SIZE); return FALSE; } /* write each block */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); chunks = fu_chunk_array_new_from_bytes (fw, 0x00, 0x00, chunksz); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); if (!fu_ata_device_fw_download (self, chk->idx, chk->address, chk->data, chk->data_sz, error)) { g_prefix_error (error, "failed to write chunk %u: ", i); return FALSE; } fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len + 1); } /* success! */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); fu_device_set_progress (device, 100); return TRUE; } static gboolean fu_ata_device_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE (device); if (g_strcmp0 (key, "AtaTransferMode") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp != ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS_ACTIVATE && tmp != ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS && tmp != ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNK) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "AtaTransferMode only supports " "values 0x3, 0x7 or 0xe"); return FALSE; } self->transfer_mode = (guint8) tmp; return TRUE; } if (g_strcmp0 (key, "AtaTransferBlocks") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp > 0xffff) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "AtaTransferBlocks only supports " "values <= 0xffff"); return FALSE; } self->transfer_blocks = (guint16) tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_ata_device_init (FuAtaDevice *self) { /* we chose this default as _DOWNLOAD_CHUNKS_ACTIVATE applies the * firmware straight away and the kernel might not like the unexpected * ATA restart and panic */ self->transfer_mode = ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS; fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_summary (FU_DEVICE (self), "ATA Drive"); fu_device_add_icon (FU_DEVICE (self), "drive-harddisk"); } static void fu_ata_device_finalize (GObject *object) { G_OBJECT_CLASS (fu_ata_device_parent_class)->finalize (object); } static void fu_ata_device_class_init (FuAtaDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUdevDeviceClass *klass_udev_device = FU_UDEV_DEVICE_CLASS (klass); object_class->finalize = fu_ata_device_finalize; klass_device->to_string = fu_ata_device_to_string; klass_device->set_quirk_kv = fu_ata_device_set_quirk_kv; klass_device->open = fu_ata_device_open; klass_device->setup = fu_ata_device_setup; klass_device->activate = fu_ata_device_activate; klass_device->close = fu_ata_device_close; klass_device->write_firmware = fu_ata_device_write_firmware; klass_udev_device->probe = fu_ata_device_probe; } FuAtaDevice * fu_ata_device_new (FuUdevDevice *device) { FuAtaDevice *self = g_object_new (FU_TYPE_ATA_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } FuAtaDevice * fu_ata_device_new_from_blob (const guint8 *buf, gsize sz, GError **error) { g_autoptr(FuAtaDevice) self = g_object_new (FU_TYPE_ATA_DEVICE, NULL); if (!fu_ata_device_parse_id (self, buf, sz, error)) return NULL; return g_steal_pointer (&self); } fwupd-1.2.14/plugins/ata/fu-ata-device.h000066400000000000000000000011601402665037500177430ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_ATA_DEVICE (fu_ata_device_get_type ()) G_DECLARE_FINAL_TYPE (FuAtaDevice, fu_ata_device, FU, ATA_DEVICE, FuUdevDevice) FuAtaDevice *fu_ata_device_new (FuUdevDevice *device); FuAtaDevice *fu_ata_device_new_from_blob (const guint8 *buf, gsize sz, GError **error); /* for self tests */ guint8 fu_ata_device_get_transfer_mode (FuAtaDevice *self); guint16 fu_ata_device_get_transfer_blocks (FuAtaDevice *self); G_END_DECLS fwupd-1.2.14/plugins/ata/fu-plugin-ata.c000066400000000000000000000035011402665037500177760ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-ata-device.h" gboolean fu_plugin_udev_device_added (FuPlugin *plugin, FuUdevDevice *device, GError **error) { GUdevDevice *udev_device = fu_udev_device_get_dev (device); g_autoptr(FuAtaDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* interesting device? */ if (udev_device == NULL) return TRUE; if (g_strcmp0 (g_udev_device_get_subsystem (udev_device), "block") != 0) return TRUE; if (g_strcmp0 (g_udev_device_get_devtype (udev_device), "disk") != 0) return TRUE; if (!g_udev_device_get_property_as_boolean (udev_device, "ID_ATA_SATA")) return TRUE; if (!g_udev_device_get_property_as_boolean (udev_device, "ID_ATA_DOWNLOAD_MICROCODE")) return TRUE; dev = fu_ata_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_udev_subsystem (plugin, "block"); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "org.t13.ata"); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } gboolean fu_plugin_activate (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_activate (device, error); } fwupd-1.2.14/plugins/ata/fu-self-test.c000066400000000000000000000025221402665037500176450ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ata-device.h" #include "fu-test.h" static void fu_ata_id_func (void) { gboolean ret; gsize sz; g_autofree gchar *data = NULL; g_autofree gchar *path = NULL; g_autoptr(FuAtaDevice) dev = NULL; g_autoptr(GError) error = NULL; path = fu_test_get_filename (TESTDATADIR, "StarDrive-SBFM61.2.bin"); g_assert_nonnull (path); ret = g_file_get_contents (path, &data, &sz, &error); g_assert_no_error (error); g_assert (ret); dev = fu_ata_device_new_from_blob ((guint8 *)data, sz, &error); g_assert_no_error (error); g_assert_nonnull (dev); g_assert_cmpint (fu_ata_device_get_transfer_mode (dev), ==, 0xe); g_assert_cmpint (fu_ata_device_get_transfer_blocks (dev), ==, 0x1); g_assert_cmpstr (fu_device_get_serial (FU_DEVICE (dev)), ==, "A45A078A198600476509"); g_assert_cmpstr (fu_device_get_name (FU_DEVICE (dev)), ==, "SATA SSD"); g_assert_cmpstr (fu_device_get_version (FU_DEVICE (dev)), ==, "SBFM61.2"); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func ("/fwupd/id", fu_ata_id_func); return g_test_run (); } fwupd-1.2.14/plugins/ata/meson.build000066400000000000000000000022541402665037500173270ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginAta"'] install_data([ 'ata.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_ata', fu_hash, sources : [ 'fu-plugin-ata.c', 'fu-ata-device.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with : [ libfwupdprivate, ], dependencies : [ plugin_deps, ], ) if get_option('tests') testdatadir = join_paths(meson.current_source_dir(), 'tests') cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'ata-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-ata-device.c', ], include_directories : [ include_directories('..'), include_directories('../..'), include_directories('../../libfwupd'), include_directories('../../src'), ], dependencies : [ plugin_deps, ], link_with : [ libfwupdprivate, ], c_args : cargs ) test('ata-self-test', e) endif fwupd-1.2.14/plugins/ata/tests/000077500000000000000000000000001402665037500163245ustar00rootroot00000000000000fwupd-1.2.14/plugins/ata/tests/StarDrive-SBFM61.2.bin000066400000000000000000000010001402665037500217440ustar00rootroot00000000000000@?7?4AA570A8916800745690BSMF162.ASATS DS @/@??xxxxLDkt}cAitcA@ D@@@)@9fwupd-1.2.14/plugins/colorhug/000077500000000000000000000000001402665037500162375ustar00rootroot00000000000000fwupd-1.2.14/plugins/colorhug/README.md000066400000000000000000000013561402665037500175230ustar00rootroot00000000000000ColorHug Support ================ Introduction ------------ The ColorHug is an affordable open source display colorimeter built by Hughski Limited. The USB device allows you to calibrate your screen for accurate color matching. ColorHug versions 1 and 2 support a custom HID-based flashing protocol, but version 3 (ColorHug+) has now switched to DFU. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * com.hughski.colorhug GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_273F&PID_1001&REV_0001` * `USB\VID_273F&PID_1001` * `USB\VID_273F` fwupd-1.2.14/plugins/colorhug/colorhug.quirk000066400000000000000000000027161402665037500211440ustar00rootroot00000000000000# ColorHug1 [DeviceInstanceId=USB\VID_273F&PID_1000] Plugin = colorhug Flags = is-bootloader Guid = 40338ceb-b966-4eae-adae-9c32edfcc484 FirmwareSizeMin = 0x2000 FirmwareSizeMax = 0x8000 CounterpartGuid = USB\VID_273F&PID_1001 InstallDuration = 8 [DeviceInstanceId=USB\VID_273F&PID_1001] Plugin = colorhug Flags = none Summary = An open source display colorimeter Icon = colorimeter-colorhug Guid = 40338ceb-b966-4eae-adae-9c32edfcc484 CounterpartGuid = USB\VID_273F&PID_1000 InstallDuration = 8 # ColorHug2 [DeviceInstanceId=USB\VID_273F&PID_1004] Plugin = colorhug Flags = none Summary = An open source display colorimeter Icon = colorimeter-colorhug Guid = 2082b5e0-7a64-478a-b1b2-e3404fab6dad FirmwareSizeMin = 0x2000 FirmwareSizeMax = 0x8000 CounterpartGuid = USB\VID_273F&PID_1005 InstallDuration = 8 [DeviceInstanceId=USB\VID_273F&PID_1005] Plugin = colorhug Flags = is-bootloader Guid = 2082b5e0-7a64-478a-b1b2-e3404fab6dad CounterpartGuid = USB\VID_273F&PID_1004 InstallDuration = 8 # ColorHugALS [DeviceInstanceId=USB\VID_273F&PID_1007] Plugin = colorhug Flags = halfsize,none Summary = An open source ambient light sensor Guid = 84f40464-9272-4ef7-9399-cd95f12da696 FirmwareSizeMin = 0x1000 FirmwareSizeMax = 0x4000 CounterpartGuid = USB\VID_273F&PID_1006 InstallDuration = 5 [DeviceInstanceId=USB\VID_273F&PID_1006] Plugin = colorhug Flags = halfsize,is-bootloader Guid = 84f40464-9272-4ef7-9399-cd95f12da696 CounterpartGuid = USB\VID_273F&PID_1007 InstallDuration = 5 fwupd-1.2.14/plugins/colorhug/fu-colorhug-common.c000066400000000000000000000057431402665037500221340ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-colorhug-common.h" const gchar * ch_strerror (ChError error_enum) { if (error_enum == CH_ERROR_NONE) return "Success"; if (error_enum == CH_ERROR_UNKNOWN_CMD) return "Unknown command"; if (error_enum == CH_ERROR_WRONG_UNLOCK_CODE) return "Wrong unlock code"; if (error_enum == CH_ERROR_NOT_IMPLEMENTED) return "Not implemented"; if (error_enum == CH_ERROR_UNDERFLOW_SENSOR) return "Underflow of sensor"; if (error_enum == CH_ERROR_NO_SERIAL) return "No serial"; if (error_enum == CH_ERROR_WATCHDOG) return "Watchdog"; if (error_enum == CH_ERROR_INVALID_ADDRESS) return "Invalid address"; if (error_enum == CH_ERROR_INVALID_LENGTH) return "Invalid length"; if (error_enum == CH_ERROR_INVALID_CHECKSUM) return "Invalid checksum"; if (error_enum == CH_ERROR_INVALID_VALUE) return "Invalid value"; if (error_enum == CH_ERROR_UNKNOWN_CMD_FOR_BOOTLOADER) return "Unknown command for bootloader"; if (error_enum == CH_ERROR_OVERFLOW_MULTIPLY) return "Overflow of multiply"; if (error_enum == CH_ERROR_OVERFLOW_ADDITION) return "Overflow of addition"; if (error_enum == CH_ERROR_OVERFLOW_SENSOR) return "Overflow of sensor"; if (error_enum == CH_ERROR_OVERFLOW_STACK) return "Overflow of stack"; if (error_enum == CH_ERROR_NO_CALIBRATION) return "No calibration"; if (error_enum == CH_ERROR_DEVICE_DEACTIVATED) return "Device deactivated"; if (error_enum == CH_ERROR_INCOMPLETE_REQUEST) return "Incomplete previous request"; if (error_enum == CH_ERROR_SELF_TEST_SENSOR) return "Self test failed: Sensor"; if (error_enum == CH_ERROR_SELF_TEST_RED) return "Self test failed: Red"; if (error_enum == CH_ERROR_SELF_TEST_GREEN) return "Self test failed: Green"; if (error_enum == CH_ERROR_SELF_TEST_BLUE) return "Self test failed: Blue"; if (error_enum == CH_ERROR_SELF_TEST_MULTIPLIER) return "Self test failed: Multiplier"; if (error_enum == CH_ERROR_SELF_TEST_COLOR_SELECT) return "Self test failed: Color Select"; if (error_enum == CH_ERROR_SELF_TEST_TEMPERATURE) return "Self test failed: Temperature"; if (error_enum == CH_ERROR_INVALID_CALIBRATION) return "Invalid calibration"; if (error_enum == CH_ERROR_SRAM_FAILED) return "SRAM failed"; if (error_enum == CH_ERROR_OUT_OF_MEMORY) return "Out of memory"; if (error_enum == CH_ERROR_SELF_TEST_I2C) return "Self test failed: I2C"; if (error_enum == CH_ERROR_SELF_TEST_ADC_VDD) return "Self test failed: ADC Vdd"; if (error_enum == CH_ERROR_SELF_TEST_ADC_VSS) return "Self test failed: ADC Vss"; if (error_enum == CH_ERROR_SELF_TEST_ADC_VREF) return "Self test failed: ADC Vref"; if (error_enum == CH_ERROR_I2C_SLAVE_ADDRESS) return "I2C set slave address failed"; if (error_enum == CH_ERROR_I2C_SLAVE_CONFIG) return "I2C set slave config failed"; if (error_enum == CH_ERROR_SELF_TEST_EEPROM) return "Self test failed: EEPROM"; return NULL; } fwupd-1.2.14/plugins/colorhug/fu-colorhug-common.h000066400000000000000000000023541402665037500221340ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS typedef enum { CH_ERROR_NONE, CH_ERROR_UNKNOWN_CMD, CH_ERROR_WRONG_UNLOCK_CODE, CH_ERROR_NOT_IMPLEMENTED, CH_ERROR_UNDERFLOW_SENSOR, CH_ERROR_NO_SERIAL, CH_ERROR_WATCHDOG, CH_ERROR_INVALID_ADDRESS, CH_ERROR_INVALID_LENGTH, CH_ERROR_INVALID_CHECKSUM, CH_ERROR_INVALID_VALUE, CH_ERROR_UNKNOWN_CMD_FOR_BOOTLOADER, CH_ERROR_NO_CALIBRATION, CH_ERROR_OVERFLOW_MULTIPLY, CH_ERROR_OVERFLOW_ADDITION, CH_ERROR_OVERFLOW_SENSOR, CH_ERROR_OVERFLOW_STACK, CH_ERROR_DEVICE_DEACTIVATED, CH_ERROR_INCOMPLETE_REQUEST, CH_ERROR_SELF_TEST_SENSOR, CH_ERROR_SELF_TEST_RED, CH_ERROR_SELF_TEST_GREEN, CH_ERROR_SELF_TEST_BLUE, CH_ERROR_SELF_TEST_COLOR_SELECT, CH_ERROR_SELF_TEST_MULTIPLIER, CH_ERROR_INVALID_CALIBRATION, CH_ERROR_SRAM_FAILED, CH_ERROR_OUT_OF_MEMORY, CH_ERROR_SELF_TEST_TEMPERATURE, CH_ERROR_SELF_TEST_I2C, CH_ERROR_SELF_TEST_ADC_VDD, CH_ERROR_SELF_TEST_ADC_VSS, CH_ERROR_SELF_TEST_ADC_VREF, CH_ERROR_I2C_SLAVE_ADDRESS, CH_ERROR_I2C_SLAVE_CONFIG, CH_ERROR_SELF_TEST_EEPROM, CH_ERROR_LAST } ChError; const gchar *ch_strerror (ChError error_enum); G_END_DECLS fwupd-1.2.14/plugins/colorhug/fu-colorhug-device.c000066400000000000000000000300271402665037500220740ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-chunk.h" #include "fu-colorhug-common.h" #include "fu-colorhug-device.h" /** * FU_COLORHUG_DEVICE_FLAG_HALFSIZE: * * Some devices have a compact memory layout and the application code starts * earlier. * * Since: 1.0.3 */ #define FU_COLORHUG_DEVICE_FLAG_HALFSIZE "halfsize" struct _FuColorhugDevice { FuUsbDevice parent_instance; guint16 start_addr; }; G_DEFINE_TYPE (FuColorhugDevice, fu_colorhug_device, FU_TYPE_USB_DEVICE) #define CH_CMD_GET_FIRMWARE_VERSION 0x07 #define CH_CMD_RESET 0x24 #define CH_CMD_READ_FLASH 0x25 #define CH_CMD_WRITE_FLASH 0x26 #define CH_CMD_BOOT_FLASH 0x27 #define CH_CMD_SET_FLASH_SUCCESS 0x28 #define CH_CMD_ERASE_FLASH 0x29 #define CH_USB_HID_EP 0x0001 #define CH_USB_HID_EP_IN (CH_USB_HID_EP | 0x80) #define CH_USB_HID_EP_OUT (CH_USB_HID_EP | 0x00) #define CH_USB_HID_EP_SIZE 64 #define CH_USB_CONFIG 0x0001 #define CH_USB_INTERFACE 0x0000 #define CH_EEPROM_ADDR_RUNCODE 0x4000 #define CH_EEPROM_ADDR_RUNCODE_ALS 0x2000 #define CH_DEVICE_USB_TIMEOUT 5000 /* ms */ #define CH_FLASH_TRANSFER_BLOCK_SIZE 0x020 /* 32 */ static gboolean fu_colorhug_device_msg (FuColorhugDevice *self, guint8 cmd, guint8 *ibuf, gsize ibufsz, guint8 *obuf, gsize obufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); guint8 buf[] = { [0] = cmd, [1 ... CH_USB_HID_EP_SIZE - 1] = 0x00 }; gsize actual_length = 0; /* check size */ if (ibufsz > sizeof(buf) - 1) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot process chunk of size %" G_GSIZE_FORMAT, ibufsz); return FALSE; } if (obufsz > sizeof(buf) - 2) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot process chunk of size %" G_GSIZE_FORMAT, ibufsz); return FALSE; } /* optionally copy in data */ if (ibuf != NULL) memcpy (buf + 1, ibuf, ibufsz); /* request */ if (g_getenv ("FWUPD_COLORHUG_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "REQ", buf, ibufsz + 1); if (!g_usb_device_interrupt_transfer (usb_device, CH_USB_HID_EP_OUT, buf, sizeof(buf), &actual_length, CH_DEVICE_USB_TIMEOUT, NULL, /* cancellable */ error)) { g_prefix_error (error, "failed to send request: "); return FALSE; } if (actual_length != CH_USB_HID_EP_SIZE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "request not all sent, got %" G_GSIZE_FORMAT, actual_length); return FALSE; } /* read reply */ if (!g_usb_device_interrupt_transfer (usb_device, CH_USB_HID_EP_IN, buf, sizeof(buf), &actual_length, CH_DEVICE_USB_TIMEOUT, NULL, /* cancellable */ error)) { g_prefix_error (error, "failed to get reply: "); return FALSE; } if (g_getenv ("FWUPD_COLORHUG_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "RES", buf, actual_length); /* old bootloaders do not return the full block */ if (actual_length != CH_USB_HID_EP_SIZE && actual_length != 2 && actual_length != obufsz + 2) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "request not all received, got %" G_GSIZE_FORMAT, actual_length); return FALSE; } /* check error code */ if (buf[0] != CH_ERROR_NONE) { const gchar *msg = ch_strerror (buf[0]); if (msg == NULL) msg = "unknown error"; g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, msg); return FALSE; } /* check cmd matches */ if (buf[1] != cmd) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cmd incorrect, expected %u, got %u", cmd, buf[1]); return FALSE; } /* copy back optional buf */ if (obuf != NULL) memcpy (obuf, buf + 2, obufsz); return TRUE; } static gboolean fu_colorhug_device_detach (FuDevice *device, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE (device); g_autoptr(GError) error_local = NULL; fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!fu_colorhug_device_msg (self, CH_CMD_RESET, NULL, 0, /* in */ NULL, 0, /* out */ &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to reset device: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_colorhug_device_attach (FuDevice *device, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE (device); g_autoptr(GError) error_local = NULL; fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!fu_colorhug_device_msg (self, CH_CMD_BOOT_FLASH, NULL, 0, /* in */ NULL, 0, /* out */ &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to boot to runtime: %s", error_local->message); return FALSE; } return TRUE; } gboolean fu_colorhug_device_set_flash_success (FuColorhugDevice *self, gboolean val, GError **error) { guint8 buf[] = { [0] = val ? 0x01 : 0x00 }; g_autoptr(GError) error_local = NULL; g_debug ("setting flash success"); if (!fu_colorhug_device_msg (self, CH_CMD_SET_FLASH_SUCCESS, buf, sizeof(buf), /* in */ NULL, 0, /* out */ &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to set flash success: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_colorhug_device_erase (FuColorhugDevice *self, guint16 addr, gsize sz, GError **error) { guint8 buf[4]; g_autoptr(GError) error_local = NULL; fu_common_write_uint16 (buf + 0, addr, G_LITTLE_ENDIAN); fu_common_write_uint16 (buf + 2, sz, G_LITTLE_ENDIAN); if (!fu_colorhug_device_msg (self, CH_CMD_ERASE_FLASH, buf, sizeof(buf), /* in */ NULL, 0, /* out */ &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to erase device: %s", error_local->message); return FALSE; } return TRUE; } static gchar * fu_colorhug_device_get_version (FuColorhugDevice *self, GError **error) { guint8 buf[6]; if (!fu_colorhug_device_msg (self, CH_CMD_GET_FIRMWARE_VERSION, NULL, 0, /* in */ buf, sizeof(buf), /* out */ error)) { return NULL; } return g_strdup_printf ("%i.%i.%i", fu_common_read_uint16 (buf + 0, G_LITTLE_ENDIAN), fu_common_read_uint16 (buf + 2, G_LITTLE_ENDIAN), fu_common_read_uint16 (buf + 4, G_LITTLE_ENDIAN)); } static gboolean fu_colorhug_device_probe (FuUsbDevice *device, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE (device); /* compact memory layout */ if (fu_device_has_custom_flag (FU_DEVICE (device), FU_COLORHUG_DEVICE_FLAG_HALFSIZE)) self->start_addr = CH_EEPROM_ADDR_RUNCODE_ALS; /* add hardcoded bits */ fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE); /* success */ return TRUE; } static gboolean fu_colorhug_device_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* got the version using the HID API */ if (!g_usb_device_set_configuration (usb_device, CH_USB_CONFIG, error)) return FALSE; if (!g_usb_device_claim_interface (usb_device, CH_USB_INTERFACE, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { return FALSE; } /* success */ return TRUE; } static gboolean fu_colorhug_device_setup (FuDevice *device, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE (device); if (fu_device_get_version (FU_DEVICE (device)) == NULL) { g_autofree gchar *version = NULL; g_autoptr(GError) error_local = NULL; version = fu_colorhug_device_get_version (self, &error_local); if (version != NULL) { g_debug ("obtained fwver using API '%s'", version); fu_device_set_version (device, version, FWUPD_VERSION_FORMAT_TRIPLET); } else { g_warning ("failed to get firmware version: %s", error_local->message); } } /* success */ return TRUE; } static guint8 ch_colorhug_device_calculate_checksum (const guint8 *data, guint32 len) { guint8 checksum = 0xff; for (guint32 i = 0; i < len; i++) checksum ^= data[i]; return checksum; } static gboolean fu_colorhug_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE (device); g_autoptr(GPtrArray) chunks = NULL; /* build packets */ chunks = fu_chunk_array_new_from_bytes (fw, self->start_addr, 0x00, /* page_sz */ CH_FLASH_TRANSFER_BLOCK_SIZE); /* don't auto-boot firmware */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); if (!fu_colorhug_device_set_flash_success (self, FALSE, error)) return FALSE; /* erase flash */ if (!fu_colorhug_device_erase (self, self->start_addr, g_bytes_get_size (fw), error)) return FALSE; /* write each block */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); guint8 buf[CH_FLASH_TRANSFER_BLOCK_SIZE+4]; g_autoptr(GError) error_local = NULL; /* set address, length, checksum, data */ fu_common_write_uint16 (buf + 0, chk->address, G_LITTLE_ENDIAN); buf[2] = chk->data_sz; buf[3] = ch_colorhug_device_calculate_checksum (chk->data, chk->data_sz); memcpy (buf + 4, chk->data, chk->data_sz); if (!fu_colorhug_device_msg (self, CH_CMD_WRITE_FLASH, buf, sizeof(buf), /* in */ NULL, 0, /* out */ &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write: %s", error_local->message); return FALSE; } /* update progress */ fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len * 2); } /* verify each block */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); guint8 buf[3]; guint8 buf_out[CH_FLASH_TRANSFER_BLOCK_SIZE+1]; g_autoptr(GError) error_local = NULL; /* set address */ fu_common_write_uint16 (buf + 0, chk->address, G_LITTLE_ENDIAN); buf[2] = chk->data_sz; if (!fu_colorhug_device_msg (self, CH_CMD_READ_FLASH, buf, sizeof(buf), /* in */ buf_out, sizeof(buf_out), /* out */ &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read: %s", error_local->message); return FALSE; } /* verify */ if (memcmp (buf_out + 1, chk->data, chk->data_sz) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to verify firmware for chunk %u, " "address 0x%0x, length 0x%0x", i, (guint) chk->address, chk->data_sz); return FALSE; } /* update progress */ fu_device_set_progress_full (device, (gsize) chunks->len + i, (gsize) chunks->len * 2); } /* success! */ return TRUE; } static void fu_colorhug_device_init (FuColorhugDevice *self) { /* this is the application code */ self->start_addr = CH_EEPROM_ADDR_RUNCODE; fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_colorhug_device_class_init (FuColorhugDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->write_firmware = fu_colorhug_device_write_firmware; klass_device->attach = fu_colorhug_device_attach; klass_device->detach = fu_colorhug_device_detach; klass_device->setup = fu_colorhug_device_setup; klass_usb_device->open = fu_colorhug_device_open; klass_usb_device->probe = fu_colorhug_device_probe; } FuColorhugDevice * fu_colorhug_device_new (FuUsbDevice *device) { FuColorhugDevice *self = NULL; self = g_object_new (FU_TYPE_COLORHUG_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } fwupd-1.2.14/plugins/colorhug/fu-colorhug-device.h000066400000000000000000000010501402665037500220730ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_COLORHUG_DEVICE (fu_colorhug_device_get_type ()) G_DECLARE_FINAL_TYPE (FuColorhugDevice, fu_colorhug_device, FU, COLORHUG_DEVICE, FuUsbDevice) FuColorhugDevice *fu_colorhug_device_new (FuUsbDevice *device); /* object methods */ gboolean fu_colorhug_device_set_flash_success (FuColorhugDevice *device, gboolean val, GError **error); G_END_DECLS fwupd-1.2.14/plugins/colorhug/fu-plugin-colorhug.c000066400000000000000000000054221402665037500221340ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-colorhug-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.hughski.colorhug"); } gboolean fu_plugin_update_detach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* switch to bootloader mode is not required */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug ("already in bootloader mode, skipping"); return TRUE; } /* reset */ if (!fu_device_detach (FU_DEVICE (device), error)) return FALSE; /* wait for replug */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* switch to runtime mode is not required */ if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug ("already in runtime mode, skipping"); return TRUE; } /* reset */ if (!fu_device_attach (device, error)) return FALSE; /* wait for replug */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } gboolean fu_plugin_update_reload (FuPlugin *plugin, FuDevice *device, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; /* also set flash success */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; if (!fu_colorhug_device_set_flash_success (self, TRUE, error)) return FALSE; return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* write firmware */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuColorhugDevice) dev = NULL; /* open the device */ dev = fu_colorhug_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; /* insert to hash */ fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } fwupd-1.2.14/plugins/colorhug/meson.build000066400000000000000000000011161402665037500204000ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginColorHug"'] install_data([ 'colorhug.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_colorhug', fu_hash, sources : [ 'fu-colorhug-common.c', 'fu-colorhug-device.c', 'fu-plugin-colorhug.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/csr/000077500000000000000000000000001402665037500152045ustar00rootroot00000000000000fwupd-1.2.14/plugins/csr/README.md000066400000000000000000000020751402665037500164670ustar00rootroot00000000000000CSR Support =========== Introduction ------------ CSR is often called “driverless DFU” and is used only by BlueCore chips from Cambridge Silicon Radio (now owned by Qualcomm). The driverless just means that it's DFU like, and is routed over HID. CSR is a ODM that makes most of the Bluetooth audio chips in vendor hardware. The hardware vendor can enable or disable features on the CSR microcontroller depending on licensing options (for instance echo cancellation), and there’s even a little virtual machine to do simple vendor-specific things. All the CSR chips are updatable in-field, and most vendors issue updates to fix sound quality issues or to add support for new protocols or devices. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in DFU file format. This plugin supports the following protocol ID: * com.qualcomm.dfu GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0A12&PID_1337&REV_2520` * `USB\VID_0A12&PID_1337` * `USB\VID_0A12` fwupd-1.2.14/plugins/csr/csr-aiaiai.quirk000066400000000000000000000005211402665037500202610ustar00rootroot00000000000000[DeviceInstanceId=USB\VID_0A12&PID_1337] Plugin = csr Name = H05 Summary = Bluetooth Headphones Icon = audio-headphones Vendor = AIAIAI [DeviceInstanceId=USB\VID_0A12&PID_1337&REV_2520] Version = 1.2 [DeviceInstanceId=USB\VID_0A12&PID_4004] Plugin = csr Name = H60 Summary = Bluetooth Headphones Icon = audio-headphones Vendor = AIAIAI fwupd-1.2.14/plugins/csr/data/000077500000000000000000000000001402665037500161155ustar00rootroot00000000000000fwupd-1.2.14/plugins/csr/data/lsusb.txt000066400000000000000000000036411402665037500200120ustar00rootroot00000000000000Bus 001 Device 040: ID 0a12:1337 Cambridge Silicon Radio, Ltd Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x0a12 Cambridge Silicon Radio, Ltd idProduct 0x1337 bcdDevice 25.20 iManufacturer 0 iProduct 2 AIAIAI H05 in iSerial 3 ABCDEF0123456789 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 34 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.00 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 40 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/csr/data/upgrade.tdc.gz000066400000000000000000000163611402665037500206660ustar00rootroot00000000000000*Zupgrade.tdcZiXҮ=#8h<" .=\#q`.0Ѡ1ո-JF%^DKP$;Ow?3y[VԡOzd! _7,0 WAG><H>c&*S|"x ?RfǼn9 5ǡq`I` I4@5,& ~?pMJD LDD*o!F11`n‚@!eT$.PE|&/؅HvnQtP\x4C.)Ƀ@*\<쐖vHBĔO+**xqPEe$@.$ Bm?cHqJJCF|=( -%7tKE'Xӣ%%|IQ5(R'#K||X 'p7JJ57Md> zaCk-,\8GҎTD]bh⧑' qބRWXk 1:oQ'uv#2wKY{k 6JƎeV(2x? +!V6A5OϼԒq) N ^_ƷAR?OqU1_GW4KoJ5MaT#KRrC/H ?GNo7n}cjUH:8 wG"/ON!l^zR^/S倯}g2^ċ67S:9Ay^/l\qD=.KsNܐ@{Cl9׽{Cߏ18Q(pGl9{x-nH .y#GG 'Lq~Lk iO pk$ )t色rs@GΓjetq+V Fњ]Tkurn#Z9ߙVB[ 1 -)\xJ) ㍪I@BM7YψF\z036Eˑ[lAG㗔D[Aevf9DUb{㍋'ôwo'(ZUfFzt !_U7i(uƋRыlБ4tp'ء1Ia$VTB@JvjMIF31.#o qSRB^o@[9e.R=ϖYDTeG ,J}ĺ64&S 1n$)4艳h@*3^TB:3qgw~ %Q~F]oZɿP5 jn,G?3|A[{M"% )”Qv)n] lif0ss}7 +[ : w'-pBfҿc=0BˊmpvFg4J/2$*וJ_Ԯ 1QSedUuLqnxI0|w3=zG9+2Q,( M5,nY`AjZxGrr4}Ie9IxG/g}<%r?u5ӫfPZRÓՉVra2]3{tY\w-{yhmm..2"dQ8Rtw$6u4nQ7/`l޳dxMFSY *%Ӳ(8ʑ'VmE1o8Dtܚi_UAa`S.CIhKӵLŤhb1/. P mZ_I`#|i f+dPDF\2*}aʃKp a a.n#L:( y 7Xp`V8>ާ).BQwnW9Rwf8p[o*84Z+{\y)j"Qс-q=R.Qh@%6Gl |KUX5Jef+& u>b Gʁ0nqg0lDX1“Tr&B36`)whI+\k"b[|\6UDO 8A2v쾚rP㬏^3N\ƅB(, d k| xf;7 6׫a`9WLdp r.P4%p5v؉-k{l3I8I\>qU*px{Rrhl"`#Z%f9$ToqQIF#l7 :K/^QiqbLSwr焕2dp NQG7t()C*2ob3ܗ5-bPH}6sN9^uG<x ¹`фpXl,8D:DTq+\GMGǪ>6oN[)3mp. pq78; qH EEUNo{+>$R$Qa2?L+=D4q}3 *nw'PPS5Crܼpkyؤgl~y܉ 5.,ݽ u˿ٷnӖ _rzO@&۬кa3pMJUmy'q":I茺jeQ^=9hа7U+'kZXg[;X \M߿q{JX9 gir+7LnAt}C|hèRV 9ϐC@N97H+=tՊS^E%tTHqQcy52fЌ2J掃úqVSI[&dnvIV|Cd$?W 4L[{606;Lq5`d1aJu|MXRx|] %Gn7fsn$OQUǮn~*+Y6Ƞ?f]`&^U;H#v~~F ܘ~SҖL+#.-ϴ٧"nykVPcp˪E?+đ5GgW2yrtۤ5 "QM*XIx-2-d_FgߒUM"hUNdp"L j}s*o#GO"JA2 QC4}EFdL@lWWmtٮnn c5[IL_H@bTq DrbLj\)6zJ/U6E8>,x5tZj ;J \-T:ST,lu40z*@zJ򂪁3GL^ۦf%qVHTf7RR&kQ;BVƻJ޴w$9膟ALZ ?g[f^?t-ӿ$Mr]3Wrj١OQ ٭+ tm- GѿE'C/vxX9xHHTXgdN^}/޸9A.Na=q'@ }gD8L[}Zc #dD֜cxѽ(}AW=@#W.+/±k}F>M|%{8& СT=x0@+?`luE{.%64XZgU'?C [|nɤr|ն^qqt+\g6Z18 UHJCh`d&1.B>f~>\ZB*Qmf0 ' l~Cd!D߂!kؒ3D#V=~UFوdp֏PeV/TDKv B-r@ar|c?{rp7'T )#?xPJe629C.9.D𓵪ƺiC85H]'#oyCa4^5TGMƣ up5Cя5*ܵ%YQ4a ץ5fXc)- Pq.cưfXA4 g0ߛXX9J#sdمh!?G t5{Z:FK-xu#KQVr"3VJt W$o~gZofJ.7}|8";)%vWljmVKXAT[ܡ<[5<vbys)ym &UlZW%,Ux_7]OUr1r T}[s~ˇ]bv`P1dtx V4QQ:jbyݻZP]3MV_XGKٟr%V Xz7nLLy^6)lx'ZjgӛUw2jz&t3u~oSLt&"jm63~r#fj'*1Wz R #(S PJr3OU:ne]Ng\cB *P|]$3/i"sEIg>U|دk~#t-3y"ޗ+^S/u7S/MںH]& [k}&*KuL^s&VȠȰ@n^ʃ~X͙V#bMI`80#XVF!e{ߢz2akW9 Yd^1A*QADBN$hvDvŧ :&B ABWmv*ZHvtH:$/*ST*YW1)GwLٝ<J &}GRj3}-TQ, ')Ew]7OAۛ3+Ty}a* _z, oU";Yvw7ResZ)ȋ#k+ꈮp ˎ깑gdp`ݱv2> pVp"DEKHLG}:kH "=PWnbډÆv0&bq_I!]-F*F$ f 20/ntI-fwupd-1.2.14/plugins/csr/fu-csr-device.c000066400000000000000000000407171402665037500200150ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-chunk.h" #include "fu-csr-device.h" #include "dfu-common.h" #include "dfu-firmware.h" /** * FU_CSR_DEVICE_QUIRK_FLAG_REQUIRE_DELAY: * * Respect the write timeout value when performing actions. This is sometimes * set to a huge amount of time, and so is not used by default. * * Since: 1.0.3 */ #define FU_CSR_DEVICE_FLAG_REQUIRE_DELAY "require-delay" typedef enum { FU_CSR_DEVICE_QUIRK_NONE = 0, FU_CSR_DEVICE_QUIRK_REQUIRE_DELAY = (1 << 0), FU_CSR_DEVICE_QUIRK_LAST } FuCsrDeviceQuirks; struct _FuCsrDevice { FuUsbDevice parent_instance; FuCsrDeviceQuirks quirks; DfuState dfu_state; guint32 dnload_timeout; }; G_DEFINE_TYPE (FuCsrDevice, fu_csr_device, FU_TYPE_USB_DEVICE) #define FU_CSR_REPORT_ID_COMMAND 0x01 #define FU_CSR_REPORT_ID_STATUS 0x02 #define FU_CSR_REPORT_ID_CONTROL 0x03 #define FU_CSR_COMMAND_HEADER_SIZE 6 /* bytes */ #define FU_CSR_COMMAND_UPGRADE 0x01 #define FU_CSR_STATUS_HEADER_SIZE 7 #define FU_CSR_CONTROL_HEADER_SIZE 2 /* bytes */ #define FU_CSR_CONTROL_CLEAR_STATUS 0x04 #define FU_CSR_CONTROL_RESET 0xff /* maximum firmware packet, including the command header */ #define FU_CSR_PACKET_DATA_SIZE 1023 /* bytes */ #define FU_CSR_DEVICE_TIMEOUT 5000 /* ms */ static void fu_csr_device_to_string (FuDevice *device, GString *str) { FuCsrDevice *self = FU_CSR_DEVICE (device); g_string_append (str, " DfuCsrDevice:\n"); g_string_append_printf (str, " state:\t\t%s\n", dfu_state_to_string (self->dfu_state)); g_string_append_printf (str, " timeout:\t\t%" G_GUINT32_FORMAT "\n", self->dnload_timeout); } static gboolean fu_csr_device_attach (FuDevice *device, GError **error) { FuCsrDevice *self = FU_CSR_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize sz = 0; guint8 buf[] = { FU_CSR_REPORT_ID_CONTROL, FU_CSR_CONTROL_RESET }; if (g_getenv ("FWUPD_CSR_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "Reset", buf, sz); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_SET, /* bRequest */ HID_FEATURE | FU_CSR_REPORT_ID_CONTROL, /* wValue */ 0x0000, /* wIndex */ buf, sizeof(buf), &sz, FU_CSR_DEVICE_TIMEOUT, /* timeout */ NULL, error)) { g_prefix_error (error, "Failed to ClearStatus: "); return FALSE; } /* check packet */ if (sz != FU_CSR_CONTROL_HEADER_SIZE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Reset packet was %" G_GSIZE_FORMAT " expected %i", sz, FU_CSR_CONTROL_HEADER_SIZE); return FALSE; } return TRUE; } static gboolean fu_csr_device_get_status (FuCsrDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize sz = 0; guint8 buf[64] = {0}; /* hit hardware */ if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_GET, /* bRequest */ HID_FEATURE | FU_CSR_REPORT_ID_STATUS, /* wValue */ 0x0000, /* wIndex */ buf, sizeof(buf), &sz, FU_CSR_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "Failed to GetStatus: "); return FALSE; } if (g_getenv ("FWUPD_CSR_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "GetStatus", buf, sz); /* check packet */ if (sz != FU_CSR_STATUS_HEADER_SIZE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "GetStatus packet was %" G_GSIZE_FORMAT " expected %i", sz, FU_CSR_STATUS_HEADER_SIZE); return FALSE; } if (buf[0] != FU_CSR_REPORT_ID_STATUS) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "GetStatus packet-id was %i expected %i", buf[0], FU_CSR_REPORT_ID_STATUS); return FALSE; } self->dfu_state = buf[5]; self->dnload_timeout = buf[2] + (((guint32) buf[3]) << 8) + (((guint32) buf[4]) << 16); g_debug ("timeout=%" G_GUINT32_FORMAT, self->dnload_timeout); g_debug ("state=%s", dfu_state_to_string (self->dfu_state)); g_debug ("status=%s", dfu_status_to_string (buf[6])); return TRUE; } static gboolean fu_csr_device_clear_status (FuCsrDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize sz = 0; guint8 buf[] = { FU_CSR_REPORT_ID_CONTROL, FU_CSR_CONTROL_CLEAR_STATUS }; /* only clear the status if the state is error */ if (!fu_csr_device_get_status (self, error)) return FALSE; if (self->dfu_state != DFU_STATE_DFU_ERROR) return TRUE; /* hit hardware */ if (g_getenv ("FWUPD_CSR_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "ClearStatus", buf, sz); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_SET, /* bRequest */ HID_FEATURE | FU_CSR_REPORT_ID_CONTROL, /* wValue */ 0x0000, /* wIndex */ buf, sizeof(buf), &sz, FU_CSR_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "Failed to ClearStatus: "); return FALSE; } /* check packet */ if (sz != FU_CSR_CONTROL_HEADER_SIZE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ClearStatus packet was %" G_GSIZE_FORMAT " expected %i", sz, FU_CSR_CONTROL_HEADER_SIZE); return FALSE; } /* check the hardware again */ return fu_csr_device_get_status (self, error); } static GBytes * fu_csr_device_upload_chunk (FuCsrDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize sz = 0; guint16 data_sz; guint8 buf[64] = {0}; /* hit hardware */ if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_GET, /* bRequest */ HID_FEATURE | FU_CSR_REPORT_ID_COMMAND, /* wValue */ 0x0000, /* wIndex */ buf, sizeof(buf), &sz, FU_CSR_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "Failed to ReadFirmware: "); return NULL; } if (g_getenv ("FWUPD_CSR_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "ReadFirmware", buf, sz); /* too small to parse */ if (sz < FU_CSR_COMMAND_HEADER_SIZE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ReadFirmware packet too small, got %" G_GSIZE_FORMAT, sz); return NULL; } /* check command byte */ if (buf[0] != FU_CSR_REPORT_ID_COMMAND) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "wrong report ID %u", buf[0]); return NULL; } /* check the length */ data_sz = fu_common_read_uint16 (&buf[1], G_LITTLE_ENDIAN); if (data_sz + FU_CSR_COMMAND_HEADER_SIZE != (guint16) sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "wrong data length %" G_GUINT16_FORMAT, data_sz); return NULL; } /* return as bytes */ return g_bytes_new (buf + FU_CSR_COMMAND_HEADER_SIZE, sz - FU_CSR_COMMAND_HEADER_SIZE); } static GBytes * fu_csr_device_upload (FuDevice *device, GError **error) { FuCsrDevice *self = FU_CSR_DEVICE (device); g_autoptr(GPtrArray) chunks = NULL; guint32 total_sz = 0; gsize done_sz = 0; /* notify UI */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_READ); chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); for (guint32 i = 0; i < 0x3ffffff; i++) { g_autoptr(GBytes) chunk = NULL; gsize chunk_sz; /* hit hardware */ chunk = fu_csr_device_upload_chunk (self, error); if (chunk == NULL) return NULL; chunk_sz = g_bytes_get_size (chunk); /* get the total size using the CSR header */ if (i == 0 && chunk_sz >= 10) { const guint8 *buf = g_bytes_get_data (chunk, NULL); if (memcmp (buf, "CSR-dfu", 7) == 0) { guint16 hdr_ver; guint16 hdr_len; hdr_ver = fu_common_read_uint16 (buf + 8, G_LITTLE_ENDIAN); if (hdr_ver != 0x03) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "CSR header version is " "invalid %" G_GUINT16_FORMAT, hdr_ver); return NULL; } total_sz = fu_common_read_uint32 (buf + 10, G_LITTLE_ENDIAN); if (total_sz == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "CSR header data length " "invalid %" G_GUINT32_FORMAT, total_sz); return NULL; } hdr_len = fu_common_read_uint16 (buf + 14, G_LITTLE_ENDIAN); g_debug ("CSR header length: %" G_GUINT16_FORMAT, hdr_len); } } /* add to chunk array */ done_sz += chunk_sz; g_ptr_array_add (chunks, g_steal_pointer (&chunk)); fu_device_set_progress_full (device, done_sz, (gsize) total_sz); /* we're done */ if (chunk_sz < 64 - FU_CSR_COMMAND_HEADER_SIZE) break; } /* notify UI */ return dfu_utils_bytes_join_array (chunks); } static gboolean fu_csr_device_download_chunk (FuCsrDevice *self, guint16 idx, GBytes *chunk, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); const guint8 *chunk_data; gsize chunk_sz = 0; gsize write_sz = 0; guint8 buf[FU_CSR_PACKET_DATA_SIZE] = {0}; /* too large? */ chunk_data = g_bytes_get_data (chunk, &chunk_sz); if (chunk_sz + FU_CSR_COMMAND_HEADER_SIZE > FU_CSR_PACKET_DATA_SIZE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "packet was too large: %" G_GSIZE_FORMAT, chunk_sz); return FALSE; } g_debug ("writing %" G_GSIZE_FORMAT " bytes of data", chunk_sz); /* create packet */ buf[0] = FU_CSR_REPORT_ID_COMMAND; buf[1] = FU_CSR_COMMAND_UPGRADE; fu_common_write_uint16 (&buf[2], idx, G_LITTLE_ENDIAN); fu_common_write_uint16 (&buf[4], chunk_sz, G_LITTLE_ENDIAN); memcpy (buf + FU_CSR_COMMAND_HEADER_SIZE, chunk_data, chunk_sz); /* hit hardware */ if (g_getenv ("FWUPD_CSR_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "Upgrade", buf, sizeof(buf)); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_SET, /* bRequest */ HID_FEATURE | FU_CSR_REPORT_ID_COMMAND, /* wValue */ 0x0000, /* wIndex */ buf, sizeof(buf), &write_sz, FU_CSR_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "Failed to Upgrade: "); return FALSE; } /* check packet */ if (write_sz != sizeof(buf)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Not all packet written for upgrade got " "%" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT, write_sz, sizeof(buf)); return FALSE; } /* wait for hardware */ if (self->quirks & FU_CSR_DEVICE_QUIRK_REQUIRE_DELAY) { g_debug ("sleeping for %ums", self->dnload_timeout); g_usleep (self->dnload_timeout * 1000); } /* get status */ if (!fu_csr_device_get_status (self, error)) return FALSE; /* is still busy */ if (self->dfu_state == DFU_STATE_DFU_DNBUSY) { g_debug ("busy, so sleeping a bit longer"); g_usleep (G_USEC_PER_SEC); if (!fu_csr_device_get_status (self, error)) return FALSE; } /* not correct */ if (self->dfu_state != DFU_STATE_DFU_DNLOAD_IDLE && self->dfu_state != DFU_STATE_DFU_IDLE) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "device did not return to IDLE"); return FALSE; } /* success */ return TRUE; } static GBytes * _dfu_firmware_get_default_element_data (DfuFirmware *firmware) { DfuElement *element; DfuImage *image; image = dfu_firmware_get_image_default (firmware); if (image == NULL) return NULL; element = dfu_image_get_element_default (image); if (element == NULL) return NULL; return dfu_element_get_contents (element); } static GBytes * fu_csr_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { GBytes *blob_noftr; g_autoptr(DfuFirmware) dfu_firmware = dfu_firmware_new (); /* parse the file */ if (!dfu_firmware_parse_data (dfu_firmware, fw, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) return NULL; if (g_getenv ("FWUPD_CSR_VERBOSE") != NULL) { g_autofree gchar *fw_str = NULL; fw_str = dfu_firmware_to_string (dfu_firmware); g_debug ("%s", fw_str); } if (dfu_firmware_get_format (dfu_firmware) != DFU_FIRMWARE_FORMAT_DFU) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "expected DFU firmware"); return NULL; } /* get the blob from the firmware file */ blob_noftr = _dfu_firmware_get_default_element_data (dfu_firmware); if (blob_noftr == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware contained no data"); return NULL; } /* success */ return g_bytes_ref (blob_noftr); } static gboolean fu_csr_device_download (FuDevice *device, GBytes *blob, FwupdInstallFlags flags, GError **error) { FuCsrDevice *self = FU_CSR_DEVICE (device); guint16 idx; g_autoptr(GBytes) blob_empty = NULL; g_autoptr(GPtrArray) chunks = NULL; /* notify UI */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); /* create chunks */ chunks = fu_chunk_array_new_from_bytes (blob, 0x0, 0x0, FU_CSR_PACKET_DATA_SIZE - FU_CSR_COMMAND_HEADER_SIZE); /* send to hardware */ for (idx = 0; idx < chunks->len; idx++) { FuChunk *chk = g_ptr_array_index (chunks, idx); g_autoptr(GBytes) blob_tmp = g_bytes_new_static (chk->data, chk->data_sz); /* send packet */ if (!fu_csr_device_download_chunk (self, idx, blob_tmp, error)) return FALSE; /* update progress */ fu_device_set_progress_full (device, (gsize) idx, (gsize) chunks->len); } /* all done */ blob_empty = g_bytes_new (NULL, 0); return fu_csr_device_download_chunk (self, idx, blob_empty, error); } static gboolean fu_csr_device_probe (FuUsbDevice *device, GError **error) { FuCsrDevice *self = FU_CSR_DEVICE (device); /* devices have to be whitelisted */ if (fu_device_has_custom_flag (FU_DEVICE (device), FU_CSR_DEVICE_FLAG_REQUIRE_DELAY)) self->quirks = FU_CSR_DEVICE_QUIRK_REQUIRE_DELAY; /* hardcoded */ fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE); /* success */ return TRUE; } static gboolean fu_csr_device_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* open device and clear status */ if (!g_usb_device_claim_interface (usb_device, 0x00, /* HID */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to claim HID interface: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_csr_device_setup (FuDevice *device, GError **error) { FuCsrDevice *self = FU_CSR_DEVICE (device); if (!fu_csr_device_clear_status (self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_csr_device_close (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* we're done here */ if (!g_usb_device_release_interface (usb_device, 0x00, /* HID */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to release interface: "); return FALSE; } /* success */ return TRUE; } static void fu_csr_device_init (FuCsrDevice *device) { } static void fu_csr_device_class_init (FuCsrDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->to_string = fu_csr_device_to_string; klass_device->write_firmware = fu_csr_device_download; klass_device->read_firmware = fu_csr_device_upload; klass_device->prepare_firmware = fu_csr_device_prepare_firmware; klass_device->attach = fu_csr_device_attach; klass_device->setup = fu_csr_device_setup; klass_usb_device->open = fu_csr_device_open; klass_usb_device->close = fu_csr_device_close; klass_usb_device->probe = fu_csr_device_probe; } FuCsrDevice * fu_csr_device_new (FuUsbDevice *device) { FuCsrDevice *self = g_object_new (FU_TYPE_CSR_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } fwupd-1.2.14/plugins/csr/fu-csr-device.h000066400000000000000000000005621402665037500200140ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_CSR_DEVICE (fu_csr_device_get_type ()) G_DECLARE_FINAL_TYPE (FuCsrDevice, fu_csr_device, FU, CSR_DEVICE, FuUsbDevice) FuCsrDevice *fu_csr_device_new (FuUsbDevice *device); G_END_DECLS fwupd-1.2.14/plugins/csr/fu-plugin-csr.c000066400000000000000000000034771402665037500200560ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-csr-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.qualcomm.dfu"); } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuCsrDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; dev = fu_csr_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } gboolean fu_plugin_verify (FuPlugin *plugin, FuDevice *device, FuPluginVerifyFlags flags, GError **error) { g_autoptr(GBytes) blob_fw = NULL; g_autoptr(FuDeviceLocker) locker = NULL; GChecksumType checksum_types[] = { G_CHECKSUM_SHA1, G_CHECKSUM_SHA256, 0 }; /* get data */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; blob_fw = fu_device_read_firmware (device, error); if (blob_fw == NULL) return FALSE; for (guint i = 0; checksum_types[i] != 0; i++) { g_autofree gchar *hash = NULL; hash = g_compute_checksum_for_bytes (checksum_types[i], blob_fw); fu_device_add_checksum (device, hash); } return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; if (!fu_device_write_firmware (device, blob_fw, flags, error)) return FALSE; return fu_device_attach (device, error); } fwupd-1.2.14/plugins/csr/meson.build000066400000000000000000000011051402665037500173430ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginCsr"'] install_data(['csr-aiaiai.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_csr', fu_hash, sources : [ 'fu-csr-device.c', 'fu-plugin-csr.c', ], include_directories : [ include_directories('../..'), include_directories('../dfu'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ libfwupdprivate, dfu, ], ) fwupd-1.2.14/plugins/dell-dock/000077500000000000000000000000001402665037500162535ustar00rootroot00000000000000fwupd-1.2.14/plugins/dell-dock/README.md000066400000000000000000000063201402665037500175330ustar00rootroot00000000000000Dell USB-C Dock ========= ### Dell System Unlike previous Dell USB-C devices, a Dell system is not needed for updating. ### Components The device contains components the following directly updatable components: * USB hubs * MST controller * Thunderbolt controller * Embedded controller This plugin is used to perform the update on the USB hubs as well as the Dell Embedded controller. The USB hubs are updated directly over a USB HID endpoint while the embedded controller is updated using an I2C over HID interface. The fwupd thunderbolt plugin is used for updating the Titan Ridge controller. The MST controller is updated through either the DP Aux interface (SynapticsMST plugin) or I2C over HID interface provided by this plugin. ## Device topology When this plugin is used, devices present in other plugins may be shown in the topology of this dock. This is intentional as this plugin works together with those plugins to manage the flashing of all components. Firmware Format --------------- The daemon will decompress the cabinet archive and extract several firmware blobs with an unspecified binary file format. This plugin supports the following protocol ID: * com.dell.dock * com.synaptics.mst GUID Generation --------------- These devices use several different generation schemes, e.g. * USB Hub1: `USB\VID_413C&PID_B06F&hub` * USB Hub2: `USB\VID_413C&PID_B06E&hub` * Embedded Controller: `USB\VID_413C&PID_B06E&hub&embedded` * Update Level: `USB\VID_413C&PID_B06E&hub&status` * MST Hub: `MST-panamera-vmm5331-259` * Thunderbolt Controller: `TBT-00d4b070` Custom flag use: ---------------- This plugin uses the following plugin-specific custom flags: * `skip-restart`: Don't run the reset or reboot procedure of the component Quirk use --------- This plugin uses the following plugin-specific quirks: | Quirk | Description | Minimum fwupd version | |------------------------------|-------------------------------------------------------------------------|-----------------------| | `DellDockUnlockTarget` | The EC argument needed for unlocking certain device usage. | 1.1.3 | | `DellDockBlobMajorOffset` | The offset of the major version number in a payload | 1.1.3 | | `DellDockBlobMinorOffset` | The offset of the minor version number in a payload | 1.1.3 | | `DellDockBlobBuildOffset` | The offset of the build version number in a payload | 1.1.3 | | `DellDockBlobVersionOffset` | The offset of the ASCII representation of a version string in a payload | 1.1.3 | | `DellDockBoardMin` | The minimum board revision required to safely operate the plugin | 1.1.3 | | `DellDockVersionLowest` | The minimum component version required to safely operate the plugin | 1.1.3 | | `DellDockBoard*` | The board description of a board revision | 1.1.3 | | `DellDockInstallDurationI2C` | The duration of time required to install a payload via I2C. | 1.1.3 | fwupd-1.2.14/plugins/dell-dock/dell-dock.quirk000066400000000000000000000063001402665037500211650ustar00rootroot00000000000000# # Copyright (C) 2018 Dell Inc. # All rights reserved. # # This software and associated documentation (if any) is furnished # under a license and may only be used or copied in accordance # with the terms of the license. # # This file is provided under a dual MIT/LGPLv2 license. When using or # redistributing this file, you may do so under either license. # Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. # # SPDX-License-Identifier: LGPL-2.1+ OR MIT # [DellDockUnlockTargets] synapticsmst = 9 # Used to make plugin probe the devices [DeviceInstanceId=USB\VID_413C&PID_B06F] Name = Unprobed Dell accessory endpoint Plugin = dell_dock [DeviceInstanceId=USB\VID_413C&PID_B06E] Name = Unprobed Dell accessory endpoint Plugin = dell_dock # USB hub1 [DeviceInstanceId=USB\VID_413C&PID_B06F&hub] Name = RTS5413 in Dell dock Summary = USB 3.1 Generation 1 Hub ParentGuid = USB\VID_413C&PID_B06E&hub&embedded Plugin = dell_dock Vendor = Dell Inc Icon = dock-usb FirmwareSize = 0x10000 Flags = require-ac,updatable DellDockUnlockTarget = 8 DellDockBlobMajorOffset = 0x7F6E DellDockBlobMinorOffset = 0x7F6F InstallDuration = 14 # USB hub2 [DeviceInstanceId=USB\VID_413C&PID_B06E&hub] Name = RTS5487 in Dell dock Summary = USB 3.1 Generation 2 Hub ParentGuid = USB\VID_413C&PID_B06E&hub&embedded Vendor = Dell Inc Plugin = dell_dock Icon = dock-usb FirmwareSize = 0x10000 Flags = require-ac,updatable,has-bridge DellDockUnlockTarget = 7 DellDockBlobMajorOffset = 0x7F52 DellDockBlobMinorOffset = 0x7F53 InstallDuration = 3 # Embedded Controller # Name is intentionally not set (it's queried by dock) [Guid=USB\VID_413C&PID_B06E&hub&embedded] Name = Dell dock Summary = High performance dock Plugin = dell_dock Vendor = Dell Inc Icon = dock-usb FirmwareSizeMin = 0x1FFC0 FirmwareSizeMax = 0x20000 Flags = require-ac Children = FuDellDockStatus|USB\VID_413C&PID_B06E&hub&status,FuDellDockMst|MST-panamera-vmm5331-259 DellDockUnlockTarget = 1 DellDockBoardMin = 6 DellDockVersionLowest = 01.00.00.00 DellDockBlobVersionOffset = 0x1AFC0 InstallDuration = 60 # Representation of overall dock update [DeviceInstanceId=USB\VID_413C&PID_B06E&hub&status] Name = Package level of Dell dock Summary = A representation of dock update status Plugin = dell_dock Vendor = Dell Inc FirmwareSize = 24 InstallDuration = 5 DellDockBlobVersionOffset = 0x14 # MST Hub [Guid=MST-panamera-vmm5331-259] Name = VMM5331 in Dell dock Summary = Multi Stream Transport controller Vendor = Dell Inc Plugin = dell_dock ParentGuid = USB\VID_413C&PID_B06E&hub&embedded Flags = skip-restart,require-ac FirmwareSize=524288 DellDockUnlockTarget = 9 InstallDuration = 95 DellDockInstallDurationI2C=360 DellDockBlobMajorOffset = 0x18400 DellDockBlobMinorOffset = 0x18401 DellDockBlobBuildOffset = 0x18402 Icon = video-display # Thunderbolt controller [Guid=TBT-00d4b070] Name = Thunderbolt controller in Dell dock Summary = Thunderbolt controller Vendor = Dell Inc ParentGuid = USB\VID_413C&PID_B06E&hub&embedded FirmwareSizeMin=0x40000 FirmwareSizeMax=0x80000 Flags = require-ac Icon = thunderbolt InstallDuration = 22 DellDockInstallDurationI2C = 181 DellDockUnlockTarget = 10 DellDockHubVersionLowest = 1.31 DellDockBlobMajorOffset = 0x400a DellDockBlobMinorOffset = 0x4009 fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-common.c000066400000000000000000000041051402665037500221530ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-dell-dock-common.h" #include "fu-device-locker.h" #include "fu-dell-dock-i2c-ec.h" gboolean fu_dell_dock_set_power (FuDevice *device, guint8 target, gboolean enabled, GError **error) { FuDevice *parent; g_autoptr(FuDeviceLocker) locker = NULL; g_return_val_if_fail (device != NULL, FALSE); parent = FU_IS_DELL_DOCK_EC (device) ? device : fu_device_get_parent (device); if (parent == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Couldn't find parent for %s", fu_device_get_name (device)); return FALSE; } locker = fu_device_locker_new (parent, error); if (locker == NULL) return FALSE; return fu_dell_dock_ec_modify_lock (parent, target, enabled, error); } void fu_dell_dock_will_replug (FuDevice *device) { guint64 timeout = fu_device_get_install_duration (device); g_return_if_fail (FU_IS_DEVICE (device)); g_debug ("Activated %" G_GUINT64_FORMAT "s replug delay for %s", timeout, fu_device_get_name (device)); fu_device_set_remove_delay (device, timeout * 1000); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } void fu_dell_dock_clone_updatable (FuDevice *device) { FuDevice *parent; parent = fu_device_get_parent (device); if (parent == NULL) return; if (fu_device_has_flag (parent, FWUPD_DEVICE_FLAG_UPDATABLE)) { fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); } else { const gchar *message = fu_device_get_update_error (parent); if (message != NULL) fu_device_set_update_error (device, message); fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); } } fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-common.h000066400000000000000000000021211402665037500221540ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include "config.h" #include "fu-device.h" #include "fu-dell-dock-i2c-ec.h" #include "fu-dell-dock-i2c-mst.h" #include "fu-dell-dock-i2c-tbt.h" #include "fu-dell-dock-hub.h" #include "fu-dell-dock-hid.h" #include "fu-dell-dock-status.h" #define DELL_DOCK_EC_INSTANCE_ID "USB\\VID_413C&PID_B06E&hub&embedded" #define DELL_DOCK_TBT_INSTANCE_ID "TBT-00d4b070" gboolean fu_dell_dock_set_power (FuDevice *device, guint8 target, gboolean enabled, GError **error); void fu_dell_dock_will_replug (FuDevice *device); void fu_dell_dock_clone_updatable (FuDevice *device); fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-hid.c000066400000000000000000000346461402665037500214440ustar00rootroot00000000000000/* * Copyright (C) 2018 Realtek Semiconductor Corporation * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include #include "fu-usb-device.h" #include "fwupd-error.h" #include "fu-dell-dock-hid.h" #define HIDI2C_MAX_REGISTER 4 #define HID_MAX_RETRIES 5 #define TBT_MAX_RETRIES 2 #define HIDI2C_TRANSACTION_TIMEOUT 2000 #define HUB_CMD_READ_DATA 0xC0 #define HUB_CMD_WRITE_DATA 0x40 #define HUB_EXT_READ_STATUS 0x09 #define HUB_EXT_MCUMODIFYCLOCK 0x06 #define HUB_EXT_I2C_WRITE 0xC6 #define HUB_EXT_WRITEFLASH 0xC8 #define HUB_EXT_I2C_READ 0xD6 #define HUB_EXT_VERIFYUPDATE 0xD9 #define HUB_EXT_ERASEBANK 0xE8 #define HUB_EXT_WRITE_TBT_FLASH 0xFF #define TBT_COMMAND_WAKEUP 0x00000000 #define TBT_COMMAND_AUTHENTICATE 0xFFFFFFFF #define TBT_COMMAND_AUTHENTICATE_STATUS 0xFFFFFFFE typedef struct __attribute__ ((packed)) { guint8 cmd; guint8 ext; union { guint32 dwregaddr; struct { guint8 cmd_data0; guint8 cmd_data1; guint8 cmd_data2; guint8 cmd_data3; }; }; guint16 bufferlen; FuHIDI2CParameters parameters; guint8 extended_cmdarea[53]; guint8 data[192]; } FuHIDCmdBuffer; typedef struct __attribute__ ((packed)) { guint8 cmd; guint8 ext; guint8 i2cslaveaddr; guint8 i2cspeed; union { guint32 startaddress; guint32 tbt_command; }; guint8 bufferlen; guint8 extended_cmdarea[55]; guint8 data[192]; } FuTbtCmdBuffer; static gboolean fu_dell_dock_hid_set_report (FuDevice *self, guint8 *outbuffer, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gboolean ret; gsize actual_len = 0; for (gint i = 1; i <= HID_MAX_RETRIES; i++) { g_autoptr(GError) error_local = NULL; ret = g_usb_device_control_transfer ( usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_SET, 0x0200, 0x0000, outbuffer, 192, &actual_len, HIDI2C_TRANSACTION_TIMEOUT, NULL, &error_local); if (ret) break; if (i == HID_MAX_RETRIES || g_error_matches (error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE)) { g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } else { g_debug ("attempt %d/%d: set control transfer failed: %s", i, HID_MAX_RETRIES, error_local->message); sleep (1); } } if (actual_len != 192) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } static gboolean fu_dell_dock_hid_get_report (FuDevice *self, guint8 *inbuffer, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gboolean ret; gsize actual_len = 0; for (gint i = 1; i <= HID_MAX_RETRIES; i++) { g_autoptr(GError) error_local = NULL; ret = g_usb_device_control_transfer ( usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_GET, 0x0100, 0x0000, inbuffer, 192, &actual_len, HIDI2C_TRANSACTION_TIMEOUT, NULL, &error_local); if (ret) break; if (i == HID_MAX_RETRIES || g_error_matches (error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE)) { g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } else { g_debug ("attempt %d/%d: get control transfer failed: %s", i, HID_MAX_RETRIES, error_local->message); } } if (actual_len != 192) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } gboolean fu_dell_dock_hid_get_hub_version (FuDevice *self, GError **error) { g_autofree gchar *version = NULL; FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_READ_DATA, .ext = HUB_EXT_READ_STATUS, .cmd_data0 = 0, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = GUINT16_TO_LE (12), .parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) { g_prefix_error (error, "failed to query hub version: "); return FALSE; } if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) { g_prefix_error (error, "failed to query hub version: "); return FALSE; } version = g_strdup_printf ("%02x.%02x", cmd_buffer.data[10], cmd_buffer.data[11]); fu_device_set_version (self, version, FWUPD_VERSION_FORMAT_PAIR); return TRUE; } gboolean fu_dell_dock_hid_raise_mcu_clock (FuDevice *self, gboolean enable, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_MCUMODIFYCLOCK, .cmd_data0 = (guint8) enable, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = 0, .parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) { g_prefix_error (error, "failed to set mcu clock to %d: ", enable); return FALSE; } return TRUE; } gboolean fu_dell_dock_hid_get_ec_status (FuDevice *self, guint8 *status1, guint8 *status2, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_READ_STATUS, .cmd_data0 = 0, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = GUINT16_TO_LE (27), .parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) { g_prefix_error (error, "failed to get EC status: "); return FALSE; } if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) { g_prefix_error (error, "failed to get EC status: "); return FALSE; } *status1 = cmd_buffer.data[25]; *status2 = cmd_buffer.data[26]; return TRUE; } gboolean fu_dell_dock_hid_erase_bank (FuDevice *self, guint8 idx, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_ERASEBANK, .cmd_data0 = 0, .cmd_data1 = idx, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = 0, .parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) { g_prefix_error (error, "failed to erase bank: "); return FALSE; } return TRUE; } gboolean fu_dell_dock_hid_write_flash (FuDevice *self, guint32 dwAddr, const guint8 *input, gsize write_size, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_WRITEFLASH, .dwregaddr = GUINT32_TO_LE (dwAddr), .bufferlen = GUINT16_TO_LE (write_size), .parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; g_return_val_if_fail (write_size <= HIDI2C_MAX_WRITE, FALSE); memcpy (cmd_buffer.data, input, write_size); if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) { g_prefix_error ( error, "failed to write %" G_GSIZE_FORMAT " flash to %x: ", write_size, dwAddr); return FALSE; } return TRUE; } gboolean fu_dell_dock_hid_verify_update (FuDevice *self, gboolean *result, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_VERIFYUPDATE, .cmd_data0 = 1, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = GUINT16_TO_LE (1), .parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) { g_prefix_error (error, "failed to verify update: "); return FALSE; } if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) { g_prefix_error (error, "failed to verify update: "); return FALSE; } *result = cmd_buffer.data[0]; return TRUE; } gboolean fu_dell_dock_hid_i2c_write (FuDevice *self, const guint8 *input, gsize write_size, const FuHIDI2CParameters *parameters, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_I2C_WRITE, .dwregaddr = 0, .bufferlen = GUINT16_TO_LE (write_size), .parameters = {.i2cslaveaddr = parameters->i2cslaveaddr, .regaddrlen = 0, .i2cspeed = parameters->i2cspeed | 0x80}, .extended_cmdarea[0 ... 52] = 0, }; g_return_val_if_fail (write_size <= HIDI2C_MAX_WRITE, FALSE); memcpy (cmd_buffer.data, input, write_size); return fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error); } gboolean fu_dell_dock_hid_i2c_read (FuDevice *self, guint32 cmd, gsize read_size, GBytes **bytes, const FuHIDI2CParameters *parameters, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_I2C_READ, .dwregaddr = GUINT32_TO_LE (cmd), .bufferlen = GUINT16_TO_LE (read_size), .parameters = {.i2cslaveaddr = parameters->i2cslaveaddr, .regaddrlen = parameters->regaddrlen, .i2cspeed = parameters->i2cspeed | 0x80}, .extended_cmdarea[0 ... 52] = 0, .data[0 ... 191] = 0, }; g_return_val_if_fail (read_size <= HIDI2C_MAX_READ, FALSE); g_return_val_if_fail (bytes != NULL, FALSE); g_return_val_if_fail (parameters->regaddrlen < HIDI2C_MAX_REGISTER, FALSE); if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) return FALSE; if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) return FALSE; *bytes = g_bytes_new (cmd_buffer.data, read_size); return TRUE; } gboolean fu_dell_dock_hid_tbt_wake (FuDevice *self, const FuHIDI2CParameters *parameters, GError **error) { FuTbtCmdBuffer cmd_buffer = { .cmd = HUB_CMD_READ_DATA, /* special write command that reads status result */ .ext = HUB_EXT_WRITE_TBT_FLASH, .i2cslaveaddr = parameters->i2cslaveaddr, .i2cspeed = parameters->i2cspeed, /* unlike other commands doesn't need | 0x80 */ .tbt_command = TBT_COMMAND_WAKEUP, .bufferlen = 0, .extended_cmdarea[0 ... 53] = 0, .data[0 ... 191] = 0, }; if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) { g_prefix_error (error, "failed to set wake thunderbolt: "); return FALSE; } if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) { g_prefix_error (error, "failed to get wake thunderbolt status: "); return FALSE; } g_debug ("thunderbolt wake result: 0x%x", cmd_buffer.data[1]); return TRUE; } static const gchar * fu_dell_dock_hid_tbt_map_error (guint32 code) { if (code == 1) return g_strerror (EINVAL); else if (code == 2) return g_strerror (EPERM); return g_strerror (EIO); } gboolean fu_dell_dock_hid_tbt_write (FuDevice *self, guint32 start_addr, const guint8 *input, gsize write_size, const FuHIDI2CParameters *parameters, GError **error) { FuTbtCmdBuffer cmd_buffer = { .cmd = HUB_CMD_READ_DATA, /* It's a special write command that reads status result */ .ext = HUB_EXT_WRITE_TBT_FLASH, .i2cslaveaddr = parameters->i2cslaveaddr, .i2cspeed = parameters->i2cspeed, /* unlike other commands doesn't need | 0x80 */ .startaddress = GUINT32_TO_LE (start_addr), .bufferlen = write_size, .extended_cmdarea[0 ... 53] = 0, }; guint8 result; g_return_val_if_fail (input != NULL, FALSE); g_return_val_if_fail (write_size <= HIDI2C_MAX_WRITE, FALSE); memcpy (cmd_buffer.data, input, write_size); for (gint i = 1; i <= TBT_MAX_RETRIES; i++) { if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) { g_prefix_error (error, "failed to run TBT update: "); return FALSE; } if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) { g_prefix_error (error, "failed to get TBT flash status: "); return FALSE; } result = cmd_buffer.data[1] & 0xf; if (result == 0) break; g_debug ("attempt %d/%d: Thunderbolt write failed: %x", i, TBT_MAX_RETRIES, result); } if (result != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Writing address 0x%04x failed: %s", start_addr, fu_dell_dock_hid_tbt_map_error (result)); return FALSE; } return TRUE; } gboolean fu_dell_dock_hid_tbt_authenticate (FuDevice *self, const FuHIDI2CParameters *parameters, GError **error) { FuTbtCmdBuffer cmd_buffer = { .cmd = HUB_CMD_READ_DATA, /* It's a special write command that reads status result */ .ext = HUB_EXT_WRITE_TBT_FLASH, .i2cslaveaddr = parameters->i2cslaveaddr, .i2cspeed = parameters->i2cspeed, /* unlike other commands doesn't need | 0x80 */ .tbt_command = GUINT32_TO_LE (TBT_COMMAND_AUTHENTICATE), .bufferlen = 0, .extended_cmdarea[0 ... 53] = 0, }; guint8 result; if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) { g_prefix_error (error, "failed to send authentication: "); return FALSE; } cmd_buffer.tbt_command = GUINT32_TO_LE (TBT_COMMAND_AUTHENTICATE_STATUS); /* needs at least 2 seconds */ g_usleep (2000000); for (gint i = 1; i <= TBT_MAX_RETRIES; i++) { if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) { g_prefix_error (error, "failed to set check authentication: "); return FALSE; } if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) { g_prefix_error (error, "failed to get check authentication: "); return FALSE; } result = cmd_buffer.data[1] & 0xf; if (result == 0) break; g_debug ("attempt %d/%d: Thunderbolt authenticate failed: %x", i, TBT_MAX_RETRIES, result); g_usleep (500000); } if (result != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Thunderbolt authentication failed: %s", fu_dell_dock_hid_tbt_map_error (result)); return FALSE; } return TRUE; } fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-hid.h000066400000000000000000000046671402665037500214510ustar00rootroot00000000000000/* * Copyright (C) 2018 Realtek Semiconductor Corporation * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include "config.h" #include #include "fu-device.h" typedef struct __attribute__ ((packed)) { guint8 i2cslaveaddr; guint8 regaddrlen; guint8 i2cspeed; } FuHIDI2CParameters; typedef enum { I2C_SPEED_250K, I2C_SPEED_400K, I2C_SPEED_800K, /* */ I2C_SPEED_LAST, } BridgedI2CSpeed; #define HIDI2C_MAX_READ 192 #define HIDI2C_MAX_WRITE 128 gboolean fu_dell_dock_hid_i2c_write (FuDevice *self, const guint8 *input, gsize write_size, const FuHIDI2CParameters *parameters, GError **error); gboolean fu_dell_dock_hid_i2c_read (FuDevice *self, guint32 cmd, gsize read_size, GBytes **bytes, const FuHIDI2CParameters *parameters, GError **error); gboolean fu_dell_dock_hid_get_hub_version (FuDevice *self, GError **error); gboolean fu_dell_dock_hid_raise_mcu_clock (FuDevice *self, gboolean enable, GError **error); gboolean fu_dell_dock_hid_get_ec_status (FuDevice *self, guint8 *status1, guint8 *status2, GError **error); gboolean fu_dell_dock_hid_erase_bank (FuDevice *self, guint8 idx, GError **error); gboolean fu_dell_dock_hid_write_flash (FuDevice *self, guint32 addr, const guint8 *input, gsize write_size, GError **error); gboolean fu_dell_dock_hid_verify_update (FuDevice *self, gboolean *result, GError **error); gboolean fu_dell_dock_hid_tbt_wake (FuDevice *self, const FuHIDI2CParameters *parameters, GError **error); gboolean fu_dell_dock_hid_tbt_write (FuDevice *self, guint32 start_addr, const guint8 *input, gsize write_size, const FuHIDI2CParameters *parameters, GError **error); gboolean fu_dell_dock_hid_tbt_authenticate (FuDevice *self, const FuHIDI2CParameters *parameters, GError **error); fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-hub.c000066400000000000000000000134111402665037500214410ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-usb-device.h" #include "fwupd-error.h" #include "fu-dell-dock-common.h" struct _FuDellDockHub { FuUsbDevice parent_instance; guint8 unlock_target; guint64 blob_major_offset; guint64 blob_minor_offset; }; G_DEFINE_TYPE (FuDellDockHub, fu_dell_dock_hub, FU_TYPE_USB_DEVICE) static gboolean fu_dell_dock_hub_probe (FuDevice *device, GError **error) { g_autofree gchar *devid = NULL; devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X&hub", (guint) fu_usb_device_get_vid (FU_USB_DEVICE (device)), (guint) fu_usb_device_get_pid (FU_USB_DEVICE (device))); fu_device_set_logical_id (device, "hub"); fu_device_add_instance_id (device, devid); return TRUE; } static gboolean fu_dell_dock_hub_write_fw (FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuDellDockHub *self = FU_DELL_DOCK_HUB (device); gsize fw_size = 0; const guint8 *data = g_bytes_get_data (blob_fw, &fw_size); gsize write_size = (fw_size / HIDI2C_MAX_WRITE) >= 1 ? HIDI2C_MAX_WRITE : fw_size; gsize nwritten = 0; guint32 address = 0; gboolean result = FALSE; g_autofree gchar *dynamic_version = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (blob_fw != NULL, FALSE); dynamic_version = g_strdup_printf ("%02x.%02x", data[self->blob_major_offset], data[self->blob_minor_offset]); g_debug ("writing hub firmware version %s", dynamic_version); if (!fu_dell_dock_set_power (device, self->unlock_target, TRUE, error)) return FALSE; if (!fu_dell_dock_hid_raise_mcu_clock (device, TRUE, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_dell_dock_hid_erase_bank (device, 1, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); do { /* last packet */ if (fw_size - nwritten < write_size) write_size = fw_size - nwritten; if (!fu_dell_dock_hid_write_flash (device, address, data, write_size, error)) return FALSE; nwritten += write_size; data += write_size; address += write_size; fu_device_set_progress_full (device, nwritten, fw_size); } while (nwritten < fw_size); fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY); if (!fu_dell_dock_hid_verify_update (device, &result, error)) return FALSE; if (!result) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to verify the update"); return FALSE; } /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version (device, dynamic_version, FWUPD_VERSION_FORMAT_PAIR); return TRUE; } static gboolean fu_dell_dock_hub_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockHub *self = FU_DELL_DOCK_HUB (device); if (g_strcmp0 (key, "DellDockUnlockTarget") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < G_MAXUINT8) { self->unlock_target = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid DellDockUnlockTarget"); return FALSE; } if (g_strcmp0 (key, "DellDockBlobMajorOffset") == 0) { self->blob_major_offset = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "DellDockBlobMinorOffset") == 0) { self->blob_minor_offset = fu_common_strtoull (value); return TRUE; } /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_dell_dock_hub_open (FuUsbDevice *fu_usb_device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (fu_usb_device); /* open device and clear status */ if (!g_usb_device_claim_interface ( usb_device, 0, /* HID */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to claim HID interface: "); return FALSE; } return TRUE; } static gboolean fu_dell_dock_hub_close (FuUsbDevice *fu_usb_device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (fu_usb_device); if (!g_usb_device_release_interface ( usb_device, 0, /* HID */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to release interface: "); return FALSE; } return TRUE; } static void fu_dell_dock_hub_finalize (GObject *object) { G_OBJECT_CLASS (fu_dell_dock_hub_parent_class)->finalize (object); } static void fu_dell_dock_hub_init (FuDellDockHub *self) { } static void fu_dell_dock_hub_class_init (FuDellDockHubClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); object_class->finalize = fu_dell_dock_hub_finalize; klass_usb_device->open = fu_dell_dock_hub_open; klass_usb_device->close = fu_dell_dock_hub_close; klass_device->setup = fu_dell_dock_hid_get_hub_version; klass_device->probe = fu_dell_dock_hub_probe; klass_device->write_firmware = fu_dell_dock_hub_write_fw; klass_device->set_quirk_kv = fu_dell_dock_hub_set_quirk_kv; } FuDellDockHub * fu_dell_dock_hub_new (FuUsbDevice *device) { FuDellDockHub *self = g_object_new (FU_TYPE_DELL_DOCK_HUB, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-hub.h000066400000000000000000000014361402665037500214520ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include "config.h" #include "fu-usb-device.h" G_BEGIN_DECLS #define FU_TYPE_DELL_DOCK_HUB (fu_dell_dock_hub_get_type ()) G_DECLARE_FINAL_TYPE (FuDellDockHub, fu_dell_dock_hub, FU, DELL_DOCK_HUB, FuUsbDevice) FuDellDockHub *fu_dell_dock_hub_new (FuUsbDevice *device); G_END_DECLS fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-i2c-ec.c000066400000000000000000000730521402665037500217340ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include "fu-common-version.h" #include "fu-usb-device.h" #include "fwupd-error.h" #include "fu-dell-dock-common.h" #define I2C_EC_ADDRESS 0xec #define EC_CMD_SET_DOCK_PKG 0x01 #define EC_CMD_GET_DOCK_INFO 0x02 #define EC_CMD_GET_DOCK_DATA 0x03 #define EC_CMD_GET_DOCK_TYPE 0x05 #define EC_CMD_MODIFY_LOCK 0x0a #define EC_CMD_RESET 0x0b #define EC_CMD_REBOOT 0x0c #define EC_CMD_PASSIVE 0x0d #define EC_GET_FW_UPDATE_STATUS 0x0f #define EXPECTED_DOCK_INFO_SIZE 0xb7 #define EXPECTED_DOCK_TYPE 0x04 #define TBT_MODE_MASK 0x01 #define BIT_SET(x,y) (x |= (1<data->board_id); summary = fu_device_get_metadata (device, board_type_str); if (summary != NULL) fu_device_set_summary (device, summary); } FuDevice * fu_dell_dock_ec_get_symbiote (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); return self->symbiote; } gboolean fu_dell_dock_ec_needs_tbt (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); gboolean port0_tbt_mode = self->data->port0_dock_status & TBT_MODE_MASK; /* check for TBT module type */ if (self->data->module_type != MODULE_TYPE_TBT) return FALSE; g_debug ("found thunderbolt dock, port mode: %d", port0_tbt_mode); return !port0_tbt_mode; } gboolean fu_dell_dock_ec_tbt_passive (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); if (self->passive_flow > 0) { self->passive_flow |= PASSIVE_TBT_MASK; return TRUE; } return FALSE; } static const gchar* fu_dell_dock_devicetype_to_str (guint device_type, guint sub_type) { switch (device_type) { case FU_DELL_DOCK_DEVICETYPE_MAIN_EC: return "EC"; case FU_DELL_DOCK_DEVICETYPE_MST: return "MST"; case FU_DELL_DOCK_DEVICETYPE_TBT: return "Thunderbolt"; case FU_DELL_DOCK_DEVICETYPE_HUB: if (sub_type == SUBTYPE_GEN2) return "USB 3.1 Gen2"; else if (sub_type == SUBTYPE_GEN1) return "USB 3.1 Gen1"; return NULL; case FU_DELL_DOCK_DEVICETYPE_PD: return "PD"; default: return NULL; } } static gboolean fu_dell_dock_ec_read (FuDevice *device, guint32 cmd, gsize length, GBytes **bytes, GError **error) { /* The first byte of result data will be the size of the return, hide this from callers */ guint8 result_length = length + 1; g_autoptr(GBytes) bytes_local = NULL; const guint8 *result; FuDellDockEc *self = FU_DELL_DOCK_EC (device); g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (self->symbiote != NULL, FALSE); g_return_val_if_fail (bytes != NULL, FALSE); if (!fu_dell_dock_hid_i2c_read (self->symbiote, cmd, result_length, &bytes_local, &ec_base_settings, error)) { g_prefix_error (error, "read over HID-I2C failed: "); return FALSE; } result = g_bytes_get_data (bytes_local, NULL); /* first byte of result should be size of our data */ if (result[0] != length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid result data: %d expected %" G_GSIZE_FORMAT, result[0], length); return FALSE; } *bytes = g_bytes_new (result + 1, length); return TRUE; } static gboolean fu_dell_dock_ec_write (FuDevice *device, gsize length, guint8 *data, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (self->symbiote != NULL, FALSE); g_return_val_if_fail (length > 1, FALSE); if (!fu_dell_dock_hid_i2c_write (self->symbiote, data, length, &ec_base_settings, error)) { g_prefix_error (error, "write over HID-I2C failed: "); return FALSE; } return TRUE; } static gboolean fu_dell_dock_is_valid_dock (FuDevice *device, GError **error) { g_autoptr(GBytes) data = NULL; const guint8 *result = NULL; g_return_val_if_fail (device != NULL, FALSE); if (!fu_dell_dock_ec_read (device, EC_CMD_GET_DOCK_TYPE, 1, &data, error)) { g_prefix_error (error, "Failed to query dock type: "); return FALSE; } result = g_bytes_get_data (data, NULL); if (result == NULL || *result != EXPECTED_DOCK_TYPE) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No valid dock was found"); return FALSE; } return TRUE; } static gboolean fu_dell_dock_ec_get_dock_info (FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); const FuDellDockDockInfoHeader *header = NULL; const FuDellDockEcQueryEntry *device_entry = NULL; const FuDellDockEcAddrMap *map = NULL; const gchar *hub_version; guint32 oldest_base_pd = 0; g_autoptr(GBytes) data = NULL; g_return_val_if_fail (device != NULL, FALSE); if (!fu_dell_dock_ec_read (device, EC_CMD_GET_DOCK_INFO, EXPECTED_DOCK_INFO_SIZE, &data, error)) { g_prefix_error (error, "Failed to query dock info: "); return FALSE; } if (!g_bytes_get_data (data, NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to read dock info"); return FALSE; } header = (FuDellDockDockInfoHeader *) g_bytes_get_data (data, NULL); if (!header) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to parse dock info"); return FALSE; } /* guard against EC not yet ready and fail init */ if (header->total_devices == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "No bridge devices detected, dock may be booting up"); return FALSE; } g_debug ("%u devices [%u->%u]", header->total_devices, header->first_index, header->last_index); device_entry = (FuDellDockEcQueryEntry *) ((guint8 *) header + sizeof(FuDellDockDockInfoHeader)); for (guint i = 0; i < header->total_devices; i++) { const gchar *type_str; map = &(device_entry[i].ec_addr_map); type_str = fu_dell_dock_devicetype_to_str (map->device_type, map->sub_type); if (type_str == NULL) continue; g_debug ("#%u: %s in %s (A: %u I: %u)", i, type_str, (map->location == LOCATION_BASE) ? "Base" : "Module", map->arg, map->instance); g_debug ("\tVersion32: %08x\tVersion8: %x %x %x %x", device_entry[i].version.version_32, device_entry[i].version.version_8[0], device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); /* BCD but guint32 */ if (map->device_type == FU_DELL_DOCK_DEVICETYPE_MAIN_EC) { self->raw_versions->ec_version = device_entry[i].version.version_32; self->ec_version = g_strdup_printf ( "%02x.%02x.%02x.%02x", device_entry[i].version.version_8[0], device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); g_debug ("\tParsed version %s", self->ec_version); fu_device_set_version (FU_DEVICE (self), self->ec_version, FWUPD_VERSION_FORMAT_QUAD); } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_MST) { self->raw_versions->mst_version = device_entry[i].version.version_32; /* guard against invalid MST version read from EC */ if (!fu_dell_dock_test_valid_byte (device_entry[i].version.version_8, 1)) { g_warning ("[EC Bug] EC read invalid MST version %08x", device_entry[i].version.version_32); continue; } self->mst_version = g_strdup_printf ("%02x.%02x.%02x", device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); g_debug ("\tParsed version %s", self->mst_version); } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_TBT && self->data->module_type == MODULE_TYPE_TBT) { /* guard against invalid Thunderbolt version read from EC */ if (!fu_dell_dock_test_valid_byte (device_entry[i].version.version_8, 2)) { g_warning ("[EC bug] EC read invalid Thunderbolt version %08x", device_entry[i].version.version_32); continue; } self->raw_versions->tbt_version = device_entry[i].version.version_32; self->tbt_version = g_strdup_printf ("%02x.%02x", device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); g_debug ("\tParsed version %s", self->tbt_version); } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_HUB) { g_debug ("\thub subtype: %u", map->sub_type); if (map->sub_type == SUBTYPE_GEN2) self->raw_versions->hub2_version = device_entry[i].version.version_32; else if (map->sub_type == SUBTYPE_GEN1) self->raw_versions->hub1_version = device_entry[i].version.version_32; } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_PD && map->location == LOCATION_BASE && map->sub_type == 0) { if (oldest_base_pd == 0 || device_entry[i].version.version_32 < oldest_base_pd) oldest_base_pd = GUINT32_TO_BE (device_entry[i].version.version_32); g_debug ("\tParsed version: %02x.%02x.%02x.%02x", device_entry[i].version.version_8[0], device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); } } /* Thunderbolt SKU takes a little longer */ if (self->data->module_type == MODULE_TYPE_TBT) { guint64 tmp = fu_device_get_install_duration (device); fu_device_set_install_duration (device, tmp + 20); } /* minimum EC version this code will support */ if (fu_common_vercmp (self->ec_version, self->ec_minimum_version) < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "dock containing EC version %s is not supported", self->ec_version); return FALSE; } fu_device_set_version_lowest (device, self->ec_minimum_version); /* Determine if the passive flow should be used when flashing */ hub_version = fu_device_get_version (self->symbiote); if (fu_common_vercmp (hub_version, "1.42") >= 0) { g_debug ("using passive flow"); self->passive_flow = PASSIVE_REBOOT_MASK; fu_device_set_custom_flags (device, "skip-restart"); } else { g_debug ("not using passive flow (EC: %s Hub2: %s)", self->ec_version, hub_version); } return TRUE; } static gboolean fu_dell_dock_ec_get_dock_data (FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); g_autoptr(GBytes) data = NULL; g_autoptr(GString) name = NULL; gchar service_tag[8] = {0x00}; const guint8 *result; gsize length = sizeof(FuDellDockDockDataStructure); g_autofree gchar *bundled_serial = NULL; FuDellDockECFWUpdateStatus status; g_return_val_if_fail (device != NULL, FALSE); if (!fu_dell_dock_ec_read (device, EC_CMD_GET_DOCK_DATA, length, &data, error)) { g_prefix_error (error, "Failed to query dock info: "); return FALSE; } result = g_bytes_get_data (data, NULL); if (result == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to read dock data"); return FALSE; } if (g_bytes_get_size (data) != length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Unexpected dock data size %" G_GSIZE_FORMAT, g_bytes_get_size (data)); return FALSE; } memcpy (self->data, result, length); /* guard against EC not yet ready and fail init */ name = g_string_new (self->data->marketing_name); if (name->len > 0) fu_device_set_name (device, name->str); else g_warning ("[EC bug] Invalid dock name detected"); if (self->data->module_type >= 0xfe) g_warning ("[EC bug] Invalid module type 0x%02x", self->data->module_type); /* set serial number */ memcpy (service_tag, self->data->service_tag, 7); bundled_serial = g_strdup_printf ("%s/%08" G_GUINT64_FORMAT, service_tag, self->data->module_serial); fu_device_set_serial (device, bundled_serial); /* copy this for being able to send in next commit transaction */ self->raw_versions->pkg_version = self->data->dock_firmware_pkg_ver; /* read if passive update pending */ if (!fu_dell_dock_get_ec_status (device, &status, error)) return FALSE; /* make sure this hardware spin matches our expecations */ if (self->data->board_id >= self->board_min) { if (status != FW_UPDATE_IN_PROGRESS) { fu_dell_dock_ec_set_board (device); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); } else { fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); fu_device_set_update_error (device, "A pending update will be completed " "next time the dock is " "unplugged from your computer"); } } else { g_warning ("This utility does not support this board, disabling updates for %s", fu_device_get_name (device)); } return TRUE; } static void fu_dell_dock_ec_to_string (FuDevice *device, GString *str) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); gchar service_tag[8] = {0x00}; g_string_append (str, " FuDellDellDockEc:\n"); g_string_append_printf (str, "\tboard ID: %u\n", self->data->board_id); g_string_append_printf (str, "\tpower supply: %uW\n", self->data->power_supply_wattage); g_string_append_printf (str, "\tstatus (port0): %x\n", self->data->port0_dock_status); g_string_append_printf (str, "\tstatus (port1): %x\n", self->data->port1_dock_status); memcpy (service_tag, self->data->service_tag, 7); g_string_append_printf (str, "\tservice tag: %s\n", service_tag); g_string_append_printf (str, "\tconfiguration: %u\n", self->data->dock_configuration); g_string_append_printf (str, "\tpackage firmware version: %x\n", self->data->dock_firmware_pkg_ver); g_string_append_printf (str, "\tmodule serial #: %08" G_GUINT64_FORMAT "\n", self->data->module_serial); g_string_append_printf (str, "\toriginal module serial #: %08" G_GUINT64_FORMAT "\n", self->data->original_module_serial); g_string_append_printf (str, "\ttype: %u\n", self->data->dock_type); g_string_append_printf (str, "\tmodule type: %x\n", self->data->module_type); g_string_append_printf (str, "\tminimum ec: %s\n", self->ec_minimum_version); g_string_append_printf (str, "\tpassive flow: %d\n", self->passive_flow); } gboolean fu_dell_dock_ec_modify_lock (FuDevice *device, guint8 target, gboolean unlocked, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); guint32 cmd; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (target != 0, FALSE); cmd = EC_CMD_MODIFY_LOCK | /* cmd */ 2 << 8 | /* length of data arguments */ target << 16 | /* device to operate on */ unlocked << 24; /* unlock/lock */ if (!fu_dell_dock_ec_write (device, 4, (guint8 *) &cmd, error)) { g_prefix_error (error, "Failed to unlock device %d: ", target); return FALSE; } g_debug ("Modified lock for %d to %d through %s (%s)", target, unlocked, fu_device_get_name (device), fu_device_get_id (device)); if (unlocked) BIT_SET (self->dock_unlock_status, target); else BIT_CLEAR (self->dock_unlock_status, target); g_debug ("current overall unlock status: 0x%08x", self->dock_unlock_status); return TRUE; } static gboolean fu_dell_dock_ec_reset (FuDevice *device, GError **error) { guint16 cmd = EC_CMD_RESET; g_return_val_if_fail (device != NULL, FALSE); return fu_dell_dock_ec_write (device, 2, (guint8 *) &cmd, error); } static gboolean fu_dell_dock_ec_activate (FuDevice *device, GError **error) { FuDellDockECFWUpdateStatus status; /* read if passive update pending */ if (!fu_dell_dock_get_ec_status (device, &status, error)) return FALSE; if (status != FW_UPDATE_IN_PROGRESS) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No firmware update pending for %s", fu_device_get_name (device)); return FALSE; } return fu_dell_dock_ec_reset (device, error); } gboolean fu_dell_dock_ec_reboot_dock (FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); g_return_val_if_fail (device != NULL, FALSE); if (self->passive_flow > 0) { guint32 cmd = EC_CMD_PASSIVE | /* cmd */ 1 << 8 | /* length of data arguments */ self->passive_flow << 16; g_debug ("activating passive flow (%x) for %s", self->passive_flow, fu_device_get_name (device)); return fu_dell_dock_ec_write (device, 3, (guint8 *) &cmd, error); } else { guint16 cmd = EC_CMD_REBOOT; g_debug ("rebooting %s", fu_device_get_name (device)); return fu_dell_dock_ec_write (device, 2, (guint8 *) &cmd, error); } return TRUE; } static gboolean fu_dell_dock_get_ec_status (FuDevice *device, FuDellDockECFWUpdateStatus *status_out, GError **error) { g_autoptr(GBytes) data = NULL; const guint8 *result = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (status_out != NULL, FALSE); if (!fu_dell_dock_ec_read (device, EC_GET_FW_UPDATE_STATUS, 1, &data, error)) { g_prefix_error (error, "Failed to read FW update status: "); return FALSE; } result = g_bytes_get_data (data, NULL); if (!result) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to read FW update status"); return FALSE; } *status_out = *result; return TRUE; } const gchar* fu_dell_dock_ec_get_tbt_version (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); return self->tbt_version; } const gchar* fu_dell_dock_ec_get_mst_version (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); return self->mst_version; } guint32 fu_dell_dock_ec_get_status_version (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); return self->raw_versions->pkg_version; } gboolean fu_dell_dock_ec_commit_package (FuDevice *device, GBytes *blob_fw, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); gsize length = 0; const guint8 *data = g_bytes_get_data (blob_fw, &length); g_autofree guint8 *payload = g_malloc0 (length + 2); g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (blob_fw != NULL, FALSE); if (length != sizeof(FuDellDockDockPackageFWVersion)) { g_set_error (error, G_IO_ERR, G_IO_ERROR_INVALID_DATA, "Invalid package size %" G_GSIZE_FORMAT, length); return FALSE; } memcpy (self->raw_versions, data, length); g_debug ("Committing (%zu) bytes ", sizeof(FuDellDockDockPackageFWVersion)); g_debug ("\tec_version: %x", self->raw_versions->ec_version); g_debug ("\tmst_version: %x", self->raw_versions->mst_version); g_debug ("\thub1_version: %x", self->raw_versions->hub1_version); g_debug ("\thub2_version: %x", self->raw_versions->hub2_version); g_debug ("\ttbt_version: %x", self->raw_versions->tbt_version); g_debug ("\tpkg_version: %x", self->raw_versions->pkg_version); payload [0] = EC_CMD_SET_DOCK_PKG; payload [1] = length; memcpy (payload + 2, data, length); if (!fu_dell_dock_ec_write (device, length + 2, payload, error)) { g_prefix_error (error, "Failed to query dock info: "); return FALSE; } return TRUE; } static gboolean fu_dell_dock_ec_write_fw (FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); FuDellDockECFWUpdateStatus status = FW_UPDATE_IN_PROGRESS; guint8 progress1 = 0, progress0 = 0; gsize fw_size = 0; const guint8 *data = g_bytes_get_data (blob_fw, &fw_size); gsize write_size = (fw_size / HIDI2C_MAX_WRITE) >= 1 ? HIDI2C_MAX_WRITE : fw_size; gsize nwritten = 0; guint32 address = 0 | 0xff << 24; g_autofree gchar *dynamic_version = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (blob_fw != NULL, FALSE); dynamic_version = g_strndup ((gchar *) data + self->blob_version_offset, 11); g_debug ("writing EC firmware version %s", dynamic_version); if (!fu_dell_dock_ec_modify_lock (device, self->unlock_target, TRUE, error)) return FALSE; if (!fu_dell_dock_hid_raise_mcu_clock (self->symbiote, TRUE, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_dell_dock_hid_erase_bank (self->symbiote, 0xff, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); do { /* last packet */ if (fw_size - nwritten < write_size) write_size = fw_size - nwritten; if (!fu_dell_dock_hid_write_flash (self->symbiote, address, data, write_size, error)) { g_prefix_error (error, "write over HID failed: "); return FALSE; } fu_device_set_progress_full (device, nwritten, fw_size); nwritten += write_size; data += write_size; address += write_size; } while (nwritten < fw_size); if (!fu_dell_dock_hid_raise_mcu_clock (self->symbiote, FALSE, error)) return FALSE; /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version (device, dynamic_version, FWUPD_VERSION_FORMAT_QUAD); /* activate passive behavior */ if (self->passive_flow) self->passive_flow |= PASSIVE_RESET_MASK; if (fu_device_has_custom_flag (device, "skip-restart")) { g_debug ("Skipping EC reset per quirk request"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); return TRUE; } if (!fu_dell_dock_ec_reset (device, error)) return FALSE; /* notify daemon that this device will need to replug */ fu_dell_dock_will_replug (device); /* poll for completion status */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY); while (status != FW_UPDATE_COMPLETE) { g_autoptr(GError) error_local = NULL; if (!fu_dell_dock_hid_get_ec_status (self->symbiote, &progress1, &progress0, error)) { g_prefix_error (error, "Failed to read scratch: "); return FALSE; } g_debug ("Read %u and %u from scratch", progress1, progress0); if (progress0 > 100) progress0 = 100; fu_device_set_progress_full (device, progress0, 100); /* This is expected to fail until update is done */ if (!fu_dell_dock_get_ec_status (device, &status, &error_local)) { g_debug ("Flash EC Received result: %s (status %u)", error_local->message, status); return TRUE; } if (status == FW_UPDATE_AUTHENTICATION_FAILED) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid EC firmware image"); return FALSE; } } return TRUE; } static gboolean fu_dell_dock_ec_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); if (g_strcmp0 (key, "DellDockUnlockTarget") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < G_MAXUINT8) { self->unlock_target = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid DellDockUnlockTarget"); return FALSE; } if (g_strcmp0 (key, "DellDockBoardMin") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < G_MAXUINT8) { self->board_min = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid DellDockBoardMin"); return FALSE; } if (g_strcmp0 (key, "DellDockVersionLowest") == 0) { self->ec_minimum_version = g_strdup (value); return TRUE; } if (g_str_has_prefix (key, "DellDockBoard")) { fu_device_set_metadata (device, key, value); return TRUE; } if (g_strcmp0 (key, "DellDockBlobVersionOffset") == 0) { self->blob_version_offset = fu_common_strtoull (value); return TRUE; } /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_dell_dock_ec_probe (FuDevice *device, GError **error) { /* this will trigger setting up all the quirks */ fu_device_add_instance_id (device, DELL_DOCK_EC_INSTANCE_ID); return TRUE; } static gboolean fu_dell_dock_ec_query (FuDevice *device, GError **error) { if (!fu_dell_dock_ec_get_dock_data (device, error)) return FALSE; return fu_dell_dock_ec_get_dock_info (device, error); } static gboolean fu_dell_dock_ec_setup (FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; GPtrArray *children; /* if query looks bad, wait a few seconds and retry */ if (!fu_dell_dock_ec_query (device, &error_local)) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID)) { g_warning ("%s", error_local->message); g_usleep (2 * G_USEC_PER_SEC); if (!fu_dell_dock_ec_query (device, error)) return FALSE; } else { g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } } /* call setup on all the children we produced */ children = fu_device_get_children (device); for (guint i=0 ; i < children->len; i++) { FuDevice *child = g_ptr_array_index (children, i); g_autoptr(FuDeviceLocker) locker = NULL; g_debug ("setup %s", fu_device_get_name (child)); locker = fu_device_locker_new (child, error); if (locker == NULL) return FALSE; } return TRUE; } static gboolean fu_dell_dock_ec_open (FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); if (!fu_device_open (self->symbiote, error)) return FALSE; return fu_dell_dock_is_valid_dock (device, error); } static gboolean fu_dell_dock_ec_close (FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); return fu_device_close (self->symbiote, error); } static void fu_dell_dock_ec_finalize (GObject *object) { FuDellDockEc *self = FU_DELL_DOCK_EC (object); g_object_unref (self->symbiote); g_free (self->ec_version); g_free (self->mst_version); g_free (self->tbt_version); g_free (self->data); g_free (self->raw_versions); g_free (self->ec_minimum_version); G_OBJECT_CLASS (fu_dell_dock_ec_parent_class)->finalize (object); } static void fu_dell_dock_ec_init (FuDellDockEc *self) { self->data = g_new0 (FuDellDockDockDataStructure, 1); self->raw_versions = g_new0 (FuDellDockDockPackageFWVersion, 1); } static void fu_dell_dock_ec_class_init (FuDellDockEcClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); object_class->finalize = fu_dell_dock_ec_finalize; klass_device->activate = fu_dell_dock_ec_activate; klass_device->to_string = fu_dell_dock_ec_to_string; klass_device->probe = fu_dell_dock_ec_probe; klass_device->setup = fu_dell_dock_ec_setup; klass_device->open = fu_dell_dock_ec_open; klass_device->close = fu_dell_dock_ec_close; klass_device->write_firmware = fu_dell_dock_ec_write_fw; klass_device->set_quirk_kv = fu_dell_dock_ec_set_quirk_kv; } FuDellDockEc * fu_dell_dock_ec_new (FuDevice *symbiote) { FuDellDockEc *self = NULL; self = g_object_new (FU_TYPE_DELL_DOCK_EC, NULL); self->symbiote = g_object_ref (symbiote); fu_device_set_physical_id (FU_DEVICE (self), fu_device_get_physical_id (self->symbiote)); fu_device_set_logical_id (FU_DEVICE (self), "ec"); return self; } fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-i2c-ec.h000066400000000000000000000027361402665037500217420ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include "config.h" #include #include "fu-device.h" G_BEGIN_DECLS #define FU_TYPE_DELL_DOCK_EC (fu_dell_dock_ec_get_type ()) G_DECLARE_FINAL_TYPE (FuDellDockEc, fu_dell_dock_ec, FU, DELL_DOCK_EC, FuDevice) FuDellDockEc *fu_dell_dock_ec_new (FuDevice *symbiote); G_END_DECLS gboolean fu_dell_dock_ec_needs_tbt (FuDevice *device); gboolean fu_dell_dock_ec_tbt_passive (FuDevice *device); gboolean fu_dell_dock_ec_modify_lock (FuDevice *self, guint8 target, gboolean unlocked, GError **error); gboolean fu_dell_dock_ec_reboot_dock (FuDevice *device, GError **error); const gchar *fu_dell_dock_ec_get_mst_version (FuDevice *device); const gchar *fu_dell_dock_ec_get_tbt_version (FuDevice *device); guint32 fu_dell_dock_ec_get_status_version (FuDevice *device); gboolean fu_dell_dock_ec_commit_package (FuDevice *device, GBytes *blob_fw, GError **error); FuDevice *fu_dell_dock_ec_get_symbiote (FuDevice *device); fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-i2c-mst.c000066400000000000000000000626721402665037500221560ustar00rootroot00000000000000/* * Copyright (C) 2018 Synaptics * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include "fu-common.h" #include "fu-dell-dock-common.h" #define I2C_MST_ADDRESS 0x72 /* MST registers */ #define MST_RC_TRIGGER_ADDR 0x2000fc #define MST_CORE_MCU_BOOTLOADER_STS 0x20010c #define MST_RC_COMMAND_ADDR 0x200110 #define MST_RC_OFFSET_ADDR 0x200114 #define MST_RC_LENGTH_ADDR 0x200118 #define MST_RC_DATA_ADDR 0x200120 #define MST_CORE_MCU_FW_VERSION 0x200160 #define MST_REG_QUAD_DISABLE 0x200fc0 #define MST_REG_HDCP22_DISABLE 0x200f90 /* MST remote control commands */ #define MST_CMD_ENABLE_REMOTE_CONTROL 0x1 #define MST_CMD_DISABLE_REMOTE_CONTROL 0x2 #define MST_CMD_CHECKSUM 0x11 #define MST_CMD_ERASE_FLASH 0x14 #define MST_CMD_WRITE_FLASH 0x20 #define MST_CMD_READ_FLASH 0x30 #define MST_CMD_WRITE_MEMORY 0x21 #define MST_CMD_READ_MEMORY 0x31 /* Arguments related to flashing */ #define FLASH_SECTOR_ERASE_4K 0x1000 #define FLASH_SECTOR_ERASE_32K 0x2000 #define FLASH_SECTOR_ERASE_64K 0x3000 #define EEPROM_TAG_OFFSET 0x1fff0 #define EEPROM_BANK_OFFSET 0x20000 #define EEPROM_ESM_OFFSET 0x40000 /* Flash offsets */ #define MST_BOARDID_OFFSET 0x10e /* Remote control offsets */ #define MST_CHIPID_OFFSET 0x1500 /* magic triggers */ #define MST_TRIGGER_WRITE 0xf2 #define MST_TRIGGER_REBOOT 0xf5 /* IDs used in DELL_DOCK */ #define EXPECTED_CHIPID 0x5331 /* firmware file offsets */ #define MST_BLOB_VERSION_OFFSET 0x06F0 typedef enum { Bank0, Bank1, ESM, } MSTBank; typedef struct { guint start; guint length; } MSTBankAttributes; const MSTBankAttributes bank0_attributes = { .start = 0, .length = EEPROM_BANK_OFFSET, }; const MSTBankAttributes bank1_attributes = { .start = EEPROM_BANK_OFFSET, .length = EEPROM_BANK_OFFSET, }; const MSTBankAttributes esm_attributes = { .start = EEPROM_ESM_OFFSET, .length = 0x3ffff }; FuHIDI2CParameters mst_base_settings = { .i2cslaveaddr = I2C_MST_ADDRESS, .regaddrlen = 0, .i2cspeed = I2C_SPEED_400K, }; struct _FuDellDockMst { FuDevice parent_instance; FuDevice *symbiote; guint8 unlock_target; guint64 blob_major_offset; guint64 blob_minor_offset; guint64 blob_build_offset; }; G_DEFINE_TYPE (FuDellDockMst, fu_dell_dock_mst, FU_TYPE_DEVICE) /** * fu_dell_dock_mst_get_bank_attribs: * @bank: An MSTBank * @out (out): The MSTBankAttributes attribute that matches * @error: the #GError, or %NULL * * Returns a structure that corresponds to the attributes for a bank * * Returns: %TRUE for success **/ static gboolean fu_dell_dock_mst_get_bank_attribs (MSTBank bank, const MSTBankAttributes **out, GError **error) { switch (bank) { case Bank0: *out = &bank0_attributes; break; case Bank1: *out = &bank1_attributes; break; case ESM: *out = &esm_attributes; break; default: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid bank specified %u", bank); return FALSE; } return TRUE; } static gboolean fu_dell_dock_mst_rc_command (FuDevice *symbiote, guint8 cmd, guint32 length, guint32 offset, const guint8 *data, GError **error); static gboolean fu_dell_dock_mst_read_register (FuDevice *symbiote, guint32 address, gsize length, GBytes **bytes, GError **error) { g_return_val_if_fail (symbiote != NULL, FALSE); g_return_val_if_fail (bytes != NULL, FALSE); g_return_val_if_fail (length <= 32, FALSE); /* write the offset we're querying */ if (!fu_dell_dock_hid_i2c_write (symbiote, (guint8 *) &address, 4, &mst_base_settings, error)) return FALSE; /* read data for the result */ if (!fu_dell_dock_hid_i2c_read (symbiote, 0, length, bytes, &mst_base_settings, error)) return FALSE; return TRUE; } static gboolean fu_dell_dock_mst_write_register (FuDevice *symbiote, guint32 address, guint8 *data, gsize length, GError **error) { g_autofree guint8 *buffer = g_malloc0 (length + 4); memcpy (buffer, &address, 4); memcpy (buffer + 4, data, length); g_return_val_if_fail (symbiote != NULL, FALSE); g_return_val_if_fail (data != NULL, FALSE); /* write the offset we're querying */ return fu_dell_dock_hid_i2c_write (symbiote, buffer, length + 4, &mst_base_settings, error); } static gboolean fu_dell_dock_mst_query_active_bank (FuDevice *symbiote, MSTBank *active, GError **error) { g_autoptr(GBytes) bytes = NULL; const guint32 *data = NULL; gsize length = 4; if (!fu_dell_dock_mst_read_register (symbiote, MST_CORE_MCU_BOOTLOADER_STS, length, &bytes, error)) { g_prefix_error (error, "Failed to query active bank: "); return FALSE; } data = g_bytes_get_data (bytes, &length); if ((data[0] & (1 << 7)) || (data[0] & (1 << 30))) *active = Bank1; else *active = Bank0; g_debug ("MST: active bank is: %u", *active); return TRUE; } static gboolean fu_dell_dock_mst_disable_remote_control (FuDevice *symbiote, GError **error) { g_debug ("MST: Disabling remote control"); return fu_dell_dock_mst_rc_command (symbiote, MST_CMD_DISABLE_REMOTE_CONTROL, 0, 0, NULL, error); } static gboolean fu_dell_dock_mst_enable_remote_control (FuDevice *symbiote, GError **error) { g_autoptr(GError) error_local = NULL; const gchar *data = "PRIUS"; g_debug ("MST: Enabling remote control"); if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_ENABLE_REMOTE_CONTROL, 5, 0, (guint8 *) data, &error_local)) { g_debug ("Failed to enable remote control: %s", error_local->message); /* try to disable / re-enable */ if (!fu_dell_dock_mst_disable_remote_control (symbiote, error)) return FALSE; return fu_dell_dock_mst_enable_remote_control (symbiote, error); } return TRUE; } static gboolean fu_dell_dock_trigger_rc_command (FuDevice *symbiote, GError **error) { const guint8 *result = NULL; guint32 tmp; /* Trigger the write */ tmp = MST_TRIGGER_WRITE; if (!fu_dell_dock_mst_write_register (symbiote, MST_RC_TRIGGER_ADDR, (guint8 *) &tmp, sizeof(guint32), error)) { g_prefix_error (error, "Failed to write MST_RC_TRIGGER_ADDR: "); return FALSE; } /* poll for completion */ tmp = 0xffff; for (guint i = 0; i < 1000; i++) { g_autoptr(GBytes) bytes = NULL; if (!fu_dell_dock_mst_read_register (symbiote, MST_RC_COMMAND_ADDR, sizeof(guint32), &bytes, error)) { g_prefix_error (error, "Failed to poll MST_RC_COMMAND_ADDR"); return FALSE; } result = g_bytes_get_data (bytes, NULL); /* complete */ if ((result[2] & 0x80) == 0) { tmp = result[3]; break; } g_usleep (2000); } switch (tmp) { /* need to enable remote control */ case 4: return fu_dell_dock_mst_enable_remote_control (symbiote, error); /* error scenarios */ case 3: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown error"); return FALSE; case 2: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unsupported command"); return FALSE; case 1: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid argument"); return FALSE; /* success scenario */ case 0: return TRUE; default: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command timed out or unknown failure: %x", tmp); return FALSE; } } static gboolean fu_dell_dock_mst_rc_command (FuDevice *symbiote, guint8 cmd, guint32 length, guint32 offset, const guint8 *data, GError **error) { /* 4 for cmd, 4 for offset, 4 for length, 4 for garbage */ gint buffer_len = (data == NULL) ? 12 : length + 16; g_autofree guint8 *buffer = g_malloc0 (buffer_len); guint32 tmp; g_return_val_if_fail (symbiote != NULL, FALSE); /* command */ tmp = (cmd | 0x80) << 16; memcpy (buffer, &tmp, 4); /* offset */ memcpy (buffer + 4, &offset, 4); /* length */ memcpy (buffer + 8, &length, 4); /* data */ if (data != NULL) memcpy (buffer + 16, data, length); /* write the combined register stream */ if (!fu_dell_dock_mst_write_register (symbiote, MST_RC_COMMAND_ADDR, buffer, buffer_len, error)) return FALSE; return fu_dell_dock_trigger_rc_command (symbiote, error); } static gboolean fu_dell_dock_mst_read_chipid (FuDevice *symbiote, guint16 *chip_id, GError **error) { g_autoptr(GBytes) bytes = NULL; const guint8 *data; gsize length = 4; g_return_val_if_fail (chip_id != NULL, FALSE); /* run an RC command to get data from memory */ if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_READ_MEMORY, length, MST_CHIPID_OFFSET, NULL, error)) return FALSE; if (!fu_dell_dock_mst_read_register (symbiote, MST_RC_DATA_ADDR, length, &bytes, error)) return FALSE; data = g_bytes_get_data (bytes, &length); *chip_id = (data[1] << 8) | data[2]; return TRUE; } static gboolean fu_dell_dock_mst_check_offset (guint8 byte, guint8 offset) { if ((byte & offset) != 0) return TRUE; return FALSE; } static gboolean fu_d19_mst_check_fw (FuDevice *symbiote, GError **error) { g_autoptr(GBytes) bytes = NULL; const guint8 *data; gsize length = 4; if (!fu_dell_dock_mst_read_register (symbiote, MST_CORE_MCU_BOOTLOADER_STS, length, &bytes, error)) return FALSE; data = g_bytes_get_data (bytes, &length); g_debug ("MST: firmware check: %d", fu_dell_dock_mst_check_offset (data[0], 0x01)); g_debug ("MST: HDCP key check: %d", fu_dell_dock_mst_check_offset (data[0], 0x02)); g_debug ("MST: Config0 check: %d", fu_dell_dock_mst_check_offset (data[0], 0x04)); g_debug ("MST: Config1 check: %d", fu_dell_dock_mst_check_offset (data[0], 0x08)); if (fu_dell_dock_mst_check_offset (data[0], 0xF0)) g_debug ("MST: running in bootloader"); else g_debug ("MST: running in firmware"); g_debug ("MST: Error code: %x", data[1]); g_debug ("MST: GPIO boot strap record: %d", data[2]); g_debug ("MST: Bootloader version number %x", data[3]); return TRUE; } static gboolean fu_dell_dock_mst_checksum_bank (FuDevice *symbiote, GBytes *blob_fw, MSTBank bank, gboolean *checksum, GError **error) { g_autoptr(GBytes) csum_bytes = NULL; const MSTBankAttributes *attribs = NULL; gsize length = 0; const guint8 *data = g_bytes_get_data (blob_fw, &length); guint32 payload_sum = 0; guint32 bank_sum = 0; g_return_val_if_fail (blob_fw != NULL, FALSE); g_return_val_if_fail (checksum != NULL, FALSE); if (!fu_dell_dock_mst_get_bank_attribs (bank, &attribs, error)) return FALSE; /* bank is specified outside of payload */ if (attribs->start + attribs->length > length) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Payload %u is bigger than bank %u", attribs->start + attribs->length, bank); return FALSE; } /* checksum the file */ for (guint i = attribs->start; i < attribs->length + attribs->start; i++) { payload_sum += data[i]; } g_debug ("MST: Payload checksum: 0x%x", payload_sum); /* checksum the bank */ if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_CHECKSUM, attribs->length, attribs->start, NULL, error)) { g_prefix_error (error, "Failed to checksum bank %u: ", bank); return FALSE; } /* read result from data register */ if (!fu_dell_dock_mst_read_register (symbiote, MST_RC_DATA_ADDR, 4, &csum_bytes, error)) return FALSE; data = g_bytes_get_data (csum_bytes, NULL); bank_sum = GUINT32_FROM_LE (data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24); g_debug ("MST: Bank %u checksum: 0x%x", bank, bank_sum); *checksum = (bank_sum == payload_sum); return TRUE; } static gboolean fu_dell_dock_mst_erase_bank (FuDevice *symbiote, MSTBank bank, GError **error) { const MSTBankAttributes *attribs = NULL; guint32 sector; if (!fu_dell_dock_mst_get_bank_attribs (bank, &attribs, error)) return FALSE; for (guint32 i = attribs->start; i < attribs->start + attribs->length; i += 0x10000) { sector = FLASH_SECTOR_ERASE_64K | (i / 0x10000); g_debug ("MST: Erasing sector 0x%x", sector); if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_ERASE_FLASH, 4, 0, (guint8 *) §or, error)) { g_prefix_error ( error, "Failed to erase sector 0x%x: ", sector); return FALSE; } } g_debug ("MST: Waiting for flash clear to settle"); g_usleep (5000000); return TRUE; } static gboolean fu_dell_dock_write_flash_bank (FuDevice *device, GBytes *blob_fw, MSTBank bank, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST (device); const MSTBankAttributes *attribs = NULL; gsize write_size = 32; guint end; const guint8 *data = g_bytes_get_data (blob_fw, NULL); g_return_val_if_fail (blob_fw != NULL, FALSE); if (!fu_dell_dock_mst_get_bank_attribs (bank, &attribs, error)) return FALSE; end = attribs->start + attribs->length; g_debug ("MST: Writing payload to bank %u", bank); for (guint i = attribs->start; i < end; i += write_size) { if (!fu_dell_dock_mst_rc_command (self->symbiote, MST_CMD_WRITE_FLASH, write_size, i, data + i, error)) { g_prefix_error ( error, "Failed to write bank %u payload offset 0x%x: ", bank, i); return FALSE; } fu_device_set_progress_full (device, i - attribs->start, end - attribs->start); } return TRUE; } static gboolean fu_dell_dock_mst_stop_esm (FuDevice *symbiote, GError **error) { g_autoptr(GBytes) quad_bytes = NULL; g_autoptr(GBytes) hdcp_bytes = NULL; guint32 payload = 0x21; gsize length = sizeof(guint32); const guint8 *data; guint8 data_out[sizeof(guint32)]; /* disable ESM first */ if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_WRITE_MEMORY, length, MST_RC_TRIGGER_ADDR, (guint8 *) &payload, error)) return FALSE; /* waiting for ESM exit */ g_usleep(200); /* disable QUAD mode */ if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_READ_MEMORY, length, MST_REG_QUAD_DISABLE, NULL, error)) return FALSE; if (!fu_dell_dock_mst_read_register (symbiote, MST_RC_DATA_ADDR, length, &quad_bytes, error)) return FALSE; data = g_bytes_get_data (quad_bytes, &length); memcpy (data_out, data, length); data_out[0] = 0x00; if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_WRITE_MEMORY, length, MST_REG_QUAD_DISABLE, data_out, error)) return FALSE; /* disable HDCP2.2 */ if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_READ_MEMORY, length, MST_REG_HDCP22_DISABLE, NULL, error)) return FALSE; if (!fu_dell_dock_mst_read_register (symbiote, MST_RC_DATA_ADDR, length, &hdcp_bytes, error)) return FALSE; data = g_bytes_get_data (hdcp_bytes, &length); memcpy (data_out, data, length); data_out[0] = data[0] & (1 << 2); if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_WRITE_MEMORY, length, MST_REG_HDCP22_DISABLE, data_out, error)) return FALSE; return TRUE; } static gboolean fu_dell_dock_mst_invalidate_bank (FuDevice *symbiote, MSTBank bank_in_use, GError **error) { const MSTBankAttributes *attribs; g_autoptr(GBytes) bytes = NULL; const guint8 *crc_tag; const guint8 *new_tag; guint32 crc_offset; guint retries = 2; if (!fu_dell_dock_mst_get_bank_attribs (bank_in_use, &attribs, error)) { g_prefix_error (error, "unable to invalidate bank: "); return FALSE; } /* we need to write 4 byte increments over I2C so this differs from DP aux */ crc_offset = attribs->start + EEPROM_TAG_OFFSET + 12; /* Read CRC byte to flip */ if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_READ_FLASH, 4, crc_offset, NULL, error)) { g_prefix_error (error, "failed to read tag from flash: "); return FALSE; } if (!fu_dell_dock_mst_read_register (symbiote, MST_RC_DATA_ADDR, 1, &bytes, error)) { return FALSE; } crc_tag = g_bytes_get_data (bytes, NULL); g_debug ("CRC byte is currently 0x%x", crc_tag[3]); for (guint32 retries_cnt = 0; ; retries_cnt++) { g_autoptr(GBytes) bytes_new = NULL; /* CRC8 is not 0xff, erase last 4k of bank# */ if (crc_tag[3] != 0xff) { guint32 sector = FLASH_SECTOR_ERASE_4K + (attribs->start + attribs->length - 0x1000) / 0x1000; g_debug ("Erasing 4k from sector 0x%x invalidate bank %u", sector, bank_in_use); /* offset for last 4k of bank# */ if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_ERASE_FLASH, 4, 0, (guint8 *) §or, error)) { g_prefix_error (error, "failed to erase sector 0x%x: ", sector); return FALSE; } /* CRC8 is 0xff, set it to 0x00 */ } else { guint32 write = 0x00; g_debug ("Writing 0x00 byte to 0x%x to invalidate bank %u", crc_offset, bank_in_use); if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_WRITE_FLASH, 4, crc_offset, (guint8*) &write, error)) { g_prefix_error (error, "failed to clear CRC byte: "); return FALSE; } } /* re-read for comparison */ if (!fu_dell_dock_mst_rc_command (symbiote, MST_CMD_READ_FLASH, 4, crc_offset, NULL, error)) { g_prefix_error (error, "failed to read tag from flash: "); return FALSE; } if (!fu_dell_dock_mst_read_register (symbiote, MST_RC_DATA_ADDR, 4, &bytes_new, error)) { return FALSE; } new_tag = g_bytes_get_data (bytes_new, NULL); g_debug ("CRC byte is currently 0x%x", new_tag[3]); /* tag successfully cleared */ if ((new_tag[3] == 0xff && crc_tag[3] != 0xff) || (new_tag[3] == 0x00 && crc_tag[3] == 0xff)) { break; } if (retries_cnt > retries) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set tag invalid fail (new 0x%x; old 0x%x)", new_tag[3], crc_tag[3]); return FALSE; } } return TRUE; } static gboolean fu_dell_dock_mst_write_fw (FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST (device); MSTBank bank_in_use = 0; guint retries = 2; gboolean checksum = FALSE; guint8 order[2] = {ESM, Bank0}; guint16 chip_id; const guint8* data = g_bytes_get_data (blob_fw, NULL); g_autofree gchar *dynamic_version = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (blob_fw != NULL, FALSE); g_return_val_if_fail (self->symbiote != NULL, FALSE); dynamic_version = g_strdup_printf ("%02x.%02x.%02x", data[self->blob_major_offset], data[self->blob_minor_offset], data[self->blob_build_offset]); g_debug ("writing MST firmware version %s", dynamic_version); /* determine the flash order */ if (!fu_dell_dock_mst_query_active_bank (self->symbiote, &bank_in_use, error)) return FALSE; if (bank_in_use == Bank0) order[1] = Bank1; /* enable remote control */ if (!fu_dell_dock_mst_enable_remote_control (self->symbiote, error)) return FALSE; /* Read Synaptics MST chip ID */ if (!fu_dell_dock_mst_read_chipid (self->symbiote, &chip_id, error)) return FALSE; if (chip_id != EXPECTED_CHIPID) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown MST chip found %x", chip_id); return FALSE; } /* ESM needs special handling during flash process*/ if (!fu_dell_dock_mst_stop_esm (self->symbiote, error)) return FALSE; /* Write each bank in order */ for (guint phase = 0; phase < 2; phase++) { g_debug ("MST: Checking bank %u", order[phase]); if (!fu_dell_dock_mst_checksum_bank (self->symbiote, blob_fw, order[phase], &checksum, error)) return FALSE; if (checksum) { g_debug ("MST: bank %u is already up to date", order[phase]); continue; } g_debug ("MST: bank %u needs to be updated", order[phase]); for (guint i = 0; i < retries; i++) { fu_device_set_progress_full (device, 0, 100); fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_dell_dock_mst_erase_bank (self->symbiote, order[phase], error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); if (!fu_dell_dock_write_flash_bank (device, blob_fw, order[phase], error)) return FALSE; if (!fu_dell_dock_mst_checksum_bank (self->symbiote, blob_fw, order[phase], &checksum, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY); if (!checksum) { g_debug ( "MST: Failed to verify checksum on bank %u", order[phase]); continue; } g_debug ("MST: Bank %u successfully flashed", order[phase]); break; } /* failed after all our retries */ if (!checksum) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to write to bank %u", order[phase]); return FALSE; } } /* invalidate the previous bank */ if (!fu_dell_dock_mst_invalidate_bank (self->symbiote, bank_in_use, error)) { g_prefix_error (error, "failed to invalidate bank %u: ", bank_in_use); return FALSE; } /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version (device, dynamic_version, FWUPD_VERSION_FORMAT_TRIPLET); /* disable remote control now */ return fu_dell_dock_mst_disable_remote_control (self->symbiote, error); } static gboolean fu_dell_dock_mst_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST (device); if (g_strcmp0 (key, "DellDockUnlockTarget") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < G_MAXUINT8) { self->unlock_target = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid DellDockUnlockTarget"); return FALSE; } if (g_strcmp0 (key, "DellDockBlobMajorOffset") == 0) { self->blob_major_offset = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "DellDockBlobMinorOffset") == 0) { self->blob_minor_offset = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "DellDockBlobBuildOffset") == 0) { self->blob_build_offset = fu_common_strtoull (value); return TRUE; } else if (g_strcmp0 (key, "DellDockInstallDurationI2C") == 0) { guint64 tmp = fu_common_strtoull (value); fu_device_set_install_duration (device, tmp); return TRUE; } /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_dell_dock_mst_setup (FuDevice *device, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST (device); FuDevice *parent; const gchar *version; /* sanity check that we can talk to MST */ if (!fu_d19_mst_check_fw (self->symbiote, error)) return FALSE; /* set version from EC if we know it */ parent = fu_device_get_parent (device); version = fu_dell_dock_ec_get_mst_version (parent); if (version != NULL) fu_device_set_version (device, version, FWUPD_VERSION_FORMAT_TRIPLET); fu_dell_dock_clone_updatable (device); return TRUE; } static gboolean fu_dell_dock_mst_probe (FuDevice *device, GError **error) { fu_device_set_logical_id (FU_DEVICE (device), "mst"); return TRUE; } static gboolean fu_dell_dock_mst_open (FuDevice *device, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST (device); FuDevice *parent = fu_device_get_parent (device); g_return_val_if_fail (self->unlock_target != 0, FALSE); g_return_val_if_fail (parent != NULL, FALSE); if (self->symbiote == NULL) self->symbiote = g_object_ref (fu_dell_dock_ec_get_symbiote (parent)); if (!fu_device_open (self->symbiote, error)) return FALSE; /* open up access to controller bus */ if (!fu_dell_dock_set_power (device, self->unlock_target, TRUE, error)) return FALSE; return TRUE; } static gboolean fu_dell_dock_mst_close (FuDevice *device, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST (device); /* close access to controller bus */ if (!fu_dell_dock_set_power (device, self->unlock_target, FALSE, error)) return FALSE; return fu_device_close (self->symbiote, error); } static void fu_dell_dock_mst_finalize (GObject *object) { FuDellDockMst *self = FU_DELL_DOCK_MST (object); g_object_unref (self->symbiote); G_OBJECT_CLASS (fu_dell_dock_mst_parent_class)->finalize (object); } static void fu_dell_dock_mst_init (FuDellDockMst *self) { } static void fu_dell_dock_mst_class_init (FuDellDockMstClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); object_class->finalize = fu_dell_dock_mst_finalize; klass_device->probe = fu_dell_dock_mst_probe; klass_device->open = fu_dell_dock_mst_open; klass_device->close = fu_dell_dock_mst_close; klass_device->setup = fu_dell_dock_mst_setup; klass_device->probe = fu_dell_dock_mst_probe; klass_device->write_firmware = fu_dell_dock_mst_write_fw; klass_device->set_quirk_kv = fu_dell_dock_mst_set_quirk_kv; } FuDellDockMst * fu_dell_dock_mst_new (void) { FuDellDockMst *device = NULL; device = g_object_new (FU_TYPE_DELL_DOCK_MST, NULL); return device; } fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-i2c-mst.h000066400000000000000000000014071402665037500221500ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include "config.h" #include "fu-device.h" G_BEGIN_DECLS #define FU_TYPE_DELL_DOCK_MST (fu_dell_dock_mst_get_type ()) G_DECLARE_FINAL_TYPE (FuDellDockMst, fu_dell_dock_mst, FU, DELL_DOCK_MST, FuDevice) FuDellDockMst *fu_dell_dock_mst_new (void); G_END_DECLS fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-i2c-tbt.c000066400000000000000000000204621402665037500221330ustar00rootroot00000000000000/* * Copyright (C) 2019 Intel Corporation. * Copyright (C) 2019 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include "fwupd-error.h" #include "fu-device.h" #include "fu-common.h" #include "fu-common-version.h" #include "fu-dell-dock-common.h" #define I2C_TBT_ADDRESS 0xa2 const FuHIDI2CParameters tbt_base_settings = { .i2cslaveaddr = I2C_TBT_ADDRESS, .regaddrlen = 1, .i2cspeed = I2C_SPEED_400K, }; /* TR Device ID */ #define PID_OFFSET 0x05 #define INTEL_PID 0x15ef /* earlier versions have bugs */ #define MIN_NVM "36.01" struct _FuDellDockTbt { FuDevice parent_instance; FuDevice *symbiote; guint8 unlock_target; guint64 blob_major_offset; guint64 blob_minor_offset; gchar *hub_minimum_version; }; G_DEFINE_TYPE (FuDellDockTbt, fu_dell_dock_tbt, FU_TYPE_DEVICE) static gboolean fu_dell_dock_tbt_write_fw (FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT (device); guint32 start_offset = 0; gsize image_size; const guint8 *buffer = g_bytes_get_data (blob_fw, &image_size); guint16 target_system = 0; g_autoptr(GTimer) timer = g_timer_new (); g_autofree gchar *dynamic_version = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (blob_fw != NULL, FALSE); dynamic_version = g_strdup_printf ("%02x.%02x", buffer[self->blob_major_offset], buffer[self->blob_minor_offset]); g_debug ("writing Thunderbolt firmware version %s", dynamic_version); g_debug ("Total Image size: %" G_GSIZE_FORMAT, image_size); memcpy (&start_offset, buffer, sizeof (guint32)); g_debug ("Header size 0x%x", start_offset); if (start_offset > image_size) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Image header is too big (0x%x)", start_offset); return FALSE; } memcpy (&target_system, buffer + start_offset + PID_OFFSET, sizeof (guint16)); if (target_system != INTEL_PID) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Image is not intended for this system (0x%x)", target_system); return FALSE; } buffer += start_offset; image_size -= start_offset; g_debug ("waking Thunderbolt controller"); if (!fu_dell_dock_hid_tbt_wake (self->symbiote, &tbt_base_settings, error)) return FALSE; g_usleep (2000000); fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < image_size; i+= HIDI2C_MAX_WRITE, buffer += HIDI2C_MAX_WRITE) { guint8 write_size = (image_size - i) > HIDI2C_MAX_WRITE ? HIDI2C_MAX_WRITE : (image_size - i); if (!fu_dell_dock_hid_tbt_write (self->symbiote, i, buffer, write_size, &tbt_base_settings, error)) return FALSE; fu_device_set_progress_full (device, i, image_size); } g_debug ("writing took %f seconds", g_timer_elapsed (timer, NULL)); fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY); if (fu_dell_dock_ec_tbt_passive (fu_device_get_parent (device))) { g_debug ("using passive flow for Thunderbolt"); } else if (!fu_dell_dock_hid_tbt_authenticate (self->symbiote, &tbt_base_settings, error)) { g_prefix_error (error, "failed to authenticate: "); return FALSE; } /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version (device, dynamic_version, FWUPD_VERSION_FORMAT_PAIR); return TRUE; } static gboolean fu_dell_dock_tbt_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT (device); if (g_strcmp0 (key, "DellDockUnlockTarget") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < G_MAXUINT8) { self->unlock_target = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid DellDockUnlockTarget"); return FALSE; } else if (g_strcmp0 (key, "DellDockInstallDurationI2C") == 0) { guint64 tmp = fu_common_strtoull (value); fu_device_set_install_duration (device, tmp); return TRUE; } else if (g_strcmp0 (key, "DellDockHubVersionLowest") == 0) { self->hub_minimum_version = g_strdup (value); return TRUE; } else if (g_strcmp0 (key, "DellDockBlobMajorOffset") == 0) { self->blob_major_offset = fu_common_strtoull (value); return TRUE; } else if (g_strcmp0 (key, "DellDockBlobMinorOffset") == 0) { self->blob_minor_offset = fu_common_strtoull (value); return TRUE; } /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_dell_dock_tbt_setup (FuDevice *device, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT (device); FuDevice *parent; const gchar *version; const gchar *hub_version; /* set version from EC if we know it */ parent = fu_device_get_parent (device); version = fu_dell_dock_ec_get_tbt_version (parent); if (version != NULL) fu_device_set_version (device, version, FWUPD_VERSION_FORMAT_PAIR); /* minimum version of NVM that supports this feature */ if (version == NULL || fu_common_vercmp (version, MIN_NVM) < 0) { fu_device_set_update_error (device, "Updates over I2C are disabled due to insuffient NVM version"); return TRUE; } /* minimum Hub2 version that supports this feature */ hub_version = fu_device_get_version (self->symbiote); if (fu_common_vercmp (hub_version, self->hub_minimum_version) < 0) { fu_device_set_update_error (device, "Updates over I2C are disabled due to insufficient USB 3.1 G2 hub version"); return TRUE; } fu_dell_dock_clone_updatable (device); return TRUE; } static gboolean fu_dell_dock_tbt_probe (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); fu_device_set_physical_id (device, fu_device_get_physical_id (parent)); fu_device_set_logical_id (FU_DEVICE (device), "tbt"); fu_device_add_instance_id (device, DELL_DOCK_TBT_INSTANCE_ID); return TRUE; } static gboolean fu_dell_dock_tbt_open (FuDevice *device, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT (device); FuDevice *parent; g_return_val_if_fail (self->unlock_target != 0, FALSE); parent = fu_device_get_parent (device); if (parent == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no parent"); return FALSE; } if (self->symbiote == NULL) self->symbiote = g_object_ref (fu_dell_dock_ec_get_symbiote (parent)); if (!fu_device_open (self->symbiote, error)) return FALSE; /* adjust to access controller */ if (!fu_dell_dock_set_power (device, self->unlock_target, TRUE, error)) return FALSE; return TRUE; } static gboolean fu_dell_dock_tbt_close (FuDevice *device, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT (device); /* adjust to access controller */ if (!fu_dell_dock_set_power (device, self->unlock_target, FALSE, error)) return FALSE; return fu_device_close (self->symbiote, error); } static void fu_dell_dock_tbt_finalize (GObject *object) { FuDellDockTbt *self = FU_DELL_DOCK_TBT (object); g_object_unref (self->symbiote); g_free (self->hub_minimum_version); G_OBJECT_CLASS (fu_dell_dock_tbt_parent_class)->finalize (object); } static void fu_dell_dock_tbt_init (FuDellDockTbt *device) {} static void fu_dell_dock_tbt_class_init (FuDellDockTbtClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); object_class->finalize = fu_dell_dock_tbt_finalize; klass_device->probe = fu_dell_dock_tbt_probe; klass_device->setup = fu_dell_dock_tbt_setup; klass_device->open = fu_dell_dock_tbt_open; klass_device->close = fu_dell_dock_tbt_close; klass_device->write_firmware = fu_dell_dock_tbt_write_fw; klass_device->set_quirk_kv = fu_dell_dock_tbt_set_quirk_kv; } FuDellDockTbt * fu_dell_dock_tbt_new (void) { FuDellDockTbt *device = NULL; device = g_object_new (FU_TYPE_DELL_DOCK_TBT, NULL); return device; } fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-i2c-tbt.h000066400000000000000000000014601402665037500221350ustar00rootroot00000000000000/* * Copyright (C) 2019 Intel Corporation. * Copyright (C) 2019 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include "config.h" #include "fu-device.h" G_BEGIN_DECLS #define FU_TYPE_DELL_DOCK_TBT (fu_dell_dock_tbt_get_type ()) G_DECLARE_FINAL_TYPE (FuDellDockTbt, fu_dell_dock_tbt, FU, DELL_DOCK_TBT, FuDevice) FuDellDockTbt *fu_dell_dock_tbt_new (void); G_END_DECLS fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-status.c000066400000000000000000000102411402665037500222040ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include "fu-dell-dock-common.h" struct _FuDellDockStatus { FuDevice parent_instance; guint64 blob_version_offset; }; G_DEFINE_TYPE (FuDellDockStatus, fu_dell_dock_status, FU_TYPE_DEVICE) static gchar * fu_dell_dock_status_ver_string (guint32 status_version) { /* guint32 BCD */ return g_strdup_printf ("%02x.%02x.%02x.%02x", status_version & 0xff, (status_version >> 8) & 0xff, (status_version >> 16) & 0xff, (status_version >> 24) & 0xff); } static gboolean fu_dell_dock_status_setup (FuDevice *device, GError **error) { FuDevice *parent; guint32 status_version; g_autofree gchar *dynamic_version = NULL; parent = fu_device_get_parent (device); status_version = fu_dell_dock_ec_get_status_version (parent); dynamic_version = fu_dell_dock_status_ver_string (status_version); fu_device_set_version (device, dynamic_version, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_logical_id (FU_DEVICE (device), "status"); fu_dell_dock_clone_updatable (device); return TRUE; } static gboolean fu_dell_dock_status_write (FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuDellDockStatus *self = FU_DELL_DOCK_STATUS (device); FuDevice *parent; gsize length = 0; guint32 status_version = 0; const guint8 *data = g_bytes_get_data (blob_fw, &length); g_autofree gchar *dynamic_version = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (blob_fw != NULL, FALSE); memcpy (&status_version, data + self->blob_version_offset, sizeof (guint32)); dynamic_version = fu_dell_dock_status_ver_string (status_version); g_debug ("writing status firmware version %s", dynamic_version); parent = fu_device_get_parent (device); if (!fu_dell_dock_ec_commit_package (parent, blob_fw, error)) return FALSE; /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version (device, dynamic_version, FWUPD_VERSION_FORMAT_QUAD); return TRUE; } static gboolean fu_dell_dock_status_open (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); g_return_val_if_fail (parent != NULL, FALSE); return fu_device_open (parent, error); } static gboolean fu_dell_dock_status_close (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); return fu_device_close (parent, error); } static gboolean fu_dell_dock_status_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockStatus *self = FU_DELL_DOCK_STATUS (device); if (g_strcmp0 (key, "DellDockBlobVersionOffset") == 0) { self->blob_version_offset = fu_common_strtoull (value); return TRUE; } /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_dell_dock_status_finalize (GObject *object) { G_OBJECT_CLASS (fu_dell_dock_status_parent_class)->finalize (object); } static void fu_dell_dock_status_init (FuDellDockStatus *self) { } static void fu_dell_dock_status_class_init (FuDellDockStatusClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); object_class->finalize = fu_dell_dock_status_finalize; klass_device->write_firmware = fu_dell_dock_status_write; klass_device->setup = fu_dell_dock_status_setup; klass_device->open = fu_dell_dock_status_open; klass_device->close = fu_dell_dock_status_close; klass_device->set_quirk_kv = fu_dell_dock_status_set_quirk_kv; } FuDellDockStatus * fu_dell_dock_status_new (void) { FuDellDockStatus *self = NULL; self = g_object_new (FU_TYPE_DELL_DOCK_STATUS, NULL); return self; } fwupd-1.2.14/plugins/dell-dock/fu-dell-dock-status.h000066400000000000000000000014331402665037500222140ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include "config.h" #include "fu-device.h" G_BEGIN_DECLS #define FU_TYPE_DELL_DOCK_STATUS (fu_dell_dock_status_get_type ()) G_DECLARE_FINAL_TYPE (FuDellDockStatus, fu_dell_dock_status, FU, DELL_DOCK_STATUS, FuDevice) FuDellDockStatus *fu_dell_dock_status_new (void); G_END_DECLS fwupd-1.2.14/plugins/dell-dock/fu-plugin-dell-dock.c000066400000000000000000000152531402665037500221670ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-device.h" #include "fwupd-error.h" #include "fu-plugin-vfuncs.h" #include "fu-dell-dock-common.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); /* allow these to be built by quirks */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); g_type_ensure (FU_TYPE_DELL_DOCK_STATUS); g_type_ensure (FU_TYPE_DELL_DOCK_MST); /* currently slower performance, but more reliable in corner cases */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_BETTER_THAN, "synapticsmst"); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.dell.dock"); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.synaptics.mst"); } static gboolean fu_plugin_dell_dock_create_node (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; fu_device_set_quirks (device, fu_plugin_get_quirks (plugin)); locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, device); return TRUE; } static gboolean fu_plugin_dell_dock_probe (FuPlugin *plugin, FuDevice *symbiote, GError **error) { g_autoptr(FuDellDockEc) ec_device = NULL; /* create all static endpoints */ ec_device = fu_dell_dock_ec_new (symbiote); if (!fu_plugin_dell_dock_create_node (plugin, FU_DEVICE (ec_device), error)) return FALSE; /* create TBT endpoint if Thunderbolt SKU and Thunderbolt link inactive */ if (fu_dell_dock_ec_needs_tbt (FU_DEVICE (ec_device))) { g_autoptr(FuDellDockTbt) tbt_device = fu_dell_dock_tbt_new (); fu_device_add_child (FU_DEVICE (ec_device), FU_DEVICE (tbt_device)); if (!fu_plugin_dell_dock_create_node (plugin, FU_DEVICE (tbt_device), error)) return FALSE; } return TRUE; } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuDellDockHub) hub = fu_dell_dock_hub_new (device); FuDevice *fu_device = FU_DEVICE (hub); const gchar *key = NULL; locker = fu_device_locker_new (fu_device, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, fu_device); if (fu_device_has_custom_flag (fu_device, "has-bridge")) { g_autoptr(GError) error_local = NULL; /* only add the device with parent to cache */ key = fu_device_get_id (fu_device); if (fu_plugin_cache_lookup (plugin, key) != NULL) { g_debug ("Ignoring already added device %s", key); return TRUE; } fu_plugin_cache_add (plugin, key, fu_device); /* probe for extended devices */ if (!fu_plugin_dell_dock_probe (plugin, fu_device, &error_local)) { g_warning ("Failed to probe bridged devices for %s: %s", key, error_local->message); } } /* clear updatable flag if parent doesn't have it */ fu_dell_dock_clone_updatable (fu_device); return TRUE; } gboolean fu_plugin_device_removed (FuPlugin *plugin, FuDevice *device, GError **error) { const gchar *device_key = fu_device_get_id (device); FuDevice *dev; FuDevice *parent; /* only the device with bridge will be in cache */ dev = fu_plugin_cache_lookup (plugin, device_key); if (dev == NULL) return TRUE; fu_plugin_cache_remove (plugin, device_key); /* find the parent and ask daemon to remove whole chain */ parent = fu_device_get_parent (dev); if (parent != NULL && FU_IS_DELL_DOCK_EC (parent)) { g_debug ("Removing %s (%s)", fu_device_get_name (parent), fu_device_get_id (parent)); fu_plugin_device_remove (plugin, parent); } return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_device_set_status (dev, FWUPD_STATUS_DEVICE_WRITE); if (!fu_device_write_firmware (dev, blob_fw, flags, error)) { g_prefix_error (error, "failed to update %s: ", fu_device_get_name (dev)); return FALSE; } fu_device_set_status (dev, FWUPD_STATUS_DEVICE_RESTART); return TRUE; } /* prefer to use EC if in the transaction and parent if it is not */ static FuDevice * fu_plugin_dell_dock_get_ec (GPtrArray *devices) { FuDevice *ec_parent = NULL; for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index (devices, i); FuDevice *parent; if (FU_IS_DELL_DOCK_EC (dev)) return dev; parent = fu_device_get_parent (dev); if (parent != NULL && FU_IS_DELL_DOCK_EC (parent)) ec_parent = parent; } return ec_parent; } gboolean fu_plugin_composite_prepare (FuPlugin *plugin, GPtrArray *devices, GError **error) { FuDevice *parent = fu_plugin_dell_dock_get_ec (devices); gboolean remaining_replug = FALSE; if (parent == NULL) return TRUE; for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index (devices, i); /* if thunderbolt is part of transaction our family is leaving us */ if (g_strcmp0 (fu_device_get_plugin (dev), "thunderbolt") == 0) { if (fu_device_get_parent (dev) != parent) continue; fu_dell_dock_will_replug (parent); /* set all other devices to replug */ remaining_replug = TRUE; continue; } /* different device */ if (fu_device_get_parent (dev) != parent) continue; if (remaining_replug) fu_dell_dock_will_replug (dev); } return TRUE; } gboolean fu_plugin_composite_cleanup (FuPlugin *plugin, GPtrArray *devices, GError **error) { FuDevice *parent = fu_plugin_dell_dock_get_ec (devices); g_autoptr(FuDeviceLocker) locker = NULL; if (parent == NULL) return TRUE; locker = fu_device_locker_new (parent, error); if (locker == NULL) return FALSE; return fu_dell_dock_ec_reboot_dock (parent, error); } gboolean fu_plugin_activate (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; if (!FU_IS_DELL_DOCK_EC (device)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Invalid device to activate"); return FALSE; } locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_activate (device, error); } fwupd-1.2.14/plugins/dell-dock/meson.build000066400000000000000000000013431402665037500204160ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginDellDock"'] install_data(['dell-dock.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_dell_dock', fu_hash, sources : [ 'fu-plugin-dell-dock.c', 'fu-dell-dock-common.c', 'fu-dell-dock-hid.c', 'fu-dell-dock-status.c', 'fu-dell-dock-i2c-ec.c', 'fu-dell-dock-hub.c', 'fu-dell-dock-i2c-tbt.c', 'fu-dell-dock-i2c-mst.c' ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, gudev, ], ) fwupd-1.2.14/plugins/dell-esrt/000077500000000000000000000000001402665037500163105ustar00rootroot00000000000000fwupd-1.2.14/plugins/dell-esrt/README.md000066400000000000000000000021661402665037500175740ustar00rootroot00000000000000Dell ESRT Support ================= Introduction ------------ This allows enabling the BIOS setup option for UEFI capsule updates without manually going into BIOS setup. GUID Generation --------------- These device uses a hardcoded GUID of `2d47f29b-83a2-4f31-a2e8-63474f4d4c2e`. Build Requirements ------------------ For Dell support you will need libsmbios_c version 2.4.0 or later. * source: https://github.com/dell/libsmbios * binaries: https://github.com/dell/libsmbios/releases If you don't want or need this functionality you can use the `-Dplugin_dell=false` option. # Devices powered by the Dell Plugin The Dell ESRT plugin allows the user to enable the UpdateCapsule functionality at runtime. A reboot will be required and the BIOS administrator password must not be set. Machines that offer this functionality will display an extra device in ```# fwupdmgr get-devices``` output. Example: ``` UEFI dummy device Guid: 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e Plugin: dell-esrt Flags: internal|updatable|locked Version: 0 Created: 2018-06-25 ``` fwupd-1.2.14/plugins/dell-esrt/dell-esrt.conf000066400000000000000000000003661402665037500210570ustar00rootroot00000000000000[fwupd Remote] # this remote provides metadata shipped with the fwupd package Enabled=true Title=Enable UEFI capsule updates on Dell systems Keyring=none MetadataURI=file://@datadir@/fwupd/remotes.d/dell-esrt/metadata.xml ApprovalRequired=false fwupd-1.2.14/plugins/dell-esrt/fu-plugin-dell-esrt.c000066400000000000000000000111041402665037500222500ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2017-2018 Dell, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include "fu-plugin-vfuncs.h" /* Whitelisted smbios class/select commands */ #define CLASS_ADMIN_PROP 10 #define SELECT_ADMIN_PROP 3 /* whitelisted tokens */ #define CAPSULE_EN_TOKEN 0x0461 #define CAPSULE_DIS_TOKEN 0x0462 /* these aren't defined upstream but used in fwupdate */ #define DELL_ADMIN_MASK 0xF #define DELL_ADMIN_INSTALLED 0 static gboolean fu_plugin_dell_esrt_query_token (guint16 token, gboolean *value, GError **error) { if (!token_is_bool (token)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "token %" G_GUINT16_FORMAT " is not boolean", token); return FALSE; } if (value != NULL) *value = token_is_active (token) > 0; return TRUE; } static gboolean fu_plugin_dell_esrt_activate_token (guint16 token, GError **error) { token_activate (token); if (token_is_active (token) < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "token %" G_GUINT16_FORMAT "cannot be activated " "as the password is set", token); return FALSE; } return TRUE; } static gboolean fu_plugin_dell_esrt_admin_password_present (gboolean *password_present, GError **error) { guint32 args[4] = { 0, }, out[4] = { 0, }; if (dell_simple_ci_smi (CLASS_ADMIN_PROP, SELECT_ADMIN_PROP, args, out)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot call SMI for CLASS_ADMIN_PROP"); return FALSE; } if (out[0] != 0 || (out[1] & DELL_ADMIN_MASK) == DELL_ADMIN_INSTALLED) { *password_present = TRUE; } else { *password_present = FALSE; } return TRUE; } void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { gboolean capsule_disable = FALSE; g_autofree gchar *sysfsfwdir = NULL; g_autofree gchar *esrtdir = NULL; /* already exists */ sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); esrtdir = g_build_filename (sysfsfwdir, "efi", "esrt", NULL); if (g_file_test (esrtdir, G_FILE_TEST_EXISTS)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "UEFI firmware already supported"); return FALSE; } /* is the capsule functionality disabled */ if (!fu_plugin_dell_esrt_query_token (CAPSULE_DIS_TOKEN, &capsule_disable, error)) return FALSE; if (!capsule_disable) { gboolean capsule_enable = FALSE; if (!fu_plugin_dell_esrt_query_token (CAPSULE_EN_TOKEN, &capsule_enable, error)) return FALSE; if (capsule_enable) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "UEFI firmware will be unlocked on next boot"); return FALSE; } } return TRUE; } gboolean fu_plugin_unlock (FuPlugin *plugin, FuDevice *device, GError **error) { gboolean password_present = FALSE; /* check the admin password isn't set */ if (!fu_plugin_dell_esrt_admin_password_present (&password_present, error)) return FALSE; if (password_present) { const gchar *err_string = "Cannot be unlocked automatically as admin password set"; g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, err_string); fu_device_set_update_error (device, err_string); return FALSE; } /* disabled in BIOS, but supported to be enabled via tool */ if (!fu_plugin_dell_esrt_query_token (CAPSULE_EN_TOKEN, NULL, error)) return FALSE; if (!fu_plugin_dell_esrt_activate_token (CAPSULE_EN_TOKEN, error)) return FALSE; fu_device_set_update_error (device, NULL); return TRUE; } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { g_autoptr(FuDevice) dev = fu_device_new (); /* create a dummy device so we can unlock the feature */ fu_device_set_id (dev, "UEFI-dummy-dev0"); fu_device_set_name (dev, "Dell UEFI updates"); fu_device_set_summary (dev, "Enable UEFI Update Functionality"); fu_device_add_guid (dev, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_set_version (dev, "0", FWUPD_VERSION_FORMAT_NUMBER); fu_device_add_icon (dev, "computer"); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_set_update_error (dev, "Firmware updates disabled; run 'fwupdmgr unlock' to enable"); fu_plugin_device_add (plugin, dev); return TRUE; } fwupd-1.2.14/plugins/dell-esrt/meson.build000066400000000000000000000014711402665037500204550ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginDellEsrt"'] install_data(['metadata.xml'], install_dir : join_paths(datadir, 'fwupd', 'remotes.d', 'dell-esrt') ) shared_module('fu_plugin_dell_esrt', fu_hash, sources : [ 'fu-plugin-dell-esrt.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, c_args : [ cargs, ], link_with : [ libfwupdprivate, ], dependencies : [ plugin_deps, libsmbios_c, ], ) # replace @datadir@ con2 = configuration_data() con2.set('datadir', datadir) configure_file( input : 'dell-esrt.conf', output : 'dell-esrt.conf', configuration : con2, install: true, install_dir: join_paths(sysconfdir, 'fwupd', 'remotes.d'), ) fwupd-1.2.14/plugins/dell-esrt/metadata.xml000066400000000000000000000016461402665037500206210ustar00rootroot00000000000000 org.fwupd.8330a096d9f1af8567c7374cb8403e1ce9cf3163.device 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e UEFI Updates Enable UEFI Update Functionality

Applying this update will enable the UEFI firmware reporting interface on your hardware.

You will have to restart your computer after this update is installed to be notified of any pending firmware updates.

fwupd-1.2.14/plugins/dell/000077500000000000000000000000001402665037500153355ustar00rootroot00000000000000fwupd-1.2.14/plugins/dell/README.md000066400000000000000000000126641402665037500166250ustar00rootroot00000000000000Dell Support ============ Introduction ------------ This allows installing Dell capsules that are not part of the ESRT table. GUID Generation --------------- These devices uses custom GUIDs for Dell-specific hardware. * Thunderbolt devices: `TBT-0x00d4u$(system-id)` * TPM devices `$(system-id)-$(mode)`, where `mode` is either `2.0` or `1.2` In both cases the `system-id` is derived from the SMBIOS Product SKU property. Build Requirements ------------------ For Dell support you will need libsmbios_c version 2.4.0 or later. * source: https://github.com/dell/libsmbios * binaries: https://github.com/dell/libsmbios/releases If you don't want or need this functionality you can use the `-Dplugin_dell=false` option. # Devices powered by the Dell Plugin The Dell plugin creates device nodes for PC's that have switchable TPMs as well as the Type-C docks (WD15/TB16). These device nodes can be flashed using UEFI capsule but don't use the ESRT table to communicate device status or version information. This is intentional behavior because more complicated decisions need to be made on the OS side to determine if the devices should be offered to flash. ## Switchable TPM Devices Machines with switchable TPMs can operate in both TPM 1.2 and TPM 2.0 modes. Switching modes will require flashing an alternative firmware and clearing the contents of the TPM. Machines that offer this functionality will display two devices in ```# fwupdmgr get-devices``` output. Example (from a *Precision 5510*): ``` Precision 5510 TPM 1.2 Guid: b2088ba1-51ae-514e-8f0a-64756c6e4ffc DeviceID: DELL-b2088ba1-51ae-514e-8f0a-64756c6e4ffclu Plugin: dell Flags: internal|allow-offline|require-ac Version: 5.81.0.0 Created: 2016-07-19 Precision 5510 TPM 2.0 Guid: 475d9bbd-1b7a-554e-8ca7-54985174a962 DeviceID: DELL-475d9bbd-1b7a-554e-8ca7-54985174a962lu Plugin: dell Flags: internal|require-ac|locked Created: 2016-07-19 ``` In this example, the TPM is currently operating in **TPM 1.2 mode**. Any firmware updates posted to *LVFS* for TPM 1.2 mode will be applied. ### Switching TPM Modes In order to be offered to switch the TPM to **TPM 2.0 mode**, the virtual device representing the *TPM 2.0 mode* will need to be unlocked. ```# fwupdmgr unlock DELL-475d9bbd-1b7a-554e-8ca7-54985174a962lu``` If the TPM is currently *owned*, an error will be displayed such as this one: ERROR: Precision 5510 TPM 1.2 is currently OWNED. Ownership must be removed to switch modes. TPM Ownership can be cleared from within the BIOS setup menus. If the unlock process was successful, then the devices will be modified: ``` Precision 5510 TPM 1.2 Guid: b2088ba1-51ae-514e-8f0a-64756c6e4ffc DeviceID: DELL-b2088ba1-51ae-514e-8f0a-64756c6e4ffclu Plugin: dell Flags: internal|require-ac Version: 5.81.0.0 Created: 2016-07-19 Precision 5510 TPM 2.0 Guid: 475d9bbd-1b7a-554e-8ca7-54985174a962 DeviceID: DELL-475d9bbd-1b7a-554e-8ca7-54985174a962lu Plugin: dell Flags: internal|allow-offline|require-ac Version: 0.0.0.0 Created: 2016-07-19 Modified: 2016-07-19 ``` Now the firmware for TPM 2.0 mode can be pulled down from LVFS and flashed: ```# fwupdmgr update``` Upon the next reboot, the new TPM firmware will be flashed. If the firmware is *not offered from LVFS*, then switching modes may not work on this machine. After updating the output from ```# fwupdmgr get-devices``` will reflect the new mode. ``` Precision 5510 TPM 2.0 Guid: 475d9bbd-1b7a-554e-8ca7-54985174a962 DeviceID: DELL-475d9bbd-1b7a-554e-8ca7-54985174a962lu Plugin: dell Flags: internal|allow-offline|require-ac Version: 1.3.0.1 Created: 2016-07-20 Precision 5510 TPM 1.2 Guid: b2088ba1-51ae-514e-8f0a-64756c6e4ffc DeviceID: DELL-b2088ba1-51ae-514e-8f0a-64756c6e4ffclu Plugin: dell Flags: internal|require-ac|locked Created: 2016-07-20 ``` Keep in mind that **TPM 1.2** and **TPM 2.0** will require different userspace tools. ## Dock Devices The *TB16* and *WD15* have a variety of updatable components. Each component will create a virtual device in ```# fwupdmgr get-devices``` For example the WD15 will display these components: ``` Dell WD15 Port Controller 1 Guid: 8ba2b709-6f97-47fc-b7e7-6a87b578fe25 DeviceID: DELL-8ba2b709-6f97-47fc-b7e7-6a87b578fe25lu Plugin: dell Flags: allow-offline|require-ac Version: 0.1.1.8 Created: 2016-07-19 Dell WD15 Guid: e7ca1f36-bf73-4574-afe6-a4ccacabf479 DeviceID: DELL-e7ca1f36-bf73-4574-afe6-a4ccacabf479lu Plugin: dell Flags: allow-offline|require-ac Version: 0.0.0.67 Created: 2016-07-19 ``` Components that can be updated via UEFI capsule will have the ```allow-offline``` moniker applied. These updates can be performed the standard method of using: ```# fwupdmgr update``` Some components are updatable via other plugins in fwupd such as multi stream transport hub (MST) and thunderbolt NVM. fwupd-1.2.14/plugins/dell/dell.quirk000066400000000000000000000017231402665037500173350ustar00rootroot00000000000000# Realtek NIC in Dell docks [DeviceInstanceId=USB\VID_0BDA&PID_8153] Plugin = dell # Dell TB16/TB18 cable [Guid=TBT-00d4b051] Plugin = thunderbolt ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 # Dell TB16/TB18 dock [Guid=TBT-00d4b054] Plugin = thunderbolt ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 # Dell WD15 dock [Guid=MST-wd15-vmm3332-274] Plugin = synapticsmst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 # Dell TB16 dock [Guid=MST-tb16-vmm3320-274] Plugin = synapticsmst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 [Guid=MST-tb16-vmm3330-274] Plugin = synapticsmst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 #Dell TB18 dock [Guid=MST-tb18-vmm3320-274] Plugin = synapticsmst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 [Guid=MST-tb18-vmm3330-274] Plugin = synapticsmst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 [SmbiosManufacturer=Dell Inc.] UefiVersionFormat = quad [SmbiosManufacturer=Alienware] UefiVersionFormat = quad fwupd-1.2.14/plugins/dell/fu-dell-smi.c000066400000000000000000000135461402665037500176300ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-dell-smi.h" /* These are for dock query capabilities */ struct dock_count_in { guint32 argument; guint32 reserved1; guint32 reserved2; guint32 reserved3; }; struct dock_count_out { guint32 ret; guint32 count; guint32 location; guint32 reserved; }; /* This is used for host flash GUIDs */ typedef union _ADDR_UNION{ uint8_t *buf; efi_guid_t *guid; } ADDR_UNION; #pragma pack() static void _dell_smi_obj_free (FuDellSmiObj *obj) { dell_smi_obj_free (obj->smi); g_free(obj); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC (FuDellSmiObj, _dell_smi_obj_free); #pragma clang diagnostic pop /* don't actually clear if we're testing */ gboolean fu_dell_clear_smi (FuDellSmiObj *obj) { if (obj->fake_smbios) return TRUE; for (gint i=0; i < 4; i++) { obj->input[i] = 0; obj->output[i] = 0; } return TRUE; } gboolean fu_dell_execute_smi (FuDellSmiObj *obj) { gint ret; if (obj->fake_smbios) return TRUE; ret = dell_smi_obj_execute (obj->smi); if (ret != 0) { g_debug ("SMI execution failed: %i", ret); return FALSE; } return TRUE; } guint32 fu_dell_get_res (FuDellSmiObj *smi_obj, guint8 arg) { if (smi_obj->fake_smbios) return smi_obj->output[arg]; return dell_smi_obj_get_res (smi_obj->smi, arg); } gboolean fu_dell_execute_simple_smi (FuDellSmiObj *obj, guint16 class, guint16 select) { /* test suite will mean don't actually call */ if (obj->fake_smbios) return TRUE; if (dell_simple_ci_smi (class, select, obj->input, obj->output)) { g_debug ("failed to run query %u/%u", class, select); return FALSE; } return TRUE; } gboolean fu_dell_detect_dock (FuDellSmiObj *smi_obj, guint32 *location) { struct dock_count_in *count_args; struct dock_count_out *count_out; /* look up dock count */ count_args = (struct dock_count_in *) smi_obj->input; count_out = (struct dock_count_out *) smi_obj->output; if (!fu_dell_clear_smi (smi_obj)) { g_debug ("failed to clear SMI buffers"); return FALSE; } count_args->argument = DACI_DOCK_ARG_COUNT; if (!fu_dell_execute_simple_smi (smi_obj, DACI_DOCK_CLASS, DACI_DOCK_SELECT)) return FALSE; if (count_out->ret != 0) { g_debug ("Failed to query system for dock count: " "(%" G_GUINT32_FORMAT ")", count_out->ret); return FALSE; } if (count_out->count < 1) { g_debug ("no dock plugged in"); return FALSE; } if (location != NULL) *location = count_out->location; return TRUE; } gboolean fu_dell_query_dock (FuDellSmiObj *smi_obj, DOCK_UNION *buf) { gint result; guint32 location; guint buf_size; if (!fu_dell_detect_dock (smi_obj, &location)) return FALSE; fu_dell_clear_smi (smi_obj); /* look up more information on dock */ if (smi_obj->fake_smbios) buf->buf = smi_obj->fake_buffer; else { dell_smi_obj_set_class (smi_obj->smi, DACI_DOCK_CLASS); dell_smi_obj_set_select (smi_obj->smi, DACI_DOCK_SELECT); dell_smi_obj_set_arg (smi_obj->smi, cbARG1, DACI_DOCK_ARG_INFO); dell_smi_obj_set_arg (smi_obj->smi, cbARG2, location); buf_size = sizeof (DOCK_INFO_RECORD); buf->buf = dell_smi_obj_make_buffer_frombios_auto (smi_obj->smi, cbARG3, buf_size); if (!buf->buf) { g_debug ("Failed to initialize buffer"); return FALSE; } } if (!fu_dell_execute_smi (smi_obj)) return FALSE; result = fu_dell_get_res (smi_obj, cbARG1); if (result != SMI_SUCCESS) { if (result == SMI_INVALID_BUFFER) { g_debug ("Invalid buffer size, needed %" G_GUINT32_FORMAT, fu_dell_get_res (smi_obj, cbARG2)); } else { g_debug ("SMI execution returned error: %d", result); } return FALSE; } return TRUE; } const gchar* fu_dell_get_dock_type (guint8 type) { g_autoptr (FuDellSmiObj) smi_obj = NULL; DOCK_UNION buf; /* not yet initialized, look it up */ if (type == DOCK_TYPE_NONE) { smi_obj = g_malloc0 (sizeof(FuDellSmiObj)); smi_obj->smi = dell_smi_factory (DELL_SMI_DEFAULTS); if (!fu_dell_query_dock (smi_obj, &buf)) return NULL; type = buf.record->dock_info_header.dock_type; } switch (type) { case DOCK_TYPE_TB16: return "TB16"; case DOCK_TYPE_WD15: return "WD15"; default: g_debug ("Dock type %d unknown", type); } return NULL; } gboolean fu_dell_toggle_dock_mode (FuDellSmiObj *smi_obj, guint32 new_mode, guint32 dock_location, GError **error) { /* Put into mode to accept AR/MST */ fu_dell_clear_smi (smi_obj); smi_obj->input[0] = DACI_DOCK_ARG_MODE; smi_obj->input[1] = dock_location; smi_obj->input[2] = new_mode; if (!fu_dell_execute_simple_smi (smi_obj, DACI_DOCK_CLASS, DACI_DOCK_SELECT)) return FALSE; if (smi_obj->output[1] != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Failed to set dock flash mode: %u", smi_obj->output[1]); return FALSE; } return TRUE; } gboolean fu_dell_toggle_host_mode (FuDellSmiObj *smi_obj, const efi_guid_t guid, int mode) { gint ret; ADDR_UNION buf; dell_smi_obj_set_class (smi_obj->smi, DACI_FLASH_INTERFACE_CLASS); dell_smi_obj_set_select (smi_obj->smi, DACI_FLASH_INTERFACE_SELECT); dell_smi_obj_set_arg (smi_obj->smi, cbARG1, DACI_FLASH_ARG_FLASH_MODE); dell_smi_obj_set_arg (smi_obj->smi, cbARG4, mode); /* needs to be padded with an empty GUID */ buf.buf = dell_smi_obj_make_buffer_frombios_withoutheader(smi_obj->smi, cbARG2, sizeof(efi_guid_t) * 2); if (!buf.buf) { g_debug ("Failed to initialize SMI buffer"); return FALSE; } *buf.guid = guid; ret = dell_smi_obj_execute(smi_obj->smi); if (ret != SMI_SUCCESS){ g_debug ("failed to execute SMI: %d", ret); return FALSE; } ret = dell_smi_obj_get_res(smi_obj->smi, cbRES1); if (ret != SMI_SUCCESS) { g_debug ("SMI execution returned error: %d", ret); return FALSE; } return TRUE; } fwupd-1.2.14/plugins/dell/fu-dell-smi.h000066400000000000000000000055571402665037500176400ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-device.h" #include #include #include typedef struct { struct dell_smi_obj *smi; guint32 input[4]; guint32 output[4]; gboolean fake_smbios; guint8 *fake_buffer; } FuDellSmiObj; /* Dock Info version 1 */ #pragma pack(1) #define MAX_COMPONENTS 5 typedef struct _COMPONENTS { gchar description[80]; guint32 fw_version; /* BCD format: 0x00XXYYZZ */ } COMPONENTS; typedef struct _DOCK_INFO { gchar dock_description[80]; guint32 flash_pkg_version; /* BCD format: 0x00XXYYZZ */ guint32 cable_type; /* bit0-7 cable type, bit7-31 set to 0 */ guint8 location; /* Location of the dock */ guint8 reserved; guint8 component_count; COMPONENTS components[MAX_COMPONENTS]; /* number of component_count */ } DOCK_INFO; typedef struct _DOCK_INFO_HEADER { guint8 dir_version; /* version 1, 2 … */ guint8 dock_type; guint16 reserved; } DOCK_INFO_HEADER; typedef struct _DOCK_INFO_RECORD { DOCK_INFO_HEADER dock_info_header; /* dock version specific definition */ DOCK_INFO dock_info; } DOCK_INFO_RECORD; typedef union _DOCK_UNION{ guint8 *buf; DOCK_INFO_RECORD *record; } DOCK_UNION; #pragma pack() typedef enum _DOCK_TYPE { DOCK_TYPE_NONE, DOCK_TYPE_TB16, DOCK_TYPE_WD15 } DOCK_TYPE; typedef enum _CABLE_TYPE { CABLE_TYPE_NONE, CABLE_TYPE_LEGACY, CABLE_TYPE_UNIV, CABLE_TYPE_TBT } CABLE_TYPE; gboolean fu_dell_clear_smi (FuDellSmiObj *obj); guint32 fu_dell_get_res (FuDellSmiObj *smi_obj, guint8 arg); gboolean fu_dell_execute_smi (FuDellSmiObj *obj); gboolean fu_dell_execute_simple_smi (FuDellSmiObj *obj, guint16 class, guint16 select); gboolean fu_dell_detect_dock (FuDellSmiObj *obj, guint32 *location); gboolean fu_dell_query_dock (FuDellSmiObj *smi_obj, DOCK_UNION *buf); const gchar* fu_dell_get_dock_type (guint8 type); gboolean fu_dell_toggle_dock_mode (FuDellSmiObj *smi_obj, guint32 new_mode, guint32 dock_location, GError **error); gboolean fu_dell_toggle_host_mode (FuDellSmiObj *smi_obj, const efi_guid_t guid, int mode); /* SMI return values used */ #define SMI_SUCCESS 0 #define SMI_INVALID_BUFFER -6 /* These are DACI class/select needed for * flash capability queries */ #define DACI_FLASH_INTERFACE_CLASS 7 #define DACI_FLASH_INTERFACE_SELECT 3 #define DACI_FLASH_ARG_TPM 2 #define DACI_FLASH_ARG_FLASH_MODE 3 #define DACI_FLASH_MODE_USER 0 #define DACI_FLASH_MODE_FLASH 1 /* DACI class/select for dock capabilities */ #define DACI_DOCK_CLASS 17 #define DACI_DOCK_SELECT 22 #define DACI_DOCK_ARG_COUNT 0 #define DACI_DOCK_ARG_INFO 1 #define DACI_DOCK_ARG_MODE 2 #define DACI_DOCK_ARG_MODE_USER 0 #define DACI_DOCK_ARG_MODE_FLASH 1 /* VID/PID of ethernet controller on dock */ #define DOCK_NIC_VID 0x0bda #define DOCK_NIC_PID 0x8153 fwupd-1.2.14/plugins/dell/fu-plugin-dell.c000066400000000000000000000676561402665037500203510ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2016 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "fwupd-common.h" #include "fu-plugin-dell.h" #include "fu-plugin-vfuncs.h" #include "fu-device-metadata.h" /* These are used to indicate the status of a previous DELL flash */ #define DELL_SUCCESS 0x0000 #define DELL_CONSISTENCY_FAIL 0x0001 #define DELL_FLASH_MEMORY_FAIL 0x0002 #define DELL_FLASH_NOT_READY 0x0003 #define DELL_FLASH_DISABLED 0x0004 #define DELL_BATTERY_MISSING 0x0005 #define DELL_BATTERY_DEAD 0x0006 #define DELL_AC_MISSING 0x0007 #define DELL_CANT_SET_12V 0x0008 #define DELL_CANT_UNSET_12V 0x0009 #define DELL_FAILURE_BLOCK_ERASE 0x000A #define DELL_GENERAL_FAILURE 0x000B #define DELL_DATA_MISCOMPARE 0x000C #define DELL_IMAGE_MISSING 0x000D #define DELL_DID_NOTHING 0xFFFF /* Delay for settling */ #define DELL_FLASH_MODE_DELAY 2 typedef struct _DOCK_DESCRIPTION { const gchar * guid; const gchar * query; const gchar * desc; } DOCK_DESCRIPTION; struct da_structure { guint8 type; guint8 length; guint16 handle; guint16 cmd_address; guint8 cmd_code; guint32 supported_cmds; guint8 *tokens; } __attribute__((packed)); /* These are for matching the components */ #define WD15_EC_STR "2 0 2 2 0" #define TB16_EC_STR "2 0 2 1 0" #define TB16_PC2_STR "2 1 0 1 1" #define TB16_PC1_STR "2 1 0 1 0" #define WD15_PC1_STR "2 1 0 2 0" #define LEGACY_CBL_STR "2 2 2 1 0" #define UNIV_CBL_STR "2 2 2 2 0" #define TBT_CBL_STR "2 2 2 3 0" #define FUTURE_EC_STR "3 0 2 4 0" #define FUTURE_EC_STR2 "4 0 2 4 0" /* supported dock related GUIDs */ #define DOCK_FLASH_GUID "e7ca1f36-bf73-4574-afe6-a4ccacabf479" #define WD15_EC_GUID "e8445370-0211-449d-9faa-107906ab189f" #define TB16_EC_GUID "33cc8870-b1fc-4ec7-948a-c07496874faf" #define TB16_PC2_GUID "1b52c630-86f6-4aee-9f0c-474dc6be49b6" #define TB16_PC1_GUID "8fe183da-c94e-4804-b319-0f1ba5457a69" #define WD15_PC1_GUID "8ba2b709-6f97-47fc-b7e7-6a87b578fe25" #define LEGACY_CBL_GUID "fece1537-d683-4ea8-b968-154530bb6f73" #define UNIV_CBL_GUID "e2bf3aad-61a3-44bf-91ef-349b39515d29" #define TBT_CBL_GUID "6dc832fc-5bb0-4e63-a2ff-02aaba5bc1dc" #define EC_DESC "EC" #define PC1_DESC "Port Controller 1" #define PC2_DESC "Port Controller 2" #define LEGACY_CBL_DESC "Passive Cable" #define UNIV_CBL_DESC "Universal Cable" #define TBT_CBL_DESC "Thunderbolt Cable" /* supported host related GUIDs */ #define MST_GPIO_GUID EFI_GUID (0xF24F9bE4, 0x2a13, 0x4344, 0xBC05, 0x01, 0xCE, 0xF7, 0xDA, 0xEF, 0x92) /** * Devices that should allow modeswitching */ static guint16 tpm_switch_whitelist[] = {0x06F2, 0x06F3, 0x06DD, 0x06DE, 0x06DF, 0x06DB, 0x06DC, 0x06BB, 0x06C6, 0x06BA, 0x06B9, 0x05CA, 0x06C7, 0x06B7, 0x06E0, 0x06E5, 0x06D9, 0x06DA, 0x06E4, 0x0704, 0x0720, 0x0730, 0x0758, 0x0759, 0x075B, 0x07A0, 0x079F, 0x07A4, 0x07A5, 0x07A6, 0x07A7, 0x07A8, 0x07A9, 0x07AA, 0x07AB, 0x07B0, 0x07B1, 0x07B2, 0x07B4, 0x07B7, 0x07B8, 0x07B9, 0x07BE, 0x07BF, 0x077A, 0x07CF}; /** * Dell device types to run */ static guint8 enclosure_whitelist [] = { 0x03, /* desktop */ 0x04, /* low profile desktop */ 0x06, /* mini tower */ 0x07, /* tower */ 0x08, /* portable */ 0x09, /* laptop */ 0x0A, /* notebook */ 0x0D, /* AIO */ 0x1E, /* tablet */ 0x1F, /* convertible */ 0x21, /* IoT gateway */ 0x22, /* embedded PC */}; /** * Systems containing host MST device */ static guint16 systems_host_mst [] = { 0x062d, /* Latitude E7250 */ 0x062e, /* Latitude E7450 */ 0x062a, /* Latitude E5250 */ 0x062b, /* Latitude E5450 */ 0x062c, /* Latitude E5550 */ 0x06db, /* Latitude E7270 */ 0x06dc, /* Latitude E7470 */ 0x06dd, /* Latitude E5270 */ 0x06de, /* Latitude E5470 */ 0x06df, /* Latitude E5570 */ 0x06e0, /* Precision 3510 */ 0x071d, /* Latitude Rugged 7214 */ 0x071e, /* Latitude Rugged 5414 */ 0x071c, /* Latitude Rugged 7414 */}; static guint16 fu_dell_get_system_id (FuPlugin *plugin) { const gchar *system_id_str = NULL; guint16 system_id = 0; gchar *endptr = NULL; system_id_str = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_SKU); if (system_id_str != NULL) system_id = g_ascii_strtoull (system_id_str, &endptr, 16); if (system_id == 0 || endptr == system_id_str) system_id = (guint16) sysinfo_get_dell_system_id (); return system_id; } static gboolean fu_dell_host_mst_supported (FuPlugin *plugin) { guint16 system_id; system_id = fu_dell_get_system_id (plugin); if (system_id == 0) return FALSE; for (guint i = 0; i < G_N_ELEMENTS (systems_host_mst); i++) if (systems_host_mst[i] == system_id) return TRUE; return FALSE; } static gboolean fu_dell_supported (FuPlugin *plugin) { g_autoptr(GBytes) de_table = NULL; g_autoptr(GBytes) da_table = NULL; g_autoptr(GBytes) enclosure = NULL; const guint8 *value; const struct da_structure *da_values; gsize len; /* make sure that Dell SMBIOS methods are available */ de_table = fu_plugin_get_smbios_data (plugin, 0xDE); if (de_table == NULL) return FALSE; value = g_bytes_get_data (de_table, &len); if (len == 0) return FALSE; if (*value != 0xDE) return FALSE; da_table = fu_plugin_get_smbios_data (plugin, 0xDA); if (da_table == NULL) return FALSE; da_values = (struct da_structure *) g_bytes_get_data (da_table, &len); if (len == 0) return FALSE; if (!(da_values->supported_cmds & (1 << DACI_FLASH_INTERFACE_CLASS))) { g_debug ("unable to access flash interface. supported commands: 0x%x", da_values->supported_cmds); return FALSE; } /* only run on intended Dell hw types */ enclosure = fu_plugin_get_smbios_data (plugin, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS); if (enclosure == NULL) return FALSE; value = g_bytes_get_data (enclosure, &len); if (len == 0) return FALSE; for (guint i = 0; i < G_N_ELEMENTS (enclosure_whitelist); i++) { if (enclosure_whitelist[i] == value[0]) return TRUE; } return FALSE; } static gboolean fu_plugin_dell_match_dock_component (const gchar *query_str, const gchar **guid_out, const gchar **name_out) { const DOCK_DESCRIPTION list[] = { {WD15_EC_GUID, WD15_EC_STR, EC_DESC}, {TB16_EC_GUID, TB16_EC_STR, EC_DESC}, {WD15_PC1_GUID, WD15_PC1_STR, PC1_DESC}, {TB16_PC1_GUID, TB16_PC1_STR, PC1_DESC}, {TB16_PC2_GUID, TB16_PC2_STR, PC2_DESC}, {TBT_CBL_GUID, TBT_CBL_STR, TBT_CBL_DESC}, {UNIV_CBL_GUID, UNIV_CBL_STR, UNIV_CBL_DESC}, {LEGACY_CBL_GUID, LEGACY_CBL_STR, LEGACY_CBL_DESC}, {NULL, FUTURE_EC_STR, NULL}, {NULL, FUTURE_EC_STR2, NULL}, }; for (guint i = 0; i < G_N_ELEMENTS (list); i++) { if (g_strcmp0 (query_str, list[i].query) == 0) { *guid_out = list[i].guid; *name_out = list[i].desc; return TRUE; } } return FALSE; } void fu_plugin_dell_inject_fake_data (FuPlugin *plugin, guint32 *output, guint16 vid, guint16 pid, guint8 *buf, gboolean can_switch_modes) { FuPluginData *data = fu_plugin_get_data (plugin); if (!data->smi_obj->fake_smbios) return; for (guint i = 0; i < 4; i++) data->smi_obj->output[i] = output[i]; data->fake_vid = vid; data->fake_pid = pid; data->smi_obj->fake_buffer = buf; data->can_switch_modes = TRUE; } static FwupdVersionFormat fu_plugin_dell_get_version_format (FuPlugin *plugin) { const gchar *content; const gchar *quirk; g_autofree gchar *group = NULL; content = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER); if (content == NULL) return FWUPD_VERSION_FORMAT_TRIPLET; /* any quirks match */ group = g_strdup_printf ("SmbiosManufacturer=%s", content); quirk = fu_plugin_lookup_quirk_by_id (plugin, group, FU_QUIRKS_UEFI_VERSION_FORMAT); if (quirk == NULL) return FWUPD_VERSION_FORMAT_TRIPLET; return fwupd_version_format_from_string (quirk); } static gboolean fu_plugin_dell_capsule_supported (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); return data->smi_obj->fake_smbios || data->capsule_supported; } static gboolean fu_plugin_dock_node (FuPlugin *plugin, const gchar *platform, guint8 type, const gchar *component_guid, const gchar *component_desc, const gchar *version, FwupdVersionFormat version_format) { const gchar *dock_type; g_autofree gchar *dock_name = NULL; g_autoptr(FuDevice) dev = NULL; dock_type = fu_dell_get_dock_type (type); if (dock_type == NULL) { g_debug ("Unknown dock type %d", type); return FALSE; } dev = fu_device_new (); fu_device_set_physical_id (dev, platform); fu_device_set_logical_id (dev, component_guid); if (component_desc != NULL) { dock_name = g_strdup_printf ("Dell %s %s", dock_type, component_desc); fu_device_add_parent_guid (dev, DOCK_FLASH_GUID); } else { dock_name = g_strdup_printf ("Dell %s", dock_type); } fu_device_set_vendor (dev, "Dell Inc."); fu_device_set_name (dev, dock_name); fu_device_set_metadata (dev, FU_DEVICE_METADATA_UEFI_DEVICE_KIND, "device-firmware"); if (type == DOCK_TYPE_TB16) { fu_device_set_summary (dev, "A Thunderbolt™ 3 docking station"); } else if (type == DOCK_TYPE_WD15) { fu_device_set_summary (dev, "A USB type-C docking station"); } fu_device_add_icon (dev, "computer"); fu_device_add_guid (dev, component_guid); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_REQUIRE_AC); if (version != NULL) { fu_device_set_version (dev, version, version_format); if (fu_plugin_dell_capsule_supported (plugin)) { fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } else { fu_device_set_update_error (dev, "UEFI capsule updates turned off in BIOS setup"); } } fu_plugin_device_register (plugin, dev); return TRUE; } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); FwupdVersionFormat version_format; guint16 pid; guint16 vid; const gchar *query_str; const gchar *component_guid = NULL; const gchar *component_name = NULL; const gchar *platform; DOCK_UNION buf; DOCK_INFO *dock_info; gboolean old_ec = FALSE; g_autofree gchar *flash_ver_str = NULL; /* don't look up immediately if a dock is connected as that would mean a SMI on every USB device that showed up on the system */ if (!data->smi_obj->fake_smbios) { vid = fu_usb_device_get_vid (device); pid = fu_usb_device_get_pid (device); platform = fu_device_get_physical_id (FU_DEVICE (device)); } else { vid = data->fake_vid; pid = data->fake_pid; platform = "fake"; } /* we're going to match on the Realtek NIC in the dock */ if (vid != DOCK_NIC_VID || pid != DOCK_NIC_PID) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "wrong VID/PID %04x:%04x", vid, pid); return FALSE; } buf.buf = NULL; if (!fu_dell_query_dock (data->smi_obj, &buf)) { g_debug ("no dock detected"); return TRUE; } if (buf.record->dock_info_header.dir_version != 1) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "dock info header version unknown %d", buf.record->dock_info_header.dir_version); return FALSE; } dock_info = &buf.record->dock_info; g_debug ("Dock description: %s", dock_info->dock_description); /* Note: fw package version is deprecated, look at components instead */ g_debug ("Dock flash pkg ver: 0x%x", dock_info->flash_pkg_version); if (dock_info->flash_pkg_version == 0x00ffffff) g_debug ("WARNING: dock flash package version invalid"); g_debug ("Dock cable type: %" G_GUINT32_FORMAT, dock_info->cable_type); g_debug ("Dock location: %d", dock_info->location); g_debug ("Dock component count: %d", dock_info->component_count); version_format = fu_plugin_dell_get_version_format (plugin); for (guint i = 0; i < dock_info->component_count; i++) { g_autofree gchar *fw_str = NULL; if (i >= MAX_COMPONENTS) { g_debug ("Too many components. Invalid: #%u", i); break; } g_debug ("Dock component %u: %s (version 0x%x)", i, dock_info->components[i].description, dock_info->components[i].fw_version); query_str = g_strrstr (dock_info->components[i].description, "Query "); if (query_str == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid dock component request"); return FALSE; } if (!fu_plugin_dell_match_dock_component (query_str + 6, &component_guid, &component_name)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid dock component request %s", query_str); return FALSE; } if (component_guid == NULL || component_name == NULL) { g_debug ("%s is supported by another plugin", query_str); return TRUE; } /* dock EC hasn't been updated for first time */ if (dock_info->flash_pkg_version == 0x00ffffff) { old_ec = TRUE; dock_info->flash_pkg_version = 0; continue; } /* if invalid version, don't mark device for updates */ else if (dock_info->components[i].fw_version == 0 || dock_info->components[i].fw_version == 0xffffffff) { old_ec = TRUE; continue; } fw_str = fu_common_version_from_uint32 (dock_info->components[i].fw_version, version_format); if (!fu_plugin_dock_node (plugin, platform, buf.record->dock_info_header.dock_type, component_guid, component_name, fw_str, version_format)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to create %s", component_name); return FALSE; } } /* if an old EC or invalid EC version found, create updatable parent */ if (old_ec) flash_ver_str = fu_common_version_from_uint32 (dock_info->flash_pkg_version, version_format); if (!fu_plugin_dock_node (plugin, platform, buf.record->dock_info_header.dock_type, DOCK_FLASH_GUID, NULL, flash_ver_str, version_format)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to create top dock node"); return FALSE; } #if defined (HAVE_SYNAPTICS) fu_plugin_request_recoldplug (plugin); #endif return TRUE; } gboolean fu_plugin_get_results (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(GBytes) de_table = NULL; const gchar *tmp = NULL; const guint16 *completion_code; gsize len; de_table = fu_plugin_get_smbios_data (plugin, 0xDE); completion_code = g_bytes_get_data (de_table, &len); if (len < 8) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ERROR: Unable to read results of %s: %" G_GSIZE_FORMAT " < 8", fu_device_get_name (device), len); return FALSE; } /* look at byte offset 0x06 for identifier meaning completion code */ if (completion_code[3] == DELL_SUCCESS) { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_SUCCESS); } else { FwupdUpdateState update_state = FWUPD_UPDATE_STATE_FAILED; switch (completion_code[3]) { case DELL_CONSISTENCY_FAIL: tmp = "The image failed one or more consistency checks."; break; case DELL_FLASH_MEMORY_FAIL: tmp = "The BIOS could not access the flash-memory device."; break; case DELL_FLASH_NOT_READY: tmp = "The flash-memory device was not ready when an erase was attempted."; break; case DELL_FLASH_DISABLED: tmp = "Flash programming is currently disabled on the system, or the voltage is low."; break; case DELL_BATTERY_MISSING: tmp = "A battery must be installed for the operation to complete."; update_state = FWUPD_UPDATE_STATE_FAILED_TRANSIENT; break; case DELL_BATTERY_DEAD: tmp = "A fully-charged battery must be present for the operation to complete."; update_state = FWUPD_UPDATE_STATE_FAILED_TRANSIENT; break; case DELL_AC_MISSING: tmp = "An external power adapter must be connected for the operation to complete."; update_state = FWUPD_UPDATE_STATE_FAILED_TRANSIENT; break; case DELL_CANT_SET_12V: tmp = "The 12V required to program the flash-memory could not be set."; break; case DELL_CANT_UNSET_12V: tmp = "The 12V required to program the flash-memory could not be removed."; break; case DELL_FAILURE_BLOCK_ERASE : tmp = "A flash-memory failure occurred during a block-erase operation."; break; case DELL_GENERAL_FAILURE: tmp = "A general failure occurred during the flash programming."; break; case DELL_DATA_MISCOMPARE: tmp = "A data miscompare error occurred during the flash programming."; break; case DELL_IMAGE_MISSING: tmp = "The image could not be found in memory, i.e. the header could not be located."; break; case DELL_DID_NOTHING: tmp = "No update operation has been performed on the system."; break; default: break; } fu_device_set_update_state (device, update_state); if (tmp != NULL) fu_device_set_update_error (device, tmp); } return TRUE; } gboolean fu_plugin_dell_detect_tpm (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); const gchar *tpm_mode; const gchar *tpm_mode_alt; guint16 system_id = 0; gboolean can_switch_modes = FALSE; g_autofree gchar *pretty_tpm_name_alt = NULL; g_autofree gchar *pretty_tpm_name = NULL; g_autofree gchar *tpm_guid_raw_alt = NULL; g_autofree gchar *tpm_guid_alt = NULL; g_autofree gchar *tpm_guid = NULL; g_autofree gchar *tpm_guid_raw = NULL; g_autofree gchar *tpm_id_alt = NULL; g_autofree gchar *tpm_id = NULL; g_autofree gchar *version_str = NULL; struct tpm_status *out = NULL; g_autoptr (FuDevice) dev_alt = NULL; g_autoptr (FuDevice) dev = NULL; const gchar *product_name = "Unknown"; fu_dell_clear_smi (data->smi_obj); out = (struct tpm_status *) data->smi_obj->output; /* execute TPM Status Query */ data->smi_obj->input[0] = DACI_FLASH_ARG_TPM; if (!fu_dell_execute_simple_smi (data->smi_obj, DACI_FLASH_INTERFACE_CLASS, DACI_FLASH_INTERFACE_SELECT)) return FALSE; if (out->ret != 0) { g_debug ("Failed to query system for TPM information: " "(%" G_GUINT32_FORMAT ")", out->ret); return FALSE; } /* HW version is output in second /input/ arg * it may be relevant as next gen TPM is enabled */ g_debug ("TPM HW version: 0x%x", data->smi_obj->input[1]); g_debug ("TPM Status: 0x%x", out->status); /* test TPM enabled (Bit 0) */ if (!(out->status & TPM_EN_MASK)) { g_debug ("TPM not enabled (%x)", out->status); return FALSE; } /* test TPM mode to determine current mode */ if (((out->status & TPM_TYPE_MASK) >> 8) == TPM_1_2_MODE) { tpm_mode = "1.2"; tpm_mode_alt = "2.0"; } else if (((out->status & TPM_TYPE_MASK) >> 8) == TPM_2_0_MODE) { tpm_mode = "2.0"; tpm_mode_alt = "1.2"; } else { g_debug ("Unable to determine TPM mode"); return FALSE; } system_id = fu_dell_get_system_id (plugin); if (data->smi_obj->fake_smbios) can_switch_modes = data->can_switch_modes; else if (system_id == 0) return FALSE; for (guint i = 0; i < G_N_ELEMENTS (tpm_switch_whitelist); i++) { if (tpm_switch_whitelist[i] == system_id) { can_switch_modes = TRUE; } } tpm_guid_raw = g_strdup_printf ("%04x-%s", system_id, tpm_mode); tpm_guid = fwupd_guid_hash_string (tpm_guid_raw); tpm_id = g_strdup_printf ("DELL-%s" G_GUINT64_FORMAT, tpm_guid); tpm_guid_raw_alt = g_strdup_printf ("%04x-%s", system_id, tpm_mode_alt); tpm_guid_alt = fwupd_guid_hash_string (tpm_guid_raw_alt); tpm_id_alt = g_strdup_printf ("DELL-%s" G_GUINT64_FORMAT, tpm_guid_alt); g_debug ("Creating primary TPM GUID %s and secondary TPM GUID %s", tpm_guid_raw, tpm_guid_raw_alt); version_str = fu_common_version_from_uint32 (out->fw_version, FWUPD_VERSION_FORMAT_QUAD); /* make it clear that the TPM is a discrete device of the product */ if (!data->smi_obj->fake_smbios) { product_name = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_NAME); } pretty_tpm_name = g_strdup_printf ("%s TPM %s", product_name, tpm_mode); pretty_tpm_name_alt = g_strdup_printf ("%s TPM %s", product_name, tpm_mode_alt); /* build Standard device nodes */ dev = fu_device_new (); fu_device_set_id (dev, tpm_id); fu_device_add_guid (dev, tpm_guid); fu_device_set_vendor (dev, "Dell Inc."); fu_device_set_name (dev, pretty_tpm_name); fu_device_set_summary (dev, "Platform TPM device"); fu_device_set_version (dev, version_str, FWUPD_VERSION_FORMAT_QUAD); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_icon (dev, "computer"); fu_device_set_metadata (dev, FU_DEVICE_METADATA_UEFI_DEVICE_KIND, "dell-tpm-firmware"); if ((out->status & TPM_OWN_MASK) == 0 && out->flashes_left > 0) { if (fu_plugin_dell_capsule_supported (plugin)) { fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } else { fu_device_set_update_error (dev, "UEFI capsule updates turned off in BIOS setup"); } fu_device_set_flashes_left (dev, out->flashes_left); } else { g_debug ("%s updating disabled due to TPM ownership", pretty_tpm_name); } fu_plugin_device_register (plugin, dev); /* build alternate device node */ if (can_switch_modes) { dev_alt = fu_device_new (); fu_device_set_id (dev_alt, tpm_id_alt); fu_device_add_guid (dev_alt, tpm_guid_alt); fu_device_set_vendor (dev, "Dell Inc."); fu_device_set_name (dev_alt, pretty_tpm_name_alt); fu_device_set_summary (dev_alt, "Alternate mode for platform TPM device"); fu_device_add_flag (dev_alt, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag (dev_alt, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag (dev_alt, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_icon (dev_alt, "computer"); fu_device_set_alternate_id (dev_alt, fu_device_get_id (dev)); fu_device_set_metadata (dev_alt, FU_DEVICE_METADATA_UEFI_DEVICE_KIND, "dell-tpm-firmware"); fu_device_add_parent_guid (dev_alt, tpm_guid); /* If TPM is not owned and at least 1 flash left allow mode switching * * Mode switching is turned on by setting flashes left on alternate * device. */ if ((out->status & TPM_OWN_MASK) == 0 && out->flashes_left > 0) { fu_device_set_flashes_left (dev_alt, out->flashes_left); } else { g_debug ("%s mode switch disabled due to TPM ownership", pretty_tpm_name); } fu_plugin_device_register (plugin, dev_alt); } else g_debug ("System %04x does not offer TPM modeswitching", system_id); return TRUE; } void fu_plugin_device_registered (FuPlugin *plugin, FuDevice *device) { /* thunderbolt plugin */ if (g_strcmp0 (fu_device_get_plugin (device), "thunderbolt") == 0 && fu_device_has_flag (device, FWUPD_DEVICE_FLAG_INTERNAL)) { /* fix VID/DID of safe mode devices */ if (fu_device_get_metadata_boolean (device, FU_DEVICE_METADATA_TBT_IS_SAFE_MODE)) { g_autofree gchar *vendor_id = NULL; g_autofree gchar *device_id = NULL; guint16 system_id = 0; vendor_id = g_strdup ("TBT:0x00D4"); system_id = fu_dell_get_system_id (plugin); if (system_id == 0) return; /* the kernel returns lowercase in sysfs, need to match it */ device_id = g_strdup_printf ("TBT-%04x%04x", 0x00d4u, (unsigned) system_id); fu_device_set_vendor_id (device, vendor_id); fu_device_add_instance_id (device, device_id); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); } } } static gboolean fu_dell_toggle_flash (FuPlugin *plugin, FuDevice *device, gboolean enable, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); gboolean has_host = fu_dell_host_mst_supported (plugin); gboolean has_dock; guint32 dock_location; const gchar *tmp; if (device) { if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE)) return TRUE; tmp = fu_device_get_plugin (device); if (g_strcmp0 (tmp, "synapticsmst") != 0) return TRUE; g_debug ("preparing/cleaning update for %s", tmp); } /* Dock MST Hub */ has_dock = fu_dell_detect_dock (data->smi_obj, &dock_location); if (has_dock) { if (!fu_dell_toggle_dock_mode (data->smi_obj, enable, dock_location, error)) g_debug ("unable to change dock to %d", enable); else g_debug ("Toggled dock mode to %d", enable); } /* System MST hub */ if (has_host) { if (!fu_dell_toggle_host_mode (data->smi_obj, MST_GPIO_GUID, enable)) g_debug ("Unable to toggle MST hub GPIO to %d", enable); else g_debug ("Toggled MST hub GPIO to %d", enable); } #if defined (HAVE_SYNAPTICS) /* set a delay to allow OS response to settling the GPIO change */ if (enable && device == NULL && (has_dock || has_host)) fu_plugin_set_coldplug_delay (plugin, DELL_FLASH_MODE_DELAY * 1000); #endif return TRUE; } gboolean fu_plugin_update_prepare (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *device, GError **error) { return fu_dell_toggle_flash (plugin, device, TRUE, error); } gboolean fu_plugin_update_cleanup (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *device, GError **error) { return fu_dell_toggle_flash (plugin, device , FALSE, error); } gboolean fu_plugin_coldplug_prepare (FuPlugin *plugin, GError **error) { return fu_dell_toggle_flash (plugin, NULL, TRUE, error); } gboolean fu_plugin_coldplug_cleanup (FuPlugin *plugin, GError **error) { return fu_dell_toggle_flash (plugin, NULL, FALSE, error); } void fu_plugin_init (FuPlugin *plugin) { FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); g_autofree gchar *tmp = NULL; fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); tmp = g_strdup_printf ("%d.%d", smbios_get_library_version_major(), smbios_get_library_version_minor()); fu_plugin_add_runtime_version (plugin, "com.dell.libsmbios", tmp); g_debug ("Using libsmbios %s", tmp); data->smi_obj = g_malloc0 (sizeof (FuDellSmiObj)); if (g_getenv ("FWUPD_DELL_VERBOSE") != NULL) g_setenv ("LIBSMBIOS_C_DEBUG_OUTPUT_ALL", "1", TRUE); if (fu_dell_supported (plugin)) data->smi_obj->smi = dell_smi_factory (DELL_SMI_DEFAULTS); data->smi_obj->fake_smbios = FALSE; if (g_getenv ("FWUPD_DELL_FAKE_SMBIOS") != NULL) data->smi_obj->fake_smbios = TRUE; fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); /* make sure that UEFI plugin is ready to receive devices */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "uefi"); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); if (data->smi_obj->smi) dell_smi_obj_free (data->smi_obj->smi); g_free(data->smi_obj); } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autofree gchar *sysfsfwdir = NULL; g_autofree gchar *esrtdir = NULL; if (data->smi_obj->fake_smbios) { g_debug ("Called with fake SMBIOS implementation. " "We're ignoring test for SBMIOS table and ESRT. " "Individual calls will need to be properly staged."); return TRUE; } if (!fu_dell_supported (plugin)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Firmware updating not supported"); return FALSE; } if (data->smi_obj->smi == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to initialize libsmbios library"); return FALSE; } /* If ESRT is not turned on, fwupd will have already created an * unlock device. * * Once unlocked, that will enable flashing capsules here too. */ sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); esrtdir = g_build_filename (sysfsfwdir, "efi", "esrt", NULL); if (g_file_test (esrtdir, G_FILE_TEST_EXISTS)) { data->capsule_supported = TRUE; } else { g_debug ("UEFI capsule firmware updating not supported"); } return TRUE; } static gboolean fu_plugin_dell_coldplug (FuPlugin *plugin, GError **error) { /* look for switchable TPM */ if (!fu_plugin_dell_detect_tpm (plugin, error)) g_debug ("No switchable TPM detected"); return TRUE; } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { return fu_plugin_dell_coldplug (plugin, error); } gboolean fu_plugin_recoldplug (FuPlugin *plugin, GError **error) { return fu_plugin_dell_coldplug (plugin, error); } fwupd-1.2.14/plugins/dell/fu-plugin-dell.h000066400000000000000000000015501402665037500203330ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-dell-smi.h" struct FuPluginData { FuDellSmiObj *smi_obj; guint16 fake_vid; guint16 fake_pid; gboolean can_switch_modes; gboolean capsule_supported; }; void fu_plugin_dell_inject_fake_data (FuPlugin *plugin, guint32 *output, guint16 vid, guint16 pid, guint8 *buf, gboolean can_switch_modes); gboolean fu_plugin_dell_detect_tpm (FuPlugin *plugin, GError **error); /* These are nodes that will indicate information about * the TPM status */ struct tpm_status { guint32 ret; guint32 fw_version; guint32 status; guint32 flashes_left; }; #define TPM_EN_MASK 0x0001 #define TPM_OWN_MASK 0x0004 #define TPM_TYPE_MASK 0x0F00 #define TPM_1_2_MODE 0x0001 #define TPM_2_0_MODE 0x0002 fwupd-1.2.14/plugins/dell/fu-self-test.c000066400000000000000000000406411402665037500200240ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-device-private.h" #include "fu-plugin-private.h" #include "fu-plugin-dell.h" #include "fu-plugin-vfuncs.h" static FuDevice * _find_device_by_id (GPtrArray *devices, const gchar *device_id) { for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); if (g_strcmp0 (fu_device_get_id (device), device_id) == 0) return device; } return NULL; } static FuDevice * _find_device_by_name (GPtrArray *devices, const gchar *device_id) { for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); if (g_strcmp0 (fu_device_get_name (device), device_id) == 0) return device; } return NULL; } static void _plugin_device_added_cb (FuPlugin *plugin, FuDevice *device, gpointer user_data) { GPtrArray *devices = (GPtrArray *) user_data; if (fu_device_get_alternate_id (device) != NULL) { FuDevice *device_alt = _find_device_by_id (devices, fu_device_get_alternate_id (device)); if (device_alt != NULL) fu_device_set_alternate (device, device_alt); } g_ptr_array_add (devices, g_object_ref (device)); } static void fu_engine_plugin_device_register_cb (FuPlugin *plugin_dell, FuDevice *device, gpointer user_data) { FuPlugin *plugin_uefi = FU_PLUGIN (user_data); g_autofree gchar *dbg = fu_device_to_string (device); g_debug ("registering device: %s", dbg); fu_plugin_runner_device_register (plugin_uefi, device); } static void fu_plugin_dell_tpm_func (void) { FuDevice *device_v12; FuDevice *device_v20; const guint8 fw[30] = { 'F', 'W', 0x00 }; gboolean ret; struct tpm_status tpm_out; g_autoptr(FuPlugin) plugin_dell = NULL; g_autoptr(FuPlugin) plugin_uefi = NULL; g_autoptr(GBytes) blob_fw = g_bytes_new_static (fw, sizeof(fw)); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; memset (&tpm_out, 0x0, sizeof(tpm_out)); plugin_uefi = fu_plugin_new (); ret = fu_plugin_open (plugin_uefi, PLUGINBUILDDIR "/../uefi/libfu_plugin_uefi.so", &error); g_assert_no_error (error); g_assert (ret); ret = fu_plugin_runner_startup (plugin_uefi, &error); g_assert_no_error (error); g_assert (ret); devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_signal_connect (plugin_uefi, "device-added", G_CALLBACK (_plugin_device_added_cb), devices); plugin_dell = fu_plugin_new (); ret = fu_plugin_open (plugin_dell, PLUGINBUILDDIR "/libfu_plugin_dell.so", &error); g_assert_no_error (error); g_assert (ret); ret = fu_plugin_runner_startup (plugin_dell, &error); g_assert_no_error (error); g_assert (ret); g_signal_connect (plugin_dell, "device-register", G_CALLBACK (fu_engine_plugin_device_register_cb), plugin_uefi); ret = fu_plugin_runner_coldplug (plugin_dell, &error); g_assert_no_error (error); g_assert (ret); /* inject fake data (no TPM) */ tpm_out.ret = -2; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &tpm_out, 0, 0, NULL, FALSE); ret = fu_plugin_dell_detect_tpm (plugin_dell, &error); g_assert_no_error (error); g_assert_false (ret); g_assert_cmpint (devices->len, ==, 0); /* inject fake data: * - that is out of flashes * - no ownership * - TPM 1.2 * dev will be the locked 2.0, alt will be the orig 1.2 */ tpm_out.ret = 0; tpm_out.fw_version = 0; tpm_out.status = TPM_EN_MASK | (TPM_1_2_MODE << 8); tpm_out.flashes_left = 0; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &tpm_out, 0, 0, NULL, TRUE); ret = fu_plugin_dell_detect_tpm (plugin_dell, &error); g_assert_true (ret); g_assert_cmpint (devices->len, ==, 2); /* make sure 2.0 is locked */ device_v20 = _find_device_by_name (devices, "Unknown TPM 2.0"); g_assert_nonnull (device_v20); g_assert_true (fu_device_has_flag (device_v20, FWUPD_DEVICE_FLAG_LOCKED)); /* make sure not allowed to flash 1.2 */ device_v12 = _find_device_by_name (devices, "Unknown TPM 1.2"); g_assert_nonnull (device_v12); g_assert_false (fu_device_has_flag (device_v12, FWUPD_DEVICE_FLAG_UPDATABLE)); /* try to unlock 2.0 */ ret = fu_plugin_runner_unlock (plugin_uefi, device_v20, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false (ret); g_clear_error (&error); /* cleanup */ g_ptr_array_set_size (devices, 0); /* inject fake data: * - that has flashes * - owned * - TPM 1.2 * dev will be the locked 2.0, alt will be the orig 1.2 */ tpm_out.status = TPM_EN_MASK | TPM_OWN_MASK | (TPM_1_2_MODE << 8); tpm_out.flashes_left = 125; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &tpm_out, 0, 0, NULL, TRUE); ret = fu_plugin_dell_detect_tpm (plugin_dell, &error); g_assert_no_error (error); g_assert (ret); /* make sure not allowed to flash 1.2 */ device_v12 = _find_device_by_name (devices, "Unknown TPM 1.2"); g_assert_nonnull (device_v12); g_assert_false (fu_device_has_flag (device_v12, FWUPD_DEVICE_FLAG_UPDATABLE)); /* try to unlock 2.0 */ device_v20 = _find_device_by_name (devices, "Unknown TPM 2.0"); g_assert_nonnull (device_v20); ret = fu_plugin_runner_unlock (plugin_uefi, device_v20, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false (ret); g_clear_error (&error); /* cleanup */ g_ptr_array_set_size (devices, 0); /* inject fake data: * - that has flashes * - not owned * - TPM 1.2 * dev will be the locked 2.0, alt will be the orig 1.2 */ tpm_out.status = TPM_EN_MASK | (TPM_1_2_MODE << 8); tpm_out.flashes_left = 125; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &tpm_out, 0, 0, NULL, TRUE); ret = fu_plugin_dell_detect_tpm (plugin_dell, &error); g_assert_no_error (error); g_assert (ret); /* make sure allowed to flash 1.2 but not 2.0 */ device_v12 = _find_device_by_name (devices, "Unknown TPM 1.2"); g_assert_nonnull (device_v12); g_assert_true (fu_device_has_flag (device_v12, FWUPD_DEVICE_FLAG_UPDATABLE)); device_v20 = _find_device_by_name (devices, "Unknown TPM 2.0"); g_assert_nonnull (device_v20); g_assert_false (fu_device_has_flag (device_v20, FWUPD_DEVICE_FLAG_UPDATABLE)); /* try to unlock 2.0 */ ret = fu_plugin_runner_unlock (plugin_uefi, device_v20, &error); g_assert_no_error (error); g_assert (ret); /* make sure no longer allowed to flash 1.2 but can flash 2.0 */ g_assert_false (fu_device_has_flag (device_v12, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_true (fu_device_has_flag (device_v20, FWUPD_DEVICE_FLAG_UPDATABLE)); /* cleanup */ g_ptr_array_set_size (devices, 0); /* inject fake data: * - that has 1 flash left * - not owned * - TPM 2.0 * dev will be the locked 1.2, alt will be the orig 2.0 */ tpm_out.status = TPM_EN_MASK | (TPM_2_0_MODE << 8); tpm_out.flashes_left = 1; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &tpm_out, 0, 0, NULL, TRUE); ret = fu_plugin_dell_detect_tpm (plugin_dell, &error); g_assert_no_error (error); g_assert (ret); /* make sure allowed to flash 2.0 but not 1.2 */ device_v20 = _find_device_by_name (devices, "Unknown TPM 2.0"); g_assert_nonnull (device_v20); g_assert_true (fu_device_has_flag (device_v20, FWUPD_DEVICE_FLAG_UPDATABLE)); device_v12 = _find_device_by_name (devices, "Unknown TPM 1.2"); g_assert_nonnull (device_v12); g_assert_false (fu_device_has_flag (device_v12, FWUPD_DEVICE_FLAG_UPDATABLE)); /* With one flash left we need an override */ ret = fu_plugin_runner_update (plugin_uefi, device_v20, blob_fw, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false (ret); g_clear_error (&error); /* test override */ ret = fu_plugin_runner_update (plugin_uefi, device_v20, blob_fw, FWUPD_INSTALL_FLAG_FORCE, &error); g_assert_no_error (error); g_assert (ret); } static void fu_plugin_dell_dock_func (void) { gboolean ret; guint32 out[4] = { 0x0, 0x0, 0x0, 0x0 }; DOCK_UNION buf; DOCK_INFO *dock_info; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(FuPlugin) plugin_uefi = fu_plugin_new (); g_autoptr(FuPlugin) plugin_dell = fu_plugin_new (); ret = fu_plugin_open (plugin_uefi, PLUGINBUILDDIR "/../uefi/libfu_plugin_uefi.so", &error); g_assert_no_error (error); g_assert (ret); ret = fu_plugin_runner_startup (plugin_uefi, &error); g_assert_no_error (error); g_assert (ret); devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_signal_connect (plugin_uefi, "device-added", G_CALLBACK (_plugin_device_added_cb), devices); ret = fu_plugin_open (plugin_dell, PLUGINBUILDDIR "/libfu_plugin_dell.so", &error); g_assert_no_error (error); g_assert (ret); ret = fu_plugin_runner_startup (plugin_dell, &error); g_assert_no_error (error); g_assert (ret); g_signal_connect (plugin_dell, "device-register", G_CALLBACK (fu_engine_plugin_device_register_cb), plugin_uefi); ret = fu_plugin_runner_coldplug (plugin_dell, &error); g_assert_no_error (error); g_assert (ret); /* make sure bad device doesn't trigger this */ fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &out, 0x1234, 0x4321, NULL, FALSE); ret = fu_plugin_usb_device_added (plugin_dell, NULL, &error); g_assert_false (ret); g_clear_error (&error); g_assert_cmpint (devices->len, ==, 0); /* inject a USB dongle matching correct VID/PID */ out[0] = 0; out[1] = 0; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &out, DOCK_NIC_VID, DOCK_NIC_PID, NULL, FALSE); ret = fu_plugin_usb_device_added (plugin_dell, NULL, &error); g_assert_true (ret); g_clear_error (&error); g_assert_cmpint (devices->len, ==, 0); /* inject valid TB16 dock w/ invalid flash pkg version */ buf.record = g_malloc0 (sizeof(DOCK_INFO_RECORD)); dock_info = &buf.record->dock_info; buf.record->dock_info_header.dir_version = 1; buf.record->dock_info_header.dock_type = DOCK_TYPE_TB16; memcpy (dock_info->dock_description, "BME_Dock", 8); dock_info->flash_pkg_version = 0x00ffffff; dock_info->cable_type = CABLE_TYPE_TBT; dock_info->location = 2; dock_info->component_count = 4; dock_info->components[0].fw_version = 0x00ffffff; memcpy (dock_info->components[0].description, "Dock1,EC,MIPS32,BME_Dock,0 :Query 2 0 2 1 0", 43); dock_info->components[1].fw_version = 0x10201; memcpy (dock_info->components[1].description, "Dock1,PC,TI,BME_Dock,0 :Query 2 1 0 1 0", 39); dock_info->components[2].fw_version = 0x10201; memcpy (dock_info->components[2].description, "Dock1,PC,TI,BME_Dock,1 :Query 2 1 0 1 1", 39); dock_info->components[3].fw_version = 0x00ffffff; memcpy (dock_info->components[3].description, "Dock1,Cable,Cyp,TBT_Cable,0 :Query 2 2 2 3 0", 44); out[0] = 0; out[1] = 1; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &out, DOCK_NIC_VID, DOCK_NIC_PID, buf.buf, FALSE); ret = fu_plugin_usb_device_added (plugin_dell, NULL, NULL); g_assert (ret); g_assert_cmpint (devices->len, ==, 4); g_ptr_array_set_size (devices, 0); g_free (buf.record); /* inject valid TB16 dock w/ older system EC */ buf.record = g_malloc0 (sizeof(DOCK_INFO_RECORD)); dock_info = &buf.record->dock_info; buf.record->dock_info_header.dir_version = 1; buf.record->dock_info_header.dock_type = DOCK_TYPE_TB16; memcpy (dock_info->dock_description, "BME_Dock", 8); dock_info->flash_pkg_version = 0x43; dock_info->cable_type = CABLE_TYPE_TBT; dock_info->location = 2; dock_info->component_count = 4; dock_info->components[0].fw_version = 0xffffffff; memcpy (dock_info->components[0].description, "Dock1,EC,MIPS32,BME_Dock,0 :Query 2 0 2 1 0", 43); dock_info->components[1].fw_version = 0x10211; memcpy (dock_info->components[1].description, "Dock1,PC,TI,BME_Dock,0 :Query 2 1 0 1 0", 39); dock_info->components[2].fw_version = 0x10212; memcpy (dock_info->components[2].description, "Dock1,PC,TI,BME_Dock,1 :Query 2 1 0 1 1", 39); dock_info->components[3].fw_version = 0xffffffff; memcpy (dock_info->components[3].description, "Dock1,Cable,Cyp,TBT_Cable,0 :Query 2 2 2 3 0", 44); out[0] = 0; out[1] = 1; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &out, DOCK_NIC_VID, DOCK_NIC_PID, buf.buf, FALSE); ret = fu_plugin_usb_device_added (plugin_dell, NULL, NULL); g_assert (ret); g_assert_cmpint (devices->len, ==, 3); g_ptr_array_set_size (devices, 0); g_free (buf.record); /* inject valid WD15 dock w/ invalid flash pkg version */ buf.record = g_malloc0 (sizeof(DOCK_INFO_RECORD)); dock_info = &buf.record->dock_info; buf.record->dock_info_header.dir_version = 1; buf.record->dock_info_header.dock_type = DOCK_TYPE_WD15; memcpy (dock_info->dock_description, "IE_Dock", 7); dock_info->flash_pkg_version = 0x00ffffff; dock_info->cable_type = CABLE_TYPE_LEGACY; dock_info->location = 2; dock_info->component_count = 3; dock_info->components[0].fw_version = 0x00ffffff; memcpy (dock_info->components[0].description, "Dock1,EC,MIPS32,IE_Dock,0 :Query 2 0 2 2 0", 42); dock_info->components[1].fw_version = 0x00ffffff; memcpy (dock_info->components[1].description, "Dock1,PC,TI,IE_Dock,0 :Query 2 1 0 2 0", 38); dock_info->components[2].fw_version = 0x00ffffff; memcpy (dock_info->components[2].description, "Dock1,Cable,Cyp,IE_Cable,0 :Query 2 2 2 1 0", 43); out[0] = 0; out[1] = 1; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &out, DOCK_NIC_VID, DOCK_NIC_PID, buf.buf, FALSE); ret = fu_plugin_usb_device_added (plugin_dell, NULL, &error); g_assert (ret); g_assert_no_error (error); g_assert_cmpint (devices->len, ==, 3); g_ptr_array_set_size (devices, 0); g_free (buf.record); /* inject valid WD15 dock w/ older system EC */ buf.record = g_malloc0 (sizeof(DOCK_INFO_RECORD)); dock_info = &buf.record->dock_info; buf.record->dock_info_header.dir_version = 1; buf.record->dock_info_header.dock_type = DOCK_TYPE_WD15; memcpy (dock_info->dock_description, "IE_Dock", 7); dock_info->flash_pkg_version = 0x43; dock_info->cable_type = CABLE_TYPE_LEGACY; dock_info->location = 2; dock_info->component_count = 3; dock_info->components[0].fw_version = 0xffffffff; memcpy (dock_info->components[0].description, "Dock1,EC,MIPS32,IE_Dock,0 :Query 2 0 2 2 0", 42); dock_info->components[1].fw_version = 0x10108; memcpy (dock_info->components[1].description, "Dock1,PC,TI,IE_Dock,0 :Query 2 1 0 2 0", 38); dock_info->components[2].fw_version = 0xffffffff; memcpy (dock_info->components[2].description, "Dock1,Cable,Cyp,IE_Cable,0 :Query 2 2 2 1 0", 43); out[0] = 0; out[1] = 1; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &out, DOCK_NIC_VID, DOCK_NIC_PID, buf.buf, FALSE); ret = fu_plugin_usb_device_added (plugin_dell, NULL, &error); g_assert (ret); g_assert_no_error (error); g_assert_cmpint (devices->len, ==, 2); g_ptr_array_set_size (devices, 0); g_free (buf.record); /* inject an invalid future dock */ buf.record = g_malloc0 (sizeof(DOCK_INFO_RECORD)); dock_info = &buf.record->dock_info; buf.record->dock_info_header.dir_version = 1; buf.record->dock_info_header.dock_type = 50; memcpy (dock_info->dock_description, "Future!", 8); dock_info->flash_pkg_version = 0x00ffffff; dock_info->cable_type = CABLE_TYPE_UNIV; dock_info->location = 2; dock_info->component_count = 1; dock_info->components[0].fw_version = 0x00ffffff; memcpy (dock_info->components[0].description, "Dock1,EC,MIPS32,FUT_Dock,0 :Query 2 0 2 2 0", 43); out[0] = 0; out[1] = 1; fu_plugin_dell_inject_fake_data (plugin_dell, (guint32 *) &out, DOCK_NIC_VID, DOCK_NIC_PID, buf.buf, FALSE); ret = fu_plugin_usb_device_added (plugin_dell, NULL, &error); g_assert_false (ret); g_assert_cmpint (devices->len, ==, 0); g_free (buf.record); } int main (int argc, char **argv) { g_autofree gchar *sysfsdir = NULL; g_test_init (&argc, &argv, NULL); /* change path */ g_setenv ("FWUPD_SYSFSFWDIR", TESTDATADIR, TRUE); /* change behaviour */ sysfsdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); g_setenv ("FWUPD_UEFI_ESP_PATH", sysfsdir, TRUE); g_setenv ("FWUPD_DELL_FAKE_SMBIOS", "1", FALSE); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_assert_cmpint (g_mkdir_with_parents ("/tmp/fwupd-self-test/var/lib/fwupd", 0755), ==, 0); /* tests go here */ g_test_add_func ("/fwupd/plugin{dell:tpm}", fu_plugin_dell_tpm_func); g_test_add_func ("/fwupd/plugin{dell:dock}", fu_plugin_dell_dock_func); return g_test_run (); } fwupd-1.2.14/plugins/dell/meson.build000066400000000000000000000025301402665037500174770ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginDell"'] install_data(['dell.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_dell', fu_hash, sources : [ 'fu-plugin-dell.c', 'fu-dell-smi.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : [ cargs, ], dependencies : [ plugin_deps, efivar, libsmbios_c, ], ) if get_option('tests') testdatadir = join_paths(meson.current_source_dir(), 'tests') cargs += '-DTESTDATADIR="' + testdatadir + '"' cargs += '-DFU_OFFLINE_DESTDIR="/tmp/fwupd-self-test"' cargs += '-DPLUGINBUILDDIR="' + meson.current_build_dir() + '"' e = executable( 'dell-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-dell-smi.c', 'fu-plugin-dell.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ plugin_deps, efivar, sqlite, libsmbios_c, valgrind, ], link_with : [ libfwupdprivate, ], c_args : [ cargs, ], ) test('dell-self-test', e) endif fwupd-1.2.14/plugins/dell/tests000077700000000000000000000000001402665037500207242../uefi/tests/ustar00rootroot00000000000000fwupd-1.2.14/plugins/dfu/000077500000000000000000000000001402665037500151735ustar00rootroot00000000000000fwupd-1.2.14/plugins/dfu/README.md000066400000000000000000000024461402665037500164600ustar00rootroot00000000000000DFU Support =========== Introduction ------------ Device Firmware Update is a standard that allows USB devices to be easily and safely updated by any operating system. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in DFU or DfuSe file format. This plugin supports the following protocol IDs: * org.usb.dfu * com.st.dfuse GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_273F&PID_1003&REV_0001` * `USB\VID_273F&PID_1003` * `USB\VID_273F` Quirk use --------- This plugin uses the following plugin-specific quirks: | Quirk | Description | Minimum fwupd version | |------------------------|---------------------------------------------|-----------------------| |`DfuFlags` | Optional quirks for a DFU device which doesn't follow the DFU 1.0 or 1.1 specification | 1.0.1| |`DfuForceVersion` | Forces a specific DFU version for the hardware device. This is required if the device does not set, or sets incorrectly, items in the DFU functional descriptor. |1.0.1| |`DfuJabraDetach` | Assigns the two magic bytes sent to the Jabra hardware when the device is in runtime mode to make it switch into DFU mode.|1.0.1| fwupd-1.2.14/plugins/dfu/contrib/000077500000000000000000000000001402665037500166335ustar00rootroot00000000000000fwupd-1.2.14/plugins/dfu/contrib/parse-avrdude-conf.py000077500000000000000000000113271402665037500227010ustar00rootroot00000000000000#!/usr/bin/python3 """ This parses avrdude.conf and generates quirks for fwupd """ # pylint: disable=wrong-import-position,pointless-string-statement """ SPDX-License-Identifier: LGPL-2.1+ """ import sys from difflib import SequenceMatcher # finds a part using the ID def _find_part_by_id(parts, part_id): for part in parts: if 'id' not in part: continue if part['id'] == part_id: return part return None # finds a memory layout for a part, climbing up the tree to the parent if reqd. def _find_mem_layout(parts, part): if 'memory-application' in part: memory_flash = part['memory-application'] if memory_flash: return memory_flash #look at the parent if 'parent' in part: parent = _find_part_by_id(parts, part['parent']) if parent: return _find_mem_layout(parts, parent) print('no parent ', part['parent'], 'found for', part['id']) return None # parses the weird syntax of avrdude.conf and makes lots of nested dictionaries def _parse_parts(fn_source): print("reading", fn_source) part = None memory_id = None parts = [] for line in open(fn_source).readlines(): # try to clean up crazy syntax line = line.replace('\n', '') if line.endswith(';'): line = line[:-1] # ignore blank lines line = line.rstrip() if not line: continue # count how many spaces deep this is lvl = 0 for char in line: if char != ' ': break lvl = lvl + 1 # ignore comments line = line.strip() if line[0] == '#': continue # level 0 of hell if lvl == 0: if line.startswith('part'): memory_id = None part = {} parts.append(part) if line.startswith('part parent '): part['parent'] = line[13:].replace('"', '') continue # level 4 of hell if lvl == 4: if line.startswith('memory'): memory_id = 'memory-' + line[7:].replace('"', '') part[memory_id] = {} continue split = line.split('=') if len(split) != 2: print('ignoring', line) continue part[split[0].strip()] = split[1].strip().replace('"', '') continue # level 8 of hell if lvl == 8: if memory_id: split = line.split('=') if len(split) != 2: continue memory = part[memory_id] memory[split[0].strip()] = split[1].strip() continue return parts def _get_longest_substring(s1, s2): match = SequenceMatcher(None, s1, s2).find_longest_match(0, len(s1), 0, len(s2)) return s2[match.b: match.b + match.size] # writes important data to the quirks file def _write_quirks(parts, fn_destination): outp = [] results = {} for part in parts: # ignore meta parts with deprecated names if 'desc' not in part: continue if 'signature' not in part: continue # find the layout mem_part = _find_mem_layout(parts, part) if not mem_part: print("no memory layout for", part['desc']) continue if not 'size' in mem_part: print("no memory size for", part['desc']) continue if mem_part['size'].startswith('0x'): size = int(mem_part['size'], 16) else: size = int(mem_part['size'], 10) # output the line for the quirk chip_id = '0x' + part['signature'].replace('0x', '').replace(' ', '') mem_layout = '@Flash/0x0/1*%.0iKg' % int(size / 1024) # merge duplicate quirks if chip_id in results: result = results[chip_id] result['desc'] = _get_longest_substring(result['desc'], part['desc']) else: result = {} result['desc'] = part['desc'] result['size'] = size result['mem_layout'] = mem_layout results[chip_id] = result for chip_id in results: result = results[chip_id] outp.append('# ' + result['desc'] + ' [USER] USER=0x%x' % result['size'] + '\n') outp.append(chip_id + '=' + result['mem_layout'] + '\n\n') # write file print("writing", fn_destination) open(fn_destination, 'w').writelines(outp) if __name__ == '__main__': if len(sys.argv) != 3: print("USAGE: %s avrdude.conf tmp.quirk" % sys.argv[0]) sys.exit(1) all_parts = _parse_parts(sys.argv[1]) _write_quirks(all_parts, sys.argv[2]) fwupd-1.2.14/plugins/dfu/dfu-cipher-xtea.c000066400000000000000000000120371402665037500203270ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "dfu-cipher-xtea.h" #include "fwupd-error.h" #define XTEA_DELTA 0x9e3779b9 #define XTEA_NUM_ROUNDS 32 static void dfu_cipher_buf_to_uint32 (const guint8 *buf, guint buflen, guint32 *array) { for (guint i = 0; i < buflen / 4; i++) array[i] = fu_common_read_uint32 (&buf[i * 4], G_LITTLE_ENDIAN); } static void dfu_cipher_uint32_to_buf (guint8 *buf, guint buflen, const guint32 *array) { for (guint i = 0; i < buflen / 4; i++) fu_common_write_uint32 (&buf[i * 4], array[i], G_LITTLE_ENDIAN); } static gboolean dfu_tool_parse_xtea_key (const gchar *key, guint32 *keys, GError **error) { gsize key_len; /* too long */ key_len = strlen (key); if (key_len > 32) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Key string too long at %" G_GSIZE_FORMAT " chars, max 16", key_len); return FALSE; } /* parse 4x32b values or generate a hash */ if (key_len == 32) { for (guint8 i = 0; i < 4; i++) { gchar buf[] = "xxxxxxxx"; gchar *endptr; guint64 tmp; /* copy to 4-char buf (with NUL) */ memcpy (buf, key + i*8, 8); tmp = g_ascii_strtoull (buf, &endptr, 16); if (endptr && endptr[0] != '\0') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Failed to parse key '%s'", key); return FALSE; } keys[3-i] = (guint32) tmp; } } else { gsize buf_len = 16; guint8 buf[16]; g_autoptr(GChecksum) csum = NULL; csum = g_checksum_new (G_CHECKSUM_MD5); g_checksum_update (csum, (const guchar *) key, (gssize) key_len); g_checksum_get_digest (csum, buf, &buf_len); g_assert (buf_len == 16); dfu_cipher_buf_to_uint32 (buf, buf_len, keys); } /* success */ g_debug ("using XTEA key %04x%04x%04x%04x", keys[3], keys[2], keys[1], keys[0]); return TRUE; } /** * dfu_cipher_decrypt_xtea: (skip) * @key: a XTEA key * @data: data to parse * @length: length of @data * @error: a #GError, or %NULL * * Decrypt a buffer using XTEA. * * Returns: %TRUE for success **/ gboolean dfu_cipher_decrypt_xtea (const gchar *key, guint8 *data, guint32 length, GError **error) { guint32 sum; guint32 v0; guint32 v1; guint32 chunks = length / 4; guint32 keys[4]; g_autofree guint32 *tmp = NULL; /* sanity check */ if (length < 8) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "8 bytes data required, got %" G_GUINT32_FORMAT, length); return FALSE; } if (length % 4 != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Multiples of 4 bytes required, got %" G_GUINT32_FORMAT, length); return FALSE; } /* parse key */ if (!dfu_tool_parse_xtea_key (key, keys, error)) return FALSE; /* allocate a buffer that can be addressed in 4-byte chunks */ tmp = g_new0 (guint32, chunks); dfu_cipher_buf_to_uint32 (data, length, tmp); /* process buffer using XTEA keys */ for (guint j = 0; j < chunks; j += 2) { v0 = tmp[j]; v1 = tmp[j+1]; sum = XTEA_DELTA * XTEA_NUM_ROUNDS; for (guint8 i = 0; i < XTEA_NUM_ROUNDS; i++) { v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + keys[(sum >> 11) & 3]); sum -= XTEA_DELTA; v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + keys[sum & 3]); } tmp[j] = v0; tmp[j+1] = v1; } /* copy the temp buffer back to data */ dfu_cipher_uint32_to_buf (data, length, tmp); return TRUE; } /** * dfu_cipher_encrypt_xtea: (skip) * @key: a XTEA key * @data: data to parse * @length: length of @data * @error: a #GError, or %NULL * * Encrypt a buffer using XTEA. * * Returns: %TRUE for success **/ gboolean dfu_cipher_encrypt_xtea (const gchar *key, guint8 *data, guint32 length, GError **error) { guint32 sum; guint32 v0; guint32 v1; guint32 chunks = length / 4; guint32 keys[4]; g_autofree guint32 *tmp = NULL; /* sanity check */ if (length < 8) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "8 bytes data required, got %" G_GUINT32_FORMAT, length); return FALSE; } if (length % 4 != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Multiples of 4 bytes required, got %" G_GUINT32_FORMAT, length); return FALSE; } /* parse key */ if (!dfu_tool_parse_xtea_key (key, keys, error)) return FALSE; /* allocate a buffer that can be addressed in 4-byte chunks */ tmp = g_new0 (guint32, chunks); dfu_cipher_buf_to_uint32 (data, length, tmp); /* process buffer using XTEA keys */ for (guint j = 0; j < chunks; j += 2) { sum = 0; v0 = tmp[j]; v1 = tmp[j+1]; for (guint8 i = 0; i < XTEA_NUM_ROUNDS; i++) { v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + keys[sum & 3]); sum += XTEA_DELTA; v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + keys[(sum >> 11) & 3]); } tmp[j] = v0; tmp[j+1] = v1; } /* copy the temp buffer back to data */ dfu_cipher_uint32_to_buf (data, length, tmp); return TRUE; } fwupd-1.2.14/plugins/dfu/dfu-cipher-xtea.h000066400000000000000000000007121402665037500203310ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include G_BEGIN_DECLS gboolean dfu_cipher_encrypt_xtea (const gchar *key, guint8 *data, guint32 length, GError **error); gboolean dfu_cipher_decrypt_xtea (const gchar *key, guint8 *data, guint32 length, GError **error); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-common.c000066400000000000000000000177251402665037500174170ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * SECTION:dfu-common * @short_description: Common functions for DFU * * These helper objects allow converting from enum values to strings. */ #include "config.h" #include #include "dfu-common.h" /** * dfu_state_to_string: * @state: a #DfuState, e.g. %DFU_STATE_DFU_MANIFEST * * Converts an enumerated value to a string. * * Return value: a string **/ const gchar * dfu_state_to_string (DfuState state) { if (state == DFU_STATE_APP_IDLE) return "appIDLE"; if (state == DFU_STATE_APP_DETACH) return "appDETACH"; if (state == DFU_STATE_DFU_IDLE) return "dfuIDLE"; if (state == DFU_STATE_DFU_DNLOAD_SYNC) return "dfuDNLOAD-SYNC"; if (state == DFU_STATE_DFU_DNBUSY) return "dfuDNBUSY"; if (state == DFU_STATE_DFU_DNLOAD_IDLE) return "dfuDNLOAD-IDLE"; if (state == DFU_STATE_DFU_MANIFEST_SYNC) return "dfuMANIFEST-SYNC"; if (state == DFU_STATE_DFU_MANIFEST) return "dfuMANIFEST"; if (state == DFU_STATE_DFU_MANIFEST_WAIT_RESET) return "dfuMANIFEST-WAIT-RESET"; if (state == DFU_STATE_DFU_UPLOAD_IDLE) return "dfuUPLOAD-IDLE"; if (state == DFU_STATE_DFU_ERROR) return "dfuERROR"; return NULL; } /** * dfu_status_to_string: * @status: a #DfuStatus, e.g. %DFU_STATUS_ERR_ERASE * * Converts an enumerated value to a string. * * Return value: a string **/ const gchar * dfu_status_to_string (DfuStatus status) { if (status == DFU_STATUS_OK) return "OK"; if (status == DFU_STATUS_ERR_TARGET) return "errTARGET"; if (status == DFU_STATUS_ERR_FILE) return "errFILE"; if (status == DFU_STATUS_ERR_WRITE) return "errwrite"; if (status == DFU_STATUS_ERR_ERASE) return "errERASE"; if (status == DFU_STATUS_ERR_CHECK_ERASED) return "errCHECK_ERASED"; if (status == DFU_STATUS_ERR_PROG) return "errPROG"; if (status == DFU_STATUS_ERR_VERIFY) return "errVERIFY"; if (status == DFU_STATUS_ERR_ADDRESS) return "errADDRESS"; if (status == DFU_STATUS_ERR_NOTDONE) return "errNOTDONE"; if (status == DFU_STATUS_ERR_FIRMWARE) return "errFIRMWARE"; if (status == DFU_STATUS_ERR_VENDOR) return "errVENDOR"; if (status == DFU_STATUS_ERR_USBR) return "errUSBR"; if (status == DFU_STATUS_ERR_POR) return "errPOR"; if (status == DFU_STATUS_ERR_UNKNOWN) return "errUNKNOWN"; if (status == DFU_STATUS_ERR_STALLDPKT) return "errSTALLDPKT"; return NULL; } /** * dfu_cipher_kind_to_string: * @cipher_kind: a #DfuCipherKind, e.g. %DFU_CIPHER_KIND_XTEA * * Converts an enumerated value to a string. * * Return value: a string **/ const gchar * dfu_cipher_kind_to_string (DfuCipherKind cipher_kind) { if (cipher_kind == DFU_CIPHER_KIND_NONE) return "none"; if (cipher_kind == DFU_CIPHER_KIND_XTEA) return "xtea"; if (cipher_kind == DFU_CIPHER_KIND_RSA) return "rsa"; return NULL; } /** * dfu_version_to_string: * @version: a #DfuVersion, e.g. %DFU_VERSION_DFU_1_1 * * Converts an enumerated value to a string. * * Return value: a string **/ const gchar * dfu_version_to_string (DfuVersion version) { if (version == DFU_VERSION_DFU_1_0) return "1.0"; if (version == DFU_VERSION_DFU_1_1) return "1.1"; if (version == DFU_VERSION_DFUSE) return "DfuSe"; if (version == DFU_VERSION_ATMEL_AVR) return "AtmelAVR"; return NULL; } /** * dfu_utils_bytes_join_array: * @chunks: (element-kind GBytes): bytes * * Creates a monolithic block of memory from an array of #GBytes. * * Return value: (transfer full): a new GBytes **/ GBytes * dfu_utils_bytes_join_array (GPtrArray *chunks) { gsize total_size = 0; guint32 offset = 0; guint8 *buffer; /* get the size of all the chunks */ for (guint i = 0; i < chunks->len; i++) { GBytes *chunk_tmp = g_ptr_array_index (chunks, i); total_size += g_bytes_get_size (chunk_tmp); } /* copy them into a buffer */ buffer = g_malloc0 (total_size); for (guint i = 0; i < chunks->len; i++) { const guint8 *chunk_data; gsize chunk_size = 0; GBytes *chunk_tmp = g_ptr_array_index (chunks, i); chunk_data = g_bytes_get_data (chunk_tmp, &chunk_size); if (chunk_size == 0) continue; memcpy (buffer + offset, chunk_data, chunk_size); offset += chunk_size; } return g_bytes_new_take (buffer, total_size); } /** * dfu_utils_bytes_pad: * @bytes: a #GBytes * @sz: the desired size in bytes * * Pads a GBytes to a given @sz with `0xff`. * * Return value: (transfer full): a #GBytes **/ GBytes * dfu_utils_bytes_pad (GBytes *bytes, gsize sz) { gsize bytes_sz; g_return_val_if_fail (g_bytes_get_size (bytes) <= sz, NULL); /* pad */ bytes_sz = g_bytes_get_size (bytes); if (bytes_sz < sz) { const guint8 *data = g_bytes_get_data (bytes, NULL); guint8 *data_new = g_malloc (sz); memcpy (data_new, data, bytes_sz); memset (data_new + bytes_sz, 0xff, sz - bytes_sz); return g_bytes_new_take (data_new, sz); } /* exactly right */ return g_bytes_ref (bytes); } /** * dfu_utils_buffer_parse_uint4: * @data: a string * * Parses a base 16 number from a string. * * The string MUST be at least 1 byte long as this function cannot check the * length of @data. Checking the size must be done in the caller. * * Return value: A parsed value, or 0 for error **/ guint8 dfu_utils_buffer_parse_uint4 (const gchar *data) { gchar buffer[2]; memcpy (buffer, data, 1); buffer[1] = '\0'; return (guint8) g_ascii_strtoull (buffer, NULL, 16); } /** * dfu_utils_buffer_parse_uint8: * @data: a string * * Parses a base 16 number from a string. * * The string MUST be at least 2 bytes long as this function cannot check the * length of @data. Checking the size must be done in the caller. * * Return value: A parsed value, or 0 for error **/ guint8 dfu_utils_buffer_parse_uint8 (const gchar *data) { gchar buffer[3]; memcpy (buffer, data, 2); buffer[2] = '\0'; return (guint8) g_ascii_strtoull (buffer, NULL, 16); } /** * dfu_utils_buffer_parse_uint16: * @data: a string * * Parses a base 16 number from a string. * * The string MUST be at least 4 bytes long as this function cannot check the * length of @data. Checking the size must be done in the caller. * * Return value: A parsed value, or 0 for error **/ guint16 dfu_utils_buffer_parse_uint16 (const gchar *data) { gchar buffer[5]; memcpy (buffer, data, 4); buffer[4] = '\0'; return (guint16) g_ascii_strtoull (buffer, NULL, 16); } /** * dfu_utils_buffer_parse_uint24: * @data: a string * * Parses a base 16 number from a string. * * The string MUST be at least 6 bytes long as this function cannot check the * length of @data. Checking the size must be done in the caller. * * Return value: A parsed value, or 0 for error **/ guint32 dfu_utils_buffer_parse_uint24 (const gchar *data) { gchar buffer[7]; memcpy (buffer, data, 6); buffer[6] = '\0'; return (guint32) g_ascii_strtoull (buffer, NULL, 16); } /** * dfu_utils_buffer_parse_uint32: * @data: a string * * Parses a base 16 number from a string. * * The string MUST be at least 8 bytes long as this function cannot check the * length of @data. Checking the size must be done in the caller. * * Return value: A parsed value, or 0 for error **/ guint32 dfu_utils_buffer_parse_uint32 (const gchar *data) { gchar buffer[9]; memcpy (buffer, data, 8); buffer[8] = '\0'; return (guint32) g_ascii_strtoull (buffer, NULL, 16); } /** * dfu_utils_strnsplit: * @str: a string to split * @sz: size of @str * @delimiter: a string which specifies the places at which to split the string * @max_tokens: the maximum number of pieces to split @str into * * Splits a string into a maximum of @max_tokens pieces, using the given * delimiter. If @max_tokens is reached, the remainder of string is appended * to the last token. * * Return value: a newly-allocated NULL-terminated array of strings **/ gchar ** dfu_utils_strnsplit (const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens) { if (str[sz - 1] != '\0') { g_autofree gchar *str2 = g_strndup (str, sz); return g_strsplit (str2, delimiter, max_tokens); } return g_strsplit (str, delimiter, max_tokens); } fwupd-1.2.14/plugins/dfu/dfu-common.h000066400000000000000000000124131402665037500174110ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include G_BEGIN_DECLS /** * DfuRequest: * @DFU_REQUEST_DETACH: Detach * @DFU_REQUEST_DNLOAD: Download host-to-device * @DFU_REQUEST_UPLOAD: Upload device-to-host * @DFU_REQUEST_GETSTATUS: Get the device status * @DFU_REQUEST_CLRSTATUS: Clear the device status * @DFU_REQUEST_GETSTATE: Get the last set state * @DFU_REQUEST_ABORT: Abort the current transfer * * The DFU request kinds. **/ typedef enum { DFU_REQUEST_DETACH = 0x00, DFU_REQUEST_DNLOAD = 0x01, DFU_REQUEST_UPLOAD = 0x02, DFU_REQUEST_GETSTATUS = 0x03, DFU_REQUEST_CLRSTATUS = 0x04, DFU_REQUEST_GETSTATE = 0x05, DFU_REQUEST_ABORT = 0x06, /*< private >*/ DFU_REQUEST_LAST } DfuRequest; /** * DfuStatus: * @DFU_STATUS_OK: No error condition is present * @DFU_STATUS_ERR_TARGET: File is not targeted for use by this device * @DFU_STATUS_ERR_FILE: File is for this device but fails a verification test * @DFU_STATUS_ERR_WRITE: Device is unable to write memory * @DFU_STATUS_ERR_ERASE: Memory erase function failed * @DFU_STATUS_ERR_CHECK_ERASED: Memory erase check failed * @DFU_STATUS_ERR_PROG: Program memory function failed * @DFU_STATUS_ERR_VERIFY: Programmed memory failed verification * @DFU_STATUS_ERR_ADDRESS: Cannot program memory due to received address that isout of range * @DFU_STATUS_ERR_NOTDONE: Received DFU_DNLOAD with wLength = 0 but data is incomplete * @DFU_STATUS_ERR_FIRMWARE: Device firmware is corrupt * @DFU_STATUS_ERR_VENDOR: iString indicates a vendor-specific error * @DFU_STATUS_ERR_USBR: Device detected unexpected USB reset signaling * @DFU_STATUS_ERR_POR: Device detected unexpected power on reset * @DFU_STATUS_ERR_UNKNOWN: Something unexpected went wrong * @DFU_STATUS_ERR_STALLDPKT: Device stalled an unexpected request * * The status enumerated kind. **/ typedef enum { DFU_STATUS_OK = 0x00, DFU_STATUS_ERR_TARGET = 0x01, DFU_STATUS_ERR_FILE = 0x02, DFU_STATUS_ERR_WRITE = 0x03, DFU_STATUS_ERR_ERASE = 0x04, DFU_STATUS_ERR_CHECK_ERASED = 0x05, DFU_STATUS_ERR_PROG = 0x06, DFU_STATUS_ERR_VERIFY = 0x07, DFU_STATUS_ERR_ADDRESS = 0x08, DFU_STATUS_ERR_NOTDONE = 0x09, DFU_STATUS_ERR_FIRMWARE = 0x0a, DFU_STATUS_ERR_VENDOR = 0x0b, DFU_STATUS_ERR_USBR = 0x0c, DFU_STATUS_ERR_POR = 0x0d, DFU_STATUS_ERR_UNKNOWN = 0x0e, DFU_STATUS_ERR_STALLDPKT = 0x0f, /*< private >*/ DFU_STATUS_LAST } DfuStatus; /** * DfuState: * @DFU_STATE_APP_IDLE: State 0 * @DFU_STATE_APP_DETACH: State 1 * @DFU_STATE_DFU_IDLE: State 2 * @DFU_STATE_DFU_DNLOAD_SYNC: State 3 * @DFU_STATE_DFU_DNBUSY: State 4 * @DFU_STATE_DFU_DNLOAD_IDLE: State 5 * @DFU_STATE_DFU_MANIFEST_SYNC: State 6 * @DFU_STATE_DFU_MANIFEST: State 7 * @DFU_STATE_DFU_MANIFEST_WAIT_RESET: State 8 * @DFU_STATE_DFU_UPLOAD_IDLE: State 9 * @DFU_STATE_DFU_ERROR: State 10 * * The state enumerated kind. **/ typedef enum { DFU_STATE_APP_IDLE = 0x00, DFU_STATE_APP_DETACH = 0x01, DFU_STATE_DFU_IDLE = 0x02, DFU_STATE_DFU_DNLOAD_SYNC = 0x03, DFU_STATE_DFU_DNBUSY = 0x04, DFU_STATE_DFU_DNLOAD_IDLE = 0x05, DFU_STATE_DFU_MANIFEST_SYNC = 0x06, DFU_STATE_DFU_MANIFEST = 0x07, DFU_STATE_DFU_MANIFEST_WAIT_RESET = 0x08, DFU_STATE_DFU_UPLOAD_IDLE = 0x09, DFU_STATE_DFU_ERROR = 0x0a, /*< private >*/ DFU_STATE_LAST } DfuState; /** * DfuCipherKind: * @DFU_CIPHER_KIND_NONE: No cipher detected * @DFU_CIPHER_KIND_XTEA: XTEA cipher detected * @DFU_CIPHER_KIND_RSA: RSA cipher detected * * The type of cipher used for transferring the firmware. **/ typedef enum { DFU_CIPHER_KIND_NONE, DFU_CIPHER_KIND_XTEA, DFU_CIPHER_KIND_RSA, /*< private >*/ DFU_CIPHER_KIND_LAST } DfuCipherKind; /** * DfuVersion: * @DFU_VERSION_UNKNOWN: Format unknown * @DFU_VERSION_DFU_1_0: DFU 1.0 * @DFU_VERSION_DFU_1_1: DFU 1.1 * @DFU_VERSION_DFUSE: DfuSe * @DFU_VERSION_ATMEL_AVR: Atmel AVR * * The known versions of the DFU standard in BCD format. **/ typedef enum { DFU_VERSION_UNKNOWN = 0, DFU_VERSION_DFU_1_0 = 0x0100, DFU_VERSION_DFU_1_1 = 0x0110, DFU_VERSION_DFUSE = 0x011a, /* defined by ST */ DFU_VERSION_ATMEL_AVR = 0xff01, /* made up */ /*< private >*/ DFU_VERSION_LAST } DfuVersion; #define DFU_METADATA_KEY_LICENSE "License" #define DFU_METADATA_KEY_COPYRIGHT "Copyright" #define DFU_METADATA_KEY_CIPHER_KIND "CipherKind" const gchar *dfu_state_to_string (DfuState state); const gchar *dfu_status_to_string (DfuStatus status); const gchar *dfu_cipher_kind_to_string (DfuCipherKind cipher_kind); const gchar *dfu_version_to_string (DfuVersion version); /* helpers */ GBytes *dfu_utils_bytes_join_array (GPtrArray *chunks); GBytes *dfu_utils_bytes_pad (GBytes *bytes, gsize sz); guint8 dfu_utils_buffer_parse_uint4 (const gchar *data); guint8 dfu_utils_buffer_parse_uint8 (const gchar *data); guint16 dfu_utils_buffer_parse_uint16 (const gchar *data); guint32 dfu_utils_buffer_parse_uint24 (const gchar *data); guint32 dfu_utils_buffer_parse_uint32 (const gchar *data); gchar **dfu_utils_strnsplit (const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-device-private.h000066400000000000000000000011461402665037500210310ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include #include "fu-quirks.h" #include "dfu-device.h" G_BEGIN_DECLS void dfu_device_error_fixup (DfuDevice *device, GError **error); guint dfu_device_get_download_timeout (DfuDevice *device); gchar *dfu_device_get_quirks_as_string (DfuDevice *device); gchar *dfu_device_get_attributes_as_string (DfuDevice *device); gboolean dfu_device_ensure_interface (DfuDevice *device, GError **error); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-device.c000066400000000000000000001661521402665037500173650ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * SECTION:dfu-device * @short_description: Object representing a DFU-capable device * * This object allows two things: * * - Downloading from the host to the device, optionally with * verification using a DFU or DfuSe firmware file. * * - Uploading from the device to the host to a DFU or DfuSe firmware * file. The file format is chosen automatically, with DfuSe being * chosen if the device contains more than one target. * * See also: #DfuTarget, #DfuFirmware */ /** * FU_QUIRKS_DFU_FLAGS: * @key: the USB device ID, e.g. `USB\VID_0763&PID_2806` * @value: a string, separated using `|`, e.g. `ignore-polltimeout|no-pid-change` * * Assigns optional quirks to use for a DFU device which does not follow the * DFU 1.0 or 1.1 specification. The list of supported quirks is thus: * * * `none`: No device quirks * * `action-required`: User has to do something manually, e.g. press a button * * `attach-extra-reset`: Device needs resetting twice for attach * * `attach-upload-download`: An upload or download is required for attach * * `force-dfu-mode`: Force DFU mode * * `ignore-polltimeout`: Ignore the device download timeout * * `ignore-runtime`: Device has broken DFU runtime support * * `ignore-upload`: Uploading from the device is broken * * `no-dfu-runtime`: No DFU runtime interface is provided * * `no-get-status-upload`: Do not do GetStatus when uploading * * `no-pid-change`: Accept the same VID:PID when changing modes * * `use-any-interface`: Use any interface for DFU * * `use-atmel-avr`: Device uses the ATMEL bootloader * * `use-protocol-zero`: Fix up the protocol number * * `legacy-protocol`: Use a legacy protocol version * * Default value: `none` * * Since: 1.0.1 */ #define FU_QUIRKS_DFU_FLAGS "DfuFlags" /** * FU_QUIRKS_DFU_FORCE_VERSION: * @key: the USB device ID, e.g. `USB\VID_0763&PID_2806` * @value: the uint16_t DFU version, encoded in base 16, e.g. `0110` * * Forces a specific DFU version for the hardware device. This is required * if the device does not set, or sets incorrectly, items in the DFU functional * descriptor. * * Since: 1.0.1 */ #define FU_QUIRKS_DFU_FORCE_VERSION "DfuForceVersion" /** * FU_QUIRKS_DFU_JABRA_DETACH: * @key: the USB device ID, e.g. `USB\VID_0763&PID_2806` * @value: the two uint8_t unlock values, encoded in base 16, e.g. `0201` * * Assigns the two magic bytes sent to the Jabra hardware when the device is * in runtime mode to make it switch into DFU mode. * * Since: 1.0.1 */ #define FU_QUIRKS_DFU_JABRA_DETACH "DfuJabraDetach" #include "config.h" #include #include "dfu-common.h" #include "dfu-device-private.h" #include "dfu-target-avr.h" #include "dfu-target-private.h" #include "dfu-target-stm.h" #include "fu-device-locker.h" #include "fwupd-error.h" static void dfu_device_finalize (GObject *object); typedef struct { DfuDeviceAttributes attributes; DfuDeviceQuirks quirks; DfuState state; DfuStatus status; GPtrArray *targets; GUsbContext *usb_context; gboolean done_upload_or_download; gboolean claimed_interface; gchar *chip_id; gchar *jabra_detach; guint16 version; guint16 force_version; guint16 runtime_pid; guint16 runtime_vid; guint16 runtime_release; guint16 transfer_size; guint8 iface_number; guint dnload_timeout; guint timeout_ms; } DfuDevicePrivate; enum { SIGNAL_STATUS_CHANGED, SIGNAL_STATE_CHANGED, SIGNAL_LAST }; static guint signals [SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE_WITH_PRIVATE (DfuDevice, dfu_device, FU_TYPE_USB_DEVICE) #define GET_PRIVATE(o) (dfu_device_get_instance_private (o)) /** * dfu_device_get_transfer_size: * @device: a #GUsbDevice * * Gets the transfer size in bytes. * * Return value: packet size, or 0 for unknown **/ guint16 dfu_device_get_transfer_size (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff); return priv->transfer_size; } /** * dfu_device_get_version: * @device: a #GUsbDevice * * Gets the DFU specification version supported by the device. * * Return value: integer, or 0 for unknown, e.g. %DFU_VERSION_DFU_1_1 **/ guint16 dfu_device_get_version (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff); return priv->version; } /** * dfu_device_get_download_timeout: * @device: a #GUsbDevice * * Gets the download timeout in ms. * * Return value: delay, or 0 for unknown **/ guint dfu_device_get_download_timeout (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0); return priv->dnload_timeout; } /** * dfu_device_set_transfer_size: * @device: a #GUsbDevice * @transfer_size: maximum packet size * * Sets the transfer size in bytes. **/ void dfu_device_set_transfer_size (DfuDevice *device, guint16 transfer_size) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (DFU_IS_DEVICE (device)); priv->transfer_size = transfer_size; } typedef struct __attribute__((packed)) { guint8 bLength; guint8 bDescriptorType; guint8 bmAttributes; guint16 wDetachTimeOut; guint16 wTransferSize; guint16 bcdDFUVersion; } DfuFuncDescriptor; static gboolean dfu_device_parse_iface_data (DfuDevice *device, GBytes *iface_data, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); DfuFuncDescriptor desc = { 0x0 }; const guint8 *buf; gsize sz; /* parse the functional descriptor */ buf = g_bytes_get_data (iface_data, &sz); if (sz == sizeof(DfuFuncDescriptor)) { memcpy (&desc, buf, sz); } else if (sz > sizeof(DfuFuncDescriptor)) { g_debug ("DFU interface with %" G_GSIZE_FORMAT " bytes vendor data", sz - sizeof(DfuFuncDescriptor)); memcpy (&desc, buf, sizeof(DfuFuncDescriptor)); } else if (sz == sizeof(DfuFuncDescriptor) - 2) { g_warning ("truncated DFU interface data, no bcdDFUVersion"); memcpy (&desc, buf, sz); desc.bcdDFUVersion = DFU_VERSION_DFU_1_1; } else { g_autoptr(GString) bufstr = g_string_new (NULL); for (gsize i = 0; i < sz; i++) g_string_append_printf (bufstr, "%02x ", buf[i]); if (bufstr->len > 0) g_string_truncate (bufstr, bufstr->len - 1); g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "interface found, but not the correct length for " "functional data: %" G_GSIZE_FORMAT " bytes: %s", sz, bufstr->str); return FALSE; } /* get transfer size and version */ priv->transfer_size = GUINT16_FROM_LE (desc.wTransferSize); priv->version = GUINT16_FROM_LE (desc.bcdDFUVersion); /* ST-specific */ if (priv->version == DFU_VERSION_DFUSE && desc.bmAttributes & DFU_DEVICE_ATTRIBUTE_CAN_ACCELERATE) priv->transfer_size = 0x1000; /* get attributes about the DFU operation */ priv->attributes = desc.bmAttributes; return TRUE; } static void dfu_device_guess_state_from_iface (DfuDevice *device, GUsbInterface *iface) { DfuDevicePrivate *priv = GET_PRIVATE (device); /* some devices use the wrong interface */ if (dfu_device_has_quirk (device, DFU_DEVICE_QUIRK_FORCE_DFU_MODE)) { g_debug ("quirking device into DFU mode"); priv->state = DFU_STATE_DFU_IDLE; return; } /* runtime */ if (g_usb_interface_get_protocol (iface) == 0x01) { priv->state = DFU_STATE_APP_IDLE; return; } /* DFU */ if (g_usb_interface_get_protocol (iface) == 0x02) { priv->state = DFU_STATE_DFU_IDLE; return; } g_warning ("unable to guess initial device state from interface %u", g_usb_interface_get_protocol (iface)); } static gboolean dfu_device_add_targets (DfuDevice *device, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_autoptr(GPtrArray) ifaces = NULL; /* add all DFU-capable targets */ ifaces = g_usb_device_get_interfaces (usb_device, error); if (ifaces == NULL) return FALSE; g_ptr_array_set_size (priv->targets, 0); for (guint i = 0; i < ifaces->len; i++) { GBytes *iface_data = NULL; DfuTarget *target; g_autoptr(GError) error_local = NULL; GUsbInterface *iface = g_ptr_array_index (ifaces, i); /* some devices don't use the right class and subclass */ if (!dfu_device_has_quirk (device, DFU_DEVICE_QUIRK_USE_ANY_INTERFACE)) { if (g_usb_interface_get_class (iface) != G_USB_DEVICE_CLASS_APPLICATION_SPECIFIC) continue; if (g_usb_interface_get_subclass (iface) != 0x01) continue; } /* parse any interface data */ iface_data = g_usb_interface_get_extra (iface); if (g_bytes_get_size (iface_data) > 0) { if (!dfu_device_parse_iface_data (device, iface_data, &error_local)) { g_warning ("failed to parse interface data for %04x:%04x: %s", g_usb_device_get_vid (usb_device), g_usb_device_get_pid (usb_device), error_local->message); continue; } } else { priv->attributes |= DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD | DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD; } /* fix up the version */ if (priv->force_version > 0) priv->version = priv->force_version; if (priv->version == DFU_VERSION_DFU_1_0 || priv->version == DFU_VERSION_DFU_1_1) { g_debug ("DFU v1.1"); } else if (priv->version == DFU_VERSION_ATMEL_AVR) { g_debug ("AVR-DFU support"); priv->version = DFU_VERSION_ATMEL_AVR; } else if (priv->version == DFU_VERSION_DFUSE) { g_debug ("STM-DFU support"); } else if (priv->version == 0x0101) { g_debug ("DFU v1.1 assumed"); priv->version = DFU_VERSION_DFU_1_1; } else { g_warning ("DFU version 0x%04x invalid, v1.1 assumed", priv->version); priv->version = DFU_VERSION_DFU_1_1; } /* fix up the transfer size */ if (priv->transfer_size == 0xffff) { priv->transfer_size = 0x0400; g_debug ("DFU transfer size unspecified, guessing"); } if (priv->transfer_size > 0x0000) { g_debug ("using DFU transfer size 0x%04x bytes", priv->transfer_size); } else { g_warning ("DFU transfer size invalid, using default"); priv->transfer_size = 64; } /* create a target of the required type */ switch (priv->version) { case DFU_VERSION_DFUSE: target = dfu_target_stm_new (); break; case DFU_VERSION_ATMEL_AVR: target = dfu_target_avr_new (); break; default: target = dfu_target_new (); break; } dfu_target_set_device (target, device); dfu_target_set_alt_idx (target, g_usb_interface_get_index (iface)); dfu_target_set_alt_setting (target, g_usb_interface_get_alternate (iface)); /* add target */ priv->iface_number = g_usb_interface_get_number (iface); g_ptr_array_add (priv->targets, target); dfu_device_guess_state_from_iface (device, iface); } /* save for reset */ if (priv->state == DFU_STATE_APP_IDLE || (priv->quirks & DFU_DEVICE_QUIRK_NO_PID_CHANGE)) { priv->runtime_vid = g_usb_device_get_vid (usb_device); priv->runtime_pid = g_usb_device_get_pid (usb_device); priv->runtime_release = g_usb_device_get_release (usb_device); } /* the device has no DFU runtime, so cheat */ if (priv->targets->len == 0 && priv->quirks & DFU_DEVICE_QUIRK_NO_DFU_RUNTIME) { g_debug ("no DFU runtime, so faking device"); priv->state = DFU_STATE_APP_IDLE; priv->iface_number = 0xff; priv->runtime_vid = g_usb_device_get_vid (usb_device); priv->runtime_pid = g_usb_device_get_pid (usb_device); priv->runtime_release = g_usb_device_get_release (usb_device); priv->attributes = DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD | DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD; return TRUE; } /* no targets */ if (priv->targets->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no DFU interfaces"); return FALSE; } /* the device upload is broken */ if (priv->quirks & DFU_DEVICE_QUIRK_IGNORE_UPLOAD) priv->attributes &= ~DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD; return TRUE; } /** * dfu_device_has_quirk: (skip) * @device: A #DfuDevice * @quirk: A #DfuDeviceQuirks * * Returns if a device has a specific quirk * * Return value: %TRUE if the device has this quirk **/ gboolean dfu_device_has_quirk (DfuDevice *device, DfuDeviceQuirks quirk) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0x0); return (priv->quirks & quirk) > 0; } /** * dfu_device_can_upload: * @device: a #GUsbDevice * * Gets if the device can upload. * * Return value: %TRUE if the device can upload from device to host **/ gboolean dfu_device_can_upload (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE); return (priv->attributes & DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD) > 0; } /** * dfu_device_can_download: * @device: a #GUsbDevice * * Gets if the device can download. * * Return value: %TRUE if the device can download from host to device **/ gboolean dfu_device_can_download (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE); return (priv->attributes & DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD) > 0; } /** * dfu_device_set_timeout: * @device: a #DfuDevice * @timeout_ms: the timeout in ms * * Sets the USB timeout to use when contacting the USB device. **/ void dfu_device_set_timeout (DfuDevice *device, guint timeout_ms) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (DFU_IS_DEVICE (device)); priv->timeout_ms = timeout_ms; } /** * dfu_device_is_runtime: * @device: a #GUsbDevice * * Gets the device mode. * * Return value: %TRUE if the device is in a runtime state **/ gboolean dfu_device_is_runtime (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE); if (priv->state == DFU_STATE_APP_IDLE || priv->state == DFU_STATE_APP_DETACH) return TRUE; return FALSE; } /** * dfu_device_get_timeout: * @device: a #GUsbDevice * * Gets the device timeout. * * Return value: enumerated timeout in ms **/ guint dfu_device_get_timeout (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0); return priv->timeout_ms; } /** * dfu_device_get_state: * @device: a #GUsbDevice * * Gets the device state. * * Return value: enumerated state, e.g. %DFU_STATE_DFU_UPLOAD_IDLE **/ DfuState dfu_device_get_state (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0); return priv->state; } /** * dfu_device_get_status: * @device: a #GUsbDevice * * Gets the device status. * * Return value: enumerated status, e.g. %DFU_STATUS_ERR_ADDRESS **/ DfuStatus dfu_device_get_status (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0); return priv->status; } /** * dfu_device_has_attribute: (skip) * @device: A #DfuDevice * @attribute: A #DfuDeviceAttributes, e.g. %DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD * * Returns if an attribute set for the device. * * Return value: %TRUE if the attribute is set **/ gboolean dfu_device_has_attribute (DfuDevice *device, DfuDeviceAttributes attribute) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0x0); return (priv->attributes & attribute) > 0; } /** * dfu_device_remove_attribute: (skip) * @device: A #DfuDevice * @attribute: A #DfuDeviceAttributes, e.g. %DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD * * Removes an attribute from the device. **/ void dfu_device_remove_attribute (DfuDevice *device, DfuDeviceAttributes attribute) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (DFU_IS_DEVICE (device)); priv->attributes &= ~attribute; } static void dfu_device_set_quirks_from_string (DfuDevice *device, const gchar *str) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_auto(GStrv) split = g_strsplit (str, ",", -1); priv->quirks = DFU_DEVICE_QUIRK_NONE; for (guint i = 0; split[i] != NULL; i++) { if (g_strcmp0 (split[i], "ignore-polltimeout") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_IGNORE_POLLTIMEOUT; continue; } if (g_strcmp0 (split[i], "force-dfu-mode") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_FORCE_DFU_MODE; continue; } if (g_strcmp0 (split[i], "no-pid-change") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_NO_PID_CHANGE; continue; } if (g_strcmp0 (split[i], "no-get-status-upload") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_NO_GET_STATUS_UPLOAD; continue; } if (g_strcmp0 (split[i], "no-dfu-runtime") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_NO_DFU_RUNTIME; continue; } if (g_strcmp0 (split[i], "attach-upload-download") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_ATTACH_UPLOAD_DOWNLOAD; continue; } if (g_strcmp0 (split[i], "ignore-runtime") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_IGNORE_RUNTIME; continue; } if (g_strcmp0 (split[i], "action-required") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_ACTION_REQUIRED; continue; } if (g_strcmp0 (split[i], "ignore-upload") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_IGNORE_UPLOAD; continue; } if (g_strcmp0 (split[i], "attach-extra-reset") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_ATTACH_EXTRA_RESET; continue; } if (g_strcmp0 (split[i], "use-any-interface") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_USE_ANY_INTERFACE; continue; } if (g_strcmp0 (split[i], "legacy-protocol") == 0) { priv->quirks |= DFU_DEVICE_QUIRK_LEGACY_PROTOCOL; continue; } } } void dfu_device_set_usb_context (DfuDevice *device, GUsbContext *quirks) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_set_object (&priv->usb_context, quirks); } GUsbContext * dfu_device_get_usb_context (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); return priv->usb_context; } /** * dfu_device_new: * * Creates a new DFU device object. * * Return value: a new #DfuDevice **/ DfuDevice * dfu_device_new (GUsbDevice *usb_device) { DfuDevice *device; device = g_object_new (DFU_TYPE_DEVICE, "usb-device", usb_device, NULL); return device; } /** * dfu_device_get_targets: * @device: a #DfuDevice * * Gets all the targets for this device. * * Return value: (transfer none) (element-type DfuTarget): #DfuTarget, or %NULL **/ GPtrArray * dfu_device_get_targets (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), NULL); return priv->targets; } /** * dfu_device_get_target_by_alt_setting: * @device: a #DfuDevice * @alt_setting: the setting used to find * @error: a #GError, or %NULL * * Gets a target with a specific alternative setting. * * Return value: (transfer full): a #DfuTarget, or %NULL **/ DfuTarget * dfu_device_get_target_by_alt_setting (DfuDevice *device, guint8 alt_setting, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* find by ID */ for (guint i = 0; i < priv->targets->len; i++) { DfuTarget *target = g_ptr_array_index (priv->targets, i); if (dfu_target_get_alt_setting (target) == alt_setting) return g_object_ref (target); } /* failed */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No target with alt-setting %i", alt_setting); return NULL; } /** * dfu_device_get_target_by_alt_name: * @device: a #DfuDevice * @alt_name: the name used to find * @error: a #GError, or %NULL * * Gets a target with a specific alternative name. * * Return value: (transfer full): a #DfuTarget, or %NULL **/ DfuTarget * dfu_device_get_target_by_alt_name (DfuDevice *device, const gchar *alt_name, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* find by ID */ for (guint i = 0; i < priv->targets->len; i++) { DfuTarget *target = g_ptr_array_index (priv->targets, i); if (g_strcmp0 (dfu_target_get_alt_name (target, NULL), alt_name) == 0) return g_object_ref (target); } /* failed */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No target with alt-name %s", alt_name); return NULL; } /** * dfu_device_get_platform_id: * @device: a #DfuDevice * * Gets the platform ID which normally corresponds to the port in some way. * * Return value: string or %NULL **/ const gchar * dfu_device_get_platform_id (DfuDevice *device) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_return_val_if_fail (DFU_IS_DEVICE (device), NULL); return g_usb_device_get_platform_id (usb_device); } /** * dfu_device_get_runtime_vid: * @device: a #DfuDevice * * Gets the runtime vendor ID. * * Return value: vendor ID, or 0xffff for unknown **/ guint16 dfu_device_get_runtime_vid (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff); return priv->runtime_vid; } /** * dfu_device_get_runtime_pid: * @device: a #DfuDevice * * Gets the runtime product ID. * * Return value: product ID, or 0xffff for unknown **/ guint16 dfu_device_get_runtime_pid (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff); return priv->runtime_pid; } /** * dfu_device_get_runtime_release: * @device: a #DfuDevice * * Gets the runtime release number in BCD format. * * Return value: release number, or 0xffff for unknown **/ guint16 dfu_device_get_runtime_release (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff); return priv->runtime_release; } /** * dfu_device_get_vid: * @device: a #DfuDevice * * Gets the present vendor ID. * * Return value: vendor ID, or 0xffff for unknown **/ guint16 dfu_device_get_vid (DfuDevice *device) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff); return g_usb_device_get_vid (usb_device); } /** * dfu_device_get_pid: * @device: a #DfuDevice * * Gets the present product ID. * * Return value: product ID, or 0xffff for unknown **/ guint16 dfu_device_get_pid (DfuDevice *device) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff); return g_usb_device_get_pid (usb_device); } /** * dfu_device_get_release: * @device: a #DfuDevice * * Gets the present release number in BCD format. * * Return value: release number, or 0xffff for unknown **/ guint16 dfu_device_get_release (DfuDevice *device) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff); return g_usb_device_get_release (usb_device); } const gchar * dfu_device_get_chip_id (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), NULL); return priv->chip_id; } void dfu_device_set_chip_id (DfuDevice *device, const gchar *chip_id) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (DFU_IS_DEVICE (device)); g_debug ("chip ID set to: %s", chip_id); priv->chip_id = g_strdup (chip_id); } static void dfu_device_set_state (DfuDevice *device, DfuState state) { DfuDevicePrivate *priv = GET_PRIVATE (device); if (priv->state == state) return; priv->state = state; g_signal_emit (device, signals[SIGNAL_STATE_CHANGED], 0, state); } static void dfu_device_set_status (DfuDevice *device, DfuStatus status) { DfuDevicePrivate *priv = GET_PRIVATE (device); if (priv->status == status) return; priv->status = status; g_signal_emit (device, signals[SIGNAL_STATUS_CHANGED], 0, status); } gboolean dfu_device_ensure_interface (DfuDevice *device, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_autoptr(GError) error_local = NULL; /* already done */ if (priv->claimed_interface) return TRUE; /* nothing set */ if (priv->iface_number == 0xff) return TRUE; /* claim, without detaching kernel driver */ if (!g_usb_device_claim_interface (usb_device, (gint) priv->iface_number, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot claim interface %i: %s", priv->iface_number, error_local->message); return FALSE; } /* success */ priv->claimed_interface = TRUE; return TRUE; } /** * dfu_device_refresh_and_clear: * @device: a #DfuDevice * @error: a #GError, or %NULL * * Refreshes the cached properties on the DFU device. If there are any transers * in progress thay are cancelled, and if there are any pending errors they are * cancelled. * * Return value: %TRUE for success **/ gboolean dfu_device_refresh_and_clear (DfuDevice *device, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); if (!dfu_device_refresh (device, error)) return FALSE; switch (priv->state) { case DFU_STATE_DFU_UPLOAD_IDLE: case DFU_STATE_DFU_DNLOAD_IDLE: case DFU_STATE_DFU_DNLOAD_SYNC: g_debug ("aborting transfer %s", dfu_status_to_string (priv->status)); if (!dfu_device_abort (device, error)) return FALSE; break; case DFU_STATE_DFU_ERROR: g_debug ("clearing error %s", dfu_status_to_string (priv->status)); if (!dfu_device_clear_status (device, error)) return FALSE; break; default: break; } return TRUE; } /** * dfu_device_refresh: * @device: a #DfuDevice * @error: a #GError, or %NULL * * Refreshes the cached properties on the DFU device. * * Return value: %TRUE for success **/ gboolean dfu_device_refresh (DfuDevice *device, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); gsize actual_length = 0; guint8 buf[6]; g_autoptr(GError) error_local = NULL; g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* no backing USB device */ if (usb_device == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to refresh: no GUsbDevice for %s", dfu_device_get_platform_id (device)); return FALSE; } /* the device has no DFU runtime, so cheat */ if (priv->state == DFU_STATE_APP_IDLE && priv->quirks & DFU_DEVICE_QUIRK_NO_DFU_RUNTIME) return TRUE; /* ensure interface is claimed */ if (!dfu_device_ensure_interface (device, error)) return FALSE; if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, DFU_REQUEST_GETSTATUS, 0, priv->iface_number, buf, sizeof(buf), &actual_length, priv->timeout_ms, NULL, /* cancellable */ &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot get device state: %s", error_local->message); return FALSE; } if (actual_length != 6) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot get device status, invalid size: %04x", (guint) actual_length); return FALSE; } /* some devices use the wrong state value */ if (dfu_device_has_quirk (device, DFU_DEVICE_QUIRK_FORCE_DFU_MODE) && dfu_device_get_state (device) != DFU_STATE_DFU_IDLE) { g_debug ("quirking device into DFU mode"); dfu_device_set_state (device, DFU_STATE_DFU_IDLE); } else { dfu_device_set_state (device, buf[4]); } /* status or state changed */ dfu_device_set_status (device, buf[0]); if (dfu_device_has_quirk (device, DFU_DEVICE_QUIRK_IGNORE_POLLTIMEOUT)) { priv->dnload_timeout = 5; } else { priv->dnload_timeout = buf[1] + (((guint32) buf[2]) << 8) + (((guint32) buf[3]) << 16); } g_debug ("refreshed status=%s and state=%s (dnload=%u)", dfu_status_to_string (priv->status), dfu_state_to_string (priv->state), priv->dnload_timeout); return TRUE; } static guint8 _g_usb_device_get_interface_for_class (GUsbDevice *dev, guint8 intf_class, GError **error) { g_autoptr(GPtrArray) intfs = NULL; intfs = g_usb_device_get_interfaces (dev, error); if (intfs == NULL) return 0xff; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index (intfs, i); if (g_usb_interface_get_class (intf) == intf_class) return g_usb_interface_get_number (intf); } return 0xff; } /** * dfu_device_detach: * @device: a #DfuDevice * @error: a #GError, or %NULL * * Detaches the device putting it into DFU-mode. * * Return value: %TRUE for success **/ gboolean dfu_device_detach (DfuDevice *device, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); const guint16 timeout_reset_ms = 1000; g_autoptr(GError) error_local = NULL; g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* already in DFU mode */ if (!dfu_device_is_runtime (device)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Already in DFU mode; state is %s", dfu_state_to_string (priv->state)); return FALSE; } /* no backing USB device */ if (usb_device == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to detach: no GUsbDevice for %s", dfu_device_get_platform_id (device)); return FALSE; } /* handle Jabra devices that need a magic HID packet */ if (priv->jabra_detach != NULL) { guint8 adr = 0x00; guint8 rep = 0x00; guint8 iface_hid; g_autofree guint8 *buf = g_malloc0 (33); g_autoptr(GError) error_jabra = NULL; /* parse string and create magic packet */ rep = dfu_utils_buffer_parse_uint8 (priv->jabra_detach + 0); adr = dfu_utils_buffer_parse_uint8 (priv->jabra_detach + 2); buf[0] = rep; buf[1] = adr; buf[2] = 0x00; buf[3] = 0x01; buf[4] = 0x85; buf[5] = 0x07; /* detach the HID interface from the kernel driver */ iface_hid = _g_usb_device_get_interface_for_class (usb_device, G_USB_DEVICE_CLASS_HID, &error_local); if (iface_hid == 0xff) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot find HID interface: %s", error_local->message); return FALSE; } g_debug ("claiming interface 0x%02x", iface_hid); if (!g_usb_device_claim_interface (usb_device, (gint) iface_hid, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot claim interface 0x%02x: %s", iface_hid, error_local->message); return FALSE; } /* send magic to device */ if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, 0x09, 0x0200 | rep, 0x0003, buf, 33, NULL, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, NULL, /* cancellable */ &error_jabra)) { g_debug ("whilst sending magic: %s, ignoring", error_jabra->message); } /* wait for device to re-appear */ fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_DEVICE_RESTART); if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* wait 10 seconds for DFU mode to settle */ g_debug ("waiting for Jabra device to settle..."); fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_DEVICE_BUSY); g_usleep (10 * G_USEC_PER_SEC); /* hacky workaround until Jabra has a plugin */ usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); } /* the device has no DFU runtime, so cheat */ if (priv->state == DFU_STATE_APP_IDLE && priv->quirks & DFU_DEVICE_QUIRK_NO_DFU_RUNTIME) return TRUE; /* ensure interface is claimed */ if (!dfu_device_ensure_interface (device, error)) return FALSE; /* inform UI there's going to be a detach:attach */ fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_DEVICE_RESTART); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, DFU_REQUEST_DETACH, timeout_reset_ms, priv->iface_number, NULL, 0, NULL, priv->timeout_ms, NULL, /* cancellable */ &error_local)) { /* some devices just reboot and stall the endpoint :/ */ if (g_error_matches (error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_SUPPORTED) || g_error_matches (error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { g_debug ("ignoring while detaching: %s", error_local->message); } else { /* refresh the error code */ dfu_device_error_fixup (device, &error_local); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot detach device: %s", error_local->message); return FALSE; } } /* do a host reset */ if ((priv->attributes & DFU_DEVICE_ATTRIBUTE_WILL_DETACH) == 0) { g_debug ("doing device reset as host will not self-reset"); if (!dfu_device_reset (device, error)) return FALSE; } /* success */ priv->force_version = 0x0; fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_IDLE); return TRUE; } /** * dfu_device_abort: * @device: a #DfuDevice * @error: a #GError, or %NULL * * Aborts any upload or download in progress. * * Return value: %TRUE for success **/ gboolean dfu_device_abort (DfuDevice *device, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_autoptr(GError) error_local = NULL; g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* no backing USB device */ if (usb_device == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to abort: no GUsbDevice for %s", dfu_device_get_platform_id (device)); return FALSE; } /* the device has no DFU runtime, so cheat */ if (priv->state == DFU_STATE_APP_IDLE && priv->quirks & DFU_DEVICE_QUIRK_NO_DFU_RUNTIME) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported as no DFU runtime"); return FALSE; } /* ensure interface is claimed */ if (!dfu_device_ensure_interface (device, error)) return FALSE; if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, DFU_REQUEST_ABORT, 0, priv->iface_number, NULL, 0, NULL, priv->timeout_ms, NULL, /* cancellable */ &error_local)) { /* refresh the error code */ dfu_device_error_fixup (device, &error_local); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot abort device: %s", error_local->message); return FALSE; } return TRUE; } /** * dfu_device_clear_status: * @device: a #DfuDevice * @error: a #GError, or %NULL * * Clears any error status on the DFU device. * * Return value: %TRUE for success **/ gboolean dfu_device_clear_status (DfuDevice *device, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_autoptr(GError) error_local = NULL; g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* no backing USB device */ if (usb_device == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to clear status: no GUsbDevice for %s", dfu_device_get_platform_id (device)); return FALSE; } /* the device has no DFU runtime, so cheat */ if (priv->state == DFU_STATE_APP_IDLE && priv->quirks & DFU_DEVICE_QUIRK_NO_DFU_RUNTIME) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported as no DFU runtime"); return FALSE; } /* ensure interface is claimed */ if (!dfu_device_ensure_interface (device, error)) return FALSE; if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, DFU_REQUEST_CLRSTATUS, 0, priv->iface_number, NULL, 0, NULL, priv->timeout_ms, NULL, /* cancellable */ &error_local)) { /* refresh the error code */ dfu_device_error_fixup (device, &error_local); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot clear status on the device: %s", error_local->message); return FALSE; } return TRUE; } /** * dfu_device_get_interface: * @device: a #DfuDevice * * Gets the interface number. **/ guint8 dfu_device_get_interface (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (DFU_IS_DEVICE (device), 0xff); return priv->iface_number; } /** * dfu_device_open: * @device: a #DfuDevice * @error: a #GError, or %NULL * * Opens a DFU-capable device. * * Return value: %TRUE for success **/ static gboolean dfu_device_open (FuUsbDevice *device, GError **error) { DfuDevice *self = DFU_DEVICE (device); DfuDevicePrivate *priv = GET_PRIVATE (self); GPtrArray *targets = dfu_device_get_targets (self); g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* the device has no DFU runtime, so cheat */ if (priv->state == DFU_STATE_APP_IDLE && priv->quirks & DFU_DEVICE_QUIRK_NO_DFU_RUNTIME) { priv->state = DFU_STATE_APP_IDLE; priv->status = DFU_STATUS_OK; } /* set up target ready for use */ for (guint j = 0; j < targets->len; j++) { DfuTarget *target = g_ptr_array_index (targets, j); if (!dfu_target_setup (target, error)) return FALSE; } /* success */ return TRUE; } /** * dfu_device_close: * @device: a #DfuDevice * @error: a #GError, or %NULL * * Closes a DFU device. * * Return value: %TRUE for success **/ static gboolean dfu_device_close (FuUsbDevice *device, GError **error) { DfuDevice *self = DFU_DEVICE (device); DfuDevicePrivate *priv = GET_PRIVATE (self); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); /* release interface */ if (priv->claimed_interface) { g_usb_device_release_interface (usb_device, (gint) priv->iface_number, 0, NULL); priv->claimed_interface = FALSE; } return TRUE; } static gboolean dfu_device_probe (FuUsbDevice *device, GError **error) { DfuDevice *self = DFU_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); /* add all the targets */ if (!dfu_device_add_targets (self, error)) { g_prefix_error (error, "%04x:%04x is not supported: ", g_usb_device_get_vid (usb_device), g_usb_device_get_pid (usb_device)); return FALSE; } /* check capabilities */ if (dfu_device_can_download (self)) { fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_remove_delay (FU_DEVICE (device), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } /* needs a manual action */ if (dfu_device_has_quirk (self, DFU_DEVICE_QUIRK_ACTION_REQUIRED)) { fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); } /* success */ return TRUE; } /** * dfu_device_wait_for_replug: * @device: a #DfuDevice * @timeout: the maximum amount of time to wait * @error: a #GError, or %NULL * * Waits for a DFU device to disconnect and reconnect. * This does rely on a #GUsbContext being set up before this is called. * * Return value: %TRUE for success **/ gboolean dfu_device_wait_for_replug (DfuDevice *device, guint timeout, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_autoptr(GUsbDevice) usb_device2 = NULL; /* close */ fu_device_close (FU_DEVICE (device), NULL); /* watch the device disappear and re-appear */ usb_device2 = g_usb_context_wait_for_replug (priv->usb_context, usb_device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error); if (usb_device2 == NULL) return FALSE; /* re-open with new device set */ fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_IDLE); fu_usb_device_set_dev (FU_USB_DEVICE (device), usb_device2); if (!fu_device_open (FU_DEVICE (device), error)) return FALSE; if (!dfu_device_refresh_and_clear (device, error)) return FALSE; /* success */ return TRUE; } /** * dfu_device_reset: * @device: a #DfuDevice * @error: a #GError, or %NULL * * Resets the USB device. * * Return value: %TRUE for success **/ gboolean dfu_device_reset (DfuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_autoptr(GError) error_local = NULL; g_autoptr(GTimer) timer = g_timer_new (); g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* no backing USB device */ if (usb_device == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to reset: no GUsbDevice for %s", dfu_device_get_platform_id (device)); return FALSE; } if (!g_usb_device_reset (usb_device, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot reset USB device: %s [%i]", error_local->message, error_local->code); return FALSE; } g_debug ("reset took %.2lfms", g_timer_elapsed (timer, NULL) * 1000); return TRUE; } /** * dfu_device_attach: * @device: a #DfuDevice * @error: a #GError, or %NULL * * Move device from DFU mode to runtime. * * Return value: %TRUE for success **/ gboolean dfu_device_attach (DfuDevice *device, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); g_autoptr(DfuTarget) target = NULL; g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* already in runtime mode */ if (dfu_device_is_runtime (device)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Already in application runtime mode"); return FALSE; } /* inform UI there's going to be a re-attach */ fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_DEVICE_RESTART); /* handle m-stack DFU bootloaders */ if (!priv->done_upload_or_download && (priv->quirks & DFU_DEVICE_QUIRK_ATTACH_UPLOAD_DOWNLOAD) > 0) { g_autoptr(GBytes) chunk = NULL; g_autoptr(DfuTarget) target_zero = NULL; g_debug ("doing dummy upload to work around m-stack quirk"); target_zero = dfu_device_get_target_by_alt_setting (device, 0, error); if (target_zero == NULL) return FALSE; chunk = dfu_target_upload_chunk (target_zero, 0, 0, error); if (chunk == NULL) return FALSE; } /* get default target */ target = dfu_device_get_target_by_alt_setting (device, 0, error); if (target == NULL) return FALSE; /* normal DFU mode just needs a bus reset */ if (!dfu_target_attach (target, error)) return FALSE; /* some devices need yet another reset */ if (dfu_device_has_quirk (device, DFU_DEVICE_QUIRK_ATTACH_EXTRA_RESET)) { if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; if (!dfu_device_reset (device, error)) return FALSE; } /* success */ priv->force_version = 0x0; fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_IDLE); return TRUE; } static void dfu_device_percentage_cb (DfuTarget *target, guint percentage, DfuDevice *device) { fu_device_set_progress (FU_DEVICE (device), percentage); } static void dfu_device_action_cb (DfuTarget *target, FwupdStatus action, DfuDevice *device) { fu_device_set_status (FU_DEVICE (device), action); } /** * dfu_device_upload: * @device: a #DfuDevice * @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY * @error: a #GError, or %NULL * * Uploads firmware from the target to the host. * * Return value: (transfer full): the uploaded firmware, or %NULL for error **/ DfuFirmware * dfu_device_upload (DfuDevice *device, DfuTargetTransferFlags flags, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_autoptr(DfuFirmware) firmware = NULL; /* no backing USB device */ if (usb_device == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to upload: no GUsbDevice for %s", dfu_device_get_platform_id (device)); return NULL; } /* ensure interface is claimed */ if (!dfu_device_ensure_interface (device, error)) return NULL; /* create ahead of time */ firmware = dfu_firmware_new (); dfu_firmware_set_vid (firmware, priv->runtime_vid); dfu_firmware_set_pid (firmware, priv->runtime_pid); dfu_firmware_set_release (firmware, 0xffff); /* upload from each target */ for (guint i = 0; i < priv->targets->len; i++) { DfuTarget *target; const gchar *alt_name; gulong id1; gulong id2; g_autoptr(DfuImage) image = NULL; /* upload to target and proxy signals */ target = g_ptr_array_index (priv->targets, i); /* ignore some target types */ alt_name = dfu_target_get_alt_name_for_display (target, NULL); if (g_strcmp0 (alt_name, "Option Bytes") == 0) { g_debug ("ignoring target %s", alt_name); continue; } id1 = g_signal_connect (target, "percentage-changed", G_CALLBACK (dfu_device_percentage_cb), device); id2 = g_signal_connect (target, "action-changed", G_CALLBACK (dfu_device_action_cb), device); image = dfu_target_upload (target, DFU_TARGET_TRANSFER_FLAG_NONE, error); g_signal_handler_disconnect (target, id1); g_signal_handler_disconnect (target, id2); if (image == NULL) return NULL; dfu_firmware_add_image (firmware, image); } /* do not do the dummy upload for quirked devices */ priv->done_upload_or_download = TRUE; /* choose the most appropriate type */ if (priv->targets->len > 1) { g_debug ("switching to DefuSe automatically"); dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_DFUSE); } else { dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_DFU); } /* success */ fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_IDLE); return g_object_ref (firmware); } static gboolean dfu_device_id_compatible (guint16 id_file, guint16 id_runtime, guint16 id_dev) { /* file doesn't specify */ if (id_file == 0xffff) return TRUE; /* runtime matches */ if (id_runtime != 0xffff && id_file == id_runtime) return TRUE; /* bootloader matches */ if (id_dev != 0xffff && id_file == id_dev) return TRUE; /* nothing */ return FALSE; } /** * dfu_device_download: * @device: a #DfuDevice * @firmware: a #DfuFirmware * @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY * @error: a #GError, or %NULL * * Downloads firmware from the host to the target, optionally verifying * the transfer. * * Return value: %TRUE for success **/ gboolean dfu_device_download (DfuDevice *device, DfuFirmware *firmware, DfuTargetTransferFlags flags, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); GPtrArray *images; GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); gboolean ret; /* no backing USB device */ if (usb_device == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to download: no GUsbDevice for %s", dfu_device_get_platform_id (device)); return FALSE; } /* ensure interface is claimed */ if (!dfu_device_ensure_interface (device, error)) return FALSE; /* do we allow wildcard VID:PID matches */ if ((flags & DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID) == 0) { if (dfu_firmware_get_vid (firmware) == 0xffff) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware vendor ID not specified"); return FALSE; } } if ((flags & DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID) == 0) { if (dfu_firmware_get_pid (firmware) == 0xffff) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware product ID not specified"); return FALSE; } } /* check vendor matches */ if (priv->runtime_vid != 0xffff) { if (!dfu_device_id_compatible (dfu_firmware_get_vid (firmware), priv->runtime_vid, dfu_device_get_vid (device))) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "vendor ID incorrect, expected 0x%04x " "got 0x%04x and 0x%04x\n", dfu_firmware_get_vid (firmware), priv->runtime_vid, dfu_device_get_vid (device)); return FALSE; } } /* check product matches */ if (priv->runtime_pid != 0xffff) { if (!dfu_device_id_compatible (dfu_firmware_get_pid (firmware), priv->runtime_pid, dfu_device_get_pid (device))) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "product ID incorrect, expected 0x%04x " "got 0x%04x and 0x%04x", dfu_firmware_get_pid (firmware), priv->runtime_pid, dfu_device_get_pid (device)); return FALSE; } } /* download each target */ images = dfu_firmware_get_images (firmware); if (images->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no images in firmware file"); return FALSE; } for (guint i = 0; i < images->len; i++) { DfuCipherKind cipher_fw; DfuCipherKind cipher_target; DfuImage *image; DfuTargetTransferFlags flags_local = DFU_TARGET_TRANSFER_FLAG_NONE; const gchar *alt_name; gulong id1; gulong id2; g_autoptr(DfuTarget) target_tmp = NULL; g_autoptr(GError) error_local = NULL; image = g_ptr_array_index (images, i); target_tmp = dfu_device_get_target_by_alt_setting (device, dfu_image_get_alt_setting (image), error); if (target_tmp == NULL) return FALSE; /* we don't actually need to print this, but it makes sure the * target is setup prior to doing the cipher checks */ alt_name = dfu_target_get_alt_name (target_tmp, &error_local); if (alt_name == NULL) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { alt_name = "unknown"; } else { g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } } g_debug ("downloading to target: %s", alt_name); /* check we're flashing a compatible firmware */ cipher_target = dfu_target_get_cipher_kind (target_tmp); cipher_fw = dfu_firmware_get_cipher_kind (firmware); if ((flags & DFU_TARGET_TRANSFER_FLAG_ANY_CIPHER) == 0) { if (cipher_fw != DFU_CIPHER_KIND_NONE && cipher_target == DFU_CIPHER_KIND_NONE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Device is only accepting " "unsigned firmware, not %s", dfu_cipher_kind_to_string (cipher_fw)); return FALSE; } if (cipher_fw == DFU_CIPHER_KIND_NONE && cipher_target != DFU_CIPHER_KIND_NONE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Device is only accepting " "firmware with %s cipher kind", dfu_cipher_kind_to_string (cipher_target)); return FALSE; } } /* download onto target */ if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY) flags_local = DFU_TARGET_TRANSFER_FLAG_VERIFY; if (dfu_firmware_get_format (firmware) == DFU_FIRMWARE_FORMAT_RAW) flags_local |= DFU_TARGET_TRANSFER_FLAG_ADDR_HEURISTIC; id1 = g_signal_connect (target_tmp, "percentage-changed", G_CALLBACK (dfu_device_percentage_cb), device); id2 = g_signal_connect (target_tmp, "action-changed", G_CALLBACK (dfu_device_action_cb), device); ret = dfu_target_download (target_tmp, image, flags_local, error); g_signal_handler_disconnect (target_tmp, id1); g_signal_handler_disconnect (target_tmp, id2); if (!ret) return FALSE; } /* do not do the dummy upload for quirked devices */ priv->done_upload_or_download = TRUE; /* success */ fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_IDLE); return TRUE; } void dfu_device_error_fixup (DfuDevice *device, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); /* sad panda */ if (error == NULL) return; /* not the right error to query */ if (!g_error_matches (*error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_SUPPORTED)) return; /* get the status */ if (!dfu_device_refresh (device, NULL)) return; /* not in an error state */ if (priv->state != DFU_STATE_DFU_ERROR) return; /* prefix the error */ switch (priv->status) { case DFU_STATUS_OK: /* ignore */ break; case DFU_STATUS_ERR_VENDOR: g_prefix_error (error, "read protection is active: "); break; default: g_prefix_error (error, "[%s,%s]: ", dfu_state_to_string (priv->state), dfu_status_to_string (priv->status)); break; } } /** * dfu_device_get_quirks_as_string: (skip) * @device: a #DfuDevice * * Gets a string describing the quirks set for a device. * * Return value: string, or %NULL for no quirks **/ gchar * dfu_device_get_quirks_as_string (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); GString *str; /* just append to a string */ str = g_string_new (""); if (priv->quirks & DFU_DEVICE_QUIRK_IGNORE_POLLTIMEOUT) g_string_append_printf (str, "ignore-polltimeout|"); if (priv->quirks & DFU_DEVICE_QUIRK_FORCE_DFU_MODE) g_string_append_printf (str, "force-dfu-mode|"); if (priv->quirks & DFU_DEVICE_QUIRK_NO_PID_CHANGE) g_string_append_printf (str, "no-pid-change|"); if (priv->quirks & DFU_DEVICE_QUIRK_NO_GET_STATUS_UPLOAD) g_string_append_printf (str, "no-get-status-upload|"); if (priv->quirks & DFU_DEVICE_QUIRK_NO_DFU_RUNTIME) g_string_append_printf (str, "no-dfu-runtime|"); if (priv->quirks & DFU_DEVICE_QUIRK_ATTACH_UPLOAD_DOWNLOAD) g_string_append_printf (str, "attach-upload-download|"); if (priv->quirks & DFU_DEVICE_QUIRK_IGNORE_RUNTIME) g_string_append_printf (str, "ignore-runtime|"); if (priv->quirks & DFU_DEVICE_QUIRK_ACTION_REQUIRED) g_string_append_printf (str, "action-required|"); if (priv->quirks & DFU_DEVICE_QUIRK_IGNORE_UPLOAD) g_string_append_printf (str, "ignore-upload|"); if (priv->quirks & DFU_DEVICE_QUIRK_ATTACH_EXTRA_RESET) g_string_append_printf (str, "attach-extra-reset|"); if (priv->quirks & DFU_DEVICE_QUIRK_USE_ANY_INTERFACE) g_string_append_printf (str, "use-any-interface|"); if (priv->quirks & DFU_DEVICE_QUIRK_LEGACY_PROTOCOL) g_string_append_printf (str, "legacy-protocol|"); /* a well behaved device */ if (str->len == 0) { g_string_free (str, TRUE); return NULL; } /* remove trailing pipe */ g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } static gboolean dfu_device_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { DfuDevice *self = DFU_DEVICE (device); DfuDevicePrivate *priv = GET_PRIVATE (self); if (g_strcmp0 (key, FU_QUIRKS_DFU_FLAGS) == 0) { dfu_device_set_quirks_from_string (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_DFU_JABRA_DETACH) == 0) { if (value != NULL && strlen (value) == 4) { priv->jabra_detach = g_strdup (value); return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unsupported jabra quirk format"); return FALSE; } if (g_strcmp0 (key, FU_QUIRKS_DFU_FORCE_VERSION) == 0) { if (value != NULL && strlen (value) == 4) { priv->force_version = dfu_utils_buffer_parse_uint16 (value); return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid DFU version"); return FALSE; } /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } /** * dfu_device_get_attributes_as_string: (skip) * @device: a #DfuDevice * * Gets a string describing the attributes for a device. * * Return value: a string, possibly empty **/ gchar * dfu_device_get_attributes_as_string (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); GString *str; /* just append to a string */ str = g_string_new (""); if (priv->attributes & DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD) g_string_append_printf (str, "can-download|"); if (priv->attributes & DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD) g_string_append_printf (str, "can-upload|"); if (priv->attributes & DFU_DEVICE_ATTRIBUTE_MANIFEST_TOL) g_string_append_printf (str, "manifest-tol|"); if (priv->attributes & DFU_DEVICE_ATTRIBUTE_WILL_DETACH) g_string_append_printf (str, "will-detach|"); if (priv->attributes & DFU_DEVICE_ATTRIBUTE_CAN_ACCELERATE) g_string_append_printf (str, "can-accelerate|"); /* remove trailing pipe */ g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } static void dfu_device_finalize (GObject *object) { DfuDevice *device = DFU_DEVICE (object); DfuDevicePrivate *priv = GET_PRIVATE (device); if (priv->usb_context != NULL) g_object_unref (priv->usb_context); g_free (priv->chip_id); g_free (priv->jabra_detach); g_ptr_array_unref (priv->targets); G_OBJECT_CLASS (dfu_device_parent_class)->finalize (object); } static void dfu_device_class_init (DfuDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->set_quirk_kv = dfu_device_set_quirk_kv; klass_usb_device->open = dfu_device_open; klass_usb_device->close = dfu_device_close; klass_usb_device->probe = dfu_device_probe; /** * DfuDevice::status-changed: * @device: the #DfuDevice instance that emitted the signal * @status: the new #DfuStatus * * The ::status-changed signal is emitted when the status changes. **/ signals [SIGNAL_STATUS_CHANGED] = g_signal_new ("status-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (DfuDeviceClass, status_changed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); /** * DfuDevice::state-changed: * @device: the #DfuDevice instance that emitted the signal * @state: the new #DfuState * * The ::state-changed signal is emitted when the state changes. **/ signals [SIGNAL_STATE_CHANGED] = g_signal_new ("state-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (DfuDeviceClass, state_changed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); object_class->finalize = dfu_device_finalize; } static void dfu_device_init (DfuDevice *device) { DfuDevicePrivate *priv = GET_PRIVATE (device); priv->iface_number = 0xff; priv->runtime_pid = 0xffff; priv->runtime_vid = 0xffff; priv->runtime_release = 0xffff; priv->state = DFU_STATE_APP_IDLE; priv->status = DFU_STATUS_OK; priv->targets = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->timeout_ms = 1500; priv->transfer_size = 64; } fwupd-1.2.14/plugins/dfu/dfu-device.h000066400000000000000000000141611402665037500173620ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include #include "fu-usb-device.h" #include "dfu-common.h" #include "dfu-target.h" #include "dfu-firmware.h" G_BEGIN_DECLS #define DFU_TYPE_DEVICE (dfu_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuDevice, dfu_device, DFU, DEVICE, FuUsbDevice) /** * DfuDeviceQuirks: * @DFU_DEVICE_QUIRK_NONE: No device quirks * @DFU_DEVICE_QUIRK_IGNORE_POLLTIMEOUT: Ignore the device download timeout * @DFU_DEVICE_QUIRK_FORCE_DFU_MODE: Force DFU mode * @DFU_DEVICE_QUIRK_USE_ANY_INTERFACE: Use any interface for DFU * @DFU_DEVICE_QUIRK_NO_PID_CHANGE: Accept the same VID:PID when changing modes * @DFU_DEVICE_QUIRK_NO_GET_STATUS_UPLOAD: Do not do GetStatus when uploading * @DFU_DEVICE_QUIRK_NO_DFU_RUNTIME: No DFU runtime interface is provided * @DFU_DEVICE_QUIRK_ATTACH_UPLOAD_DOWNLOAD: An upload or download is required for attach * @DFU_DEVICE_QUIRK_IGNORE_RUNTIME: Device has broken DFU runtime support * @DFU_DEVICE_QUIRK_ACTION_REQUIRED: User has to do something manually, e.g. press a button * @DFU_DEVICE_QUIRK_IGNORE_UPLOAD: Uploading from the device is broken * @DFU_DEVICE_QUIRK_ATTACH_EXTRA_RESET: Device needs resetting twice for attach * @DFU_DEVICE_QUIRK_LEGACY_PROTOCOL: Use a legacy protocol version * * The workarounds for different devices. **/ typedef enum { DFU_DEVICE_QUIRK_NONE = 0, DFU_DEVICE_QUIRK_IGNORE_POLLTIMEOUT = (1 << 0), DFU_DEVICE_QUIRK_FORCE_DFU_MODE = (1 << 1), DFU_DEVICE_QUIRK_USE_ANY_INTERFACE = (1 << 2), DFU_DEVICE_QUIRK_NO_PID_CHANGE = (1 << 4), DFU_DEVICE_QUIRK_NO_GET_STATUS_UPLOAD = (1 << 5), DFU_DEVICE_QUIRK_NO_DFU_RUNTIME = (1 << 6), DFU_DEVICE_QUIRK_ATTACH_UPLOAD_DOWNLOAD = (1 << 7), DFU_DEVICE_QUIRK_IGNORE_RUNTIME = (1 << 8), DFU_DEVICE_QUIRK_ACTION_REQUIRED = (1 << 9), DFU_DEVICE_QUIRK_IGNORE_UPLOAD = (1 << 10), DFU_DEVICE_QUIRK_ATTACH_EXTRA_RESET = (1 << 11), DFU_DEVICE_QUIRK_LEGACY_PROTOCOL = (1 << 12), /*< private >*/ DFU_DEVICE_QUIRK_LAST } DfuDeviceQuirks; /** * DfuDeviceAttributes: * @DFU_DEVICE_ATTRIBUTE_NONE: No attributes set * @DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD: Can download from host->device * @DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD: Can upload from device->host * @DFU_DEVICE_ATTRIBUTE_MANIFEST_TOL: Can answer GetStatus in manifest * @DFU_DEVICE_ATTRIBUTE_WILL_DETACH: Will self-detach * @DFU_DEVICE_ATTRIBUTE_CAN_ACCELERATE: Use a larger transfer size for speed * * The device DFU attributes. **/ typedef enum { DFU_DEVICE_ATTRIBUTE_NONE = 0, DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD = (1 << 0), DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD = (1 << 1), DFU_DEVICE_ATTRIBUTE_MANIFEST_TOL = (1 << 2), DFU_DEVICE_ATTRIBUTE_WILL_DETACH = (1 << 3), DFU_DEVICE_ATTRIBUTE_CAN_ACCELERATE = (1 << 7), /*< private >*/ DFU_DEVICE_ATTRIBUTE_LAST } DfuDeviceAttributes; struct _DfuDeviceClass { FuUsbDeviceClass parent_class; void (*status_changed) (DfuDevice *device, DfuStatus status); void (*state_changed) (DfuDevice *device, DfuState state); void (*percentage_changed) (DfuDevice *device, guint percentage); void (*action_changed) (DfuDevice *device, FwupdStatus action); }; DfuDevice *dfu_device_new (GUsbDevice *usb_device); const gchar *dfu_device_get_platform_id (DfuDevice *device); GPtrArray *dfu_device_get_targets (DfuDevice *device); DfuTarget *dfu_device_get_target_by_alt_setting (DfuDevice *device, guint8 alt_setting, GError **error); DfuTarget *dfu_device_get_target_by_alt_name (DfuDevice *device, const gchar *alt_name, GError **error); const gchar *dfu_device_get_chip_id (DfuDevice *device); void dfu_device_set_chip_id (DfuDevice *device, const gchar *chip_id); guint16 dfu_device_get_runtime_vid (DfuDevice *device); guint16 dfu_device_get_runtime_pid (DfuDevice *device); guint16 dfu_device_get_runtime_release (DfuDevice *device); guint16 dfu_device_get_vid (DfuDevice *device); guint16 dfu_device_get_pid (DfuDevice *device); guint16 dfu_device_get_release (DfuDevice *device); gboolean dfu_device_reset (DfuDevice *device, GError **error); gboolean dfu_device_attach (DfuDevice *device, GError **error); gboolean dfu_device_wait_for_replug (DfuDevice *device, guint timeout, GError **error); DfuFirmware *dfu_device_upload (DfuDevice *device, DfuTargetTransferFlags flags, GError **error); gboolean dfu_device_download (DfuDevice *device, DfuFirmware *firmware, DfuTargetTransferFlags flags, GError **error); gboolean dfu_device_refresh (DfuDevice *device, GError **error); gboolean dfu_device_refresh_and_clear (DfuDevice *device, GError **error); gboolean dfu_device_detach (DfuDevice *device, GError **error); gboolean dfu_device_abort (DfuDevice *device, GError **error); gboolean dfu_device_clear_status (DfuDevice *device, GError **error); guint8 dfu_device_get_interface (DfuDevice *device); gboolean dfu_device_is_runtime (DfuDevice *device); DfuState dfu_device_get_state (DfuDevice *device); DfuStatus dfu_device_get_status (DfuDevice *device); guint16 dfu_device_get_transfer_size (DfuDevice *device); guint16 dfu_device_get_version (DfuDevice *device); guint dfu_device_get_timeout (DfuDevice *device); gboolean dfu_device_can_upload (DfuDevice *device); gboolean dfu_device_can_download (DfuDevice *device); gboolean dfu_device_has_attribute (DfuDevice *device, DfuDeviceAttributes attribute); void dfu_device_remove_attribute (DfuDevice *device, DfuDeviceAttributes attribute); gboolean dfu_device_has_quirk (DfuDevice *device, DfuDeviceQuirks quirk); void dfu_device_set_transfer_size (DfuDevice *device, guint16 transfer_size); void dfu_device_set_timeout (DfuDevice *device, guint timeout_ms); void dfu_device_set_usb_context (DfuDevice *device, GUsbContext *quirks); GUsbContext *dfu_device_get_usb_context (DfuDevice *device); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-element.c000066400000000000000000000162171402665037500175530ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * SECTION:dfu-element * @short_description: Object representing a binary element * * This object represents an binary blob of data at a specific address. * * This allows relocatable data segments to be stored in different * locations on the device itself. * * See also: #DfuImage, #DfuFirmware */ #include "config.h" #include #include #include "dfu-common.h" #include "dfu-element.h" #include "fwupd-error.h" static void dfu_element_finalize (GObject *object); typedef struct { GBytes *contents; guint32 target_size; guint32 address; guint8 padding_value; } DfuElementPrivate; G_DEFINE_TYPE_WITH_PRIVATE (DfuElement, dfu_element, G_TYPE_OBJECT) #define GET_PRIVATE(o) (dfu_element_get_instance_private (o)) static void dfu_element_class_init (DfuElementClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = dfu_element_finalize; } static void dfu_element_init (DfuElement *element) { } static void dfu_element_finalize (GObject *object) { DfuElement *element = DFU_ELEMENT (object); DfuElementPrivate *priv = GET_PRIVATE (element); if (priv->contents != NULL) g_bytes_unref (priv->contents); G_OBJECT_CLASS (dfu_element_parent_class)->finalize (object); } /** * dfu_element_new: * * Creates a new DFU element object. * * Return value: a new #DfuElement **/ DfuElement * dfu_element_new (void) { DfuElement *element; element = g_object_new (DFU_TYPE_ELEMENT, NULL); return element; } /** * dfu_element_get_contents: * @element: a #DfuElement * * Gets the element data. * * Return value: (transfer none): element data **/ GBytes * dfu_element_get_contents (DfuElement *element) { DfuElementPrivate *priv = GET_PRIVATE (element); g_return_val_if_fail (DFU_IS_ELEMENT (element), NULL); return priv->contents; } /** * dfu_element_get_address: * @element: a #DfuElement * * Gets the offset address of the element. * * Return value: memory offset value, or 0x00 for unset **/ guint32 dfu_element_get_address (DfuElement *element) { DfuElementPrivate *priv = GET_PRIVATE (element); g_return_val_if_fail (DFU_IS_ELEMENT (element), 0x00); return priv->address; } /** * dfu_element_set_contents: * @element: a #DfuElement * @contents: element data * * Sets the element data. **/ void dfu_element_set_contents (DfuElement *element, GBytes *contents) { DfuElementPrivate *priv = GET_PRIVATE (element); g_return_if_fail (DFU_IS_ELEMENT (element)); g_return_if_fail (contents != NULL); if (priv->contents == contents) return; if (priv->contents != NULL) g_bytes_unref (priv->contents); priv->contents = g_bytes_ref (contents); } /** * dfu_element_set_address: * @element: a #DfuElement * @address: memory offset value * * Sets the offset address of the element. **/ void dfu_element_set_address (DfuElement *element, guint32 address) { DfuElementPrivate *priv = GET_PRIVATE (element); g_return_if_fail (DFU_IS_ELEMENT (element)); priv->address = address; } /** * dfu_element_to_string: * @element: a #DfuElement * * Returns a string representaiton of the object. * * Return value: NULL terminated string, or %NULL for invalid **/ gchar * dfu_element_to_string (DfuElement *element) { DfuElementPrivate *priv = GET_PRIVATE (element); GString *str; g_return_val_if_fail (DFU_IS_ELEMENT (element), NULL); str = g_string_new (""); g_string_append_printf (str, "address: 0x%02x\n", priv->address); if (priv->target_size > 0) { g_string_append_printf (str, "target: 0x%04x\n", priv->target_size); } if (priv->contents != NULL) { g_string_append_printf (str, "contents: 0x%04x\n", (guint32) g_bytes_get_size (priv->contents)); } g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } /** * dfu_element_set_padding_value: * @element: a #DfuElement * @padding_value: char value, typically 0x00 or 0xff * * Sets a the value of the padding byte to be used in the function * dfu_element_set_target_size(). **/ void dfu_element_set_padding_value (DfuElement *element, guint8 padding_value) { DfuElementPrivate *priv = GET_PRIVATE (element); g_return_if_fail (DFU_IS_ELEMENT (element)); priv->padding_value = padding_value; } /** * dfu_element_set_target_size: * @element: a #DfuElement * @target_size: size in bytes * * Sets a target size for the element. If the prepared element is smaller * than this then it will be padded up to the required size. * * If a padding byte other than 0x00 is required then the function * dfu_element_set_padding_value() should be used before this function is * called. **/ void dfu_element_set_target_size (DfuElement *element, guint32 target_size) { DfuElementPrivate *priv = GET_PRIVATE (element); const guint8 *data; gsize length; guint8 *buf; g_return_if_fail (DFU_IS_ELEMENT (element)); /* save for dump */ priv->target_size = target_size; /* no need to pad */ if (priv->contents == NULL) return; if (g_bytes_get_size (priv->contents) >= target_size) return; /* reallocate and pad */ data = g_bytes_get_data (priv->contents, &length); buf = g_malloc0 (target_size); g_assert (buf != NULL); memcpy (buf, data, length); /* set the padding value */ if (priv->padding_value != 0x00) { memset (buf + length, priv->padding_value, target_size - length); } /* replace */ g_bytes_unref (priv->contents); priv->contents = g_bytes_new_take (buf, target_size); } /** * dfu_element_get_contents_chunk: * @element: a #DfuElement * @address: an address greater than dfu_element_get_address() * @chunk_sz_max: the size of the new chunk * @error: a #GError, or %NULL * * Gets a block of data from the @element. If the contents of the element is * smaller than the requested chunk size then the #GBytes will be smaller * than @chunk_sz_max. Use dfu_utils_bytes_pad() if padding is required. * * If the @address is larger than the size of the @element then an error is returned. * * Return value: (transfer full): a #GBytes, or %NULL **/ GBytes * dfu_element_get_contents_chunk (DfuElement *element, guint32 address, guint32 chunk_sz_max, GError **error) { GBytes *blob; gsize chunk_left; guint32 offset; /* check address requested is larger than base address */ if (address < dfu_element_get_address (element)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "requested address 0x%x less than base address 0x%x", (guint) address, (guint) dfu_element_get_address (element)); return NULL; } /* offset into data */ offset = address - dfu_element_get_address (element); blob = dfu_element_get_contents (element); if (offset > g_bytes_get_size (blob)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "offset 0x%x larger than data size 0x%x", (guint) offset, (guint) g_bytes_get_size (blob)); return NULL; } /* if we have less data than requested */ chunk_left = g_bytes_get_size (blob) - offset; if (chunk_sz_max > chunk_left) return g_bytes_new_from_bytes (blob, offset, chunk_left); /* check chunk */ return g_bytes_new_from_bytes (blob, offset, chunk_sz_max); } fwupd-1.2.14/plugins/dfu/dfu-element.h000066400000000000000000000020571402665037500175550ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include G_BEGIN_DECLS #define DFU_TYPE_ELEMENT (dfu_element_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuElement, dfu_element, DFU, ELEMENT, GObject) struct _DfuElementClass { GObjectClass parent_class; }; DfuElement *dfu_element_new (void); GBytes *dfu_element_get_contents (DfuElement *element); guint32 dfu_element_get_address (DfuElement *element); GBytes *dfu_element_get_contents_chunk (DfuElement *element, guint32 address, guint32 chunk_sz_max, GError **error); void dfu_element_set_contents (DfuElement *element, GBytes *contents); void dfu_element_set_address (DfuElement *element, guint32 address); void dfu_element_set_target_size (DfuElement *element, guint32 target_size); void dfu_element_set_padding_value (DfuElement *element, guint8 padding_value); gchar *dfu_element_to_string (DfuElement *element); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-firmware.c000066400000000000000000000462621402665037500177410ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * SECTION:dfu-firmware * @short_description: Object representing a DFU or DfuSe firmware file * * This object allows reading and writing firmware files either in * raw, DFU or DfuSe formats. * * A #DfuFirmware can be made up of several #DfuImages, although * typically there is only one. * * See also: #DfuImage */ #include "config.h" #include #include #include "fu-common-version.h" #include "dfu-common.h" #include "dfu-firmware.h" #include "dfu-format-dfu.h" #include "dfu-format-ihex.h" #include "dfu-format-raw.h" #include "dfu-format-srec.h" #include "dfu-image.h" #include "fwupd-error.h" static void dfu_firmware_finalize (GObject *object); typedef struct { GHashTable *metadata; GPtrArray *images; guint16 vid; guint16 pid; guint16 release; DfuCipherKind cipher_kind; DfuFirmwareFormat format; } DfuFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE (DfuFirmware, dfu_firmware, G_TYPE_OBJECT) #define GET_PRIVATE(o) (dfu_firmware_get_instance_private (o)) static void dfu_firmware_class_init (DfuFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = dfu_firmware_finalize; } static void dfu_firmware_init (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); priv->vid = 0xffff; priv->pid = 0xffff; priv->release = 0xffff; priv->images = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } static void dfu_firmware_finalize (GObject *object) { DfuFirmware *firmware = DFU_FIRMWARE (object); DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_ptr_array_unref (priv->images); g_hash_table_destroy (priv->metadata); G_OBJECT_CLASS (dfu_firmware_parent_class)->finalize (object); } /** * dfu_firmware_new: * * Creates a new DFU firmware object. * * Return value: a new #DfuFirmware **/ DfuFirmware * dfu_firmware_new (void) { DfuFirmware *firmware; firmware = g_object_new (DFU_TYPE_FIRMWARE, NULL); return firmware; } /** * dfu_firmware_get_image: * @firmware: a #DfuFirmware * @alt_setting: an alternative setting, typically 0x00 * * Gets an image from the firmware file. * * Return value: (transfer none): a #DfuImage, or %NULL for not found **/ DfuImage * dfu_firmware_get_image (DfuFirmware *firmware, guint8 alt_setting) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL); /* find correct image */ for (guint i = 0; i < priv->images->len; i++) { DfuImage *im = g_ptr_array_index (priv->images, i); if (dfu_image_get_alt_setting (im) == alt_setting) return im; } return NULL; } /** * dfu_firmware_get_image_by_name: * @firmware: a #DfuFirmware * @name: an alternative setting name * * Gets an image from the firmware file. * * Return value: (transfer none): a #DfuImage, or %NULL for not found **/ DfuImage * dfu_firmware_get_image_by_name (DfuFirmware *firmware, const gchar *name) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL); /* find correct image */ for (guint i = 0; i < priv->images->len; i++) { DfuImage *im = g_ptr_array_index (priv->images, i); if (g_strcmp0 (dfu_image_get_name (im), name) == 0) return im; } return NULL; } /** * dfu_firmware_get_image_default: * @firmware: a #DfuFirmware * * Gets the default image from the firmware file. * * Return value: (transfer none): a #DfuImage, or %NULL for not found **/ DfuImage * dfu_firmware_get_image_default (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL); if (priv->images->len == 0) return NULL; return g_ptr_array_index (priv->images, 0); } /** * dfu_firmware_get_images: * @firmware: a #DfuFirmware * * Gets all the images contained in this firmware file. * * Return value: (transfer none) (element-type DfuImage): list of images **/ GPtrArray * dfu_firmware_get_images (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL); return priv->images; } /** * dfu_firmware_get_size: * @firmware: a #DfuFirmware * * Gets the size of all the images in the firmware. * * This only returns actual data that would be sent to the device and * does not include any padding. * * Return value: a integer value, or 0 if there are no images. **/ guint32 dfu_firmware_get_size (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); guint32 length = 0; g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0); for (guint i = 0; i < priv->images->len; i++) { DfuImage *image = g_ptr_array_index (priv->images, i); length += dfu_image_get_size (image); } return length; } /** * dfu_firmware_add_image: * @firmware: a #DfuFirmware * @image: a #DfuImage * * Adds an image to the list of images. **/ void dfu_firmware_add_image (DfuFirmware *firmware, DfuImage *image) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_if_fail (DFU_IS_FIRMWARE (firmware)); g_return_if_fail (DFU_IS_IMAGE (image)); g_ptr_array_add (priv->images, g_object_ref (image)); } /** * dfu_firmware_get_vid: * @firmware: a #DfuFirmware * * Gets the vendor ID. * * Return value: a vendor ID, or 0xffff for unset **/ guint16 dfu_firmware_get_vid (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0xffff); return priv->vid; } /** * dfu_firmware_get_pid: * @firmware: a #DfuFirmware * * Gets the product ID. * * Return value: a product ID, or 0xffff for unset **/ guint16 dfu_firmware_get_pid (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0xffff); return priv->pid; } /** * dfu_firmware_get_release: * @firmware: a #DfuFirmware * * Gets the device ID. * * Return value: a device ID, or 0xffff for unset **/ guint16 dfu_firmware_get_release (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0xffff); return priv->release; } /** * dfu_firmware_get_format: * @firmware: a #DfuFirmware * * Gets the DFU version. * * Return value: a version, or 0x0 for unset **/ guint16 dfu_firmware_get_format (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0xffff); return priv->format; } /** * dfu_firmware_set_vid: * @firmware: a #DfuFirmware * @vid: vendor ID, or 0xffff for unset * * Sets the vendor ID. **/ void dfu_firmware_set_vid (DfuFirmware *firmware, guint16 vid) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_if_fail (DFU_IS_FIRMWARE (firmware)); priv->vid = vid; } /** * dfu_firmware_set_pid: * @firmware: a #DfuFirmware * @pid: product ID, or 0xffff for unset * * Sets the product ID. **/ void dfu_firmware_set_pid (DfuFirmware *firmware, guint16 pid) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_if_fail (DFU_IS_FIRMWARE (firmware)); priv->pid = pid; } /** * dfu_firmware_set_release: * @firmware: a #DfuFirmware * @release: device ID, or 0xffff for unset * * Sets the device ID. **/ void dfu_firmware_set_release (DfuFirmware *firmware, guint16 release) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_if_fail (DFU_IS_FIRMWARE (firmware)); priv->release = release; } /** * dfu_firmware_set_format: * @firmware: a #DfuFirmware * @format: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_DFUSE * * Sets the DFU version in BCD format. **/ void dfu_firmware_set_format (DfuFirmware *firmware, DfuFirmwareFormat format) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_if_fail (DFU_IS_FIRMWARE (firmware)); priv->format = format; } /** * dfu_firmware_parse_data: * @firmware: a #DfuFirmware * @bytes: raw firmware data * @flags: optional flags, e.g. %DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST * @error: a #GError, or %NULL * * Parses firmware data which may have an optional DFU suffix. * * Return value: %TRUE for success **/ gboolean dfu_firmware_parse_data (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), FALSE); g_return_val_if_fail (bytes != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* set defaults */ priv->vid = 0xffff; priv->pid = 0xffff; priv->release = 0xffff; /* try to get format if not already set */ if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN) priv->format = dfu_firmware_detect_ihex (bytes); if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN) priv->format = dfu_firmware_detect_srec (bytes); if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN) priv->format = dfu_firmware_detect_dfu (bytes); if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN) priv->format = dfu_firmware_detect_raw (bytes); /* handled easily */ switch (priv->format) { case DFU_FIRMWARE_FORMAT_INTEL_HEX: if (!dfu_firmware_from_ihex (firmware, bytes, flags, error)) return FALSE; break; case DFU_FIRMWARE_FORMAT_SREC: if (!dfu_firmware_from_srec (firmware, bytes, flags, error)) return FALSE; break; case DFU_FIRMWARE_FORMAT_DFU: case DFU_FIRMWARE_FORMAT_DFUSE: if (!dfu_firmware_from_dfu (firmware, bytes, flags, error)) return FALSE; break; default: if (!dfu_firmware_from_raw (firmware, bytes, flags, error)) return FALSE; break; } return TRUE; } /** * dfu_firmware_parse_file: * @firmware: a #DfuFirmware * @file: a #GFile to load and parse * @flags: optional flags, e.g. %DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST * @error: a #GError, or %NULL * * Parses a DFU firmware, which may contain an optional footer. * * Return value: %TRUE for success **/ gboolean dfu_firmware_parse_file (DfuFirmware *firmware, GFile *file, DfuFirmwareParseFlags flags, GError **error) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); gchar *contents = NULL; gsize length = 0; g_autofree gchar *basename = NULL; g_autoptr(GBytes) bytes = NULL; g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), FALSE); g_return_val_if_fail (G_IS_FILE (file), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* guess cipher kind based on file extension */ basename = g_file_get_basename (file); if (g_str_has_suffix (basename, ".xdfu")) priv->cipher_kind = DFU_CIPHER_KIND_XTEA; if (!g_file_load_contents (file, NULL, &contents, &length, NULL, error)) return FALSE; bytes = g_bytes_new_take (contents, length); return dfu_firmware_parse_data (firmware, bytes, flags, error); } /** * dfu_firmware_get_metadata: * @firmware: a #DfuFirmware * @key: metadata string key * * Gets metadata from the store with a specific key. * * Return value: the metadata value, or %NULL for unset **/ const gchar * dfu_firmware_get_metadata (DfuFirmware *firmware, const gchar *key) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); return g_hash_table_lookup (priv->metadata, key); } /** * dfu_firmware_get_metadata_table: * @firmware: a #DfuFirmware * * Gets all metadata from the store. * * Return value: (transfer none): the metadata hash table **/ GHashTable * dfu_firmware_get_metadata_table (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); return priv->metadata; } /** * dfu_firmware_set_metadata: * @firmware: a #DfuFirmware * @key: metadata string key * @value: metadata string value * * Sets a metadata value with a specific key. **/ void dfu_firmware_set_metadata (DfuFirmware *firmware, const gchar *key, const gchar *value) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_debug ("adding metadata %s=%s", key, value); g_hash_table_insert (priv->metadata, g_strdup (key), g_strdup (value)); } /** * dfu_firmware_remove_metadata: * @firmware: a #DfuFirmware * @key: metadata string key * * Removes a metadata item from the store **/ void dfu_firmware_remove_metadata (DfuFirmware *firmware, const gchar *key) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_debug ("removing metadata %s", key); g_hash_table_remove (priv->metadata, key); } static gboolean dfu_firmware_check_acceptable_for_format (DfuFirmware *firmware, GError **error) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); /* always okay */ if (priv->images->len <= 1) return TRUE; if (priv->format == DFU_FIRMWARE_FORMAT_DFUSE) return TRUE; /* one is usual, and 2 is okay if one image is the signature */ if (priv->format == DFU_FIRMWARE_FORMAT_INTEL_HEX) { if (priv->images->len == 2 && dfu_firmware_get_image_by_name (firmware, "signature") != NULL) return TRUE; } /* unsupported */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "multiple images (%u) not supported for %s", priv->images->len, dfu_firmware_format_to_string (priv->format)); return TRUE; } /** * dfu_firmware_write_data: * @firmware: a #DfuFirmware * @error: a #GError, or %NULL * * Writes DFU data to a data blob with a DFU-specific footer. * * Return value: (transfer none): firmware data **/ GBytes * dfu_firmware_write_data (DfuFirmware *firmware, GError **error) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* at least one image */ if (priv->images == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no image data to write"); return NULL; } /* does the format support this many images */ if (!dfu_firmware_check_acceptable_for_format (firmware, error)) return NULL; /* raw */ if (priv->format == DFU_FIRMWARE_FORMAT_RAW) return dfu_firmware_to_raw (firmware, error); /* DFU or DfuSe*/ if (priv->format == DFU_FIRMWARE_FORMAT_DFU || priv->format == DFU_FIRMWARE_FORMAT_DFUSE) return dfu_firmware_to_dfu (firmware, error); /* Intel HEX */ if (priv->format == DFU_FIRMWARE_FORMAT_INTEL_HEX) return dfu_firmware_to_ihex (firmware, error); /* Motorola S-record */ if (priv->format == DFU_FIRMWARE_FORMAT_SREC) return dfu_firmware_to_srec (firmware, error); /* invalid */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid format for write (0x%04x)", priv->format); return NULL; } /** * dfu_firmware_write_file: * @firmware: a #DfuFirmware * @file: a #GFile * @error: a #GError, or %NULL * * Writes a DFU firmware with the optional footer. * * Return value: %TRUE for success **/ gboolean dfu_firmware_write_file (DfuFirmware *firmware, GFile *file, GError **error) { const guint8 *data; gsize length = 0; g_autoptr(GBytes) bytes = NULL; g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), FALSE); g_return_val_if_fail (G_IS_FILE (file), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* get blob */ bytes = dfu_firmware_write_data (firmware, error); if (bytes == NULL) return FALSE; /* save to firmware */ data = g_bytes_get_data (bytes, &length); return g_file_replace_contents (file, (const gchar *) data, length, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, /* cancellable */ error); } /** * dfu_firmware_to_string: * @firmware: a #DfuFirmware * * Returns a string representaiton of the object. * * Return value: NULL terminated string, or %NULL for invalid **/ gchar * dfu_firmware_to_string (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); DfuImage *image; GString *str; g_autofree gchar *release_str = NULL; g_autoptr(GList) keys = NULL; g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL); release_str = fu_common_version_from_uint16 (priv->release, FWUPD_VERSION_FORMAT_BCD); str = g_string_new (""); g_string_append_printf (str, "vid: 0x%04x\n", priv->vid); g_string_append_printf (str, "pid: 0x%04x\n", priv->pid); g_string_append_printf (str, "release: 0x%04x [%s]\n", priv->release, release_str); g_string_append_printf (str, "format: %s [0x%04x]\n", dfu_firmware_format_to_string (priv->format), priv->format); g_string_append_printf (str, "cipher: %s\n", dfu_cipher_kind_to_string (priv->cipher_kind)); /* print metadata */ keys = g_hash_table_get_keys (priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value; value = g_hash_table_lookup (priv->metadata, key); g_string_append_printf (str, "metadata: %s=%s\n", key, value); } /* print images */ for (guint i = 0; i < priv->images->len; i++) { g_autofree gchar *tmp = NULL; image = g_ptr_array_index (priv->images, i); tmp = dfu_image_to_string (image); g_string_append_printf (str, "= IMAGE %u =\n", i); g_string_append_printf (str, "%s\n", tmp); } g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } /** * dfu_firmware_format_to_string: * @format: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_DFU * * Returns a string representaiton of the format. * * Return value: NULL terminated string, or %NULL for invalid **/ const gchar * dfu_firmware_format_to_string (DfuFirmwareFormat format) { if (format == DFU_FIRMWARE_FORMAT_RAW) return "raw"; if (format == DFU_FIRMWARE_FORMAT_DFU) return "dfu"; if (format == DFU_FIRMWARE_FORMAT_DFUSE) return "dfuse"; if (format == DFU_FIRMWARE_FORMAT_INTEL_HEX) return "ihex"; if (format == DFU_FIRMWARE_FORMAT_SREC) return "srec"; return NULL; } /** * dfu_firmware_format_from_string: * @format: a format string, e.g. `dfuse` * * Returns an enumerated version of the format. * * Return value: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_DFUSE **/ DfuFirmwareFormat dfu_firmware_format_from_string (const gchar *format) { if (g_strcmp0 (format, "raw") == 0) return DFU_FIRMWARE_FORMAT_RAW; if (g_strcmp0 (format, "dfu") == 0) return DFU_FIRMWARE_FORMAT_DFU; if (g_strcmp0 (format, "dfuse") == 0) return DFU_FIRMWARE_FORMAT_DFUSE; if (g_strcmp0 (format, "ihex") == 0) return DFU_FIRMWARE_FORMAT_INTEL_HEX; if (g_strcmp0 (format, "srec") == 0) return DFU_FIRMWARE_FORMAT_SREC; return DFU_FIRMWARE_FORMAT_UNKNOWN; } /** * dfu_firmware_get_cipher_kind: * @firmware: a #DfuFirmware * * Returns the kind of cipher used by the firmware file. * * NOTE: this value is based on a heuristic, and may not be accurate. * The value %DFU_CIPHER_KIND_NONE will be returned when the cipher * is not recognised. * * Return value: NULL terminated string, or %NULL for invalid **/ DfuCipherKind dfu_firmware_get_cipher_kind (DfuFirmware *firmware) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0); return priv->cipher_kind; } /** * dfu_firmware_set_cipher_kind: * @firmware: a #DfuFirmware * @cipher_kind: a #DfuCipherKind, e.g. %DFU_CIPHER_KIND_XTEA * * Sets the kind of cipher used by the firmware file. **/ void dfu_firmware_set_cipher_kind (DfuFirmware *firmware, DfuCipherKind cipher_kind) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_return_if_fail (DFU_IS_FIRMWARE (firmware)); priv->cipher_kind = cipher_kind; } fwupd-1.2.14/plugins/dfu/dfu-firmware.h000066400000000000000000000075061402665037500177440ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-common.h" #include "dfu-image.h" G_BEGIN_DECLS #define DFU_TYPE_FIRMWARE (dfu_firmware_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuFirmware, dfu_firmware, DFU, FIRMWARE, GObject) struct _DfuFirmwareClass { GObjectClass parent_class; }; /** * DfuFirmwareParseFlags: * @DFU_FIRMWARE_PARSE_FLAG_NONE: No flags set * @DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST: Do not verify the CRC * @DFU_FIRMWARE_PARSE_FLAG_NO_VERSION_TEST: Do not verify the DFU version * @DFU_FIRMWARE_PARSE_FLAG_NO_METADATA: Do not read the metadata table * * The optional flags used for parsing. **/ typedef enum { DFU_FIRMWARE_PARSE_FLAG_NONE = 0, DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST = (1 << 0), DFU_FIRMWARE_PARSE_FLAG_NO_VERSION_TEST = (1 << 1), DFU_FIRMWARE_PARSE_FLAG_NO_METADATA = (1 << 2), /*< private >*/ DFU_FIRMWARE_PARSE_FLAG_LAST } DfuFirmwareParseFlags; /** * DfuFirmwareFormat: * @DFU_FIRMWARE_FORMAT_UNKNOWN: Format unknown * @DFU_FIRMWARE_FORMAT_RAW: Raw format * @DFU_FIRMWARE_FORMAT_DFU: DFU footer * @DFU_FIRMWARE_FORMAT_DFUSE: DfuSe header * @DFU_FIRMWARE_FORMAT_INTEL_HEX: Intel HEX * @DFU_FIRMWARE_FORMAT_SREC: Motorola S-record * * The known versions of the DFU standard in BCD format. **/ typedef enum { DFU_FIRMWARE_FORMAT_UNKNOWN, DFU_FIRMWARE_FORMAT_RAW, DFU_FIRMWARE_FORMAT_DFU, DFU_FIRMWARE_FORMAT_DFUSE, DFU_FIRMWARE_FORMAT_INTEL_HEX, DFU_FIRMWARE_FORMAT_SREC, /*< private >*/ DFU_FIRMWARE_FORMAT_LAST } DfuFirmwareFormat; DfuFirmware *dfu_firmware_new (void); const gchar *dfu_firmware_format_to_string (DfuFirmwareFormat format); DfuFirmwareFormat dfu_firmware_format_from_string(const gchar *format); DfuImage *dfu_firmware_get_image (DfuFirmware *firmware, guint8 alt_setting); DfuImage *dfu_firmware_get_image_by_name (DfuFirmware *firmware, const gchar *name); DfuImage *dfu_firmware_get_image_default (DfuFirmware *firmware); GPtrArray *dfu_firmware_get_images (DfuFirmware *firmware); guint16 dfu_firmware_get_vid (DfuFirmware *firmware); guint16 dfu_firmware_get_pid (DfuFirmware *firmware); guint16 dfu_firmware_get_release (DfuFirmware *firmware); guint16 dfu_firmware_get_format (DfuFirmware *firmware); guint32 dfu_firmware_get_size (DfuFirmware *firmware); DfuCipherKind dfu_firmware_get_cipher_kind (DfuFirmware *firmware); void dfu_firmware_add_image (DfuFirmware *firmware, DfuImage *image); void dfu_firmware_set_vid (DfuFirmware *firmware, guint16 vid); void dfu_firmware_set_pid (DfuFirmware *firmware, guint16 pid); void dfu_firmware_set_release (DfuFirmware *firmware, guint16 release); void dfu_firmware_set_format (DfuFirmware *firmware, DfuFirmwareFormat format); void dfu_firmware_set_cipher_kind (DfuFirmware *firmware, DfuCipherKind cipher_kind); gboolean dfu_firmware_parse_data (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error); gboolean dfu_firmware_parse_file (DfuFirmware *firmware, GFile *file, DfuFirmwareParseFlags flags, GError **error); GBytes *dfu_firmware_write_data (DfuFirmware *firmware, GError **error); gboolean dfu_firmware_write_file (DfuFirmware *firmware, GFile *file, GError **error); gchar *dfu_firmware_to_string (DfuFirmware *firmware); GHashTable *dfu_firmware_get_metadata_table(DfuFirmware *firmware); const gchar *dfu_firmware_get_metadata (DfuFirmware *firmware, const gchar *key); void dfu_firmware_set_metadata (DfuFirmware *firmware, const gchar *key, const gchar *value); void dfu_firmware_remove_metadata (DfuFirmware *firmware, const gchar *key); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-format-dfu.c000066400000000000000000000250761402665037500201710ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "dfu-element.h" #include "dfu-format-dfu.h" #include "dfu-format-metadata.h" #include "dfu-format-dfuse.h" #include "dfu-format-raw.h" #include "dfu-image.h" #include "fwupd-error.h" typedef struct __attribute__((packed)) { guint16 release; guint16 pid; guint16 vid; guint16 ver; guint8 sig[3]; guint8 len; guint32 crc; } DfuFirmwareFooter; /** * dfu_firmware_detect_dfu: (skip) * @bytes: data to parse * * Attempts to sniff the data and work out the firmware format * * Returns: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_RAW **/ DfuFirmwareFormat dfu_firmware_detect_dfu (GBytes *bytes) { DfuFirmwareFooter *ftr; guint8 *data; gsize len; /* check data size */ data = (guint8 *) g_bytes_get_data (bytes, &len); if (len < 16) return DFU_FIRMWARE_FORMAT_UNKNOWN; /* check for DFU signature */ ftr = (DfuFirmwareFooter *) &data[len - sizeof(DfuFirmwareFooter)]; if (memcmp (ftr->sig, "UFD", 3) != 0) return DFU_FIRMWARE_FORMAT_UNKNOWN; /* check versions */ switch (GUINT16_FROM_LE (ftr->ver)) { case DFU_VERSION_DFU_1_0: case DFU_VERSION_DFU_1_1: return DFU_FIRMWARE_FORMAT_DFU; case DFU_VERSION_DFUSE: return DFU_FIRMWARE_FORMAT_DFUSE; default: break; } return DFU_FIRMWARE_FORMAT_UNKNOWN; } static guint32 _crctbl[] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; static guint32 dfu_firmware_generate_crc32 (const guint8 *data, gsize length) { guint32 accum = 0xffffffff; for (guint i = 0; i < length; i++) accum = _crctbl[(accum^data[i]) & 0xff] ^ (accum >> 8); return accum; } /** * dfu_firmware_from_dfu: (skip) * @firmware: a #DfuFirmware * @bytes: data to parse * @flags: some #DfuFirmwareParseFlags * @error: a #GError, or %NULL * * Unpacks into a firmware object from dfu data. * * Returns: %TRUE for success **/ gboolean dfu_firmware_from_dfu (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error) { DfuFirmwareFooter *ftr; const gchar *cipher_str; gsize len; guint32 crc; guint32 crc_new; guint8 *data; g_autoptr(GBytes) contents = NULL; /* check data size */ data = (guint8 *) g_bytes_get_data (bytes, &len); if (len < 16) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "size check failed, too small"); return FALSE; } /* check for DFU signature */ ftr = (DfuFirmwareFooter *) &data[len - sizeof(DfuFirmwareFooter)]; if (memcmp (ftr->sig, "UFD", 3) != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no DFU signature"); return FALSE; } /* check version */ if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_VERSION_TEST) == 0) { if (dfu_firmware_get_format (firmware) != DFU_FIRMWARE_FORMAT_DFU && dfu_firmware_get_format (firmware) != DFU_FIRMWARE_FORMAT_DFUSE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "version check failed, got %04x", dfu_firmware_get_format (firmware)); return FALSE; } } /* verify the checksum */ crc = GUINT32_FROM_LE (ftr->crc); if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST) == 0) { crc_new = dfu_firmware_generate_crc32 (data, len - 4); if (crc != crc_new) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "CRC failed, expected %04x, got %04x", crc_new, GUINT32_FROM_LE (ftr->crc)); return FALSE; } } /* set from footer */ dfu_firmware_set_vid (firmware, GUINT16_FROM_LE (ftr->vid)); dfu_firmware_set_pid (firmware, GUINT16_FROM_LE (ftr->pid)); dfu_firmware_set_release (firmware, GUINT16_FROM_LE (ftr->release)); /* check reported length */ if (ftr->len > len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "reported firmware size %04x larger than file %04x", (guint) ftr->len, (guint) len); return FALSE; } /* parse the optional metadata segment */ if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_METADATA) == 0) { gsize offset = len - ftr->len; g_autoptr(GBytes) md = g_bytes_new (&data[offset], ftr->len); if (!dfu_firmware_from_metadata (firmware, md, flags, error)) return FALSE; } /* set this automatically */ cipher_str = dfu_firmware_get_metadata (firmware, DFU_METADATA_KEY_CIPHER_KIND); if (cipher_str != NULL) { if (g_strcmp0 (cipher_str, "XTEA") == 0) dfu_firmware_set_cipher_kind (firmware, DFU_CIPHER_KIND_XTEA); else g_warning ("Unknown CipherKind: %s", cipher_str); } /* parse DfuSe prefix */ contents = g_bytes_new_from_bytes (bytes, 0, len - ftr->len); if (dfu_firmware_get_format (firmware) == DFU_FIRMWARE_FORMAT_DFUSE) return dfu_firmware_from_dfuse (firmware, contents, flags, error); /* just copy old-plain DFU file */ return dfu_firmware_from_raw (firmware, contents, flags, error); } static DfuVersion dfu_convert_version (DfuFirmwareFormat format) { if (format == DFU_FIRMWARE_FORMAT_DFU) return DFU_VERSION_DFU_1_0; if (format == DFU_FIRMWARE_FORMAT_DFUSE) return DFU_VERSION_DFUSE; return DFU_VERSION_UNKNOWN; } static GBytes * dfu_firmware_add_footer (DfuFirmware *firmware, GBytes *contents, GError **error) { DfuFirmwareFooter *ftr; const guint8 *data_bin; const guint8 *data_md; gsize length_bin = 0; gsize length_md = 0; guint32 crc_new; guint8 *buf; g_autoptr(GBytes) metadata_table = NULL; /* get any file metadata */ metadata_table = dfu_firmware_to_metadata (firmware, error); if (metadata_table == NULL) return NULL; data_md = g_bytes_get_data (metadata_table, &length_md); /* add the raw firmware data */ data_bin = g_bytes_get_data (contents, &length_bin); buf = g_malloc0 (length_bin + length_md + 0x10); memcpy (buf + 0, data_bin, length_bin); /* add the metadata table */ memcpy (buf + length_bin, data_md, length_md); /* set up LE footer */ ftr = (DfuFirmwareFooter *) (buf + length_bin + length_md); ftr->release = GUINT16_TO_LE (dfu_firmware_get_release (firmware)); ftr->pid = GUINT16_TO_LE (dfu_firmware_get_pid (firmware)); ftr->vid = GUINT16_TO_LE (dfu_firmware_get_vid (firmware)); ftr->ver = GUINT16_TO_LE (dfu_convert_version (dfu_firmware_get_format (firmware))); ftr->len = (guint8) (sizeof (DfuFirmwareFooter) + length_md); memcpy(ftr->sig, "UFD", 3); crc_new = dfu_firmware_generate_crc32 (buf, length_bin + length_md + 12); ftr->crc = GUINT32_TO_LE (crc_new); /* return all data */ return g_bytes_new_take (buf, length_bin + length_md + 0x10); } /** * dfu_firmware_to_dfu: (skip) * @firmware: a #DfuFirmware * @error: a #GError, or %NULL * * Packs dfu firmware * * Returns: (transfer full): the packed data **/ GBytes * dfu_firmware_to_dfu (DfuFirmware *firmware, GError **error) { /* plain DFU */ if (dfu_firmware_get_format (firmware) == DFU_FIRMWARE_FORMAT_DFU) { GBytes *contents; DfuElement *element; DfuImage *image; image = dfu_firmware_get_image_default (firmware); g_assert (image != NULL); element = dfu_image_get_element (image, 0); if (element == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no firmware element data to write"); return NULL; } contents = dfu_element_get_contents (element); return dfu_firmware_add_footer (firmware, contents, error); } /* DfuSe */ if (dfu_firmware_get_format (firmware) == DFU_FIRMWARE_FORMAT_DFUSE) { g_autoptr(GBytes) contents = NULL; contents = dfu_firmware_to_dfuse (firmware, error); if (contents == NULL) return NULL; return dfu_firmware_add_footer (firmware, contents, error); } g_assert_not_reached (); return NULL; } fwupd-1.2.14/plugins/dfu/dfu-format-dfu.h000066400000000000000000000010001402665037500201530ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-firmware.h" G_BEGIN_DECLS DfuFirmwareFormat dfu_firmware_detect_dfu (GBytes *bytes); GBytes *dfu_firmware_to_dfu (DfuFirmware *firmware, GError **error); gboolean dfu_firmware_from_dfu (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-format-dfuse.c000066400000000000000000000231131402665037500205070ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "dfu-element.h" #include "dfu-format-dfuse.h" #include "dfu-image.h" #include "fwupd-error.h" /* DfuSe element header */ typedef struct __attribute__((packed)) { guint32 address; guint32 size; } DfuSeElementPrefix; /** * dfu_element_from_dfuse: (skip) * @data: data buffer * @length: length of @data we can access * @consumed: (out): the number of bytes we consued * @error: a #GError, or %NULL * * Unpacks an element from DfuSe data. * * Returns: a #DfuElement, or %NULL for error **/ static DfuElement * dfu_element_from_dfuse (const guint8 *data, guint32 length, guint32 *consumed, GError **error) { DfuElement *element = NULL; DfuSeElementPrefix *el = (DfuSeElementPrefix *) data; guint32 size; g_autoptr(GBytes) contents = NULL; g_assert_cmpint(sizeof(DfuSeElementPrefix), ==, 8); /* check input buffer size */ if (length < sizeof(DfuSeElementPrefix)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid element data size %u", (guint32) length); return NULL; } /* check size */ size = GUINT32_FROM_LE (el->size); if (size + sizeof(DfuSeElementPrefix) > length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid element size %u, only %u bytes left", size, (guint32) (length - sizeof(DfuSeElementPrefix))); return NULL; } /* create new element */ element = dfu_element_new (); dfu_element_set_address (element, GUINT32_FROM_LE (el->address)); contents = g_bytes_new (data + sizeof(DfuSeElementPrefix), size); dfu_element_set_contents (element, contents); /* return size */ if (consumed != NULL) *consumed = (guint32) sizeof(DfuSeElementPrefix) + size; return element; } /** * dfu_element_to_dfuse: (skip) * @element: a #DfuElement * * Packs a DfuSe element. * * Returns: (transfer full): the packed data **/ static GBytes * dfu_element_to_dfuse (DfuElement *element) { DfuSeElementPrefix *el; const guint8 *data; gsize length; guint8 *buf; data = g_bytes_get_data (dfu_element_get_contents (element), &length); buf = g_malloc0 (length + sizeof (DfuSeElementPrefix)); el = (DfuSeElementPrefix *) buf; el->address = GUINT32_TO_LE (dfu_element_get_address (element)); el->size = GUINT32_TO_LE (length); memcpy (buf + sizeof (DfuSeElementPrefix), data, length); return g_bytes_new_take (buf, length + sizeof (DfuSeElementPrefix)); } /* DfuSe image header */ typedef struct __attribute__((packed)) { guint8 sig[6]; guint8 alt_setting; guint32 target_named; gchar target_name[255]; guint32 target_size; guint32 elements; } DfuSeImagePrefix; /** * dfu_image_from_dfuse: (skip) * @data: data buffer * @length: length of @data we can access * @consumed: (out): the number of bytes we consued * @error: a #GError, or %NULL * * Unpacks an image from DfuSe data. * * Returns: a #DfuImage, or %NULL for error **/ static DfuImage * dfu_image_from_dfuse (const guint8 *data, guint32 length, guint32 *consumed, GError **error) { DfuSeImagePrefix *im; guint32 elements; guint32 offset = sizeof(DfuSeImagePrefix); g_autoptr(DfuImage) image = NULL; g_assert_cmpint(sizeof(DfuSeImagePrefix), ==, 274); /* check input buffer size */ if (length < sizeof(DfuSeImagePrefix)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid image data size %u", (guint32) length); return NULL; } /* verify image signature */ im = (DfuSeImagePrefix *) data; if (memcmp (im->sig, "Target", 6) != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid DfuSe target signature"); return NULL; } /* create new image */ image = dfu_image_new (); dfu_image_set_alt_setting (image, im->alt_setting); if (GUINT32_FROM_LE (im->target_named) == 0x01) dfu_image_set_name (image, im->target_name); /* parse elements */ length -= offset; elements = GUINT32_FROM_LE (im->elements); for (guint j = 0; j < elements; j++) { guint32 consumed_local; g_autoptr(DfuElement) element = NULL; element = dfu_element_from_dfuse (data + offset, length, &consumed_local, error); if (element == NULL) return NULL; dfu_image_add_element (image, element); offset += consumed_local; length -= consumed_local; } /* return size */ if (consumed != NULL) *consumed = offset; return g_object_ref (image); } /** * dfu_image_to_dfuse: (skip) * @image: a #DfuImage * * Packs a DfuSe image * * Returns: (transfer full): the packed data **/ static GBytes * dfu_image_to_dfuse (DfuImage *image) { DfuSeImagePrefix *im; GPtrArray *elements; guint32 length_total = 0; guint32 offset = sizeof (DfuSeImagePrefix); guint8 *buf; g_autoptr(GPtrArray) element_array = NULL; /* get total size */ element_array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); elements = dfu_image_get_elements (image); for (guint i = 0; i < elements->len; i++) { DfuElement *element = g_ptr_array_index (elements, i); GBytes *bytes = dfu_element_to_dfuse (element); g_ptr_array_add (element_array, bytes); length_total += (guint32) g_bytes_get_size (bytes); } /* add prefix */ buf = g_malloc0 (length_total + sizeof (DfuSeImagePrefix)); im = (DfuSeImagePrefix *) buf; memcpy (im->sig, "Target", 6); im->alt_setting = dfu_image_get_alt_setting (image); if (dfu_image_get_name (image) != NULL) { im->target_named = GUINT32_TO_LE (0x01); memcpy (im->target_name, dfu_image_get_name (image), 255); } im->target_size = GUINT32_TO_LE (length_total); im->elements = GUINT32_TO_LE (elements->len); /* copy data */ for (guint i = 0; i < element_array->len; i++) { gsize length; GBytes *bytes = g_ptr_array_index (element_array, i); const guint8 *data = g_bytes_get_data (bytes, &length); memcpy (buf + offset, data, length); offset += (guint32) length; } return g_bytes_new_take (buf, length_total + sizeof (DfuSeImagePrefix)); } /* DfuSe header */ typedef struct __attribute__((packed)) { guint8 sig[5]; guint8 ver; guint32 image_size; guint8 targets; } DfuSePrefix; /** * dfu_firmware_to_dfuse: (skip) * @firmware: a #DfuFirmware * @error: a #GError, or %NULL * * Packs a DfuSe firmware * * Returns: (transfer full): the packed data **/ GBytes * dfu_firmware_to_dfuse (DfuFirmware *firmware, GError **error) { DfuSePrefix *prefix; GPtrArray *images; guint32 image_size_total = 0; guint32 offset = sizeof (DfuSePrefix); g_autofree guint8 *buf = NULL; g_autoptr(GPtrArray) dfuse_images = NULL; /* get all the image data */ dfuse_images = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); images = dfu_firmware_get_images (firmware); for (guint i = 0; i < images->len; i++) { DfuImage *im = g_ptr_array_index (images, i); GBytes *contents; contents = dfu_image_to_dfuse (im); image_size_total += (guint32) g_bytes_get_size (contents); g_ptr_array_add (dfuse_images, contents); } g_debug ("image_size_total: %" G_GUINT32_FORMAT, image_size_total); buf = g_malloc0 (sizeof (DfuSePrefix) + image_size_total); /* DfuSe header */ prefix = (DfuSePrefix *) buf; memcpy (prefix->sig, "DfuSe", 5); prefix->ver = 0x01; prefix->image_size = GUINT32_TO_LE (offset + image_size_total); if (images->len > G_MAXUINT8) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "too many (%u) images to write DfuSe file", images->len); return NULL; } prefix->targets = (guint8) images->len; /* copy images */ for (guint i = 0; i < dfuse_images->len; i++) { GBytes *contents = g_ptr_array_index (dfuse_images, i); gsize length; const guint8 *data; data = g_bytes_get_data (contents, &length); memcpy (buf + offset, data, length); offset += (guint32) length; } /* return blob */ return g_bytes_new (buf, sizeof (DfuSePrefix) + image_size_total); } /** * dfu_firmware_from_dfuse: (skip) * @firmware: a #DfuFirmware * @bytes: data to parse * @flags: some #DfuFirmwareParseFlags * @error: a #GError, or %NULL * * Unpacks into a firmware object from DfuSe data. * * Returns: %TRUE for success **/ gboolean dfu_firmware_from_dfuse (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error) { DfuSePrefix *prefix; gsize len; guint32 offset = sizeof(DfuSePrefix); guint8 *data; /* check the prefix (BE) */ data = (guint8 *) g_bytes_get_data (bytes, &len); prefix = (DfuSePrefix *) data; if (memcmp (prefix->sig, "DfuSe", 5) != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid DfuSe prefix"); return FALSE; } /* check the version */ if (prefix->ver != 0x01) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid DfuSe version, got %02x", prefix->ver); return FALSE; } /* check image size */ if (GUINT32_FROM_LE (prefix->image_size) != len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid DfuSe image size, " "got %" G_GUINT32_FORMAT ", " "expected %" G_GSIZE_FORMAT, GUINT32_FROM_LE (prefix->image_size), len); return FALSE; } /* parse the image targets */ len -= sizeof(DfuSePrefix); for (guint i = 0; i < prefix->targets; i++) { guint consumed; g_autoptr(DfuImage) image = NULL; image = dfu_image_from_dfuse (data + offset, (guint32) len, &consumed, error); if (image == NULL) return FALSE; dfu_firmware_add_image (firmware, image); offset += consumed; len -= consumed; } return TRUE; } fwupd-1.2.14/plugins/dfu/dfu-format-dfuse.h000066400000000000000000000010111402665037500205050ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-firmware.h" G_BEGIN_DECLS DfuFirmwareFormat dfu_firmware_detect_dfuse (GBytes *bytes); GBytes *dfu_firmware_to_dfuse (DfuFirmware *firmware, GError **error); gboolean dfu_firmware_from_dfuse (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-format-ihex.c000066400000000000000000000275541402665037500203530ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "dfu-element.h" #include "dfu-firmware.h" #include "dfu-format-ihex.h" #include "dfu-image.h" #include "fwupd-error.h" /** * dfu_firmware_detect_ihex: (skip) * @bytes: data to parse * * Attempts to sniff the data and work out the firmware format * * Returns: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_RAW **/ DfuFirmwareFormat dfu_firmware_detect_ihex (GBytes *bytes) { guint8 *data; gsize len; data = (guint8 *) g_bytes_get_data (bytes, &len); if (len < 12) return DFU_FIRMWARE_FORMAT_UNKNOWN; /* match the first char */ if (data[0] == ':') return DFU_FIRMWARE_FORMAT_INTEL_HEX; /* look for the EOF line */ if (g_strstr_len ((const gchar *) data, (gssize) len, ":000000") != NULL) return DFU_FIRMWARE_FORMAT_INTEL_HEX; /* failed */ return DFU_FIRMWARE_FORMAT_UNKNOWN; } #define DFU_INHX32_RECORD_TYPE_DATA 0x00 #define DFU_INHX32_RECORD_TYPE_EOF 0x01 #define DFU_INHX32_RECORD_TYPE_EXTENDED_SEGMENT 0x02 #define DFU_INHX32_RECORD_TYPE_START_SEGMENT 0x03 #define DFU_INHX32_RECORD_TYPE_EXTENDED_LINEAR 0x04 #define DFU_INHX32_RECORD_TYPE_START_LINEAR 0x05 #define DFU_INHX32_RECORD_TYPE_SIGNATURE 0xfd static const gchar * dfu_firmware_ihex_record_type_to_string (guint8 record_type) { if (record_type == DFU_INHX32_RECORD_TYPE_DATA) return "DATA"; if (record_type == DFU_INHX32_RECORD_TYPE_EOF) return "EOF"; if (record_type == DFU_INHX32_RECORD_TYPE_EXTENDED_SEGMENT) return "EXTENDED_SEGMENT"; if (record_type == DFU_INHX32_RECORD_TYPE_START_SEGMENT) return "START_SEGMENT"; if (record_type == DFU_INHX32_RECORD_TYPE_EXTENDED_LINEAR) return "EXTENDED_LINEAR"; if (record_type == DFU_INHX32_RECORD_TYPE_START_LINEAR) return "ADDR32"; if (record_type == DFU_INHX32_RECORD_TYPE_SIGNATURE) return "SIGNATURE"; return NULL; } /** * dfu_firmware_from_ihex: (skip) * @firmware: a #DfuFirmware * @bytes: data to parse * @flags: some #DfuFirmwareParseFlags * @error: a #GError, or %NULL * * Unpacks into a firmware object from raw data. * * Returns: %TRUE for success **/ gboolean dfu_firmware_from_ihex (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error) { const gchar *data; gboolean got_eof = FALSE; gsize sz = 0; guint32 abs_addr = 0x0; guint32 addr_last = 0x0; guint32 base_addr = G_MAXUINT32; guint32 seg_addr = 0x0; g_auto(GStrv) lines = NULL; g_autoptr(DfuElement) element = NULL; g_autoptr(DfuImage) image = NULL; g_autoptr(GBytes) contents = NULL; g_autoptr(GString) buf = g_string_new (NULL); g_autoptr(GString) buf_signature = g_string_new (NULL); g_return_val_if_fail (bytes != NULL, FALSE); /* create element */ image = dfu_image_new (); dfu_image_set_name (image, "ihex"); element = dfu_element_new (); /* parse records */ data = g_bytes_get_data (bytes, &sz); lines = dfu_utils_strnsplit (data, sz, "\n", -1); for (guint ln = 0; lines[ln] != NULL; ln++) { const gchar *line = lines[ln]; gsize linesz; guint32 addr; guint8 byte_cnt; guint8 record_type; guint line_end; /* ignore comments */ if (g_str_has_prefix (line, ";")) continue; /* ignore blank lines */ g_strdelimit (lines[ln], "\r\x1a", '\0'); linesz = strlen (line); if (linesz == 0) continue; /* check starting token */ if (line[0] != ':') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid starting token on line %u: %s", ln + 1, line); return FALSE; } /* check there's enough data for the smallest possible record */ if (linesz < 11) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "line %u is incomplete, length %u", ln + 1, (guint) linesz); return FALSE; } /* length, 16-bit address, type */ byte_cnt = dfu_utils_buffer_parse_uint8 (line + 1); addr = dfu_utils_buffer_parse_uint16 (line + 3); record_type = dfu_utils_buffer_parse_uint8 (line + 7); g_debug ("%s:", dfu_firmware_ihex_record_type_to_string (record_type)); g_debug (" addr_start:\t0x%04x", addr); g_debug (" length:\t0x%02x", byte_cnt); addr += seg_addr; addr += abs_addr; g_debug (" addr:\t0x%08x", addr); /* position of checksum */ line_end = 9 + byte_cnt * 2; if (line_end > (guint) linesz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "line %u malformed, length: %u", ln + 1, line_end); return FALSE; } /* verify checksum */ if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST) == 0) { guint8 checksum = 0; for (guint i = 1; i < line_end + 2; i += 2) { guint8 data_tmp = dfu_utils_buffer_parse_uint8 (line + i); checksum += data_tmp; } if (checksum != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "line %u has invalid checksum (0x%02x)", ln + 1, checksum); return FALSE; } } /* process different record types */ switch (record_type) { case DFU_INHX32_RECORD_TYPE_DATA: /* base address for element */ if (base_addr == G_MAXUINT32) base_addr = addr; /* does not make sense */ if (addr < addr_last) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid address 0x%x, last was 0x%x", (guint) addr, (guint) addr_last); return FALSE; } /* parse bytes from line */ g_debug ("writing data 0x%08x", (guint32) addr); for (guint i = 9; i < line_end; i += 2) { /* any holes in the hex record */ guint32 len_hole = addr - addr_last; guint8 data_tmp; if (addr_last > 0 && len_hole > 0x100000) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "hole of 0x%x bytes too large to fill", (guint) len_hole); return FALSE; } if (addr_last > 0x0 && len_hole > 1) { g_debug ("filling address 0x%08x to 0x%08x", addr_last + 1, addr_last + len_hole - 1); for (guint j = 1; j < len_hole; j++) { /* although 0xff might be clearer, * we can't write 0xffff to pic14 */ g_string_append_c (buf, 0x00); } } /* write into buf */ data_tmp = dfu_utils_buffer_parse_uint8 (line + i); g_string_append_c (buf, (gchar) data_tmp); addr_last = addr++; } break; case DFU_INHX32_RECORD_TYPE_EOF: if (got_eof) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "duplicate EOF, perhaps " "corrupt file"); return FALSE; } got_eof = TRUE; break; case DFU_INHX32_RECORD_TYPE_EXTENDED_LINEAR: abs_addr = dfu_utils_buffer_parse_uint16 (line + 9) << 16; g_debug (" abs_addr:\t0x%02x", abs_addr); break; case DFU_INHX32_RECORD_TYPE_START_LINEAR: abs_addr = dfu_utils_buffer_parse_uint32 (line + 9); g_debug (" abs_addr:\t0x%08x", abs_addr); break; case DFU_INHX32_RECORD_TYPE_EXTENDED_SEGMENT: /* segment base address, so ~1Mb addressable */ seg_addr = dfu_utils_buffer_parse_uint16 (line + 9) * 16; g_debug (" seg_addr:\t0x%08x", seg_addr); break; case DFU_INHX32_RECORD_TYPE_START_SEGMENT: /* initial content of the CS:IP registers */ seg_addr = dfu_utils_buffer_parse_uint32 (line + 9); g_debug (" seg_addr:\t0x%02x", seg_addr); break; case DFU_INHX32_RECORD_TYPE_SIGNATURE: for (guint i = 9; i < line_end; i += 2) { guint8 tmp_c = dfu_utils_buffer_parse_uint8 (line + i); g_string_append_c (buf_signature, tmp_c); } break; default: /* vendors sneak in nonstandard sections past the EOF */ if (got_eof) break; g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid ihex record type %i", record_type); return FALSE; } } /* no EOF */ if (!got_eof) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no EOF, perhaps truncated file"); return FALSE; } /* add single image */ contents = g_bytes_new (buf->str, buf->len); dfu_element_set_contents (element, contents); if (base_addr != G_MAXUINT32) dfu_element_set_address (element, base_addr); dfu_image_add_element (image, element); dfu_firmware_add_image (firmware, image); dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_INTEL_HEX); /* add optional signature */ if (buf_signature->len > 0) { g_autoptr(DfuElement) element_sig = dfu_element_new (); g_autoptr(DfuImage) image_sig = dfu_image_new (); g_autoptr(GBytes) data_sig = g_bytes_new_static (buf_signature->str, buf_signature->len); dfu_element_set_contents (element_sig, data_sig); dfu_image_add_element (image_sig, element_sig); dfu_image_set_name (image_sig, "signature"); dfu_firmware_add_image (firmware, image_sig); } return TRUE; } static void dfu_firmware_ihex_emit_chunk (GString *str, guint16 address, guint8 record_type, const guint8 *data, gsize sz) { guint8 checksum = 0x00; g_string_append_printf (str, ":%02X%04X%02X", (guint) sz, (guint) address, (guint) record_type); for (gsize j = 0; j < sz; j++) g_string_append_printf (str, "%02X", data[j]); checksum = (guint8) sz; checksum += (guint8) ((address & 0xff00) >> 8); checksum += (guint8) (address & 0xff); checksum += record_type; for (gsize j = 0; j < sz; j++) checksum += data[j]; g_string_append_printf (str, "%02X\n", (guint) (((~checksum) + 0x01) & 0xff)); } static void dfu_firmware_to_ihex_bytes (GString *str, guint8 record_type, guint32 address, GBytes *contents) { const guint8 *data; const guint chunk_size = 16; gsize len; guint32 address_offset_last = 0x0; /* get number of chunks */ data = g_bytes_get_data (contents, &len); for (gsize i = 0; i < len; i += chunk_size) { guint32 address_tmp = address + i; guint32 address_offset = (address_tmp >> 16) & 0xffff; gsize chunk_len = MIN (len - i, 16); /* need to offset */ if (address_offset != address_offset_last) { guint8 buf[2]; fu_common_write_uint16 (buf, address_offset, G_BIG_ENDIAN); dfu_firmware_ihex_emit_chunk (str, 0x0, DFU_INHX32_RECORD_TYPE_EXTENDED_LINEAR, buf, 2); address_offset_last = address_offset; } address_tmp &= 0xffff; dfu_firmware_ihex_emit_chunk (str, address_tmp, record_type, data + i, chunk_len); } } static gboolean dfu_firmware_to_ihex_element (DfuElement *element, GString *str, guint8 record_type, GError **error) { GBytes *contents = dfu_element_get_contents (element); dfu_firmware_to_ihex_bytes (str, record_type, dfu_element_get_address (element), contents); return TRUE; } static gboolean dfu_firmware_to_ihex_image (DfuImage *image, GString *str, GError **error) { GPtrArray *elements; guint8 record_type = DFU_INHX32_RECORD_TYPE_DATA; if (g_strcmp0 (dfu_image_get_name (image), "signature") == 0) record_type = DFU_INHX32_RECORD_TYPE_SIGNATURE; elements = dfu_image_get_elements (image); for (guint i = 0; i < elements->len; i++) { DfuElement *element = g_ptr_array_index (elements, i); if (!dfu_firmware_to_ihex_element (element, str, record_type, error)) return FALSE; } return TRUE; } /** * dfu_firmware_to_ihex: (skip) * @firmware: a #DfuFirmware * @error: a #GError, or %NULL * * Packs a IHEX firmware * * Returns: (transfer full): the packed data **/ GBytes * dfu_firmware_to_ihex (DfuFirmware *firmware, GError **error) { GPtrArray *images; g_autoptr(GString) str = NULL; /* write all the element data */ str = g_string_new (""); images = dfu_firmware_get_images (firmware); for (guint i = 0; i < images->len; i++) { DfuImage *image = g_ptr_array_index (images, i); if (!dfu_firmware_to_ihex_image (image, str, error)) return NULL; } /* add EOF */ dfu_firmware_ihex_emit_chunk (str, 0x0, DFU_INHX32_RECORD_TYPE_EOF, NULL, 0); return g_bytes_new (str->str, str->len); } fwupd-1.2.14/plugins/dfu/dfu-format-ihex.h000066400000000000000000000010031402665037500203350ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-firmware.h" G_BEGIN_DECLS DfuFirmwareFormat dfu_firmware_detect_ihex (GBytes *bytes); GBytes *dfu_firmware_to_ihex (DfuFirmware *firmware, GError **error); gboolean dfu_firmware_from_ihex (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-format-metadata.c000066400000000000000000000111021402665037500211540ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "dfu-element.h" #include "dfu-format-metadata.h" #include "dfu-image.h" #include "fwupd-error.h" /** * dfu_firmware_from_metadata: (skip) * @firmware: a #DfuFirmware * @bytes: data to parse * @flags: some #DfuFirmwareParseFlags * @error: a #GError, or %NULL * * Unpacks into a firmware object from metadata data. * * The representation in memory is as follows: * * uint16 signature='MD' * uint8 number_of_keys * uint8 number_of_keys * uint8 key(n)_length * ... key(n) (no NUL) * uint8 value(n)_length * ... value(n) (no NUL) * * Returns: %TRUE for success **/ gboolean dfu_firmware_from_metadata (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error) { const guint8 *data; gsize data_length; guint idx = 2; guint kvlen; guint number_keys; /* not big enough */ data = g_bytes_get_data (bytes, &data_length); if (data_length <= 0x10) return TRUE; /* signature invalid */ if (memcmp (data, "MD", 2) != 0) return TRUE; /* parse key=value store */ number_keys = data[idx++]; for (guint i = 0; i < number_keys; i++) { g_autofree gchar *key = NULL; g_autofree gchar *value = NULL; /* parse key */ kvlen = data[idx++]; if (kvlen > 233) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "metadata table corrupt, key=%u", kvlen); return FALSE; } if (idx + kvlen + 0x10 > data_length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "metadata table corrupt, k-kvlen=%u", kvlen); return FALSE; } key = g_strndup ((const gchar *) data + idx, kvlen); idx += kvlen; /* parse value */ kvlen = data[idx++]; if (kvlen > 233) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "metadata table corrupt, value=%u", kvlen); return FALSE; } if (idx + kvlen + 0x10 > data_length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "metadata table corrupt, v-kvlen=%u", kvlen); return FALSE; } value = g_strndup ((const gchar *) data + idx, kvlen); idx += kvlen; dfu_firmware_set_metadata (firmware, key, value); } return TRUE; } /** * dfu_firmware_to_metadata: (skip) * @firmware: a #DfuFirmware * @error: a #GError, or %NULL * * Packs metadata firmware * * Returns: (transfer full): the packed data **/ GBytes * dfu_firmware_to_metadata (DfuFirmware *firmware, GError **error) { GHashTable *metadata; guint8 mdbuf[239]; guint idx = 0; guint number_keys; g_autoptr(GList) keys = NULL; /* no metadata */ metadata = dfu_firmware_get_metadata_table (firmware); if (g_hash_table_size (metadata) == 0) return g_bytes_new (NULL, 0); /* check the number of keys */ keys = g_hash_table_get_keys (metadata); number_keys = g_list_length (keys); if (number_keys > 59) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "too many metadata keys (%u)", number_keys); return NULL; } /* write the signature */ mdbuf[idx++] = 'M'; mdbuf[idx++] = 'D'; mdbuf[idx++] = (guint8) number_keys; for (GList *l = keys; l != NULL; l = l->next) { const gchar *key; const gchar *value; guint key_len; guint value_len; /* check key and value length */ key = l->data; key_len = (guint) strlen (key); if (key_len > 233) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "metadata key too long: %s", key); return NULL; } value = g_hash_table_lookup (metadata, key); value_len = (guint) strlen (value); if (value_len > 233) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "value too long: %s", value); return NULL; } /* do we still have space? */ if (idx + key_len + value_len + 2 > sizeof(mdbuf)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not enough space in metadata table, " "already used %u bytes", idx); return NULL; } /* write the key */ mdbuf[idx++] = (guint8) key_len; memcpy(mdbuf + idx, key, key_len); idx += key_len; /* write the value */ mdbuf[idx++] = (guint8) value_len; memcpy(mdbuf + idx, value, value_len); idx += value_len; } g_debug ("metadata table was %u/%" G_GSIZE_FORMAT " bytes", idx, sizeof(mdbuf)); return g_bytes_new (mdbuf, idx); } fwupd-1.2.14/plugins/dfu/dfu-format-metadata.h000066400000000000000000000007171402665037500211730ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-firmware.h" G_BEGIN_DECLS GBytes *dfu_firmware_to_metadata (DfuFirmware *firmware, GError **error); gboolean dfu_firmware_from_metadata (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-format-raw.c000066400000000000000000000040051402665037500201710ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "dfu-element.h" #include "dfu-format-raw.h" #include "dfu-image.h" #include "fwupd-error.h" /** * dfu_firmware_detect_raw: (skip) * @bytes: data to parse * * Attempts to sniff the data and work out the firmware format * * Returns: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_RAW **/ DfuFirmwareFormat dfu_firmware_detect_raw (GBytes *bytes) { return DFU_FIRMWARE_FORMAT_RAW; } /** * dfu_firmware_from_raw: (skip) * @firmware: a #DfuFirmware * @bytes: data to parse * @flags: some #DfuFirmwareParseFlags * @error: a #GError, or %NULL * * Unpacks into a firmware object from raw data. * * Returns: %TRUE for success **/ gboolean dfu_firmware_from_raw (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error) { g_autoptr(DfuElement) element = NULL; g_autoptr(DfuImage) image = NULL; image = dfu_image_new (); element = dfu_element_new (); dfu_element_set_contents (element, bytes); dfu_image_add_element (image, element); dfu_firmware_add_image (firmware, image); return TRUE; } /** * dfu_firmware_to_raw: (skip) * @firmware: a #DfuFirmware * @error: a #GError, or %NULL * * Packs raw firmware * * Returns: (transfer full): the packed data **/ GBytes * dfu_firmware_to_raw (DfuFirmware *firmware, GError **error) { DfuElement *element; DfuImage *image; GBytes *contents; image = dfu_firmware_get_image_default (firmware); if (image == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no firmware image data to write"); return NULL; } element = dfu_image_get_element (image, 0); if (element == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no firmware element data to write"); return NULL; } contents = dfu_element_get_contents (element); return g_bytes_ref (contents); } fwupd-1.2.14/plugins/dfu/dfu-format-raw.h000066400000000000000000000010001402665037500201660ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-firmware.h" G_BEGIN_DECLS DfuFirmwareFormat dfu_firmware_detect_raw (GBytes *bytes); GBytes *dfu_firmware_to_raw (DfuFirmware *firmware, GError **error); gboolean dfu_firmware_from_raw (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-format-srec.c000066400000000000000000000212701402665037500203370ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "dfu-element.h" #include "dfu-firmware.h" #include "dfu-format-srec.h" #include "dfu-image.h" #include "fwupd-error.h" /** * dfu_firmware_detect_srec: (skip) * @bytes: data to parse * * Attempts to sniff the data and work out the firmware format * * Returns: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_RAW **/ DfuFirmwareFormat dfu_firmware_detect_srec (GBytes *bytes) { guint8 *data; gsize len; data = (guint8 *) g_bytes_get_data (bytes, &len); if (len < 12) return DFU_FIRMWARE_FORMAT_UNKNOWN; if (memcmp (data, "S0", 2) != 0) return DFU_FIRMWARE_FORMAT_UNKNOWN; return DFU_FIRMWARE_FORMAT_SREC; } /** * dfu_firmware_from_srec: (skip) * @firmware: a #DfuFirmware * @bytes: data to parse * @flags: some #DfuFirmwareParseFlags * @error: a #GError, or %NULL * * Unpacks into a firmware object from raw data. * * Returns: %TRUE for success **/ gboolean dfu_image_from_srec (DfuImage *image, GBytes *bytes, guint32 start_addr, DfuFirmwareParseFlags flags, GError **error) { const gchar *data; gboolean got_eof = FALSE; gboolean got_hdr = FALSE; gsize sz = 0; guint16 data_cnt = 0; guint32 addr32_last = 0; guint32 element_address = 0; g_auto(GStrv) lines = NULL; g_autoptr(DfuElement) element = dfu_element_new (); g_autoptr(GBytes) contents = NULL; g_autoptr(GString) outbuf = g_string_new (NULL); g_return_val_if_fail (bytes != NULL, FALSE); /* parse records */ data = g_bytes_get_data (bytes, &sz); lines = dfu_utils_strnsplit (data, sz, "\n", -1); for (guint ln = 0; lines[ln] != NULL; ln++) { const gchar *line = lines[ln]; gsize linesz; guint32 rec_addr32; guint8 addrsz = 0; /* bytes */ guint8 rec_count; /* words */ guint8 rec_kind; /* ignore blank lines */ g_strdelimit (lines[ln], "\r", '\0'); linesz = strlen (line); if (linesz == 0) continue; /* check starting token */ if (line[0] != 'S') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid starting token, got '%c' at line %u", line[0], ln); return FALSE; } /* check there's enough data for the smallest possible record */ if (linesz < 10) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "record incomplete at line %u, length %u", ln, (guint) linesz); return FALSE; } /* kind, count, address, (data), checksum, linefeed */ rec_kind = line[1] - '0'; rec_count = dfu_utils_buffer_parse_uint8 (line + 2); if (rec_count * 2 != linesz - 4) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "count incomplete at line %u, " "length %u, expected %u", ln, (guint) linesz - 4, (guint) rec_count * 2); return FALSE; } /* checksum check */ if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST) == 0) { guint8 rec_csum = 0; guint8 rec_csum_expected; for (guint8 i = 0; i < rec_count; i++) rec_csum += dfu_utils_buffer_parse_uint8 (line + (i * 2) + 2); rec_csum ^= 0xff; rec_csum_expected = dfu_utils_buffer_parse_uint8 (line + (rec_count * 2) + 2); if (rec_csum != rec_csum_expected) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum incorrect line %u, " "expected %02x, got %02x", ln, rec_csum_expected, rec_csum); return FALSE; } } /* set each command settings */ switch (rec_kind) { case 0: addrsz = 2; if (got_hdr) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "duplicate header record"); return FALSE; } got_hdr = TRUE; break; case 1: addrsz = 2; break; case 2: addrsz = 3; break; case 3: addrsz = 4; break; case 5: addrsz = 2; got_eof = TRUE; break; case 6: addrsz = 3; break; case 7: addrsz = 4; got_eof = TRUE; break; case 8: addrsz = 3; got_eof = TRUE; break; case 9: addrsz = 2; got_eof = TRUE; break; default: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid srec record type S%c", line[1]); return FALSE; } /* parse address */ switch (addrsz) { case 2: rec_addr32 = dfu_utils_buffer_parse_uint16 (line + 4); break; case 3: rec_addr32 = dfu_utils_buffer_parse_uint24 (line + 4); break; case 4: rec_addr32 = dfu_utils_buffer_parse_uint32 (line + 4); break; default: g_assert_not_reached (); } /* header */ if (rec_kind == 0) { g_autoptr(GString) modname = g_string_new (NULL); if (rec_addr32 != 0x0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid header record address, got %04x", rec_addr32); return FALSE; } /* could be anything, lets assume text */ for (guint8 i = 4 + (addrsz * 2); i <= rec_count * 2; i += 2) { guint8 tmp = dfu_utils_buffer_parse_uint8 (line + i); if (!g_ascii_isgraph (tmp)) break; g_string_append_c (modname, tmp); } if (modname->len != 0) dfu_image_set_name (image, modname->str); continue; } /* verify we got all records */ if (rec_kind == 5) { if (rec_addr32 != data_cnt) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "count record was not valid, got 0x%02x expected 0x%02x", (guint) rec_addr32, (guint) data_cnt); return FALSE; } } /* data */ if (rec_kind == 1 || rec_kind == 2 || rec_kind == 3) { /* invalid */ if (!got_hdr) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "missing header record"); return FALSE; } /* does not make sense */ if (rec_addr32 < addr32_last) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid address 0x%x, last was 0x%x", (guint) rec_addr32, (guint) addr32_last); return FALSE; } if (rec_addr32 < start_addr) { g_debug ("ignoring data at 0x%x as before start address 0x%x", (guint) rec_addr32, (guint) start_addr); } else { guint bytecnt = 0; guint32 len_hole = rec_addr32 - addr32_last; /* fill any holes, but only up to 1Mb to avoid a DoS */ if (addr32_last > 0 && len_hole > 0x100000) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "hole of 0x%x bytes too large to fill", (guint) len_hole); return FALSE; } if (addr32_last > 0x0 && len_hole > 1) { g_debug ("filling address 0x%08x to 0x%08x", addr32_last + 1, addr32_last + len_hole - 1); for (guint j = 0; j < len_hole; j++) g_string_append_c (outbuf, 0xff); } /* add data */ for (guint8 i = 4 + (addrsz * 2); i <= rec_count * 2; i += 2) { guint8 tmp = dfu_utils_buffer_parse_uint8 (line + i); g_string_append_c (outbuf, tmp); bytecnt++; } if (element_address == 0x0) element_address = rec_addr32; addr32_last = rec_addr32 + bytecnt; } data_cnt++; } } /* no EOF */ if (!got_eof) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no EOF, perhaps truncated file"); return FALSE; } /* add single image */ contents = g_bytes_new (outbuf->str, outbuf->len); dfu_element_set_contents (element, contents); dfu_element_set_address (element, element_address); dfu_image_add_element (image, element); return TRUE; } /** * dfu_firmware_from_srec: (skip) * @firmware: a #DfuFirmware * @bytes: data to parse * @flags: some #DfuFirmwareParseFlags * @error: a #GError, or %NULL * * Unpacks into a firmware object from raw data. * * Returns: %TRUE for success **/ gboolean dfu_firmware_from_srec (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error) { g_autoptr(DfuImage) image = NULL; g_return_val_if_fail (bytes != NULL, FALSE); /* add single image */ image = dfu_image_new (); if (!dfu_image_from_srec (image, bytes, 0x0, flags, error)) return FALSE; dfu_firmware_add_image (firmware, image); dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_SREC); return TRUE; } /** * dfu_firmware_to_srec: (skip) * @firmware: a #DfuFirmware * @error: a #GError, or %NULL * * Exports a Motorola S-record file * * Returns: (transfer full): the packed data **/ GBytes * dfu_firmware_to_srec (DfuFirmware *firmware, GError **error) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Motorola S-record export functionality missing"); return NULL; } fwupd-1.2.14/plugins/dfu/dfu-format-srec.h000066400000000000000000000012551402665037500203450ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-firmware.h" G_BEGIN_DECLS DfuFirmwareFormat dfu_firmware_detect_srec (GBytes *bytes); GBytes *dfu_firmware_to_srec (DfuFirmware *firmware, GError **error); gboolean dfu_firmware_from_srec (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error); gboolean dfu_image_from_srec (DfuImage *image, GBytes *bytes, guint32 start_addr, DfuFirmwareParseFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-image.c000066400000000000000000000144601402665037500172020ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * SECTION:dfu-image * @short_description: Object representing a a firmware image * * A #DfuImage is typically made up of several #DfuElements, although * typically there will only be one. * * See also: #DfuElement */ #include "config.h" #include #include #include "dfu-common.h" #include "dfu-element.h" #include "dfu-image.h" static void dfu_image_finalize (GObject *object); typedef struct { GPtrArray *elements; gchar name[255]; guint8 alt_setting; } DfuImagePrivate; G_DEFINE_TYPE_WITH_PRIVATE (DfuImage, dfu_image, G_TYPE_OBJECT) #define GET_PRIVATE(o) (dfu_image_get_instance_private (o)) static void dfu_image_class_init (DfuImageClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = dfu_image_finalize; } static void dfu_image_init (DfuImage *image) { DfuImagePrivate *priv = GET_PRIVATE (image); priv->elements = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); memset (priv->name, 0x00, 255); } static void dfu_image_finalize (GObject *object) { DfuImage *image = DFU_IMAGE (object); DfuImagePrivate *priv = GET_PRIVATE (image); g_ptr_array_unref (priv->elements); G_OBJECT_CLASS (dfu_image_parent_class)->finalize (object); } /** * dfu_image_new: * * Creates a new DFU image object. * * Return value: a new #DfuImage **/ DfuImage * dfu_image_new (void) { DfuImage *image; image = g_object_new (DFU_TYPE_IMAGE, NULL); return image; } /** * dfu_image_get_elements: * @image: a #DfuImage * * Gets the element data. * * Return value: (transfer none) (element-type DfuElement): element data **/ GPtrArray * dfu_image_get_elements (DfuImage *image) { DfuImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (DFU_IS_IMAGE (image), NULL); return priv->elements; } /** * dfu_image_get_element: * @image: a #DfuImage * @idx: an array index * * Gets the element. * * Return value: (transfer none): element data, or %NULL for invalid **/ DfuElement * dfu_image_get_element (DfuImage *image, guint8 idx) { DfuImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (DFU_IS_IMAGE (image), NULL); if (idx >= priv->elements->len) return NULL; return g_ptr_array_index (priv->elements, idx); } /** * dfu_image_get_element_default: * @image: a #DfuImage * * Gets the default element. * * Return value: (transfer none): element data, or %NULL for invalid **/ DfuElement * dfu_image_get_element_default (DfuImage *image) { DfuImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (DFU_IS_IMAGE (image), NULL); if (priv->elements->len == 0) return NULL; return g_ptr_array_index (priv->elements, 0); } /** * dfu_image_get_alt_setting: * @image: a #DfuImage * * Gets the alternate setting. * * Return value: integer, or 0x00 for unset **/ guint8 dfu_image_get_alt_setting (DfuImage *image) { DfuImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (DFU_IS_IMAGE (image), 0xff); return priv->alt_setting; } /** * dfu_image_get_name: * @image: a #DfuImage * * Gets the target name. * * Return value: a string, or %NULL for unset **/ const gchar * dfu_image_get_name (DfuImage *image) { DfuImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (DFU_IS_IMAGE (image), NULL); return priv->name; } /** * dfu_image_get_size: * @image: a #DfuImage * * Gets the size of all the elements in the image. * * This only returns actual data that would be sent to the device and * does not include any padding. * * Return value: a integer value, or 0 if there are no elements. **/ guint32 dfu_image_get_size (DfuImage *image) { DfuImagePrivate *priv = GET_PRIVATE (image); guint32 length = 0; g_return_val_if_fail (DFU_IS_IMAGE (image), 0); for (guint i = 0; i < priv->elements->len; i++) { DfuElement *element = g_ptr_array_index (priv->elements, i); length += (guint32) g_bytes_get_size (dfu_element_get_contents (element)); } return length; } /** * dfu_image_add_element: * @image: a #DfuImage * @element: a #DfuElement * * Adds an element to the image. **/ void dfu_image_add_element (DfuImage *image, DfuElement *element) { DfuImagePrivate *priv = GET_PRIVATE (image); g_return_if_fail (DFU_IS_IMAGE (image)); g_return_if_fail (DFU_IS_ELEMENT (element)); g_ptr_array_add (priv->elements, g_object_ref (element)); } /** * dfu_image_set_alt_setting: * @image: a #DfuImage * @alt_setting: vendor ID, or 0xffff for unset * * Sets the vendor ID. **/ void dfu_image_set_alt_setting (DfuImage *image, guint8 alt_setting) { DfuImagePrivate *priv = GET_PRIVATE (image); g_return_if_fail (DFU_IS_IMAGE (image)); priv->alt_setting = alt_setting; } /** * dfu_image_set_name: * @image: a #DfuImage * @name: a target string, or %NULL * * Sets the target name. **/ void dfu_image_set_name (DfuImage *image, const gchar *name) { guint16 sz; DfuImagePrivate *priv = GET_PRIVATE (image); g_return_if_fail (DFU_IS_IMAGE (image)); /* this is a hard limit in DfuSe */ memset (priv->name, 0x00, 0xff); if (name != NULL) { sz = MIN ((guint16) strlen (name), 0xff - 1); memcpy (priv->name, name, sz); } /* copy junk data in self tests for 1:1 copies */ if (name != NULL && G_UNLIKELY (g_getenv ("DFU_SELF_TEST_IMAGE_MEMCPY_NAME") != NULL)) memcpy (priv->name, name, 0xff); } /** * dfu_image_to_string: * @image: a #DfuImage * * Returns a string representaiton of the object. * * Return value: NULL terminated string, or %NULL for invalid **/ gchar * dfu_image_to_string (DfuImage *image) { DfuImagePrivate *priv = GET_PRIVATE (image); GString *str; g_return_val_if_fail (DFU_IS_IMAGE (image), NULL); str = g_string_new (""); g_string_append_printf (str, "alt_setting: 0x%02x\n", priv->alt_setting); if (priv->name[0] != '\0') g_string_append_printf (str, "name: %s\n", priv->name); g_string_append_printf (str, "elements: 0x%02x\n", priv->elements->len); /* add elements */ for (guint i = 0; i < priv->elements->len; i++) { DfuElement *element = g_ptr_array_index (priv->elements, i); g_autofree gchar *tmp = NULL; tmp = dfu_element_to_string (element); g_string_append_printf (str, "== ELEMENT %u ==\n", i); g_string_append_printf (str, "%s\n", tmp); } g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } fwupd-1.2.14/plugins/dfu/dfu-image.h000066400000000000000000000020411402665037500171770ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-element.h" G_BEGIN_DECLS #define DFU_TYPE_IMAGE (dfu_image_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuImage, dfu_image, DFU, IMAGE, GObject) struct _DfuImageClass { GObjectClass parent_class; }; DfuImage *dfu_image_new (void); GPtrArray *dfu_image_get_elements (DfuImage *image); DfuElement *dfu_image_get_element (DfuImage *image, guint8 idx); DfuElement *dfu_image_get_element_default (DfuImage *image); guint8 dfu_image_get_alt_setting (DfuImage *image); const gchar *dfu_image_get_name (DfuImage *image); guint32 dfu_image_get_size (DfuImage *image); void dfu_image_add_element (DfuImage *image, DfuElement *element); void dfu_image_set_alt_setting (DfuImage *image, guint8 alt_setting); void dfu_image_set_name (DfuImage *image, const gchar *name); gchar *dfu_image_to_string (DfuImage *image); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-patch.c000066400000000000000000000401631402665037500172160ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * SECTION:dfu-patch * @short_description: Object representing a binary patch * * This object represents an binary patch that can be applied on a firmware * image. The patch itself is made up of chunks of data that have an offset * and that can replace the data to upgrade the firmware. * * Note: this is one way operation -- the patch can only be used to go forwards * and also cannot be used to truncate the existing image. * * See also: #DfuImage, #DfuFirmware */ #include "config.h" #include #include #include "dfu-common.h" #include "dfu-patch.h" #include "fwupd-error.h" static void dfu_patch_finalize (GObject *object); typedef struct __attribute__((packed)) { guint32 off; guint32 sz; guint32 flags; } DfuPatchChunkHeader; typedef struct __attribute__((packed)) { guint8 signature[4]; /* 'DfuP' */ guint8 reserved[4]; guint8 checksum_old[20]; /* SHA1 */ guint8 checksum_new[20]; /* SHA1 */ } DfuPatchFileHeader; typedef struct { GBytes *checksum_old; GBytes *checksum_new; GPtrArray *chunks; /* of DfuPatchChunk */ } DfuPatchPrivate; typedef struct { guint32 off; GBytes *blob; } DfuPatchChunk; G_DEFINE_TYPE_WITH_PRIVATE (DfuPatch, dfu_patch, G_TYPE_OBJECT) #define GET_PRIVATE(o) (dfu_patch_get_instance_private (o)) static void dfu_patch_class_init (DfuPatchClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = dfu_patch_finalize; } static void dfu_patch_chunk_free (DfuPatchChunk *chunk) { g_bytes_unref (chunk->blob); g_free (chunk); } static void dfu_patch_init (DfuPatch *self) { DfuPatchPrivate *priv = GET_PRIVATE (self); priv->chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) dfu_patch_chunk_free); } static void dfu_patch_finalize (GObject *object) { DfuPatch *self = DFU_PATCH (object); DfuPatchPrivate *priv = GET_PRIVATE (self); if (priv->checksum_old != NULL) g_bytes_unref (priv->checksum_old); if (priv->checksum_new != NULL) g_bytes_unref (priv->checksum_new); g_ptr_array_unref (priv->chunks); G_OBJECT_CLASS (dfu_patch_parent_class)->finalize (object); } /** * dfu_patch_export: * @self: a #DfuPatch * @error: a #GError, or %NULL * * Converts the patch to a binary blob that can be stored as a file. * * Return value: (transfer full): blob **/ GBytes * dfu_patch_export (DfuPatch *self, GError **error) { DfuPatchPrivate *priv = GET_PRIVATE (self); gsize addr; gsize sz; guint8 *data; g_return_val_if_fail (DFU_IS_PATCH (self), NULL); /* check we have something to write */ if (priv->chunks->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no chunks to process"); return NULL; } /* calculate the size of the new blob */ sz = sizeof(DfuPatchFileHeader); for (guint i = 0; i < priv->chunks->len; i++) { DfuPatchChunk *chunk = g_ptr_array_index (priv->chunks, i); sz += sizeof(DfuPatchChunkHeader) + g_bytes_get_size (chunk->blob); } g_debug ("blob size is %" G_GSIZE_FORMAT, sz); /* actually allocate and fill in the blob */ data = g_malloc0 (sz); memcpy (data, "DfuP", 4); /* add checksums */ if (priv->checksum_old != NULL) { gsize csum_sz = 0; const guint8 *csum_data = g_bytes_get_data (priv->checksum_old, &csum_sz); memcpy (data + G_STRUCT_OFFSET(DfuPatchFileHeader,checksum_old), csum_data, csum_sz); } if (priv->checksum_new != NULL) { gsize csum_sz = 0; const guint8 *csum_data = g_bytes_get_data (priv->checksum_new, &csum_sz); memcpy (data + G_STRUCT_OFFSET(DfuPatchFileHeader,checksum_new), csum_data, csum_sz); } addr = sizeof(DfuPatchFileHeader); for (guint i = 0; i < priv->chunks->len; i++) { DfuPatchChunk *chunk = g_ptr_array_index (priv->chunks, i); DfuPatchChunkHeader chunkhdr; gsize sz_tmp = 0; const guint8 *data_new = g_bytes_get_data (chunk->blob, &sz_tmp); /* build chunk header and append data */ chunkhdr.off = GUINT32_TO_LE (chunk->off); chunkhdr.sz = GUINT32_TO_LE (sz_tmp); chunkhdr.flags = 0; memcpy (data + addr, &chunkhdr, sizeof(DfuPatchChunkHeader)); memcpy (data + addr + sizeof(DfuPatchChunkHeader), data_new, sz_tmp); /* move up after the copied data */ addr += sizeof(DfuPatchChunkHeader) + sz_tmp; } return g_bytes_new_take (data, sz); } /** * dfu_patch_import: * @self: a #DfuPatch * @blob: patch data * @error: a #GError, or %NULL * * Creates a patch from a serialized patch, possibly from a file. * * Return value: %TRUE on success **/ gboolean dfu_patch_import (DfuPatch *self, GBytes *blob, GError **error) { DfuPatchPrivate *priv = GET_PRIVATE (self); const guint8 *data; gsize sz = 0; guint32 off; g_return_val_if_fail (DFU_IS_PATCH (self), FALSE); g_return_val_if_fail (blob != NULL, FALSE); /* cannot reuse object */ if (priv->chunks->len > 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "patch has already been loaded"); return FALSE; } /* check minimum size */ data = g_bytes_get_data (blob, &sz); if (sz < sizeof(DfuPatchFileHeader) + sizeof(DfuPatchChunkHeader) + 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "file is too small"); return FALSE; } /* check header */ if (memcmp (data, "DfuP", 4) != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "header signature is not correct"); return FALSE; } /* get checksums */ priv->checksum_old = g_bytes_new (data + G_STRUCT_OFFSET(DfuPatchFileHeader,checksum_old), 20); priv->checksum_new = g_bytes_new (data + G_STRUCT_OFFSET(DfuPatchFileHeader,checksum_new), 20); /* look for each chunk */ off = sizeof(DfuPatchFileHeader); while (off < (guint32) sz) { DfuPatchChunkHeader *chunkhdr = (DfuPatchChunkHeader *) (data + off); DfuPatchChunk *chunk; guint32 chunk_sz = GUINT32_FROM_LE (chunkhdr->sz); guint32 chunk_off = GUINT32_FROM_LE (chunkhdr->off); /* check chunk size, assuming it can overflow */ if (chunk_sz > sz || off + chunk_sz > sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "chunk offset 0x%04x outsize file size 0x%04x", (guint) (off + chunk_sz), (guint) sz); return FALSE; } chunk = g_new0 (DfuPatchChunk, 1); chunk->off = chunk_off; chunk->blob = g_bytes_new_from_bytes (blob, off + sizeof(DfuPatchChunkHeader), chunk_sz); g_ptr_array_add (priv->chunks, chunk); off += sizeof(DfuPatchChunkHeader) + chunk_sz; } /* check we finished properly */ if (off != sz) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "blob chunk sizes did not sum to total"); return FALSE; } /* success */ return TRUE; } static GBytes * dfu_patch_calculate_checksum (GBytes *blob) { const guchar *data; gsize digest_len = 20; gsize sz = 0; guint8 *buf = g_malloc0 (digest_len); g_autoptr(GChecksum) csum = NULL; csum = g_checksum_new (G_CHECKSUM_SHA1); data = g_bytes_get_data (blob, &sz); g_checksum_update (csum, data, (gssize) sz); g_checksum_get_digest (csum, buf, &digest_len); return g_bytes_new_take (buf, digest_len); } typedef struct { guint32 diff_start; guint32 diff_end; GBytes *blob; /* no ref */ } DfuPatchCreateHelper; static void dfu_patch_flush (DfuPatch *self, DfuPatchCreateHelper *helper) { DfuPatchChunk *chunk; DfuPatchPrivate *priv = GET_PRIVATE (self); if (helper->diff_end == 0xffff) return; g_debug ("add chunk @0x%04x (len %" G_GUINT32_FORMAT ")", (guint) helper->diff_start, helper->diff_end - helper->diff_start + 1); chunk = g_new0 (DfuPatchChunk, 1); chunk->off = helper->diff_start; chunk->blob = g_bytes_new_from_bytes (helper->blob, chunk->off, helper->diff_end - helper->diff_start + 1); g_ptr_array_add (priv->chunks, chunk); helper->diff_end = 0xffff; } /** * dfu_patch_create: * @self: a #DfuPatch * @blob1: a #GBytes, typically the old firmware image * @blob2: a #GBytes, typically the new firmware image * @error: a #GError, or %NULL * * Creates a patch from two blobs of memory. * * The blobs should ideally be the same size. If @blob2 is has grown in size * the binary diff will still work but the algorithm will probably not perform * well unless the majority of data has just been appended. * * As an additional constrainst, @blob2 cannot be smaller than @blob1, i.e. * the firmware cannot be truncated by this format. * * Return value: %TRUE on success **/ gboolean dfu_patch_create (DfuPatch *self, GBytes *blob1, GBytes *blob2, GError **error) { DfuPatchPrivate *priv = GET_PRIVATE (self); DfuPatchCreateHelper helper; const guint8 *data1; const guint8 *data2; gsize sz1 = 0; gsize sz2 = 0; guint32 same_sz = 0; g_return_val_if_fail (DFU_IS_PATCH (self), FALSE); g_return_val_if_fail (blob1 != NULL, FALSE); g_return_val_if_fail (blob2 != NULL, FALSE); /* are the blobs the same */ if (g_bytes_equal (blob1, blob2)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "old and new binaries are the same"); return FALSE; } /* cannot reuse object */ if (priv->chunks->len > 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "patch has already been loaded"); return FALSE; } /* get the hash of the old firmware file */ priv->checksum_old = dfu_patch_calculate_checksum (blob1); priv->checksum_new = dfu_patch_calculate_checksum (blob2); /* get the raw data, and ensure they are the same size */ data1 = g_bytes_get_data (blob1, &sz1); data2 = g_bytes_get_data (blob2, &sz2); if (sz1 > sz2) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware binary cannot go down, got " "%" G_GSIZE_FORMAT " and %" G_GSIZE_FORMAT, sz1, sz2); return FALSE; } if (sz1 == sz2) { g_debug ("binary staying same size: %" G_GSIZE_FORMAT, sz1); } else { g_debug ("binary growing from: %" G_GSIZE_FORMAT " to %" G_GSIZE_FORMAT, sz1, sz2); } /* start the dumb comparison algorithm */ helper.diff_start = 0; helper.diff_end = 0xffff; helper.blob = blob2; for (gsize i = 0; i < sz1 || i < sz2; i++) { if (i < sz1 && i < sz2 && data1[i] == data2[i]) { /* if we got enough the same, dump what is pending */ if (++same_sz > sizeof(DfuPatchChunkHeader) * 2) dfu_patch_flush (self, &helper); continue; } if (helper.diff_end == 0xffff) helper.diff_start = (guint32) i; helper.diff_end = (guint32) i; same_sz = 0; } dfu_patch_flush (self, &helper); return TRUE; } static gchar * _g_bytes_to_string (GBytes *blob) { gsize sz = 0; const guint8 *data = g_bytes_get_data (blob, &sz); GString *str = g_string_new (NULL); for (gsize i = 0; i < sz; i++) g_string_append_printf (str, "%02x", (guint) data[i]); return g_string_free (str, FALSE); } /** * dfu_patch_get_checksum_old: * @self: a #DfuPatch * * Get the checksum for the old firmware image. * * Return value: A #GBytes, or %NULL if nothing has been loaded. **/ GBytes * dfu_patch_get_checksum_old (DfuPatch *self) { DfuPatchPrivate *priv = GET_PRIVATE (self); return priv->checksum_old; } /** * dfu_patch_get_checksum_new: * @self: a #DfuPatch * * Get the checksum for the new firmware image. * * Return value: A #GBytes, or %NULL if nothing has been loaded. **/ GBytes * dfu_patch_get_checksum_new (DfuPatch *self) { DfuPatchPrivate *priv = GET_PRIVATE (self); return priv->checksum_new; } /** * dfu_patch_apply: * @self: a #DfuPatch * @blob: a #GBytes, typically the old firmware image * @flags: a #DfuPatchApplyFlags, e.g. %DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM * @error: a #GError, or %NULL * * Apply the currently loaded patch to a new firmware image. * * Return value: A #GBytes, typically saved as the new firmware file **/ GBytes * dfu_patch_apply (DfuPatch *self, GBytes *blob, DfuPatchApplyFlags flags, GError **error) { DfuPatchPrivate *priv = GET_PRIVATE (self); const guint8 *data_old; gsize sz; gsize sz_max = 0; g_autofree guint8 *data_new = NULL; g_autoptr(GBytes) blob_checksum_new = NULL; g_autoptr(GBytes) blob_checksum = NULL; g_autoptr(GBytes) blob_new = NULL; /* not loaded yet */ if (priv->chunks->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no patches loaded"); return NULL; } /* get the hash of the old firmware file */ blob_checksum = dfu_patch_calculate_checksum (blob); if ((flags & DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM) == 0 && !g_bytes_equal (blob_checksum, priv->checksum_old)) { g_autofree gchar *actual = _g_bytes_to_string (blob_checksum); g_autofree gchar *expect = _g_bytes_to_string (priv->checksum_old); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum for source did not match, expected %s, got %s", expect, actual); return NULL; } /* get the size of the new image size */ for (guint i = 0; i < priv->chunks->len; i++) { DfuPatchChunk *chunk = g_ptr_array_index (priv->chunks, i); gsize chunk_sz = g_bytes_get_size (chunk->blob); if (chunk->off + chunk_sz > sz_max) sz_max = chunk->off + chunk_sz; } /* first, copy the data buffer */ data_old = g_bytes_get_data (blob, &sz); if (sz_max < sz) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "binary patch cannot truncate binary"); return NULL; } if (sz == sz_max) { g_debug ("binary staying same size: %" G_GSIZE_FORMAT, sz); } else { g_debug ("binary growing from: %" G_GSIZE_FORMAT " to %" G_GSIZE_FORMAT, sz, sz_max); } data_new = g_malloc0 (sz_max); memcpy (data_new, data_old, MIN (sz, sz_max)); for (guint i = 0; i < priv->chunks->len; i++) { DfuPatchChunk *chunk = g_ptr_array_index (priv->chunks, i); const guint8 *chunk_data; gsize chunk_sz; /* bigger than the total size */ chunk_data = g_bytes_get_data (chunk->blob, &chunk_sz); if (chunk->off + chunk_sz > sz_max) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot apply chunk as larger than max size"); return NULL; } /* apply one chunk */ g_debug ("applying chunk %u/%u @0x%04x (length %" G_GSIZE_FORMAT ")", i + 1, priv->chunks->len, chunk->off, chunk_sz); memcpy (data_new + chunk->off, chunk_data, chunk_sz); } /* check we got the desired hash */ blob_new = g_bytes_new (data_new, sz_max); blob_checksum_new = dfu_patch_calculate_checksum (blob_new); if ((flags & DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM) == 0 && !g_bytes_equal (blob_checksum_new, priv->checksum_new)) { g_autofree gchar *actual = _g_bytes_to_string (blob_checksum_new); g_autofree gchar *expect = _g_bytes_to_string (priv->checksum_new); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum for result did not match, expected %s, got %s", expect, actual); return NULL; } /* success */ return g_steal_pointer (&blob_new); } /** * dfu_patch_to_string: * @self: a #DfuPatch * * Returns a string representaiton of the object. * * Return value: NULL terminated string, or %NULL for invalid **/ gchar * dfu_patch_to_string (DfuPatch *self) { DfuPatchPrivate *priv = GET_PRIVATE (self); GString *str = g_string_new (NULL); g_autofree gchar *checksum_old = NULL; g_autofree gchar *checksum_new = NULL; g_return_val_if_fail (DFU_IS_PATCH (self), NULL); /* add checksums */ checksum_old = _g_bytes_to_string (priv->checksum_old); g_string_append_printf (str, "checksum-old: %s\n", checksum_old); checksum_new = _g_bytes_to_string (priv->checksum_new); g_string_append_printf (str, "checksum-new: %s\n", checksum_new); /* add chunks */ for (guint i = 0; i < priv->chunks->len; i++) { DfuPatchChunk *chunk = g_ptr_array_index (priv->chunks, i); g_string_append_printf (str, "chunk #%02u 0x%04x, length %" G_GSIZE_FORMAT "\n", i, chunk->off, g_bytes_get_size (chunk->blob)); } g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } /** * dfu_patch_new: * * Creates a new DFU patch object. * * Return value: a new #DfuPatch **/ DfuPatch * dfu_patch_new (void) { DfuPatch *self; self = g_object_new (DFU_TYPE_PATCH, NULL); return self; } fwupd-1.2.14/plugins/dfu/dfu-patch.h000066400000000000000000000024541402665037500172240ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include G_BEGIN_DECLS #define DFU_TYPE_PATCH (dfu_patch_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuPatch, dfu_patch, DFU, PATCH, GObject) struct _DfuPatchClass { GObjectClass parent_class; }; /** * DfuPatchApplyFlags: * @DFU_PATCH_APPLY_FLAG_NONE: No flags set * @DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM: Do not check the checksum * * The optional flags used for applying a patch. **/ typedef enum { DFU_PATCH_APPLY_FLAG_NONE = 0, DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM = (1 << 0), /*< private >*/ DFU_PATCH_APPLY_FLAG_LAST } DfuPatchApplyFlags; DfuPatch *dfu_patch_new (void); gchar *dfu_patch_to_string (DfuPatch *self); GBytes *dfu_patch_export (DfuPatch *self, GError **error); gboolean dfu_patch_import (DfuPatch *self, GBytes *blob, GError **error); gboolean dfu_patch_create (DfuPatch *self, GBytes *blob1, GBytes *blob2, GError **error); GBytes *dfu_patch_apply (DfuPatch *self, GBytes *blob, DfuPatchApplyFlags flags, GError **error); GBytes *dfu_patch_get_checksum_old (DfuPatch *self); GBytes *dfu_patch_get_checksum_new (DfuPatch *self); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-sector-private.h000066400000000000000000000005221402665037500210660ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "dfu-sector.h" G_BEGIN_DECLS DfuSector *dfu_sector_new (guint32 address, guint32 size, guint32 size_left, guint16 zone, guint16 number, DfuSectorCap cap); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-sector.c000066400000000000000000000124271402665037500174200ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * SECTION:dfu-sector * @short_description: Object representing a sector on a chip * * This object represents an sector of memory at a specific address on the * device itself. * * This allows relocatable data segments to be stored in different * locations on the device itself. * * You can think of these objects as flash segments on devices, where a * complete block can be erased and then written to. * * See also: #DfuElement */ #include "config.h" #include #include #include "dfu-common.h" #include "dfu-sector-private.h" typedef struct { guint32 address; guint32 size; guint32 size_left; guint16 zone; guint16 number; DfuSectorCap cap; } DfuSectorPrivate; G_DEFINE_TYPE_WITH_PRIVATE (DfuSector, dfu_sector, G_TYPE_OBJECT) #define GET_PRIVATE(o) (dfu_sector_get_instance_private (o)) static void dfu_sector_class_init (DfuSectorClass *klass) { } static void dfu_sector_init (DfuSector *sector) { } /** * dfu_sector_new: (skip) * address: the address for the sector * size: the size of this sector * size_left: the size of the rest of the sector * zone: the zone of memory the setor belongs * number: the sector number in the zone * cap: the #DfuSectorCap * * Creates a new DFU sector object. * * Return value: a new #DfuSector **/ DfuSector * dfu_sector_new (guint32 address, guint32 size, guint32 size_left, guint16 zone, guint16 number, DfuSectorCap cap) { DfuSectorPrivate *priv; DfuSector *sector; sector = g_object_new (DFU_TYPE_SECTOR, NULL); priv = GET_PRIVATE (sector); priv->address = address; priv->size = size; priv->size_left = size_left; priv->zone = zone; priv->number = number; priv->cap = cap; return sector; } /** * dfu_sector_get_address: * @sector: a #DfuSector * * Gets the alternate setting. * * Return value: integer, or 0x00 for unset **/ guint32 dfu_sector_get_address (DfuSector *sector) { DfuSectorPrivate *priv = GET_PRIVATE (sector); g_return_val_if_fail (DFU_IS_SECTOR (sector), 0x00); return priv->address; } /** * dfu_sector_get_size: * @sector: a #DfuSector * * Gets the sector size. * * Return value: integer, or 0x00 for unset **/ guint32 dfu_sector_get_size (DfuSector *sector) { DfuSectorPrivate *priv = GET_PRIVATE (sector); g_return_val_if_fail (DFU_IS_SECTOR (sector), 0x00); return priv->size; } /** * dfu_sector_get_size_left: * @sector: a #DfuSector * * Gets the size of the rest of the sector. * * Return value: integer, or 0x00 for unset **/ guint32 dfu_sector_get_size_left (DfuSector *sector) { DfuSectorPrivate *priv = GET_PRIVATE (sector); g_return_val_if_fail (DFU_IS_SECTOR (sector), 0x00); return priv->size_left; } /** * dfu_sector_get_zone: * @sector: a #DfuSector * * Gets the sector zone number. * * Return value: integer, or 0x00 for unset **/ guint16 dfu_sector_get_zone (DfuSector *sector) { DfuSectorPrivate *priv = GET_PRIVATE (sector); g_return_val_if_fail (DFU_IS_SECTOR (sector), 0x00); return priv->zone; } /** * dfu_sector_get_number: * @sector: a #DfuSector * * Gets the sector index number. * * Return value: integer, or 0x00 for unset **/ guint16 dfu_sector_get_number (DfuSector *sector) { DfuSectorPrivate *priv = GET_PRIVATE (sector); g_return_val_if_fail (DFU_IS_SECTOR (sector), 0x00); return priv->number; } /** * dfu_sector_get_id: * @sector: a #DfuSector * * Gets the sector ID which is a combination of the zone and sector number. * You can use this number to check if the segment is the 'same' as the last * written or read sector. * * Return value: integer ID, or 0x00 for unset **/ guint32 dfu_sector_get_id (DfuSector *sector) { DfuSectorPrivate *priv = GET_PRIVATE (sector); g_return_val_if_fail (DFU_IS_SECTOR (sector), 0x00); return (((guint32) priv->zone) << 16) | priv->number; } /** * dfu_sector_has_cap: * @sector: a #DfuSector * @cap: a #DfuSectorCap, e.g. %DFU_SECTOR_CAP_ERASEABLE * * Finds out if the sector has the required capability. * * Return value: %TRUE if the sector has the capabilily **/ gboolean dfu_sector_has_cap (DfuSector *sector, DfuSectorCap cap) { DfuSectorPrivate *priv = GET_PRIVATE (sector); g_return_val_if_fail (DFU_IS_SECTOR (sector), FALSE); return (priv->cap & cap) > 0; } static gchar * dfu_sector_cap_to_string (DfuSectorCap cap) { GString *str = g_string_new (NULL); if (cap & DFU_SECTOR_CAP_READABLE) g_string_append (str, "R"); if (cap & DFU_SECTOR_CAP_ERASEABLE) g_string_append (str, "E"); if (cap & DFU_SECTOR_CAP_WRITEABLE) g_string_append (str, "W"); return g_string_free (str, FALSE); } /** * dfu_sector_to_string: * @sector: a #DfuSector * * Returns a string representaiton of the object. * * Return value: NULL terminated string, or %NULL for invalid **/ gchar * dfu_sector_to_string (DfuSector *sector) { DfuSectorPrivate *priv = GET_PRIVATE (sector); GString *str; g_autofree gchar *caps_str = NULL; g_return_val_if_fail (DFU_IS_SECTOR (sector), NULL); str = g_string_new (""); caps_str = dfu_sector_cap_to_string (priv->cap); g_string_append_printf (str, "Zone:%i, Sec#:%i, Addr:0x%08x, " "Size:0x%04x, Caps:0x%01x [%s]", priv->zone, priv->number, priv->address, priv->size, priv->cap, caps_str); return g_string_free (str, FALSE); } fwupd-1.2.14/plugins/dfu/dfu-sector.h000066400000000000000000000024351402665037500174230ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include G_BEGIN_DECLS #define DFU_TYPE_SECTOR (dfu_sector_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuSector, dfu_sector, DFU, SECTOR, GObject) struct _DfuSectorClass { GObjectClass parent_class; }; /** * DfuSectorCap: * @DFU_SECTOR_CAP_NONE: No operations possible * @DFU_SECTOR_CAP_READABLE: Sector can be read * @DFU_SECTOR_CAP_WRITEABLE: Sector can be written * @DFU_SECTOR_CAP_ERASEABLE: Sector can be erased * * The flags indicating what the sector can do. **/ typedef enum { DFU_SECTOR_CAP_NONE = 0, DFU_SECTOR_CAP_READABLE = 1 << 0, DFU_SECTOR_CAP_WRITEABLE = 1 << 1, DFU_SECTOR_CAP_ERASEABLE = 1 << 2, /*< private >*/ DFU_SECTOR_CAP_LAST } DfuSectorCap; guint32 dfu_sector_get_id (DfuSector *sector); guint32 dfu_sector_get_address (DfuSector *sector); guint32 dfu_sector_get_size (DfuSector *sector); guint32 dfu_sector_get_size_left (DfuSector *sector); guint16 dfu_sector_get_zone (DfuSector *sector); guint16 dfu_sector_get_number (DfuSector *sector); gboolean dfu_sector_has_cap (DfuSector *sector, DfuSectorCap cap); gchar *dfu_sector_to_string (DfuSector *sector); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-self-test.c000066400000000000000000000666401402665037500200350ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "dfu-cipher-xtea.h" #include "dfu-common.h" #include "dfu-device-private.h" #include "dfu-firmware.h" #include "dfu-patch.h" #include "dfu-sector-private.h" #include "dfu-target-private.h" #include "fu-test.h" #include "fwupd-error.h" static gchar * dfu_test_get_filename (const gchar *filename) { gchar *tmp; char full_tmp[PATH_MAX]; g_autofree gchar *path = NULL; path = g_build_filename (TESTDATADIR, filename, NULL); tmp = realpath (path, full_tmp); if (tmp == NULL) return NULL; return g_strdup (full_tmp); } static void dfu_cipher_xtea_func (void) { gboolean ret; guint8 buf[] = { 'H', 'i', 'y', 'a', 'D', 'a', 'v', 'e' }; g_autoptr(GError) error = NULL; ret = dfu_cipher_encrypt_xtea ("test", buf, sizeof(buf), &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (buf[0], ==, 128); g_assert_cmpint (buf[1], ==, 220); g_assert_cmpint (buf[2], ==, 23); g_assert_cmpint (buf[3], ==, 55); g_assert_cmpint (buf[4], ==, 201); g_assert_cmpint (buf[5], ==, 207); g_assert_cmpint (buf[6], ==, 182); g_assert_cmpint (buf[7], ==, 177); ret = dfu_cipher_decrypt_xtea ("test", buf, sizeof(buf), &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (buf[0], ==, 'H'); g_assert_cmpint (buf[1], ==, 'i'); g_assert_cmpint (buf[2], ==, 'y'); g_assert_cmpint (buf[3], ==, 'a'); g_assert_cmpint (buf[4], ==, 'D'); g_assert_cmpint (buf[5], ==, 'a'); g_assert_cmpint (buf[6], ==, 'v'); g_assert_cmpint (buf[7], ==, 'e'); } static void dfu_firmware_xdfu_func (void) { gboolean ret; g_autofree gchar *fn = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; fn = dfu_test_get_filename ("example.xdfu"); g_assert (fn != NULL); firmware = dfu_firmware_new (); file = g_file_new_for_path (fn); ret = dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (dfu_firmware_get_cipher_kind (firmware), ==, DFU_CIPHER_KIND_XTEA); } static void dfu_enums_func (void) { for (guint i = 0; i < DFU_STATE_LAST; i++) g_assert_cmpstr (dfu_state_to_string (i), !=, NULL); for (guint i = 0; i < DFU_STATUS_LAST; i++) g_assert_cmpstr (dfu_status_to_string (i), !=, NULL); } static GBytes * dfu_self_test_get_bytes_for_file (GFile *file, GError **error) { gchar *contents = NULL; gsize length = 0; if (!g_file_load_contents (file, NULL, &contents, &length, NULL, error)) return NULL; return g_bytes_new_take (contents, length); } static void dfu_firmware_raw_func (void) { DfuElement *element; DfuImage *image_tmp; GBytes *no_suffix_contents; gchar buf[256]; gboolean ret; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) roundtrip = NULL; g_autoptr(GError) error = NULL; /* set up some dummy data */ for (guint i = 0; i < 256; i++) buf[i] = (gchar) i; fw = g_bytes_new_static (buf, 256); /* load a non DFU firmware */ firmware = dfu_firmware_new (); ret = dfu_firmware_parse_data (firmware, fw, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (dfu_firmware_get_vid (firmware), ==, 0xffff); g_assert_cmpint (dfu_firmware_get_pid (firmware), ==, 0xffff); g_assert_cmpint (dfu_firmware_get_release (firmware), ==, 0xffff); g_assert_cmpint (dfu_firmware_get_format (firmware), ==, DFU_FIRMWARE_FORMAT_RAW); g_assert_cmpint (dfu_firmware_get_cipher_kind (firmware), ==, DFU_CIPHER_KIND_NONE); image_tmp = dfu_firmware_get_image (firmware, 0xfe); g_assert (image_tmp == NULL); image_tmp = dfu_firmware_get_image (firmware, 0); g_assert (image_tmp != NULL); g_assert_cmpint (dfu_image_get_size (image_tmp), ==, 256); element = dfu_image_get_element (image_tmp, 0); g_assert (element != NULL); no_suffix_contents = dfu_element_get_contents (element); g_assert (no_suffix_contents != NULL); g_assert_cmpint (g_bytes_compare (no_suffix_contents, fw), ==, 0); /* can we roundtrip without adding data */ roundtrip = dfu_firmware_write_data (firmware, &error); g_assert_no_error (error); g_assert (roundtrip != NULL); ret = fu_common_bytes_compare (roundtrip, fw, &error); g_assert_no_error (error); g_assert_true (ret); } static void dfu_firmware_dfu_func (void) { gchar buf[256]; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(DfuImage) image = NULL; g_autoptr(DfuElement) element = NULL; g_autoptr(GBytes) data = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) roundtrip_orig = NULL; g_autoptr(GBytes) roundtrip = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; /* set up some dummy data */ for (guint i = 0; i < 256; i++) buf[i] = (gchar) i; fw = g_bytes_new_static (buf, 256); /* write DFU format */ firmware = dfu_firmware_new (); dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_DFU); dfu_firmware_set_vid (firmware, 0x1234); dfu_firmware_set_pid (firmware, 0x5678); dfu_firmware_set_release (firmware, 0xfedc); image = dfu_image_new (); element = dfu_element_new (); dfu_element_set_contents (element, fw); dfu_image_add_element (image, element); dfu_firmware_add_image (firmware, image); g_assert_cmpint (dfu_firmware_get_size (firmware), ==, 256); data = dfu_firmware_write_data (firmware, &error); g_assert_no_error (error); g_assert (data != NULL); /* can we load it again? */ g_ptr_array_set_size (dfu_firmware_get_images (firmware), 0); ret = dfu_firmware_parse_data (firmware, data, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (dfu_firmware_get_vid (firmware), ==, 0x1234); g_assert_cmpint (dfu_firmware_get_pid (firmware), ==, 0x5678); g_assert_cmpint (dfu_firmware_get_release (firmware), ==, 0xfedc); g_assert_cmpint (dfu_firmware_get_format (firmware), ==, DFU_FIRMWARE_FORMAT_DFU); g_assert_cmpint (dfu_firmware_get_size (firmware), ==, 256); /* load a real firmware */ filename = dfu_test_get_filename ("kiibohd.dfu.bin"); g_assert (filename != NULL); file = g_file_new_for_path (filename); g_ptr_array_set_size (dfu_firmware_get_images (firmware), 0); ret = dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (dfu_firmware_get_vid (firmware), ==, 0x1c11); g_assert_cmpint (dfu_firmware_get_pid (firmware), ==, 0xb007); g_assert_cmpint (dfu_firmware_get_release (firmware), ==, 0xffff); g_assert_cmpint (dfu_firmware_get_format (firmware), ==, DFU_FIRMWARE_FORMAT_DFU); g_assert_cmpint (dfu_firmware_get_size (firmware), ==, 0x8eB4); g_assert_cmpint (dfu_firmware_get_cipher_kind (firmware), ==, DFU_CIPHER_KIND_NONE); /* can we roundtrip without losing data */ roundtrip_orig = dfu_self_test_get_bytes_for_file (file, &error); g_assert_no_error (error); g_assert (roundtrip_orig != NULL); roundtrip = dfu_firmware_write_data (firmware, &error); g_assert_no_error (error); g_assert (roundtrip != NULL); ret = fu_common_bytes_compare (roundtrip, roundtrip_orig, &error); g_assert_no_error (error); g_assert_true (ret); } static void dfu_firmware_dfuse_func (void) { gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GBytes) roundtrip_orig = NULL; g_autoptr(GBytes) roundtrip = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; /* load a DeFUse firmware */ g_setenv ("DFU_SELF_TEST_IMAGE_MEMCPY_NAME", "", FALSE); filename = dfu_test_get_filename ("dev_VRBRAIN.dfu"); g_assert (filename != NULL); file = g_file_new_for_path (filename); firmware = dfu_firmware_new (); ret = dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (dfu_firmware_get_vid (firmware), ==, 0x0483); g_assert_cmpint (dfu_firmware_get_pid (firmware), ==, 0x0000); g_assert_cmpint (dfu_firmware_get_release (firmware), ==, 0x0000); g_assert_cmpint (dfu_firmware_get_format (firmware), ==, DFU_FIRMWARE_FORMAT_DFUSE); g_assert_cmpint (dfu_firmware_get_size (firmware), ==, 0x168d5); g_assert_cmpint (dfu_firmware_get_cipher_kind (firmware), ==, DFU_CIPHER_KIND_NONE); /* can we roundtrip without losing data */ roundtrip_orig = dfu_self_test_get_bytes_for_file (file, &error); g_assert_no_error (error); g_assert (roundtrip_orig != NULL); roundtrip = dfu_firmware_write_data (firmware, &error); g_assert_no_error (error); g_assert (roundtrip != NULL); // g_file_set_contents ("/tmp/1.bin", // g_bytes_get_data (roundtrip, NULL), // g_bytes_get_size (roundtrip), NULL); ret = fu_common_bytes_compare (roundtrip, roundtrip_orig, &error); g_assert_no_error (error); g_assert_true (ret); /* use usual image name copying */ g_unsetenv ("DFU_SELF_TEST_IMAGE_MEMCPY_NAME"); } static void dfu_firmware_metadata_func (void) { gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GBytes) roundtrip_orig = NULL; g_autoptr(GBytes) roundtrip = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; /* load a DFU firmware with a metadata table */ filename = dfu_test_get_filename ("metadata.dfu"); g_assert (filename != NULL); file = g_file_new_for_path (filename); firmware = dfu_firmware_new (); ret = dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (dfu_firmware_get_size (firmware), ==, 6); g_assert_cmpstr (dfu_firmware_get_metadata (firmware, "key"), ==, "value"); g_assert_cmpstr (dfu_firmware_get_metadata (firmware, "???"), ==, NULL); /* can we roundtrip without losing data */ roundtrip_orig = dfu_self_test_get_bytes_for_file (file, &error); g_assert_no_error (error); g_assert (roundtrip_orig != NULL); roundtrip = dfu_firmware_write_data (firmware, &error); g_assert_no_error (error); g_assert (roundtrip != NULL); ret = fu_common_bytes_compare (roundtrip, roundtrip_orig, &error); g_assert_no_error (error); g_assert_true (ret); } static void dfu_firmware_intel_hex_offset_func (void) { DfuElement *element_verify; DfuImage *image_verify; const guint8 *data; gboolean ret; gsize len; g_autofree gchar *str = NULL; g_autoptr(DfuElement) element = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(DfuFirmware) firmware_verify = NULL; g_autoptr(DfuImage) image = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GBytes) data_dummy = NULL; g_autoptr(GError) error = NULL; /* add a 4 byte image in high memory */ element = dfu_element_new (); data_dummy = g_bytes_new_static ("foo", 4); dfu_element_set_address (element, 0x80000000); dfu_element_set_contents (element, data_dummy); image = dfu_image_new (); dfu_image_add_element (image, element); firmware = dfu_firmware_new (); dfu_firmware_add_image (firmware, image); dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_INTEL_HEX); data_bin = dfu_firmware_write_data (firmware, &error); g_assert_no_error (error); g_assert (data_bin != NULL); data = g_bytes_get_data (data_bin, &len); str = g_strndup ((const gchar *) data, len); g_assert_cmpstr (str, ==, ":0200000480007A\n" ":04000000666F6F00B8\n" ":00000001FF\n"); /* check we can load it too */ firmware_verify = dfu_firmware_new (); ret = dfu_firmware_parse_data (firmware_verify, data_bin, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); image_verify = dfu_firmware_get_image_default (firmware_verify); g_assert (image_verify != NULL); element_verify = dfu_image_get_element_default (image); g_assert (element_verify != NULL); g_assert_cmpint (dfu_element_get_address (element_verify), ==, 0x80000000); g_assert_cmpint (g_bytes_get_size (dfu_element_get_contents (element_verify)), ==, 0x4); } static void dfu_firmware_srec_func (void) { gboolean ret; g_autofree gchar *filename_hex = NULL; g_autofree gchar *filename_ref = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GBytes) data_ref = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_bin = NULL; g_autoptr(GFile) file_hex = NULL; filename_hex = dfu_test_get_filename ("firmware.srec"); g_assert (filename_hex != NULL); file_hex = g_file_new_for_path (filename_hex); firmware = dfu_firmware_new (); ret = dfu_firmware_parse_file (firmware, file_hex, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (dfu_firmware_get_size (firmware), ==, 136); dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_RAW); data_bin = dfu_firmware_write_data (firmware, &error); g_assert_no_error (error); g_assert (data_bin != NULL); /* did we match the reference file? */ filename_ref = dfu_test_get_filename ("firmware.bin"); g_assert (filename_ref != NULL); file_bin = g_file_new_for_path (filename_ref); data_ref = dfu_self_test_get_bytes_for_file (file_bin, &error); g_assert_no_error (error); g_assert (data_ref != NULL); ret = fu_common_bytes_compare (data_bin, data_ref, &error); g_assert_no_error (error); g_assert_true (ret); } static void dfu_firmware_intel_hex_func (void) { const guint8 *data; gboolean ret; gsize len; g_autofree gchar *filename_hex = NULL; g_autofree gchar *filename_ref = NULL; g_autofree gchar *str = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GBytes) data_bin2 = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GBytes) data_hex = NULL; g_autoptr(GBytes) data_ref = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_bin = NULL; g_autoptr(GFile) file_hex = NULL; /* load a Intel hex32 file */ filename_hex = dfu_test_get_filename ("firmware.hex"); g_assert (filename_hex != NULL); file_hex = g_file_new_for_path (filename_hex); firmware = dfu_firmware_new (); ret = dfu_firmware_parse_file (firmware, file_hex, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (dfu_firmware_get_size (firmware), ==, 136); dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_RAW); data_bin = dfu_firmware_write_data (firmware, &error); g_assert_no_error (error); g_assert (data_bin != NULL); /* did we match the reference file? */ filename_ref = dfu_test_get_filename ("firmware.bin"); g_assert (filename_ref != NULL); file_bin = g_file_new_for_path (filename_ref); data_ref = dfu_self_test_get_bytes_for_file (file_bin, &error); g_assert_no_error (error); g_assert (data_ref != NULL); ret = fu_common_bytes_compare (data_bin, data_ref, &error); g_assert_no_error (error); g_assert_true (ret); /* export a ihex file (which will be slightly different due to * non-continous regions being expanded */ dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_INTEL_HEX); data_hex = dfu_firmware_write_data (firmware, &error); g_assert_no_error (error); g_assert (data_hex != NULL); data = g_bytes_get_data (data_hex, &len); str = g_strndup ((const gchar *) data, len); g_assert_cmpstr (str, ==, ":104000003DEF20F000000000FACF01F0FBCF02F0FE\n" ":10401000E9CF03F0EACF04F0E1CF05F0E2CF06F0FC\n" ":10402000D9CF07F0DACF08F0F3CF09F0F4CF0AF0D8\n" ":10403000F6CF0BF0F7CF0CF0F8CF0DF0F5CF0EF078\n" ":104040000EC0F5FF0DC0F8FF0CC0F7FF0BC0F6FF68\n" ":104050000AC0F4FF09C0F3FF08C0DAFF07C0D9FFA8\n" ":1040600006C0E2FF05C0E1FF04C0EAFF03C0E9FFAC\n" ":1040700002C0FBFF01C0FAFF11003FEF20F000017A\n" ":0840800042EF20F03DEF20F0BB\n" ":00000001FF\n"); /* do we match the binary file again */ dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_RAW); data_bin2 = dfu_firmware_write_data (firmware, &error); g_assert_no_error (error); g_assert (data_bin2 != NULL); ret = fu_common_bytes_compare (data_bin, data_bin2, &error); g_assert_no_error (error); g_assert_true (ret); } static void dfu_firmware_intel_hex_signed_func (void) { DfuElement *element; DfuImage *image; GBytes *data_sig; const guint8 *data; gboolean ret; gsize len; g_autofree gchar *filename_hex = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_hex = NULL; /* load a Intel hex32 file */ filename_hex = dfu_test_get_filename ("firmware.shex"); g_assert (filename_hex != NULL); file_hex = g_file_new_for_path (filename_hex); firmware = dfu_firmware_new (); ret = dfu_firmware_parse_file (firmware, file_hex, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (dfu_firmware_get_size (firmware), ==, 144); /* get the signed image element */ image = dfu_firmware_get_image_by_name (firmware, "signature"); g_assert (image != NULL); element = dfu_image_get_element_default (image); data_sig = dfu_element_get_contents (element); g_assert (data_sig != NULL); data = g_bytes_get_data (data_sig, &len); g_assert_cmpint (len, ==, 8); g_assert (data != NULL); } static gchar * dfu_target_sectors_to_string (DfuTarget *target) { GPtrArray *sectors; GString *str; str = g_string_new (""); sectors = dfu_target_get_sectors (target); for (guint i = 0; i < sectors->len; i++) { DfuSector *sector = g_ptr_array_index (sectors, i); g_autofree gchar *tmp = dfu_sector_to_string (sector); g_string_append_printf (str, "%s\n", tmp); } if (str->len > 0) g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } static void dfu_target_dfuse_func (void) { gboolean ret; gchar *tmp; g_autoptr(DfuTarget) target = NULL; g_autoptr(GError) error = NULL; /* NULL */ target = g_object_new (DFU_TYPE_TARGET, NULL); ret = dfu_target_parse_sectors (target, NULL, &error); g_assert_no_error (error); g_assert (ret); tmp = dfu_target_sectors_to_string (target); g_assert_cmpstr (tmp, ==, ""); g_free (tmp); /* no addresses */ ret = dfu_target_parse_sectors (target, "@Flash3", &error); g_assert_no_error (error); g_assert (ret); tmp = dfu_target_sectors_to_string (target); g_assert_cmpstr (tmp, ==, ""); g_free (tmp); /* one sector, no space */ ret = dfu_target_parse_sectors (target, "@Internal Flash /0x08000000/2*001Ka", &error); g_assert_no_error (error); g_assert (ret); tmp = dfu_target_sectors_to_string (target); ret = fu_test_compare_lines (tmp, "Zone:0, Sec#:0, Addr:0x08000000, Size:0x0400, Caps:0x1 [R]\n" "Zone:0, Sec#:0, Addr:0x08000400, Size:0x0400, Caps:0x1 [R]", &error); g_assert_no_error (error); g_assert (ret); g_free (tmp); /* multiple sectors */ ret = dfu_target_parse_sectors (target, "@Flash1 /0x08000000/2*001Ka,4*001Kg", &error); g_assert_no_error (error); g_assert (ret); tmp = dfu_target_sectors_to_string (target); ret = fu_test_compare_lines (tmp, "Zone:0, Sec#:0, Addr:0x08000000, Size:0x0400, Caps:0x1 [R]\n" "Zone:0, Sec#:0, Addr:0x08000400, Size:0x0400, Caps:0x1 [R]\n" "Zone:0, Sec#:1, Addr:0x08000800, Size:0x0400, Caps:0x7 [REW]\n" "Zone:0, Sec#:1, Addr:0x08000c00, Size:0x0400, Caps:0x7 [REW]\n" "Zone:0, Sec#:1, Addr:0x08001000, Size:0x0400, Caps:0x7 [REW]\n" "Zone:0, Sec#:1, Addr:0x08001400, Size:0x0400, Caps:0x7 [REW]", &error); g_assert_no_error (error); g_assert (ret); g_free (tmp); /* non-contiguous */ ret = dfu_target_parse_sectors (target, "@Flash2 /0xF000/4*100Ba/0xE000/3*8Kg/0x80000/2*24Kg", &error); g_assert_no_error (error); g_assert (ret); tmp = dfu_target_sectors_to_string (target); ret = fu_test_compare_lines (tmp, "Zone:0, Sec#:0, Addr:0x0000f000, Size:0x0064, Caps:0x1 [R]\n" "Zone:0, Sec#:0, Addr:0x0000f064, Size:0x0064, Caps:0x1 [R]\n" "Zone:0, Sec#:0, Addr:0x0000f0c8, Size:0x0064, Caps:0x1 [R]\n" "Zone:0, Sec#:0, Addr:0x0000f12c, Size:0x0064, Caps:0x1 [R]\n" "Zone:1, Sec#:0, Addr:0x0000e000, Size:0x2000, Caps:0x7 [REW]\n" "Zone:1, Sec#:0, Addr:0x00010000, Size:0x2000, Caps:0x7 [REW]\n" "Zone:1, Sec#:0, Addr:0x00012000, Size:0x2000, Caps:0x7 [REW]\n" "Zone:2, Sec#:0, Addr:0x00080000, Size:0x6000, Caps:0x7 [REW]\n" "Zone:2, Sec#:0, Addr:0x00086000, Size:0x6000, Caps:0x7 [REW]", &error); g_assert_no_error (error); g_assert (ret); g_free (tmp); /* invalid */ ret = dfu_target_parse_sectors (target, "Flash", NULL); g_assert (ret); ret = dfu_target_parse_sectors (target, "@Internal Flash /0x08000000", NULL); g_assert (!ret); ret = dfu_target_parse_sectors (target, "@Internal Flash /0x08000000/12*001a", NULL); g_assert (!ret); /* indicate a cipher being used */ g_assert_cmpint (dfu_target_get_cipher_kind (target), ==, DFU_CIPHER_KIND_NONE); ret = dfu_target_parse_sectors (target, "@Flash|XTEA", &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (dfu_target_get_cipher_kind (target), ==, DFU_CIPHER_KIND_XTEA); } static gboolean dfu_patch_create_from_strings (DfuPatch *patch, const gchar *dold, const gchar *dnew, GError **error) { guint32 sz1 = strlen (dold); guint32 sz2 = strlen (dnew); g_autoptr(GBytes) blob1 = g_bytes_new (dold, sz1); g_autoptr(GBytes) blob2 = g_bytes_new (dnew, sz2); g_debug ("compare:\n%s\n%s", dold, dnew); return dfu_patch_create (patch, blob1, blob2, error); } static void dfu_patch_merges_func (void) { const guint8 *data; gboolean ret; gsize sz; g_autoptr(DfuPatch) patch = dfu_patch_new (); g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; /* check merges happen */ ret = dfu_patch_create_from_strings (patch, "XXX", "YXY", &error); g_assert_no_error (error); g_assert (ret); blob = dfu_patch_export (patch, &error); g_assert_no_error (error); g_assert (ret); data = g_bytes_get_data (blob, &sz); g_assert_cmpint (data[0x00], ==, 'D'); g_assert_cmpint (data[0x01], ==, 'f'); g_assert_cmpint (data[0x02], ==, 'u'); g_assert_cmpint (data[0x03], ==, 'P'); g_assert_cmpint (data[0x04], ==, 0x00); /* reserved */ g_assert_cmpint (data[0x05], ==, 0x00); g_assert_cmpint (data[0x06], ==, 0x00); g_assert_cmpint (data[0x07], ==, 0x00); g_assert_cmpint (data[0x08 + 0x28], ==, 0x00); /* chunk1, offset */ g_assert_cmpint (data[0x09 + 0x28], ==, 0x00); g_assert_cmpint (data[0x0a + 0x28], ==, 0x00); g_assert_cmpint (data[0x0b + 0x28], ==, 0x00); g_assert_cmpint (data[0x0c + 0x28], ==, 0x03); /* chunk1, size */ g_assert_cmpint (data[0x0d + 0x28], ==, 0x00); g_assert_cmpint (data[0x0e + 0x28], ==, 0x00); g_assert_cmpint (data[0x0f + 0x28], ==, 0x00); g_assert_cmpint (data[0x10 + 0x28], ==, 0x00); /* reserved */ g_assert_cmpint (data[0x11 + 0x28], ==, 0x00); g_assert_cmpint (data[0x12 + 0x28], ==, 0x00); g_assert_cmpint (data[0x13 + 0x28], ==, 0x00); g_assert_cmpint (data[0x14 + 0x28], ==, 'Y'); g_assert_cmpint (data[0x15 + 0x28], ==, 'X'); g_assert_cmpint (data[0x16 + 0x28], ==, 'Y'); g_assert_cmpint (sz, ==, 48 /* hdr */ + 12 /* chunk */ + 3 /* data */); } static void dfu_patch_apply_func (void) { gboolean ret; g_autoptr(DfuPatch) patch = dfu_patch_new (); g_autoptr(GBytes) blob_new2 = NULL; g_autoptr(GBytes) blob_new3 = NULL; g_autoptr(GBytes) blob_new4 = NULL; g_autoptr(GBytes) blob_new = NULL; g_autoptr(GBytes) blob_old = NULL; g_autoptr(GBytes) blob_wrong = NULL; g_autoptr(GError) error = NULL; /* create a patch */ blob_old = g_bytes_new_static ("helloworldhelloworldhelloworldhelloworld", 40); blob_new = g_bytes_new_static ("XelloXorldhelloworldhelloworldhelloworlXXX", 42); ret = dfu_patch_create (patch, blob_old, blob_new, &error); g_assert_no_error (error); g_assert (ret); /* apply the patch */ blob_new2 = dfu_patch_apply (patch, blob_old, DFU_PATCH_APPLY_FLAG_NONE, &error); g_assert_no_error (error); g_assert (blob_new2 != NULL); g_assert_cmpint (g_bytes_compare (blob_new, blob_new2), ==, 0); /* check we force the patch to an unrelated blob */ blob_wrong = g_bytes_new_static ("wrongwrongwrongwrongwrongwrongwrongwrong", 40); blob_new3 = dfu_patch_apply (patch, blob_wrong, DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM, &error); g_assert_no_error (error); g_assert (blob_new3 != NULL); /* check we can't apply the patch to an unrelated blob */ blob_new4 = dfu_patch_apply (patch, blob_wrong, DFU_PATCH_APPLY_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert (blob_new4 == NULL); } static void dfu_patch_func (void) { const guint8 *data; gboolean ret; gsize sz; g_autoptr(DfuPatch) patch = dfu_patch_new (); g_autoptr(DfuPatch) patch2 = dfu_patch_new (); g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *serialized_str = NULL; /* create binary diff */ ret = dfu_patch_create_from_strings (patch, "XXX", "XYY", &error); g_assert_no_error (error); g_assert (ret); /* check we can serialize this object to a blob */ blob = dfu_patch_export (patch, &error); g_assert_no_error (error); g_assert (ret); data = g_bytes_get_data (blob, &sz); g_assert_cmpint (data[0x00], ==, 'D'); g_assert_cmpint (data[0x01], ==, 'f'); g_assert_cmpint (data[0x02], ==, 'u'); g_assert_cmpint (data[0x03], ==, 'P'); g_assert_cmpint (data[0x04], ==, 0x00); /* reserved */ g_assert_cmpint (data[0x05], ==, 0x00); g_assert_cmpint (data[0x06], ==, 0x00); g_assert_cmpint (data[0x07], ==, 0x00); g_assert_cmpint (data[0x08 + 0x28], ==, 0x01); /* chunk1, offset */ g_assert_cmpint (data[0x09 + 0x28], ==, 0x00); g_assert_cmpint (data[0x0a + 0x28], ==, 0x00); g_assert_cmpint (data[0x0b + 0x28], ==, 0x00); g_assert_cmpint (data[0x0c + 0x28], ==, 0x02); /* chunk1, size */ g_assert_cmpint (data[0x0d + 0x28], ==, 0x00); g_assert_cmpint (data[0x0e + 0x28], ==, 0x00); g_assert_cmpint (data[0x0f + 0x28], ==, 0x00); g_assert_cmpint (data[0x10 + 0x28], ==, 0x00); /* reserved */ g_assert_cmpint (data[0x11 + 0x28], ==, 0x00); g_assert_cmpint (data[0x12 + 0x28], ==, 0x00); g_assert_cmpint (data[0x13 + 0x28], ==, 0x00); g_assert_cmpint (data[0x14 + 0x28], ==, 'Y'); g_assert_cmpint (data[0x15 + 0x28], ==, 'Y'); g_assert_cmpint (sz, ==, 48 /* hdr */ + 12 /* chunk */ + 2 /* data */); /* try to load it from the serialized blob */ ret = dfu_patch_import (patch2, blob, &error); g_assert_no_error (error); g_assert (ret); serialized_str = dfu_patch_to_string (patch2); g_debug ("serialized blob %s", serialized_str); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* log everything */ g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); /* tests go here */ g_test_add_func ("/dfu/firmware{srec}", dfu_firmware_srec_func); g_test_add_func ("/dfu/patch", dfu_patch_func); g_test_add_func ("/dfu/patch{merges}", dfu_patch_merges_func); g_test_add_func ("/dfu/patch{apply}", dfu_patch_apply_func); g_test_add_func ("/dfu/enums", dfu_enums_func); g_test_add_func ("/dfu/target(DfuSe}", dfu_target_dfuse_func); g_test_add_func ("/dfu/cipher{xtea}", dfu_cipher_xtea_func); g_test_add_func ("/dfu/firmware{raw}", dfu_firmware_raw_func); g_test_add_func ("/dfu/firmware{dfu}", dfu_firmware_dfu_func); g_test_add_func ("/dfu/firmware{dfuse}", dfu_firmware_dfuse_func); g_test_add_func ("/dfu/firmware{xdfu}", dfu_firmware_xdfu_func); g_test_add_func ("/dfu/firmware{metadata}", dfu_firmware_metadata_func); g_test_add_func ("/dfu/firmware{intel-hex-offset}", dfu_firmware_intel_hex_offset_func); g_test_add_func ("/dfu/firmware{intel-hex}", dfu_firmware_intel_hex_func); g_test_add_func ("/dfu/firmware{intel-hex-signed}", dfu_firmware_intel_hex_signed_func); return g_test_run (); } fwupd-1.2.14/plugins/dfu/dfu-target-avr.c000066400000000000000000000540131402665037500201720ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-chunk.h" #include "dfu-common.h" #include "dfu-sector.h" #include "dfu-target-avr.h" #include "dfu-target-private.h" #include "dfu-device-private.h" #include "fwupd-error.h" /** * FU_QUIRKS_DFU_AVR_ALT_NAME: * @key: the AVR chip ID, e.g. `0x58200204` * @value: the UM0424 sector description, e.g. `@Flash/0x2000/1*248Kg` * * Assigns a sector description for the chip ID. This is required so fwupd can * program the user firmware avoiding the bootloader and for checking the total * element size. * * The chip ID can be found from a datasheet or using `dfu-tool list` when the * hardware is connected and in bootloader mode. * * Since: 1.0.1 */ #define FU_QUIRKS_DFU_AVR_ALT_NAME "DfuAltName" typedef struct { guint32 device_id; } DfuTargetAvrPrivate; G_DEFINE_TYPE_WITH_PRIVATE (DfuTargetAvr, dfu_target_avr, DFU_TYPE_TARGET) #define GET_PRIVATE(o) (dfu_target_avr_get_instance_private (o)) /* ATMEL AVR version of DFU: * http://www.atmel.com/Images/doc7618.pdf */ #define DFU_AVR_CMD_PROG_START 0x01 #define DFU_AVR_CMD_DISPLAY_DATA 0x03 #define DFU_AVR_CMD_WRITE_COMMAND 0x04 #define DFU_AVR_CMD_READ_COMMAND 0x05 #define DFU_AVR_CMD_CHANGE_BASE_ADDR 0x06 /* Atmel AVR32 version of DFU: * http://www.atmel.com/images/doc32131.pdf */ #define DFU_AVR32_GROUP_SELECT 0x06 /** SELECT */ #define DFU_AVR32_CMD_SELECT_MEMORY 0x03 #define DFU_AVR32_MEMORY_UNIT 0x00 #define DFU_AVR32_MEMORY_PAGE 0x01 #define DFU_AVR32_MEMORY_UNIT_FLASH 0x00 #define DFU_AVR32_MEMORY_UNIT_EEPROM 0x01 #define DFU_AVR32_MEMORY_UNIT_SECURITY 0x02 #define DFU_AVR32_MEMORY_UNIT_CONFIGURATION 0x03 #define DFU_AVR32_MEMORY_UNIT_BOOTLOADER 0x04 #define DFU_AVR32_MEMORY_UNIT_SIGNATURE 0x05 #define DFU_AVR32_MEMORY_UNIT_USER 0x06 #define DFU_AVR32_GROUP_DOWNLOAD 0x01 /** DOWNLOAD */ #define DFU_AVR32_CMD_PROGRAM_START 0x00 #define DFU_AVR32_GROUP_UPLOAD 0x03 /** UPLOAD */ #define DFU_AVR32_CMD_READ_MEMORY 0x00 #define DFU_AVR32_CMD_BLANK_CHECK 0x01 #define DFU_AVR32_GROUP_EXEC 0x04 /** EXEC */ #define DFU_AVR32_CMD_ERASE 0x00 #define DFU_AVR32_ERASE_EVERYTHING 0xff #define DFU_AVR32_CMD_START_APPLI 0x03 #define DFU_AVR32_START_APPLI_RESET 0x00 #define DFU_AVR32_START_APPLI_NO_RESET 0x01 #define ATMEL_64KB_PAGE 0x10000 #define ATMEL_MAX_TRANSFER_SIZE 0x0400 #define ATMEL_AVR_CONTROL_BLOCK_SIZE 32 #define ATMEL_AVR32_CONTROL_BLOCK_SIZE 64 #define ATMEL_MANUFACTURER_CODE1 0x58 #define ATMEL_MANUFACTURER_CODE2 0x1e static gboolean dfu_target_avr_mass_erase (DfuTarget *target, GError **error) { g_autoptr(GBytes) data_in = NULL; guint8 buf[3]; /* this takes a long time on some devices */ dfu_device_set_timeout (dfu_target_get_device (target), 5000); /* format buffer */ buf[0] = DFU_AVR32_GROUP_EXEC; buf[1] = DFU_AVR32_CMD_ERASE; buf[2] = 0xff; data_in = g_bytes_new_static (buf, sizeof(buf)); g_debug ("mass erasing"); dfu_target_set_action (target, FWUPD_STATUS_DEVICE_ERASE); if (!dfu_target_download_chunk (target, 0, data_in, error)) { g_prefix_error (error, "cannot mass-erase: "); return FALSE; } dfu_target_set_action (target, FWUPD_STATUS_IDLE); return TRUE; } static gboolean dfu_target_avr_attach (DfuTarget *target, GError **error) { guint8 buf[3]; g_autoptr(GBytes) data_empty = NULL; g_autoptr(GBytes) data_in = NULL; g_autoptr(GError) error_local = NULL; /* format buffer */ buf[0] = DFU_AVR32_GROUP_EXEC; buf[1] = DFU_AVR32_CMD_START_APPLI; buf[2] = DFU_AVR32_START_APPLI_RESET; data_in = g_bytes_new_static (buf, sizeof(buf)); if (!dfu_target_download_chunk (target, 0, data_in, &error_local)) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug ("ignoring as device rebooting: %s", error_local->message); return TRUE; } g_prefix_error (error, "cannot start application reset attach: "); return FALSE; } /* do zero-sized download to initiate the reset */ data_empty = g_bytes_new (NULL, 0); if (!dfu_target_download_chunk (target, 0, data_empty, &error_local)) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug ("ignoring as device rebooting: %s", error_local->message); return TRUE; } g_prefix_error (error, "cannot initiate reset for attach: "); return FALSE; } /* success */ return TRUE; } /** * dfu_target_avr_select_memory_unit: * @target: a #DfuTarget * @memory_unit: a unit, e.g. %DFU_AVR32_MEMORY_UNIT_FLASH * @error: a #GError, or %NULL * * Selects the memory unit for the device. * * Return value: %TRUE for success **/ static gboolean dfu_target_avr_select_memory_unit (DfuTarget *target, guint8 memory_unit, GError **error) { g_autoptr(GBytes) data_in = NULL; guint8 buf[4]; /* check legacy protocol quirk */ if (dfu_device_has_quirk (dfu_target_get_device (target), DFU_DEVICE_QUIRK_LEGACY_PROTOCOL)) { g_debug ("ignoring select memory unit as legacy protocol"); return TRUE; } /* format buffer */ buf[0] = DFU_AVR32_GROUP_SELECT; buf[1] = DFU_AVR32_CMD_SELECT_MEMORY; buf[2] = DFU_AVR32_MEMORY_UNIT; buf[3] = memory_unit; data_in = g_bytes_new_static (buf, sizeof(buf)); g_debug ("selecting memory unit 0x%02x", (guint) memory_unit); if (!dfu_target_download_chunk (target, 0, data_in, error)) { g_prefix_error (error, "cannot select memory unit: "); return FALSE; } return TRUE; } /** * dfu_target_avr_select_memory_page: * @target: a #DfuTarget * @memory_page: an address * @error: a #GError, or %NULL * * Selects the memory page for the AVR device. * * Return value: %TRUE for success **/ static gboolean dfu_target_avr_select_memory_page (DfuTarget *target, guint16 memory_page, GError **error) { g_autoptr(GBytes) data_in = NULL; guint8 buf[4]; /* check page not too large for protocol */ if (memory_page > 0xff) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot select memory page:0x%02x " "with FLIP protocol version 1", memory_page); return FALSE; } /* format buffer */ buf[0] = DFU_AVR_CMD_CHANGE_BASE_ADDR; buf[1] = 0x03; buf[2] = 0x00; buf[3] = memory_page & 0xff; data_in = g_bytes_new_static (buf, sizeof(buf)); g_debug ("selecting memory page 0x%01x", (guint) memory_page); if (!dfu_target_download_chunk (target, 0, data_in, error)) { g_prefix_error (error, "cannot select memory page: "); return FALSE; } return TRUE; } /** * dfu_target_avr32_select_memory_page: * @target: a #DfuTarget * @memory_page: an address * @error: a #GError, or %NULL * * Selects the memory page for the AVR32 device. * * Return value: %TRUE for success **/ static gboolean dfu_target_avr32_select_memory_page (DfuTarget *target, guint16 memory_page, GError **error) { g_autoptr(GBytes) data_in = NULL; guint8 buf[5]; /* format buffer */ buf[0] = DFU_AVR32_GROUP_SELECT; buf[1] = DFU_AVR32_CMD_SELECT_MEMORY; buf[2] = DFU_AVR32_MEMORY_PAGE; fu_common_write_uint16 (&buf[3], memory_page, G_BIG_ENDIAN); data_in = g_bytes_new_static (buf, sizeof(buf)); g_debug ("selecting memory page 0x%02x", (guint) memory_page); if (!dfu_target_download_chunk (target, 0, data_in, error)) { g_prefix_error (error, "cannot select memory page: "); return FALSE; } return TRUE; } /** * dfu_target_avr_read_memory * @target: a #DfuTarget * @addr_start: an address * @addr_end: an address * @error: a #GError, or %NULL * * Reads flash data from the device. * * Return value: %TRUE for success **/ static gboolean dfu_target_avr_read_memory (DfuTarget *target, guint16 addr_start, guint16 addr_end, GError **error) { g_autoptr(GBytes) data_in = NULL; guint8 buf[6]; /* format buffer */ buf[0] = DFU_AVR32_GROUP_UPLOAD; buf[1] = DFU_AVR32_CMD_READ_MEMORY; fu_common_write_uint16 (&buf[2], addr_start, G_BIG_ENDIAN); fu_common_write_uint16 (&buf[4], addr_end, G_BIG_ENDIAN); data_in = g_bytes_new_static (buf, sizeof(buf)); g_debug ("reading memory from 0x%04x to 0x%04x", (guint) addr_start, (guint) addr_end); if (!dfu_target_download_chunk (target, 0, data_in, error)) { g_prefix_error (error, "cannot read memory 0x%04x to 0x%04x: ", (guint) addr_start, (guint) addr_end); return FALSE; } return TRUE; } /** * dfu_target_avr_read_command: * @target: a #DfuTarget * @memory_unit: a unit, e.g. %DFU_AVR32_MEMORY_UNIT_FLASH * @error: a #GError, or %NULL * * Performs a read operation on the device. * * Return value: %TRUE for success **/ static gboolean dfu_target_avr_read_command (DfuTarget *target, guint8 page, guint8 addr, GError **error) { g_autoptr(GBytes) data_in = NULL; guint8 buf[3]; /* format buffer */ buf[0] = DFU_AVR_CMD_READ_COMMAND; buf[1] = page; buf[2] = addr; data_in = g_bytes_new_static (buf, sizeof(buf)); g_debug ("read command page:0x%02x addr:0x%02x", (guint) page, (guint) addr); if (!dfu_target_download_chunk (target, 0, data_in, error)) { g_prefix_error (error, "cannot read command page: "); return FALSE; } return TRUE; } /** * dfu_target_avr32_get_chip_signature: * @target: a #DfuTarget * @error: a #GError, or %NULL * * Gets the chip signature for the AVR32 device. * * Return value: a 4-byte %GBytes object for success, else %NULL **/ static GBytes * dfu_target_avr32_get_chip_signature (DfuTarget *target, GError **error) { /* select unit, and request 4 bytes */ if (!dfu_target_avr_select_memory_unit (target, DFU_AVR32_MEMORY_UNIT_SIGNATURE, error)) return NULL; if (!dfu_target_avr32_select_memory_page (target, 0x00, error)) return NULL; if (!dfu_target_avr_read_memory (target, 0x00, 0x03, error)) return NULL; /* get data back */ return dfu_target_upload_chunk (target, 0x00, 0, error); } /** * dfu_target_avr_get_chip_signature: * @target: a #DfuTarget * @error: a #GError, or %NULL * * Gets the chip signature for the AVR device. * * Return value: a 4-byte %GBytes object for success, else %NULL **/ static GBytes * dfu_target_avr_get_chip_signature (DfuTarget *target, GError **error) { struct { guint8 page; guint addr; } signature_locations[] = { { 0x01, 0x30 }, { 0x01, 0x31 }, { 0x01, 0x60 }, { 0x01, 0x61 }, { 0xff, 0xff } }; g_autoptr(GPtrArray) chunks = NULL; /* we have to request this one byte at a time */ chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); for (guint i = 0; signature_locations[i].page != 0xff; i++) { g_autoptr(GBytes) chunk_byte = NULL; /* request a single byte */ if (!dfu_target_avr_read_command (target, signature_locations[i].page, signature_locations[i].addr, error)) return NULL; /* get data back */ chunk_byte = dfu_target_upload_chunk (target, 0x00, 0x01, error); if (chunk_byte == NULL) return NULL; if (g_bytes_get_size (chunk_byte) != 1) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot read signature memory page:0x%02x " "addr:0x%02x, got 0x%02x bytes", (guint) signature_locations[i].page, (guint) signature_locations[i].addr, (guint) g_bytes_get_size (chunk_byte)); return NULL; } g_ptr_array_add (chunks, g_steal_pointer (&chunk_byte)); } return dfu_utils_bytes_join_array (chunks); } static gboolean dfu_target_avr_setup (DfuTarget *target, GError **error) { DfuDevice *device; DfuTargetAvr *target_avr = DFU_TARGET_AVR (target); DfuTargetAvrPrivate *priv = GET_PRIVATE (target_avr); const gchar *quirk_str; const guint8 *buf; gsize sz; guint32 device_id_be; g_autofree gchar *chip_id = NULL; g_autofree gchar *chip_id_prefixed = NULL; g_autoptr(GBytes) chunk_sig = NULL; /* already done */ if (priv->device_id > 0x0) return TRUE; /* different methods for AVR vs. AVR32 */ if (dfu_device_has_quirk (dfu_target_get_device (target), DFU_DEVICE_QUIRK_LEGACY_PROTOCOL)) { chunk_sig = dfu_target_avr_get_chip_signature (target, error); if (chunk_sig == NULL) return FALSE; } else { chunk_sig = dfu_target_avr32_get_chip_signature (target, error); if (chunk_sig == NULL) { g_prefix_error (error, "failed to get chip signature: "); return FALSE; } } /* get data back */ buf = g_bytes_get_data (chunk_sig, &sz); if (sz != 4) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot read config memory, got 0x%02x bytes", (guint) sz); return FALSE; } memcpy (&device_id_be, buf, 4); priv->device_id = GINT32_FROM_BE (device_id_be); if (buf[0] == ATMEL_MANUFACTURER_CODE1) { chip_id = g_strdup_printf ("0x%08x", (guint) priv->device_id); } else if (buf[0] == ATMEL_MANUFACTURER_CODE2) { chip_id = g_strdup_printf ("0x%06x", (guint) priv->device_id >> 8); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot read config vendor, got 0x%08x, " "expected 0x%02x or 0x%02x", (guint) priv->device_id, (guint) ATMEL_MANUFACTURER_CODE1, (guint) ATMEL_MANUFACTURER_CODE2); return FALSE; } /* set the alt-name using the device ID */ dfu_device_set_chip_id (dfu_target_get_device (target), chip_id); device = dfu_target_get_device (target); chip_id_prefixed = g_strdup_printf ("AvrChipId=%s", chip_id); quirk_str = fu_quirks_lookup_by_id (fu_device_get_quirks (FU_DEVICE (device)), chip_id_prefixed, FU_QUIRKS_DFU_AVR_ALT_NAME); if (quirk_str == NULL) { dfu_device_remove_attribute (dfu_target_get_device (target), DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD); dfu_device_remove_attribute (dfu_target_get_device (target), DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "DeviceID %s is not supported", chip_id); return FALSE; } dfu_target_set_alt_name (target, quirk_str); return TRUE; } static gboolean dfu_target_avr_download_element (DfuTarget *target, DfuElement *element, DfuTargetTransferFlags flags, GError **error) { DfuSector *sector; GBytes *blob; const guint8 *data; gsize header_sz = ATMEL_AVR32_CONTROL_BLOCK_SIZE; guint16 page_last = G_MAXUINT16; guint32 address; guint32 address_offset = 0x0; g_autoptr(GPtrArray) chunks = NULL; const guint8 footer[] = { 0x00, 0x00, 0x00, 0x00, /* CRC */ 16, /* len */ 'D', 'F', 'U', /* signature */ 0x01, 0x10, /* version */ 0xff, 0xff, /* vendor ID */ 0xff, 0xff, /* product ID */ 0xff, 0xff }; /* release */ /* select a memory and erase everything */ if (!dfu_target_avr_select_memory_unit (target, dfu_target_get_alt_setting (target), error)) return FALSE; if (!dfu_target_avr_mass_erase (target, error)) return FALSE; /* verify the element isn't larger than the target size */ blob = dfu_element_get_contents (element); sector = dfu_target_get_sector_default (target); if (sector == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sector defined for target"); return FALSE; } address = dfu_element_get_address (element) & ~0x80000000; if (address < dfu_sector_get_address (sector)) { address_offset = dfu_sector_get_address (sector) - address; g_warning ("firmware element starts at 0x%x but sector " "starts at 0x%x, so offsetting by 0x%x (bootloader?)", (guint) address, (guint) dfu_sector_get_address (sector), (guint) address_offset); } if (g_bytes_get_size (blob) + address_offset > dfu_sector_get_size (sector)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "element was larger than sector size: 0x%x", (guint) dfu_sector_get_size (sector)); return FALSE; } /* the original AVR protocol uses a half-size control block */ if (dfu_device_has_quirk (dfu_target_get_device (target), DFU_DEVICE_QUIRK_LEGACY_PROTOCOL)) { header_sz = ATMEL_AVR_CONTROL_BLOCK_SIZE; } /* chunk up the memory space into pages */ data = g_bytes_get_data (blob, NULL); chunks = fu_chunk_array_new (data + address_offset, g_bytes_get_size (blob) - address_offset, dfu_sector_get_address (sector), ATMEL_64KB_PAGE, ATMEL_MAX_TRANSFER_SIZE); /* update UI */ dfu_target_set_action (target, FWUPD_STATUS_DEVICE_WRITE); /* process each chunk */ for (guint i = 0; i < chunks->len; i++) { const FuChunk *chk = g_ptr_array_index (chunks, i); g_autofree guint8 *buf = NULL; g_autoptr(GBytes) chunk_tmp = NULL; /* select page if required */ if (chk->page != page_last) { if (dfu_device_has_quirk (dfu_target_get_device (target), DFU_DEVICE_QUIRK_LEGACY_PROTOCOL)) { if (!dfu_target_avr_select_memory_page (target, chk->page, error)) return FALSE; } else { if (!dfu_target_avr32_select_memory_page (target, chk->page, error)) return FALSE; } page_last = chk->page; } /* create chk with header and footer */ buf = g_malloc0 (chk->data_sz + header_sz + sizeof(footer)); buf[0] = DFU_AVR32_GROUP_DOWNLOAD; buf[1] = DFU_AVR32_CMD_PROGRAM_START; fu_common_write_uint16 (&buf[2], chk->address, G_BIG_ENDIAN); fu_common_write_uint16 (&buf[4], chk->address + chk->data_sz - 1, G_BIG_ENDIAN); memcpy (&buf[header_sz], chk->data, chk->data_sz); memcpy (&buf[header_sz + chk->data_sz], footer, sizeof(footer)); /* download data */ chunk_tmp = g_bytes_new_static (buf, chk->data_sz + header_sz + sizeof(footer)); g_debug ("sending %" G_GSIZE_FORMAT " bytes to the hardware", g_bytes_get_size (chunk_tmp)); if (!dfu_target_download_chunk (target, i, chunk_tmp, error)) return FALSE; /* update UI */ dfu_target_set_percentage (target, i + 1, chunks->len); } /* done */ dfu_target_set_percentage_raw (target, 100); dfu_target_set_action (target, FWUPD_STATUS_IDLE); return TRUE; } static DfuElement * dfu_target_avr_upload_element (DfuTarget *target, guint32 address, gsize expected_size, gsize maximum_size, GError **error) { guint16 page_last = G_MAXUINT16; guint chunk_valid = G_MAXUINT; g_autoptr(DfuElement) element = NULL; g_autoptr(GBytes) contents = NULL; g_autoptr(GBytes) contents_truncated = NULL; g_autoptr(GPtrArray) blobs = NULL; g_autoptr(GPtrArray) chunks = NULL; DfuSector *sector; /* select unit */ if (!dfu_target_avr_select_memory_unit (target, dfu_target_get_alt_setting (target), error)) return NULL; /* verify the element isn't lower than the flash area */ sector = dfu_target_get_sector_default (target); if (sector == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sector defined for target"); return NULL; } if (address < dfu_sector_get_address (sector)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot read from below sector start"); return NULL; } /* the flash starts at 0x80000000, but is indexed from zero */ address &= ~0x80000000; /* chunk up the memory space into pages */ chunks = fu_chunk_array_new (NULL, maximum_size, address, ATMEL_64KB_PAGE, ATMEL_MAX_TRANSFER_SIZE); /* update UI */ dfu_target_set_action (target, FWUPD_STATUS_DEVICE_READ); /* process each chunk */ blobs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); for (guint i = 0; i < chunks->len; i++) { GBytes *blob_tmp = NULL; const FuChunk *chk = g_ptr_array_index (chunks, i); /* select page if required */ if (chk->page != page_last) { if (dfu_device_has_quirk (dfu_target_get_device (target), DFU_DEVICE_QUIRK_LEGACY_PROTOCOL)) { if (!dfu_target_avr_select_memory_page (target, chk->page, error)) return NULL; } else { if (!dfu_target_avr32_select_memory_page (target, chk->page, error)) return NULL; } page_last = chk->page; } /* prepare to read */ if (!dfu_target_avr_read_memory (target, chk->address, chk->address + chk->data_sz - 1, error)) return NULL; /* upload data */ g_debug ("requesting %i bytes from the hardware for chunk 0x%x", ATMEL_MAX_TRANSFER_SIZE, i); blob_tmp = dfu_target_upload_chunk (target, i, ATMEL_MAX_TRANSFER_SIZE, error); if (blob_tmp == NULL) return NULL; g_ptr_array_add (blobs, blob_tmp); /* this page has valid data */ if (!fu_common_bytes_is_empty (blob_tmp)) { g_debug ("chunk %u has data (page %" G_GUINT32_FORMAT ")", i, chk->page); chunk_valid = i; } else { g_debug ("chunk %u is empty", i); } /* update UI */ dfu_target_set_percentage (target, i + 1, chunks->len); } /* done */ dfu_target_set_percentage_raw (target, 100); dfu_target_set_action (target, FWUPD_STATUS_IDLE); /* truncate the image if any sectors are empty, i.e. all 0xff */ if (chunk_valid == G_MAXUINT) { g_debug ("all %u chunks are empty", blobs->len); g_ptr_array_set_size (chunks, 0); } else if (blobs->len != chunk_valid + 1) { g_debug ("truncating chunks from %u to %u", blobs->len, chunk_valid + 1); g_ptr_array_set_size (blobs, chunk_valid + 1); } /* create element of required size */ contents = dfu_utils_bytes_join_array (blobs); if (expected_size > 0 && g_bytes_get_size (contents) > expected_size) { contents_truncated = g_bytes_new_from_bytes (contents, 0x0, expected_size); } else { contents_truncated = g_bytes_ref (contents); } element = dfu_element_new (); dfu_element_set_address (element, address | 0x80000000); /* flash */ dfu_element_set_contents (element, contents_truncated); return g_steal_pointer (&element); } static void dfu_target_avr_init (DfuTargetAvr *target_avr) { } static void dfu_target_avr_class_init (DfuTargetAvrClass *klass) { DfuTargetClass *klass_target = DFU_TARGET_CLASS (klass); klass_target->setup = dfu_target_avr_setup; klass_target->attach = dfu_target_avr_attach; klass_target->mass_erase = dfu_target_avr_mass_erase; klass_target->upload_element = dfu_target_avr_upload_element; klass_target->download_element = dfu_target_avr_download_element; } DfuTarget * dfu_target_avr_new (void) { DfuTarget *target; target = g_object_new (DFU_TYPE_TARGET_AVR, NULL); return target; } fwupd-1.2.14/plugins/dfu/dfu-target-avr.h000066400000000000000000000007231402665037500201760ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-target.h" G_BEGIN_DECLS #define DFU_TYPE_TARGET_AVR (dfu_target_avr_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuTargetAvr, dfu_target_avr, DFU, TARGET_AVR, DfuTarget) struct _DfuTargetAvrClass { DfuTargetClass parent_class; }; DfuTarget *dfu_target_avr_new (void); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-target-private.h000066400000000000000000000031351402665037500210600ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "dfu-device.h" #include "dfu-target.h" #include "dfu-sector.h" G_BEGIN_DECLS DfuTarget *dfu_target_new (void); GBytes *dfu_target_upload_chunk (DfuTarget *target, guint16 index, gsize buf_sz, GError **error); gboolean dfu_target_download_chunk (DfuTarget *target, guint16 index, GBytes *bytes, GError **error); gboolean dfu_target_attach (DfuTarget *target, GError **error); void dfu_target_set_alt_idx (DfuTarget *target, guint8 alt_idx); void dfu_target_set_alt_setting (DfuTarget *target, guint8 alt_setting); /* for the other implementations */ void dfu_target_set_action (DfuTarget *target, FwupdStatus action); void dfu_target_set_percentage_raw (DfuTarget *target, guint percentage); void dfu_target_set_percentage (DfuTarget *target, guint value, guint total); void dfu_target_set_alt_name (DfuTarget *target, const gchar *alt_name); void dfu_target_set_device (DfuTarget *target, DfuDevice *device); DfuDevice *dfu_target_get_device (DfuTarget *target); gboolean dfu_target_check_status (DfuTarget *target, GError **error); DfuSector *dfu_target_get_sector_for_addr (DfuTarget *target, guint32 addr); /* export this just for the self tests */ gboolean dfu_target_parse_sectors (DfuTarget *target, const gchar *alt_name, GError **error); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-target-stm.c000066400000000000000000000261351402665037500202110ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "dfu-common.h" #include "dfu-sector.h" #include "dfu-target-stm.h" #include "dfu-target-private.h" #include "fwupd-error.h" G_DEFINE_TYPE (DfuTargetStm, dfu_target_stm, DFU_TYPE_TARGET) /* STMicroelectronics STM32 version of DFU: * www.st.com/resource/en/application_note/cd00264379.pdf */ #define DFU_STM_CMD_GET_COMMAND 0x00 #define DFU_STM_CMD_SET_ADDRESS_POINTER 0x21 #define DFU_STM_CMD_ERASE 0x41 #define DFU_STM_CMD_READ_UNPROTECT 0x92 static gboolean dfu_target_stm_attach (DfuTarget *target, GError **error) { g_autoptr(GBytes) bytes_tmp = g_bytes_new (NULL, 0); return dfu_target_download_chunk (target, 2, bytes_tmp, error); } static gboolean dfu_target_stm_mass_erase (DfuTarget *target, GError **error) { GBytes *data_in; guint8 buf[1]; /* format buffer */ buf[0] = DFU_STM_CMD_ERASE; data_in = g_bytes_new_static (buf, sizeof(buf)); if (!dfu_target_download_chunk (target, 0, data_in, error)) { g_prefix_error (error, "cannot mass-erase: "); return FALSE; } /* 2nd check required to get error code */ return dfu_target_check_status (target, error); } /** * dfu_target_stm_set_address: * @target: a #DfuTarget * @address: memory address * @error: a #GError, or %NULL * * Sets the address used for the next download or upload request. * * Return value: %TRUE for success **/ static gboolean dfu_target_stm_set_address (DfuTarget *target, guint32 address, GError **error) { GBytes *data_in; guint8 buf[5]; /* format buffer */ buf[0] = DFU_STM_CMD_SET_ADDRESS_POINTER; memcpy (buf + 1, &address, 4); data_in = g_bytes_new_static (buf, sizeof(buf)); if (!dfu_target_download_chunk (target, 0, data_in, error)) { g_prefix_error (error, "cannot set address 0x%x: ", address); return FALSE; } /* 2nd check required to get error code */ g_debug ("doing actual check status"); return dfu_target_check_status (target, error); } static DfuElement * dfu_target_stm_upload_element (DfuTarget *target, guint32 address, gsize expected_size, gsize maximum_size, GError **error) { DfuDevice *device = dfu_target_get_device (target); DfuSector *sector; DfuElement *element = NULL; GBytes *chunk_tmp; guint32 offset = address; guint percentage_size = expected_size > 0 ? expected_size : maximum_size; gsize total_size = 0; guint16 transfer_size = dfu_device_get_transfer_size (device); g_autoptr(GBytes) contents = NULL; g_autoptr(GBytes) contents_truncated = NULL; g_autoptr(GPtrArray) chunks = NULL; /* for DfuSe devices we need to handle the address manually */ sector = dfu_target_get_sector_for_addr (target, offset); if (sector == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no memory sector at 0x%04x", (guint) offset); return NULL; } g_debug ("using sector %u for read of %x", dfu_sector_get_id (sector), offset); if (!dfu_sector_has_cap (sector, DFU_SECTOR_CAP_READABLE)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "memory sector at 0x%04x is not readable", (guint) offset); return NULL; } /* update UI */ dfu_target_set_action (target, FWUPD_STATUS_DEVICE_READ); /* manually set the sector address */ g_debug ("setting DfuSe address to 0x%04x", (guint) offset); if (!dfu_target_stm_set_address (target, offset, error)) return NULL; /* abort back to IDLE */ if (!dfu_device_abort (device, error)) return NULL; /* get all the chunks from the hardware */ chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); for (guint16 idx = 0; idx < G_MAXUINT16; idx++) { guint32 chunk_size; /* read chunk of data -- ST uses wBlockNum=0 for DfuSe commands * and wBlockNum=1 is reserved */ chunk_tmp = dfu_target_upload_chunk (target, idx + 2, 0, /* device transfer size */ error); if (chunk_tmp == NULL) return NULL; /* add to array */ chunk_size = (guint32) g_bytes_get_size (chunk_tmp); g_debug ("got #%04x chunk @0x%x of size %" G_GUINT32_FORMAT, idx, offset, chunk_size); g_ptr_array_add (chunks, chunk_tmp); total_size += chunk_size; offset += chunk_size; /* update UI */ if (chunk_size > 0) dfu_target_set_percentage (target, total_size, percentage_size); /* detect short write as EOF */ if (chunk_size < transfer_size) break; /* more data than we needed */ if (maximum_size > 0 && total_size > maximum_size) break; } /* abort back to IDLE */ if (!dfu_device_abort (device, error)) return NULL; /* check final size */ if (expected_size > 0) { if (total_size < expected_size) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid size, got %" G_GSIZE_FORMAT ", " "expected %" G_GSIZE_FORMAT , total_size, expected_size); return NULL; } } /* done */ dfu_target_set_percentage_raw (target, 100); dfu_target_set_action (target, FWUPD_STATUS_IDLE); /* create new image */ contents = dfu_utils_bytes_join_array (chunks); if (expected_size > 0) contents_truncated = g_bytes_new_from_bytes (contents, 0, expected_size); else contents_truncated = g_bytes_ref (contents); element = dfu_element_new (); dfu_element_set_contents (element, contents_truncated); dfu_element_set_address (element, address); return element; } /** * dfu_target_stm_erase_address: * @target: a #DfuTarget * @address: memory address * @error: a #GError, or %NULL * * Erases a memory sector at a given address. * * Return value: %TRUE for success **/ static gboolean dfu_target_stm_erase_address (DfuTarget *target, guint32 address, GError **error) { GBytes *data_in; guint8 buf[5]; /* format buffer */ buf[0] = DFU_STM_CMD_ERASE; memcpy (buf + 1, &address, 4); data_in = g_bytes_new_static (buf, sizeof(buf)); if (!dfu_target_download_chunk (target, 0, data_in, error)) { g_prefix_error (error, "cannot erase address 0x%x: ", address); return FALSE; } /* 2nd check required to get error code */ g_debug ("doing actual check status"); return dfu_target_check_status (target, error); } static gboolean dfu_target_stm_download_element (DfuTarget *target, DfuElement *element, DfuTargetTransferFlags flags, GError **error) { DfuDevice *device = dfu_target_get_device (target); DfuSector *sector; GBytes *bytes; guint nr_chunks; guint zone_last = G_MAXUINT; guint16 transfer_size = dfu_device_get_transfer_size (device); g_autoptr(GPtrArray) sectors_array = NULL; g_autoptr(GHashTable) sectors_hash = NULL; /* round up as we have to transfer incomplete blocks */ bytes = dfu_element_get_contents (element); nr_chunks = (guint) ceil ((gdouble) g_bytes_get_size (bytes) / (gdouble) transfer_size); if (nr_chunks == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "zero-length firmware"); return FALSE; } /* 1st pass: work out which sectors need erasing */ sectors_array = g_ptr_array_new (); sectors_hash = g_hash_table_new (g_direct_hash, g_direct_equal); for (guint i = 0; i < nr_chunks; i++) { guint32 offset_dev; /* for DfuSe devices we need to handle the erase and setting * the sectory address manually */ offset_dev = dfu_element_get_address (element) + (i * transfer_size); sector = dfu_target_get_sector_for_addr (target, offset_dev); if (sector == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no memory sector at 0x%04x", (guint) offset_dev); return FALSE; } if (!dfu_sector_has_cap (sector, DFU_SECTOR_CAP_WRITEABLE)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "memory sector at 0x%04x is not writable", (guint) offset_dev); return FALSE; } /* if it's erasable and not yet blanked */ if (dfu_sector_has_cap (sector, DFU_SECTOR_CAP_ERASEABLE) && g_hash_table_lookup (sectors_hash, sector) == NULL) { g_hash_table_insert (sectors_hash, sector, GINT_TO_POINTER (1)); g_ptr_array_add (sectors_array, sector); g_debug ("marking sector 0x%04x-%04x to be erased", dfu_sector_get_address (sector), dfu_sector_get_address (sector) + dfu_sector_get_size (sector)); } } /* 2nd pass: actually erase sectors */ dfu_target_set_action (target, FWUPD_STATUS_DEVICE_ERASE); for (guint i = 0; i < sectors_array->len; i++) { sector = g_ptr_array_index (sectors_array, i); g_debug ("erasing sector at 0x%04x", dfu_sector_get_address (sector)); if (!dfu_target_stm_erase_address (target, dfu_sector_get_address (sector), error)) return FALSE; dfu_target_set_percentage (target, i + 1, sectors_array->len); } dfu_target_set_percentage_raw (target, 100); dfu_target_set_action (target, FWUPD_STATUS_IDLE); /* 3rd pass: write data */ dfu_target_set_action (target, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < nr_chunks; i++) { gsize length; guint32 offset; guint32 offset_dev; g_autoptr(GBytes) bytes_tmp = NULL; /* caclulate the offset into the element data */ offset = i * transfer_size; offset_dev = dfu_element_get_address (element) + offset; /* for DfuSe devices we need to set the address manually */ sector = dfu_target_get_sector_for_addr (target, offset_dev); g_assert (sector != NULL); /* manually set the sector address */ if (dfu_sector_get_zone (sector) != zone_last) { g_debug ("setting address to 0x%04x", (guint) offset_dev); if (!dfu_target_stm_set_address (target, (guint32) offset_dev, error)) return FALSE; zone_last = dfu_sector_get_zone (sector); } /* we have to write one final zero-sized chunk for EOF */ length = g_bytes_get_size (bytes) - offset; if (length > transfer_size) length = transfer_size; bytes_tmp = g_bytes_new_from_bytes (bytes, offset, length); g_debug ("writing sector at 0x%04x (0x%" G_GSIZE_FORMAT ")", offset_dev, g_bytes_get_size (bytes_tmp)); /* ST uses wBlockNum=0 for DfuSe commands and wBlockNum=1 is reserved */ if (!dfu_target_download_chunk (target, (guint8) (i + 2), bytes_tmp, error)) return FALSE; /* getting the status moves the state machine to DNLOAD-IDLE */ if (!dfu_target_check_status (target, error)) return FALSE; /* update UI */ dfu_target_set_percentage (target, offset, g_bytes_get_size (bytes)); } /* done */ dfu_target_set_percentage_raw (target, 100); dfu_target_set_action (target, FWUPD_STATUS_IDLE); /* success */ return TRUE; } static void dfu_target_stm_init (DfuTargetStm *target_stm) { } static void dfu_target_stm_class_init (DfuTargetStmClass *klass) { DfuTargetClass *klass_target = DFU_TARGET_CLASS (klass); klass_target->attach = dfu_target_stm_attach; klass_target->mass_erase = dfu_target_stm_mass_erase; klass_target->upload_element = dfu_target_stm_upload_element; klass_target->download_element = dfu_target_stm_download_element; } DfuTarget * dfu_target_stm_new (void) { DfuTarget *target; target = g_object_new (DFU_TYPE_TARGET_STM, NULL); return target; } fwupd-1.2.14/plugins/dfu/dfu-target-stm.h000066400000000000000000000007231402665037500202110ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-target.h" G_BEGIN_DECLS #define DFU_TYPE_TARGET_STM (dfu_target_stm_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuTargetStm, dfu_target_stm, DFU, TARGET_STM, DfuTarget) struct _DfuTargetStmClass { DfuTargetClass parent_class; }; DfuTarget *dfu_target_stm_new (void); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-target.c000066400000000000000000001077411402665037500174130ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * SECTION:dfu-target * @short_description: Object representing a DFU-capable target * * This object allows uploading and downloading an image onto a * specific DFU-capable target. * * You only need to use this in preference to #DfuDevice if you only * want to update one target on the device. Most users will want to * update all the targets on the device at the same time. * * See also: #DfuDevice, #DfuImage */ #include "config.h" #include #include #include "dfu-common.h" #include "dfu-device-private.h" #include "dfu-sector-private.h" #include "dfu-target-private.h" #include "fwupd-error.h" static void dfu_target_finalize (GObject *object); typedef struct { DfuDevice *device; /* not refcounted */ DfuCipherKind cipher_kind; gboolean done_setup; guint8 alt_setting; guint8 alt_idx; gchar *alt_name; gchar *alt_name_for_display; GPtrArray *sectors; /* of DfuSector */ guint old_percentage; FwupdStatus old_action; } DfuTargetPrivate; enum { SIGNAL_PERCENTAGE_CHANGED, SIGNAL_ACTION_CHANGED, SIGNAL_LAST }; static guint signals [SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE_WITH_PRIVATE (DfuTarget, dfu_target, G_TYPE_OBJECT) #define GET_PRIVATE(o) (dfu_target_get_instance_private (o)) static void dfu_target_class_init (DfuTargetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); /** * DfuTarget::percentage-changed: * @device: the #DfuTarget instance that emitted the signal * @percentage: the new percentage * * The ::percentage-changed signal is emitted when the percentage changes. **/ signals [SIGNAL_PERCENTAGE_CHANGED] = g_signal_new ("percentage-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (DfuTargetClass, percentage_changed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); /** * DfuTarget::action-changed: * @device: the #DfuTarget instance that emitted the signal * @action: the new FwupdStatus * * The ::action-changed signal is emitted when the high level action changes. **/ signals [SIGNAL_ACTION_CHANGED] = g_signal_new ("action-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (DfuTargetClass, action_changed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); object_class->finalize = dfu_target_finalize; } static void dfu_target_init (DfuTarget *target) { DfuTargetPrivate *priv = GET_PRIVATE (target); priv->sectors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->old_percentage = G_MAXUINT; priv->old_action = FWUPD_STATUS_IDLE; } static void dfu_target_finalize (GObject *object) { DfuTarget *target = DFU_TARGET (object); DfuTargetPrivate *priv = GET_PRIVATE (target); g_free (priv->alt_name); g_free (priv->alt_name_for_display); g_ptr_array_unref (priv->sectors); /* we no longer care */ if (priv->device != NULL) { g_object_remove_weak_pointer (G_OBJECT (priv->device), (gpointer *) &priv->device); } G_OBJECT_CLASS (dfu_target_parent_class)->finalize (object); } static gchar * dfu_target_sectors_to_string (DfuTarget *target) { DfuTargetPrivate *priv = GET_PRIVATE (target); GString *str = g_string_new (""); for (guint i = 0; i < priv->sectors->len; i++) { DfuSector *sector = g_ptr_array_index (priv->sectors, i); g_autofree gchar *tmp = dfu_sector_to_string (sector); g_string_append_printf (str, "%s\n", tmp); } if (str->len > 0) g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } DfuSector * dfu_target_get_sector_for_addr (DfuTarget *target, guint32 addr) { DfuTargetPrivate *priv = GET_PRIVATE (target); for (guint i = 0; i < priv->sectors->len; i++) { DfuSector *sector = g_ptr_array_index (priv->sectors, i); if (addr < dfu_sector_get_address (sector)) continue; if (addr > dfu_sector_get_address (sector) + dfu_sector_get_size (sector)) continue; return sector; } return NULL; } static gboolean dfu_target_parse_sector (DfuTarget *target, const gchar *dfuse_sector_id, guint32 *addr, guint16 zone, guint16 number, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); DfuSectorCap cap = DFU_SECTOR_CAP_NONE; gchar *tmp; guint32 addr_offset = 0; guint64 nr_sectors; guint64 sector_size; /* parse # of sectors */ nr_sectors = g_ascii_strtoull (dfuse_sector_id, &tmp, 10); if (nr_sectors > 999) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid number of sectors: %s", dfuse_sector_id); return FALSE; } /* check this is the delimiter */ if (tmp[0] != '*') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid sector ID: %s", dfuse_sector_id); return FALSE; } /* parse sector size */ sector_size = g_ascii_strtoull (tmp + 1, &tmp, 10); if (sector_size > 999) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid sector size: %s", dfuse_sector_id); return FALSE; } /* get multiplier */ switch (tmp[0]) { case 'B': /* byte */ case ' ': /* byte, ST reference bootloader :/ */ break; case 'K': /* Kilo */ sector_size *= 0x400; break; case 'M': /* Mega */ sector_size *= 0x100000 ; break; default: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid sector multiplier: %s", tmp); return FALSE; } /* get sector type */ switch (tmp[1]) { case 'a': cap = DFU_SECTOR_CAP_READABLE; break; case 'b': cap = DFU_SECTOR_CAP_ERASEABLE; break; case 'c': cap = DFU_SECTOR_CAP_READABLE | DFU_SECTOR_CAP_ERASEABLE; break; case 'd': cap = DFU_SECTOR_CAP_WRITEABLE; break; case 'e': cap = DFU_SECTOR_CAP_READABLE | DFU_SECTOR_CAP_WRITEABLE; break; case 'f': cap = DFU_SECTOR_CAP_ERASEABLE | DFU_SECTOR_CAP_WRITEABLE; break; case 'g': cap = DFU_SECTOR_CAP_READABLE | DFU_SECTOR_CAP_ERASEABLE | DFU_SECTOR_CAP_WRITEABLE; break; default: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid sector type: %s", tmp); return FALSE; } /* add all the sectors */ for (guint i = 0; i < nr_sectors; i++) { DfuSector *sector; sector = dfu_sector_new (*addr + addr_offset, (guint32) sector_size, (guint32) ((nr_sectors * sector_size) - addr_offset), zone, number, cap); g_ptr_array_add (priv->sectors, sector); addr_offset += dfu_sector_get_size (sector); } /* update for next sector */ *addr += addr_offset; return TRUE; } gboolean dfu_target_parse_sectors (DfuTarget *target, const gchar *alt_name, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); g_autofree gchar *str_debug = NULL; g_auto(GStrv) zones = NULL; /* not set */ if (alt_name == NULL) return TRUE; /* do we have any hint for the cipher */ if (g_strstr_len (alt_name, -1, "|XTEA") != NULL) priv->cipher_kind = DFU_CIPHER_KIND_XTEA; /* From the Neo Freerunner */ if (g_str_has_prefix (alt_name, "RAM 0x")) { DfuSector *sector; guint64 addr_tmp; addr_tmp = g_ascii_strtoull (alt_name + 6, NULL, 16); if (addr_tmp == 0 || addr_tmp > G_MAXUINT32) return FALSE; g_debug ("RAM description, so parsing"); sector = dfu_sector_new ((guint32) addr_tmp, 0x0, /* size */ 0x0, /* size_left */ 0x0, /* zone */ 0x0, /* number */ DFU_SECTOR_CAP_READABLE | DFU_SECTOR_CAP_WRITEABLE); g_ptr_array_add (priv->sectors, sector); } /* not a DfuSe alternative name */ if (alt_name[0] != '@') return TRUE; /* clear any existing zones */ g_ptr_array_set_size (priv->sectors, 0); /* parse zones */ zones = g_strsplit (alt_name, "/", -1); g_free (priv->alt_name_for_display); priv->alt_name_for_display = g_strdup (g_strchomp (zones[0] + 1)); for (guint i = 1; zones[i] != NULL; i += 2) { guint32 addr; guint64 addr_tmp; g_auto(GStrv) sectors = NULL; /* parse address */ if (!g_str_has_prefix (zones[i], "0x")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No sector address"); return FALSE; } addr_tmp = g_ascii_strtoull (zones[i] + 2, NULL, 16); if (addr_tmp > G_MAXUINT32) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Sector address too large"); return FALSE; } addr = (guint32) addr_tmp; /* no sectors?! */ if (zones[i+1] == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No sector section"); return FALSE; } /* parse sectors */ sectors = g_strsplit (zones[i+1], ",", -1); for (guint16 j = 0; sectors[j] != NULL; j++) { if (!dfu_target_parse_sector (target, sectors[j], &addr, (i - 1) / 2, j, error)) { g_prefix_error (error, "Failed to parse: '%s': ", sectors[j]); return FALSE; } } } /* success */ str_debug = dfu_target_sectors_to_string (target); g_debug ("%s", str_debug); return TRUE; } /** * dfu_target_new: (skip) * * Creates a new DFU target, which represents an alt-setting on a * DFU-capable device. * * Return value: a #DfuTarget **/ DfuTarget * dfu_target_new (void) { DfuTarget *target; target = g_object_new (DFU_TYPE_TARGET, NULL); return target; } /** * dfu_target_get_sectors: * @target: a #GUsbDevice * * Gets the sectors exported by the target. * * Return value: (transfer none) (element-type DfuSector): sectors **/ GPtrArray * dfu_target_get_sectors (DfuTarget *target) { DfuTargetPrivate *priv = GET_PRIVATE (target); g_return_val_if_fail (DFU_IS_TARGET (target), NULL); return priv->sectors; } /** * dfu_target_get_sector_default: * @target: a #GUsbDevice * * Gets the default (first) sector exported by the target. * * Return value: (transfer none): a #DfuSector, or %NULL **/ DfuSector * dfu_target_get_sector_default (DfuTarget *target) { DfuTargetPrivate *priv = GET_PRIVATE (target); g_return_val_if_fail (DFU_IS_TARGET (target), NULL); if (priv->sectors->len == 0) return NULL; return DFU_SECTOR (g_ptr_array_index (priv->sectors, 0)); } /** * dfu_target_status_to_error_msg: * @status: a #DfuStatus, e.g. %DFU_STATUS_ERR_ERASE * * Converts an enumerated value to an error description. * * Return value: a string **/ static const gchar * dfu_target_status_to_error_msg (DfuStatus status) { if (status == DFU_STATUS_OK) return "No error condition is present"; if (status == DFU_STATUS_ERR_TARGET) return "Firmware is not for designed this device"; if (status == DFU_STATUS_ERR_FILE) return "Firmware is for this device but fails verification"; if (status == DFU_STATUS_ERR_WRITE) return "Device is unable to write memory"; if (status == DFU_STATUS_ERR_ERASE) return "Memory erase function failed"; if (status == DFU_STATUS_ERR_CHECK_ERASED) return "Memory erase check failed"; if (status == DFU_STATUS_ERR_PROG) return "Program memory function failed"; if (status == DFU_STATUS_ERR_VERIFY) return "Programmed memory failed verification"; if (status == DFU_STATUS_ERR_ADDRESS) return "Cannot program memory due to address out of range"; if (status == DFU_STATUS_ERR_NOTDONE) return "Received zero-length download but data is incomplete"; if (status == DFU_STATUS_ERR_FIRMWARE) return "Device firmware is corrupt"; if (status == DFU_STATUS_ERR_VENDOR) return "Vendor-specific error"; if (status == DFU_STATUS_ERR_USBR) return "Device detected unexpected USB reset signaling"; if (status == DFU_STATUS_ERR_POR) return "Device detected unexpected power on reset"; if (status == DFU_STATUS_ERR_UNKNOWN) return "Something unexpected went wrong"; if (status == DFU_STATUS_ERR_STALLDPKT) return "Device stalled an unexpected request"; return NULL; } gboolean dfu_target_check_status (DfuTarget *target, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); DfuStatus status; /* get the status */ if (!dfu_device_refresh (priv->device, error)) return FALSE; /* wait for dfuDNBUSY to not be set */ if (dfu_device_get_version (priv->device) == DFU_VERSION_DFUSE) { while (dfu_device_get_state (priv->device) == DFU_STATE_DFU_DNBUSY) { g_debug ("waiting for DFU_STATE_DFU_DNBUSY to clear"); g_usleep (dfu_device_get_download_timeout (priv->device) * 1000); if (!dfu_device_refresh (priv->device, error)) return FALSE; } } /* not in an error state */ if (dfu_device_get_state (priv->device) != DFU_STATE_DFU_ERROR) return TRUE; /* STM32-specific long errors */ status = dfu_device_get_status (priv->device); if (dfu_device_get_version (priv->device) == DFU_VERSION_DFUSE) { if (status == DFU_STATUS_ERR_VENDOR) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Read protection is active"); return FALSE; } if (status == DFU_STATUS_ERR_TARGET) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Address is wrong or unsupported"); return FALSE; } } /* use a proper error description */ g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, dfu_target_status_to_error_msg (status)); return FALSE; } /** * dfu_target_use_alt_setting: * @target: a #DfuTarget * @error: a #GError, or %NULL * * Opens a DFU-capable target. * * Return value: %TRUE for success **/ static gboolean dfu_target_use_alt_setting (DfuTarget *target, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (priv->device)); g_autoptr(GError) error_local = NULL; g_return_val_if_fail (DFU_IS_TARGET (target), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* ensure interface is claimed */ if (!dfu_device_ensure_interface (priv->device, error)) return FALSE; /* use the correct setting */ if (!dfu_device_is_runtime (priv->device)) { if (!g_usb_device_set_interface_alt (usb_device, (gint) dfu_device_get_interface (priv->device), (gint) priv->alt_setting, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot set alternate setting 0x%02x on interface %i: %s", priv->alt_setting, dfu_device_get_interface (priv->device), error_local->message); return FALSE; } } return TRUE; } void dfu_target_set_alt_name (DfuTarget *target, const gchar *alt_name) { DfuTargetPrivate *priv = GET_PRIVATE (target); g_free (priv->alt_name); priv->alt_name = g_strdup (alt_name); } void dfu_target_set_device (DfuTarget *target, DfuDevice *device) { DfuTargetPrivate *priv = GET_PRIVATE (target); g_set_object (&priv->device, device); /* if we try to ref the target and destroy the device */ g_object_add_weak_pointer (G_OBJECT (priv->device), (gpointer *) &priv->device); } /** * dfu_target_setup: * @target: a #DfuTarget * @error: a #GError, or %NULL * * Opens a DFU-capable target. * * Return value: %TRUE for success **/ gboolean dfu_target_setup (DfuTarget *target, GError **error) { DfuTargetClass *klass = DFU_TARGET_GET_CLASS (target); DfuTargetPrivate *priv = GET_PRIVATE (target); g_return_val_if_fail (DFU_IS_TARGET (target), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* already done */ if (priv->done_setup) return TRUE; /* superclassed */ if (klass->setup != NULL) { if (!klass->setup (target, error)) return FALSE; } /* get string */ if (priv->alt_idx != 0x00 && priv->alt_name == NULL) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (priv->device)); priv->alt_name = g_usb_device_get_string_descriptor (usb_device, priv->alt_idx, NULL); } /* parse the DfuSe format according to UM0424 */ if (!dfu_target_parse_sectors (target, priv->alt_name, error)) return FALSE; /* add a dummy entry */ if (priv->sectors->len == 0) { DfuSector *sector; sector = dfu_sector_new (0x0, /* addr */ 0x0, /* size */ 0x0, /* size_left */ 0x0, /* zone */ 0x0, /* number */ DFU_SECTOR_CAP_READABLE | DFU_SECTOR_CAP_WRITEABLE); g_debug ("no UM0424 sector description in %s", priv->alt_name); g_ptr_array_add (priv->sectors, sector); } priv->done_setup = TRUE; return TRUE; } /** * dfu_target_mass_erase: * @target: a #DfuTarget * @error: a #GError, or %NULL * * Mass erases the device clearing all SRAM and EEPROM memory. * * IMPORTANT: This only works on STM32 devices from ST and AVR32 devices from Atmel. * * Return value: %TRUE for success **/ gboolean dfu_target_mass_erase (DfuTarget *target, GError **error) { DfuTargetClass *klass = DFU_TARGET_GET_CLASS (target); if (!dfu_target_setup (target, error)) return FALSE; if (klass->mass_erase == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "mass erase not supported"); return FALSE; } return klass->mass_erase (target, error); } gboolean dfu_target_download_chunk (DfuTarget *target, guint16 index, GBytes *bytes, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (priv->device)); g_autoptr(GError) error_local = NULL; gsize actual_length; /* low level packet debugging */ if (g_getenv ("FWUPD_DFU_VERBOSE") != NULL) { gsize sz = 0; const guint8 *data = g_bytes_get_data (bytes, &sz); for (gsize i = 0; i < sz; i++) g_print ("Message: m[%" G_GSIZE_FORMAT "] = 0x%02x\n", i, (guint) data[i]); } if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, DFU_REQUEST_DNLOAD, index, dfu_device_get_interface (priv->device), (guint8 *) g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), &actual_length, dfu_device_get_timeout (priv->device), NULL, &error_local)) { /* refresh the error code */ dfu_device_error_fixup (priv->device, &error_local); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot download data: %s", error_local->message); return FALSE; } /* for STM32 devices, the action only occurs when we do GetStatus */ if (dfu_device_get_version (priv->device) == DFU_VERSION_DFUSE) { if (!dfu_device_refresh (priv->device, error)) return FALSE; } /* wait for the device to write contents to the EEPROM */ if (g_bytes_get_size (bytes) == 0 && dfu_device_get_download_timeout (priv->device) > 0) { dfu_target_set_action (target, FWUPD_STATUS_IDLE); dfu_target_set_action (target, FWUPD_STATUS_DEVICE_BUSY); } if (dfu_device_get_download_timeout (priv->device) > 0) { g_debug ("sleeping for %ums…", dfu_device_get_download_timeout (priv->device)); g_usleep (dfu_device_get_download_timeout (priv->device) * 1000); } /* find out if the write was successful */ if (!dfu_device_refresh (priv->device, error)) return FALSE; g_assert (actual_length == g_bytes_get_size (bytes)); return TRUE; } GBytes * dfu_target_upload_chunk (DfuTarget *target, guint16 index, gsize buf_sz, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (priv->device)); g_autoptr(GError) error_local = NULL; guint8 *buf; gsize actual_length; /* unset */ if (buf_sz == 0) buf_sz = (gsize) dfu_device_get_transfer_size (priv->device); buf = g_new0 (guint8, buf_sz); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, DFU_REQUEST_UPLOAD, index, dfu_device_get_interface (priv->device), buf, buf_sz, &actual_length, dfu_device_get_timeout (priv->device), NULL, &error_local)) { /* refresh the error code */ dfu_device_error_fixup (priv->device, &error_local); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot upload data: %s", error_local->message); return NULL; } /* low level packet debugging */ if (g_getenv ("FWUPD_DFU_VERBOSE") != NULL) { for (gsize i = 0; i < actual_length; i++) g_print ("Message: r[%" G_GSIZE_FORMAT "] = 0x%02x\n", i, (guint) buf[i]); } return g_bytes_new_take (buf, actual_length); } void dfu_target_set_alt_idx (DfuTarget *target, guint8 alt_idx) { DfuTargetPrivate *priv = GET_PRIVATE (target); priv->alt_idx = alt_idx; } void dfu_target_set_alt_setting (DfuTarget *target, guint8 alt_setting) { DfuTargetPrivate *priv = GET_PRIVATE (target); priv->alt_setting = alt_setting; } void dfu_target_set_action (DfuTarget *target, FwupdStatus action) { DfuTargetPrivate *priv = GET_PRIVATE (target); /* unchanged */ if (priv->old_action == action) return; if (priv->old_action != FWUPD_STATUS_IDLE && action != FWUPD_STATUS_IDLE) { g_debug ("ignoring action %s as %s already set and not idle", fwupd_status_to_string (action), fwupd_status_to_string (priv->old_action)); return; } g_debug ("setting action %s", fwupd_status_to_string (action)); g_signal_emit (target, signals[SIGNAL_ACTION_CHANGED], 0, action); priv->old_action = action; } DfuDevice * dfu_target_get_device (DfuTarget *target) { DfuTargetPrivate *priv = GET_PRIVATE (target); return priv->device; } void dfu_target_set_percentage_raw (DfuTarget *target, guint percentage) { DfuTargetPrivate *priv = GET_PRIVATE (target); if (percentage == priv->old_percentage) return; g_debug ("setting percentage %u%% of %s", percentage, fwupd_status_to_string (priv->old_action)); g_signal_emit (target, signals[SIGNAL_PERCENTAGE_CHANGED], 0, percentage); priv->old_percentage = percentage; } void dfu_target_set_percentage (DfuTarget *target, guint value, guint total) { guint percentage; g_return_if_fail (total > 0); percentage = (value * 100) / total; if (percentage >= 100) return; dfu_target_set_percentage_raw (target, percentage); } gboolean dfu_target_attach (DfuTarget *target, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); DfuTargetClass *klass = DFU_TARGET_GET_CLASS (target); /* ensure populated */ if (!dfu_target_setup (target, error)) return FALSE; /* implemented as part of a superclass */ if (klass->attach != NULL) return klass->attach (target, error); /* normal DFU mode just needs a bus reset */ return dfu_device_reset (priv->device, error); } static DfuElement * dfu_target_upload_element_dfu (DfuTarget *target, guint32 address, gsize expected_size, gsize maximum_size, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); DfuElement *element = NULL; GBytes *chunk_tmp; guint32 offset = 0; guint percentage_size = expected_size > 0 ? expected_size : maximum_size; gsize total_size = 0; guint16 transfer_size = dfu_device_get_transfer_size (priv->device); g_autoptr(GBytes) contents = NULL; g_autoptr(GPtrArray) chunks = NULL; /* update UI */ dfu_target_set_action (target, FWUPD_STATUS_DEVICE_READ); /* get all the chunks from the hardware */ chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); for (guint16 idx = 0; idx < G_MAXUINT16; idx++) { guint32 chunk_size; /* read chunk of data */ chunk_tmp = dfu_target_upload_chunk (target, idx, 0, /* device transfer size */ error); if (chunk_tmp == NULL) return NULL; /* keep a sum of all the chunks */ chunk_size = (guint32) g_bytes_get_size (chunk_tmp); total_size += chunk_size; offset += chunk_size; /* add to array */ g_debug ("got #%04x chunk of size %" G_GUINT32_FORMAT, idx, chunk_size); g_ptr_array_add (chunks, chunk_tmp); /* update UI */ if (chunk_size > 0) dfu_target_set_percentage (target, total_size, percentage_size); /* detect short write as EOF */ if (chunk_size < transfer_size) break; } /* check final size */ if (expected_size > 0) { if (total_size != expected_size) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid size, got %" G_GSIZE_FORMAT ", " "expected %" G_GSIZE_FORMAT , total_size, expected_size); return NULL; } } /* done */ dfu_target_set_percentage_raw (target, 100); dfu_target_set_action (target, FWUPD_STATUS_IDLE); /* create new image */ contents = dfu_utils_bytes_join_array (chunks); element = dfu_element_new (); dfu_element_set_contents (element, contents); return element; } static DfuElement * dfu_target_upload_element (DfuTarget *target, guint32 address, gsize expected_size, gsize maximum_size, GError **error) { DfuTargetClass *klass = DFU_TARGET_GET_CLASS (target); /* implemented as part of a superclass */ if (klass->upload_element != NULL) { return klass->upload_element (target, address, expected_size, maximum_size, error); } return dfu_target_upload_element_dfu (target, address, expected_size, maximum_size, error); } static guint32 dfu_target_get_size_of_zone (DfuTarget *target, guint16 zone) { DfuTargetPrivate *priv = GET_PRIVATE (target); guint32 len = 0; for (guint i = 0; i < priv->sectors->len; i++) { DfuSector *sector = g_ptr_array_index (priv->sectors, i); if (dfu_sector_get_zone (sector) != zone) continue; len += dfu_sector_get_size (sector); } return len; } /** * dfu_target_upload: * @target: a #DfuTarget * @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY * @error: a #GError, or %NULL * * Uploads firmware from the target to the host. * * Return value: (transfer full): the uploaded image, or %NULL for error **/ DfuImage * dfu_target_upload (DfuTarget *target, DfuTargetTransferFlags flags, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); DfuSector *sector; guint16 zone_cur; guint32 zone_size = 0; guint32 zone_last = G_MAXUINT; g_autoptr(DfuImage) image = NULL; g_return_val_if_fail (DFU_IS_TARGET (target), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* ensure populated */ if (!dfu_target_setup (target, error)) return NULL; /* can the target do this? */ if (!dfu_device_can_upload (priv->device)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "target cannot do uploading"); return NULL; } /* use correct alt */ if (!dfu_target_use_alt_setting (target, error)) return NULL; /* no open?! */ if (priv->sectors->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sectors defined for target"); return NULL; } /* create a new image */ image = dfu_image_new (); dfu_image_set_name (image, priv->alt_name); dfu_image_set_alt_setting (image, priv->alt_setting); /* get all the sectors for the device */ for (guint i = 0; i < priv->sectors->len; i++) { g_autoptr(DfuElement) element = NULL; /* only upload to the start of any zone:sector */ sector = g_ptr_array_index (priv->sectors, i); zone_cur = dfu_sector_get_zone (sector); if (zone_cur == zone_last) continue; /* get the size of the entire continuous zone */ zone_size = dfu_target_get_size_of_zone (target, zone_cur); zone_last = zone_cur; /* get the first element from the hardware */ g_debug ("starting upload from 0x%08x (0x%04x)", dfu_sector_get_address (sector), zone_size); element = dfu_target_upload_element (target, dfu_sector_get_address (sector), 0, /* expected */ zone_size, /* maximum */ error); if (element == NULL) return NULL; /* this element was uploaded okay */ dfu_image_add_element (image, element); } /* success */ return g_object_ref (image); } static gchar * _g_bytes_compare_verbose (GBytes *bytes1, GBytes *bytes2) { const guint8 *data1; const guint8 *data2; gsize length1; gsize length2; data1 = g_bytes_get_data (bytes1, &length1); data2 = g_bytes_get_data (bytes2, &length2); /* not the same length */ if (length1 != length2) { return g_strdup_printf ("got %" G_GSIZE_FORMAT " bytes, " "expected %" G_GSIZE_FORMAT, length1, length2); } /* return 00 01 02 03 */ for (guint i = 0; i < length1; i++) { if (data1[i] != data2[i]) { return g_strdup_printf ("got 0x%02x, expected 0x%02x @ 0x%04x", data1[i], data2[i], i); } } return NULL; } static gboolean dfu_target_download_element_dfu (DfuTarget *target, DfuElement *element, DfuTargetTransferFlags flags, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); GBytes *bytes; guint16 nr_chunks; guint16 transfer_size = dfu_device_get_transfer_size (priv->device); /* round up as we have to transfer incomplete blocks */ bytes = dfu_element_get_contents (element); nr_chunks = (guint) ceil ((gdouble) g_bytes_get_size (bytes) / (gdouble) transfer_size); if (nr_chunks == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "zero-length firmware"); return FALSE; } dfu_target_set_action (target, FWUPD_STATUS_DEVICE_WRITE); for (guint16 i = 0; i < nr_chunks + 1; i++) { gsize length; guint32 offset; g_autoptr(GBytes) bytes_tmp = NULL; /* caclulate the offset into the element data */ offset = i * transfer_size; /* we have to write one final zero-sized chunk for EOF */ if (i < nr_chunks) { length = g_bytes_get_size (bytes) - offset; if (length > transfer_size) length = transfer_size; bytes_tmp = g_bytes_new_from_bytes (bytes, offset, length); } else { bytes_tmp = g_bytes_new (NULL, 0); } g_debug ("writing #%04x chunk of size %" G_GSIZE_FORMAT, i, g_bytes_get_size (bytes_tmp)); if (!dfu_target_download_chunk (target, i, bytes_tmp, error)) return FALSE; /* update UI */ dfu_target_set_percentage (target, offset, g_bytes_get_size (bytes)); } /* done */ dfu_target_set_percentage_raw (target, 100); dfu_target_set_action (target, FWUPD_STATUS_IDLE); /* success */ return TRUE; } static gboolean dfu_target_download_element (DfuTarget *target, DfuElement *element, DfuTargetTransferFlags flags, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); DfuTargetClass *klass = DFU_TARGET_GET_CLASS (target); /* implemented as part of a superclass */ if (klass->download_element != NULL) { if (!klass->download_element (target, element, flags, error)) return FALSE; } else { if (!dfu_target_download_element_dfu (target, element, flags, error)) return FALSE; } /* verify */ if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY && dfu_device_has_attribute (priv->device, DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD)) { GBytes *bytes; GBytes *bytes_tmp; g_autoptr(DfuElement) element_tmp = NULL; dfu_target_set_action (target, FWUPD_STATUS_DEVICE_VERIFY); bytes = dfu_element_get_contents (element); element_tmp = dfu_target_upload_element (target, dfu_element_get_address (element), g_bytes_get_size (bytes), g_bytes_get_size (bytes), error); if (element_tmp == NULL) return FALSE; bytes_tmp = dfu_element_get_contents (element_tmp); if (g_bytes_compare (bytes_tmp, bytes) != 0) { g_autofree gchar *bytes_cmp_str = NULL; bytes_cmp_str = _g_bytes_compare_verbose (bytes_tmp, bytes); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "verify failed: %s", bytes_cmp_str); return FALSE; } dfu_target_set_action (target, FWUPD_STATUS_IDLE); } return TRUE; } /** * dfu_target_download: * @target: a #DfuTarget * @image: a #DfuImage * @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY * @error: a #GError, or %NULL * * Downloads firmware from the host to the target, optionally verifying * the transfer. * * Return value: %TRUE for success **/ gboolean dfu_target_download (DfuTarget *target, DfuImage *image, DfuTargetTransferFlags flags, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); GPtrArray *elements; gboolean ret; g_return_val_if_fail (DFU_IS_TARGET (target), FALSE); g_return_val_if_fail (DFU_IS_IMAGE (image), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* ensure populated */ if (!dfu_target_setup (target, error)) return FALSE; /* can the target do this? */ if (!dfu_device_can_download (priv->device)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "target cannot do downloading"); return FALSE; } /* use correct alt */ if (!dfu_target_use_alt_setting (target, error)) return FALSE; /* download all elements in the image to the device */ elements = dfu_image_get_elements (image); if (elements->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no image elements"); return FALSE; } for (guint i = 0; i < elements->len; i++) { DfuElement *element = dfu_image_get_element (image, (guint8) i); g_debug ("downloading element at 0x%04x", dfu_element_get_address (element)); /* auto-detect missing firmware address -- this assumes * that the first target is the main program memory and that * there is only one element in the firmware file */ if (flags & DFU_TARGET_TRANSFER_FLAG_ADDR_HEURISTIC && dfu_element_get_address (element) == 0x0 && elements->len == 1 && priv->sectors->len > 0) { DfuSector *sector = g_ptr_array_index (priv->sectors, 0); g_debug ("fixing up firmware address from 0x0 to 0x%x", dfu_sector_get_address (sector)); dfu_element_set_address (element, dfu_sector_get_address (sector)); } /* download to device */ ret = dfu_target_download_element (target, element, flags, error); if (!ret) return FALSE; } /* success */ return TRUE; } /** * dfu_target_get_alt_setting: * @target: a #DfuTarget * * Gets the alternate setting to use for this interface. * * Return value: the alternative setting, typically zero **/ guint8 dfu_target_get_alt_setting (DfuTarget *target) { DfuTargetPrivate *priv = GET_PRIVATE (target); g_return_val_if_fail (DFU_IS_TARGET (target), 0xff); return priv->alt_setting; } /** * dfu_target_get_alt_name: * @target: a #DfuTarget * @error: a #GError, or %NULL * * Gets the alternate setting name to use for this interface. * * Return value: the alternative setting name, typically %NULL **/ const gchar * dfu_target_get_alt_name (DfuTarget *target, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); g_return_val_if_fail (DFU_IS_TARGET (target), NULL); /* ensure populated */ if (!dfu_target_setup (target, error)) return NULL; /* nothing */ if (priv->alt_name == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no alt-name"); return NULL; } return priv->alt_name; } /** * dfu_target_get_alt_name_for_display: * @target: a #DfuTarget * @error: a #GError, or %NULL * * Gets the alternate setting name to use for this interface that can be * shown on the display. * * Return value: the alternative setting name **/ const gchar * dfu_target_get_alt_name_for_display (DfuTarget *target, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); g_return_val_if_fail (DFU_IS_TARGET (target), NULL); /* ensure populated */ if (!dfu_target_setup (target, error)) return NULL; /* nothing */ if (priv->alt_name_for_display == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no alt-name for display"); return NULL; } return priv->alt_name_for_display; } /** * dfu_target_get_cipher_kind: * @target: a #DfuTarget * * Gets the cipher used for data sent to this interface. * * Return value: the cipher, typically %DFU_CIPHER_KIND_NONE **/ DfuCipherKind dfu_target_get_cipher_kind (DfuTarget *target) { DfuTargetPrivate *priv = GET_PRIVATE (target); g_return_val_if_fail (DFU_IS_TARGET (target), 0); return priv->cipher_kind; } fwupd-1.2.14/plugins/dfu/dfu-target.h000066400000000000000000000057041402665037500174140ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include #include "dfu-common.h" #include "dfu-image.h" #include "dfu-sector.h" #include "fwupd-enums.h" G_BEGIN_DECLS #define DFU_TYPE_TARGET (dfu_target_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuTarget, dfu_target, DFU, TARGET, GUsbDevice) /** * DfuTargetTransferFlags: * @DFU_TARGET_TRANSFER_FLAG_NONE: No flags set * @DFU_TARGET_TRANSFER_FLAG_VERIFY: Verify the download once complete * @DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID: Allow downloading images with wildcard VIDs * @DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID: Allow downloading images with wildcard PIDs * @DFU_TARGET_TRANSFER_FLAG_ANY_CIPHER: Allow any cipher kinds to be downloaded * @DFU_TARGET_TRANSFER_FLAG_ADDR_HEURISTIC: Automatically detect the address to use * * The optional flags used for transferring firmware. **/ typedef enum { DFU_TARGET_TRANSFER_FLAG_NONE = 0, DFU_TARGET_TRANSFER_FLAG_VERIFY = (1 << 0), DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID = (1 << 4), DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID = (1 << 5), DFU_TARGET_TRANSFER_FLAG_ANY_CIPHER = (1 << 6), DFU_TARGET_TRANSFER_FLAG_ADDR_HEURISTIC = (1 << 7), /*< private >*/ DFU_TARGET_TRANSFER_FLAG_LAST } DfuTargetTransferFlags; struct _DfuTargetClass { GUsbDeviceClass parent_class; void (*percentage_changed) (DfuTarget *target, guint percentage); void (*action_changed) (DfuTarget *target, FwupdStatus action); gboolean (*setup) (DfuTarget *target, GError **error); gboolean (*attach) (DfuTarget *target, GError **error); gboolean (*detach) (DfuTarget *target, GError **error); gboolean (*mass_erase) (DfuTarget *target, GError **error); DfuElement *(*upload_element) (DfuTarget *target, guint32 address, gsize expected_size, gsize maximum_size, GError **error); gboolean (*download_element) (DfuTarget *target, DfuElement *element, DfuTargetTransferFlags flags, GError **error); }; GPtrArray *dfu_target_get_sectors (DfuTarget *target); DfuSector *dfu_target_get_sector_default (DfuTarget *target); guint8 dfu_target_get_alt_setting (DfuTarget *target); const gchar *dfu_target_get_alt_name (DfuTarget *target, GError **error); const gchar *dfu_target_get_alt_name_for_display (DfuTarget *target, GError **error); DfuImage *dfu_target_upload (DfuTarget *target, DfuTargetTransferFlags flags, GError **error); gboolean dfu_target_setup (DfuTarget *target, GError **error); gboolean dfu_target_download (DfuTarget *target, DfuImage *image, DfuTargetTransferFlags flags, GError **error); gboolean dfu_target_mass_erase (DfuTarget *target, GError **error); DfuCipherKind dfu_target_get_cipher_kind (DfuTarget *target); G_END_DECLS fwupd-1.2.14/plugins/dfu/dfu-tool.c000066400000000000000000002050251402665037500170740ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "dfu-cipher-xtea.h" #include "dfu-device-private.h" #include "dfu-patch.h" #include "dfu-sector.h" #include "fu-device-locker.h" #include "fu-progressbar.h" #include "fwupd-error.h" typedef struct { GCancellable *cancellable; GPtrArray *cmd_array; gboolean force; gchar *device_vid_pid; guint16 transfer_size; FuProgressbar *progressbar; FuQuirks *quirks; } DfuToolPrivate; static void dfu_tool_print_indent (const gchar *title, const gchar *message, guint indent) { for (gsize i = 0; i < indent; i++) g_print (" "); g_print ("%s:", title); for (gsize i = strlen (title) + indent; i < 15; i++) g_print (" "); g_print ("%s\n", message); } static void dfu_tool_private_free (DfuToolPrivate *priv) { if (priv == NULL) return; g_free (priv->device_vid_pid); g_object_unref (priv->progressbar); g_object_unref (priv->cancellable); g_object_unref (priv->quirks); if (priv->cmd_array != NULL) g_ptr_array_unref (priv->cmd_array); g_free (priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(DfuToolPrivate, dfu_tool_private_free) #pragma clang diagnostic pop typedef gboolean (*FuUtilPrivateCb) (DfuToolPrivate *util, gchar **values, GError **error); typedef struct { gchar *name; gchar *arguments; gchar *description; FuUtilPrivateCb callback; } FuUtilItem; static void dfu_tool_item_free (FuUtilItem *item) { g_free (item->name); g_free (item->arguments); g_free (item->description); g_free (item); } static gint dfu_tool_sort_command_name_cb (FuUtilItem **item1, FuUtilItem **item2) { return g_strcmp0 ((*item1)->name, (*item2)->name); } static void dfu_tool_add (GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, FuUtilPrivateCb callback) { g_auto(GStrv) names = NULL; g_return_if_fail (name != NULL); g_return_if_fail (description != NULL); g_return_if_fail (callback != NULL); /* add each one */ names = g_strsplit (name, ",", -1); for (guint i = 0; names[i] != NULL; i++) { FuUtilItem *item = g_new0 (FuUtilItem, 1); item->name = g_strdup (names[i]); if (i == 0) { item->description = g_strdup (description); } else { /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */ item->description = g_strdup_printf (_("Alias to %s"), names[0]); } item->arguments = g_strdup (arguments); item->callback = callback; g_ptr_array_add (array, item); } } static gchar * dfu_tool_get_descriptions (GPtrArray *array) { gsize len; const gsize max_len = 31; FuUtilItem *item; GString *string; /* print each command */ string = g_string_new (""); for (guint i = 0; i < array->len; i++) { item = g_ptr_array_index (array, i); g_string_append (string, " "); g_string_append (string, item->name); len = strlen (item->name) + 2; if (item->arguments != NULL) { g_string_append (string, " "); g_string_append (string, item->arguments); len += strlen (item->arguments) + 1; } if (len < max_len) { for (guint j = len; j < max_len + 1; j++) g_string_append_c (string, ' '); g_string_append (string, item->description); g_string_append_c (string, '\n'); } else { g_string_append_c (string, '\n'); for (guint j = 0; j < max_len + 1; j++) g_string_append_c (string, ' '); g_string_append (string, item->description); g_string_append_c (string, '\n'); } } /* remove trailing newline */ if (string->len > 0) g_string_set_size (string, string->len - 1); return g_string_free (string, FALSE); } static gboolean dfu_tool_run (DfuToolPrivate *priv, const gchar *command, gchar **values, GError **error) { /* find command */ for (guint i = 0; i < priv->cmd_array->len; i++) { FuUtilItem *item = g_ptr_array_index (priv->cmd_array, i); if (g_strcmp0 (item->name, command) == 0) return item->callback (priv, values, error); } /* not found */ g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, /* TRANSLATORS: error message */ _("Command not found")); return FALSE; } static DfuDevice * dfu_tool_get_default_device (DfuToolPrivate *priv, GError **error) { g_autoptr(GUsbContext) usb_context = NULL; g_autoptr(GPtrArray) devices = NULL; /* get all the DFU devices */ usb_context = g_usb_context_new (error); if (usb_context == NULL) return NULL; g_usb_context_enumerate (usb_context); /* we specified it manually */ if (priv->device_vid_pid != NULL) { gchar *tmp; guint64 pid; guint64 vid; g_autoptr(DfuDevice) device = NULL; g_autoptr(GUsbDevice) usb_device = NULL; /* parse */ vid = g_ascii_strtoull (priv->device_vid_pid, &tmp, 16); if (vid == 0 || vid > G_MAXUINT16) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid format of VID:PID"); return NULL; } if (tmp[0] != ':') { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid format of VID:PID"); return NULL; } pid = g_ascii_strtoull (tmp + 1, NULL, 16); if (pid == 0 || pid > G_MAXUINT16) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid format of VID:PID"); return NULL; } /* find device */ usb_device = g_usb_context_find_by_vid_pid (usb_context, (guint16) vid, (guint16) pid, error); if (usb_device == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no device matches for %04x:%04x", (guint) vid, (guint) pid); return NULL; } device = dfu_device_new (usb_device); fu_device_set_quirks (FU_DEVICE (device), priv->quirks); dfu_device_set_usb_context (device, usb_context); return device; } /* auto-detect first device */ devices = g_usb_context_get_devices (usb_context); for (guint i = 0; i < devices->len; i++) { GUsbDevice *usb_device = g_ptr_array_index (devices, i); g_autoptr(DfuDevice) device = dfu_device_new (usb_device); fu_device_set_quirks (FU_DEVICE (device), priv->quirks); dfu_device_set_usb_context (device, usb_context); if (fu_device_probe (FU_DEVICE (device), NULL)) return g_steal_pointer (&device); } /* failed */ g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no DFU devices found"); return NULL; } static gboolean dfu_tool_set_vendor (DfuToolPrivate *priv, gchar **values, GError **error) { guint64 tmp; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILE VID" " -- e.g. `firmware.dfu 273f"); return FALSE; } /* open */ file = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* parse VID */ tmp = g_ascii_strtoull (values[1], NULL, 16); if (tmp == 0 || tmp > G_MAXUINT16) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse VID '%s'", values[1]); return FALSE; } dfu_firmware_set_vid (firmware, (guint16) tmp); /* write out new file */ return dfu_firmware_write_file (firmware, file, error); } static gboolean dfu_tool_set_product (DfuToolPrivate *priv, gchar **values, GError **error) { guint64 tmp; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILE PID" " -- e.g. `firmware.dfu 1004"); return FALSE; } /* open */ file = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* parse VID */ tmp = g_ascii_strtoull (values[1], NULL, 16); if (tmp == 0 || tmp > G_MAXUINT16) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse PID '%s'", values[1]); return FALSE; } dfu_firmware_set_pid (firmware, (guint16) tmp); /* write out new file */ return dfu_firmware_write_file (firmware, file, error); } static guint16 dfu_tool_parse_release_uint16 (const gchar *version, GError **error) { gchar *endptr = NULL; guint64 tmp_lsb, tmp_msb; g_auto(GStrv) split = g_strsplit (version, ".", -1); /* check if valid */ if (g_strv_length (split) != 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid format, expected 'major.minor'"); return 0xffff; } /* parse MSB & LSB */ tmp_msb = g_ascii_strtoull (split[0], &endptr, 10); if (tmp_msb > 0xff || endptr[0] != '\0') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse version '%s'", version); return 0xffff; } tmp_lsb = g_ascii_strtoull (split[1], &endptr, 10); if (tmp_lsb > 0xff || endptr[0] != '\0') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse version '%s'", version); return 0xffff; } return (tmp_msb << 8) + tmp_lsb; } static gboolean dfu_tool_set_release (DfuToolPrivate *priv, gchar **values, GError **error) { gchar *endptr = NULL; guint64 tmp; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILE RELEASE" " -- e.g. `firmware.dfu ffff"); return FALSE; } /* open */ file = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* parse release */ tmp = g_ascii_strtoull (values[1], &endptr, 16); if (tmp > G_MAXUINT16 || endptr[0] != '\0') { tmp = dfu_tool_parse_release_uint16 (values[1], error); if (tmp == 0xffff) return FALSE; } dfu_firmware_set_release (firmware, (guint16) tmp); /* write out new file */ return dfu_firmware_write_file (firmware, file, error); } static GBytes * dfu_tool_parse_hex_string (const gchar *val, GError **error) { gsize result_size; g_autofree guint8 *result = NULL; /* sanity check */ if (val == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "nothing to parse"); return NULL; } /* parse each hex byte */ result_size = strlen (val) / 2; result = g_malloc (result_size); for (guint i = 0; i < result_size; i++) { gchar buf[3] = { "xx" }; gchar *endptr = NULL; guint64 tmp; /* copy two bytes and parse as hex */ memcpy (buf, val + (i * 2), 2); tmp = g_ascii_strtoull (buf, &endptr, 16); if (tmp > 0xff || endptr[0] != '\0') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to parse '%s'", val); return NULL; } result[i] = tmp; } return g_bytes_new (result, result_size); } static guint dfu_tool_bytes_replace (GBytes *data, GBytes *search, GBytes *replace) { gsize data_sz; gsize replace_sz; gsize search_sz; guint8 *data_buf; guint8 *replace_buf; guint8 *search_buf; guint cnt = 0; data_buf = (gpointer) g_bytes_get_data (data, &data_sz); search_buf = (gpointer) g_bytes_get_data (search, &search_sz); replace_buf = (gpointer) g_bytes_get_data (replace, &replace_sz); g_return_val_if_fail (search_sz == replace_sz, FALSE); /* find and replace each one */ for (gsize i = 0; i < data_sz - search_sz + 1; i++) { if (memcmp (data_buf + i, search_buf, search_sz) == 0) { g_print ("Replacing %" G_GSIZE_FORMAT " bytes @0x%04x\n", replace_sz, (guint) i); memcpy (data_buf + i, replace_buf, replace_sz); i += replace_sz; cnt++; } } return cnt; } static gboolean dfu_tool_patch_dump (DfuToolPrivate *priv, gchar **values, GError **error) { gsize sz = 0; g_autofree gchar *data = NULL; g_autofree gchar *str = NULL; g_autoptr(DfuPatch) patch = NULL; g_autoptr(GBytes) blob = NULL; if (g_strv_length (values) != 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILE.bdiff"); return FALSE; } /* load file */ if (!g_file_get_contents (values[0], &data, &sz, error)) return FALSE; blob = g_bytes_new (data, sz); /* dump the patch to disk */ patch = dfu_patch_new (); if (!dfu_patch_import (patch, blob, error)) return FALSE; str = dfu_patch_to_string (patch); g_print ("%s\n", str); /* success */ return TRUE; } static gboolean dfu_tool_patch_apply (DfuToolPrivate *priv, gchar **values, GError **error) { DfuPatchApplyFlags flags = DFU_PATCH_APPLY_FLAG_NONE; const gchar *data_new; gsize sz_diff = 0; gsize sz_new = 0; gsize sz_old = 0; g_autofree gchar *data_diff = NULL; g_autofree gchar *data_old = NULL; g_autoptr(DfuPatch) patch = NULL; g_autoptr(GBytes) blob_diff = NULL; g_autoptr(GBytes) blob_new = NULL; g_autoptr(GBytes) blob_old = NULL; if (g_strv_length (values) != 3) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected OLD.bin OUT.bdiff NEW.bin"); return FALSE; } /* allow the user to shoot themselves in the foot */ if (priv->force) flags |= DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM; if (!g_file_get_contents (values[0], &data_old, &sz_old, error)) return FALSE; blob_old = g_bytes_new (data_old, sz_old); if (!g_file_get_contents (values[1], &data_diff, &sz_diff, error)) return FALSE; blob_diff = g_bytes_new (data_diff, sz_diff); patch = dfu_patch_new (); if (!dfu_patch_import (patch, blob_diff, error)) return FALSE; blob_new = dfu_patch_apply (patch, blob_old, flags, error); if (blob_new == NULL) return FALSE; /* save to disk */ data_new = g_bytes_get_data (blob_new, &sz_new); return g_file_set_contents (values[2], data_new, sz_new, error); } static gboolean dfu_tool_patch_create (DfuToolPrivate *priv, gchar **values, GError **error) { const gchar *data_diff; gsize sz_diff = 0; gsize sz_new = 0; gsize sz_old = 0; g_autofree gchar *data_new = NULL; g_autofree gchar *data_old = NULL; g_autoptr(DfuPatch) patch = NULL; g_autoptr(GBytes) blob_diff = NULL; g_autoptr(GBytes) blob_new = NULL; g_autoptr(GBytes) blob_old = NULL; if (g_strv_length (values) != 3) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected OLD.bin NEW.bin OUT.bdiff"); return FALSE; } /* read files */ if (!g_file_get_contents (values[0], &data_old, &sz_old, error)) return FALSE; blob_old = g_bytes_new (data_old, sz_old); if (!g_file_get_contents (values[1], &data_new, &sz_new, error)) return FALSE; blob_new = g_bytes_new (data_new, sz_new); /* create patch */ patch = dfu_patch_new (); if (!dfu_patch_create (patch, blob_old, blob_new, error)) return FALSE; blob_diff = dfu_patch_export (patch, error); if (blob_diff == NULL) return FALSE; /* save to disk */ data_diff = g_bytes_get_data (blob_diff, &sz_diff); return g_file_set_contents (values[2], data_diff, sz_diff, error); } static gboolean dfu_tool_replace_data (DfuToolPrivate *priv, gchar **values, GError **error) { GPtrArray *images; guint cnt = 0; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GBytes) data_search = NULL; g_autoptr(GBytes) data_replace = NULL; /* check args */ if (g_strv_length (values) < 3) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILE SEARCH REPLACE" " -- e.g. `firmware.dfu deadbeef beefdead"); return FALSE; } /* open */ file = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* parse hex values */ data_search = dfu_tool_parse_hex_string (values[1], error); if (data_search == NULL) return FALSE; data_replace = dfu_tool_parse_hex_string (values[2], error); if (data_replace == NULL) return FALSE; if (g_bytes_get_size (data_search) != g_bytes_get_size (data_replace)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "search and replace were different sizes"); return FALSE; } /* get each data segment */ images = dfu_firmware_get_images (firmware); for (guint i = 0; i < images->len; i++) { DfuImage *image = g_ptr_array_index (images, i); GPtrArray *elements = dfu_image_get_elements (image); for (guint j = 0; j < elements->len; j++) { DfuElement *element = g_ptr_array_index (elements, j); GBytes *contents = dfu_element_get_contents (element); if (contents == NULL) continue; cnt += dfu_tool_bytes_replace (contents, data_search, data_replace); } } /* nothing done */ if (cnt == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "search string was not found"); return FALSE; } /* write out new file */ return dfu_firmware_write_file (firmware, file, error); } static gboolean dfu_tool_set_target_size (DfuToolPrivate *priv, gchar **values, GError **error) { DfuElement *element; DfuImage *image; gchar *endptr; guint64 padding_char = 0x00; guint64 target_size; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILE SIZE [VAL]" " -- e.g. `firmware.dfu 8000 ff"); return FALSE; } /* open */ file = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* doesn't make sense for DfuSe */ if (dfu_firmware_get_format (firmware) == DFU_FIRMWARE_FORMAT_DFUSE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot pad DfuSe image, try DFU"); return FALSE; } /* parse target size */ target_size = g_ascii_strtoull (values[1], &endptr, 16); if (target_size > 0xffff || endptr[0] != '\0') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse target size '%s'", values[1]); return FALSE; } /* parse padding value */ if (g_strv_length (values) > 3) { padding_char = g_ascii_strtoull (values[2], &endptr, 16); if (padding_char > 0xff || endptr[0] != '\0') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse padding value '%s'", values[2]); return FALSE; } } /* this has to exist */ if (target_size > 0) { image = dfu_firmware_get_image_default (firmware); g_assert (image != NULL); element = dfu_image_get_element (image, 0); dfu_element_set_padding_value (element, (guint8) padding_char); dfu_element_set_target_size (element, (guint32) target_size); } /* write out new file */ return dfu_firmware_write_file (firmware, file, error); } static gboolean dfu_tool_set_address (DfuToolPrivate *priv, gchar **values, GError **error) { DfuElement *element; DfuFirmwareFormat firmware_format; DfuImage *image; gchar *endptr; guint64 address; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILE ADDR" " -- e.g. `firmware.dfu 8000"); return FALSE; } /* open */ file = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* only makes sense for DfuSe */ firmware_format = dfu_firmware_get_format (firmware); if (firmware_format != DFU_FIRMWARE_FORMAT_DFUSE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot set address of %s image, try DfuSe", dfu_firmware_format_to_string (firmware_format)); return FALSE; } /* parse address */ address = g_ascii_strtoull (values[1], &endptr, 16); if (address > G_MAXUINT32 || endptr[0] != '\0') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse address '%s'", values[1]); return FALSE; } /* this has to exist */ if (address > 0) { image = dfu_firmware_get_image_default (firmware); g_assert (image != NULL); element = dfu_image_get_element (image, 0); dfu_element_set_address (element, (guint32) address); } /* write out new file */ return dfu_firmware_write_file (firmware, file, error); } static gboolean dfu_tool_set_metadata (DfuToolPrivate *priv, gchar **values, GError **error) { g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 3) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILE KEY VALUE" " -- e.g. `firmware.dfu Licence GPL-2.0+"); return FALSE; } /* open */ file = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* doesn't make sense for non-DFU */ if (dfu_firmware_get_format (firmware) == DFU_FIRMWARE_FORMAT_RAW) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Only possible on DFU/DfuSe images, try convert"); return FALSE; } /* set metadata */ dfu_firmware_set_metadata (firmware, values[1], values[2]); /* write out new file */ return dfu_firmware_write_file (firmware, file, error); } static gboolean dfu_tool_set_alt_setting (DfuToolPrivate *priv, gchar **values, GError **error) { DfuImage *image; guint64 tmp; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILE ALT-ID" " -- e.g. `firmware.dfu 1"); return FALSE; } /* open */ file = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* doesn't make sense for non-DfuSe */ if (dfu_firmware_get_format (firmware) != DFU_FIRMWARE_FORMAT_DFUSE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Only possible on DfuSe images, try convert"); return FALSE; } /* parse VID */ tmp = g_ascii_strtoull (values[1], NULL, 10); if (tmp == 0 || tmp > G_MAXUINT8) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse alternative setting '%s'", values[1]); return FALSE; } image = dfu_firmware_get_image_default (firmware); if (image == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "found no image '%s'", values[1]); return FALSE; } dfu_image_set_alt_setting (image, (guint8) tmp); /* write out new file */ return dfu_firmware_write_file (firmware, file, error); } static gboolean dfu_tool_set_alt_setting_name (DfuToolPrivate *priv, gchar **values, GError **error) { DfuImage *image; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILE ALT-NAME" " -- e.g. `firmware.dfu ST"); return FALSE; } /* open */ file = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* doesn't make sense for non-DfuSe */ if (dfu_firmware_get_format (firmware) != DFU_FIRMWARE_FORMAT_DFUSE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Only possible on DfuSe images, try convert"); return FALSE; } /* parse VID */ image = dfu_firmware_get_image_default (firmware); if (image == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "found no image '%s'", values[1]); return FALSE; } dfu_image_set_name (image, values[1]); /* write out new file */ return dfu_firmware_write_file (firmware, file, error); } static gboolean dfu_tool_merge (DfuToolPrivate *priv, gchar **values, GError **error) { guint16 pid = 0xffff; guint16 rel = 0xffff; guint16 vid = 0xffff; g_autofree gchar *str_debug = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 3) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected " "FILE-OUT FILE1 FILE2 [FILE3...]" " -- e.g. `combined.dfu lib.dfu app.dfu`"); return FALSE; } /* parse source files */ firmware = dfu_firmware_new (); dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_DFUSE); for (guint i = 1; values[i] != NULL; i++) { GPtrArray *images; g_autoptr(GFile) file_tmp = NULL; g_autoptr(DfuFirmware) firmware_tmp = NULL; /* open up source */ file_tmp = g_file_new_for_path (values[i]); firmware_tmp = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware_tmp, file_tmp, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* check same vid:pid:rel */ if (vid != 0xffff && dfu_firmware_get_vid (firmware_tmp) != vid) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Vendor ID was already set as " "0x%04x, %s is 0x%04x", vid, values[i], dfu_firmware_get_vid (firmware_tmp)); return FALSE; } if (pid != 0xffff && dfu_firmware_get_pid (firmware_tmp) != pid) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Product ID was already set as " "0x%04x, %s is 0x%04x", pid, values[i], dfu_firmware_get_pid (firmware_tmp)); return FALSE; } if (rel != 0xffff && dfu_firmware_get_release (firmware_tmp) != rel) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Release was already set as " "0x%04x, %s is 0x%04x", rel, values[i], dfu_firmware_get_release (firmware_tmp)); return FALSE; } /* add all images to destination */ images = dfu_firmware_get_images (firmware_tmp); for (guint j = 0; j < images->len; j++) { DfuImage *image; guint8 alt_id; /* verify the alt-setting does not already exist */ image = g_ptr_array_index (images, j); alt_id = dfu_image_get_alt_setting (image); g_print ("Adding alternative setting ID of 0x%02x\n", alt_id); if (dfu_firmware_get_image (firmware, alt_id) != NULL) { if (!priv->force) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "The alternative setting ID " "of 0x%02x has already been added", alt_id); return FALSE; } g_print ("WARNING: The alternative setting " "ID of 0x%02x has already been added\n", alt_id); } /* add to destination */ dfu_firmware_add_image (firmware, image); } /* save last IDs */ vid = dfu_firmware_get_vid (firmware_tmp); pid = dfu_firmware_get_pid (firmware_tmp); rel = dfu_firmware_get_release (firmware_tmp); } /* print the new object */ str_debug = dfu_firmware_to_string (firmware); g_print ("New merged file:\n%s\n", str_debug); /* write out new file */ file = g_file_new_for_path (values[0]); return dfu_firmware_write_file (firmware, file, error); } static gboolean dfu_tool_convert (DfuToolPrivate *priv, gchar **values, GError **error) { DfuFirmwareFormat format; guint argc = g_strv_length (values); g_autofree gchar *str_debug = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file_in = NULL; g_autoptr(GFile) file_out = NULL; /* check args */ if (argc != 3) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected " "FORMAT FILE-IN FILE-OUT" " -- e.g. `dfu firmware.hex firmware.dfu`"); return FALSE; } /* parse file */ file_in = g_file_new_for_path (values[1]); file_out = g_file_new_for_path (values[2]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file_in, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* set output format */ format = dfu_firmware_format_from_string (values[0]); dfu_firmware_set_format (firmware, format); if (format == DFU_FIRMWARE_FORMAT_UNKNOWN) { g_autoptr(GString) tmp = g_string_new (NULL); for (guint i = 1; i < DFU_FIRMWARE_FORMAT_LAST; i++) { if (tmp->len > 0) g_string_append (tmp, "|"); g_string_append (tmp, dfu_firmware_format_to_string (i)); } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unknown format '%s', expected [%s]", values[0], tmp->str); return FALSE; } /* print the new object */ str_debug = dfu_firmware_to_string (firmware); g_debug ("DFU: %s", str_debug); /* write out new file */ return dfu_firmware_write_file (firmware, file_out, error); } static gboolean dfu_tool_attach (DfuToolPrivate *priv, gchar **values, GError **error) { g_autoptr(DfuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; device = dfu_tool_get_default_device (priv, error); if (device == NULL) return FALSE; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; if (!dfu_device_refresh_and_clear (device, error)) return FALSE; return dfu_device_attach (device, error); } static gboolean dfu_tool_reset (DfuToolPrivate *priv, gchar **values, GError **error) { g_autoptr(DfuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; device = dfu_tool_get_default_device (priv, error); if (device == NULL) return FALSE; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; if (!dfu_device_refresh (device, error)) return FALSE; return dfu_device_reset (device, error); } static void fu_tool_action_changed_cb (FuDevice *device, GParamSpec *pspec, DfuToolPrivate *priv) { fu_progressbar_update (priv->progressbar, fu_device_get_status (device), fu_device_get_progress (device)); } static gboolean dfu_tool_read_alt (DfuToolPrivate *priv, gchar **values, GError **error) { DfuTargetTransferFlags flags = DFU_TARGET_TRANSFER_FLAG_NONE; g_autofree gchar *str_debug = NULL; g_autoptr(DfuDevice) device = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(DfuImage) image = NULL; g_autoptr(DfuTarget) target = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected " "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID"); return FALSE; } /* open correct device */ device = dfu_tool_get_default_device (priv, error); if (device == NULL) return FALSE; if (priv->transfer_size > 0) dfu_device_set_transfer_size (device, priv->transfer_size); locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; if (!dfu_device_refresh (device, error)) return FALSE; /* set up progress */ g_signal_connect (device, "notify::status", G_CALLBACK (fu_tool_action_changed_cb), priv); g_signal_connect (device, "notify::progress", G_CALLBACK (fu_tool_action_changed_cb), priv); /* APP -> DFU */ if (dfu_device_is_runtime (device)) { g_debug ("detaching"); if (!dfu_device_detach (device, error)) return FALSE; if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; } /* transfer */ target = dfu_device_get_target_by_alt_name (device, values[1], NULL); if (target == NULL) { gchar *endptr; guint64 tmp = g_ascii_strtoull (values[1], &endptr, 10); if (tmp > 0xff || endptr[0] != '\0') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse alt-setting '%s'", values[1]); return FALSE; } target = dfu_device_get_target_by_alt_setting (device, (guint8) tmp, error); if (target == NULL) return FALSE; } /* do transfer */ image = dfu_target_upload (target, flags, error); if (image == NULL) return FALSE; /* do host reset */ if (!dfu_device_attach (device, error)) return FALSE; if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* create new firmware object */ firmware = dfu_firmware_new (); dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_DFU); dfu_firmware_set_vid (firmware, dfu_device_get_runtime_vid (device)); dfu_firmware_set_pid (firmware, dfu_device_get_runtime_pid (device)); dfu_firmware_add_image (firmware, image); /* save file */ file = g_file_new_for_path (values[0]); if (!dfu_firmware_write_file (firmware, file, error)) return FALSE; /* print the new object */ str_debug = dfu_firmware_to_string (firmware); g_debug ("DFU: %s", str_debug); /* success */ g_print ("%u bytes successfully uploaded from device\n", dfu_image_get_size (image)); return TRUE; } static gboolean dfu_tool_read (DfuToolPrivate *priv, gchar **values, GError **error) { DfuFirmwareFormat format; DfuTargetTransferFlags flags = DFU_TARGET_TRANSFER_FLAG_NONE; g_autofree gchar *str_debug = NULL; g_autoptr(DfuDevice) device = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) == 1) { /* guess output format */ if (g_str_has_suffix (values[0], ".dfu")) { format = DFU_FIRMWARE_FORMAT_DFU; } else if (g_str_has_suffix (values[0], ".bin") || g_str_has_suffix (values[0], ".rom")) { format = DFU_FIRMWARE_FORMAT_RAW; } else if (g_str_has_suffix (values[0], ".ihex") || g_str_has_suffix (values[0], ".hex")) { format = DFU_FIRMWARE_FORMAT_INTEL_HEX; } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Could not guess a file format"); return FALSE; } } else if (g_strv_length (values) == 2) { format = dfu_firmware_format_from_string (values[1]); if (format == DFU_FIRMWARE_FORMAT_UNKNOWN) { g_autoptr(GString) tmp = g_string_new (NULL); for (guint i = 1; i < DFU_FIRMWARE_FORMAT_LAST; i++) { if (tmp->len > 0) g_string_append (tmp, "|"); g_string_append (tmp, dfu_firmware_format_to_string (i)); } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unknown format '%s', expected [%s]", values[0], tmp->str); return FALSE; } } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILENAME [FORMAT]"); return FALSE; } /* open correct device */ device = dfu_tool_get_default_device (priv, error); if (device == NULL) return FALSE; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; if (!dfu_device_refresh (device, error)) return FALSE; /* APP -> DFU */ if (dfu_device_is_runtime (device)) { if (!dfu_device_detach (device, error)) return FALSE; if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) { return FALSE; } } /* transfer */ g_signal_connect (device, "notify::status", G_CALLBACK (fu_tool_action_changed_cb), priv); g_signal_connect (device, "notify::progress", G_CALLBACK (fu_tool_action_changed_cb), priv); firmware = dfu_device_upload (device, flags, error); if (firmware == NULL) return FALSE; /* do host reset */ if (!dfu_device_attach (device, error)) return FALSE; if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* save file */ file = g_file_new_for_path (values[0]); dfu_firmware_set_format (firmware, format); if (!dfu_firmware_write_file (firmware, file, error)) return FALSE; /* print the new object */ str_debug = dfu_firmware_to_string (firmware); g_debug ("DFU: %s", str_debug); /* success */ g_print ("%u bytes successfully uploaded from device\n", dfu_firmware_get_size (firmware)); return TRUE; } static gchar * dfu_tool_get_device_string (DfuToolPrivate *priv, DfuDevice *device) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_autoptr(FuDeviceLocker) locker = NULL; /* open if required, and get status */ if (usb_device == NULL) { return g_strdup_printf ("%04x:%04x [%s]", dfu_device_get_runtime_vid (device), dfu_device_get_runtime_pid (device), "removed"); } if (!fu_usb_device_is_open (FU_USB_DEVICE (device))) { g_autoptr(GError) error = NULL; locker = fu_device_locker_new (device, &error); if (locker == NULL) { return g_strdup_printf ("%04x:%04x [%s]", dfu_device_get_vid (device), dfu_device_get_pid (device), error->message); } if (!dfu_device_refresh (device, &error)) return NULL; } return g_strdup_printf ("%04x:%04x [%s:%s]", dfu_device_get_vid (device), dfu_device_get_pid (device), dfu_state_to_string (dfu_device_get_state (device)), dfu_status_to_string (dfu_device_get_status (device))); } static void dfu_tool_device_added_cb (GUsbContext *context, DfuDevice *device, gpointer user_data) { DfuToolPrivate *priv = (DfuToolPrivate *) user_data; g_autofree gchar *tmp = dfu_tool_get_device_string (priv, device); /* TRANSLATORS: this is when a device is hotplugged */ dfu_tool_print_indent (_("Added"), tmp, 0); } static void dfu_tool_device_removed_cb (GUsbContext *context, DfuDevice *device, gpointer user_data) { DfuToolPrivate *priv = (DfuToolPrivate *) user_data; g_autofree gchar *tmp = dfu_tool_get_device_string (priv, device); /* TRANSLATORS: this is when a device is hotplugged */ dfu_tool_print_indent (_("Removed"), tmp, 0); } static void dfu_tool_device_changed_cb (GUsbContext *context, DfuDevice *device, gpointer user_data) { DfuToolPrivate *priv = (DfuToolPrivate *) user_data; g_autofree gchar *tmp = dfu_tool_get_device_string (priv, device); /* TRANSLATORS: this is when a device is hotplugged */ dfu_tool_print_indent (_("Changed"), tmp, 0); } static void dfu_tool_watch_cancelled_cb (GCancellable *cancellable, gpointer user_data) { GMainLoop *loop = (GMainLoop *) user_data; /* TRANSLATORS: this is when a device ctrl+c's a watch */ g_print ("%s\n", _("Cancelled")); g_main_loop_quit (loop); } static guint8 * dfu_tool_get_firmware_contents_default (DfuFirmware *firmware, gsize *length, GError **error) { DfuElement *element; DfuImage *image; GBytes *contents; image = dfu_firmware_get_image_default (firmware); if (image == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "No default image"); return NULL; } element = dfu_image_get_element (image, 0); if (element == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "No default element"); return NULL; } contents = dfu_element_get_contents (element); if (contents == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "No image contents"); return NULL; } return (guint8 *) g_bytes_get_data (contents, length); } static gboolean dfu_tool_encrypt (DfuToolPrivate *priv, gchar **values, GError **error) { gsize len; guint8 *data; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file_in = NULL; g_autoptr(GFile) file_out = NULL; /* check args */ if (g_strv_length (values) < 4) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected " "FILENAME-IN FILENAME-OUT TYPE KEY" " -- e.g. firmware.dfu firmware.xdfu xtea deadbeef"); return FALSE; } /* check extensions */ if (!priv->force) { if (!g_str_has_suffix (values[0], ".dfu")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid filename, expected *.dfu"); return FALSE; } if (!g_str_has_suffix (values[1], ".xdfu")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid filename, expected *.xdfu"); return FALSE; } } /* open */ file_in = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file_in, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* get data */ data = dfu_tool_get_firmware_contents_default (firmware, &len, error); if (data == NULL) return FALSE; /* check type */ if (g_strcmp0 (values[2], "xtea") == 0) { if (!dfu_cipher_encrypt_xtea (values[3], data, (guint32) len, error)) return FALSE; dfu_firmware_set_metadata (firmware, DFU_METADATA_KEY_CIPHER_KIND, "XTEA"); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unknown type '%s', expected [xtea]", values[2]); return FALSE; } /* write out new file */ file_out = g_file_new_for_path (values[1]); g_debug ("wrote %s", values[1]); return dfu_firmware_write_file (firmware, file_out, error); } static gboolean dfu_tool_decrypt (DfuToolPrivate *priv, gchar **values, GError **error) { gsize len; guint8 *data; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file_in = NULL; g_autoptr(GFile) file_out = NULL; /* check args */ if (g_strv_length (values) < 4) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected " "FILENAME-IN FILENAME-OUT TYPE KEY" " -- e.g. firmware.xdfu firmware.dfu xtea deadbeef"); return FALSE; } /* check extensions */ if (!priv->force) { if (!g_str_has_suffix (values[0], ".xdfu")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid filename, expected *.xdfu"); return FALSE; } if (!g_str_has_suffix (values[1], ".dfu")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid filename, expected *.dfu"); return FALSE; } } /* open */ file_in = g_file_new_for_path (values[0]); firmware = dfu_firmware_new (); if (!dfu_firmware_parse_file (firmware, file_in, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) { return FALSE; } /* get data */ data = dfu_tool_get_firmware_contents_default (firmware, &len, error); if (data == NULL) return FALSE; /* check type */ if (g_strcmp0 (values[2], "xtea") == 0) { if (!dfu_cipher_decrypt_xtea (values[3], data, (guint32) len, error)) return FALSE; dfu_firmware_remove_metadata (firmware, DFU_METADATA_KEY_CIPHER_KIND); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unknown type '%s', expected [xtea]", values[2]); return FALSE; } /* write out new file */ file_out = g_file_new_for_path (values[1]); g_debug ("wrote %s", values[1]); return dfu_firmware_write_file (firmware, file_out, error); } static gboolean dfu_tool_watch (DfuToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GUsbContext) usb_context = NULL; g_autoptr(GMainLoop) loop = NULL; g_autoptr(GPtrArray) devices = NULL; /* get all the DFU devices */ usb_context = g_usb_context_new (error); if (usb_context == NULL) return FALSE; g_usb_context_enumerate (usb_context); /* print what's already attached */ devices = g_usb_context_get_devices (usb_context); for (guint i = 0; i < devices->len; i++) { DfuDevice *device = g_ptr_array_index (devices, i); dfu_tool_device_added_cb (usb_context, device, priv); } /* watch for any hotplugged device */ loop = g_main_loop_new (NULL, FALSE); g_signal_connect (usb_context, "device-added", G_CALLBACK (dfu_tool_device_added_cb), priv); g_signal_connect (usb_context, "device-removed", G_CALLBACK (dfu_tool_device_removed_cb), priv); g_signal_connect (usb_context, "device-changed", G_CALLBACK (dfu_tool_device_changed_cb), priv); g_signal_connect (priv->cancellable, "cancelled", G_CALLBACK (dfu_tool_watch_cancelled_cb), loop); g_main_loop_run (loop); return TRUE; } static gboolean dfu_tool_dump (DfuToolPrivate *priv, gchar **values, GError **error) { DfuFirmwareParseFlags flags = DFU_FIRMWARE_PARSE_FLAG_NONE; /* check args */ if (g_strv_length (values) < 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILENAME"); return FALSE; } /* dump corrupt files */ if (priv->force) { flags |= DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST; flags |= DFU_FIRMWARE_PARSE_FLAG_NO_VERSION_TEST; } /* open files */ for (guint i = 0; values[i] != NULL; i++) { g_autofree gchar *tmp = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GError) error_local = NULL; /* dump to screen */ g_print ("Loading %s:\n", values[i]); firmware = dfu_firmware_new (); file = g_file_new_for_path (values[i]); if (!dfu_firmware_parse_file (firmware, file, flags, &error_local)) { g_print ("Failed to load firmware: %s\n", error_local->message); continue; } tmp = dfu_firmware_to_string (firmware); g_print ("%s\n", tmp); } return TRUE; } static gboolean dfu_tool_write_alt (DfuToolPrivate *priv, gchar **values, GError **error) { DfuImage *image; DfuTargetTransferFlags flags = DFU_TARGET_TRANSFER_FLAG_VERIFY; g_autofree gchar *str_debug = NULL; g_autoptr(DfuDevice) device = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(DfuTarget) target = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected " "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID " "[IMAGE-ALT-NAME|IMAGE-ALT-ID]"); return FALSE; } /* open file */ firmware = dfu_firmware_new (); file = g_file_new_for_path (values[0]); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) return FALSE; /* open correct device */ device = dfu_tool_get_default_device (priv, error); if (device == NULL) return FALSE; if (priv->transfer_size > 0) dfu_device_set_transfer_size (device, priv->transfer_size); locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; if (!dfu_device_refresh (device, error)) return FALSE; /* set up progress */ g_signal_connect (device, "notify::status", G_CALLBACK (fu_tool_action_changed_cb), priv); g_signal_connect (device, "notify::progress", G_CALLBACK (fu_tool_action_changed_cb), priv); /* APP -> DFU */ if (dfu_device_is_runtime (device)) { g_debug ("detaching"); if (!dfu_device_detach (device, error)) return FALSE; if (!dfu_device_wait_for_replug (device, 5000, error)) return FALSE; } /* print the new object */ str_debug = dfu_firmware_to_string (firmware); g_debug ("DFU: %s", str_debug); /* get correct target on device */ target = dfu_device_get_target_by_alt_name (device, values[1], NULL); if (target == NULL) { gchar *endptr; guint64 tmp = g_ascii_strtoull (values[1], &endptr, 10); if (tmp > 0xff || endptr[0] != '\0') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse alt-setting '%s'", values[1]); return FALSE; } target = dfu_device_get_target_by_alt_setting (device, (guint8) tmp, error); if (target == NULL) return FALSE; } /* allow overriding the firmware alt-setting */ if (g_strv_length (values) > 2) { image = dfu_firmware_get_image_by_name (firmware, values[2]); if (image == NULL) { gchar *endptr; guint64 tmp = g_ascii_strtoull (values[2], &endptr, 10); if (tmp > 0xff || endptr[0] != '\0') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse image alt-setting '%s'", values[2]); return FALSE; } image = dfu_firmware_get_image (firmware, (guint8) tmp); if (image == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "could not locate image in firmware for %02x", (guint) tmp); return FALSE; } } } else { g_print ("WARNING: Using default firmware image\n"); image = dfu_firmware_get_image_default (firmware); if (image == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no default image"); return FALSE; } } /* allow forcing firmware kinds */ if (priv->force) { flags |= DFU_TARGET_TRANSFER_FLAG_ANY_CIPHER; } /* transfer */ if (!dfu_target_download (target, image, flags, error)) return FALSE; /* do host reset */ if (!dfu_device_attach (device, error)) return FALSE; if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* success */ g_print ("%u bytes successfully downloaded to device\n", dfu_image_get_size (image)); return TRUE; } static gboolean dfu_tool_write (DfuToolPrivate *priv, gchar **values, GError **error) { DfuTargetTransferFlags flags = DFU_TARGET_TRANSFER_FLAG_VERIFY; g_autofree gchar *str_debug = NULL; g_autoptr(DfuDevice) device = NULL; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GFile) file = NULL; /* check args */ if (g_strv_length (values) < 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILENAME"); return FALSE; } /* open file */ firmware = dfu_firmware_new (); file = g_file_new_for_path (values[0]); if (!dfu_firmware_parse_file (firmware, file, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) return FALSE; /* open correct device */ device = dfu_tool_get_default_device (priv, error); if (device == NULL) return FALSE; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; if (!dfu_device_refresh (device, error)) return FALSE; /* print the new object */ str_debug = dfu_firmware_to_string (firmware); g_debug ("DFU: %s", str_debug); /* APP -> DFU */ if (dfu_device_is_runtime (device)) { if (!dfu_device_detach (device, error)) return FALSE; if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) { return FALSE; } } /* allow wildcards */ if (priv->force) { flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID; flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID; flags |= DFU_TARGET_TRANSFER_FLAG_ANY_CIPHER; } /* transfer */ g_signal_connect (device, "notify::status", G_CALLBACK (fu_tool_action_changed_cb), priv); g_signal_connect (device, "notify::progress", G_CALLBACK (fu_tool_action_changed_cb), priv); if (!dfu_device_download (device, firmware, flags, error)) return FALSE; /* do host reset */ if (!dfu_device_attach (device, error)) return FALSE; if (!dfu_device_wait_for_replug (device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* success */ g_print ("%u bytes successfully downloaded to device\n", dfu_firmware_get_size (firmware)); return TRUE; } static void dfu_tool_list_target (DfuTarget *target) { DfuCipherKind cipher_kind; GPtrArray *sectors; const gchar *tmp; g_autofree gchar *alt_id = NULL; g_autoptr(GError) error_local = NULL; /* TRANSLATORS: the identifier name please */ alt_id = g_strdup_printf ("%i", dfu_target_get_alt_setting (target)); dfu_tool_print_indent (_("ID"), alt_id, 1); /* this is optional */ tmp = dfu_target_get_alt_name_for_display (target, &error_local); if (tmp != NULL) { /* TRANSLATORS: interface name, e.g. "Flash" */ dfu_tool_print_indent (_("Name"), tmp, 2); } else if (!g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_autofree gchar *str = NULL; str = g_strdup_printf ("Error: %s", error_local->message); dfu_tool_print_indent (_("Name"), str, 2); } /* this is optional */ cipher_kind = dfu_target_get_cipher_kind (target); if (cipher_kind != DFU_CIPHER_KIND_NONE) { /* TRANSLATORS: this is the encryption method used when writing */ dfu_tool_print_indent (_("Cipher"), dfu_cipher_kind_to_string (cipher_kind), 2); } /* print sector information */ sectors = dfu_target_get_sectors (target); for (guint i = 0; i < sectors->len; i++) { DfuSector *sector; g_autofree gchar *msg = NULL; g_autofree gchar *title = NULL; sector = g_ptr_array_index (sectors, i); msg = dfu_sector_to_string (sector); /* TRANSLATORS: these are areas of memory on the chip */ title = g_strdup_printf ("%s 0x%02x", _("Region"), i); dfu_tool_print_indent (title, msg, 2); } } static gboolean dfu_tool_list (DfuToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GUsbContext) usb_context = NULL; g_autoptr(GPtrArray) devices = NULL; /* get all the connected USB devices */ usb_context = g_usb_context_new (error); if (usb_context == NULL) return FALSE; g_usb_context_enumerate (usb_context); devices = g_usb_context_get_devices (usb_context); for (guint i = 0; i < devices->len; i++) { DfuTarget *target; GUsbDevice *usb_device; GPtrArray *dfu_targets; const gchar *tmp; gboolean is_runtime; guint16 transfer_size; g_autofree gchar *attrs = NULL; g_autofree gchar *quirks = NULL; g_autofree gchar *version = NULL; g_autoptr(DfuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* device specific */ usb_device = g_ptr_array_index (devices, i); device = dfu_device_new (usb_device); fu_device_set_quirks (FU_DEVICE (device), priv->quirks); dfu_device_set_usb_context (device, usb_context); if (!fu_device_probe (FU_DEVICE (device), NULL)) continue; version = fu_common_version_from_uint16 (g_usb_device_get_release (usb_device), FWUPD_VERSION_FORMAT_BCD); g_print ("%s %04x:%04x [v%s]:\n", /* TRANSLATORS: detected a DFU device */ _("Found"), g_usb_device_get_vid (usb_device), g_usb_device_get_pid (usb_device), version); tmp = dfu_version_to_string (dfu_device_get_version (device)); if (tmp != NULL) { /* TRANSLATORS: DFU protocol version, e.g. 1.1 */ dfu_tool_print_indent (_("Protocol"), tmp, 1); } /* open */ locker = fu_device_locker_new (device, &error_local); if (locker == NULL) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED)) { /* TRANSLATORS: probably not run as root... */ dfu_tool_print_indent (_("Status"), _("Permission denied"), 1); continue; } if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug ("ignoring warning, continuing..."); } else { /* TRANSLATORS: device has failed to report status */ dfu_tool_print_indent (_("Status"), error_local->message, 1); } continue; } if (!dfu_device_refresh_and_clear (device, &error_local)) { /* TRANSLATORS: device has failed to report status */ dfu_tool_print_indent (_("Status"), error_local->message, 1); continue; } tmp = fu_device_get_name (FU_DEVICE (device)); if (tmp != NULL) { /* TRANSLATORS: device name, e.g. 'ColorHug2' */ dfu_tool_print_indent (_("Name"), tmp, 1); } tmp = fu_device_get_serial (FU_DEVICE (device)); if (tmp != NULL) { /* TRANSLATORS: serial number, e.g. '00012345' */ dfu_tool_print_indent (_("Serial"), tmp, 1); } /* TRANSLATORS: device mode, e.g. application runtime or DFU */ is_runtime = dfu_device_is_runtime (device); dfu_tool_print_indent (_("Mode"), is_runtime ? _("Runtime") : _("DFU"), 1); tmp = dfu_status_to_string (dfu_device_get_status (device)); /* TRANSLATORS: device status, e.g. "OK" */ dfu_tool_print_indent (_("Status"), tmp, 1); tmp = dfu_state_to_string (dfu_device_get_state (device)); /* TRANSLATORS: device state, i.e. appIDLE */ dfu_tool_print_indent (_("State"), tmp, 1); transfer_size = dfu_device_get_transfer_size (device); if (transfer_size > 0) { g_autofree gchar *str = NULL; str = g_format_size_full (transfer_size, G_FORMAT_SIZE_LONG_FORMAT); /* TRANSLATORS: transfer size in bytes */ dfu_tool_print_indent (_("Transfer Size"), str, 1); } /* attributes can be an empty string */ attrs = dfu_device_get_attributes_as_string (device); if (attrs != NULL && attrs[0] != '\0') { /* TRANSLATORS: device attributes, i.e. things that * the device can do */ dfu_tool_print_indent (_("Attributes"), attrs, 1); } /* quirks are NULL if none are set */ quirks = dfu_device_get_quirks_as_string (device); if (quirks != NULL) { /* TRANSLATORS: device quirks, i.e. things that * it does that we have to work around */ dfu_tool_print_indent (_("Quirks"), quirks, 1); } /* this is optional */ tmp = dfu_device_get_chip_id (device); if (tmp != NULL) { /* TRANSLATORS: chip ID, e.g. "0x58200204" */ dfu_tool_print_indent (_("Chip ID"), tmp, 1); } /* list targets */ dfu_targets = dfu_device_get_targets (device); for (guint j = 0; j < dfu_targets->len; j++) { target = g_ptr_array_index (dfu_targets, j); dfu_tool_list_target (target); } } return TRUE; } static gboolean dfu_tool_detach (DfuToolPrivate *priv, gchar **values, GError **error) { g_autoptr(DfuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* open correct device */ device = dfu_tool_get_default_device (priv, error); if (device == NULL) return FALSE; if (priv->transfer_size > 0) dfu_device_set_transfer_size (device, priv->transfer_size); /* detach */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; if (!dfu_device_refresh_and_clear (device, error)) return FALSE; return dfu_device_detach (device, error); } static gboolean dfu_tool_sigint_cb (gpointer user_data) { DfuToolPrivate *priv = (DfuToolPrivate *) user_data; g_debug ("Handling SIGINT"); g_cancellable_cancel (priv->cancellable); return FALSE; } int main (int argc, char *argv[]) { gboolean ret; gboolean verbose = FALSE; gboolean version = FALSE; g_autofree gchar *cmd_descriptions = NULL; g_autoptr(DfuToolPrivate) priv = g_new0 (DfuToolPrivate, 1); g_autoptr(GError) error = NULL; g_autoptr(GOptionContext) context = NULL; const GOptionEntry options[] = { { "version", '\0', 0, G_OPTION_ARG_NONE, &version, _("Print the version number"), NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, _("Print verbose debug statements"), NULL }, { "device", 'd', 0, G_OPTION_ARG_STRING, &priv->device_vid_pid, _("Specify Vendor/Product ID(s) of DFU device"), "VID:PID" }, { "transfer-size", 't', 0, G_OPTION_ARG_STRING, &priv->transfer_size, _("Specify the number of bytes per USB transfer"), "BYTES" }, { "force", '\0', 0, G_OPTION_ARG_NONE, &priv->force, _("Force the action ignoring all warnings"), NULL }, { NULL} }; setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); /* add commands */ priv->cmd_array = g_ptr_array_new_with_free_func ((GDestroyNotify) dfu_tool_item_free); dfu_tool_add (priv->cmd_array, "convert", "FORMAT FILE-IN FILE OUT [SIZE]", /* TRANSLATORS: command description */ _("Convert firmware to DFU format"), dfu_tool_convert); dfu_tool_add (priv->cmd_array, "merge", "FILE-OUT FILE1 FILE2 [FILE3...]", /* TRANSLATORS: command description */ _("Merge multiple firmware files into one"), dfu_tool_merge); dfu_tool_add (priv->cmd_array, "set-vendor", "FILE VID", /* TRANSLATORS: command description */ _("Set vendor ID on firmware file"), dfu_tool_set_vendor); dfu_tool_add (priv->cmd_array, "set-product", "FILE PID", /* TRANSLATORS: command description */ _("Set product ID on firmware file"), dfu_tool_set_product); dfu_tool_add (priv->cmd_array, "set-address", "FILE ADDRESS", /* TRANSLATORS: command description */ _("Set element address on firmware file"), dfu_tool_set_address); dfu_tool_add (priv->cmd_array, "set-target-size", "FILE SIZE", /* TRANSLATORS: command description */ _("Set the firmware size for the target"), dfu_tool_set_target_size); dfu_tool_add (priv->cmd_array, "set-release", "FILE RELEASE", /* TRANSLATORS: command description */ _("Set release version on firmware file"), dfu_tool_set_release); dfu_tool_add (priv->cmd_array, "set-alt-setting", "FILE ALT-ID", /* TRANSLATORS: command description */ _("Set alternative number on firmware file"), dfu_tool_set_alt_setting); dfu_tool_add (priv->cmd_array, "set-alt-setting-name", "FILE VALUE", /* TRANSLATORS: command description */ _("Set alternative name on firmware file"), dfu_tool_set_alt_setting_name); dfu_tool_add (priv->cmd_array, "attach", NULL, /* TRANSLATORS: command description */ _("Attach DFU capable device back to runtime"), dfu_tool_attach); dfu_tool_add (priv->cmd_array, "reset", NULL, /* TRANSLATORS: command description */ _("Reset a DFU device"), dfu_tool_reset); dfu_tool_add (priv->cmd_array, "read", "FILENAME", /* TRANSLATORS: command description */ _("Read firmware from device into a file"), dfu_tool_read); dfu_tool_add (priv->cmd_array, "read-alt", "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID", /* TRANSLATORS: command description */ _("Read firmware from one partition into a file"), dfu_tool_read_alt); dfu_tool_add (priv->cmd_array, "write", NULL, /* TRANSLATORS: command description */ _("Write firmware from file into device"), dfu_tool_write); dfu_tool_add (priv->cmd_array, "write-alt", "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]", /* TRANSLATORS: command description */ _("Write firmware from file into one partition"), dfu_tool_write_alt); dfu_tool_add (priv->cmd_array, "list", NULL, /* TRANSLATORS: command description */ _("List currently attached DFU capable devices"), dfu_tool_list); dfu_tool_add (priv->cmd_array, "detach", NULL, /* TRANSLATORS: command description */ _("Detach currently attached DFU capable device"), dfu_tool_detach); dfu_tool_add (priv->cmd_array, "dump", "FILENAME", /* TRANSLATORS: command description */ _("Dump details about a firmware file"), dfu_tool_dump); dfu_tool_add (priv->cmd_array, "watch", NULL, /* TRANSLATORS: command description */ _("Watch DFU devices being hotplugged"), dfu_tool_watch); dfu_tool_add (priv->cmd_array, "encrypt", "FILENAME-IN FILENAME-OUT TYPE KEY", /* TRANSLATORS: command description */ _("Encrypt firmware data"), dfu_tool_encrypt); dfu_tool_add (priv->cmd_array, "decrypt", "FILENAME-IN FILENAME-OUT TYPE KEY", /* TRANSLATORS: command description */ _("Decrypt firmware data"), dfu_tool_decrypt); dfu_tool_add (priv->cmd_array, "set-metadata", "FILE KEY VALUE", /* TRANSLATORS: command description */ _("Sets metadata on a firmware file"), dfu_tool_set_metadata); dfu_tool_add (priv->cmd_array, "replace-data", NULL, /* TRANSLATORS: command description */ _("Replace data in an existing firmware file"), dfu_tool_replace_data); dfu_tool_add (priv->cmd_array, "patch-create", NULL, /* TRANSLATORS: command description */ _("Create a binary patch using two files"), dfu_tool_patch_create); dfu_tool_add (priv->cmd_array, "patch-apply", NULL, /* TRANSLATORS: command description */ _("Apply a binary patch"), dfu_tool_patch_apply); dfu_tool_add (priv->cmd_array, "patch-dump", NULL, /* TRANSLATORS: command description */ _("Dump information about a binary patch to the screen"), dfu_tool_patch_dump); /* use animated progress bar */ priv->progressbar = fu_progressbar_new (); fu_progressbar_set_length_percentage (priv->progressbar, 50); fu_progressbar_set_length_status (priv->progressbar, 20); /* use quirks */ priv->quirks = fu_quirks_new (); if (!fu_quirks_load (priv->quirks, &error)) { /* TRANSLATORS: quirks are device-specific workarounds */ g_print ("%s: %s\n", _("Failed to load quirks"), error->message); return EXIT_FAILURE; } /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new (); g_unix_signal_add_full (G_PRIORITY_DEFAULT, SIGINT, dfu_tool_sigint_cb, priv, NULL); /* sort by command name */ g_ptr_array_sort (priv->cmd_array, (GCompareFunc) dfu_tool_sort_command_name_cb); /* get a list of the commands */ context = g_option_context_new (NULL); cmd_descriptions = dfu_tool_get_descriptions (priv->cmd_array); g_option_context_set_summary (context, cmd_descriptions); /* TRANSLATORS: DFU stands for device firmware update */ g_set_application_name (_("DFU Utility")); g_option_context_add_main_entries (context, options, NULL); ret = g_option_context_parse (context, &argc, &argv, &error); if (!ret) { /* TRANSLATORS: the user didn't read the man page */ g_print ("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* set verbose? */ if (verbose) g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); /* version */ if (version) { g_print ("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION); return EXIT_SUCCESS; } /* run the specified command */ ret = dfu_tool_run (priv, argv[1], (gchar**) &argv[2], &error); if (!ret) { if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL)) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help (context, TRUE, NULL); g_print ("%s\n\n%s", error->message, tmp); } else { g_print ("%s\n", error->message); } return EXIT_FAILURE; } /* success/ */ return EXIT_SUCCESS; } fwupd-1.2.14/plugins/dfu/dfu-tool.h2m000066400000000000000000000041671402665037500173440ustar00rootroot00000000000000[DESCRIPTION] .PP This manual page documents briefly the \fBdfu-tool\fR command. .PP \fBdfu-tool\fR allows a user to write various kinds of firmware onto devices supporting the USB Device Firmware Upgrade protocol. This tool can be used to switch the device from the normal runtime mode to `DFU mode' which allows the user to read and write firmware. Either the whole device can be written in one operation, or individual `targets' can be specified with the alternative name or number. .PP \fBdfu-tool\fR uses the libdfu shared library to perform actions. All synchronous actions can be safely cancelled and on failure will return errors with both a type and a full textual description. libdfu supports DFU 1.0, DFU 1.1 and the ST DfuSe vendor extension, and handles many device `quirks' necessary for the real-world implementations of DFU\&. .PP Additionally \fBdfu-tool\fR can be used to convert firmware from various different formats, or to modify details about the elements, images and metadata contained inside the firmware file. For example, you can easily convert DFU 1.1 firmware into the vendor-specific DfuSe format, convert a Intel HEX file into a raw file padded to a specific size, or add new copyright and licensing information to an existing file. Fields such as the vendor and product IDs can be changed, and the firmware elements can be encrypted and decrypted using various different methods. Merging two DfuSe files together is also possible, although specifying different alt-setting numbers before merging is a good idea to avoid confusion. .PP Although \fBdfu-tool\fR tries to provide a large number of easy-to-use commands, it may only be possible to do certain operations using the libdfu library directly. This is easier than it sounds, as the library is built with GObject Introspection support making it usable in many languages such as C, Javascript and Python. Furthermore, using the library is a good idea if you want to perform multiple operations on large firmware files, for instance, converting from an Intel HEX file, padding to a certain size, setting vendor and adding licensing information and then saving to a remote location. fwupd-1.2.14/plugins/dfu/dfu.quirk000066400000000000000000000210021402665037500170210ustar00rootroot00000000000000# All DFU devices [DeviceInstanceId=USB\CLASS_FE&SUBCLASS_01] Plugin = dfu # on PC platforms the DW1820A firmware is loaded at runtime and can't # be stored on the device itself as the flash chip is unpopulated [DeviceInstanceId=USB\VID_0A5C&PID_6412] Plugin = dfu DfuFlags = ignore-runtime # Openmoko Freerunner / GTA02 [DeviceInstanceId=USB\VID_1D50&PID_5119] Plugin = dfu DfuFlags = ignore-polltimeout,no-pid-change,no-dfu-runtime,action-required,no-get-status-upload # OpenPCD Reader [DeviceInstanceId=USB\VID_16C0&PID_076B] Plugin = dfu DfuFlags = ignore-polltimeout # SIMtrace [DeviceInstanceId=USB\VID_16C0&PID_0762] Plugin = dfu DfuFlags = ignore-polltimeout # OpenPICC [DeviceInstanceId=USB\VID_16C0&PID_076C] Plugin = dfu DfuFlags = ignore-polltimeout # Siemens AG, PXM 40 & PXM 50 [DeviceInstanceId=USB\VID_0908&PID_02C4] Plugin = dfu [DeviceInstanceId=USB\VID_0908&PID_02C5] Plugin = dfu [DeviceInstanceId=USB\VID_0908&PID_02C4&REV_0000] DfuFlags = ignore-polltimeout [DeviceInstanceId=USB\VID_0908&PID_02C5&REV_0000] DfuFlags = ignore-polltimeout # Midiman M-Audio Transit [DeviceInstanceId=USB\VID_0763&PID_2806] Plugin = dfu DfuFlags = ignore-polltimeout # LPC DFU bootloader [DeviceInstanceId=USB\VID_1FC9&PID_000C] Plugin = dfu DfuFlags = force-dfu-mode # m-stack DFU [DeviceInstanceId=USB\VID_273F&PID_1003] DfuFlags = attach-upload-download [DeviceInstanceId=USB\VID_273F&PID_100A] DfuFlags = attach-upload-download # HydraBus [DeviceInstanceId=USB\VID_1D50&PID_60A7] Plugin = dfu DfuFlags = no-dfu-runtime,action-required # Jabra 410 [DeviceInstanceId=USB\VID_0B0E&PID_0412] Plugin = dfu DfuFlags = no-dfu-runtime DfuJabraDetach = 0201 CounterpartGuid = USB\VID_0B0E&PID_0411 # Jabra 510 [DeviceInstanceId=USB\VID_0B0E&PID_0420] Plugin = dfu DfuFlags = no-dfu-runtime DfuJabraDetach = 0201 CounterpartGuid = USB\VID_0B0E&PID_0421 # Jabra 710 [DeviceInstanceId=USB\VID_0B0E&PID_2475] Plugin = dfu DfuFlags = no-dfu-runtime DfuJabraDetach = 0508 CounterpartGuid = USB\VID_0B0E&PID_0982 # Jabra 810 [DeviceInstanceId=USB\VID_0B0E&PID_2456] Plugin = dfu DfuFlags = no-dfu-runtime DfuJabraDetach = 0508 CounterpartGuid = USB\VID_0B0E&PID_0971 [DeviceInstanceId=USB\VID_0B0E&PID_0411] Plugin = dfu DfuFlags = no-pid-change,force-dfu-mode,ignore-upload,attach-extra-reset [DeviceInstanceId=USB\VID_0B0E&PID_0421] Plugin = dfu DfuFlags = no-pid-change,force-dfu-mode,ignore-upload,attach-extra-reset [DeviceInstanceId=USB\VID_0B0E&PID_0982] Plugin = dfu DfuFlags = no-pid-change,force-dfu-mode,ignore-upload,attach-extra-reset [DeviceInstanceId=USB\VID_0B0E&PID_0971] Plugin = dfu DfuFlags = no-pid-change,force-dfu-mode,ignore-upload,attach-extra-reset # Atmel AT90USB Bootloader [DeviceInstanceId=USB\VID_03EB&PID_2FF7] Plugin = dfu DfuFlags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FF9] Plugin = dfu DfuFlags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FFA] Plugin = dfu DfuFlags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FFB] Plugin = dfu DfuFlags = use-any-interface,legacy-protocol,force-dfu-mode # Atmel ATMEGA Bootloader [DeviceInstanceId=USB\VID_03EB&PID_2FEE] Plugin = dfu DfuFlags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FEF] Plugin = dfu DfuFlags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FF0] Plugin = dfu DfuFlags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FF2] Plugin = dfu DfuFlags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FF3] Plugin = dfu DfuFlags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FF4] Plugin = dfu DfuFlags = use-any-interface,legacy-protocol,force-dfu-mode # Atmel XMEGA Bootloader [DeviceInstanceId=USB\VID_03EB&PID_2FE2] Plugin = dfu DfuFlags = use-any-interface,force-dfu-mode # Leaflabs Maple3 [DeviceInstanceId=USB\VID_1EAF&PID_0003&REV_0200] Plugin = dfu DfuForceVersion = 0110 # Atmel FLIP Bootloader [DeviceInstanceId=USB\VID_03EB] Plugin = dfu DfuForceVersion = ff01 # AT32UC3B1256 [BLDR][USER] USER@0x2000, BLDR+USER=0x40000 [AvrChipId=0x58200203] DfuAltName = @Flash/0x2000/1*248Kg # AT32UC3A3256 [BLDR][USER] USER@0x2000, BLDR+USER=0x40000 [AvrChipId=0x58200204] DfuAltName = @Flash/0x2000/1*248Kg # AT90USB1287 [USER][BLDR] BLDR@0x1e000, BLDR+USER=0x20000 [AvrChipId=0x581e9782] DfuAltName = @Flash/0x0/1*120Kg # AT90USB647 [USER][BLDR] BLDR@0x0e000, BLDR+USER=0x10000 # AT90USB646 [USER][BLDR] BLDR@0x0e000, BLDR+USER=0x10000 [AvrChipId=0x581e9682] DfuAltName = @Flash/0x0/1*56Kg # ATmega32U4 [USER][BLDR] BLDR@0x07000, BLDR+USER=0x08000 [AvrChipId=0x581e9587] DfuAltName = @Flash/0x0/1*28Kg # ATmega16U4 [USER][BLDR] BLDR@0x03000, BLDR+USER=0x04000 [AvrChipId=0x581e9488] DfuAltName = @Flash/0x0/1*12Kg # ATmega32U2 [USER][BLDR] BLDR@0x07000, BLDR+USER=0x08000 [AvrChipId=0x581e958a] DfuAltName = @Flash/0x0/1*28Kg # ATmega16U2 [USER][BLDR] BLDR@0x03000, BLDR+USER=0x04000 [AvrChipId=0x581e9489] DfuAltName = @Flash/0x0/1*12Kg # AT90USB162 [USER][BLDR] BLDR@0x03000, BLDR+USER=0x04000 [AvrChipId=0x581e9482] DfuAltName = @Flash/0x0/1*12Kg # ATmega8U2 [USER][BLDR] BLDR@0x01000, BLDR+USER=0x02000 [AvrChipId=0x581e9389] DfuAltName = @Flash/0x0/1*4Kg # AT90USB82 [USER][BLDR] BLDR@0x01000, BLDR+USER=0x02000 [AvrChipId=0x581e9382] DfuAltName = @Flash/0x0/1*4Kg # ATxmega16A4 [USER] USER=0x4000 [AvrChipId=0x1e9441] DfuAltName = @Flash/0x0/1*16Kg # ATxmega16C4 [USER] USER=0x4000 [AvrChipId=0x1e9544] DfuAltName = @Flash/0x0/1*16Kg # ATxmega16D4 [USER] USER=0x4000 [AvrChipId=0x1e9442] DfuAltName = @Flash/0x0/1*16Kg # ATxmega32A4 [USER] USER=0x8000 [AvrChipId=0x1e9541] DfuAltName = @Flash/0x0/1*32Kg # ATxmega32C4 [USER] USER=0x8000 [AvrChipId=0x1e9443] DfuAltName = @Flash/0x0/1*32Kg # ATxmega32D4 [USER] USER=0x8000 [AvrChipId=0x1e9542] DfuAltName = @Flash/0x0/1*32Kg # ATxmega64A4 [USER] USER=0x10000 [AvrChipId=0x1e9646] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64C3 [USER] USER=0x10000 [AvrChipId=0x1e9649] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64D3 [USER] USER=0x10000 [AvrChipId=0x1e964a] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64D4 [USER] USER=0x10000 [AvrChipId=0x1e9647] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64A1 [USER] USER=0x10000 [AvrChipId=0x1e964e] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64A3 [USER] USER=0x10000 [AvrChipId=0x1e9642] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64B1 [USER] USER=0x10000 [AvrChipId=0x1e9652] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64B3 [USER] USER=0x10000 [AvrChipId=0x1e9651] DfuAltName = @Flash/0x0/1*64Kg # ATxmega128C3 [USER] USER=0x20000 [AvrChipId=0x1e9752] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128D3 [USER] USER=0x20000 [AvrChipId=0x1e9748] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128D4 [USER] USER=0x20000 [AvrChipId=0x1e9747] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128A1 [USER] USER=0x20000 [AvrChipId=0x1e974c] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128A1D [USER] USER=0x20000 [AvrChipId=0x1e9741] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128A3 [USER] USER=0x20000 [AvrChipId=0x1e9742] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128A4 [USER] USER=0x20000 [AvrChipId=0x1e9746] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128B1 [USER] USER=0x20000 [AvrChipId=0x1e974d] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128B3 [USER] USER=0x20000 [AvrChipId=0x1e974b] DfuAltName = @Flash/0x0/1*128Kg # ATxmega192C3 [USER] USER=0x30000 [AvrChipId=0x1e9751] DfuAltName = @Flash/0x0/1*192Kg # ATxmega192D3 [USER] USER=0x30000 [AvrChipId=0x1e9749] DfuAltName = @Flash/0x0/1*192Kg # ATxmega192A1 [USER] USER=0x30000 [AvrChipId=0x1e974e] DfuAltName = @Flash/0x0/1*192Kg # ATxmega192A3 [USER] USER=0x30000 [AvrChipId=0x1e9744] DfuAltName = @Flash/0x0/1*192Kg # ATxmega256 [USER] USER=0x40000 [AvrChipId=0x1e9846] DfuAltName = @Flash/0x0/1*256Kg # ATxmega256D3 [USER] USER=0x40000 [AvrChipId=0x1e9844] DfuAltName = @Flash/0x0/1*256Kg # ATxmega256A3 [USER] USER=0x40000 [AvrChipId=0x1e9842] DfuAltName = @Flash/0x0/1*256Kg # ATxmega256A3B [USER] USER=0x40000 [AvrChipId=0x1e9843] DfuAltName = @Flash/0x0/1*256Kg # ATxmega384C3 [USER] USER=0x60000 [AvrChipId=0x1e9845] DfuAltName = @Flash/0x0/1*384Kg # ATxmega384D3 [USER] USER=0x60000 [AvrChipId=0x1e9847] DfuAltName = @Flash/0x0/1*384Kg # ATxmega8E5 [USER] USER=0x2000 [AvrChipId=0x1e9341] DfuAltName = @Flash/0x0/1*8Kg # ATxmega16E5 [USER] USER=0x4000 [AvrChipId=0x1e9445] DfuAltName = @Flash/0x0/1*16Kg # ATxmega32E5 [USER] USER=0x8000 [AvrChipId=0x1e954c] DfuAltName = @Flash/0x0/1*32Kg fwupd-1.2.14/plugins/dfu/fu-plugin-dfu.c000066400000000000000000000122211402665037500200170ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "dfu-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "org.usb.dfu"); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.st.dfuse"); } static void fu_plugin_dfu_state_changed_cb (DfuDevice *device, DfuState state, FuPlugin *plugin) { switch (state) { case DFU_STATE_DFU_UPLOAD_IDLE: fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_DEVICE_VERIFY); break; case DFU_STATE_DFU_DNLOAD_IDLE: fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_DEVICE_WRITE); break; default: break; } } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *dev, GError **error) { g_autoptr(DfuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* open the device */ device = dfu_device_new (fu_usb_device_get_dev (dev)); fu_device_set_quirks (FU_DEVICE (device), fu_plugin_get_quirks (plugin)); dfu_device_set_usb_context (device, fu_plugin_get_usb_context (plugin)); locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* ignore defective runtimes */ if (dfu_device_is_runtime (device) && dfu_device_has_quirk (device, DFU_DEVICE_QUIRK_IGNORE_RUNTIME)) { g_debug ("ignoring %s runtime", dfu_device_get_platform_id (device)); return TRUE; } /* watch all signals */ g_signal_connect (device, "state-changed", G_CALLBACK (fu_plugin_dfu_state_changed_cb), plugin); /* this is a guess and can be overridden in the metainfo file */ fu_device_add_icon (device, "drive-harddisk-usb"); /* insert to hash */ fu_plugin_device_add (plugin, FU_DEVICE (device)); return TRUE; } gboolean fu_plugin_update_detach (FuPlugin *plugin, FuDevice *dev, GError **error) { DfuDevice *device = DFU_DEVICE (dev); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* open it */ locker = fu_device_locker_new (device, &error_local); if (locker == NULL) return FALSE; if (!dfu_device_refresh_and_clear (device, error)) return FALSE; /* already in DFU mode */ if (!dfu_device_is_runtime (device)) return TRUE; /* detach and USB reset */ if (!dfu_device_detach (device, error)) return FALSE; /* wait for replug */ fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *dev, GError **error) { DfuDevice *device = DFU_DEVICE (dev); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* open it */ locker = fu_device_locker_new (device, &error_local); if (locker == NULL) return FALSE; if (!dfu_device_refresh_and_clear (device, error)) return FALSE; /* already in runtime mode */ if (dfu_device_is_runtime (device)) return TRUE; /* attach it */ if (!dfu_device_attach (device, error)) return FALSE; /* wait for replug */ fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { DfuDevice *device = DFU_DEVICE (dev); g_autoptr(DfuFirmware) dfu_firmware = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* open it */ locker = fu_device_locker_new (device, &error_local); if (locker == NULL) return FALSE; if (!dfu_device_refresh_and_clear (device, error)) return FALSE; /* hit hardware */ dfu_firmware = dfu_firmware_new (); if (!dfu_firmware_parse_data (dfu_firmware, blob_fw, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) return FALSE; if (!dfu_device_download (device, dfu_firmware, DFU_TARGET_TRANSFER_FLAG_VERIFY | DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID | DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID, error)) return FALSE; /* success */ return TRUE; } gboolean fu_plugin_verify (FuPlugin *plugin, FuDevice *dev, FuPluginVerifyFlags flags, GError **error) { GBytes *blob_fw; DfuDevice *device = DFU_DEVICE (dev); g_autoptr(DfuFirmware) dfu_firmware = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; GChecksumType checksum_types[] = { G_CHECKSUM_SHA1, G_CHECKSUM_SHA256, 0 }; /* open it */ locker = fu_device_locker_new (device, &error_local); if (locker == NULL) return FALSE; if (!dfu_device_refresh_and_clear (device, error)) return FALSE; /* get data from hardware */ g_debug ("uploading from device->host"); dfu_firmware = dfu_device_upload (device, DFU_TARGET_TRANSFER_FLAG_NONE, error); if (dfu_firmware == NULL) return FALSE; /* get the checksum */ blob_fw = dfu_firmware_write_data (dfu_firmware, error); if (blob_fw == NULL) return FALSE; for (guint i = 0; checksum_types[i] != 0; i++) { g_autofree gchar *hash = NULL; hash = g_compute_checksum_for_bytes (checksum_types[i], blob_fw); fu_device_add_checksum (dev, hash); } /* success */ return TRUE; } fwupd-1.2.14/plugins/dfu/fuzzing.md000066400000000000000000000017211402665037500172120ustar00rootroot00000000000000Fuzzing ======= CC=afl-gcc meson --default-library=static ../ AFL_HARDEN=1 ninja afl-fuzz -m 300 -i fuzzing -o findings ./plugins/dfu/dfu-tool --force dump @@ afl-fuzz -m 300 -i fuzzing-patch-dump -o findings ./plugins/dfu/dfu-tool --force patch-dump @@ Generating ---------- mkdir -p fuzzing-patch-dump echo -n hello > complete-replace.old echo -n XXXXX > complete-replace.new ./plugins/dfu/dfu-tool patch-create complete-replace.old complete-replace.new fuzzing-patch-dump/complete-replace.bdiff echo -n helloworldhelloworldhelloworldhelloworld > grow-two-chunks.old echo -n XelloXorldhelloworldhelloworldhelloworlXXX > grow-two-chunks.new ./plugins/dfu/dfu-tool patch-create grow-two-chunks.old grow-two-chunks.new fuzzing-patch-dump/grow-two-chunks.bdiff echo "1" -n > test.bin srec_cat test.bin -binary -o fuzzing/test2.srec -address-length=4 -header=a srec_cat test.bin -binary -o fuzzing/test1.srec -header=b fwupd-1.2.14/plugins/dfu/fuzzing/000077500000000000000000000000001402665037500166675ustar00rootroot00000000000000fwupd-1.2.14/plugins/dfu/fuzzing/example-addr32.srec000066400000000000000000000000551402665037500222550ustar00rootroot00000000000000S00400006299 S108000031202D6E0A01 S5030001FB fwupd-1.2.14/plugins/dfu/fuzzing/example.dfu000066400000000000000000000000341402665037500210170ustar00rootroot00000000000000hello world UFDfd-fwupd-1.2.14/plugins/dfu/fuzzing/example.dfuse000066400000000000000000000005011402665037500213460ustar00rootroot00000000000000DfuSe1TargetST hello world UFDw~fwupd-1.2.14/plugins/dfu/fuzzing/example.srec000066400000000000000000000000611402665037500211750ustar00rootroot00000000000000S0040000619A S30A0000000031202D6E0AFF S5030001FB fwupd-1.2.14/plugins/dfu/fuzzing/firmware.hex000066400000000000000000000006001402665037500212050ustar00rootroot00000000000000:044000003DEF20F080 :10400800FACF01F0FBCF02F0E9CF03F0EACF04F0DA :10401800E1CF05F0E2CF06F0D9CF07F0DACF08F00C :10402800F3CF09F0F4CF0AF0F6CF0BF0F7CF0CF08E :10403800F8CF0DF0F5CF0EF00EC0F5FF0DC0F8FF6C :104048000CC0F7FF0BC0F6FF0AC0F4FF09C0F3FF6E :1040580008C0DAFF07C0D9FF06C0E2FF05C0E1FFCC :1040680004C0EAFF03C0E9FF02C0FBFF01C0FAFF7A :1040780011003FEF20F0000142EF20F03DEF20F06B :00000001FF fwupd-1.2.14/plugins/dfu/fuzzing/metadata-multiple.dfu000066400000000000000000000001151402665037500227750ustar00rootroot00000000000000amaliaMDkeyvalue CopyrightRichard HughesLicenseGPL-2.0+UFDGZ+@fwupd-1.2.14/plugins/dfu/fuzzing/metadata.dfu000066400000000000000000000000431402665037500211440ustar00rootroot00000000000000amaliaMDkeyvalueUFDXfwupd-1.2.14/plugins/dfu/meson.build000066400000000000000000000054271402665037500173450ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginDfu"'] install_data(['dfu.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) dfu = static_library( 'dfu', fu_hash, sources : [ 'dfu-cipher-xtea.c', 'dfu-common.c', 'dfu-device.c', 'dfu-element.c', 'dfu-firmware.c', 'dfu-format-dfu.c', 'dfu-format-dfuse.c', 'dfu-format-ihex.c', 'dfu-format-srec.c', 'dfu-format-metadata.c', 'dfu-format-raw.c', 'dfu-image.c', 'dfu-patch.c', 'dfu-sector.c', 'dfu-target.c', 'dfu-target-stm.c', 'dfu-target-avr.c', ], dependencies : [ giounix, libm, gusb, gudev, ], link_with : [ libfwupdprivate, ], c_args : cargs, include_directories : [ include_directories('../..'), include_directories('../../libfwupd'), include_directories('../../src'), ], ) shared_module('fu_plugin_dfu', fu_hash, sources : [ 'fu-plugin-dfu.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ libfwupdprivate, dfu, ], ) dfu_tool = executable( 'dfu-tool', fu_hash, sources : [ 'dfu-tool.c', ], include_directories : [ include_directories('..'), include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ libxmlb, giounix, libm, gusb, gudev, ], link_with : [ dfu, libfwupdprivate, ], c_args : cargs, install : true, install_dir : bindir ) if get_option('man') help2man = find_program('help2man') extra = join_paths(meson.current_source_dir(), 'dfu-tool.h2m') custom_target('dfu-tool-man', input : dfu_tool, output : 'dfu-tool.1', command : [ help2man, '@INPUT@', '--no-info', '--output', '@OUTPUT@', '--name', 'dfu-tool', '--manual', 'User Commands', '--version-string', fwupd_version, '--include', extra, ], install : true, install_dir : join_paths(mandir, 'man1'), ) endif if get_option('tests') testdatadir = join_paths(meson.current_source_dir(), 'tests') cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'dfu-self-test', fu_hash, sources : [ 'dfu-self-test.c' ], include_directories : [ include_directories('..'), include_directories('../..'), include_directories('../../libfwupd'), include_directories('../../src'), ], dependencies : [ libxmlb, gio, gusb, gudev, libm, ], link_with : [ dfu, libfwupdprivate, ], c_args : cargs ) test('dfu-self-test', e) endif fwupd-1.2.14/plugins/dfu/tests/000077500000000000000000000000001402665037500163355ustar00rootroot00000000000000fwupd-1.2.14/plugins/dfu/tests/dev_VRBRAIN.dfu000066400000000000000000002650221402665037500210050ustar00rootroot00000000000000DfuSejTargetST...00DD200085DD40008A9 :1067DC00E1D2000849D200080000000000000000CF :1067EC003DD20008D90300000000803F0000803F2C :1067FC000000803F4BFB00084FFB0008C5FB000866 :10680C0055FB000885FB000800C2010000000800D1 :10681C0041FC00084DFC00087DFC000859FC0008F8 :10682C00hf8?  Q=%5AM-MaY8ML+h"\+#+ K "p K"Z K"p8 *h# M\ !(`3#8  @  8BL#xB (};61@""EN@', ##p 9K`9K"Z9J#p8J`b8Kx*a5J6I` ""p"pV1K3J` p".K2J` #K,K0J` #F)K/J`#A'K-J`#<$K,J`#7"K*J` p3K)J`##pK"`*Op#Kz#Kx(#!I"ZT%Up+аR+F" Dd#Sp#p K`KJ`##pK"8  @   p/////////& '  /  -A K!L[{+#x+ F#x +2L#h&F+-M+x/FK E #+pK"h\K"A;xKh B K"p K2h\+K"Z'     @pFFF FdK];pH1x *F *-*F +*F 7zFF0IJ ,  zj&zgz.)zgjF09Ȳ ( j'jgz7zxE*4Zx-*3"+*33";!08IJ , $4 FOsFz)j9gz+jgz;"'zzg'z '  LD!Kh & Hm1H$KHX 4Rh ,1h01p )K)KOzqx'H%Kh%Kh%KS!%KF$H$"JR#I"+BH4 ILK"xHS"#x+KH}+ n!o!HK!FFOcHp@x' . 1( \ B@B1A"2 (1&2& 024D 428 hF F5!FF(h8@7 HJ! tHC q @ f2p2F($#I\ hDIx) :T3+%f]H1F(5`1FH- FH#JD! D IY\ 3+# H 0p 4M22L! 10 HH@پ22-AFFF)H)KS$A#@3B&H4$H!(F$I"FX"H#LT)H+x-+54OO&KS&p'HA(F9F"Fu @5HOH~9F HA62/"24? 3/ 3:3D31FH[J#pJ`Jp~ F@M3% X $ H@ H: h @ e3r2F F8KHZyK:S"(8FI"F`HLTH:H%KS%`&H@8F1F"F5(K H]q1F@L! 3 0?30"2433F F K H@ FS(KL FL! 36pF H0 FI$&F%Fx,,,F(FI 4 -H !p@,KH3 )Fp@𰽦zsz Hp@ H)F2FK#`p36!4^( G4_448F?hFKHD!FF 4},8(F(F !(FKDF8H!8@bH 44#+( " F-  XB#p F0-@! "F+z̿8'8g(z('iFȊ "Zpp- #-#hF 0(0# 000 (0# 00(0# 0iF8Ų*F F#cU FI F)D F0o:zD}27yF F+7 hx0h,h)h%hh"hhF rFHzhF fFH hF [FH !H%Hh"i0-2,244-C-F:ONF (+x*+LHP$1YJHu09FHHD4@3B}(F=! (_D FF F'@$|CV@FAFF(F (<1KDzz/۔zz&"y*:* hp hhhAFH F!C0H7/HCػF7Y)F8FX H9FH! DH4@3BѽB454445p-^H^L]H]HF?H:H|I hF-F6Hs1Hkh hFF/Hb -,HiZ*LFT)HP F'KS$A#@+B$HC4s1D J\,1)$H@iF.MHaY)(!GH4@3Bpm1L! 556 0e5p56-21x5/5/54M5B54𵉰FŲ-rHLTz@z,5)F{H FwH FsH wFnH nFkH -hzhKgzgj"B ZSZj2vjwz7z]Hjzzc$#DzWzWKXHzzؿF4O ,RHt FQI".8PKD0"(a{ FLI" F F !(nF0F ,FFDKS$P CHM0F)F8 F'?H)FAL4F .FH F !FPE(F|/K $F(F !FHE(Fj&K 4(F !FHE(FZK 4(F !H0HK, HH ! 5d! 551`! 5 #<5546L! 6 036"66\6-GgKxFB#eH0dHcM(h F((h`L  (F?(OO %h[OVF-[H9h*FPF>FWK 7Bα+FpB#` ZMI0-+ 'T"`TTF#h VEGH>VE V 8 ! 9H/%#hB:KY] 5#h+(64HU (4H ) )D2H-M"h*I#T.K" #F8h0h(DG&HH!0"##`0+b" ()^/+?\ Ҳ^*?V )?RZ"`J(hT ]I); J#`!TH>$ 66& X h0% (1664i66KxH*Zoy*ѓ$*O +P@JB8 pG pGpGpFDaN0FJ(ІH0Dc1p4ML!  K IFOb$*"$OrqHL! H -G.H P#9iCYC@aC'J"3+%J!0@aB"d  \B_OL d "/)F%MFED !}CeD-Ozq I5DUR2 4*xyGH 8( L! ' m@/KH"pObZ"q"$"F$FQ&IOr0pi"F\2oJ%4 X ! (L3wCr Fh 1(5 -K4B - 7 !"@1L! H 8F F (F!F8@ ŻKhB  pG\ KhC`pG\ Kh"`pG\ KhpG\ KhB  pGL! KhC`pGL! -GF,JMhQhLkFOb!(F!Or FH#+pO #kqOpO` Osb1#d1*#`1Osh1 #f1n#}1+#~1!#1##1@LC1@ls&'"O Op!@^1@~CS!T!UxdjalanaVaXaZa]a^a\qaaqaaaq@1@:sB1OzsO1D1N@~S OQF1v@S @H1@S@q`uuU O(J12#!pO - L1P1r!s`sqqaasatt&pu#qO O OP Od #0$0!0#vO OZ  u10A cKOx  %&. s&vsfv w"`f&/0ccRKcRKcRK#dRHDp#E0mOHsz0#0Oc0@30v`w`xyp|`ΐ#FOr~ Ob  6@Q. " d" Or (" O|R " @lr @R  $O~R@`!R#4#3!+a$(p IOrt"C FG,ML! H ?(|?= w?@=MKh"`pGL! K"`pGL! KhpGL! 8L% pa U%b`b8H&  z zzؿgz    K K5< *[-:Kz('( z ' H zh z z zzz:+ C3#` ,}? F E zz''zzzzzGjzjz F*  zzz 0"FI\ F.,3xD$,T\ $bC0 ,0:R+3F0 0 8 Mz Kx+ K Lh[hc` `<KKK`#+s8& ' ' ' (  ' "FHL!RHRHDR `a`` LD3 `a`` LD+ `a``  ' 0'  H :K;zXy:J{ Gzy'zjzGz3I`'zz Gz/J|`&zz Gz~'z zFj&zyjj Fj!JX|`&jj Fj~'jj FjjjJ|ڱyJ{~Fj&jj fjzz:gzzzpGH B D0 zD & & 0& F _Op[(> Mnp$`l`M1B '']K!FrhH;F K` :1+ȿA!SK IBBh ځ JhQh H& L! O@&  NN-AKx++M #kKOBZLh P &.h'h F8F n hO ( L8hOF& F@F fO0H& @ @& sLh#` hc`MhNK `hihJ3h`AI hz)hJzAgz`zz J` K Kp ' p D& &   | H  DPcBݠ @8pGKB @0pG&KhFx+F^x2nB [۲+0:D۲ *"B RҲk0>2DҲ.,L ! &qC&xx0941D8!HBCd !$H @B-OL-hӹN3x;+Kh K1+F4+Рh ($-  - -*- ,-~J#p~Jp~Jp{O:x{K`!TzJxpG)YxP)љxG)xG)yA)!pxnIR(xM( yC)!px**9N*K  (J.aH1bK`?xS+<_KhRB`7ZH#[KX`1xW+.XKZhRBZ`)xVK0*""r! PKr MKX.. HK؁  ACOzrXCAJ6#*-`;pa?J!pc=N3x+K7I x@+٣7ҲKx@+7;0;۲D,Jx۲B2,Kx- -,*K,Jډ+K$ 0 #"J'Kh`QhY`%Kz%Op S;(ј0+ K:hd#ZCK`#"2p Jx+Yp JTKx# JxM@p#~& H& L!     (  &  4' L ' ' ' ' & Kx")T %3Pitb("p-@Ex2p@"pKpK I"pK xx*DҲ pDpKp,I"p~K xx*DҲ pDp|KxI"pwK xx*DҲ pDpuK%"kKpoJ#iHjJxx)Dɲp1DpiI *hHT2 cI B@""p\KxBXJ#ppXKxB@؀VKx+J++5+U0+@ɀRKyRK*"pxNJ#mLKNJYhQ`h`iLMLNOzs+JKxrJK" p(i3|+fEK*hd#ZCCK`]:JSyyCBCA:Jp+P7JrM2KzКzJBJA2I p 0Ir/ 3Kp:(K2NZi2i1MBs+/K" p@#K|+3d+"* #'K )ݢrB !H\T H\TH\THxT3I xKx"p p#0       ' ' ' &   ' & ' 4' L  ( & ' ' '( #+?qMh`` IKx* ""pKz*?\Jx*WZx|s{ KMxO2KxK#JIXPNXPQ N@S p^ `N B|&8DC xHP( %FѬ y3+nOnMK;hlz Ezzz;z8`zXHzz_K [Id# YJ 0/F UIVK x*=z0hh'zphO*zZh''zzKJ jHJz'&z g'zz" p0hqhY``0#Ky+?pԀԐ԰ rh(K*$Jz2hzgz`z z0" *?*Ѐ:H& l & '  ( < ` '   ig  @ zD' ' ' )     t D  D& p |N%S{J z5   zg'zz oJR zzg'zDz1FJF2eIfZzS zjFjqhr`&%jQjXD%Ob6jZjSIz7gzzzzfzz 5 II12b*5 D%@2BJ( !"5V`-ql5K 7KEFBݕz0zg'zzD+KPh XF(AݐBCBB̿##""N+K ɚ ( KI @2 WHC(DBݠ @8( @00`#   H @0HH  B| 79&  D 0' 0 5`3hN S(3:z%gh h w Jв^ *ESjIzzOzrzz % % K z z1Fg'zFz5 XZzjshzr`Fj&%j:jDzK{I@2Z6jjz7gzzzzfzz `DRXSkK "35V+`-[Ft 2dJhd"cJ0 B`Kh`KhBs(@BB#0YKUKWKqF/ ЈrxSKhYh |hP (x3xLM+;+D+pGkjc+Fii++ AMihYh sxhh iwb3aN@ (И1+i[hO0K"a 6jx+KYh  ++i+ +%;*j%K/aS2ia[haiiB ji\ Yi3a*jI2*bQ2 #a#+a |#s`SH D  & @0' H & ( p |  L! OH& HM H & rhK2r`J!JZx2Ҳ!RZpJ`#pJ3ar h@B  % H& L! ' & 8)M-h F ;@h @I -@I zjg @z` ( fjz) 1zzK '  z0 z 8Ds8 DI@ 8(L(zE0'M: ' +:z' -zKZz'zK zzzzz0:zK'zzgzzzzKg'zz'zzQ8@8H 5<@ A$ & @FaD& L=DI@&  zz 8 F F 7@ zzzzzzz  88- H F F F@H @H 8@H ʰ@L *@L kZ+zeZHZz )jk+z*z( Fjjzzk'zkhzjjjz+jzhj(z jzz8ssB(@pGDpG pG -VKVzVzVMfhzgzzzPK0gzz:MKz gzFz* 0zz*ziFz:zzBD0z+9KZx9Krh@!Qz7'zzzh@#:z-K:zwz*Jzz(K)L 'hxzzwz 0d 8D `gh 0YH 8D``h {/NKhD`Kh8D2`` 755Д z  aD B  75|&  & &  $tI$    &   #Khz"I"N #Or3h(` }IJ00`z0`J8zz+hgzzgzBzD o".`0(`#+`   ;-A b(ЧKLN" `$r , 10`3*э0#01 ' [ ' 1O \KOBZ'MkiCka ?GOCk4   >#J@QR3$+~KJ` pu1+ vH tHrHpHJO`nHOp)O ReK "Z_JOA'F0[M 0 kiCka ?VK "OCk|OpX# R(h3# 0G C   : O 4 / cy]kBkA# 0cyYJBJACBCA0N10P10O@Ozs 0 KJ100#O00O010K00 #K`cy+KOrKOzrK"K"Zs( L! & @' P MH@@H 4  p | & K "ZOB$OB$@-OK 0*RB+[B$BF%FF'FF&F00+?.=|K{Ih!{KH_[_QB@wK 5 *0  d#oQCjKiIFX`DBrP0XC0#aKy .<_K`;p]I 0YK^T DOzRbH^+[B . ѻ0+[Bd+#H0X }# 0O@Ky. r {C 3Oq x .BF;FSF1I԰hZ0I5 q\%  T T DDT D  D  O RoOD6D.%  K ZJ҈ڀd' L!   L & H  $( D' t F d @ ( |' -O$%F'F/FN@і$0 3SC[#AK8h!@QB>K^s* D;Jxk#08y3KC:|PC #3zCC2J5࠻/I>D zT 0 0 D OOO#KD XD0Or YCKT TD  `K>N#hD=J#`x3۲p"7G0O 8FXD10c̐0  PDK"h`KPCOzr AqJ# K'``J3B6 9' H L! ( d' ' & ( I@' 4C  D x' m ' ( @~' ' 8( $( '  8 *JB ?'K&K+  (#KZi Za xK "ZkxK "]KIh h*h{JXpJPi# sPa `#pqKx[O`0O5z'  p @,  & $ KDL! -A #O(7"LFF0貘G"@SBKI hK@+;c3 K# cDDD 5-S2 `$0G'4,ѽ'  L! < P (03J(_(1K2K[2K2zYxz1L2HgzZz]}.Ifjz,zjjZfjxjjZfj{jjZfj}jjZfjxjjZfjzz\{}JGz'zz:Gz K'zz K`0( m=?H B( X' l' zDEMA-O- M1+   '20 IJ hh,F(nC 3`O@z Kx >OppO(;ЍNL0B, !:z DB#0}K20 B xN#3#"AF^BĿc۲2BC@*iLȁ"xBј *2" O@#p [NXL!0B DB O@( 1BO RJ#`S``PJ`S`F0#EKx+@O F0s@Kx[DK[xC1ј0+@;JSxFñF0##x_+1+𰀶F0+@#x}+@r"xW*/KOzrOpY v)K " o&K JxZ*!Jxp"K KxpK ""p#x]+[+^+F0k"xo**};L! , @ ' ' H p  & 8 t L p | ( n \ ]   !#x~+#x+щKOr+#0#x+ѳ3+ѳ; +s3+ s;s Fp#0 HuJuKxquIIxYBxpHp2$[|SnKx*nJxjJ2!"jKx2eJRxpgK"pgH#"0T$$@ "C@TB̿$$>"C@dBԿ!!\@3 C +#CLIFB  XT3+FKxFLKKK KK0B  o@xCEJ`S` O #HZ  KThG KShGK Kȱ!d"*j zzzzgzz"` L! ( ( ( ( H  < j z z Kfj}1j:zfzz pG33S@EL! @CXCK@rz!OzrPCx!pGL! 8 %$ D =O I~A#"FB3+"DJpJXC!SCJ8L! . z' ' 0 J Hh1KB#H`hH@!@IP# h,D$ `0t H    pKMh*h*!N+`3hL{ciGhG+h"hHID+`iG#3` phG#iG+hbh D0`+`p p, l x (   K-O[hHIG(gKԒF*L&5FF Oz !#Q a9Y DQ#i3+z#a#zz`j:wjzz6z7jzzzzzz#*)0*09#i+ ;:z  Kf1:z Ozs0#0$080`{`;` YOzr3 !"*0q5-|0;0#9: ) 3+lOjL8dM(@#FiN5!PXaDP! R3+]Nj( 0Osj! 2l! 2QJn1PKYF;;; (iJO:2*IKjlYnBIDKHY*2#F@H52*&FPFXfDFP& R3+0Hja*5Kp5Kp5K!p/KjYl,In1'KIX::,Jx&I#hp2"jHhhIl Rn! JFS*j1+jl1kn1( t& & p  L!   @ H :  n  \  8JLKUhOE%GKe"p8L! @( p:K:Lh"h*j33#`6K7H[hG6K7I{F"h `#4ND"p!F.J2NZS1NS3+хs/Kx(K*Jp(]rt!(X h+1"h!H"I#JB"KZi Za#J^ZBĿMZS^BLS3+J#`^^J,D%D+D+pA FM p p, ( & &  L! @B@BKO 0O0C#@H 8M+hF!Fhn+hyT@q8D F @ @޿F @ӿKhZyQYqDypGD F F@8F$ FM , L > ! #h"(Fq#hF8@D F ܿF ؿKhyD pFF F$㲝B0]w4pDm-C'$%KxBKJ\ !#@F F &FDNEJ65$$ dWGNGM3hGL+`O`##` hd#h3#`AM!"(F # +p$#lpp$ h#OPf+U 3K"4U4F H*#+U!Qp"T4 ~( #+U4Op(c "*U "T4O{y*+ #+U4 # f+UyK"U0O tUi K"U{y+*#+U!Qp"T4Kp& L! -O%KhBpKNL"33`Kx OھO;x 3hLh(R#hh#hI $bBbAI *ѭK[x+#(K4B *M(""&*<(""* @(OG ZqHq"*yFP@q"*ѓHG ByA@qQGDqB@F0u+i+@E+ȆB+ @+A+@φ j$C+RD+@Æ ^pJ#pf+bd+9e+@ LjK=FD D [F WF S@D MDD DDRJyxDCSxCyDSyDCSzDLKyDzD$YzDA$yD$zzD$D4Z{DB4{D4|DDyDZ|DBD|DD|DD}DTZ}DBT}}DTDT3K xFg+h+@1/Ho+$l+ƃj+k+@! &Km%Kh#KxѼm+n+r+Kp+q+@L@$s+|t+@ D $ & L! D ' F( ' ' l +݄>+w'x+ԁ v+Vw+@L xSOF++@x0@$+J+X+@𓅿L r++++F++zw++@qKJ+ń +ˀ+@bL`8+v+@T $$<$KS4, @KLrKp ```KKKxBp z4`pvrsDE`u|KxB҅KpzKH4F]vLwY U#Q$M%I!E"JoaCoLeO+?@;B7D3%!|@8 }~x$MKS4,oBK[x+kEK(O"F$Y  ;KXy 9HhB 4J"@C3-K -K-Lo&O4#%L L}vrnHs G ( H ' & ' ' ' 4' l L! ^( @ &  & 0 ' 8 LO~62.K[y+++Q+!+2L_+3+F0_+AѴ0_++6Ѵ0H@*_+Ѵ0+"Ѵ0@_++Ѵ0++ Ѵ0@@ t O~ 0sKFt!عhй@'_;FPF!FJFX;FPF'!F"PPF!FJF;FI|mй|_O SFHF!F"5'SFHF!F",SFHF!F"%SFHFO !F"HF!F"SFHF!F"SF HF!F"SFHF!F"SF6#_!F"KF.'_;FHF!F";FHF!F"'HF!F"#HF!F"#'_;FPF!F"#PF!F";F 9!N :$K_4,ѕ .KLzKx h`h K K KH L! ' & ' ' ' 4' L L K$ KhK K!"hoKOrh!^KK|!K2h +[BXCh(@B!OrAL B >#:$6%2!." L 4`x!z`}DE/ H0B݁K\KB4F$KJ#DzB 40E$xL P@BD| ( #}~3 $4 ,hF RKPK\h$FHF@F FIKh m,*k 4UKhTKhSKh0BPK]OK]NK]MK]4 IL`q=OVXZxzP8L `yyqVXZxz, $$K#D4Q,zK#D4, $K]4; , F C 3h"I 5D' H & ' ' '( L! VBېBFpGFpG Դ ȿA pG` pGAKV!-X)t*;z;zX!Z19I*hȊ p( :'ꪰh *z'@h @H @H  @j @j  zizGZZK* jijZFZZzHj*(( ih ZjzL! I@4CH 80"L-:0 z :ɚ芰0) : FȊz U z() z F zh) z 78 :*0"& CC [BK[B CC[B [B C C  CC[B [BK[BKxF|pGH K!!#K`]L! & pGpG*K+Jhh`)JhY`Qhh`'J`haQhha%JYahaQhhZb"JbQY҈څJQYJنQZJx"BLB4@2Jd JlQp JnhfJR^ \ , ' ' L' d'  &  F( 2 &  0 I8+ F,d ಽ@CppNLMG 0x#h(hchhh K K K3x#t+h`kh`p' ' ' 4' L 8FF#F IX8+ FI׹+  S+I)]pGe-O F BM kh3FF+hBv=H+hFT#<9Hkh3k`7K"Dp$5KxBT  !\@0KhB kh3k` &OE5'Kx!, @4!p+h #XDCDx ( '00&Jh4x#0004 :h2:`O2z`(hHE,  XY I -OM+x;+-Pg{ON9shd3B@$KJxB 3h}O\0;h34;`1hwKR\*@z`O2Z`#r##sK rH !3"F(@aOpfK0O2`Z`#S`#O#dJ#cH!(@AXKO2`Z`#8[J\H#$!(@*MK,p`%JL#hc`##`HKxchD@+@#+c`@BKh + ^^  $)2:BKVDHch;OBHBICJch);GAKAH$ch;<=K>H9K=H@7K;HBch;*8K9Hhch;"6K7Hych ;*K4H}pch;&K0H~!ech;,K-H]ch ;c`#h3#`2FE F"J#`"J`"J`!K`# I h\\X^(\8\^XXVXL! XH YY Y( 2Y@ EYRYbY }Y h Kh)@MI ,hKh h`hhh i`ii &O";h@BS z6.Ѵ(Nr*n,j@1.S;hզKd0 G;hZ;hՠnN lInEpA0<2844608,:(@1<&KxB F6<<0;h+h^P+h{J|Ik``KC3!A[t!"*`oH4$9DB#eNZP whh3hhhDshhC$ ;Ph[h4 ,#  iiQ3 +"FX ?( (1)+##2 *++CCO @@pB ?@b^Z %cS)(O @?J 4J J Bj?8% +`] ++,`Y &`Y !`Y@ `Y `Y@`Y5 -O Ѭh \ L!  5rOK!h@B Ы ;DR S<5-# (( D 3+ORO_i-H1)HHB@F%FF)))6ѻbY_F-`Y@ O # `Y@ `Y `]0@ {`YvbY_5-OѻPFguKhUйdd0%P# llA3+5 nh A<5%-J$k R@ @;#O T+2  E2h2rh2h20" D4,$2h[D4rhDhD"0 D,$6KxB2h[D4rhDhD"0 D'I hչ^^0sh2h`"K"Ir`JC2!ARt!33`Op(NKIhBFLM`"h+hB bhkhBJh+&Kh["H h`h#h+`chk`#  5rO ?B' h JKx*|BJhhBRhhBs Kh2` Kh2 *"` Kh2`#+p ' ' h `N3x+@^M  B#4 FF24F"F4OHOIAiBĿ@BKJ̿۲#p"1 0$ ,hFDIc,hFCI zhFAIBsB?I?IhF"F[>HiF"6P;HiF "08 86K4B0pN!"3H3O3K3J`t2`Y`1K1K$<`;+&!*)KxB  hyCXBXA$K#Dz0   m ;h"@C;`4,L##`#``3p L! ( YI YYYYY+  \ 2  H Kx* $Hpp"F I@o + YO /K"pp  pG pG FFF pG+ FFF pGFF pGHpGBFH<@K`F (#h+ѰXBXA K" @ K" ZOpF$ F@8F F7 !(6 ,D@(@ d,  (  , $ <88J#pF F`F z(2h*( )F"H i f p p <@FOrH T Q NCXBXA<@ F##0#0#0H#iF0']@YBK" % =(F)FL(F$\9NOzs Oq3`>(3 xU5-ѝ0+ѝ0+3h3h+ OA!(: !(F 8U4,ѝ0@ $ $)F ($%%F3h[3h+ Oq($(F!( Kp Kx"pxpx@ l J KxpxpGJ 8KxFF,KxXI,  ع(FOq8$ h(FOq<u !v  8 8J l p,KFxFFLxYK(KxXv, 1Ft$5Op;( 0(F8$$З !FY 1FUOpP (F<uOp(   p p pJ l rKx F܀(Ԁ@ !F$hF!R(Н0 + 0? 1"] "C2;!MN3x뱍 !( hF!(0$ <ѝ 0"  F(hF!(1x 0a ? B 39@+`J[ 33+`F=#KxF?p iFT(i0 ѝ 0Z`3xXIO Xdh(Q!F! aF(I& Z(CG0P!8$$- F pJ l Op!LH#Op#pK"`.   #$    #  #   # Op(Op##pL:0! xm t KhK@3KHI"`pGd@ p d@! #B\T3DBpGhFFF (:( F B08²*3 `O0hsxbوBXxF   ~~(C#H} }B"CCFpGj0F9F@x*F#2#j qiB x.٣i`xD9F*F#> y ٿFpGFF-R#x+Gfy.D08F)FOrzU#A".2#/23 R#""i0010a#202#"2 "h2#"2 "i2r!2b`x9F3Feq!`x F0 jBpF FFx*Fpx0#(O5$b$ Fp#qO3bF.2/"C"KB(єh i0Bbf0Cg0B"K"BBД @` C @ @   UFAT-A#`FFF_KS'@,[4`#xk`x3-R R##p`pEBC F!($s"xIyy AayCXyA!M2*&]P- F)Fh  6.%( < ;0C#єGF0S єVW0@`T0CU0@ @`as+pؔ=p)K BєA0B C""єDpC0S# єR0SpCcPp;CQpC#?>pW(gDB+@qBOvB&&&3/D.caa!bb*є^ _Bb\ C]B"bb*?t.OCaDab FRS/`O3#a`.OcqXєa `0C#+P Fi(Jє.2/"`qC".KB?є2 30Bb00C10B"&KB.є"2Bb2C2B"KBє2"Cc"C"C##a2"Cc"C"C#`J&p3  (F   | URRaArrAax Ci9;B=xj0 pG)F F^CiB[x+)+:+TQjV!O0w!j&D FW!0`(F&5Fci5B. % F)F0A(!еB F)FopBOW#i`Z;#acyCcq F9F*F=((F( -Cƈ6FF i+Yh3ahBPx:Kn(F B hCiB4/hzF(=(C hF( h9F00Or)FHF5,#hxB "q h(#hj2b7jb` h)F a#hOH0CDca  -A x/+FF\+6%`3x+I F)Fhea\+@Чi !8F "%:(F!*F0< +6 +##-t8x( 8p)Ҳ) C*Cr F!2`iz(JRoaizL#hx;`3x/+F/+\+.+RЍB;Հ;+N\BoB, h!i(bixzYi\3 B + F! ($   A.Ba. ;B۲{U5h)ђ FҲ !a^^!Fp h!iUHcix+; F!( 8FF h!i<F``i)F "i "`ii[#h"q(F8)pF F$CiB!ciB F)F F p(A F)F"h#iZ3#acyCcq5F p p@F1 HP#F p!pB#P,iF"@ | -CFF(#`'(@ , BH(gG 0ozjsd>8g#&ssr #t&wfwww`t!F#vv&ufu0F@F1F,O(?>`IFrH7(5z/Ա-;O,0aHG ,bqq!F(aCc"Cb`C#`0a0F    -OF#0FFF(@y(@𫀣y@񦀣hB(%h*w h : S* !iYaiF(hhB}`{)JU#iaa!ay[ !FiQ$;Xx#(Dѣy#@qaiXF 8_U( "Л0 B9F2FCFiAE $A!Ory#@qOH&&iBТhhB қ$2F#T qahvB8.F D$09F2FyC@qh3D`03D7D0dyC q  Ch+7F]D<-s *Fh 0]ohe`3`0pF(Mѣy *F[ !FiQ$;Xx#(8ѣy#@q hi9F%jzC rh+w kw㉫w{w#iv" +u vku#uu +vhvtty# q#h"q hp@ ppF FV `F#Fy*O0 kF(B]KxF ( Kh KOzq9Y` I #!`!`KK`##p \ j8- F F!( F! ( F!(ѠFFF$B1](F4-AFFFF$CE(F9F0U4p%#!F& 0 P P`OSA=*H(H !"`&H !"["#H !VO@!OsPP PP0@,ѭP@ ,ѭ@P,#`0Os! H0aHH/H!Vp@<@ Kh2`pG  J IhhhBJhhOzrPC  KhpG 8FF@B8F,Ozp< N@ #xCssiL sa8F%% e@@KJ`KJ`pG ᆳ  JKh ))JJPhQhPhJJ? IIhA1BCI`JIh\h@`pG8@\ $zK OKOp MKhB`"`h!q!1`HIY`h!!``hB2`@Kh22oh3#+ 4Kh"`4J hBb`0.KlBRd.JhAA`h`hBB`hBR`(JZ`hBr`h J$K@a`h#`hC`Kh +hLOc`GJJ`KhOzsK:Z`"# "`"`O`|8@0$0Pp@T@<@@B \ 5FiG#1 #15hG 5iG 5jG 5[jG ! FF@")!@" F###15x[hG iFF'+ + FiF FiFK FiFp Fb pF)<ѐ1+A4! 1Bx)4!$ D$1 F$)8QV^B <1B2FH .؆h&@`h L`Fh&E`yFh@5CE`h,@`yh"C`3+ѽOs`#qCqqqpGpGApGj!@$bj@CbKBO!_OKBO!TO KB O!IO!@BT@X@\@𵅰F2OhF F*h0K&?66 L!!  B! S  +# "KsBB#SCCC  COrWCOzr7CC'#iC###c# C C#*C#@BA3`#KrCOC€pGC# pGCs#s pGCs#s pGApGCc#c pG C#pGK"pppG8@Kh **KKYhZhYhKK?JRhB2KCR I`h J\h@C`h$]#`hB1R\@`8@$z Kk C"cpG8@KZk C"XcpG8@Kl C"dpG8@KZl C"XdpG8@Kj C"bpG8@KZj C"XbpG8@C +Jh+ oSo#pG8@#1 3o(1 0 ]KZoBrZgpG8@Kk C"cpG,@K[kB  pG,@KcpG,@KB OP!OP!@KBO@!O@ KB O@!uO@!@n0@8@<@ L#C#C̈#C #CL#C#C̉AR#CC#c #CÀCÁ#pGC@#@ pGpGpGCS#S pGB  pG0$$$ %*C$ BD!C00$$$ %@u-- $DE"B3D0JBbBаO Т2BbBbBJ#pCJBbB#@s CKhb KBcBыz#pG@@O3C`#CrpGC# pG# p N"s *C#5C +CMBeB͉N#+C#+C 5C$@t,ChBcp@# 0 $D$$ # D$ CMCMBeB͉#COu@E "@bBMBhc0@#s 0 $s$$ #s,C C#MC#MBeB͉#cC#O5@E# "@RBMBhc0@#S 0 "B #SB" C3MC3 MBeB $DDhd0@#C`ÁCpG# CpG#c C!pG# CpG#c C!pG#"CÀpG@kpGkpGkpGlpG#  CpG#@c C!pG#  CpG#@c C!pG FI+F> F鈽@. F鈽@.'w?? &% eFE!CAq@aF'W?? &@v%U6mm 6 F&E1F3AQ@aFD0)oCCoSCD0pG C#pGB  pGCpGʈ0#@S FC Ci C"R" C#@sCFhFKBcB**hORYCd# (21 21C"0@CS#S pGpGB$B 0+ 00h C#`B  pG #@CpG7#LF +h<i*BaJi:  0A 0xl3P"FBQ"(`2 02@#BO hA#3D"#p)O@p #p#CpOsÀ#rrOC A #p#CpOcÀ#KrbExa`FF$B g0aeWa41FxbBaDb ebFB 24RTFB R`c 1 @8z+FOP /$ѓkazeC Cch##PCoooQCoC`+x+>#cDhE `6hC@`#zbz+O4O< Ch ch!zh)C3`#z+k#ceoU-#EUcEc hEc x+hh"bDE ` 8hhB` pGhh"` pG#C ahaKi; U ]A #ChaKi; 9 ]A hh)o]sosCS CC`2 ) hXipGhZii@pGiha`pGhO1#aQahQ`QaIaxB#CpiC`SC Ca pG8 1"`iFhe"`z+F!h!OaYbb"baEOrQboEQ Q! FQ Fn"i#O1aSaa!iabxB3ih( O@` a `#bxB3Rmh( O@` a ` iCs ax+K@"bYC c Fs 8ihA+++ pG pG pGiih hA( ` o  `ZhBrZ` 0Mx x-Oic%dmcB#h ԍhe xɈeCaSCSCC#`ii Ca 00Mx x$-"Fic%ImcBio4 `i!a 0Nx F xSi.Eэi)h,icfD h?fDx.f^t,axx,iila x,3i'fk5Cecx,ihTAQAQxAAi!`x)6hx@0Nmh1h4i+e#cD +D;cD ]Ce4ax+x\micax+SyAQAQxAACm` Ji=фi#h&iGBblPphBfbNa">FbP`"ax* xi ijaCC#`x+?Ki+<i xXk!CZc3 x\m#h%iFhJ"ebp` :FeMa 5F"bP"ax* xTm iba xCCBm` Kx+ x тih+CCC`CmhB` pGKx+ xx iBmh)oUSCS` pGiii@ pGiKmhPi@pGiii@pG##0"Ob0#0Cmax*`XahO"`pGzcih 1 !oAo`pGFA#"1!#FaxB(!@ ! 2#F"axB(!@ H#N#IKPT\2 F Fk F!# Fd F@ѼpͲO(q\q pMp`p̀+# q p˲O(q\q pKp 8(%eC\uAKa#`aKpDUx+ ay 8(&fCv'OpU` a"aKaa ˲O(q\q $p pKp ˲O(q\q$ p pKp Fi#a h C`pG-OF  FF(@(T F&FFFO H=_ FYFF sm%`xBix33Kh FhYFGx+ Ѻє1+ Fsm%`jsm%`+՘Kh FhGsm%`  OX6 (  }@ FiFF&FO #i_i]k% ih C@Zk% "Zc0`yKh F[hYFGxB ѹє1+ F4i0%`*0%`0%`0 %`i0@%`0%`+A01,! 1B8F 0iO cE)0Q,1B# !$]B(F O FZF0$1+D$101D0Q0i0%`  OZ (6Y h"Za/ڣzC 1 !oAo`#iZh"Z`+Kh FiGhOBZa 9%Kh F[iG#ihhOaYaz 1hB`hB`KiBa ;Kh FhGh"Za 7hi"aj(%ECC+\u+ Fa"i 3Da` V2F Fhh3D`iDahiBa 6#iZh%"Z` F)F#F*FaxBXi!`m2`#i&O2fafE"aZa/"aho ` F hORZa Kh FiG0F F`h FhK(#pOs ##p@##c%hOR` Za Kh FiGhOZa  Kh FjGhOZa ` L h(KJhIhG##`  $ - C# HpG Kh*`4N"`3x+.L#hoK"`!hhB#3p KOb`h590BD!`=`)D!`!9` L!1p"D!: - 8(# M KiF(FG5 F!*F@0 8- $ p x`F F' -!͈Jx K!IhF*FG0FIeKI`K*F`v%(KFh)F*FG!%Kx + ++K !+ʈ:*(:"0F I I"9xK`(Fp$ - !F} F!y! FuK[hG $ 85F9 !#C5 F@ !#:! F"#4 K"%q]qKhG5J@0 F!\(F8` $ - pKx*M)h p5 L90&hBD&`1D!`!L)`"D!M p - # J IB IYXP3 H IOB@+@  f 87 pGKpG -A 9L:NFQ! F F F!"" F!%#' F$OXp p P P0F@ p P @0FAF0F!F0F`F0F@F!F/Os0Os0(#00F#0 PPp@P0F)F0F!F F@@8'MF!(F+,, ,,!,6#82@,% ,-#8C ', д"#8C8#8C#8C#8C #8C(#8C0H!8@0@K2O0pG 8F@4H! <, H)Ft@4H!|0<,H`80@KpG K"pG -C FG !C !?UO !: !6"8F !$%&Oh8F@ P ` @CH@ P ` @u@F@g(F!F8F !"8F!""8F!OS8FOIPP`@H8FPP@`:OC8F P!P"`#@+@FXHF!FOs&0Os.0(#00@F# 40$`(`*P,@2`j@F1F@F!F@ @8@8'MF!(F|+,, ,,!,6#82@,% ,-#8C ', д"#8C8#8C#8C#8C #8C(#8C0H!8@38@K2O0pG 8F@H!< <, H)F$@H!,0<,H88@-AF FF~5O@H! ?/HAF@ H!@?/H p4> 8@KpG K"pG KS 0hzqKCOzp`pG KS 0h`pG KS Bh# h$h`C (K JB"xCpx+ K"pK(9pGpG  p JH@#B L#"@SB x(K##x3#p  d -OO\TO[ @& FFC! FBF F\0"# O 0 z0Os$08Kx p!+ O O +*********" F FIF| F FIF{ F FIF| F' FIF{ { F! F!L$l``   48<@Q`(F+  _KxBKhG  KxBKS 0h`pG h K3pG F FF'#0 F#P` 0 0WpFHM"F4"p+(Y{"ـ  @RBؘx K# v+6"(Y{p@ _ -OlKO &6h#FFt`h0O #'iF0pZY!{RF F:FOqJ F)FZF0F _tK|-ApsK|pF!  !O0!kH !"riH !"mfH !"hdH!"caH!"^_H!"Y]H!"T[H !"O! FXHJWH!"ETH!"@SH!";PH!"6"MH!1cy"+KKybxOO+##3DJR#p%z]y*Ix?K?J`"`=Jaby (#"y#"6I/6K"pE3N3I2x$3x33p:Z&#|* !!!|*A&J|JCOzc#KxKKxrpcJ!㉒KC&5-ї| J`  @@@@ `_ eE %@B   h uhhG8F F(F8h[hGhhGhhGhiG"Բ KS$0B"ӲH0B``pF&4K] F!([ F!)F F,K%5 F5F- F)F(C F)F)F F K%5 F.- F)F(+ F)F)F FK%5 F  - F!( F!)F FK%5 Fh ({G6.p` p FFFn(K"0`qsp )   !!!!"[pG8F FF!{h*F h!{8@ۿ#000J]FFFSJKUCh>OIBR:# 0: # F 00p@B\ @Fh h!(`{@H@O@H@H@H@@FkF!" : 0C#:Kzzgzzz*  B"*zgzzz*  B"*z!Fgzz: 0Kx  -A^KpOS$0[H@#0o2 !F" !`" d  %'F&F!" 2 x  0DB FDBF_D >KZi=BZaOO !" %!" 2 I  0B FBF_&KZi=BZaOJ!z!Mzzz!p" zzzzjzzzz'zzz<! " 7 !"2d FO~S+`k`` @ @@oF ^F7$FM !"P0H+K+`Kk` F0KppG KORKppG@  pF>H FOa  Oa 5H 0 [Oa.H !"OaF'H,ѝ0 C"#  B"  B"0KhFx1Fn 0C#0 0C#0 0C#0KhF)FxP Fp@  pFF F,, ,\,дb,$ $$$$$$Oa\H'k ? -W@9*>h!3?h!I@*"AO8@zJ$@Oܲ~_FЕEuh-;П? -g 8*7' 8O2аOA<)!*I* ;-8---@П-z-z0 0g 8*z* ,g 8& # 88@O"--+z -g 8zz0 7 8:C:8<1ڟ 8-- z -g 8 8 8 8I.3I@?ɿI?I@-GZ5G-Jj&HOݽPHGOݸ~_ѷ .QO OCз~_\дOhд|_jc )@C~_Os;ZfзOs6JÉ,ρ4 ۽G˻,z ڨ~XZ;0@ ~_,ZOڷ~_ GBO ,jO ,ZB j& j % yv@ rj2B ka?KV_z`'zoJOV>EDH~Q@?JE@ 6Fj::JZJO *Dzwj*&*IAQ!DwZej1z 7 &z `:   ::DgjJ!ZZZJe:vZ'zeZZ$ZEzzJ:7%zj ` *7EZJ'ZuZejjf** ` ***vjfjjvjfzz7$z7z*"b"*5z7dzzvz$d$ZJ*vGjf'z"zz :Z wjj:F+#B@󪀲O{oв|_~"F#c#:}j}*}:}*}J}J}Z}Z6z&jwz:j Fzz'zg*"J*JvBjZgjZjjFz7Cz'jzwfjvz0g :D:(   F!XJET,1V Ujvj7jjNz(' ' O~:ADQHME"*7ez@A+7j@B:^U2l>>>m>?aO8v?Ý68v?5r1?r1?L13ݵU8a 6*>?Iq<83C`B ;?p6?aap:J#DBjFfݏJB+zJ$@ۀBpgzfПzwj wzj7zzpJBfݴOI:TJz~jz7gzz'&zzz7gzz'&z@zz@@z ##nI )F.ڕzzGzgz@Bzz" B` pp@z zz[j[zwz wjwzj7zz$SjJZKzz&zzj fj(% fzHKA$S!0B0gzz0G .pgzzڱGzgzzz@B0gz*S+ݟ1z1Z@ZZpezzz5z*S,,܅ze Bp'zПzwjO0wzj7zzr#SjzwzO0wjwzj7zz]zZe  uzzzI?@??CD57ICCPbD57."?a.21$:#BOp0j+=۲OQ,ӡCH[%V[,F!OrBD9OCORDm|UUZppG ppG [D0@ "D:#CH_,z:+``z1J1Z1Z1j1j1zZ'Zj'jz''z j z 7@ pG`zJZZjjzJZB'Zj'jz''zJBC:ZuZ` j jv 5 pGZj pGNGt1| 7a *=>H?-O-װLd9T%FH#sCFeD0"+XzjzBzѸO C^DBzQF"F#j2z3BzzB  DFzxDV3D, ݬB2D1D`(z@zzrjzz7 z CB@F@ ) zgɚz9g@a\!0CL!0DC +-*, *@@cF !S+pC !BѸ ݸb\"0?L"0+iе@xgB fD#V-^EC+@S\# *@ OS-*ЦDfvE. De ewDBPzzz QF"F#j2z3BzѸBztF@c\#0lheC; 0I)@F9@@I XB_z Tz zgz2zg z :L$0JL'@ /6}B*z 2DszzcEgz  bzB 逐Iz"F#Bjz3EzV0BC(zd,𳇇*b\"0L"0z "wzzBE[*!2, *?!/@󹀹Vz. DS(z/FSj6zvjvjGzcjBz/@VDQ(zF0Sj6zvjvjGzcjBzPzF3zBwz,}.j/zgzfjGzzjzW/t.{;z3zBwzgz.j/zvz/zBwz gzz/E{.%z3zBwzgzzz$gzO_\'0:+ݮ S?:) z,./z*`k` 'FZL$Pzzte:#CH_z:#`zZjjzZj'Z'jz'ZpeZzZ z  0` pG zz pG./4/ײ86 P<*>* pG pGS+ P1)FzOv1)O=HAC'@C O,)#L@ 'OG2KI?JD6zZ'ZD- ugz0g $80 85jp&jzj8c/KBܣB*zz'z#p'z'z@ 86zg g 8!KB zz'z#pgzz|zz#vzpGz0z#zl?ɿ>i8㽫>LeeIq?@*"C:pG:#@O  pG:#@ 9))F"HABzp'zz +!$ ]KpGO:]KpG0 ]KpGzp'zz+۟ OC D( :Iq:3@ pGO pGKBB pGCXBXApG pG:3A-*бOұ D)0)"BBR*0 zJ 'zB:"Q*9LP2B ܟ:H *  :H   ' 1"BBRz*'' L<`B Iq3!BCC:pG! "-NFFFF +&BҥmPFIFG(n%F,FHFKhpG #B\T3DFBpGF"F4x+\T3,ɲF;BFpG)FpGF+*8pGKh#B\tdx,\ 5x/ 4-3,%(F F2KK:,DB!0x F x+ 0"F4x8#\(\B30F0K-AhmFFP e`m`D`a`ma\ambbm\ccmccm\ddmddmwm\b0F9Fm#A h(FF[k>B F`pM`(FkB.F#FF[!!p+F`F?H-OF4F[@x&F--Ѵux'+-ux3 0- xX(Qex#4+ #/ oJOJ&0F 0=  лOW O7 Bڶ? HEUEP&O6[s /O" o@O@0*@BBa`+# 0FJF Fh)F"F0@r KFh p%5 -8 %-F?ۍB=!KhF!F h[ + `PBbh`Khc` F FIhL#h0F' `)F0F"CF$B %` #  ZBPp0Fa 0 #3` pL H 8L#FF#`C#h+`847 JhcDiFB`FpGK "`O0pGK`P 47 87 AC0OAOCT U d\e\OTTUUmB ,D6-0OO1OL1@BaAOO3L3RBcC𧀤  "ACYA  *(L CQqEO ~nӱ I_0O< ODR𚀼O_P PAQA0_L @AAёF  3   2  ! ؿ  ܿA @Q)C0o< 4  @!E0   @)F0!)F04=Nd\e\)ДT FF0! 0_T\@IA(AA0<0EEAApO0d\FFe\ FFP4R5A!0!pG0Od2OOP!pG0Od2EH@BO>BOO1OpCO`QpGOpG0O`tE!A PpG0O PpG0E@BaAOd2_\?ܮO_ 2_ 2  @!DpO LlTU  ,D!LQ#LSP5R5AC8РOBOVNd@t_NmAFB!AUQO @^PO. oؾO_PPAQpFF@\¿ AQpAO<6޿ Ap <5 4  @B!Ap!B^C pp   @ApA^C pp N C!A! p^C ppF@AъBWٲK  @C) 3 |C GB <B@:/DqFABI7퀈B@;CB#B #FFF@BaApG@BӂBހ#"#*9( #2 wBG B8ҺBAFpF@ E@7҄E:FBBCRBcCd@BaAO4[@ '( &@C; CA BI2yҋBw٨)DGG B1`ҺB^;/DCC %C OI! C 3: BC E 25ҚE39cD 3GG E 3ҹE:gDBEBO *F bF;FBj#F FFFCFBF :S9/D<-FFF+CъBSٲW  C! + gCFB 67B@:&DaEAB 6 B@߀;CB#FFpGBJس.MыBӂBր#"FFpG$*| & #* BAB <ҊBÀ`FEEB,1𖀧B@:BBFFpG#FFFpG "@COH! /C3= EC E 2mӔF3GG E?3aFE L #BOIEbF+Fc@ !& C ̧C LcE  2:cE8٨D A AB 0"ҊB ;!DCCI:F 3F Fk@B 2#3F2FE٬ #DE='DFF8!D:# (( ( (( F #F@@@@@@@@@@@@@@AAAA&A*A3A7A=ACAFAMAPAUAaAdAjAqA{AAAAAAAAAA}>>Y >>>>>> ?6a ?(?y??? ??e ??6?] @ @u@6=6F@N@V@^@e@m@0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZVRBrain CLI version 2.31 Jul 20 2015 / 13:01:43Available commands: %s %s System Uptime: %d seconds, Voltage: %d * 0.1V (%dS battery) Hardware: %s @ %dMHz, detected sensors: %s ACCHW: %s.%cCycle Time: %d, I2C Errors: %d, config size: %d Saving... Rebooting...Must be any order of AETR1234 Current assignment: Error: Enable and plug in GPS first Enabling GPS passthrough... Enabled features: Available features: Invalid feature name... Disabled Enabled Leaving CLI mode... Resetting to defaults... Current mixer: %s Available mixers: Invalid mixer type... Mixer set to %s Current profile: %d Usage: motor index [value] - show [or set] motor value No such motor, use a number [0, %d] Motor %d is set at %d Invalid motor value, 1000..2000 Setting motor %d to %d aux %u %u Invalid Feature index: must be < %u %d %dCurrent settings: %s set to ERR: Value assignment out of range ERR: Unknown variable name Current Config: Copy everything below here... mixer %s cmix %dcmix %d 0 0 0 0 feature -%s feature %s map %s set %s = NG OK Custom mixer: Motor Thr Roll Pitch Yaw #%d: %s Sanity check: resetloadLoaded %s mix... Wrong number of arguments, needs idx thr roll pitch yaw Motor number must be between 1 and %d Entering CLI Mode, type 'exit' to return, or 'help' # ERR: Unknown command, try 'help' looptimeemf_avoidancemidrcminthrottlemaxthrottlemincommandmincheckmaxcheckdeadband3d_lowdeadband3d_highneutral3ddeadband3d_throttlemotor_pwm_rateservo_pwm_ratepwm_filterretarded_armdisarm_kill_switchfw_althold_dirreboot_charactersoftserial_baudratesoftserial_1_invertedsoftserial_2_invertedgps_typegps_baudrategps_ubx_sbasgps_autobaudserialrx_typespektrum_sat_bindspektrum_sat_on_flexporttelemetry_providertelemetry_porttelemetry_switchvbatscalecurrentscalecurrentoffsetmultiwiicurrentoutputvbatmaxcellvoltagevbatmincellvoltagevbatwarningcellvoltagepower_adc_channelalign_gyroalign_accalign_magalign_board_rollalign_board_pitchalign_board_yawyaw_control_directionacc_hardwaremag_hardwaremax_angle_inclinationmoron_thresholdgyro_lpfgyro_cmpf_factorgyro_cmpfm_factorpid_controlleryawdeadbandalt_hold_throttle_neutralalt_hold_fast_changethrottle_correction_valuethrottle_correction_anglerc_raterc_expothr_midthr_exporoll_pitch_rateyaw_ratetpa_ratetpa_breakpointfailsafe_delayfailsafe_off_delayfailsafe_throttlefailsafe_detect_thresholdauto_disarm_boardrssi_aux_channelrssi_adc_channelrssi_adc_maxrssi_adc_offsetyaw_directiontri_unarmed_servofw_roll_throwfw_pitch_throwfw_vector_trustgimbal_flagsacc_lpf_factoraccxy_deadbandaccz_deadbandacc_unarmedcalsmall_angleacc_trim_pitchacc_trim_rollbaro_tab_sizebaro_noise_lpfbaro_cf_velbaro_cf_altaccz_lpf_cutoffmag_declinationgps_pos_pgps_pos_igps_pos_dgps_posr_pgps_posr_igps_posr_dgps_nav_pgps_nav_igps_nav_dgps_wp_radiusnav_controls_headingnav_speed_minnav_speed_maxnav_slew_ratep_pitchi_pitchp_rolli_rollp_yawi_yawp_alti_altd_altp_leveli_leveld_levelp_veli_veld_velfw_gps_maxcorrfw_gps_rudderfw_gps_maxclimbfw_gps_maxdivefw_climb_throttlefw_cruise_throttlefw_idle_throttlefw_scaler_throttlefw_roll_compfw_rth_altblackbox_rate_numblackbox_rate_denomauxfeature_name auxflag or blank for listcmixdesign custom mixerdefaultsreset to defaults and rebootdumpprint configurable settings in a pastable formexitfeaturelist or -val or valgpspassthroughpassthrough gps to serialhelpmapmapping of rc channel ordermixer name or listmotorget/set motor output valueprofileindex (0 to 2)savesave and rebootname=value or blank or * for listshow system statusversionNaze 32Naze32 rev.5Naze32 SPADXL345MPU6050MMA845xBMA280MPU6500NoneGYROACCBAROSONARGPSGPS+MAGPPMVBATINFLIGHT_ACC_CALSERIALRXMOTOR_STOPSERVO_TILTSOFTSERIALLED_RINGFAILSAFETELEMETRYPOWERMETERVARIO3DFW_FAILSAFE_RTHSYNCPWMFASTPWMI2CBLACKBOXTRIQUADPQUADXBIGIMBALY6HEX6FLYING_WINGY4HEX6XOCTOX8OCTOFLATPOCTOFLATXAIRPLANEHELI_120_CCPMHELI_90_DEGVTAIL4HEX6HPPM_TO_SERVODUALCOPTERSINGLECOPTERCUSTOMr@w@{@@@@@6'@/@<@6X! (#6Z!  7" 7" 7" +7" 67" ?7" H7" W7" g7" q7" 7" 2}7" 27" 7" 7" 7" 7 & 0~7" 7" K8" 8" .8" 78" D8" Q8" ^8" l8" ~8" 8" 8" 8" 8" 8" '8" r8" 9" 29" 2+9" 2B9" T9" _9" i9" s9" Lh9" Lh9" Lh9" 9" 9" 9" d9" 9" :" d:" d(:H < 7: dC: ]: r: : :g :h d:i d:j d:k d:l d:m d:n :! ;! ;! ,;! dF;" <X;" i;" z;" ;" ;! ;! ;,! ;0! ;4! ;! ;v <x d<w d< ,< 8<t ,G<r ,U<y 0c<| r< ~< < <p PF<M <W <a <N <X <b <O <Y <c = ! =$! *=&! 8=(! F=#! dT=J \=T 9^ d=I k=S }9] r=K x=U 9_ ~=L =V =` =P =Z =d =R =\ =f =6! -=8! -=:! -=! >@! >B! ,>D! ?>H! L>b W>&  i>&  ddddddAERT1234AETR1234`M bbbbbbbGbIbObgb0?[]|"*+,.:;<=>?[]|!ޟHI@@@@__p_?_ !"#$%&'@M !"#$%&'HIJKLMHIJKLMDEFGABC@@@@@@ @@@ @@@@@@@ @@.@@ .@@@@@@ @@ @@@@ @@@@@@ @  @@ @um{eigSTM32 Virtual ComPort in HS modeSTM32 Virtual ComPort in FS ModeSTMicroelectronics00000000050B00000000050CVCP ConfigVCP InterfaceKacosfpowfsqrtf?5???I@˖@@S@A/AIA1bAS{A:A˖A\AA~AAA1AASAB: BBBB\#B)B/B66B~5.Dp&_~A99S9_(;/Z mm6~' OF?f_-u'{=9Rk_]V0F{k 6a^e_h@Ms'1Vs`{k ?973.P+'"D0C0i71h!"33h!38c>I?^{?? ((((( AAAAAABBBBBB f b1??m=  z d Ca3ݯ% C2 $$$$  @@ ]I=???KOUAM}Y @@We}a UFD"Ofwupd-1.2.14/plugins/dfu/tests/example.bin000066400000000000000000000000141402665037500204550ustar00rootroot00000000000000hello world fwupd-1.2.14/plugins/dfu/tests/example.dfu000066400000000000000000000000341402665037500204650ustar00rootroot00000000000000hello world UFDfd-fwupd-1.2.14/plugins/dfu/tests/example.xdfu000066400000000000000000000000341402665037500206550ustar00rootroot00000000000000iy_fs5UFDM3fwupd-1.2.14/plugins/dfu/tests/firmware.bin000066400000000000000000000002101402665037500206340ustar00rootroot00000000000000=   ? B = fwupd-1.2.14/plugins/dfu/tests/firmware.hex000066400000000000000000000006001402665037500206530ustar00rootroot00000000000000:044000003DEF20F080 :10400800FACF01F0FBCF02F0E9CF03F0EACF04F0DA :10401800E1CF05F0E2CF06F0D9CF07F0DACF08F00C :10402800F3CF09F0F4CF0AF0F6CF0BF0F7CF0CF08E :10403800F8CF0DF0F5CF0EF00EC0F5FF0DC0F8FF6C :104048000CC0F7FF0BC0F6FF0AC0F4FF09C0F3FF6E :1040580008C0DAFF07C0D9FF06C0E2FF05C0E1FFCC :1040680004C0EAFF03C0E9FF02C0FBFF01C0FAFF7A :1040780011003FEF20F0000142EF20F03DEF20F06B :00000001FF fwupd-1.2.14/plugins/dfu/tests/firmware.shex000066400000000000000000000013371402665037500210460ustar00rootroot00000000000000DfuSeTargetihex@=   ? B = TargetsignaturedeadbeefUFD'fwupd-1.2.14/plugins/dfu/tests/firmware.sig000066400000000000000000000000101402665037500206440ustar00rootroot00000000000000deadbeeffwupd-1.2.14/plugins/dfu/tests/firmware.srec000066400000000000000000000006331402665037500210310ustar00rootroot00000000000000S0220000687474703A2F2F737265636F72642E736F75726365666F7267652E6E65742F1D S12300003DEF20F000000000FACF01F0FBCF02F0E9CF03F0EACF04F0E1CF05F0E2CF06F086 S1230020D9CF07F0DACF08F0F3CF09F0F4CF0AF0F6CF0BF0F7CF0CF0F8CF0DF0F5CF0EF0FC S12300400EC0F5FF0DC0F8FF0CC0F7FF0BC0F6FF0AC0F4FF09C0F3FF08C0DAFF07C0D9FFDC S123006006C0E2FF05C0E1FF04C0EAFF03C0E9FF02C0FBFF01C0FAFF11003FEF20F0000112 S10B008042EF20F03DEF20F0F7 S5030005F7 fwupd-1.2.14/plugins/dfu/tests/kiibohd.dfu.bin000066400000000000000000001073041402665037500212220ustar00rootroot00000000000000 AM9999999999999999999999999999999999999-L9999999999!KL RM("#,KJ`JZ`KxxBp#JIBIYXP3KJB"C+K"+JBK"pKO"`KK2`"C,b @8@? @ .@@@Cirv(bUKh2`pG H L#hX#hY#h KhPl4@HnbHK!hHJK`ub,u  H K!hHK!hH@𾾌b(b4u H K!hiHK!haH@𞾹b(b8uHK!hIH@b(uDFBpG#B\T3pGr K Ih Kh hbIKN1B3;B0!2Ozp pG8FF@z<zu8CB$pG8 FFH,!(Fa"KBЊBXB H(F!H8@B#cchT8d/du8#M(F눛B,FH(!yHh!qH(y!jH 8+yC#+qKq(qh#\#3#"㈒BK"#y;۲#q8idziu-Ar9M`+x8L#yO[G4H#+p##c#qT0O;y /H#+p#0+H;*H~8y!'Hw:y*""Kp/#;K"pp#;H(㈛BK"pxK"pRKp+K ذ"*px I#;р"xI K"pb`@ddd0eu`@`@-C3L㈛BF FFX FkBR"cB""#FB)I)ah;ET!ah;!`ph7TgN7B pw*J#pKx Fд"px԰# p K"pKp  `@`@`@-AFKFFF!"0"#0 `,FBڍ@!"n @ɲ"a 340 cHt !$"#@u7#0#F F0 !"6 (F!F", 0HDH!"@ӿu7f#$0@ 0hF!" !" #H#I#KhB@`!JOs`S` K"p"Zq"Zp "qKOb` !" #>KhB2`KOr`Kh B2`!#F*H!"mH!"h ! "@pcg4@@`@T@@@D@g7f H # !" ! "mH!"@4ug0 " FF! Y# 0# 0@!"` 2#$0 @BЍ @!"L #0! F? H4Z ! "0^eiFx"IJH@ !Fu0$H1H.iFmx|+ H!!F"%,FeD  H -%FH)F"0uxeezi0$HHiF)x|+ H!F"%,FeD  H-%FH)F"0uxeezix+@#$0@|xs@n#0hF!"b@ 4030xsP!"G(  pGx+6 "-JCDBxxJCDBxx Dp"CIBx DKxAx3aBpKxAx3!DBp#JAxD3+pH#p$#Cp!"@UTTx  Hx <xF])s FFF(Hp@ڹ (-JhJxɲ(#pJx"x* b*ِ:b&& Jy0!*Fd. Fp@IpesLiF##xm iFxT+ t+л Kx2" Kx*""HpmHjKx]ujh7² "B(L")I"-$J$JR#$CB#8T#%CD#Os`Kx+)hC $JR#0"B  T#%!D#Os` Kx(B hChC`` 0@@@@p-I-H-H !q$*K D!Xx4B  ,$H#H !X$ K D!Xx4B f,HH!H ?H! FM&HVC#2qCqH*UO5cpppq J Jpjjhuhh( HHHHHqhhh iiK-OB8FK J2Kh%_4OE 0Px!C O"KzCay!q!aq|IHIx@ !!O~HqE1O\ R E  OQRcy+EѢy#y Ҳ+  aB# *# aB*cq(#cq#VH8FayzRKxay"yB+)8FY+ 8F!R`yDGH 7}CJ0RD!Px5C T -^=K*^:;H:H-K!%5H6H)K!0H1H!0F+H-H$&H{*Hx!'Hq'HnI%4(yhy!H`!(HYh!HRH,HOJ%iziuYi|iiijvjjH kHHKx8"puk pFOrBF F1K x+;/H(F!t-H !m*H);( 'H>&K'Hh2`&Kp%K&Hh2`%K"p(F!KH pKKh2`#+#JJp KxH!(F- H !&H 8 plziu5lYlaljl0M͋+hH#`k Hh$ HcH]U$4j , 0kulp89H9M:LF9HC9H@8K9Jx9K( FF66H36K!x4H+4K!x2H#2K!x0H0K!h.H.K!h,H (y!*Hx!'H'K!xH$K!hH!K!hH(x!jH x!8@au6llllmmm4m@mBmJmRm7iFHMLJ#pxS+ D+ d+s+#p H#+p# H#+p##p0uhmm-AFFF(M#Sx;D+ %H` F#HZ &B5Kxk!HG FvHA"SYxHXpD6px1p)!Yp\RxB2T#cCV*"T mmnu* FJHJKx#Oq*IZB" 1BCK#ZCBK""pr?K@MxAɲ)pBUi:N"p3xS:H0x!`8HY7Kx6H!(xR4H!hxK0Hx!D*H&H:XB OrB&Kp$Kxx")pp# 0  ! =KC#K!p F ! F "K!pp 8 pn5EB9nunzi)H*HQnn FYC;F98M+ hp`p^p\(qZhq8WkyY/KDHp;+O+K[x++JxBѪx)KxS20)y(JGkx+#JxB86O&+p!LNxxp*p cx"bp"x*#pK!2F0 H!2F2F Hiy"#pp"pcp88 s(k!LxcxF*+#pcp&xx+.%#"1F0 p%pPp/1F*F*#0B! F 5 !""#pp"pcps Lxx".p Kpx!0ppp*ksLex&x#.cp" 0#1F*F%p0&pepp HF5,H0Kx   o+osLxx".p#0#1Fp 0ppp8FF H HK FpKx` 8aoo8FFH-HKx{ H F!R H KxBp F 8o+oou-CLxFFx%/p# hF*F!0pPp#HFɲ*F+ppiFHxS(    B Kh " 0]u-k-G0,K x(p,F $ B@`pF;Lex`phF!*F%p@F9F*Fep0BLx%.&phF!2Fp@F9F2FpsLbxxF*+#cpp"xx*+%#%pp0B! F Z5 !"S"#pp"pcppiF HH( ]uo! L H"!Or H !" FK!" !pp!6 >HrHoK"pu$pNNNIF$NH4pMKp LKpLKMLhMMMOBb`hBb`@"/Or/FK2`OQrZ`DKEJ pxQpCJCIxpBI pxAKBHpxZrAJ#pR!p>KpSp=KhB`ZhBZ`9J##pp7J`7Jp"*pE<5M/`W/b넪`b`b0K+a3+c/KYYYY,Io ` b+KhA`##p(I#p # pQ p%I,# pQ p#KO1`O!`2p@*q4k4@@@@@@ @@@@@@<@@@@@>@@ @ @@-CF(WKWKOTHr#$0RK"2FE𒀲ɱ"bCDJNM DP\3xs8F!AHG!Eѽ=M$0F+ I3x8H0_B_A3x4H&/ ''$pD3x/H/#$09/O "$ OqWpQ"$ kx+ѩ"F 3x۱Hhx!x3xH KJx8FS"01"FG K"$ 3x+?gHb6@@tp>zi6Up\pbphpnpupcKxFBbKx*aJxxD_K`JhhB @qB `#xCXKx+WKx VKx+TLx#0+ACQN3x+PKxPKpx%MHm!(FKHf!0xGH_x!EHX>Kx-Тx:K*xHDpx=I:pp *"p=8N3x;  p@3Kx,Kpx%ñ/H%!(F'H!0x#H! H!Kx-ДK)ГBD `H1H"p5H pD @@p@mu@@@p@Kx]K"DKBED pGK"pGHH !$ H! H K S40F"4G ,uRtwtpsiFDK( ]|-C%FFF'iFx+YuK+ 0!+K_FFyDCB:"H-"H*HFY H$! xH!`xHx!H#JHXB HHH3h+R90 xaxG7F5BҰFsu{tpt=Gtt L H#x8##pH x@<u%us$%FiFx ,L+0ŲƲ H H(F H0F!F#K^U4,puJuluO  L H#x8##pH| x@.usu L H#x8##peHb x@0uu-C+HАЀ*O*N+M+LI+HFHFC! @F<%HK%H68F30x!(F,!  F%!V HF F@FH&8Fpx!(F !  F !CuvO v0luuu4vwtp8DHDMDH(x-BH$+xB ?K"3!xU4EњBZ.PoKOE#kp2Ҳ1ɲ34$'FcKB@ЀbM=(Y((M]H5xy#Bh<E:3۲K, P>0x!;O1ɲC\DFAE HE BHO 1ɲ3#ZC9K!T5 {% ߲I3H5Py#B>E63۲K. P50x!3%1ɲC\DFaEM@E H]]O p1ɲ3#ZCK!T4L/HB10 |y: J~%/F0KBB/K/J0̀O  @R#0] 4O E!K ]S1PxxG ]  y3D_ @ ] 050xpJx#3pb J Ip pK3z#ײ~}s.>BJHIJ#pJpJJpJI"XZT! D+ApF H"TJpp3@B{.0|/F JV(-OFg5L @ 2H2H(F.HT50x$f4#FBp !$H8]! 4 H|䲛!BHq>]HkHgH`HY0++нHHHOGuyzn z zz/zzzGz V(-OFLMMKO KDJHIH FcEHU$0x&չCH>I S@H!Xx=Hx!Ow4F&!l3H !;2b.H0 [30F"G)H #:x yB!PxC:x 3yBH۲H;x 6y3DBHB0]f-H}JuOz zzz@mzvzxzn zsiFxR+T+0;04)]KxKpKxKppGCBE>H8H5Kx@auLMH$H!Kx@Mur=7 K"pF+xL+ iFIx#x3#p0CLiF8##px pAiF'Kp]E)0(HKx# H0@ɼ+ K L%xE@%p(00M ?)(HpG)0(HKx# H0@+ K L%xE %p(p0xp0 M%X ?)(HoYl"I#ST2*JJpJppGY  >X )(H@JL#x+)##pM)8 F(H8@1M+x+,,p8M)p(dHdL#x%F. +^K$pQ(p1(CXBXA +xx-!p+ QK"xCppMJx+LHth,YpJJTpJH F!d+ф@KC䲥@xC"p9KxBpp#۲-+<.,8K9J]]"F/I xD 3Ҳh* 2I2K\\(I xD p1c۲+,J\!KxAp##PҲ-*&I'K\\I xDDKxp@Bp Hr F)FHp@i% IL(\CT x+D\ T x3 ppM?>B͌Y cr3/6uHI@`KxFJKx+J!T3۲Kx Jp"x(KpF@\MBY ?>h!C\ 1JK`pG K LxBp` Hpx. ''GApD5lC+r JR#AhB#J2@a"b  8( rKS 0 0[hbpG pGr#JD,ՙJR1@,C0X`K AR1@d0X` KB10 Kx:pb3 +bK"p@z@@bC+02J@@DrL\* 0"0"HJP#@@#R#A`B#""T C` ##CC`b0l8KJ\+JBJK`pG@@ -GKx`խKxJx);۲p Jx);۲p K"p+KLxҲ O@ZZhT70; +LJJJJJJ(JJJ hMRh+`j`JD7 OJ!&o9`!aap+س:؂+)؀+@𑁂L!papHh@2B س@/rB@zxKa@2B$p@!BCسoأc+ajkK"o@Y]Kx`NpxfKҲpxB!Bv آRB:rB@>x\K8BB:2B@2xWK,3jpj8~6FE&FdV (pPFIJKLPV (aPFEKIPDK#CK :*"" 9K"p&F8J8K2D3D,pO%8F0X`0KD800D830H80P`JD3 D3030H6H! .D3D8ѭK Lx1F#p @kn @X@HlcDM|L8 lbz0@+h؝LK!pap\i&pgૈ*Yi)VњKDx"MH/ૈ*Hi)EњKDxB<3 lB 3SL ,23 lB * !x2+y+R~H!1F|H3 _ 3SL,3 B3jqK pqKppiK"p(!mL F!B8F@.45F@% F)F v,D@-5F@.45F@% F)Fv,D@-[K<`"ZKXZM+B!B#\SIXT3+ x+SK"p @!Bk(y ( RxLKxJKpF >H!&F( x2"HpAM+xՅKNxKH0x!nH2x##r#br !F+x# [wKzNxKzH0!QtH2#br #r#r !Ff+x#@3x.EнdKjOxciH!8x,gH0F!eN$cH0x!px!x!x!0y! py! WH};xTI#r&"fr ## !F.pBKxFKHc+x+l3xCO+68x!AHU&!6.:HG&!y6.4H:3K!}0H2&!X}6.+H%;x(Icr FO&" k"$I F2 +""!I F2 +BF"I FBD +"#3#0F!F#+pkTMm?AX  >ȑziY u_ m n r Jh+b pGO@@!`bIH"SC"PB`pG  KH"+ Jx" Jx rJOA!hC`bpG bkL#hk hBSDzB K"` KxO0 `(C FxkK؈Kh[DpG x-C-N#F F3p,O+M+hF+G X)Kx 2p (#3pmKpKx*"pO0)hJH#p@B8"F DDHB\ 3DK?+@#  #+`K"p4p Fopktn8Kx L!h1K %`8F  8@}K"p8opnHû͒HHH@uttH֒-A$!KxB< HKS$H';NV$0YY!HJKmV$0XY _H8v_V$0D7hhl Hi4䲾ْvziuKxp\!H> H; H8H5H2H/H,H)H&H#H HHHHK!hK!hK!hK!h@u4Ux.bޔ%yT@X@\@`@ L H#x3H#H##pu0F0#x +F0x[ ``0-A"Kx+=!M$(FTFKxB&3MU$0YqR0U$0DhG64L FvK K K H KIJKK(lunv Jx +H@>LD#YpJB#/KH$pKpKpKp H K H Ip Kp Kp(tzXZ-O(M+x+I'O$8FiFT.F&F#KxB#O O [$0S 8[$0S `_  _4#H+p H0F;3+xZ*p,T(ltzRB 3@DpG J K8x Id"2 x#xd!JCTpGBH\T3pG(l Kpd$ M4& H.p[ HX Fg3FڲY\HT F*pp(tzltM,xIH+xpNc+oH0nH-#3p>Z2pkJTkKx +xBiHhH#F*xB bJ^\!bH 3FaH&0*&+#+p+xTOB@UI ] *FD "+,!#MH+pSHRH@;PH+p *z;x;;p*)* *n"=HCEKEJxxB4 @p1ɲ )p!p!px;Jpd"SC:J!TBM [x[+IK]A+"2O/K xB#F U ! "8'KFx!8 "B#Kps]B+ M! "0KFx!0 "B,p@"04TkW(ulߖzitzKhB"`KOr`pG@L@KKhB"`pG@@S x3"hCx+уx+]pGFF3x)pGF"CBڡ\\TT2;0F" %303TT"F UF0@ٿ]0F" %303PT F T0@FiFhF]p$ -7&0&c5D UF* * *x"T40" UBX0$T"TFp@ FiFhF]p$ -7&0&5D  UF* ** x"T D0"pB0 T3"TFp@M FiFhFL]F+"KBF ,BxOppF %& F;++ F  + -+x+FO6%F --"F!!BFL0<3 $:aC-9*0:3 BF*7:f*W:pNMI! Hard Fault! SCB_HFSR: Memory Manager Fault! SCB_CFSR: SCB_MMAR: Bus Fault! SCB_CFSR: SCB_BFAR: Usage Fault! SCB_CFSR: RESET TO LOADERWrite to given register page starting at address. i.e. 0x2 0x24 0xF0 0x12Test out the led pages.efAesgefececeVce cef9Read the given register page.Disable software shutdown.DEBUG - DATA: WARNING - I2C_BufferPush failed, buffer full: ERROR - No buffer to pop an entry from... ERROR - I2C NAK detected... ERROR - Arbitration lost... ERROR - Slave Address I2C NAK detected... DEBUG - Attempting to read byte - DEBUG - NEXT INFO - Sending: | LED_control_capability(mode,amount,index)i2cRecvi2cSendledCtrlledRPageledStartledTestledWPageledZeroBasic LED control. Args: []$Zero out LED register pages (non-configuration).Send I2C sequence of bytes and expect a reply of 1 byte on the last sequence. Use |'s to split sequences with a stop.Send I2C sequence of bytes. Use |'s to split sequences with a stop.ISSI LED Module CommandsEnables matrix debug mode, prints out each scan code. If argument T is given, prints out each scan code state transition.INFO - Matrix Debug Mode: INFO - Columns: INFO - Rows: INFO - Max Keys: OPHRIERROR - Matrix scan bug!! Report me! INFO - Max scans: INFO - Previous scans: INFO - Scan Number: : 0x: 0xmatrixDebugmatrixStatePrints out the current scan table N times. O - Off, P - Press, H - Hold, R - Release, I - InvalidMatrix Module Commandsjg!j-jpppppppqq q4r)++qk!6qr,Aqmq9#Lqq$Wqq=-bqk#Toggle UARTConnect debug mode.UARTConnect status.DEBUG - Animation INFO - Connect Debug Mode ToggleDEBUG - PENDING SET -> WARNING - Cable Fault! Slave Master DEBUG - CABLECHECK RECEIVE - INFO - List of UARTConnect commandsMasterSlaveINFO - UARTConnect Status Device Type: Device Id: Max Id: Master <= Status: Faults: / Rx: Tx: Slave <= Status: INFO - Setting device as slave.INFO - Setting device as master.WARNING - Too much data to send on UART, waiting... +ERROR - Invalid ScanCode direction... WARNING - Not enough interconnect layout nodes configured: DEBUG - ERROR - Too big of a command to fit into the buffer...ERROR - Invalid UART to send from... DEBUG - IdRequest ERROR - Invalid IdRequest direction... DEBUG - IdEnumeration ERROR - Invalid IdEnumeration direction... DEBUG - IdReport INFO - Id Reported: INFO - Sending Sync Idles...INFO - Resetting UARTConnect state... Wait SYN SOH ### CMD ERROR - Invalid UARTStatus...TxFIFO 0 - TxFIFO 1 - CableCheckIdRequestIdEnumerationIdReportScanCodeAnimationRemoteCapabilityRemoteOutputRemoteInputconnectCmdconnectDbgconnectIdlconnectLstconnectMstconnectRstconnectStsLists available UARTConnect commands and index idUARTConnect Module CommandsSets the device as master. Use argument of s to set as slave.Resets both Rx and Tx connect buffers and state variables.Sends a command via UART Connect, first arg is which uart, next arg is the command, rest are the arguments.Sends N number of Idle commands, 2 is the default value, and should be sufficient in most cases. +/  ,Y88%988yF=GGGFFG  N  N?   # @ 1 vR "Send key-release event to macro module. Duplicates have undefined behaviour. S10 Scancode 0x0AEG K8 INFO - Capabilities List INFO - KWARNING - flashModeEnabled not set, cancelling firmware reload... INFO - Set flashModeEnabled to 1 in your kll configuration.INFO - Layer Debug Mode: INFO - Setting Layer L to - INFO - Macro Debug Mode: INFO - Macro Processing Mode: INFO - Layer ListD: KType + stdFuncMap (default) Layer State: First -> Last Indices: 1: INFO - Pending Key Events: : INFO - Pending Trigger Macros: INFO - Pending Result Macros: INFO - Trigger Macros Range: T0 -> TINFO - Result Macros Range: R0 -> RINFO - Trigger : Result Macro Pairs T : RDEBUG - Layer 0Macro_layerState(layerIndex,layerState)Macro_layerShift(layerIndex)Macro_layerLatch(layerIndex)Macro_layerLock(layerIndex)Macro_layerRotate(previous)ERROR - Scan Code has no defined Trigger Macro: ERROR - Invalid key state - ERROR - Invalid type - WARNING - ScanCode is out of range/not defined - WARNING - ScanCode is out of range/not defined: ERROR - LED State Type - Not implemented... ERROR - Analog State Type - Not implemented... ERROR - Invalid State Type. This is a bug. DEBUG - Macro Step INFO - Trigger Macro Index: |; Position: Result Macro Index: Trigger Macro State: WaitingINFO - Result Macro Index: (, Final Trigger State (State/Type): capListcapSelectkeyHoldkeyPresskeyReleaselayerDebuglayerListlayerStatemacroDebugmacroListmacroProcmacroShowmacroStep DPause/Resume macro processing.   ! Send key-press events to the macro module. Duplicates have undefined behaviour. S10 Scancode 0x0AMacro Module Commands RCK Layer debug mode. Shows layer stack and any changes. /  : %L O ! Show the macro corresponding to the given index. T16 Indexed Trigger Macro 0x10, R12 Indexed Result Macro 0x0C QSHL>$& (F>  ǀss؂7|{<a|`h@tsNt {_4ՀsƂT|}y|/|ssssL{D{|;|̓]|\!LEu3|s8sɃscssL܂у{dN}W~8H{@XÀ s P U9 % 7H 0 Q  8:I "36*,/#&MP BD:=1 & JUuVH4v * =D;Modify specified indexed layer state . L2 Indexed Layer 0x02 0 Off, 1 Shift, 2 Latch, 4 Lock States ) 6  3 HM< EGJ')List available layers. 7  B C? E(0z|2z‡u3zP;z_{;zs<z{q4z-}5z04z 5z]6{${I5 {e|ME{߃M3 'Send key-hold events to the macro module. Duplicates have undefined behaviour. S10 Scancode 0x0A G  $1I  P  BS*A579.0%   T+ ORTFPrints an indexed list of all non USB keycode capabilities.<  I  45=Do N macro processing steps. Defaults to 1.Disables/Enables sending USB keycodes to the Output Module and prints U/K codes.  F > L6  .I}DDt<@p D} q W} s ~C{P{H{&)X|%PI}Z{R ˀ!s"#s$\%s&'$()(*{+ă,D|-Ճ.|/l0R}1/2Z3S456Ԉ789!:;s<l=s>?s@ʂAItBCU{DMEڃF|GvH\}IxJ~KL*MUNNOP Q+R.SЀT?|UV  V2' AV(+-!$.Triggers the specified capabilities. First two args are state and stateType. K11 Keyboard Capability 0x0B ?" 5@Q  M4# 9 A2List the defined trigger and result macros.KN@C38; , -   ;4 ) O  < J-Keyboard Protocol Mode: 0 - Boot, 1 - OS/NKRO ModePrepare a space separated list of USB codes (decimal). Waits until sendKeys.USB Module CommandsЍcE܍ 5F#EEFYFSend the prepared list of USB codes and modifier byte.Set the modfier byte: 1 LCtrl, 2 LShft, 4 LAlt, 8 LGUI, 16 RCtrl, 32 RShft, 64 RAlt, 128 RGUIINFO - Keyboard Protocol: INFO - LED State: Output_consCtrlSend(consCode)WARNING - Consumer Control is not implemented for Boot Mode Output_noneSend()Output_sysCtrlSend(sysCode)WARNING - System Control is not implemented for Boot Mode Output_flashMode()Output_kbdProtocolBoot()Output_kbdProtocolNKRO()Output_usbCodeSend(usbCode)WARNING - USB Key limit reached WARNING - USB Code above 104/0x68 in Boot Mode: WARNING - USB Code not within 4-49 (0x4-0x31), 51-155 (0x33-0x9B), 157-164 (0x9D-0xA4), 176-221 (0xB0-0xDD) or 224-231 (0xE0-0xE7) NKRO Mode: kbdProtocoloutputDebugreadLEDssendKeyssetKeyssetModToggle Output Debug mode.Read LED byte: 1 NumLck, 2 CapsLck, 4 ScrlLck, 16 Kana, etc. BG F"?! "N}! "xv!6 "U!O "5!h 6   : $  T   l  X  zWARNING - CLEAR_FEATURE - Device/Interface WARNING - SET_FEATURE - Device/Interface WARNING - Unknown interface - ERROR - USB not configured... WARNING - USB Transmit Timeout... SysCtrl[] ConsCtrl[DEBUG - Boot USB: DEBUG - NKRO USB: You're looking at it :PSends a software restart, should be similar to powering on the device.Clear the screen.Signals microcontroller to reflash/reload.Version information about this firmware. c   Revision: 7b7a55899f392ebb7f615fd1801aaaa3dcc3f738 Branch: master Tree Status: Clean Repo Origin: https://github.com/kiibohd/controller.git Commit Date: 2015-10-18 17:54:41 -0700 Commit Author: Jacob Alexander Build Date: 2015-11-12 14:20:55 +0000 Build OS: Linux-4.1.5-x86_64-linode61 Architecture: arm Chip: mk20dx128vlf5 CPU: cortex-m4 Device: Keyboard Modules: Scan(KType) Macro(PartialMap) Output(pjrcUSB) Debug(full) Unique Id: INFO - Hex debug mode disabled... INFO - Hex debug mode enabled... RROR"" is not a valid command...type helpERROR - Max number of dictionaries defined already...  : ERROR - Serial line buffer is full, dropping character and resetting...   clearcliDebughelpledreloadresetrestartversionResets the terminal back to initial settings.gXkyZXėY yX'X YYEnables/Disables indicator LED. Try a couple times just in case the LED is in an odd state. Warning: May adversely affect some modules...General CommandsEnables/Disables hex output of the most recent cli input.),   )")-*m* &!'|} }}}$},}4}<}d}l}t}|}}}dlt|8t܀ LT\dL|t (08\dltЂDPX`hphĈ̈0t܈{} u)uu%)u.%)1uui%3)uu%)u.%)݁u %u  ) %5F;ue 9B &u  0 1 2 5&u  6 66Virtual Serial Port - DataJoystick MouseMedia KeysNKRO Keyboard   !"?  !"}@  $$$$@  @@  !"v  !"U  !"5    )%uu 0 1%u H%5Eu 8%5Eu H5Eu 8%u u&) u& *Boot Keyboard@M:Virtual Serial Port - StatusKiibohdRKeyboard - KType PartialMap pjrcUSB full u)%uu)uu%) RClean master - 2015-10-18 17:54:41 -0700UFD0U5fwupd-1.2.14/plugins/dfu/tests/metadata.dfu000066400000000000000000000000431402665037500206120ustar00rootroot00000000000000amaliaMDkeyvalueUFDXfwupd-1.2.14/plugins/ebitdo/000077500000000000000000000000001402665037500156635ustar00rootroot00000000000000fwupd-1.2.14/plugins/ebitdo/README.md000066400000000000000000000015751402665037500171520ustar00rootroot000000000000008Bitdo Support ============== Introduction ------------ This plugin can flash the firmware on the 8Bitdo game pads. Ebitdo support is supported directly by this project with the embedded libebitdo library and is possible thanks to the vendor open sourcing the flashing tool. The 8Bitdo devices share legacy USB VID/PIDs with other projects and so we have to be a bit careful to not claim other devices as our own. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. The binary file has a vendor-specific header that is used when flashing the image. This plugin supports the following protocol ID: * com.8bitdo GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_2DC8&PID_AB11&REV_0001` * `USB\VID_2DC8&PID_AB11` * `USB\VID_2DC8` fwupd-1.2.14/plugins/ebitdo/data/000077500000000000000000000000001402665037500165745ustar00rootroot00000000000000fwupd-1.2.14/plugins/ebitdo/data/m30.txt000066400000000000000000000175211402665037500177420ustar00rootroot00000000000000Bus 002 Device 008: ID 2dc8:5006 8BitDo M30 gamepad Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x2dc8 8BitDo idProduct 0x5006 M30 gamepad bcdDevice 0.01 iManufacturer 1 8Bitdo iProduct 2 8BitDo M30 gamepad iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0029 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 123 Report Descriptor: (length is 123) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x05 ] 5 Gamepad Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Physical Minimum, data= [ 0x00 ] 0 Item(Global): Physical Maximum, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x0f ] 15 Item(Global): Usage Page, data= [ 0x09 ] 9 Buttons Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Button 1 (Primary) Item(Local ): Usage Maximum, data= [ 0x0f ] 15 (null) Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x01 ] 1 Constant Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Global): Logical Maximum, data= [ 0x07 ] 7 Item(Global): Physical Maximum, data= [ 0x3b 0x01 ] 315 Item(Global): Report Size, data= [ 0x04 ] 4 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Unit, data= [ 0x14 ] 20 System: English Rotation, Unit: Degrees Item(Local ): Usage, data= [ 0x39 ] 57 Hat Switch Item(Main ): Input, data= [ 0x42 ] 66 Data Variable Absolute No_Wrap Linear Preferred_State Null_State Non_Volatile Bitfield Item(Global): Unit, data= [ 0x00 ] 0 System: None, Unit: (None) Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x01 ] 1 Constant Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x30 ] 48 Direction-X Item(Local ): Usage, data= [ 0x31 ] 49 Direction-Y Item(Local ): Usage, data= [ 0x32 ] 50 Direction-Z Item(Local ): Usage, data= [ 0x35 ] 53 Rotate-Z Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x04 ] 4 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x02 ] 2 Simulation Controls Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0xc4 ] 196 Accelerator Item(Local ): Usage, data= [ 0xc5 ] 197 Brake Item(Global): Report Count, data= [ 0x02 ] 2 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x08 ] 8 LEDs Item(Local ): Usage, data= [ 0x43 ] 67 Slow Blink On Time Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Physical Minimum, data= [ 0x00 ] 0 Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x02 ] 2 Item(Main ): Output, data= [ 0x82 ] 130 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Volatile Bitfield Item(Local ): Usage, data= [ 0x44 ] 68 Slow Blink Off Time Item(Main ): Output, data= [ 0x82 ] 130 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Volatile Bitfield Item(Local ): Usage, data= [ 0x45 ] 69 Fast Blink On Time Item(Main ): Output, data= [ 0x82 ] 130 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Volatile Bitfield Item(Local ): Usage, data= [ 0x46 ] 70 Fast Blink Off Time Item(Main ): Output, data= [ 0x82 ] 130 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 16 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/ebitdo/data/nes30pro.txt000066400000000000000000000107671402665037500210210ustar00rootroot00000000000000Bus 003 Device 017: ID 2002:9000 DAP Technologies Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x2002 DAP Technologies idProduct 0x9000 bcdDevice 0.01 iManufacturer 1 8Bitdo NES30 Pro iProduct 2 8Bitdo NES30 Pro iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 123 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 16 Device Status: 0x0000 (Bus Powered) Bus 003 Device 019: ID 0483:5750 STMicroelectronics Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x0483 STMicroelectronics idProduct 0x5750 bcdDevice 2.00 iManufacturer 1 8BitdoJoy iProduct 2 8Bitdo iSerial 3 BootMod bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 33 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 16 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/ebitdo/data/scf30.txt000066400000000000000000000106001402665037500202500ustar00rootroot00000000000000 Bus 002 Device 053: ID 1235:ab21 Focusrite-Novation Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x1235 Focusrite-Novation idProduct 0xab21 bcdDevice 0.01 iManufacturer 1 iProduct 2 iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 99 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 16 Bus 002 Device 055: ID 0483:5750 STMicroelectronics Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x0483 STMicroelectronics idProduct 0x5750 bcdDevice 2.00 iManufacturer 1 iProduct 2 iSerial 3 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 33 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 16 fwupd-1.2.14/plugins/ebitdo/data/sf30pro.txt000066400000000000000000000303401402665037500206310ustar00rootroot00000000000000Start + Y --------- Bus 001 Device 008: ID 057e:2009 Nintendo Co., Ltd Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x057e Nintendo Co., Ltd idProduct 0x2009 bcdDevice 2.00 iManufacturer 1 Nintendo Co., Ltd. iProduct 2 Pro Controller iSerial 3 000000000001 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 203 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 8 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 8 Device Status: 0x0000 (Bus Powered) Start + B ------------ Bus 001 Device 009: ID 2dc8:6000 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x2dc8 idProduct 0x6000 bcdDevice 0.01 iManufacturer 1 8Bitdo SF30 Pro iProduct 2 8Bitdo SF30 Pro iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 123 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 16 Device Status: 0x0000 (Bus Powered) Start + A --------- Bus 001 Device 012: ID 054c:05c4 Sony Corp. DualShock 4 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x054c Sony Corp. idProduct 0x05c4 DualShock 4 bcdDevice 1.00 iManufacturer 1 Sony Computer Entertainment iProduct 2 Wireless Controller iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 499 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x03 EP 3 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Device Status: 0x0000 (Bus Powered) Start + X --------- Bus 001 Device 013: ID 045e:028e Microsoft Corp. Xbox360 Controller Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 255 Vendor Specific Class bDeviceSubClass 255 Vendor Specific Subclass bDeviceProtocol 255 Vendor Specific Protocol bMaxPacketSize0 8 idVendor 0x045e Microsoft Corp. idProduct 0x028e Xbox360 Controller bcdDevice 1.14 iManufacturer 1 8Bitdo SF30 Pro iProduct 2 Controller iSerial 3 (error) bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 153 bNumInterfaces 4 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 93 bInterfaceProtocol 1 iInterface 0 ** UNRECOGNIZED: 11 21 00 01 01 25 81 14 00 00 00 00 13 01 08 00 00 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 4 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 8 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 4 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 93 bInterfaceProtocol 3 iInterface 0 ** UNRECOGNIZED: 1b 21 00 01 01 01 82 40 01 02 20 16 83 00 00 00 00 00 00 16 03 00 00 00 00 00 00 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 2 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 4 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 64 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x03 EP 3 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 16 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 93 bInterfaceProtocol 2 iInterface 0 ** UNRECOGNIZED: 09 21 00 01 01 22 84 07 00 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 16 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 3 bAlternateSetting 0 bNumEndpoints 0 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 253 bInterfaceProtocol 19 iInterface 4 µ∡H釰俸샴`翰⣸贠øĀ贤Ǹɀ败˸赐ϸ桀F㏰៸贠ø贀Ǹ赀˸赐ϸ桀F⟰ ** UNRECOGNIZED: 06 41 00 01 01 03 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/ebitdo/data/update.csv000066400000000000000000032101711402665037500206000ustar00rootroot00000000000000# Total Phase Data Center(tm) v6.63 # (c) 2005-2015 Total Phase, Inc. # www.totalphase.com # # Fri Jun 03 12:02:30 2016 # # Level,Sp,Index,m:s.ms.us,Dur,Len,Err,Dev,Ep,Record,Summary 0,,0,0:00.000.000,,,,,,Capture started (Aggregate),[Fri 03 Jun 2016 12:00:01 BST] 0,,1,0:00.000.024,,,,,, / , 0,,2,0:09.124.398,,,,,, / , 0,,3,0:09.565.223,,,,,, / , 0,,4,0:09.643.335,,,,,, / , 0,,5,0:10.095.673,,,,,, / , 0,,6,0:10.126.773,,,,,, / , 0,,7,0:10.127.444,31.007.125 ms,,,,,[32 SOF],[Frames: 1881 - 1912] 0,,8,0:10.158.452,13.000 us,8 B,,00,00,SETUP txn,80 06 00 01 00 00 40 00 0,,12,0:10.159.448,2.812 us,,,,,[1 SOF],[Frame: 1913] 0,,13,0:10.159.451,20.229 us,18 B,,00,00,IN txn,12 01 00 02 00 00 00 40 83 04 50 57 00 02 01 02 03 01 0,,17,0:10.160.448,1.002.958 ms,,,,,[2 SOF],[Frames: 1914 - 1915] 0,,18,0:10.161.452,7.666 us,0 B,,00,00,OUT txn, 0,,22,0:10.162.449,2.833 us,,,,,[1 SOF],[Frame: 1916] 0,,23,0:10.162.542,,,,,, / , 0,,24,0:10.189.147,,,,,, / , 0,,25,0:10.189.452,31.007.125 ms,,,,,[32 SOF],[Frames: 1943 - 1974] 0,,26,0:10.220.460,13.000 us,8 B,,00,00,SETUP txn,00 05 01 00 00 00 00 00 0,,30,0:10.221.457,2.812 us,,,,,[1 SOF],[Frame: 1975] 0,,31,0:10.221.460,8.229 us,0 B,,00,00,IN txn, 0,,35,0:10.222.457,30.007.000 ms,,,,,[31 SOF],[Frames: 1976 - 2006] 0,,36,0:10.252.465,13.000 us,8 B,,01,00,SETUP txn,80 06 00 01 00 00 12 00 0,,40,0:10.253.461,2.895 us,,,,,[1 SOF],[Frame: 2007] 0,,41,0:10.253.465,20.229 us,18 B,,01,00,IN txn,12 01 00 02 00 00 00 40 83 04 50 57 00 02 01 02 03 01 0,,45,0:10.254.461,2.812 us,,,,,[1 SOF],[Frame: 2008] 0,,46,0:10.254.465,7.645 us,0 B,,01,00,OUT txn, 0,,50,0:10.255.462,1.003.041 ms,,,,,[2 SOF],[Frames: 2009 - 2010] 0,,51,0:10.256.465,13.083 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 FF 00 0,,55,0:10.257.462,2.812 us,,,,,[1 SOF],[Frame: 2011] 0,,56,0:10.257.465,35.729 us,41 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 09 04 00 00 02 03 00 00 00 09 21 10 01 00 01… 0,,60,0:10.258.462,1.002.958 ms,,,,,[2 SOF],[Frames: 2012 - 2013] 0,,61,0:10.259.465,7.666 us,0 B,,01,00,OUT txn, 0,,65,0:10.260.462,1.003.125 ms,,,,,[2 SOF],[Frames: 2014 - 2015] 0,,66,0:10.261.466,13.083 us,8 B,,01,00,SETUP txn,80 06 03 03 09 04 FF 00 0,,70,0:10.262.463,2.895 us,,,,,[1 SOF],[Frame: 2016] 0,,71,0:10.262.466,25.562 us,26 B,,01,00,IN txn,1A 03 38 00 42 00 69 00 74 00 64 00 6F 00 20 00 00 00 00 00 00 00 00 00… 0,,75,0:10.263.463,1.003.041 ms,,,,,[2 SOF],[Frames: 2017 - 2018] 0,,76,0:10.264.466,7.666 us,0 B,,01,00,OUT txn, 0,,80,0:10.265.463,1.003.041 ms,,,,,[2 SOF],[Frames: 2019 - 2020] 0,,81,0:10.266.466,13.062 us,8 B,,01,00,SETUP txn,80 06 00 03 00 00 FF 00 0,,85,0:10.267.463,2.916 us,,,,,[1 SOF],[Frame: 2021] 0,,86,0:10.267.467,10.916 us,4 B,,01,00,IN txn,04 03 09 04 0,,90,0:10.268.463,1.003.041 ms,,,,,[2 SOF],[Frames: 2022 - 2023] 0,,91,0:10.269.467,7.645 us,0 B,,01,00,OUT txn, 0,,95,0:10.270.464,1.003.041 ms,,,,,[2 SOF],[Frames: 2024 - 2025] 0,,96,0:10.271.467,13.062 us,8 B,,01,00,SETUP txn,80 06 02 03 09 04 FF 00 0,,100,0:10.272.464,2.895 us,,,,,[1 SOF],[Frame: 2026] 0,,101,0:10.272.467,20.229 us,18 B,,01,00,IN txn,12 03 38 00 42 00 69 00 74 00 64 00 6F 00 20 00 20 00 0,,105,0:10.273.464,1.003.041 ms,,,,,[2 SOF],[Frames: 2027 - 2028] 0,,106,0:10.274.468,7.645 us,0 B,,01,00,OUT txn, 0,,110,0:10.275.464,1.003.041 ms,,,,,[2 SOF],[Frames: 2029 - 2030] 0,,111,0:10.276.468,13.000 us,8 B,,01,00,SETUP txn,80 06 00 06 00 00 0A 00 0,,115,0:10.277.465,2.895 us,,,,,[1 SOF],[Frame: 2031] 0,,116,0:10.277.468,4.583 us,,,01,00,IN txn (STALL), 0,,119,0:10.278.465,4.003.458 ms,,,,,[5 SOF],[Frames: 2032 - 2036] 0,,120,0:10.282.469,13.020 us,8 B,,01,00,SETUP txn,80 06 00 01 00 00 12 00 0,,124,0:10.283.465,2.895 us,,,,,[1 SOF],[Frame: 2037] 0,,125,0:10.283.469,20.229 us,18 B,,01,00,IN txn,12 01 00 02 00 00 00 40 83 04 50 57 00 02 01 02 03 01 0,,129,0:10.284.466,2.895 us,,,,,[1 SOF],[Frame: 2038] 0,,130,0:10.284.469,7.666 us,0 B,,01,00,OUT txn, 0,,134,0:10.285.466,1.003.041 ms,,,,,[2 SOF],[Frames: 2039 - 2040] 0,,135,0:10.286.470,13.000 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 09 00 0,,139,0:10.287.466,2.895 us,,,,,[1 SOF],[Frame: 2041] 0,,140,0:10.287.469,14.229 us,9 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 0,,144,0:10.288.466,2.916 us,,,,,[1 SOF],[Frame: 2042] 0,,145,0:10.288.469,7.666 us,0 B,,01,00,OUT txn, 0,,149,0:10.289.466,1.003.041 ms,,,,,[2 SOF],[Frames: 2043 - 2044] 0,,150,0:10.290.470,13.000 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 29 00 0,,154,0:10.291.467,3.000 us,,,,,[1 SOF],[Frame: 2045] 0,,155,0:10.291.470,35.750 us,41 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 09 04 00 00 02 03 00 00 00 09 21 10 01 00 01… 0,,159,0:10.292.467,2.979 us,,,,,[1 SOF],[Frame: 2046] 0,,160,0:10.292.470,7.666 us,0 B,,01,00,OUT txn, 0,,164,0:10.293.467,1.002.958 ms,,,,,[2 SOF],[Frames: 2047 - 0] 0,,165,0:10.294.472,13.000 us,8 B,,01,00,SETUP txn,00 09 01 00 00 00 00 00 0,,169,0:10.295.467,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,170,0:10.295.470,8.229 us,0 B,,01,00,IN txn, 0,,174,0:10.296.467,2.812 us,,,,,[1 SOF],[Frame: 2] 0,,175,0:10.296.471,13.000 us,8 B,,01,00,SETUP txn,21 0A 00 00 00 00 00 00 0,,179,0:10.297.467,2.833 us,,,,,[1 SOF],[Frame: 3] 0,,180,0:10.297.471,4.604 us,,,01,00,IN txn (STALL), 0,,183,0:10.298.468,1.002.958 ms,,,,,[2 SOF],[Frames: 4 - 5] 0,,184,0:10.299.473,13.083 us,8 B,,01,00,SETUP txn,81 06 00 22 00 00 61 00 0,,188,0:10.300.468,2.833 us,,,,,[1 SOF],[Frame: 6] 0,,189,0:10.300.471,30.416 us,33 B,,01,00,IN txn,05 8C 09 01 A1 01 09 03 15 00 26 00 FF 75 08 95 40 81 02 09 04 15 00 26… 0,,193,0:10.301.468,1.002.958 ms,,,,,[2 SOF],[Frames: 7 - 8] 0,,194,0:10.302.471,7.666 us,0 B,,01,00,OUT txn, 0,,198,0:10.303.468,88.015.041 ms,,,,,[89 SOF],[Frames: 9 - 97] 0,,199,0:10.391.484,50.312 us,64 B,,01,01,OUT txn,05 00 16 01 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,203,0:10.392.481,207.031.562 ms,,,,,[208 SOF],[Frames: 98 - 305] 0,,204,0:10.599.513,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,208,0:10.600.509,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,209,0:10.326.475,288.090.895 ms,64 B,,01,02,IN txn [9 POLL],0C 00 16 07 00 04 04 00 0B 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,214,0:10.615.512,192.029.479 ms,,,,,[193 SOF],[Frames: 321 - 513] 0,,215,0:10.807.541,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,219,0:10.808.538,30.006.979 ms,,,,,[31 SOF],[Frames: 514 - 544] 0,,220,0:10.646.519,192.077.562 ms,64 B,,01,02,IN txn [6 POLL],0C 00 16 07 00 04 04 00 0B 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,225,0:10.839.543,1.999.280.375 s,,,,,[2000 SOF],[Frames: 545 - 496] [Periodic Timeout] 0,,226,0:10.870.550,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,227,0:12.839.820,1.999.280.375 s,,,,,[2000 SOF],[Frames: 497 - 448] [Periodic Timeout] 0,,228,0:12.886.830,1.984.280.062 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,229,0:14.840.098,1.999.280.375 s,,,,,[2000 SOF],[Frames: 449 - 400] [Periodic Timeout] 0,,230,0:14.903.110,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,231,0:16.840.376,1.999.280.375 s,,,,,[2000 SOF],[Frames: 401 - 352] [Periodic Timeout] 0,,232,0:16.919.390,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,233,0:18.840.653,288.042.812 ms,,,,,[289 SOF],[Frames: 353 - 641] 0,,234,0:19.128.697,50.333 us,64 B,,01,01,OUT txn,05 00 1A 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,238,0:19.129.694,30.006.979 ms,,,,,[31 SOF],[Frames: 642 - 672] 0,,239,0:18.935.670,224.082.354 ms,64 B,,01,02,IN txn [7 POLL],2D 00 19 24 00 7F 1E 50 33 39 31 32 02 4E 39 31 38 2C BD CB 0F 9A 89 12… 0,,244,0:19.160.698,1.999.280.354 s,,,,,[2000 SOF],[Frames: 673 - 624] [Periodic Timeout] 0,,245,0:19.191.705,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,246,0:21.160.975,1.999.280.375 s,,,,,[2000 SOF],[Frames: 625 - 576] [Periodic Timeout] 0,,247,0:21.207.985,1.984.280.020 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,248,0:23.161.253,1.999.280.354 s,,,,,[2000 SOF],[Frames: 577 - 528] [Periodic Timeout] 0,,249,0:23.224.265,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,250,0:25.161.531,1.999.280.375 s,,,,,[2000 SOF],[Frames: 529 - 480] [Periodic Timeout] 0,,251,0:25.240.545,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,252,0:27.161.809,1.552.218.375 s,,,,,[1553 SOF],[Frames: 481 - 2033] 0,,253,0:28.714.027,50.333 us,64 B,,01,01,OUT txn,23 00 16 1F 00 01 1C 00 0B 01 00 00 00 A0 00 08 00 B8 00 00 80 68 CF 01… 0,,257,0:28.715.024,15.004.916 ms,,,,,[16 SOF],[Frames: 2034 - 1] 0,,258,0:28.730.029,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 1C 00 0B 01 00 00 00 A0 00 08 00 B8 00 00 80 68 CF 01… 0,,262,0:27.256.825,1.984.280.020 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,263,0:28.731.026,1.839.258.145 s,,,,,[1840 SOF],[Frames: 2 - 1841] 0,,264,0:28.746.032,1.824.303.562 s,64 B,,01,01,OUT txn [114 POLL],05 00 1D 01 00 00 1C 00 0B 01 00 00 00 A0 00 08 00 B8 00 00 80 68 CF 01… 0,,725,0:30.571.282,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,726,0:29.273.105,1.312.233.062 s,64 B,,01,02,IN txn [41 POLL],06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,731,0:30.586.284,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,732,0:30.586.287,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 1C 00 0B 01 00 00 00 A0 00 08 00 B8 00 00 80 68 CF 01… 0,,736,0:30.587.284,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,737,0:30.602.289,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB 7D 28 D4 3A 00 4A F1 30 AC 25 4C FD 08 67 17… 0,,741,0:30.603.286,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,742,0:30.617.291,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,746,0:30.618.288,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,747,0:30.618.292,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB 7D 28 D4 3A 00 4A F1 30 AC 25 4C FD 08 67 17… 0,,751,0:30.619.288,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,752,0:30.634.294,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D0 E3 13 0F 27 61 F6 24 5D 4B B2 25 D4 AF 56 6E… 0,,756,0:30.635.291,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,757,0:30.649.296,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,761,0:30.650.293,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,762,0:30.650.296,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D0 E3 13 0F 27 61 F6 24 5D 4B B2 25 D4 AF 56 6E… 0,,766,0:30.651.293,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,767,0:30.666.298,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C DD 4E 02 98 67 3D 24 80 29 06 7C CB A8 FD 44… 0,,771,0:30.667.295,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,772,0:30.681.300,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,776,0:30.682.297,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,777,0:30.682.300,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C DD 4E 02 98 67 3D 24 80 29 06 7C CB A8 FD 44… 0,,781,0:30.683.297,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,782,0:30.698.303,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 6A 81 1E F9 A0 8D 24 E8 A3 35 4C E8 67 F3 F9… 0,,786,0:30.699.300,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,787,0:30.713.305,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,791,0:30.714.302,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,792,0:30.714.305,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 6A 81 1E F9 A0 8D 24 E8 A3 35 4C E8 67 F3 F9… 0,,796,0:30.715.302,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,797,0:30.730.307,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 32 52 7B 9D C1 93 B1 88 79 49 FB 02 32 9F BA… 0,,801,0:30.731.304,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,802,0:30.745.309,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,806,0:30.746.306,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,807,0:30.746.309,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 32 52 7B 9D C1 93 B1 88 79 49 FB 02 32 9F BA… 0,,811,0:30.747.306,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,812,0:30.762.312,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 08 B8 FC B9 85 51 F6 32 2E E3 BD EF 38 77 72… 0,,816,0:30.763.308,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,817,0:30.777.314,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,821,0:30.778.311,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,822,0:30.778.314,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 08 B8 FC B9 85 51 F6 32 2E E3 BD EF 38 77 72… 0,,826,0:30.779.311,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,827,0:30.794.316,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D1 EC AF 7B 87 DB 1A 24 35 70 6F 77 0B 07 AE 30… 0,,831,0:30.795.313,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,832,0:30.809.318,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,836,0:30.810.315,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,837,0:30.810.318,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D1 EC AF 7B 87 DB 1A 24 35 70 6F 77 0B 07 AE 30… 0,,841,0:30.811.315,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,842,0:30.826.320,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 91 59 84 CB 48 C0 82 4C D9 EB C5 7E 78 F1 0B… 0,,846,0:30.827.317,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,847,0:30.841.323,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,851,0:30.842.319,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,852,0:30.842.323,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 91 59 84 CB 48 C0 82 4C D9 EB C5 7E 78 F1 0B… 0,,856,0:30.843.320,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,857,0:30.858.325,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE BA 5B 62 0B 67 2F A6 8F 71 D4 C3 30 08 C9 B7… 0,,861,0:30.859.322,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,862,0:30.873.327,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,866,0:30.874.324,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,867,0:30.874.327,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE BA 5B 62 0B 67 2F A6 8F 71 D4 C3 30 08 C9 B7… 0,,871,0:30.875.324,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,872,0:30.890.329,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 BF D6 CF DE B4 36 32 1D 5C 56 AE 14 03 70 42… 0,,876,0:30.891.326,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,877,0:30.905.331,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,881,0:30.906.328,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,882,0:30.906.332,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 BF D6 CF DE B4 36 32 1D 5C 56 AE 14 03 70 42… 0,,886,0:30.907.328,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,887,0:30.922.334,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF 24 EF 66 05 78 4C 13 3E AA C1 D2 E9 C7 6C 41… 0,,891,0:30.923.331,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,892,0:30.937.336,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,896,0:30.938.333,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,897,0:30.938.336,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF 24 EF 66 05 78 4C 13 3E AA C1 D2 E9 C7 6C 41… 0,,901,0:30.939.333,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,902,0:30.954.338,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8B B7 26 DC 03 B1 E2 C0 F6 AB 95 C8 C6 8A B8 77… 0,,906,0:30.955.335,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,907,0:30.969.340,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,911,0:30.970.337,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,912,0:30.970.340,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8B B7 26 DC 03 B1 E2 C0 F6 AB 95 C8 C6 8A B8 77… 0,,916,0:30.971.337,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,917,0:30.986.343,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D 68 58 E9 BE C1 35 C5 D8 04 E9 3B E4 CB 8C 0E… 0,,921,0:30.987.340,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,922,0:31.001.345,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,926,0:31.002.342,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,927,0:31.002.345,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D 68 58 E9 BE C1 35 C5 D8 04 E9 3B E4 CB 8C 0E… 0,,931,0:31.003.342,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,932,0:31.018.347,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 21 BB 73 CE 2E 26 27 1C DF E7 01 8A 78 1C BC… 0,,936,0:31.019.344,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,937,0:31.033.349,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,941,0:31.034.346,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,942,0:31.034.349,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 21 BB 73 CE 2E 26 27 1C DF E7 01 8A 78 1C BC… 0,,946,0:31.035.346,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,947,0:31.050.352,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF 98 9B 98 3F D8 A7 7B FF FD A5 2B 32 CF 42 E3… 0,,951,0:31.051.348,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,952,0:31.065.354,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,956,0:31.066.351,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,957,0:31.066.354,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF 98 9B 98 3F D8 A7 7B FF FD A5 2B 32 CF 42 E3… 0,,961,0:31.067.351,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,962,0:31.082.356,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AC C5 DE 5D 0C A9 27 3D 37 88 70 55 0A AF 1B 33… 0,,966,0:31.083.353,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,967,0:31.097.358,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,971,0:31.098.355,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,972,0:31.098.358,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AC C5 DE 5D 0C A9 27 3D 37 88 70 55 0A AF 1B 33… 0,,976,0:31.099.355,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,977,0:31.114.360,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 77 F3 6D 38 6A 16 2E F4 76 80 DD 73 3A D4 88… 0,,981,0:31.115.357,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,982,0:31.129.363,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,986,0:31.130.359,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,987,0:31.130.363,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 77 F3 6D 38 6A 16 2E F4 76 80 DD 73 3A D4 88… 0,,991,0:31.131.360,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,992,0:31.146.365,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 B9 74 C4 E1 68 3B 17 91 E7 22 F0 F4 91 77 E2… 0,,996,0:31.147.362,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,997,0:31.161.367,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1001,0:31.162.364,16.005.041 ms,,,,,[17 SOF],[Frames: 385 - 401] 0,,1002,0:31.178.369,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 A6 A0 56 CA 7E 26 FF AB EE 6A 14 00 AB 77 D6… 0,,1006,0:31.179.366,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,1007,0:31.193.371,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1011,0:31.194.368,16.005.041 ms,,,,,[17 SOF],[Frames: 417 - 433] 0,,1012,0:31.210.374,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 A6 A0 56 CA 7E 26 FF AB EE 6A 14 00 AB 77 D6… 0,,1016,0:31.211.371,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,1017,0:31.225.376,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1021,0:31.226.373,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,1022,0:31.226.376,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 A6 A0 56 CA 7E 26 FF AB EE 6A 14 00 AB 77 D6… 0,,1026,0:31.227.373,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,1027,0:31.242.378,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 90 84 4C FF 0A E7 3B 9A AC 33 6A A2 52 E2 A4… 0,,1031,0:31.243.375,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,1032,0:31.257.380,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1036,0:31.258.377,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,1037,0:31.258.380,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 90 84 4C FF 0A E7 3B 9A AC 33 6A A2 52 E2 A4… 0,,1041,0:31.259.377,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,1042,0:31.274.383,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 07 97 FC 22 EC 53 D1 BB 1F C2 4E D3 05 56 1E A9… 0,,1046,0:31.275.380,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,1047,0:31.289.385,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1051,0:31.290.382,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,1052,0:31.290.385,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 07 97 FC 22 EC 53 D1 BB 1F C2 4E D3 05 56 1E A9… 0,,1056,0:31.291.382,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,1057,0:31.306.387,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF 19 3C 8C 2F C8 7B 3A 5E 28 74 86 64 6E 97 28… 0,,1061,0:31.307.384,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,1062,0:31.321.389,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1066,0:31.322.386,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,1067,0:31.322.389,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF 19 3C 8C 2F C8 7B 3A 5E 28 74 86 64 6E 97 28… 0,,1071,0:31.323.386,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,1072,0:31.338.392,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 68 E4 2B F6 74 64 05 5E C3 F7 B4 46 E6 8C BC… 0,,1076,0:31.339.388,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,1077,0:31.353.394,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1081,0:31.354.391,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,1082,0:31.354.394,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 68 E4 2B F6 74 64 05 5E C3 F7 B4 46 E6 8C BC… 0,,1086,0:31.355.391,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,1087,0:31.370.396,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 97 F8 20 A8 A5 6A 61 16 FD 82 60 00 4B 05 DB 2D… 0,,1091,0:31.371.393,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,1092,0:31.385.398,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1096,0:31.386.395,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,1097,0:31.386.398,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 97 F8 20 A8 A5 6A 61 16 FD 82 60 00 4B 05 DB 2D… 0,,1101,0:31.387.395,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,1102,0:31.402.400,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A2 7E 0A FF 96 2C E8 4C A1 D0 BE E3 3A 36 18 28… 0,,1106,0:31.403.397,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,1107,0:31.417.403,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1111,0:31.418.399,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,1112,0:31.418.403,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A2 7E 0A FF 96 2C E8 4C A1 D0 BE E3 3A 36 18 28… 0,,1116,0:31.419.400,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,1117,0:31.434.405,50.854 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF D8 5F 12 48 C6 CD 87 70 93 E0 CE C2 C8 92 FF… 0,,1121,0:31.435.402,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,1122,0:31.449.407,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1126,0:31.450.404,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,1127,0:31.450.407,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF D8 5F 12 48 C6 CD 87 70 93 E0 CE C2 C8 92 FF… 0,,1131,0:31.451.404,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,1132,0:31.466.409,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 27 3E 1F C9 6F 01 DD A9 D7 33 14 B9 DE 56 08… 0,,1136,0:31.467.406,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,1137,0:31.481.411,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1141,0:31.482.408,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,1142,0:31.482.412,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 27 3E 1F C9 6F 01 DD A9 D7 33 14 B9 DE 56 08… 0,,1146,0:31.483.408,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,1147,0:31.498.414,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C3 26 6B 5E 85 30 FB 6A 78 D9 C4 D1 71 0D 01 AA… 0,,1151,0:31.499.411,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,1152,0:31.513.416,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1156,0:31.514.413,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,1157,0:31.514.416,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C3 26 6B 5E 85 30 FB 6A 78 D9 C4 D1 71 0D 01 AA… 0,,1161,0:31.515.413,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,1162,0:31.530.418,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 49 B1 42 9A AD 07 51 4A 3B 9F 83 F5 3A 52 98… 0,,1166,0:31.531.415,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,1167,0:31.545.420,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1171,0:31.546.417,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,1172,0:31.546.420,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 49 B1 42 9A AD 07 51 4A 3B 9F 83 F5 3A 52 98… 0,,1176,0:31.547.417,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,1177,0:31.562.423,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 91 B9 91 C2 AD A6 00 99 92 1B 63 B8 60 D0 26… 0,,1181,0:31.563.420,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,1182,0:31.577.425,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1186,0:31.578.422,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,1187,0:31.578.425,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC 91 B9 91 C2 AD A6 00 99 92 1B 63 B8 60 D0 26… 0,,1191,0:31.579.422,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,1192,0:31.594.427,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 3A 70 87 32 41 29 84 34 5E 03 54 85 C0 C4 9D… 0,,1196,0:31.595.424,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,1197,0:31.609.429,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1201,0:31.610.426,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,1202,0:31.610.429,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 3A 70 87 32 41 29 84 34 5E 03 54 85 C0 C4 9D… 0,,1206,0:31.611.426,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,1207,0:31.626.432,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 9F 96 DB 76 CC A0 2D 87 D2 F0 74 2E C9 B8 F9… 0,,1211,0:31.627.428,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,1212,0:31.641.434,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1216,0:31.642.431,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,1217,0:31.642.434,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 9F 96 DB 76 CC A0 2D 87 D2 F0 74 2E C9 B8 F9… 0,,1221,0:31.643.431,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,1222,0:31.658.436,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 D2 EA CB FE 7B 7B E3 FE 98 D9 6D F3 D4 9F 4C… 0,,1226,0:31.659.433,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,1227,0:31.673.438,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1231,0:31.674.435,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,1232,0:31.674.438,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 D2 EA CB FE 7B 7B E3 FE 98 D9 6D F3 D4 9F 4C… 0,,1236,0:31.675.435,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,1237,0:31.690.440,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 2C B9 B8 67 65 0E 62 6A 12 00 B9 51 93 AB 76… 0,,1241,0:31.691.437,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,1242,0:31.705.443,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1246,0:31.706.439,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,1247,0:31.706.443,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 2C B9 B8 67 65 0E 62 6A 12 00 B9 51 93 AB 76… 0,,1251,0:31.707.440,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,1252,0:31.722.445,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 54 91 CD 15 CF 37 70 1E 40 83 70 52 33 C6 A1… 0,,1256,0:31.723.442,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,1257,0:31.737.447,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1261,0:31.738.444,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,1262,0:31.738.447,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 54 91 CD 15 CF 37 70 1E 40 83 70 52 33 C6 A1… 0,,1266,0:31.739.444,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,1267,0:31.754.449,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 0A F6 77 15 90 B5 B0 6A AC EC B9 56 F8 51 C8… 0,,1271,0:31.755.446,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,1272,0:31.769.451,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1276,0:31.770.448,16.005.125 ms,,,,,[17 SOF],[Frames: 993 - 1009] 0,,1277,0:31.786.454,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 20 64 E9 0E 99 B4 72 5F DC 48 F6 3A F2 B3 C6… 0,,1281,0:31.787.451,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,1282,0:31.801.456,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1286,0:31.802.453,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,1287,0:31.802.456,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 20 64 E9 0E 99 B4 72 5F DC 48 F6 3A F2 B3 C6… 0,,1291,0:31.803.453,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,1292,0:31.818.458,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B B1 5A 26 3A B6 1A BC CC 28 BA 62 86 70 89 1E… 0,,1296,0:31.819.455,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,1297,0:31.833.460,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1301,0:31.834.457,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,1302,0:31.834.460,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B B1 5A 26 3A B6 1A BC CC 28 BA 62 86 70 89 1E… 0,,1306,0:31.835.457,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,1307,0:31.850.463,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C B0 C5 8E D3 6B F4 2A E7 D1 E0 EB 2B 22 B9 2C… 0,,1311,0:31.851.460,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,1312,0:31.865.465,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1316,0:31.866.462,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,1317,0:31.866.465,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C B0 C5 8E D3 6B F4 2A E7 D1 E0 EB 2B 22 B9 2C… 0,,1321,0:31.867.462,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,1322,0:31.882.467,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 F1 7E 3F BA 67 BA 7C DE DD 8B 8C 98 9F C6 F5… 0,,1326,0:31.883.464,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,1327,0:31.897.469,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1331,0:31.898.466,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,1332,0:31.898.469,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 F1 7E 3F BA 67 BA 7C DE DD 8B 8C 98 9F C6 F5… 0,,1336,0:31.899.466,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,1337,0:31.914.472,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 35 C8 8B 4D CE A3 EC FC EE 20 B9 53 00 2E 2F 67… 0,,1341,0:31.915.468,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,1342,0:31.929.474,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1346,0:31.930.471,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,1347,0:31.930.474,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 35 C8 8B 4D CE A3 EC FC EE 20 B9 53 00 2E 2F 67… 0,,1351,0:31.931.471,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,1352,0:31.946.476,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 0C 95 20 4C 59 B9 B1 E0 43 20 E8 00 29 C1 FA… 0,,1356,0:31.947.473,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,1357,0:31.961.478,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1361,0:31.962.475,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,1362,0:31.962.478,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 0C 95 20 4C 59 B9 B1 E0 43 20 E8 00 29 C1 FA… 0,,1366,0:31.963.475,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,1367,0:31.978.480,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A B6 DC FD A0 C5 1B 37 C6 6F 8C C3 63 23 2C E8… 0,,1371,0:31.979.477,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,1372,0:31.993.483,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1376,0:31.994.479,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,1377,0:31.994.483,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A B6 DC FD A0 C5 1B 37 C6 6F 8C C3 63 23 2C E8… 0,,1381,0:31.995.480,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,1382,0:32.010.485,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 EB 47 C8 52 F3 28 09 82 7E F6 91 B3 24 8A 6B… 0,,1386,0:32.011.482,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,1387,0:32.025.487,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1391,0:32.026.484,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,1392,0:32.026.487,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 EB 47 C8 52 F3 28 09 82 7E F6 91 B3 24 8A 6B… 0,,1396,0:32.027.484,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,1397,0:32.042.489,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB B1 35 6D B3 AD E0 D5 08 64 94 45 F1 00 14 7A… 0,,1401,0:32.043.486,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,1402,0:32.057.491,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1406,0:32.058.488,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,1407,0:32.058.492,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB B1 35 6D B3 AD E0 D5 08 64 94 45 F1 00 14 7A… 0,,1411,0:32.059.488,15.004.916 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,1412,0:32.074.494,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 F7 F9 DC 55 01 23 D1 90 80 05 69 01 80 90 5E… 0,,1416,0:32.075.491,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,1417,0:32.089.496,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1421,0:32.090.493,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,1422,0:32.090.496,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 F7 F9 DC 55 01 23 D1 90 80 05 69 01 80 90 5E… 0,,1426,0:32.091.493,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,1427,0:32.106.498,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C 0B 1E 13 8D B2 6E EF 95 57 69 90 3E CB 86 D9… 0,,1431,0:32.107.495,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,1432,0:32.121.500,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1436,0:32.122.497,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,1437,0:32.122.500,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C 0B 1E 13 8D B2 6E EF 95 57 69 90 3E CB 86 D9… 0,,1441,0:32.123.497,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,1442,0:32.138.503,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 45 AD E8 A2 46 6F E0 2F 78 FB AD B6 1E 13 9B… 0,,1446,0:32.139.500,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,1447,0:32.153.505,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1451,0:32.154.502,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,1452,0:32.154.505,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 45 AD E8 A2 46 6F E0 2F 78 FB AD B6 1E 13 9B… 0,,1456,0:32.155.502,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,1457,0:32.170.507,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 F7 0A F2 B5 7E 4D 07 06 B9 A5 AA 13 56 98 A0… 0,,1461,0:32.171.504,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,1462,0:32.185.509,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1466,0:32.186.506,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,1467,0:32.186.509,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 F7 0A F2 B5 7E 4D 07 06 B9 A5 AA 13 56 98 A0… 0,,1471,0:32.187.506,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,1472,0:32.202.512,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C4 07 28 27 AD 79 8B EA FF 90 30 CD ED 1E 7E 65… 0,,1476,0:32.203.508,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,1477,0:32.217.514,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1481,0:32.218.510,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,1482,0:32.218.514,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C4 07 28 27 AD 79 8B EA FF 90 30 CD ED 1E 7E 65… 0,,1486,0:32.219.511,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,1487,0:32.234.516,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D EF A8 CB C9 99 CE D2 69 EB 90 72 17 08 01 14… 0,,1491,0:32.235.513,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,1492,0:32.249.518,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1496,0:32.250.515,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,1497,0:32.250.518,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D EF A8 CB C9 99 CE D2 69 EB 90 72 17 08 01 14… 0,,1501,0:32.251.515,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,1502,0:32.266.520,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A D5 7B F6 67 10 92 64 46 45 A4 48 D0 AC 08 87… 0,,1506,0:32.267.517,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,1507,0:32.281.523,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1511,0:32.282.519,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,1512,0:32.282.523,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A D5 7B F6 67 10 92 64 46 45 A4 48 D0 AC 08 87… 0,,1516,0:32.283.520,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,1517,0:32.298.525,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D1 D8 84 39 8E 3B 94 24 A1 4B 89 B9 3A 9D EB 33… 0,,1521,0:32.299.522,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,1522,0:32.313.527,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1526,0:32.314.524,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,1527,0:32.314.527,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D1 D8 84 39 8E 3B 94 24 A1 4B 89 B9 3A 9D EB 33… 0,,1531,0:32.315.524,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,1532,0:32.330.529,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE 1A FF 43 FA 58 94 D0 35 DC 9D 55 BB 7A ED 41… 0,,1536,0:32.331.526,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,1537,0:32.345.531,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1541,0:32.346.528,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,1542,0:32.346.532,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE 1A FF 43 FA 58 94 D0 35 DC 9D 55 BB 7A ED 41… 0,,1546,0:32.347.528,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,1547,0:32.362.534,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A1 7A 47 E5 00 E0 0D 6D 1D 9A DC A1 0B 2F 24 D1… 0,,1551,0:32.363.531,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,1552,0:32.377.536,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1556,0:32.378.533,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,1557,0:32.378.536,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A1 7A 47 E5 00 E0 0D 6D 1D 9A DC A1 0B 2F 24 D1… 0,,1561,0:32.379.533,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,1562,0:32.394.538,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C0 F9 EC 91 41 76 FC 09 42 B6 ED C2 71 3B AE 63… 0,,1566,0:32.395.535,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,1567,0:32.409.540,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1571,0:32.410.537,16.005.041 ms,,,,,[17 SOF],[Frames: 1633 - 1649] 0,,1572,0:32.426.543,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A0 3A 51 81 A7 26 D5 0E 6C ED DB 99 02 B0 6A 9C… 0,,1576,0:32.427.540,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,1577,0:32.441.545,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1581,0:32.442.542,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,1582,0:32.442.545,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A0 3A 51 81 A7 26 D5 0E 6C ED DB 99 02 B0 6A 9C… 0,,1586,0:32.443.542,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,1587,0:32.458.547,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 6A 03 2E BD 3D FD B8 DA 07 A2 22 86 BB 9D FC… 0,,1591,0:32.459.544,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,1592,0:32.473.549,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1596,0:32.474.546,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,1597,0:32.474.549,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 6A 03 2E BD 3D FD B8 DA 07 A2 22 86 BB 9D FC… 0,,1601,0:32.475.546,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,1602,0:32.490.552,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 A3 FA 54 E3 F3 CD 3F 20 0B 43 41 FE 0D 52 3C… 0,,1606,0:32.491.548,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,1607,0:32.505.554,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1611,0:32.506.550,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,1612,0:32.506.554,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 A3 FA 54 E3 F3 CD 3F 20 0B 43 41 FE 0D 52 3C… 0,,1616,0:32.507.551,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,1617,0:32.522.556,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD BE 6D 16 2E BF 61 E2 2F 8E E4 88 5C 9D 8E 27… 0,,1621,0:32.523.553,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,1622,0:32.537.558,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1626,0:32.538.555,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,1627,0:32.538.558,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD BE 6D 16 2E BF 61 E2 2F 8E E4 88 5C 9D 8E 27… 0,,1631,0:32.539.555,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,1632,0:32.554.560,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 54 5D F2 53 23 65 43 57 D4 C2 C7 F9 A1 F0 B6 20… 0,,1636,0:32.555.557,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,1637,0:32.569.562,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1641,0:32.570.559,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,1642,0:32.570.563,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 54 5D F2 53 23 65 43 57 D4 C2 C7 F9 A1 F0 B6 20… 0,,1646,0:32.571.559,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,1647,0:32.586.565,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1F 92 B1 0A 6D 3B DF A0 C7 CE B8 DD DD 6B 8E 20… 0,,1651,0:32.587.562,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,1652,0:32.601.567,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1656,0:32.602.564,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,1657,0:32.602.567,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1F 92 B1 0A 6D 3B DF A0 C7 CE B8 DD DD 6B 8E 20… 0,,1661,0:32.603.564,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,1662,0:32.618.569,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC EE A7 CC 67 01 8F 3D 78 9C 22 C2 ED 73 54 31… 0,,1666,0:32.619.566,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,1667,0:32.633.571,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1671,0:32.634.568,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,1672,0:32.634.571,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EC EE A7 CC 67 01 8F 3D 78 9C 22 C2 ED 73 54 31… 0,,1676,0:32.635.568,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,1677,0:32.650.574,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 2A 96 8B 8B 07 9E E6 FE F9 3B 43 22 45 06 F4… 0,,1681,0:32.651.571,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,1682,0:32.665.576,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1686,0:32.666.573,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,1687,0:32.666.576,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9A 2A 96 8B 8B 07 9E E6 FE F9 3B 43 22 45 06 F4… 0,,1691,0:32.667.573,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,1692,0:32.682.578,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EE 69 75 CB 1F 8C 63 DF 7F A7 EB A7 EA C2 3F 54… 0,,1696,0:32.683.575,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,1697,0:32.697.580,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1701,0:32.698.577,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,1702,0:32.698.580,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EE 69 75 CB 1F 8C 63 DF 7F A7 EB A7 EA C2 3F 54… 0,,1706,0:32.699.577,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,1707,0:32.714.583,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 20 BA 64 DD 01 08 6E 7D 57 CB F1 6D A7 ED 9D FE… 0,,1711,0:32.715.579,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,1712,0:32.729.585,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1716,0:32.730.582,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,1717,0:32.730.585,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 20 BA 64 DD 01 08 6E 7D 57 CB F1 6D A7 ED 9D FE… 0,,1721,0:32.731.582,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,1722,0:32.746.587,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CB 5A 01 0F 9C 98 A8 9E 74 7E 5E 74 26 20 D5 72… 0,,1726,0:32.747.584,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,1727,0:32.761.589,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1731,0:32.762.586,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,1732,0:32.762.589,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CB 5A 01 0F 9C 98 A8 9E 74 7E 5E 74 26 20 D5 72… 0,,1736,0:32.763.586,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,1737,0:32.778.592,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D4 E6 7A 96 C0 4E BA 80 CA C4 3C DC AD B7 B0 3C… 0,,1741,0:32.779.588,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,1742,0:32.793.594,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1746,0:32.794.590,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,1747,0:32.794.594,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D4 E6 7A 96 C0 4E BA 80 CA C4 3C DC AD B7 B0 3C… 0,,1751,0:32.795.591,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,1752,0:32.810.596,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D3 A0 BC CF 14 D7 7A BB 25 F7 1E 45 8C F1 76 BA… 0,,1756,0:32.811.593,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,1757,0:32.825.598,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1761,0:32.826.595,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,1762,0:32.826.598,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D3 A0 BC CF 14 D7 7A BB 25 F7 1E 45 8C F1 76 BA… 0,,1766,0:32.827.595,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,1767,0:32.842.600,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 A2 66 12 39 B0 83 CE E5 84 74 C6 AC 50 C3 BC… 0,,1771,0:32.843.597,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,1772,0:32.857.602,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1776,0:32.858.599,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,1777,0:32.858.603,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 A2 66 12 39 B0 83 CE E5 84 74 C6 AC 50 C3 BC… 0,,1781,0:32.859.599,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,1782,0:32.874.605,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 FD 25 FF 65 86 B5 1E 20 E0 31 7E E2 8C A5 FE… 0,,1786,0:32.875.602,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,1787,0:32.889.607,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1791,0:32.890.604,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,1792,0:32.890.607,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 FD 25 FF 65 86 B5 1E 20 E0 31 7E E2 8C A5 FE… 0,,1796,0:32.891.604,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,1797,0:32.906.609,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C5 DA B6 D7 2D B7 2C 8D 07 D3 92 80 F1 2B 91 4A… 0,,1801,0:32.907.606,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,1802,0:32.921.611,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1806,0:32.922.608,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,1807,0:32.922.611,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C5 DA B6 D7 2D B7 2C 8D 07 D3 92 80 F1 2B 91 4A… 0,,1811,0:32.923.608,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,1812,0:32.938.614,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 1F A4 8C 58 9D 4F E9 05 B8 B7 6D 62 3F 48 D2… 0,,1816,0:32.939.611,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,1817,0:32.953.616,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1821,0:32.954.613,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,1822,0:32.954.616,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 1F A4 8C 58 9D 4F E9 05 B8 B7 6D 62 3F 48 D2… 0,,1826,0:32.955.613,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,1827,0:32.970.618,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 ED 54 23 7D 48 1E 3C 0D 0B E2 2C 2C FD E5 93 E4… 0,,1831,0:32.971.615,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,1832,0:32.985.620,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1836,0:32.986.617,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,1837,0:32.986.620,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 ED 54 23 7D 48 1E 3C 0D 0B E2 2C 2C FD E5 93 E4… 0,,1841,0:32.987.617,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,1842,0:33.002.623,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 3D 45 74 34 A6 63 3F 89 6E 3F D6 8A 04 FD 74… 0,,1846,0:33.003.619,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,1847,0:33.017.625,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1851,0:33.018.622,16.005.041 ms,,,,,[17 SOF],[Frames: 193 - 209] 0,,1852,0:33.034.627,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 FC 9B 03 2D 29 7D FD 47 6F A9 C9 6D C9 5C 86… 0,,1856,0:33.035.624,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,1857,0:33.049.629,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1861,0:33.050.626,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,1862,0:33.050.629,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 FC 9B 03 2D 29 7D FD 47 6F A9 C9 6D C9 5C 86… 0,,1866,0:33.051.626,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,1867,0:33.066.631,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 99 0E 95 5F BF 6F D0 99 D9 10 FC CE BC 31 2F FE… 0,,1871,0:33.067.628,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,1872,0:33.081.634,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1876,0:33.082.630,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,1877,0:33.082.634,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 99 0E 95 5F BF 6F D0 99 D9 10 FC CE BC 31 2F FE… 0,,1881,0:33.083.631,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,1882,0:33.098.636,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 50 A2 7D 4F 26 27 68 4A 9D D9 8D 19 40 29 0B… 0,,1886,0:33.099.633,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,1887,0:33.113.638,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1891,0:33.114.635,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,1892,0:33.114.638,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 50 A2 7D 4F 26 27 68 4A 9D D9 8D 19 40 29 0B… 0,,1896,0:33.115.635,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,1897,0:33.130.640,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA FF 5B 7A B8 4A C3 7C 58 C0 52 71 05 3D BA BB… 0,,1901,0:33.131.637,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,1902,0:33.145.642,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1906,0:33.146.639,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,1907,0:33.146.643,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA FF 5B 7A B8 4A C3 7C 58 C0 52 71 05 3D BA BB… 0,,1911,0:33.147.639,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,1912,0:33.162.645,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 1A 17 C8 5C 7A 47 6E 59 E1 A3 B6 17 C6 53 51… 0,,1916,0:33.163.642,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,1917,0:33.177.647,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1921,0:33.178.644,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,1922,0:33.178.647,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 1A 17 C8 5C 7A 47 6E 59 E1 A3 B6 17 C6 53 51… 0,,1926,0:33.179.644,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,1927,0:33.194.649,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9D 97 DF 34 83 7B 9C 9C 0E CF A0 A4 26 87 EC 9A… 0,,1931,0:33.195.646,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,1932,0:33.209.651,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1936,0:33.210.648,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,1937,0:33.210.651,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9D 97 DF 34 83 7B 9C 9C 0E CF A0 A4 26 87 EC 9A… 0,,1941,0:33.211.648,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,1942,0:33.226.654,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 81 94 4A 76 2C 4D AA 19 85 E9 B1 EC 35 94 4A… 0,,1946,0:33.227.651,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,1947,0:33.241.656,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1951,0:33.242.653,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,1952,0:33.242.656,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 81 94 4A 76 2C 4D AA 19 85 E9 B1 EC 35 94 4A… 0,,1956,0:33.243.653,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,1957,0:33.258.658,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 C5 74 96 6B B0 76 AC 97 DE F8 02 50 59 D6 5D… 0,,1961,0:33.259.655,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,1962,0:33.273.660,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1966,0:33.274.657,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,1967,0:33.274.660,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 C5 74 96 6B B0 76 AC 97 DE F8 02 50 59 D6 5D… 0,,1971,0:33.275.657,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,1972,0:33.290.663,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 21 8B 60 AF B1 AB C8 56 7D 5B FE B7 38 7F 6D 2D… 0,,1976,0:33.291.659,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,1977,0:33.305.665,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1981,0:33.306.662,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,1982,0:33.306.665,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 21 8B 60 AF B1 AB C8 56 7D 5B FE B7 38 7F 6D 2D… 0,,1986,0:33.307.662,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,1987,0:33.322.667,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9F D2 4D 4D EB 1B AC 51 5B A3 E5 58 79 7D D7 D0… 0,,1991,0:33.323.664,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,1992,0:33.337.669,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1996,0:33.338.666,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,1997,0:33.338.669,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9F D2 4D 4D EB 1B AC 51 5B A3 E5 58 79 7D D7 D0… 0,,2001,0:33.339.666,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,2002,0:33.354.671,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 59 BE 1B C4 AA EE 1C EE BB EF 36 92 02 F1 88… 0,,2006,0:33.355.668,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,2007,0:33.369.674,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2011,0:33.370.670,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,2012,0:33.370.674,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 59 BE 1B C4 AA EE 1C EE BB EF 36 92 02 F1 88… 0,,2016,0:33.371.671,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,2017,0:33.386.676,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EE 8B C5 D3 A6 A7 9B E0 0E FF 60 E9 0B DC F0 C5… 0,,2021,0:33.387.673,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,2022,0:33.401.678,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2026,0:33.402.675,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,2027,0:33.402.678,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EE 8B C5 D3 A6 A7 9B E0 0E FF 60 E9 0B DC F0 C5… 0,,2031,0:33.403.675,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,2032,0:33.418.680,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F B6 0A BF D1 4F E4 7B 84 DE 01 00 0F 8A 96 E8… 0,,2036,0:33.419.677,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,2037,0:33.433.682,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2041,0:33.434.679,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,2042,0:33.434.683,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F B6 0A BF D1 4F E4 7B 84 DE 01 00 0F 8A 96 E8… 0,,2046,0:33.435.679,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,2047,0:33.450.685,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 BB 6B D9 28 2D F5 CF 18 89 00 66 D9 55 81 92… 0,,2051,0:33.451.682,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,2052,0:33.465.687,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2056,0:33.466.684,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,2057,0:33.466.687,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 BB 6B D9 28 2D F5 CF 18 89 00 66 D9 55 81 92… 0,,2061,0:33.467.684,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,2062,0:33.482.689,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 07 AC 24 58 34 5E D0 3D CB 55 E8 EA 37 3E AC… 0,,2066,0:33.483.686,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,2067,0:33.497.691,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2071,0:33.498.688,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,2072,0:33.498.691,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 07 AC 24 58 34 5E D0 3D CB 55 E8 EA 37 3E AC… 0,,2076,0:33.499.688,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,2077,0:33.514.694,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D CA 5F 42 85 E3 1F 9E 46 A7 84 D2 CE 41 99 A1… 0,,2081,0:33.515.691,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,2082,0:33.529.696,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2086,0:33.530.693,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,2087,0:33.530.696,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D CA 5F 42 85 E3 1F 9E 46 A7 84 D2 CE 41 99 A1… 0,,2091,0:33.531.693,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,2092,0:33.546.698,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C1 FC 6B 5C 0F 4F D6 BE 83 BC 22 FE 85 5B 8B EA… 0,,2096,0:33.547.695,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,2097,0:33.561.700,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2101,0:33.562.697,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,2102,0:33.562.700,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C1 FC 6B 5C 0F 4F D6 BE 83 BC 22 FE 85 5B 8B EA… 0,,2106,0:33.563.697,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,2107,0:33.578.703,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 E1 C6 AD C7 1F 46 95 51 69 F1 0F E8 90 C1 69… 0,,2111,0:33.579.699,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,2112,0:33.593.705,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2116,0:33.594.702,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,2117,0:33.594.705,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 E1 C6 AD C7 1F 46 95 51 69 F1 0F E8 90 C1 69… 0,,2121,0:33.595.702,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,2122,0:33.610.707,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 AC F1 35 B2 BD 1A 21 EB B5 76 4A E3 59 A9 FE… 0,,2126,0:33.611.704,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,2127,0:33.625.709,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2131,0:33.626.706,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,2132,0:33.626.709,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 AC F1 35 B2 BD 1A 21 EB B5 76 4A E3 59 A9 FE… 0,,2136,0:33.627.706,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,2137,0:33.642.711,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 21 4E FD AB 65 F4 65 CD 3F E0 BB 40 52 FD 69… 0,,2141,0:33.643.708,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,2142,0:33.657.714,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2146,0:33.658.710,16.005.041 ms,,,,,[17 SOF],[Frames: 833 - 849] 0,,2147,0:33.674.716,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 E3 1D 93 7E FE AA B1 7B F3 38 72 C2 50 9F 6D… 0,,2151,0:33.675.713,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,2152,0:33.689.718,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2156,0:33.690.715,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,2157,0:33.690.718,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 E3 1D 93 7E FE AA B1 7B F3 38 72 C2 50 9F 6D… 0,,2161,0:33.691.715,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,2162,0:33.706.720,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 7A 07 EA AC B7 AD 05 58 6F C9 51 60 C3 C3 02… 0,,2166,0:33.707.717,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,2167,0:33.721.722,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2171,0:33.722.719,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,2172,0:33.722.723,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 7A 07 EA AC B7 AD 05 58 6F C9 51 60 C3 C3 02… 0,,2176,0:33.723.719,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,2177,0:33.738.725,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 81 1E 1D A4 D7 2C A7 F5 15 81 F7 F9 A6 A2 24… 0,,2181,0:33.739.722,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,2182,0:33.753.727,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2186,0:33.754.724,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,2187,0:33.754.727,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 81 1E 1D A4 D7 2C A7 F5 15 81 F7 F9 A6 A2 24… 0,,2191,0:33.755.724,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,2192,0:33.770.729,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 24 2A 89 38 3F F1 66 68 E5 A6 54 7E 5C EB E8… 0,,2196,0:33.771.726,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,2197,0:33.785.731,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2201,0:33.786.728,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,2202,0:33.786.731,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 24 2A 89 38 3F F1 66 68 E5 A6 54 7E 5C EB E8… 0,,2206,0:33.787.728,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,2207,0:33.802.734,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F FD 56 69 9A 60 BB A0 CD 63 F3 D2 EA C4 97 68… 0,,2211,0:33.803.731,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,2212,0:33.817.736,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2216,0:33.818.733,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,2217,0:33.818.736,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F FD 56 69 9A 60 BB A0 CD 63 F3 D2 EA C4 97 68… 0,,2221,0:33.819.733,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,2222,0:33.834.738,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 53 57 1F 5D BB 31 C4 B1 0E 0C 5F 4B A8 85 FF… 0,,2226,0:33.835.735,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,2227,0:33.849.740,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2231,0:33.850.737,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,2232,0:33.850.740,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 53 57 1F 5D BB 31 C4 B1 0E 0C 5F 4B A8 85 FF… 0,,2236,0:33.851.737,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,2237,0:33.866.743,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 04 53 56 D8 2B C9 B0 8E 94 7C BF 12 B7 D0 2D… 0,,2241,0:33.867.739,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,2242,0:33.881.745,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2246,0:33.882.742,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,2247,0:33.882.745,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 04 53 56 D8 2B C9 B0 8E 94 7C BF 12 B7 D0 2D… 0,,2251,0:33.883.742,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,2252,0:33.898.747,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D4 F7 67 CC D4 AD E1 45 75 5F 8E 45 15 1B 2F 93… 0,,2256,0:33.899.744,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,2257,0:33.913.749,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2261,0:33.914.746,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,2262,0:33.914.749,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D4 F7 67 CC D4 AD E1 45 75 5F 8E 45 15 1B 2F 93… 0,,2266,0:33.915.746,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,2267,0:33.930.751,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5C 5B E7 63 93 87 E2 C0 EC D5 E6 E3 9E 6F CF 01… 0,,2271,0:33.931.748,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,2272,0:33.945.754,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2276,0:33.946.750,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,2277,0:33.946.754,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5C 5B E7 63 93 87 E2 C0 EC D5 E6 E3 9E 6F CF 01… 0,,2281,0:33.947.751,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,2282,0:33.962.756,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 B3 5E B8 2B 1C 2E C2 EF 69 11 DC 69 70 4D 32… 0,,2286,0:33.963.753,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,2287,0:33.977.758,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2291,0:33.978.755,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,2292,0:33.978.758,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 B3 5E B8 2B 1C 2E C2 EF 69 11 DC 69 70 4D 32… 0,,2296,0:33.979.755,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,2297,0:33.994.760,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B 50 F6 5B 45 B2 86 CC B4 EB B2 80 A5 B4 7F C2… 0,,2301,0:33.995.757,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,2302,0:34.009.762,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2306,0:34.010.759,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,2307,0:34.010.763,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B 50 F6 5B 45 B2 86 CC B4 EB B2 80 A5 B4 7F C2… 0,,2311,0:34.011.759,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,2312,0:34.026.765,50.916 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D 54 0B BF 20 6C 1A 08 89 77 2A 9D 69 58 FC BF… 0,,2316,0:34.027.762,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,2317,0:34.041.767,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2321,0:34.042.764,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,2322,0:34.042.767,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D 54 0B BF 20 6C 1A 08 89 77 2A 9D 69 58 FC BF… 0,,2326,0:34.043.764,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,2327,0:34.058.769,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 69 DC 56 86 00 38 21 4F 22 E6 58 6B 2C 0A 4F… 0,,2331,0:34.059.766,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,2332,0:34.073.771,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2336,0:34.074.768,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,2337,0:34.074.771,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 69 DC 56 86 00 38 21 4F 22 E6 58 6B 2C 0A 4F… 0,,2341,0:34.075.768,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,2342,0:34.090.774,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 4B 7D A1 64 EC 87 D4 22 09 2D 0B FF 7F 64 F4… 0,,2346,0:34.091.771,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,2347,0:34.105.776,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2351,0:34.106.773,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,2352,0:34.106.776,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 4B 7D A1 64 EC 87 D4 22 09 2D 0B FF 7F 64 F4… 0,,2356,0:34.107.773,15.004.916 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,2357,0:34.122.778,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 60 8F D0 CB 17 5B F1 C3 06 71 B3 56 0B F7 C8… 0,,2361,0:34.123.775,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,2362,0:34.137.780,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2366,0:34.138.777,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,2367,0:34.138.780,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 60 8F D0 CB 17 5B F1 C3 06 71 B3 56 0B F7 C8… 0,,2371,0:34.139.777,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,2372,0:34.154.783,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FB DB A2 1B D6 7E 8A 16 95 2A 83 E0 6C 2A BF 85… 0,,2376,0:34.155.779,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,2377,0:34.169.785,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2381,0:34.170.781,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,2382,0:34.170.785,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FB DB A2 1B D6 7E 8A 16 95 2A 83 E0 6C 2A BF 85… 0,,2386,0:34.171.782,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,2387,0:34.186.787,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D1 FF F7 9E 82 94 07 C4 0D 8B 6C 79 C2 92 A9 84… 0,,2391,0:34.187.784,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,2392,0:34.201.789,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2396,0:34.202.786,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,2397,0:34.202.789,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D1 FF F7 9E 82 94 07 C4 0D 8B 6C 79 C2 92 A9 84… 0,,2401,0:34.203.786,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,2402,0:34.218.791,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 1C EF 46 A1 E7 3B 90 F3 74 27 42 7C 4E 1E C2… 0,,2406,0:34.219.788,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,2407,0:34.233.793,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2411,0:34.234.790,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,2412,0:34.234.794,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 1C EF 46 A1 E7 3B 90 F3 74 27 42 7C 4E 1E C2… 0,,2416,0:34.235.791,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,2417,0:34.250.796,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 ED 2A C7 6F C4 06 A0 BF 78 89 0E A5 2B DC CA 00… 0,,2421,0:34.251.793,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,2422,0:34.265.798,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2426,0:34.266.795,16.005.041 ms,,,,,[17 SOF],[Frames: 1441 - 1457] 0,,2427,0:34.282.800,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 70 03 63 0D C3 B5 A3 6B F6 B3 18 0E 97 F9 44… 0,,2431,0:34.283.797,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,2432,0:34.297.802,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2436,0:34.298.799,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,2437,0:34.298.803,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 70 03 63 0D C3 B5 A3 6B F6 B3 18 0E 97 F9 44… 0,,2441,0:34.299.799,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,2442,0:34.314.805,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 69 95 89 1B D6 D0 B7 C2 83 95 10 75 06 44 FE… 0,,2446,0:34.315.802,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,2447,0:34.329.807,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2451,0:34.330.804,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,2452,0:34.330.807,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 69 95 89 1B D6 D0 B7 C2 83 95 10 75 06 44 FE… 0,,2456,0:34.331.804,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,2457,0:34.346.809,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 82 BE 6A 45 AF 82 B0 CA 63 42 29 72 D3 6C 85… 0,,2461,0:34.347.806,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,2462,0:34.361.811,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2466,0:34.362.808,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,2467,0:34.362.811,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 82 BE 6A 45 AF 82 B0 CA 63 42 29 72 D3 6C 85… 0,,2471,0:34.363.808,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,2472,0:34.378.814,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 6C F1 3D 3B 97 CB 0F 86 AB 1B EC 7D 03 4E 4E… 0,,2476,0:34.379.811,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,2477,0:34.393.816,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2481,0:34.394.813,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,2482,0:34.394.816,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 6C F1 3D 3B 97 CB 0F 86 AB 1B EC 7D 03 4E 4E… 0,,2486,0:34.395.813,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,2487,0:34.410.818,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 2D ED CF 5B E8 BF 21 44 84 25 9D 0B 1E 51 48… 0,,2491,0:34.411.815,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,2492,0:34.425.820,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2496,0:34.426.817,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,2497,0:34.426.820,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9A 2D ED CF 5B E8 BF 21 44 84 25 9D 0B 1E 51 48… 0,,2501,0:34.427.817,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,2502,0:34.442.823,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 24 59 DF 7D D9 D5 51 C7 34 62 A7 94 65 9D 09… 0,,2506,0:34.443.819,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,2507,0:34.457.825,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2511,0:34.458.821,2.833 us,,,,,[1 SOF],[Frame: 1633] 0,,2512,0:34.458.825,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 24 59 DF 7D D9 D5 51 C7 34 62 A7 94 65 9D 09… 0,,2516,0:34.459.822,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,2517,0:34.474.827,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 CB 54 3E 5D 9B 5C 29 A4 9C 64 3F 24 2A CC 2E… 0,,2521,0:34.475.824,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,2522,0:34.489.829,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2526,0:34.490.826,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,2527,0:34.490.829,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 CB 54 3E 5D 9B 5C 29 A4 9C 64 3F 24 2A CC 2E… 0,,2531,0:34.491.826,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,2532,0:34.506.831,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 13 92 83 E9 70 31 E0 3D 04 E9 6C 6B C3 63 6B… 0,,2536,0:34.507.828,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,2537,0:34.521.833,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2541,0:34.522.830,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,2542,0:34.522.834,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 13 92 83 E9 70 31 E0 3D 04 E9 6C 6B C3 63 6B… 0,,2546,0:34.523.831,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,2547,0:34.538.836,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 35 6F 7D 86 79 6F EE 64 48 75 4B 36 EA 98 A7 4E… 0,,2551,0:34.539.833,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,2552,0:34.553.838,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2556,0:34.554.835,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,2557,0:34.554.838,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 35 6F 7D 86 79 6F EE 64 48 75 4B 36 EA 98 A7 4E… 0,,2561,0:34.555.835,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,2562,0:34.570.840,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 2B 3C 15 F4 4F 30 5B 03 4E 04 31 31 33 11 CE… 0,,2566,0:34.571.837,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,2567,0:34.585.842,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2571,0:34.586.839,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,2572,0:34.586.843,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 2B 3C 15 F4 4F 30 5B 03 4E 04 31 31 33 11 CE… 0,,2576,0:34.587.839,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,2577,0:34.602.845,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 21 45 2C 94 DB D3 A3 39 E9 5A 44 A6 BC 6C D9 4E… 0,,2581,0:34.603.842,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,2582,0:34.617.847,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2586,0:34.618.844,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,2587,0:34.618.847,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 21 45 2C 94 DB D3 A3 39 E9 5A 44 A6 BC 6C D9 4E… 0,,2591,0:34.619.844,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,2592,0:34.634.849,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 64 46 0B FF 18 F4 46 44 73 61 55 E3 02 CE 9B 46… 0,,2596,0:34.635.846,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,2597,0:34.649.851,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2601,0:34.650.848,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,2602,0:34.650.851,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 64 46 0B FF 18 F4 46 44 73 61 55 E3 02 CE 9B 46… 0,,2606,0:34.651.848,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,2607,0:34.666.854,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 2B DE 82 16 9E D3 C1 05 DB D7 C3 28 C4 E4 D8… 0,,2611,0:34.667.850,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,2612,0:34.681.856,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2616,0:34.682.853,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,2617,0:34.682.856,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 2B DE 82 16 9E D3 C1 05 DB D7 C3 28 C4 E4 D8… 0,,2621,0:34.683.853,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,2622,0:34.698.858,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 8C 22 16 FC 1D 79 E6 FF EC DC B5 03 F2 95 FC… 0,,2626,0:34.699.855,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,2627,0:34.713.860,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2631,0:34.714.857,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,2632,0:34.714.860,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 8C 22 16 FC 1D 79 E6 FF EC DC B5 03 F2 95 FC… 0,,2636,0:34.715.857,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,2637,0:34.730.862,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 BE CB C5 2A 35 AC 6F FA 8B 3E 5A 2E C2 EA 41… 0,,2641,0:34.731.859,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,2642,0:34.745.865,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2646,0:34.746.861,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,2647,0:34.746.865,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C6 BE CB C5 2A 35 AC 6F FA 8B 3E 5A 2E C2 EA 41… 0,,2651,0:34.747.862,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,2652,0:34.762.867,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 29 8C 92 43 A3 87 DB 17 D8 91 0B DC 31 29 F8… 0,,2656,0:34.763.864,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,2657,0:34.777.869,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2661,0:34.778.866,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,2662,0:34.778.869,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C6 29 8C 92 43 A3 87 DB 17 D8 91 0B DC 31 29 F8… 0,,2666,0:34.779.866,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,2667,0:34.794.871,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E E9 09 57 76 6C 4F D6 62 61 A7 E6 4C 67 E4 DB… 0,,2671,0:34.795.868,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,2672,0:34.809.873,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2676,0:34.810.870,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,2677,0:34.810.874,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E E9 09 57 76 6C 4F D6 62 61 A7 E6 4C 67 E4 DB… 0,,2681,0:34.811.870,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,2682,0:34.826.876,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC 48 25 24 DE 7E 6F B8 76 B4 9F 06 AB 4C 9C D7… 0,,2686,0:34.827.873,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,2687,0:34.841.878,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2691,0:34.842.875,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,2692,0:34.842.878,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC 48 25 24 DE 7E 6F B8 76 B4 9F 06 AB 4C 9C D7… 0,,2696,0:34.843.875,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,2697,0:34.858.880,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E 29 63 B6 D9 AA EF 34 76 DF 79 98 3F 8C B5 D2… 0,,2701,0:34.859.877,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,2702,0:34.873.882,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2706,0:34.874.879,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,2707,0:34.874.882,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E 29 63 B6 D9 AA EF 34 76 DF 79 98 3F 8C B5 D2… 0,,2711,0:34.875.879,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,2712,0:34.890.885,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3A 08 E5 D1 BA 9F 4C 1A 5B A7 55 11 23 89 CB 80… 0,,2716,0:34.891.882,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,2717,0:34.905.887,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2721,0:34.906.884,16.005.041 ms,,,,,[17 SOF],[Frames: 33 - 49] 0,,2722,0:34.922.889,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D8 66 C2 6C 83 00 2F 89 96 CB 16 EB 52 43 68 CF… 0,,2726,0:34.923.886,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,2727,0:34.937.891,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2731,0:34.938.888,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,2732,0:34.938.891,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D8 66 C2 6C 83 00 2F 89 96 CB 16 EB 52 43 68 CF… 0,,2736,0:34.939.888,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,2737,0:34.954.894,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 AC AB 74 98 6E 61 27 BE F7 69 08 76 B4 73 6B… 0,,2741,0:34.955.890,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,2742,0:34.969.896,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2746,0:34.970.893,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,2747,0:34.970.896,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 AC AB 74 98 6E 61 27 BE F7 69 08 76 B4 73 6B… 0,,2751,0:34.971.893,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,2752,0:34.986.898,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B 92 6F 5B 63 96 83 E3 45 98 DE 59 D9 11 EB 87… 0,,2756,0:34.987.895,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,2757,0:35.001.900,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2761,0:35.002.897,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,2762,0:35.002.900,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B 92 6F 5B 63 96 83 E3 45 98 DE 59 D9 11 EB 87… 0,,2766,0:35.003.897,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,2767,0:35.018.902,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 1E 9E 7E 0B 7D A0 ED 5C F2 84 6C 52 28 7A 7A… 0,,2771,0:35.019.899,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,2772,0:35.033.905,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2776,0:35.034.901,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,2777,0:35.034.905,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 1E 9E 7E 0B 7D A0 ED 5C F2 84 6C 52 28 7A 7A… 0,,2781,0:35.035.902,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,2782,0:35.050.907,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C5 64 13 73 C3 A0 E8 1B AF 0A 4B 39 D4 54 32 26… 0,,2786,0:35.051.904,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,2787,0:35.065.909,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2791,0:35.066.906,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,2792,0:35.066.909,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C5 64 13 73 C3 A0 E8 1B AF 0A 4B 39 D4 54 32 26… 0,,2796,0:35.067.906,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,2797,0:35.082.911,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 F1 3B A7 73 C2 7D A6 12 13 70 57 52 BB 7C 9B… 0,,2801,0:35.083.908,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,2802,0:35.097.913,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2806,0:35.098.910,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,2807,0:35.098.914,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 F1 3B A7 73 C2 7D A6 12 13 70 57 52 BB 7C 9B… 0,,2811,0:35.099.910,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,2812,0:35.114.916,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 4F 46 B4 93 4E 56 DA 7C 82 E3 BC 6E 86 72 6C… 0,,2816,0:35.115.913,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,2817,0:35.129.918,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2821,0:35.130.915,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,2822,0:35.130.918,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 4F 46 B4 93 4E 56 DA 7C 82 E3 BC 6E 86 72 6C… 0,,2826,0:35.131.915,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,2827,0:35.146.920,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 D7 0D 2B F8 1E 70 05 F4 8C 86 C0 6F C4 4B 03… 0,,2831,0:35.147.917,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,2832,0:35.161.922,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2836,0:35.162.919,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,2837,0:35.162.922,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 D7 0D 2B F8 1E 70 05 F4 8C 86 C0 6F C4 4B 03… 0,,2841,0:35.163.919,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,2842,0:35.178.925,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 B9 90 89 DB 3C 2C FF D7 89 CB 42 AA 78 29 6C… 0,,2846,0:35.179.922,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,2847,0:35.193.927,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2851,0:35.194.924,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,2852,0:35.194.927,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 B9 90 89 DB 3C 2C FF D7 89 CB 42 AA 78 29 6C… 0,,2856,0:35.195.924,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,2857,0:35.210.929,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 91 DC FC B6 7E 01 62 41 D1 98 72 5B 65 D7 18… 0,,2861,0:35.211.926,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,2862,0:35.225.931,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2866,0:35.226.928,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,2867,0:35.226.931,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 91 DC FC B6 7E 01 62 41 D1 98 72 5B 65 D7 18… 0,,2871,0:35.227.928,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,2872,0:35.242.934,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E9 B2 7A 96 3B 6C 66 D7 1C 82 B0 CA 57 1A 69 48… 0,,2876,0:35.243.930,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,2877,0:35.257.936,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2881,0:35.258.933,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,2882,0:35.258.936,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E9 B2 7A 96 3B 6C 66 D7 1C 82 B0 CA 57 1A 69 48… 0,,2886,0:35.259.933,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,2887,0:35.274.938,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B 26 D9 C6 7C B0 78 5B CE 0D 7C CC 51 3C EB 3C… 0,,2891,0:35.275.935,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,2892,0:35.289.940,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2896,0:35.290.937,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,2897,0:35.290.940,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B 26 D9 C6 7C B0 78 5B CE 0D 7C CC 51 3C EB 3C… 0,,2901,0:35.291.937,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,2902,0:35.306.942,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0C 7F 8A 6C 7C AA 59 EA 84 1A 60 F0 5A 3F 61 91… 0,,2906,0:35.307.939,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,2907,0:35.321.945,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2911,0:35.322.941,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,2912,0:35.322.945,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0C 7F 8A 6C 7C AA 59 EA 84 1A 60 F0 5A 3F 61 91… 0,,2916,0:35.323.942,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,2917,0:35.338.947,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 17 51 85 99 58 4A 89 37 2B 0C 91 E1 3D 2C 2C E9… 0,,2921,0:35.339.944,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,2922,0:35.353.949,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2926,0:35.354.946,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,2927,0:35.354.949,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 17 51 85 99 58 4A 89 37 2B 0C 91 E1 3D 2C 2C E9… 0,,2931,0:35.355.946,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,2932,0:35.370.951,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AB B0 BD 54 D0 BE 6F 9B C4 02 42 5C 17 5A 74 6A… 0,,2936,0:35.371.948,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,2937,0:35.385.953,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2941,0:35.386.950,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,2942,0:35.386.954,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AB B0 BD 54 D0 BE 6F 9B C4 02 42 5C 17 5A 74 6A… 0,,2946,0:35.387.950,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,2947,0:35.402.956,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF 06 30 30 A2 1C 73 A9 B8 3D 37 37 8D BC B5 F7… 0,,2951,0:35.403.953,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,2952,0:35.417.958,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2956,0:35.418.955,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,2957,0:35.418.958,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF 06 30 30 A2 1C 73 A9 B8 3D 37 37 8D BC B5 F7… 0,,2961,0:35.419.955,15.004.916 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,2962,0:35.434.960,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AD 61 D7 99 B1 FD DC D1 C8 62 7A BC 83 F3 B3 7B… 0,,2966,0:35.435.957,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,2967,0:35.449.962,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2971,0:35.450.959,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,2972,0:35.450.962,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AD 61 D7 99 B1 FD DC D1 C8 62 7A BC 83 F3 B3 7B… 0,,2976,0:35.451.959,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,2977,0:35.466.965,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 BE 91 BB 19 68 85 83 8F 30 FB 66 9D 19 4F C7… 0,,2981,0:35.467.962,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,2982,0:35.481.967,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2986,0:35.482.964,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,2987,0:35.482.967,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 BE 91 BB 19 68 85 83 8F 30 FB 66 9D 19 4F C7… 0,,2991,0:35.483.964,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,2992,0:35.498.969,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 0E EF FA 4C 20 07 83 F3 B9 FE 72 01 57 6D 5C… 0,,2996,0:35.499.966,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,2997,0:35.513.971,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3001,0:35.514.968,16.005.041 ms,,,,,[17 SOF],[Frames: 641 - 657] 0,,3002,0:35.530.974,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 55 BA 2E 4E 75 8F 38 E8 DA BC C9 70 43 76 0B… 0,,3006,0:35.531.970,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,3007,0:35.545.976,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3011,0:35.546.973,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,3012,0:35.546.976,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 55 BA 2E 4E 75 8F 38 E8 DA BC C9 70 43 76 0B… 0,,3016,0:35.547.973,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,3017,0:35.562.978,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 CD 5B FF DA 80 1A 2D E0 BB 2A 99 46 6F E3 36… 0,,3021,0:35.563.975,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,3022,0:35.577.980,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3026,0:35.578.977,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,3027,0:35.578.980,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 CD 5B FF DA 80 1A 2D E0 BB 2A 99 46 6F E3 36… 0,,3031,0:35.579.977,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,3032,0:35.594.982,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA E1 AF FE FE FA FB 85 8F 9A 5B 8A 9C AC F8 3D… 0,,3036,0:35.595.979,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,3037,0:35.609.985,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3041,0:35.610.981,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,3042,0:35.610.985,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA E1 AF FE FE FA FB 85 8F 9A 5B 8A 9C AC F8 3D… 0,,3046,0:35.611.982,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,3047,0:35.626.987,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 34 58 B7 16 BE 8D 33 D1 1D 6F C7 00 1A 1E 26… 0,,3051,0:35.627.984,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,3052,0:35.641.989,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3056,0:35.642.986,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,3057,0:35.642.989,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 34 58 B7 16 BE 8D 33 D1 1D 6F C7 00 1A 1E 26… 0,,3061,0:35.643.986,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,3062,0:35.658.991,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 89 D4 B6 A8 6F 14 CD 3F 43 E3 6B 12 68 9B F9 3C… 0,,3066,0:35.659.988,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,3067,0:35.673.993,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3071,0:35.674.990,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,3072,0:35.674.994,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 89 D4 B6 A8 6F 14 CD 3F 43 E3 6B 12 68 9B F9 3C… 0,,3076,0:35.675.990,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,3077,0:35.690.996,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E8 11 24 19 1A BA 1C 72 BB CB B9 93 20 3E 7B C8… 0,,3081,0:35.691.993,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,3082,0:35.705.998,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3086,0:35.706.995,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,3087,0:35.706.998,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E8 11 24 19 1A BA 1C 72 BB CB B9 93 20 3E 7B C8… 0,,3091,0:35.707.995,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,3092,0:35.723.000,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 F8 34 A3 4E E9 33 11 08 18 03 4D 0C 38 B1 6A… 0,,3096,0:35.723.997,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,3097,0:35.738.002,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3101,0:35.738.999,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,3102,0:35.739.002,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 F8 34 A3 4E E9 33 11 08 18 03 4D 0C 38 B1 6A… 0,,3106,0:35.739.999,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,3107,0:35.755.005,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A 8E D4 41 0D E0 23 44 05 AD 4E 5D 5B AB 37 63… 0,,3111,0:35.756.002,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,3112,0:35.770.007,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3116,0:35.771.004,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,3117,0:35.771.007,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A 8E D4 41 0D E0 23 44 05 AD 4E 5D 5B AB 37 63… 0,,3121,0:35.772.004,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,3122,0:35.787.009,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 CA 20 38 AA 23 3B 38 91 D2 0E FA 33 8C 7B 5A… 0,,3126,0:35.788.006,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,3127,0:35.802.011,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3131,0:35.803.008,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,3132,0:35.803.011,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 CA 20 38 AA 23 3B 38 91 D2 0E FA 33 8C 7B 5A… 0,,3136,0:35.804.008,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,3137,0:35.819.014,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 ED 62 CE D8 AC EE 00 B2 58 E8 35 89 71 36 41… 0,,3141,0:35.820.010,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,3142,0:35.834.016,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3146,0:35.835.013,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,3147,0:35.835.016,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 ED 62 CE D8 AC EE 00 B2 58 E8 35 89 71 36 41… 0,,3151,0:35.836.013,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,3152,0:35.851.018,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B3 E4 C5 74 8F 0F 96 56 70 27 A5 11 6A CA 6B 7B… 0,,3156,0:35.852.015,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,3157,0:35.866.020,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3161,0:35.867.017,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,3162,0:35.867.020,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B3 E4 C5 74 8F 0F 96 56 70 27 A5 11 6A CA 6B 7B… 0,,3166,0:35.868.017,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,3167,0:35.883.023,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 C9 F0 81 2C 79 0B A9 E6 AF 0A 88 78 AB 01 38… 0,,3171,0:35.884.019,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,3172,0:35.898.025,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3176,0:35.899.021,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,3177,0:35.899.025,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 C9 F0 81 2C 79 0B A9 E6 AF 0A 88 78 AB 01 38… 0,,3181,0:35.900.022,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,3182,0:35.915.027,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E C6 0C 60 B2 EB 94 DA 94 3B E9 4B 6C BC 63 0F… 0,,3186,0:35.916.024,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,3187,0:35.930.029,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3191,0:35.931.026,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,3192,0:35.931.029,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E C6 0C 60 B2 EB 94 DA 94 3B E9 4B 6C BC 63 0F… 0,,3196,0:35.932.026,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,3197,0:35.947.031,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 84 F8 D7 DF B8 C9 30 80 FF 1D B4 99 66 5F 0D 23… 0,,3201,0:35.948.028,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,3202,0:35.962.033,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3206,0:35.963.030,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,3207,0:35.963.034,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 84 F8 D7 DF B8 C9 30 80 FF 1D B4 99 66 5F 0D 23… 0,,3211,0:35.964.030,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,3212,0:35.979.036,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C B4 58 4B 1B 7D F8 DC 80 B9 F8 E8 5C 80 6A 97… 0,,3216,0:35.980.033,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,3217,0:35.994.038,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3221,0:35.995.035,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,3222,0:35.995.038,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C B4 58 4B 1B 7D F8 DC 80 B9 F8 E8 5C 80 6A 97… 0,,3226,0:35.996.035,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,3227,0:36.011.040,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 28 26 F2 51 9D 76 72 1D 9E 1F 9A BE 8B 9F E1 EA… 0,,3231,0:36.012.037,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,3232,0:36.026.042,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3236,0:36.027.039,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,3237,0:36.027.043,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 28 26 F2 51 9D 76 72 1D 9E 1F 9A BE 8B 9F E1 EA… 0,,3241,0:36.028.039,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,3242,0:36.043.045,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 19 98 6A D7 85 35 EE 8D 03 16 A0 DF 47 90 0F… 0,,3246,0:36.044.042,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,3247,0:36.058.047,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3251,0:36.059.044,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,3252,0:36.059.047,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 19 98 6A D7 85 35 EE 8D 03 16 A0 DF 47 90 0F… 0,,3256,0:36.060.044,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,3257,0:36.075.049,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D3 81 3C 6B 32 27 E5 94 39 98 10 5A 6F 28 EB 09… 0,,3261,0:36.076.046,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,3262,0:36.090.051,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3266,0:36.091.048,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,3267,0:36.091.051,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D3 81 3C 6B 32 27 E5 94 39 98 10 5A 6F 28 EB 09… 0,,3271,0:36.092.048,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,3272,0:36.107.054,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF 6A 22 A7 6F 0B 68 F7 8A 3E B3 4F DA C6 80 24… 0,,3276,0:36.108.050,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,3277,0:36.122.056,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3281,0:36.123.052,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,3282,0:36.123.056,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF 6A 22 A7 6F 0B 68 F7 8A 3E B3 4F DA C6 80 24… 0,,3286,0:36.124.053,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,3287,0:36.139.058,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B 81 A1 77 E9 C3 A0 41 32 44 2E DC 3A 32 DA 9A… 0,,3291,0:36.140.055,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,3292,0:36.154.060,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3296,0:36.155.057,16.005.041 ms,,,,,[17 SOF],[Frames: 1281 - 1297] 0,,3297,0:36.171.062,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 81 2D 84 83 A1 75 B6 B6 8F 0A D8 E8 3E F4 B4… 0,,3301,0:36.172.059,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,3302,0:36.186.064,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3306,0:36.187.061,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,3307,0:36.187.065,50.895 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 81 2D 84 83 A1 75 B6 B6 8F 0A D8 E8 3E F4 B4… 0,,3311,0:36.188.062,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,3312,0:36.203.067,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 C4 34 6F DB 27 0B C0 61 48 90 8E 27 2A 1A E7… 0,,3316,0:36.204.064,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,3317,0:36.218.069,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3321,0:36.219.066,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,3322,0:36.219.069,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 C4 34 6F DB 27 0B C0 61 48 90 8E 27 2A 1A E7… 0,,3326,0:36.220.066,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,3327,0:36.235.071,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3D 5E 13 85 CD 7A E7 51 2D A0 0E 17 EB 9C 83 00… 0,,3331,0:36.236.068,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,3332,0:36.250.073,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3336,0:36.251.070,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,3337,0:36.251.074,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3D 5E 13 85 CD 7A E7 51 2D A0 0E 17 EB 9C 83 00… 0,,3341,0:36.252.070,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,3342,0:36.267.076,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 79 4B E2 7F 7B F6 64 4F D4 8C 78 33 1B 63 E5… 0,,3346,0:36.268.073,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,3347,0:36.282.078,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3351,0:36.283.075,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,3352,0:36.283.078,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 79 4B E2 7F 7B F6 64 4F D4 8C 78 33 1B 63 E5… 0,,3356,0:36.284.075,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,3357,0:36.299.080,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE FB AB 46 B9 12 68 FD B6 7E 69 F2 58 7F 7D 5F… 0,,3361,0:36.300.077,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,3362,0:36.314.082,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3366,0:36.315.079,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,3367,0:36.315.082,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE FB AB 46 B9 12 68 FD B6 7E 69 F2 58 7F 7D 5F… 0,,3371,0:36.316.079,15.004.916 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,3372,0:36.331.085,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 63 8B 7A 94 B9 55 B8 B2 6D 92 69 2C 8E DD 12… 0,,3376,0:36.332.082,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,3377,0:36.346.087,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3381,0:36.347.084,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,3382,0:36.347.087,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 63 8B 7A 94 B9 55 B8 B2 6D 92 69 2C 8E DD 12… 0,,3386,0:36.348.084,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,3387,0:36.363.089,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 1E 56 F6 A7 EE C2 1E 95 E1 6B 70 9C 6E A8 15… 0,,3391,0:36.364.086,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,3392,0:36.378.091,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3396,0:36.379.088,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,3397,0:36.379.091,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A 1E 56 F6 A7 EE C2 1E 95 E1 6B 70 9C 6E A8 15… 0,,3401,0:36.380.088,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,3402,0:36.395.094,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 77 F1 F7 FD BB 77 D3 1F 6D 0B 71 99 6F 44 5F… 0,,3406,0:36.396.090,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,3407,0:36.410.096,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3411,0:36.411.092,2.833 us,,,,,[1 SOF],[Frame: 1537] 0,,3412,0:36.411.096,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 77 F1 F7 FD BB 77 D3 1F 6D 0B 71 99 6F 44 5F… 0,,3416,0:36.412.093,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,3417,0:36.427.098,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E 0A 27 0F 6A 8D 3D F8 6E 3A 44 A2 C2 73 2E 0D… 0,,3421,0:36.428.095,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,3422,0:36.442.100,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3426,0:36.443.097,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,3427,0:36.443.100,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E 0A 27 0F 6A 8D 3D F8 6E 3A 44 A2 C2 73 2E 0D… 0,,3431,0:36.444.097,15.004.916 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,3432,0:36.459.102,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB 08 57 77 E9 17 D4 0C 5E BB 02 11 F6 40 D4 E5… 0,,3436,0:36.460.099,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,3437,0:36.474.104,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3441,0:36.475.101,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,3442,0:36.475.105,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB 08 57 77 E9 17 D4 0C 5E BB 02 11 F6 40 D4 E5… 0,,3446,0:36.476.102,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,3447,0:36.491.107,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 40 E5 53 C2 00 5E 17 B3 41 D8 CE 58 07 30 47 86… 0,,3451,0:36.492.104,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,3452,0:36.506.109,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3456,0:36.507.106,2.833 us,,,,,[1 SOF],[Frame: 1633] 0,,3457,0:36.507.109,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 40 E5 53 C2 00 5E 17 B3 41 D8 CE 58 07 30 47 86… 0,,3461,0:36.508.106,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,3462,0:36.523.111,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 40 9D 5A 99 57 14 6F 32 BA F6 7E 37 2D D5 5A… 0,,3466,0:36.524.108,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,3467,0:36.538.113,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3471,0:36.539.110,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,3472,0:36.539.113,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 40 9D 5A 99 57 14 6F 32 BA F6 7E 37 2D D5 5A… 0,,3476,0:36.540.110,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,3477,0:36.555.116,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 A4 3D 01 D2 DF 2C 78 F8 84 A6 D9 90 C0 34 F1… 0,,3481,0:36.556.113,14.004.750 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,3482,0:36.570.118,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3486,0:36.571.115,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,3487,0:36.571.118,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 A4 3D 01 D2 DF 2C 78 F8 84 A6 D9 90 C0 34 F1… 0,,3491,0:36.572.115,15.004.916 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,3492,0:36.587.120,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 49 D2 9A 26 2D B2 FD 86 E0 38 E2 44 2E 06 78… 0,,3496,0:36.588.117,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,3497,0:36.602.122,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3501,0:36.603.119,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,3502,0:36.603.122,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 49 D2 9A 26 2D B2 FD 86 E0 38 E2 44 2E 06 78… 0,,3506,0:36.604.119,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,3507,0:36.619.125,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 35 DB 35 DA 85 44 34 2F 71 62 3C D9 01 E4 5C 2B… 0,,3511,0:36.620.121,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,3512,0:36.634.127,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3516,0:36.635.124,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,3517,0:36.635.127,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 35 DB 35 DA 85 44 34 2F 71 62 3C D9 01 E4 5C 2B… 0,,3521,0:36.636.124,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,3522,0:36.651.129,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 46 CE 1D 4D 73 C4 AA D8 95 44 95 E0 18 28 E4… 0,,3526,0:36.652.126,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,3527,0:36.666.131,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3531,0:36.667.128,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,3532,0:36.667.131,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 46 CE 1D 4D 73 C4 AA D8 95 44 95 E0 18 28 E4… 0,,3536,0:36.668.128,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,3537,0:36.683.133,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 88 32 FC 0F D6 6C 46 CE 19 6F 78 B5 ED 21 A9 83… 0,,3541,0:36.684.130,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,3542,0:36.698.136,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3546,0:36.699.132,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,3547,0:36.699.136,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 88 32 FC 0F D6 6C 46 CE 19 6F 78 B5 ED 21 A9 83… 0,,3551,0:36.700.133,15.004.916 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,3552,0:36.715.138,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A 5F DF F8 FD 99 DC C4 78 66 0F 33 89 4F AA F6… 0,,3556,0:36.716.135,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,3557,0:36.730.140,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3561,0:36.731.137,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,3562,0:36.731.140,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A 5F DF F8 FD 99 DC C4 78 66 0F 33 89 4F AA F6… 0,,3566,0:36.732.137,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,3567,0:36.747.142,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 CB 14 EF 50 12 A8 0C 6A BB 2F 30 8B 9C ED 90… 0,,3571,0:36.748.139,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,3572,0:36.762.144,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3576,0:36.763.141,16.005.041 ms,,,,,[17 SOF],[Frames: 1889 - 1905] 0,,3577,0:36.779.147,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 E6 F6 76 CF 6E D3 80 8C 43 6F 85 20 26 AF B7… 0,,3581,0:36.780.144,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,3582,0:36.794.149,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3586,0:36.795.146,2.833 us,,,,,[1 SOF],[Frame: 1921] 0,,3587,0:36.795.149,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C6 E6 F6 76 CF 6E D3 80 8C 43 6F 85 20 26 AF B7… 0,,3591,0:36.796.146,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,3592,0:36.811.151,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D 91 58 F8 5A F2 F0 8A 30 34 85 00 CD 59 01 E2… 0,,3596,0:36.812.148,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,3597,0:36.826.153,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3601,0:36.827.150,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,3602,0:36.827.153,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 91 58 F8 5A F2 F0 8A 30 34 85 00 CD 59 01 E2… 0,,3606,0:36.828.150,15.004.916 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,3607,0:36.843.156,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 8E D6 A5 95 63 EC 64 86 44 DF 7B 11 11 C5 DD… 0,,3611,0:36.844.153,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,3612,0:36.858.158,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3616,0:36.859.155,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,3617,0:36.859.158,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 26 8E D6 A5 95 63 EC 64 86 44 DF 7B 11 11 C5 DD… 0,,3621,0:36.860.155,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,3622,0:36.875.160,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 2A 3B 2F 93 C8 04 09 58 C2 C3 B2 EF 7E 93 3B… 0,,3626,0:36.876.157,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,3627,0:36.890.162,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3631,0:36.891.159,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,3632,0:36.891.162,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 2A 3B 2F 93 C8 04 09 58 C2 C3 B2 EF 7E 93 3B… 0,,3636,0:36.892.159,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,3637,0:36.907.165,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 E7 61 D7 09 D8 11 93 E3 53 FC F4 52 4B AB C0… 0,,3641,0:36.908.161,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,3642,0:36.922.167,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3646,0:36.923.164,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,3647,0:36.923.167,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 E7 61 D7 09 D8 11 93 E3 53 FC F4 52 4B AB C0… 0,,3651,0:36.924.164,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,3652,0:36.939.169,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D 59 45 3A 78 41 FE 3D F0 6B 61 C8 B3 77 1D 64… 0,,3656,0:36.940.166,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,3657,0:36.954.171,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3661,0:36.955.168,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,3662,0:36.955.171,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D 59 45 3A 78 41 FE 3D F0 6B 61 C8 B3 77 1D 64… 0,,3666,0:36.956.168,15.004.916 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,3667,0:36.971.173,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8B 62 B4 06 AF 8D 71 0F 03 00 C6 16 83 B2 FB 1D… 0,,3671,0:36.972.170,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,3672,0:36.986.176,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3676,0:36.987.172,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,3677,0:36.987.176,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8B 62 B4 06 AF 8D 71 0F 03 00 C6 16 83 B2 FB 1D… 0,,3681,0:36.988.173,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,3682,0:37.003.178,50.916 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 50 6E 9D FF 30 A3 C0 20 17 76 AB FF CF D2 46 E5… 0,,3686,0:37.004.175,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,3687,0:37.018.180,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3691,0:37.019.177,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,3692,0:37.019.180,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 50 6E 9D FF 30 A3 C0 20 17 76 AB FF CF D2 46 E5… 0,,3696,0:37.020.177,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,3697,0:37.035.182,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A2 A1 57 8C 42 65 B1 B5 15 5F 52 D1 41 FA 5C 19… 0,,3701,0:37.036.179,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,3702,0:37.050.184,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3706,0:37.051.181,2.833 us,,,,,[1 SOF],[Frame: 129] 0,,3707,0:37.051.185,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A2 A1 57 8C 42 65 B1 B5 15 5F 52 D1 41 FA 5C 19… 0,,3711,0:37.052.181,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,3712,0:37.067.187,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD D2 F9 31 20 27 F8 41 D5 D0 3F 7A F7 8E 22 06… 0,,3716,0:37.068.184,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,3717,0:37.082.189,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3721,0:37.083.186,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,3722,0:37.083.189,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD D2 F9 31 20 27 F8 41 D5 D0 3F 7A F7 8E 22 06… 0,,3726,0:37.084.186,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,3727,0:37.099.191,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 7B 53 48 41 4B 90 45 62 14 0C 6C 81 A7 BC 0B… 0,,3731,0:37.100.188,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,3732,0:37.114.193,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3736,0:37.115.190,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,3737,0:37.115.193,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 7B 53 48 41 4B 90 45 62 14 0C 6C 81 A7 BC 0B… 0,,3741,0:37.116.190,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,3742,0:37.131.196,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D D1 9C 75 D0 DE 85 C0 4B AD 9A 85 24 62 A9 0F… 0,,3746,0:37.132.193,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,3747,0:37.146.198,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3751,0:37.147.195,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,3752,0:37.147.198,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D D1 9C 75 D0 DE 85 C0 4B AD 9A 85 24 62 A9 0F… 0,,3756,0:37.148.195,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,3757,0:37.163.200,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 91 C7 A0 56 0D C4 D7 4F 42 7E 64 A9 0A E0 EA… 0,,3761,0:37.164.197,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,3762,0:37.178.202,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3766,0:37.179.199,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,3767,0:37.179.202,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 91 C7 A0 56 0D C4 D7 4F 42 7E 64 A9 0A E0 EA… 0,,3771,0:37.180.199,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,3772,0:37.195.205,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 43 08 37 D6 B8 AC EF 46 86 24 C0 8A D2 09 19 CC… 0,,3776,0:37.196.201,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,3777,0:37.210.207,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3781,0:37.211.204,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,3782,0:37.211.207,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 43 08 37 D6 B8 AC EF 46 86 24 C0 8A D2 09 19 CC… 0,,3786,0:37.212.204,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,3787,0:37.227.209,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF FB CA B4 0C CC 03 B1 B4 3F A5 98 B7 14 58 3A… 0,,3791,0:37.228.206,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,3792,0:37.242.211,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3796,0:37.243.208,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,3797,0:37.243.211,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF FB CA B4 0C CC 03 B1 B4 3F A5 98 B7 14 58 3A… 0,,3801,0:37.244.208,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,3802,0:37.259.213,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA C4 32 36 7F 78 CB 75 C9 E2 55 25 F5 79 18 4E… 0,,3806,0:37.260.210,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,3807,0:37.274.216,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3811,0:37.275.212,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,3812,0:37.275.216,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA C4 32 36 7F 78 CB 75 C9 E2 55 25 F5 79 18 4E… 0,,3816,0:37.276.213,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,3817,0:37.291.218,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EA DF 2E 6A 5B E4 DA 2D 8B 36 6D 19 98 CD 62 A9… 0,,3821,0:37.292.215,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,3822,0:37.306.220,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3826,0:37.307.217,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,3827,0:37.307.220,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EA DF 2E 6A 5B E4 DA 2D 8B 36 6D 19 98 CD 62 A9… 0,,3831,0:37.308.217,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,3832,0:37.323.222,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 22 1B 4D E7 E4 EA E1 29 1B C5 95 7D 21 C0 87… 0,,3836,0:37.324.219,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,3837,0:37.338.224,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3841,0:37.339.221,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,3842,0:37.339.225,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 22 1B 4D E7 E4 EA E1 29 1B C5 95 7D 21 C0 87… 0,,3846,0:37.340.221,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,3847,0:37.355.227,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 52 27 23 31 18 1F 24 1D B7 8C 59 3C 22 71 45 71… 0,,3851,0:37.356.224,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,3852,0:37.370.229,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3856,0:37.371.226,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,3857,0:37.371.229,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 52 27 23 31 18 1F 24 1D B7 8C 59 3C 22 71 45 71… 0,,3861,0:37.372.226,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,3862,0:37.387.231,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C9 79 F8 84 AE 61 CF 9E 6A 83 A2 2E 59 1E B8 2E… 0,,3866,0:37.388.228,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,3867,0:37.402.233,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3871,0:37.403.230,16.005.041 ms,,,,,[17 SOF],[Frames: 481 - 497] 0,,3872,0:37.419.236,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 13 31 27 BB 58 66 50 8E 1C A6 E4 98 D1 13 19… 0,,3876,0:37.420.233,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,3877,0:37.434.238,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3881,0:37.435.235,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,3882,0:37.435.238,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E3 13 31 27 BB 58 66 50 8E 1C A6 E4 98 D1 13 19… 0,,3886,0:37.436.235,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,3887,0:37.451.240,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 19 E9 4C 86 43 85 A3 9D 0C C7 F9 48 93 BA 48 92… 0,,3891,0:37.452.237,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,3892,0:37.466.242,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3896,0:37.467.239,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,3897,0:37.467.242,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 19 E9 4C 86 43 85 A3 9D 0C C7 F9 48 93 BA 48 92… 0,,3901,0:37.468.239,15.004.916 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,3902,0:37.483.245,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7F B5 61 C7 48 8F FA 39 50 45 02 57 03 19 D2 F4… 0,,3906,0:37.484.241,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,3907,0:37.498.247,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3911,0:37.499.244,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,3912,0:37.499.247,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7F B5 61 C7 48 8F FA 39 50 45 02 57 03 19 D2 F4… 0,,3916,0:37.500.244,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,3917,0:37.515.249,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 7E 09 BD 88 74 34 C1 F0 2C 51 39 F7 49 F2 FB… 0,,3921,0:37.516.246,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,3922,0:37.530.251,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3926,0:37.531.248,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,3927,0:37.531.251,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 7E 09 BD 88 74 34 C1 F0 2C 51 39 F7 49 F2 FB… 0,,3931,0:37.532.248,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,3932,0:37.547.253,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6B 2D 4E 2D 8A 39 A0 2D 33 53 3D 0A AD A8 AF 3A… 0,,3936,0:37.548.250,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,3937,0:37.562.256,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3941,0:37.563.252,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,3942,0:37.563.256,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6B 2D 4E 2D 8A 39 A0 2D 33 53 3D 0A AD A8 AF 3A… 0,,3946,0:37.564.253,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,3947,0:37.579.258,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D3 42 06 04 5B 76 9C 5E F4 C0 5B EE 2F 79 CD 3E… 0,,3951,0:37.580.255,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,3952,0:37.594.260,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3956,0:37.595.257,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,3957,0:37.595.260,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D3 42 06 04 5B 76 9C 5E F4 C0 5B EE 2F 79 CD 3E… 0,,3961,0:37.596.257,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,3962,0:37.611.262,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 1F B1 9F 3C 9E 1A 0B 2E A3 D1 A9 F3 7B 1B 61… 0,,3966,0:37.612.259,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,3967,0:37.626.264,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3971,0:37.627.261,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,3972,0:37.627.265,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 1F B1 9F 3C 9E 1A 0B 2E A3 D1 A9 F3 7B 1B 61… 0,,3976,0:37.628.261,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,3977,0:37.643.267,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 D6 D9 9C CA AC E2 5C 6A 23 3F EC 08 15 81 30… 0,,3981,0:37.644.264,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,3982,0:37.658.269,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3986,0:37.659.266,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,3987,0:37.659.269,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 D6 D9 9C CA AC E2 5C 6A 23 3F EC 08 15 81 30… 0,,3991,0:37.660.266,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,3992,0:37.675.271,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 23 15 A3 1A 9A 5A 42 08 86 27 3F 50 9A 31 62… 0,,3996,0:37.676.268,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,3997,0:37.690.273,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4001,0:37.691.270,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,4002,0:37.691.273,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 23 15 A3 1A 9A 5A 42 08 86 27 3F 50 9A 31 62… 0,,4006,0:37.692.270,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,4007,0:37.707.276,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 7C FD 72 2A C1 77 78 98 77 83 16 09 EE 6B EA… 0,,4011,0:37.708.273,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,4012,0:37.722.278,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4016,0:37.723.275,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,4017,0:37.723.278,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 7C FD 72 2A C1 77 78 98 77 83 16 09 EE 6B EA… 0,,4021,0:37.724.275,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,4022,0:37.739.280,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC FB 15 7D FB 41 70 E8 DF 4F 87 49 CB E3 51 40… 0,,4026,0:37.740.277,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,4027,0:37.754.282,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4031,0:37.755.279,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,4032,0:37.755.282,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC FB 15 7D FB 41 70 E8 DF 4F 87 49 CB E3 51 40… 0,,4036,0:37.756.279,15.004.916 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,4037,0:37.771.285,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E 1F EF AD DA C5 52 9C AC 25 B1 F5 EA FC 99 8A… 0,,4041,0:37.772.281,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,4042,0:37.786.287,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4046,0:37.787.284,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,4047,0:37.787.287,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E 1F EF AD DA C5 52 9C AC 25 B1 F5 EA FC 99 8A… 0,,4051,0:37.788.284,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,4052,0:37.803.289,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A 58 DF 3F 73 9A 11 8B 84 34 EF C7 60 9C DD 59… 0,,4056,0:37.804.286,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,4057,0:37.818.291,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4061,0:37.819.288,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,4062,0:37.819.291,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A 58 DF 3F 73 9A 11 8B 84 34 EF C7 60 9C DD 59… 0,,4066,0:37.820.288,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,4067,0:37.835.293,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 29 3B 81 F5 AA E2 AA 0C 46 4F BE 3E AD 0F A0… 0,,4071,0:37.836.290,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,4072,0:37.850.296,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4076,0:37.851.292,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,4077,0:37.851.296,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A 29 3B 81 F5 AA E2 AA 0C 46 4F BE 3E AD 0F A0… 0,,4081,0:37.852.293,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,4082,0:37.867.298,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B7 CA BF A4 CA E9 E6 1F 42 8C D2 1B D9 1F 36 52… 0,,4086,0:37.868.295,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,4087,0:37.882.300,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4091,0:37.883.297,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,4092,0:37.883.300,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B7 CA BF A4 CA E9 E6 1F 42 8C D2 1B D9 1F 36 52… 0,,4096,0:37.884.297,15.004.916 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,4097,0:37.899.302,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 28 C3 04 39 5F 69 1F 30 59 FD 7E 11 BC 82 5B F6… 0,,4101,0:37.900.299,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,4102,0:37.914.304,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4106,0:37.915.301,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,4107,0:37.915.305,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 28 C3 04 39 5F 69 1F 30 59 FD 7E 11 BC 82 5B F6… 0,,4111,0:37.916.301,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,4112,0:37.931.307,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 70 09 E5 11 05 D0 B0 43 D2 FA D4 2D E1 32 3C 1F… 0,,4116,0:37.932.304,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,4117,0:37.946.309,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4121,0:37.947.306,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,4122,0:37.947.309,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 70 09 E5 11 05 D0 B0 43 D2 FA D4 2D E1 32 3C 1F… 0,,4126,0:37.948.306,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,4127,0:37.963.311,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1F C2 73 3B 1A 28 C6 17 89 57 10 FC D4 21 61 95… 0,,4131,0:37.964.308,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,4132,0:37.978.313,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4136,0:37.979.310,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,4137,0:37.979.313,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1F C2 73 3B 1A 28 C6 17 89 57 10 FC D4 21 61 95… 0,,4141,0:37.980.310,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,4142,0:37.995.316,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 63 0E C2 91 0F 43 E5 C8 C8 EF 79 4A E4 07 32… 0,,4146,0:37.996.313,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,4147,0:38.010.318,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4151,0:38.011.315,16.005.041 ms,,,,,[17 SOF],[Frames: 1089 - 1105] 0,,4152,0:38.027.320,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 2B 05 C1 9F 75 4B EA D2 9F 1B 7D 6E 0C E2 0B… 0,,4156,0:38.028.317,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,4157,0:38.042.322,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4161,0:38.043.319,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,4162,0:38.043.322,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 2B 05 C1 9F 75 4B EA D2 9F 1B 7D 6E 0C E2 0B… 0,,4166,0:38.044.319,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,4167,0:38.059.325,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 EF 7A 5D 6C A8 94 BB 17 F0 D7 51 71 C0 87 23… 0,,4171,0:38.060.321,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,4172,0:38.074.327,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4176,0:38.075.323,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,4177,0:38.075.327,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 EF 7A 5D 6C A8 94 BB 17 F0 D7 51 71 C0 87 23… 0,,4181,0:38.076.324,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,4182,0:38.091.329,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 54 9A 58 82 72 82 3E 83 DE 45 6A ED 75 ED FE… 0,,4186,0:38.092.326,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,4187,0:38.106.331,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4191,0:38.107.328,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,4192,0:38.107.331,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 54 9A 58 82 72 82 3E 83 DE 45 6A ED 75 ED FE… 0,,4196,0:38.108.328,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,4197,0:38.123.333,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 FE 81 18 9B 8D AD 7B 0A 83 66 76 EA 4C E2 C8… 0,,4201,0:38.124.330,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,4202,0:38.138.335,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4206,0:38.139.332,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,4207,0:38.139.336,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 FE 81 18 9B 8D AD 7B 0A 83 66 76 EA 4C E2 C8… 0,,4211,0:38.140.333,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,4212,0:38.155.338,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 26 52 ED 4C E2 AA 2C 53 60 95 4B DF 29 7B 42… 0,,4216,0:38.156.335,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,4217,0:38.170.340,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4221,0:38.171.337,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,4222,0:38.171.340,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 26 52 ED 4C E2 AA 2C 53 60 95 4B DF 29 7B 42… 0,,4226,0:38.172.337,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,4227,0:38.187.342,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 A5 61 4E 54 64 E2 9E D2 E0 16 8E 3E 25 39 E5… 0,,4231,0:38.188.339,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,4232,0:38.202.344,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4236,0:38.203.341,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,4237,0:38.203.345,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 A5 61 4E 54 64 E2 9E D2 E0 16 8E 3E 25 39 E5… 0,,4241,0:38.204.341,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,4242,0:38.219.347,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB BB B2 64 AF 9A 11 E1 77 39 FB A6 DB 2A 92 2D… 0,,4246,0:38.220.344,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,4247,0:38.234.349,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4251,0:38.235.346,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,4252,0:38.235.349,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB BB B2 64 AF 9A 11 E1 77 39 FB A6 DB 2A 92 2D… 0,,4256,0:38.236.346,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,4257,0:38.251.351,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A 85 2D B4 E2 7C 56 39 78 8B 61 01 BD A9 38 13… 0,,4261,0:38.252.348,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,4262,0:38.266.353,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4266,0:38.267.350,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,4267,0:38.267.353,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A 85 2D B4 E2 7C 56 39 78 8B 61 01 BD A9 38 13… 0,,4271,0:38.268.350,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,4272,0:38.283.356,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC E6 53 2B 86 71 1B 58 83 23 27 99 26 FE F0 7E… 0,,4276,0:38.284.353,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,4277,0:38.298.358,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4281,0:38.299.355,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,4282,0:38.299.358,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EC E6 53 2B 86 71 1B 58 83 23 27 99 26 FE F0 7E… 0,,4286,0:38.300.355,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,4287,0:38.315.360,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 DE 11 BF BC 77 07 C9 4D D1 BD 9B 70 C2 A7 41… 0,,4291,0:38.316.357,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,4292,0:38.330.362,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4296,0:38.331.359,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,4297,0:38.331.362,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 DE 11 BF BC 77 07 C9 4D D1 BD 9B 70 C2 A7 41… 0,,4301,0:38.332.359,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,4302,0:38.347.365,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B 59 0A 6F DF B1 7D AD 3E 63 B9 1E D7 82 9D F5… 0,,4306,0:38.348.361,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,4307,0:38.362.367,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4311,0:38.363.363,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,4312,0:38.363.367,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B 59 0A 6F DF B1 7D AD 3E 63 B9 1E D7 82 9D F5… 0,,4316,0:38.364.364,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,4317,0:38.379.369,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 11 E5 84 90 6C 33 1C D5 5D D3 BB F4 8A 34 98 BA… 0,,4321,0:38.380.366,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,4322,0:38.394.371,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4326,0:38.395.368,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,4327,0:38.395.371,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 11 E5 84 90 6C 33 1C D5 5D D3 BB F4 8A 34 98 BA… 0,,4331,0:38.396.368,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,4332,0:38.411.373,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AC A7 20 79 3F B0 45 CB AB FF 4E 5E E9 99 19 56… 0,,4336,0:38.412.370,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,4337,0:38.426.376,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4341,0:38.427.372,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,4342,0:38.427.376,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AC A7 20 79 3F B0 45 CB AB FF 4E 5E E9 99 19 56… 0,,4346,0:38.428.373,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,4347,0:38.443.378,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B ED 01 E9 AB 5A B3 55 AC FE 5A 16 A5 57 44 3A… 0,,4351,0:38.444.375,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,4352,0:38.458.380,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4356,0:38.459.377,2.833 us,,,,,[1 SOF],[Frame: 1537] 0,,4357,0:38.459.380,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B ED 01 E9 AB 5A B3 55 AC FE 5A 16 A5 57 44 3A… 0,,4361,0:38.460.377,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,4362,0:38.475.382,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 65 FA FC 9A C0 A5 26 C6 13 37 D0 28 3D 65 FB… 0,,4366,0:38.476.379,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,4367,0:38.490.384,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4371,0:38.491.381,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,4372,0:38.491.384,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 65 FA FC 9A C0 A5 26 C6 13 37 D0 28 3D 65 FB… 0,,4376,0:38.492.381,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,4377,0:38.507.387,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B D6 95 CE 0D 12 9D 55 69 1C CA 7B 1C A1 47 34… 0,,4381,0:38.508.384,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,4382,0:38.522.389,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4386,0:38.523.386,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,4387,0:38.523.389,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B D6 95 CE 0D 12 9D 55 69 1C CA 7B 1C A1 47 34… 0,,4391,0:38.524.386,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,4392,0:38.539.391,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F9 6A 6E DD 9E D7 C2 8B 9E 94 53 45 49 C9 4C 81… 0,,4396,0:38.540.388,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,4397,0:38.554.393,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4401,0:38.555.390,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,4402,0:38.555.393,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F9 6A 6E DD 9E D7 C2 8B 9E 94 53 45 49 C9 4C 81… 0,,4406,0:38.556.390,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,4407,0:38.571.396,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 84 D9 8F 4D C6 90 CD 03 03 52 C5 8B B9 D4 D6 37… 0,,4411,0:38.572.392,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,4412,0:38.586.398,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4416,0:38.587.395,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,4417,0:38.587.398,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 84 D9 8F 4D C6 90 CD 03 03 52 C5 8B B9 D4 D6 37… 0,,4421,0:38.588.395,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,4422,0:38.603.400,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 1E 4B 36 AF 33 29 D0 F3 D3 88 AE 45 E9 A2 C8… 0,,4426,0:38.604.397,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,4427,0:38.618.402,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4431,0:38.619.399,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,4432,0:38.619.402,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 1E 4B 36 AF 33 29 D0 F3 D3 88 AE 45 E9 A2 C8… 0,,4436,0:38.620.399,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,4437,0:38.635.404,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 DD AD 76 8C 17 66 EE 4B E0 88 41 4A EF 17 2C… 0,,4441,0:38.636.401,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,4442,0:38.650.407,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4446,0:38.651.403,16.005.041 ms,,,,,[17 SOF],[Frames: 1729 - 1745] 0,,4447,0:38.667.409,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 6C 8F 69 D2 A1 62 D6 DE 67 8F B0 61 D6 9F 50… 0,,4451,0:38.668.406,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,4452,0:38.682.411,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4456,0:38.683.408,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,4457,0:38.683.411,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 6C 8F 69 D2 A1 62 D6 DE 67 8F B0 61 D6 9F 50… 0,,4461,0:38.684.408,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,4462,0:38.699.413,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 5B DB 08 D8 F3 FD F3 22 08 A6 46 F5 B6 25 30… 0,,4466,0:38.700.410,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,4467,0:38.714.415,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4471,0:38.715.412,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,4472,0:38.715.416,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 5B DB 08 D8 F3 FD F3 22 08 A6 46 F5 B6 25 30… 0,,4476,0:38.716.412,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,4477,0:38.731.418,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 30 4D B9 A4 0E F8 C4 D9 07 45 97 08 7F 25 A1… 0,,4481,0:38.732.415,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,4482,0:38.746.420,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4486,0:38.747.417,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,4487,0:38.747.420,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 30 4D B9 A4 0E F8 C4 D9 07 45 97 08 7F 25 A1… 0,,4491,0:38.748.417,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,4492,0:38.763.422,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF 9B F6 F8 7C 2A C8 7B F5 56 A7 C4 85 5C CA 28… 0,,4496,0:38.764.419,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,4497,0:38.778.424,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4501,0:38.779.421,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,4502,0:38.779.424,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF 9B F6 F8 7C 2A C8 7B F5 56 A7 C4 85 5C CA 28… 0,,4506,0:38.780.421,15.004.916 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,4507,0:38.795.427,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 8B 2D 1A 6D 8A 68 11 AA EF 4C 04 25 1D D4 63… 0,,4511,0:38.796.424,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,4512,0:38.810.429,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4516,0:38.811.426,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,4517,0:38.811.429,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 8B 2D 1A 6D 8A 68 11 AA EF 4C 04 25 1D D4 63… 0,,4521,0:38.812.426,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,4522,0:38.827.431,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4F F8 DE D5 42 90 D5 9F 93 57 3D DE 44 9A 6C A2… 0,,4526,0:38.828.428,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,4527,0:38.842.433,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4531,0:38.843.430,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,4532,0:38.843.433,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4F F8 DE D5 42 90 D5 9F 93 57 3D DE 44 9A 6C A2… 0,,4536,0:38.844.430,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,4537,0:38.859.436,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 A8 76 AA 52 BC F5 A9 11 7A 36 C3 91 4B E3 60… 0,,4541,0:38.860.432,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,4542,0:38.874.438,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4546,0:38.875.435,2.833 us,,,,,[1 SOF],[Frame: 1953] 0,,4547,0:38.875.438,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 A8 76 AA 52 BC F5 A9 11 7A 36 C3 91 4B E3 60… 0,,4551,0:38.876.435,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,4552,0:38.891.440,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C0 D8 22 FB 12 46 EF E8 ED D3 09 A8 6A C1 DB 14… 0,,4556,0:38.892.437,14.004.750 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,4557,0:38.906.442,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4561,0:38.907.439,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,4562,0:38.907.442,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C0 D8 22 FB 12 46 EF E8 ED D3 09 A8 6A C1 DB 14… 0,,4566,0:38.908.439,15.005.000 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,4567,0:38.923.445,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB 47 70 20 FA 9C 0E CC 34 2A 7F A9 10 95 8C 4A… 0,,4571,0:38.924.441,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,4572,0:38.938.447,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4576,0:38.939.443,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,4577,0:38.939.447,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB 47 70 20 FA 9C 0E CC 34 2A 7F A9 10 95 8C 4A… 0,,4581,0:38.940.444,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,4582,0:38.955.449,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 33 2E 9B A9 5E 95 A4 E0 02 35 FA 0F 1F B1 DB 44… 0,,4586,0:38.956.446,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,4587,0:38.970.451,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4591,0:38.971.448,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,4592,0:38.971.451,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 33 2E 9B A9 5E 95 A4 E0 02 35 FA 0F 1F B1 DB 44… 0,,4596,0:38.972.448,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,4597,0:38.987.453,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 78 D0 82 EB 98 B3 E5 AA 4D 46 B8 27 A0 12 9C 1A… 0,,4601,0:38.988.450,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,4602,0:39.002.455,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4606,0:39.003.452,2.833 us,,,,,[1 SOF],[Frame: 33] 0,,4607,0:39.003.456,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 78 D0 82 EB 98 B3 E5 AA 4D 46 B8 27 A0 12 9C 1A… 0,,4611,0:39.004.452,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,4612,0:39.019.458,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 CE EA 7F 9F A2 53 F7 3C EE 03 E0 94 07 50 E4… 0,,4616,0:39.020.455,14.004.750 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,4617,0:39.034.460,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4621,0:39.035.457,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,4622,0:39.035.460,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 CE EA 7F 9F A2 53 F7 3C EE 03 E0 94 07 50 E4… 0,,4626,0:39.036.457,15.004.916 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,4627,0:39.051.462,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 1E A4 CA 4F 39 CB D9 53 C6 C3 D6 5A 89 FB 1E… 0,,4631,0:39.052.459,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,4632,0:39.066.464,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4636,0:39.067.461,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,4637,0:39.067.464,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 1E A4 CA 4F 39 CB D9 53 C6 C3 D6 5A 89 FB 1E… 0,,4641,0:39.068.461,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,4642,0:39.083.467,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 78 15 CC 2D 94 6A CF 88 A8 18 D4 A9 8F 64 31… 0,,4646,0:39.084.464,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,4647,0:39.098.469,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4651,0:39.099.466,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,4652,0:39.099.469,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 78 15 CC 2D 94 6A CF 88 A8 18 D4 A9 8F 64 31… 0,,4656,0:39.100.466,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,4657,0:39.115.471,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 78 DB 39 3E 15 D2 31 50 A3 77 B6 19 45 78 49… 0,,4661,0:39.116.468,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,4662,0:39.130.473,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4666,0:39.131.470,2.833 us,,,,,[1 SOF],[Frame: 161] 0,,4667,0:39.131.473,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 78 DB 39 3E 15 D2 31 50 A3 77 B6 19 45 78 49… 0,,4671,0:39.132.470,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,4672,0:39.147.476,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BB 16 0F 5D 2F D3 55 7F 2C F2 A9 A5 D7 08 54 5B… 0,,4676,0:39.148.472,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,4677,0:39.162.478,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4681,0:39.163.475,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,4682,0:39.163.478,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BB 16 0F 5D 2F D3 55 7F 2C F2 A9 A5 D7 08 54 5B… 0,,4686,0:39.164.475,15.004.916 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,4687,0:39.179.480,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 1D 76 30 65 4F 27 24 54 E6 DE 06 1A 96 11 31… 0,,4691,0:39.180.477,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,4692,0:39.194.482,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4696,0:39.195.479,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,4697,0:39.195.482,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 1D 76 30 65 4F 27 24 54 E6 DE 06 1A 96 11 31… 0,,4701,0:39.196.479,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,4702,0:39.211.484,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 94 0C A6 3E 6C 23 C2 C6 25 9D B2 4A A5 C6 1B… 0,,4706,0:39.212.481,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,4707,0:39.226.487,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4711,0:39.227.483,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,4712,0:39.227.487,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 94 0C A6 3E 6C 23 C2 C6 25 9D B2 4A A5 C6 1B… 0,,4716,0:39.228.484,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,4717,0:39.243.489,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 83 AF 13 B8 51 9E FC 9A 11 DC 96 A6 50 35 96 C4… 0,,4721,0:39.244.486,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,4722,0:39.258.491,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4726,0:39.259.488,16.005.041 ms,,,,,[17 SOF],[Frames: 289 - 305] 0,,4727,0:39.275.493,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 30 FE CF 3D 6E 4D 60 CE F0 88 9B 15 71 39 A9 57… 0,,4731,0:39.276.490,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,4732,0:39.290.495,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4736,0:39.291.492,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,4737,0:39.291.496,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 30 FE CF 3D 6E 4D 60 CE F0 88 9B 15 71 39 A9 57… 0,,4741,0:39.292.492,15.004.916 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,4742,0:39.307.498,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 28 7A 45 1B E6 F2 9E 28 C0 A9 74 77 16 ED D0… 0,,4746,0:39.308.495,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,4747,0:39.322.500,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4751,0:39.323.497,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,4752,0:39.323.500,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 28 7A 45 1B E6 F2 9E 28 C0 A9 74 77 16 ED D0… 0,,4756,0:39.324.497,15.004.916 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,4757,0:39.339.502,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 C8 4B E4 EB 5E DF 2C D0 71 C8 19 A2 C5 BF B2… 0,,4761,0:39.340.499,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,4762,0:39.354.504,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4766,0:39.355.501,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,4767,0:39.355.504,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 C8 4B E4 EB 5E DF 2C D0 71 C8 19 A2 C5 BF B2… 0,,4771,0:39.356.501,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,4772,0:39.371.507,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 59 4D 99 33 AF A2 8C BF 01 A1 7B EA 72 C9 12… 0,,4776,0:39.372.504,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,4777,0:39.386.509,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4781,0:39.387.506,2.833 us,,,,,[1 SOF],[Frame: 417] 0,,4782,0:39.387.509,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 59 4D 99 33 AF A2 8C BF 01 A1 7B EA 72 C9 12… 0,,4786,0:39.388.506,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,4787,0:39.403.511,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A E8 D7 1A 5E 9B B7 7C 35 AD 47 AE 40 EB 9A 6A… 0,,4791,0:39.404.508,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,4792,0:39.418.513,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4796,0:39.419.510,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,4797,0:39.419.513,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A E8 D7 1A 5E 9B B7 7C 35 AD 47 AE 40 EB 9A 6A… 0,,4801,0:39.420.510,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,4802,0:39.435.516,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 53 B0 3F D7 7B B9 AA FC A3 89 7B 53 6E 93 DD 50… 0,,4806,0:39.436.512,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,4807,0:39.450.518,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4811,0:39.451.515,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,4812,0:39.451.518,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 53 B0 3F D7 7B B9 AA FC A3 89 7B 53 6E 93 DD 50… 0,,4816,0:39.452.515,15.004.916 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,4817,0:39.467.520,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE 43 5D D2 96 F1 25 AF D3 2F F8 AC A9 66 50 55… 0,,4821,0:39.468.517,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,4822,0:39.482.522,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4826,0:39.483.519,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,4827,0:39.483.522,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE 43 5D D2 96 F1 25 AF D3 2F F8 AC A9 66 50 55… 0,,4831,0:39.484.519,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,4832,0:39.499.524,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF E3 03 24 8A E8 1B 45 42 4D B7 C5 DA 83 26 7F… 0,,4836,0:39.500.521,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,4837,0:39.514.527,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4841,0:39.515.523,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,4842,0:39.515.527,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF E3 03 24 8A E8 1B 45 42 4D B7 C5 DA 83 26 7F… 0,,4846,0:39.516.524,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,4847,0:39.531.529,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 70 36 4D 35 6F 4A 5C 52 1D 4B B3 1B E5 03 AF 80… 0,,4851,0:39.532.526,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,4852,0:39.546.531,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4856,0:39.547.528,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,4857,0:39.547.531,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 70 36 4D 35 6F 4A 5C 52 1D 4B B3 1B E5 03 AF 80… 0,,4861,0:39.548.528,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,4862,0:39.563.533,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B0 70 03 C7 22 B8 F6 63 AD D3 68 46 09 49 7B 29… 0,,4866,0:39.564.530,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,4867,0:39.578.535,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4871,0:39.579.532,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,4872,0:39.579.536,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B0 70 03 C7 22 B8 F6 63 AD D3 68 46 09 49 7B 29… 0,,4876,0:39.580.532,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,4877,0:39.595.538,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 39 D5 36 6F 66 F6 68 57 FC 9C 98 50 79 B9 54 FF… 0,,4881,0:39.596.535,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,4882,0:39.610.540,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4886,0:39.611.537,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,4887,0:39.611.540,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 39 D5 36 6F 66 F6 68 57 FC 9C 98 50 79 B9 54 FF… 0,,4891,0:39.612.537,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,4892,0:39.627.542,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF 8A 9B 49 C1 67 D3 E4 B1 28 3B 6B 8A 76 44 97… 0,,4896,0:39.628.539,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,4897,0:39.642.544,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4901,0:39.643.541,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,4902,0:39.643.544,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF 8A 9B 49 C1 67 D3 E4 B1 28 3B 6B 8A 76 44 97… 0,,4906,0:39.644.541,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,4907,0:39.659.547,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F8 68 8D F9 35 C0 13 3B 96 3A AD 0B F4 0E 1B A5… 0,,4911,0:39.660.544,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,4912,0:39.674.549,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4916,0:39.675.546,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,4917,0:39.675.549,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F8 68 8D F9 35 C0 13 3B 96 3A AD 0B F4 0E 1B A5… 0,,4921,0:39.676.546,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,4922,0:39.691.551,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F 8E 8C 8E 3A 4E E4 25 AF E7 D2 11 6E 3F C0 87… 0,,4926,0:39.692.548,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,4927,0:39.706.553,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4931,0:39.707.550,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,4932,0:39.707.553,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F 8E 8C 8E 3A 4E E4 25 AF E7 D2 11 6E 3F C0 87… 0,,4936,0:39.708.550,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,4937,0:39.723.556,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6E 98 1F 27 B8 FA 7C 6F 69 83 74 0F 2E E2 37 78… 0,,4941,0:39.724.552,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,4942,0:39.738.558,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4946,0:39.739.555,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,4947,0:39.739.558,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6E 98 1F 27 B8 FA 7C 6F 69 83 74 0F 2E E2 37 78… 0,,4951,0:39.740.555,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,4952,0:39.755.560,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 70 8D 35 90 2E AE C1 B9 45 63 C8 AF D1 28 8D A0… 0,,4956,0:39.756.557,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,4957,0:39.770.562,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4961,0:39.771.559,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,4962,0:39.771.562,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 70 8D 35 90 2E AE C1 B9 45 63 C8 AF D1 28 8D A0… 0,,4966,0:39.772.559,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,4967,0:39.787.564,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E 7E 19 16 D6 66 E7 43 0F 35 EB D4 A7 0D D7 07… 0,,4971,0:39.788.561,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,4972,0:39.802.567,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4976,0:39.803.563,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,4977,0:39.803.567,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E 7E 19 16 D6 66 E7 43 0F 35 EB D4 A7 0D D7 07… 0,,4981,0:39.804.564,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,4982,0:39.819.569,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 40 9A 7D BD 1E DB 21 F1 58 A9 0D 36 9E 5E 9A… 0,,4986,0:39.820.566,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,4987,0:39.834.571,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4991,0:39.835.568,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,4992,0:39.835.571,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 40 9A 7D BD 1E DB 21 F1 58 A9 0D 36 9E 5E 9A… 0,,4996,0:39.836.568,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,4997,0:39.851.573,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 E4 8B 27 38 43 7C 9E C8 E5 E5 4E 3A DF 98 C6… 0,,5001,0:39.852.570,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,5002,0:39.866.575,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5006,0:39.867.572,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,5007,0:39.867.576,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 E4 8B 27 38 43 7C 9E C8 E5 E5 4E 3A DF 98 C6… 0,,5011,0:39.868.572,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,5012,0:39.883.578,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 77 B6 E8 24 4F BE 68 58 C9 86 FA 40 70 30 01… 0,,5016,0:39.884.575,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,5017,0:39.898.580,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5021,0:39.899.577,16.005.041 ms,,,,,[17 SOF],[Frames: 929 - 945] 0,,5022,0:39.915.582,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 13 F5 E7 6A FC C1 88 24 94 49 44 E2 96 40 01… 0,,5026,0:39.916.579,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,5027,0:39.930.584,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5031,0:39.931.581,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,5032,0:39.931.584,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 13 F5 E7 6A FC C1 88 24 94 49 44 E2 96 40 01… 0,,5036,0:39.932.581,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,5037,0:39.947.587,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC BD 70 AB 5F 56 8B 70 DC AF AD CC 43 8D 6A 10… 0,,5041,0:39.948.584,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,5042,0:39.962.589,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5046,0:39.963.586,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,5047,0:39.963.589,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC BD 70 AB 5F 56 8B 70 DC AF AD CC 43 8D 6A 10… 0,,5051,0:39.964.586,15.005.000 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,5052,0:39.979.591,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 29 E1 3C 14 9D 2F 6D AE 60 B7 AA 26 75 7F 84… 0,,5056,0:39.980.588,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,5057,0:39.994.593,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5061,0:39.995.590,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,5062,0:39.995.593,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 29 E1 3C 14 9D 2F 6D AE 60 B7 AA 26 75 7F 84… 0,,5066,0:39.996.590,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,5067,0:40.011.596,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 7F E7 E6 E8 9D F9 05 09 66 C7 6C 11 E8 9A 54… 0,,5071,0:40.012.592,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,5072,0:40.026.598,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5076,0:40.027.594,2.833 us,,,,,[1 SOF],[Frame: 1057] 0,,5077,0:40.027.598,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 7F E7 E6 E8 9D F9 05 09 66 C7 6C 11 E8 9A 54… 0,,5081,0:40.028.595,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,5082,0:40.043.600,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 09 26 42 2F 9C C9 0F E8 D7 03 CD EC 8B 8B 6E E5… 0,,5086,0:40.044.597,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,5087,0:40.058.602,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5091,0:40.059.599,2.833 us,,,,,[1 SOF],[Frame: 1089] 0,,5092,0:40.059.602,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 09 26 42 2F 9C C9 0F E8 D7 03 CD EC 8B 8B 6E E5… 0,,5096,0:40.060.599,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,5097,0:40.075.604,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 0B 6E C2 D2 01 EE 88 E7 21 B3 2A CB AB 1E 41… 0,,5101,0:40.076.601,14.004.750 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,5102,0:40.090.606,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5106,0:40.091.603,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,5107,0:40.091.607,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 0B 6E C2 D2 01 EE 88 E7 21 B3 2A CB AB 1E 41… 0,,5111,0:40.092.604,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,5112,0:40.107.609,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A 2D 5A 07 96 00 75 EA DA 3A 7A 8E 88 74 FB CD… 0,,5116,0:40.108.606,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,5117,0:40.122.611,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5121,0:40.123.608,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,5122,0:40.123.611,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A 2D 5A 07 96 00 75 EA DA 3A 7A 8E 88 74 FB CD… 0,,5126,0:40.124.608,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,5127,0:40.139.613,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 EC 1C 1E CA 9C D9 E7 AC 82 AA FF 44 1F ED 32… 0,,5131,0:40.140.610,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,5132,0:40.154.615,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5136,0:40.155.612,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,5137,0:40.155.616,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 EC 1C 1E CA 9C D9 E7 AC 82 AA FF 44 1F ED 32… 0,,5141,0:40.156.612,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,5142,0:40.171.618,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD 61 08 79 AB 53 20 52 90 53 30 3B 64 A2 AB 87… 0,,5146,0:40.172.615,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,5147,0:40.186.620,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5151,0:40.187.617,2.833 us,,,,,[1 SOF],[Frame: 1217] 0,,5152,0:40.187.620,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD 61 08 79 AB 53 20 52 90 53 30 3B 64 A2 AB 87… 0,,5156,0:40.188.617,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,5157,0:40.203.622,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF FF 04 2C 9E C5 09 A0 A4 D6 DA 2C E8 00 B8 7F… 0,,5161,0:40.204.619,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,5162,0:40.218.624,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5166,0:40.219.621,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,5167,0:40.219.624,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF FF 04 2C 9E C5 09 A0 A4 D6 DA 2C E8 00 B8 7F… 0,,5171,0:40.220.621,15.004.916 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,5172,0:40.235.627,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 76 1E 23 A0 8C 27 6F 92 EF 46 85 24 B3 5D D9… 0,,5176,0:40.236.624,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,5177,0:40.250.629,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5181,0:40.251.626,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,5182,0:40.251.629,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 76 1E 23 A0 8C 27 6F 92 EF 46 85 24 B3 5D D9… 0,,5186,0:40.252.626,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,5187,0:40.267.631,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 46 B2 22 0E 4E 74 E7 34 DE EF A2 3C 9D F4 21… 0,,5191,0:40.268.628,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,5192,0:40.282.633,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5196,0:40.283.630,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,5197,0:40.283.633,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 46 B2 22 0E 4E 74 E7 34 DE EF A2 3C 9D F4 21… 0,,5201,0:40.284.630,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,5202,0:40.299.636,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D4 73 34 48 8C 7A FB 3F 77 3B 35 94 4D 62 6F 4F… 0,,5206,0:40.300.632,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,5207,0:40.314.638,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5211,0:40.315.634,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,5212,0:40.315.638,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D4 73 34 48 8C 7A FB 3F 77 3B 35 94 4D 62 6F 4F… 0,,5216,0:40.316.635,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,5217,0:40.331.640,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C C7 C9 93 A2 06 8C 07 22 13 44 C0 DE 59 16 F5… 0,,5221,0:40.332.637,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,5222,0:40.346.642,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5226,0:40.347.639,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,5227,0:40.347.642,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C C7 C9 93 A2 06 8C 07 22 13 44 C0 DE 59 16 F5… 0,,5231,0:40.348.639,15.004.916 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,5232,0:40.363.644,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 5B BC 0E 87 29 19 7A 52 03 B3 BF 85 30 91 B1… 0,,5236,0:40.364.641,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,5237,0:40.378.646,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5241,0:40.379.643,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,5242,0:40.379.647,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 5B BC 0E 87 29 19 7A 52 03 B3 BF 85 30 91 B1… 0,,5246,0:40.380.644,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,5247,0:40.395.649,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E9 FD 3A 89 B6 92 25 E0 FC 06 F6 50 DE D5 46 CE… 0,,5251,0:40.396.646,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,5252,0:40.410.651,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5256,0:40.411.648,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,5257,0:40.411.651,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E9 FD 3A 89 B6 92 25 E0 FC 06 F6 50 DE D5 46 CE… 0,,5261,0:40.412.648,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,5262,0:40.427.653,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E F9 0E 83 46 03 1A 86 E5 D4 74 2B D8 19 B0 F2… 0,,5266,0:40.428.650,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,5267,0:40.442.655,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5271,0:40.443.652,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,5272,0:40.443.655,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E F9 0E 83 46 03 1A 86 E5 D4 74 2B D8 19 B0 F2… 0,,5276,0:40.444.652,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,5277,0:40.459.658,50.916 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F A1 D4 D6 A4 93 9F 7A 4B F2 FE 57 86 0B B6 19… 0,,5281,0:40.460.655,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,5282,0:40.474.660,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5286,0:40.475.657,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,5287,0:40.475.660,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F A1 D4 D6 A4 93 9F 7A 4B F2 FE 57 86 0B B6 19… 0,,5291,0:40.476.657,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,5292,0:40.491.662,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 E7 CD 40 70 16 D3 97 8D B6 9E F8 F7 32 26 78… 0,,5296,0:40.492.659,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,5297,0:40.506.664,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5301,0:40.507.661,16.005.125 ms,,,,,[17 SOF],[Frames: 1537 - 1553] 0,,5302,0:40.523.667,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 D4 31 1A E6 5A 78 D3 E1 EE 69 92 53 2A 58 93… 0,,5306,0:40.524.663,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,5307,0:40.538.669,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5311,0:40.539.666,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,5312,0:40.539.669,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 D4 31 1A E6 5A 78 D3 E1 EE 69 92 53 2A 58 93… 0,,5316,0:40.540.666,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,5317,0:40.555.671,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D1 3F 29 CF FE 48 4B B5 BE E0 BA 9D B3 DA 72 54… 0,,5321,0:40.556.668,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,5322,0:40.570.673,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5326,0:40.571.670,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,5327,0:40.571.673,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D1 3F 29 CF FE 48 4B B5 BE E0 BA 9D B3 DA 72 54… 0,,5331,0:40.572.670,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,5332,0:40.587.675,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1A F1 7F 68 52 EB 7C 44 57 47 36 36 05 1A D8 29… 0,,5336,0:40.588.672,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,5337,0:40.602.678,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5341,0:40.603.674,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,5342,0:40.603.678,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1A F1 7F 68 52 EB 7C 44 57 47 36 36 05 1A D8 29… 0,,5346,0:40.604.675,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,5347,0:40.619.680,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 49 98 44 70 0C 4F BD FB 20 50 29 BD A0 6C BA C6… 0,,5351,0:40.620.677,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,5352,0:40.634.682,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5356,0:40.635.679,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,5357,0:40.635.682,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 49 98 44 70 0C 4F BD FB 20 50 29 BD A0 6C BA C6… 0,,5361,0:40.636.679,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,5362,0:40.651.684,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 DC BF E8 8A B9 39 AA 6D 7F 48 56 8B F1 5E AF… 0,,5366,0:40.652.681,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,5367,0:40.666.686,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5371,0:40.667.683,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,5372,0:40.667.687,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 DC BF E8 8A B9 39 AA 6D 7F 48 56 8B F1 5E AF… 0,,5376,0:40.668.683,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,5377,0:40.683.689,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3D A8 43 96 82 18 70 4D 5A AA 2B 21 0D 56 D9 01… 0,,5381,0:40.684.686,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,5382,0:40.698.691,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5386,0:40.699.688,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,5387,0:40.699.691,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3D A8 43 96 82 18 70 4D 5A AA 2B 21 0D 56 D9 01… 0,,5391,0:40.700.688,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,5392,0:40.715.693,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 C7 E3 B6 B1 DF 6B E4 0A 21 08 F4 27 57 B5 7C… 0,,5396,0:40.716.690,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,5397,0:40.730.695,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5401,0:40.731.692,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,5402,0:40.731.696,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 C7 E3 B6 B1 DF 6B E4 0A 21 08 F4 27 57 B5 7C… 0,,5406,0:40.732.692,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,5407,0:40.747.698,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A 74 AC 2D C6 A2 FA 4B D9 19 A5 00 DE 64 37 27… 0,,5411,0:40.748.695,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,5412,0:40.762.700,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5416,0:40.763.697,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,5417,0:40.763.700,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A 74 AC 2D C6 A2 FA 4B D9 19 A5 00 DE 64 37 27… 0,,5421,0:40.764.697,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,5422,0:40.779.702,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 16 7D 4A 28 61 09 8C 39 EF 94 34 E8 0E A7 94… 0,,5426,0:40.780.699,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,5427,0:40.794.704,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5431,0:40.795.701,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,5432,0:40.795.704,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 16 7D 4A 28 61 09 8C 39 EF 94 34 E8 0E A7 94… 0,,5436,0:40.796.701,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,5437,0:40.811.707,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3C 03 3E CD E3 2C AD 47 B0 1D D3 53 B2 E4 16 A1… 0,,5441,0:40.812.703,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,5442,0:40.826.709,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5446,0:40.827.706,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,5447,0:40.827.709,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3C 03 3E CD E3 2C AD 47 B0 1D D3 53 B2 E4 16 A1… 0,,5451,0:40.828.706,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,5452,0:40.843.711,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 17 38 82 03 0D 8C 06 FC AF 3E 10 39 0E 4D 01… 0,,5456,0:40.844.708,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,5457,0:40.858.713,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5461,0:40.859.710,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,5462,0:40.859.713,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 17 38 82 03 0D 8C 06 FC AF 3E 10 39 0E 4D 01… 0,,5466,0:40.860.710,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,5467,0:40.875.715,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 69 0F 13 54 CC 46 D7 6E C9 32 AA 25 A5 56 2C 3C… 0,,5471,0:40.876.712,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,5472,0:40.890.718,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5476,0:40.891.714,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,5477,0:40.891.718,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 69 0F 13 54 CC 46 D7 6E C9 32 AA 25 A5 56 2C 3C… 0,,5481,0:40.892.715,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,5482,0:40.907.720,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 9B 99 FF E5 FA E8 FC E1 CA C8 42 EA 69 2E 9E… 0,,5486,0:40.908.717,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,5487,0:40.922.722,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5491,0:40.923.719,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,5492,0:40.923.722,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC 9B 99 FF E5 FA E8 FC E1 CA C8 42 EA 69 2E 9E… 0,,5496,0:40.924.719,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,5497,0:40.939.724,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A 12 C7 B7 09 B9 1E 6D 37 E8 5F 87 18 56 54 61… 0,,5501,0:40.940.721,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,5502,0:40.954.726,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5506,0:40.955.723,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,5507,0:40.955.727,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A 12 C7 B7 09 B9 1E 6D 37 E8 5F 87 18 56 54 61… 0,,5511,0:40.956.723,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,5512,0:40.971.729,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 12 2E 5A 0F 5F EA 8D 2D 90 EC 65 12 2B 7C B6… 0,,5516,0:40.972.726,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,5517,0:40.986.731,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5521,0:40.987.728,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,5522,0:40.987.731,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 12 2E 5A 0F 5F EA 8D 2D 90 EC 65 12 2B 7C B6… 0,,5526,0:40.988.728,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,5527,0:41.003.733,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F4 4E 98 A2 2C 4C 43 34 0B 4E EF E8 C2 85 8A 06… 0,,5531,0:41.004.730,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,5532,0:41.018.735,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5536,0:41.019.732,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,5537,0:41.019.735,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F4 4E 98 A2 2C 4C 43 34 0B 4E EF E8 C2 85 8A 06… 0,,5541,0:41.020.732,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,5542,0:41.035.738,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D8 9F 33 EE 23 FA BD 25 A7 21 0F EB 47 CC 29 8C… 0,,5546,0:41.036.735,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,5547,0:41.050.740,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5551,0:41.051.737,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,5552,0:41.051.740,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D8 9F 33 EE 23 FA BD 25 A7 21 0F EB 47 CC 29 8C… 0,,5556,0:41.052.737,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,5557,0:41.067.742,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 F8 A8 C7 98 F5 01 4C EC 5A 11 F0 FB 0D 3D 6A… 0,,5561,0:41.068.739,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,5562,0:41.082.744,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5566,0:41.083.741,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,5567,0:41.083.744,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 F8 A8 C7 98 F5 01 4C EC 5A 11 F0 FB 0D 3D 6A… 0,,5571,0:41.084.741,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,5572,0:41.099.747,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D 7F B2 25 D3 57 0F 97 76 B0 B4 86 CC 73 04 7F… 0,,5576,0:41.100.743,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,5577,0:41.114.749,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5581,0:41.115.746,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,5582,0:41.115.749,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 7F B2 25 D3 57 0F 97 76 B0 B4 86 CC 73 04 7F… 0,,5586,0:41.116.746,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,5587,0:41.131.751,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4B 2B FE 5F C7 06 79 EC F6 DB DD 6B 3D 86 B3 52… 0,,5591,0:41.132.748,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,5592,0:41.146.753,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5596,0:41.147.750,16.005.041 ms,,,,,[17 SOF],[Frames: 129 - 145] 0,,5597,0:41.163.755,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4B 2B FE 5F C7 06 79 EC F6 DB DD 6B 3D 86 B3 52… 0,,5601,0:41.164.752,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,5602,0:41.178.758,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5606,0:41.179.754,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,5607,0:41.179.758,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4B 2B FE 5F C7 06 79 EC F6 DB DD 6B 3D 86 B3 52… 0,,5611,0:41.180.755,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,5612,0:41.195.760,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 5A 18 7B 79 D3 2C CA 33 B5 A7 40 85 1A A0 43… 0,,5616,0:41.196.757,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,5617,0:41.210.762,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5621,0:41.211.759,2.833 us,,,,,[1 SOF],[Frame: 193] 0,,5622,0:41.211.762,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 5A 18 7B 79 D3 2C CA 33 B5 A7 40 85 1A A0 43… 0,,5626,0:41.212.759,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,5627,0:41.227.764,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 03 F9 9D 33 1E 22 70 D6 CD FB 29 92 92 20 96 6A… 0,,5631,0:41.228.761,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,5632,0:41.242.766,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5636,0:41.243.763,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,5637,0:41.243.767,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 03 F9 9D 33 1E 22 70 D6 CD FB 29 92 92 20 96 6A… 0,,5641,0:41.244.763,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,5642,0:41.259.769,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 53 EA 8F AE B0 1D 88 C5 8A 6C 7E 8B 02 A7 4B A3… 0,,5646,0:41.260.766,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,5647,0:41.274.771,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5651,0:41.275.768,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,5652,0:41.275.771,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 53 EA 8F AE B0 1D 88 C5 8A 6C 7E 8B 02 A7 4B A3… 0,,5656,0:41.276.768,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,5657,0:41.291.773,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 07 EC 7B C5 C9 EC 70 56 E7 3C 61 C6 F1 FD 75… 0,,5661,0:41.292.770,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,5662,0:41.306.775,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5666,0:41.307.772,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,5667,0:41.307.775,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 07 EC 7B C5 C9 EC 70 56 E7 3C 61 C6 F1 FD 75… 0,,5671,0:41.308.772,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,5672,0:41.323.778,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 4F E9 7D BA 92 E7 7D C8 AE F9 32 2E A9 E1 44… 0,,5676,0:41.324.775,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,5677,0:41.338.780,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5681,0:41.339.777,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,5682,0:41.339.780,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 4F E9 7D BA 92 E7 7D C8 AE F9 32 2E A9 E1 44… 0,,5686,0:41.340.777,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,5687,0:41.355.782,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA DB 79 79 07 7F 71 45 5E F4 63 4C D8 E7 C9 A7… 0,,5691,0:41.356.779,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,5692,0:41.370.784,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5696,0:41.371.781,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,5697,0:41.371.784,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA DB 79 79 07 7F 71 45 5E F4 63 4C D8 E7 C9 A7… 0,,5701,0:41.372.781,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,5702,0:41.387.787,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 E3 B9 C7 DB 0F 59 AE A3 87 86 5F 27 BC EE 04… 0,,5706,0:41.388.783,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,5707,0:41.402.789,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5711,0:41.403.786,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,5712,0:41.403.789,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 E3 B9 C7 DB 0F 59 AE A3 87 86 5F 27 BC EE 04… 0,,5716,0:41.404.786,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,5717,0:41.419.791,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7E C6 C5 55 80 30 5F C0 5E 76 93 BF 7B 00 C3 31… 0,,5721,0:41.420.788,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,5722,0:41.434.793,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5726,0:41.435.790,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,5727,0:41.435.793,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7E C6 C5 55 80 30 5F C0 5E 76 93 BF 7B 00 C3 31… 0,,5731,0:41.436.790,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,5732,0:41.451.795,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 2A 38 E2 E0 09 9E 16 0F B5 B8 44 88 AB EC 52… 0,,5736,0:41.452.792,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,5737,0:41.466.798,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5741,0:41.467.794,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,5742,0:41.467.798,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 2A 38 E2 E0 09 9E 16 0F B5 B8 44 88 AB EC 52… 0,,5746,0:41.468.795,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,5747,0:41.483.800,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE 37 1B 35 3A 46 7A 1F 62 B9 FD AE 37 BD 2E EF… 0,,5751,0:41.484.797,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,5752,0:41.498.802,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5756,0:41.499.799,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,5757,0:41.499.802,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE 37 1B 35 3A 46 7A 1F 62 B9 FD AE 37 BD 2E EF… 0,,5761,0:41.500.799,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,5762,0:41.515.804,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 E6 6D 0D 3A AA 20 7A 11 03 A8 8C 25 86 A3 36… 0,,5766,0:41.516.801,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,5767,0:41.530.806,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5771,0:41.531.803,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,5772,0:41.531.807,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 E6 6D 0D 3A AA 20 7A 11 03 A8 8C 25 86 A3 36… 0,,5776,0:41.532.803,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,5777,0:41.547.809,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C4 A5 42 56 93 25 13 8B F1 AC 8B 59 62 66 17 56… 0,,5781,0:41.548.806,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,5782,0:41.562.811,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5786,0:41.563.808,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,5787,0:41.563.811,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C4 A5 42 56 93 25 13 8B F1 AC 8B 59 62 66 17 56… 0,,5791,0:41.564.808,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,5792,0:41.579.813,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 89 BE F3 C6 B9 08 41 B7 A9 23 88 EF 6C 07 5E 6F… 0,,5796,0:41.580.810,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,5797,0:41.594.815,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5801,0:41.595.812,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,5802,0:41.595.815,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 89 BE F3 C6 B9 08 41 B7 A9 23 88 EF 6C 07 5E 6F… 0,,5806,0:41.596.812,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,5807,0:41.611.818,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 A4 83 2A 0E 46 17 B9 9D 22 48 B1 0B 8D F8 9C… 0,,5811,0:41.612.815,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,5812,0:41.626.820,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5816,0:41.627.817,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,5817,0:41.627.820,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 A4 83 2A 0E 46 17 B9 9D 22 48 B1 0B 8D F8 9C… 0,,5821,0:41.628.817,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,5822,0:41.643.822,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 08 F8 B0 FD AB 0C 7A 68 D2 BA B1 44 57 12 2A… 0,,5826,0:41.644.819,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,5827,0:41.658.824,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5831,0:41.659.821,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,5832,0:41.659.824,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 08 F8 B0 FD AB 0C 7A 68 D2 BA B1 44 57 12 2A… 0,,5836,0:41.660.821,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,5837,0:41.675.827,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA BF 26 7E F4 B5 F0 1F EE 8B A8 A7 CD EA FB C4… 0,,5841,0:41.676.823,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,5842,0:41.690.829,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5846,0:41.691.826,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,5847,0:41.691.829,50.729 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA BF 26 7E F4 B5 F0 1F EE 8B A8 A7 CD EA FB C4… 0,,5851,0:41.692.826,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,5852,0:41.707.831,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC 96 8C 90 E9 C7 F5 38 35 3E 51 95 5E F6 1B 94… 0,,5856,0:41.708.828,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,5857,0:41.722.833,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5861,0:41.723.830,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,5862,0:41.723.833,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC 96 8C 90 E9 C7 F5 38 35 3E 51 95 5E F6 1B 94… 0,,5866,0:41.724.830,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,5867,0:41.739.835,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 AF 88 95 89 2F 03 63 D2 E7 24 1E 7A 15 44 8E… 0,,5871,0:41.740.832,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,5872,0:41.754.838,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5876,0:41.755.834,16.005.041 ms,,,,,[17 SOF],[Frames: 737 - 753] 0,,5877,0:41.771.840,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 DA 25 45 05 F6 CA A2 15 B9 88 80 A3 2F DF B1… 0,,5881,0:41.772.837,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,5882,0:41.786.842,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5886,0:41.787.839,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,5887,0:41.787.842,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 DA 25 45 05 F6 CA A2 15 B9 88 80 A3 2F DF B1… 0,,5891,0:41.788.839,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,5892,0:41.803.844,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3D 1A 95 52 65 03 7D A7 31 43 35 32 6E 25 24 7D… 0,,5896,0:41.804.841,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,5897,0:41.818.846,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5901,0:41.819.843,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,5902,0:41.819.847,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3D 1A 95 52 65 03 7D A7 31 43 35 32 6E 25 24 7D… 0,,5906,0:41.820.843,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,5907,0:41.835.849,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 17 5E AE 13 27 28 F8 96 20 4E 60 6B FB B2 70… 0,,5911,0:41.836.846,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,5912,0:41.850.851,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5916,0:41.851.848,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,5917,0:41.851.851,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 17 5E AE 13 27 28 F8 96 20 4E 60 6B FB B2 70… 0,,5921,0:41.852.848,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,5922,0:41.867.853,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A1 C7 ED 1F EF 52 61 4D 34 30 D3 63 63 F6 E4 8C… 0,,5926,0:41.868.850,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,5927,0:41.882.855,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5931,0:41.883.852,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,5932,0:41.883.855,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A1 C7 ED 1F EF 52 61 4D 34 30 D3 63 63 F6 E4 8C… 0,,5936,0:41.884.852,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,5937,0:41.899.858,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 72 3C 08 53 62 43 5B 51 2A C4 AF E0 7A 6B 43… 0,,5941,0:41.900.855,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,5942,0:41.914.860,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5946,0:41.915.857,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,5947,0:41.915.860,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 72 3C 08 53 62 43 5B 51 2A C4 AF E0 7A 6B 43… 0,,5951,0:41.916.857,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,5952,0:41.931.862,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E 09 5F 89 1E C0 05 F4 B4 CD D2 EF B4 3A E4 65… 0,,5956,0:41.932.859,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,5957,0:41.946.864,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5961,0:41.947.861,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,5962,0:41.947.864,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E 09 5F 89 1E C0 05 F4 B4 CD D2 EF B4 3A E4 65… 0,,5966,0:41.948.861,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,5967,0:41.963.867,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 BD CA E2 7C 9C ED 2B 52 AF A7 C1 D8 01 18 82… 0,,5971,0:41.964.863,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,5972,0:41.978.869,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5976,0:41.979.865,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,5977,0:41.979.869,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 BD CA E2 7C 9C ED 2B 52 AF A7 C1 D8 01 18 82… 0,,5981,0:41.980.866,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,5982,0:41.995.871,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 18 2A 32 F7 28 94 19 29 C3 E7 F2 DB FB E7 F5… 0,,5986,0:41.996.868,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,5987,0:42.010.873,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5991,0:42.011.870,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,5992,0:42.011.873,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A 18 2A 32 F7 28 94 19 29 C3 E7 F2 DB FB E7 F5… 0,,5996,0:42.012.870,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,5997,0:42.027.875,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E 39 8F 44 27 97 B0 DB 78 EF 6B 90 78 E4 6A BB… 0,,6001,0:42.028.872,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,6002,0:42.042.877,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6006,0:42.043.874,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,6007,0:42.043.878,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E 39 8F 44 27 97 B0 DB 78 EF 6B 90 78 E4 6A BB… 0,,6011,0:42.044.875,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,6012,0:42.059.880,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 DA CF 22 79 58 FB D5 09 FC 92 CC EE 86 9C B1… 0,,6016,0:42.060.877,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,6017,0:42.074.882,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6021,0:42.075.879,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,6022,0:42.075.882,50.729 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 DA CF 22 79 58 FB D5 09 FC 92 CC EE 86 9C B1… 0,,6026,0:42.076.879,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,6027,0:42.091.884,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 8A A0 FB E8 5A EE F3 3E F8 E9 AE BE 48 90 A8… 0,,6031,0:42.092.881,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,6032,0:42.106.886,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6036,0:42.107.883,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,6037,0:42.107.887,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 8A A0 FB E8 5A EE F3 3E F8 E9 AE BE 48 90 A8… 0,,6041,0:42.108.883,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,6042,0:42.123.889,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 8B 55 1C B2 14 21 46 C1 E4 74 B8 73 6F B1 4F… 0,,6046,0:42.124.886,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,6047,0:42.138.891,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6051,0:42.139.888,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,6052,0:42.139.891,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 8B 55 1C B2 14 21 46 C1 E4 74 B8 73 6F B1 4F… 0,,6056,0:42.140.888,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,6057,0:42.155.893,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 52 1A FF 71 8D 9B E9 6B A9 41 37 DB DD E4 BC DC… 0,,6061,0:42.156.890,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,6062,0:42.170.895,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6066,0:42.171.892,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,6067,0:42.171.895,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 52 1A FF 71 8D 9B E9 6B A9 41 37 DB DD E4 BC DC… 0,,6071,0:42.172.892,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,6072,0:42.187.898,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C7 A6 C6 E6 15 40 FF C1 A4 9E 09 2B 5F 20 66 73… 0,,6076,0:42.188.895,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,6077,0:42.202.900,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6081,0:42.203.897,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,6082,0:42.203.900,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C7 A6 C6 E6 15 40 FF C1 A4 9E 09 2B 5F 20 66 73… 0,,6086,0:42.204.897,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,6087,0:42.219.902,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A 2C 73 C1 6C 5F C5 81 D2 AB 05 2F EC 40 E1 4D… 0,,6091,0:42.220.899,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,6092,0:42.234.904,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6096,0:42.235.901,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,6097,0:42.235.904,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A 2C 73 C1 6C 5F C5 81 D2 AB 05 2F EC 40 E1 4D… 0,,6101,0:42.236.901,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,6102,0:42.251.907,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3A E8 BB 93 4A 78 0A EA 9E 28 E9 20 18 C0 B7 07… 0,,6106,0:42.252.903,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,6107,0:42.266.909,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6111,0:42.267.905,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,6112,0:42.267.909,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3A E8 BB 93 4A 78 0A EA 9E 28 E9 20 18 C0 B7 07… 0,,6116,0:42.268.906,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,6117,0:42.283.911,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 C2 EE 6E 63 AD 46 81 BE 41 58 B1 F3 83 E8 C0… 0,,6121,0:42.284.908,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,6122,0:42.298.913,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6126,0:42.299.910,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,6127,0:42.299.913,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 C2 EE 6E 63 AD 46 81 BE 41 58 B1 F3 83 E8 C0… 0,,6131,0:42.300.910,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,6132,0:42.315.915,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 56 06 50 AF CE 53 D0 53 0F C2 AC 7B 8D EC 95 CE… 0,,6136,0:42.316.912,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,6137,0:42.330.917,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6141,0:42.331.914,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,6142,0:42.331.918,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 56 06 50 AF CE 53 D0 53 0F C2 AC 7B 8D EC 95 CE… 0,,6146,0:42.332.914,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,6147,0:42.347.920,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 DD C8 EB 92 7F 8F BB 43 BD 96 80 D9 4A 18 13… 0,,6151,0:42.348.917,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,6152,0:42.362.922,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6156,0:42.363.919,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,6157,0:42.363.922,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 DD C8 EB 92 7F 8F BB 43 BD 96 80 D9 4A 18 13… 0,,6161,0:42.364.919,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,6162,0:42.379.924,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 60 02 1E 0C 61 93 28 BB B0 BA 83 B8 02 1A DB E1… 0,,6166,0:42.380.921,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,6167,0:42.394.926,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6171,0:42.395.923,16.005.041 ms,,,,,[17 SOF],[Frames: 1377 - 1393] 0,,6172,0:42.411.929,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 02 1E 0C 61 93 28 BB B0 BA 83 B8 02 1A DB E1… 0,,6176,0:42.412.926,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,6177,0:42.426.931,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6181,0:42.427.928,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,6182,0:42.427.931,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 02 1E 0C 61 93 28 BB B0 BA 83 B8 02 1A DB E1… 0,,6186,0:42.428.928,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,6187,0:42.443.933,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F 42 29 42 9F F5 3C 02 31 14 5C 96 11 A6 AE B1… 0,,6191,0:42.444.930,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,6192,0:42.458.935,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6196,0:42.459.932,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,6197,0:42.459.935,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F 42 29 42 9F F5 3C 02 31 14 5C 96 11 A6 AE B1… 0,,6201,0:42.460.932,15.004.916 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,6202,0:42.475.938,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 B3 4B 3E 4D 30 3E 79 30 26 5F F0 24 18 BC 00… 0,,6206,0:42.476.934,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,6207,0:42.490.940,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6211,0:42.491.937,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,6212,0:42.491.940,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 B3 4B 3E 4D 30 3E 79 30 26 5F F0 24 18 BC 00… 0,,6216,0:42.492.937,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,6217,0:42.507.942,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D 03 40 E6 3C 7D F6 3F 11 83 F6 78 AD 3B 70 58… 0,,6221,0:42.508.939,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,6222,0:42.522.944,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6226,0:42.523.941,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,6227,0:42.523.944,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 03 40 E6 3C 7D F6 3F 11 83 F6 78 AD 3B 70 58… 0,,6231,0:42.524.941,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,6232,0:42.539.946,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB E8 59 D2 89 3A E5 0E 6A 2F 91 1C F4 F7 B2 DE… 0,,6236,0:42.540.943,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,6237,0:42.554.949,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6241,0:42.555.945,2.833 us,,,,,[1 SOF],[Frame: 1537] 0,,6242,0:42.555.949,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB E8 59 D2 89 3A E5 0E 6A 2F 91 1C F4 F7 B2 DE… 0,,6246,0:42.556.946,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,6247,0:42.571.951,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C5 CB BA FA 44 65 02 BC A7 72 77 DE 3F 44 EF AE… 0,,6251,0:42.572.948,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,6252,0:42.586.953,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6256,0:42.587.950,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,6257,0:42.587.953,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C5 CB BA FA 44 65 02 BC A7 72 77 DE 3F 44 EF AE… 0,,6261,0:42.588.950,15.004.916 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,6262,0:42.603.955,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 54 AC C6 1E E1 19 FC 70 45 32 52 21 9E E6 E8 65… 0,,6266,0:42.604.952,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,6267,0:42.618.957,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6271,0:42.619.954,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,6272,0:42.619.958,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 54 AC C6 1E E1 19 FC 70 45 32 52 21 9E E6 E8 65… 0,,6276,0:42.620.954,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,6277,0:42.635.960,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 5E DB C5 5C B7 58 3B DE EB 7D D1 10 B6 59 DB… 0,,6281,0:42.636.957,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,6282,0:42.650.962,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6286,0:42.651.959,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,6287,0:42.651.962,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 5E DB C5 5C B7 58 3B DE EB 7D D1 10 B6 59 DB… 0,,6291,0:42.652.959,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,6292,0:42.667.964,50.812 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C7 8A 79 B7 74 4E 5C F0 8D 0E 91 90 F8 AB 0D 06… 0,,6296,0:42.668.961,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,6297,0:42.682.966,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6301,0:42.683.963,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,6302,0:42.683.966,50.854 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C7 8A 79 B7 74 4E 5C F0 8D 0E 91 90 F8 AB 0D 06… 0,,6306,0:42.684.963,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,6307,0:42.699.969,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 EA 5D C7 54 B9 7A D7 60 17 2B 2E 1F F6 D8 2B… 0,,6311,0:42.700.966,14.004.750 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,6312,0:42.714.971,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6316,0:42.715.968,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,6317,0:42.715.971,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 EA 5D C7 54 B9 7A D7 60 17 2B 2E 1F F6 D8 2B… 0,,6321,0:42.716.968,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,6322,0:42.731.973,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 2B 22 5E 38 AA 43 97 B4 C7 78 25 EA 95 05 91… 0,,6326,0:42.732.970,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,6327,0:42.746.975,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6331,0:42.747.972,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,6332,0:42.747.975,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 2B 22 5E 38 AA 43 97 B4 C7 78 25 EA 95 05 91… 0,,6336,0:42.748.972,15.004.916 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,6337,0:42.763.978,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF E2 31 C6 B0 D7 B9 CE 64 C2 EC A2 6C AC D1 80… 0,,6341,0:42.764.974,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,6342,0:42.778.980,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6346,0:42.779.977,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,6347,0:42.779.980,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF E2 31 C6 B0 D7 B9 CE 64 C2 EC A2 6C AC D1 80… 0,,6351,0:42.780.977,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,6352,0:42.795.982,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 B2 A2 AB 90 51 01 AF A9 56 4D 4E 87 47 CD 67… 0,,6356,0:42.796.979,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,6357,0:42.810.984,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6361,0:42.811.981,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,6362,0:42.811.984,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 B2 A2 AB 90 51 01 AF A9 56 4D 4E 87 47 CD 67… 0,,6366,0:42.812.981,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,6367,0:42.827.986,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8F 9F CF 3A 43 79 A1 93 12 9E 02 A9 89 17 D6 96… 0,,6371,0:42.828.983,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,6372,0:42.842.989,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6376,0:42.843.985,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,6377,0:42.843.989,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8F 9F CF 3A 43 79 A1 93 12 9E 02 A9 89 17 D6 96… 0,,6381,0:42.844.986,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,6382,0:42.859.991,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F8 04 1E 26 45 C6 11 A9 33 73 19 5B B3 D3 9D 76… 0,,6386,0:42.860.988,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,6387,0:42.874.993,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6391,0:42.875.990,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,6392,0:42.875.993,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F8 04 1E 26 45 C6 11 A9 33 73 19 5B B3 D3 9D 76… 0,,6396,0:42.876.990,15.004.916 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,6397,0:42.891.995,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 05 12 92 01 BA 7C A8 12 F7 6B D1 AC D3 78 F3… 0,,6401,0:42.892.992,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,6402,0:42.906.997,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6406,0:42.907.994,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,6407,0:42.907.998,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 05 12 92 01 BA 7C A8 12 F7 6B D1 AC D3 78 F3… 0,,6411,0:42.908.994,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,6412,0:42.924.000,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 58 40 81 D2 33 40 4F 78 79 48 AD 5F 4B C8 7F… 0,,6416,0:42.924.997,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,6417,0:42.939.002,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6421,0:42.939.999,2.833 us,,,,,[1 SOF],[Frame: 1921] 0,,6422,0:42.940.002,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 58 40 81 D2 33 40 4F 78 79 48 AD 5F 4B C8 7F… 0,,6426,0:42.940.999,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,6427,0:42.956.004,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC 43 D5 36 0C 12 1E 2F BE 7A 73 8B 34 BC 86 95… 0,,6431,0:42.957.001,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,6432,0:42.971.006,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6436,0:42.972.003,2.833 us,,,,,[1 SOF],[Frame: 1953] 0,,6437,0:42.972.006,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC 43 D5 36 0C 12 1E 2F BE 7A 73 8B 34 BC 86 95… 0,,6441,0:42.973.003,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,6442,0:42.988.009,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B A8 54 AE B1 26 F4 EE 27 C6 75 6D 22 19 C3 58… 0,,6446,0:42.989.006,14.004.750 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,6447,0:43.003.011,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6451,0:43.004.008,16.005.125 ms,,,,,[17 SOF],[Frames: 1985 - 2001] 0,,6452,0:43.020.013,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D 65 45 6C 53 46 FC 1D AF 34 52 63 C9 0F 06 9C… 0,,6456,0:43.021.010,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,6457,0:43.035.015,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6461,0:43.036.012,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,6462,0:43.036.015,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D 65 45 6C 53 46 FC 1D AF 34 52 63 C9 0F 06 9C… 0,,6466,0:43.037.012,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,6467,0:43.052.018,50.729 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 3A 07 9C 6B 1B 17 FF A5 A6 4E BD 1F 4A D6 FF… 0,,6471,0:43.053.014,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,6472,0:43.067.020,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6476,0:43.068.017,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,6477,0:43.068.020,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 3A 07 9C 6B 1B 17 FF A5 A6 4E BD 1F 4A D6 FF… 0,,6481,0:43.069.017,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,6482,0:43.084.022,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 AB 21 A1 70 27 6B E9 81 BE 3A 4B C8 66 D0 05… 0,,6486,0:43.085.019,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,6487,0:43.099.024,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6491,0:43.100.021,2.833 us,,,,,[1 SOF],[Frame: 33] 0,,6492,0:43.100.024,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 AB 21 A1 70 27 6B E9 81 BE 3A 4B C8 66 D0 05… 0,,6496,0:43.101.021,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,6497,0:43.116.026,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D9 81 62 76 F1 BA 80 10 82 15 F6 9E 83 56 B5 65… 0,,6501,0:43.117.023,14.004.750 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,6502,0:43.131.029,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6506,0:43.132.025,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,6507,0:43.132.029,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D9 81 62 76 F1 BA 80 10 82 15 F6 9E 83 56 B5 65… 0,,6511,0:43.133.026,15.004.916 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,6512,0:43.148.031,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E D6 B2 14 EB 88 ED 29 FA 88 3E 21 2E E7 64 0B… 0,,6516,0:43.149.028,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,6517,0:43.163.033,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6521,0:43.164.030,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,6522,0:43.164.033,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E D6 B2 14 EB 88 ED 29 FA 88 3E 21 2E E7 64 0B… 0,,6526,0:43.165.030,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,6527,0:43.180.035,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA 49 85 96 48 67 6A 09 75 D0 66 20 9A C4 4D 81… 0,,6531,0:43.181.032,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,6532,0:43.195.037,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6536,0:43.196.034,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,6537,0:43.196.038,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA 49 85 96 48 67 6A 09 75 D0 66 20 9A C4 4D 81… 0,,6541,0:43.197.034,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,6542,0:43.212.040,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 B8 52 2D 42 6F FE 35 05 0E B2 54 6B 0C 7F 7B… 0,,6546,0:43.213.037,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,6547,0:43.227.042,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6551,0:43.228.039,2.833 us,,,,,[1 SOF],[Frame: 161] 0,,6552,0:43.228.042,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 B8 52 2D 42 6F FE 35 05 0E B2 54 6B 0C 7F 7B… 0,,6556,0:43.229.039,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,6557,0:43.244.044,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 E6 35 AF D5 F1 F1 A5 CA 77 78 EE EF A6 BE D6… 0,,6561,0:43.245.041,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,6562,0:43.259.046,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6566,0:43.260.043,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,6567,0:43.260.046,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 E6 35 AF D5 F1 F1 A5 CA 77 78 EE EF A6 BE D6… 0,,6571,0:43.261.043,15.004.916 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,6572,0:43.276.049,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 DE 2E C1 38 25 07 DE 1A AA EB 7E 47 E0 BA CD… 0,,6576,0:43.277.046,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,6577,0:43.291.051,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6581,0:43.292.048,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,6582,0:43.292.051,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 DE 2E C1 38 25 07 DE 1A AA EB 7E 47 E0 BA CD… 0,,6586,0:43.293.048,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,6587,0:43.308.053,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 C1 6D 87 81 F6 9C 7E 8F D8 AD 49 CA 36 2C C4… 0,,6591,0:43.309.050,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,6592,0:43.323.055,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6596,0:43.324.052,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,6597,0:43.324.055,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 C1 6D 87 81 F6 9C 7E 8F D8 AD 49 CA 36 2C C4… 0,,6601,0:43.325.052,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,6602,0:43.340.058,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 3F D3 97 FB 77 41 39 10 FD 95 E0 3B AF 20 9D… 0,,6606,0:43.341.054,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,6607,0:43.355.060,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6611,0:43.356.057,2.833 us,,,,,[1 SOF],[Frame: 289] 0,,6612,0:43.356.060,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 3F D3 97 FB 77 41 39 10 FD 95 E0 3B AF 20 9D… 0,,6616,0:43.357.057,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,6617,0:43.372.062,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 B4 C9 0C A4 F8 6E 31 83 28 C4 FF 95 89 EC 7B… 0,,6621,0:43.373.059,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,6622,0:43.387.064,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6626,0:43.388.061,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,6627,0:43.388.064,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 B4 C9 0C A4 F8 6E 31 83 28 C4 FF 95 89 EC 7B… 0,,6631,0:43.389.061,15.004.916 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,6632,0:43.404.066,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C CC 8E F8 B3 B0 0E 8E 93 D6 93 AA D8 91 53 5B… 0,,6636,0:43.405.063,14.004.750 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,6637,0:43.419.069,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6641,0:43.420.065,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,6642,0:43.420.069,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C CC 8E F8 B3 B0 0E 8E 93 D6 93 AA D8 91 53 5B… 0,,6646,0:43.421.066,15.004.916 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,6647,0:43.436.071,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 17 09 E3 98 81 95 D1 67 B7 00 9D E7 14 45 56 AF… 0,,6651,0:43.437.068,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,6652,0:43.451.073,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6656,0:43.452.070,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,6657,0:43.452.073,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 17 09 E3 98 81 95 D1 67 B7 00 9D E7 14 45 56 AF… 0,,6661,0:43.453.070,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,6662,0:43.468.075,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E 0E E8 C6 19 00 5F AE E5 21 18 7C E6 DB 9D 14… 0,,6666,0:43.469.072,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,6667,0:43.483.077,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6671,0:43.484.074,2.833 us,,,,,[1 SOF],[Frame: 417] 0,,6672,0:43.484.078,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E 0E E8 C6 19 00 5F AE E5 21 18 7C E6 DB 9D 14… 0,,6676,0:43.485.074,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,6677,0:43.500.080,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA 71 9E 84 62 C4 D0 08 67 8A 05 D7 6A 03 3A 5F… 0,,6681,0:43.501.077,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,6682,0:43.515.082,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6686,0:43.516.079,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,6687,0:43.516.082,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA 71 9E 84 62 C4 D0 08 67 8A 05 D7 6A 03 3A 5F… 0,,6691,0:43.517.079,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,6692,0:43.532.084,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 6D F8 0C 22 A3 1A 3E 16 B0 A0 15 C9 F8 82 F3… 0,,6696,0:43.533.081,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,6697,0:43.547.086,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6701,0:43.548.083,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,6702,0:43.548.086,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 6D F8 0C 22 A3 1A 3E 16 B0 A0 15 C9 F8 82 F3… 0,,6706,0:43.549.083,15.004.916 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,6707,0:43.564.089,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 7A 6F 26 8A 82 3C FB 9B AB 7A C4 E6 BB C7 14… 0,,6711,0:43.565.086,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,6712,0:43.579.091,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6716,0:43.580.088,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,6717,0:43.580.091,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 7A 6F 26 8A 82 3C FB 9B AB 7A C4 E6 BB C7 14… 0,,6721,0:43.581.088,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,6722,0:43.596.093,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C3 42 61 37 31 88 FF 0F 56 2D BE AD 01 E9 27 97… 0,,6726,0:43.597.090,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,6727,0:43.611.095,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6731,0:43.612.092,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,6732,0:43.612.095,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C3 42 61 37 31 88 FF 0F 56 2D BE AD 01 E9 27 97… 0,,6736,0:43.613.092,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,6737,0:43.628.098,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7E E7 5B 3F 6C B0 94 A2 53 ED B2 3A C9 0D CA B5… 0,,6741,0:43.629.094,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,6742,0:43.643.100,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6746,0:43.644.097,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,6747,0:43.644.100,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7E E7 5B 3F 6C B0 94 A2 53 ED B2 3A C9 0D CA B5… 0,,6751,0:43.645.097,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,6752,0:43.660.102,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF 29 6A 50 89 9E A5 C5 21 B5 7B FF B9 E7 8D 34… 0,,6756,0:43.661.099,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,6757,0:43.675.104,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6761,0:43.676.101,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,6762,0:43.676.104,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF 29 6A 50 89 9E A5 C5 21 B5 7B FF B9 E7 8D 34… 0,,6766,0:43.677.101,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,6767,0:43.692.106,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5C A0 90 A6 2D 72 28 64 FA 40 3E 42 CA AD 3F BD… 0,,6771,0:43.693.103,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,6772,0:43.707.108,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6776,0:43.708.105,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,6777,0:43.708.109,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5C A0 90 A6 2D 72 28 64 FA 40 3E 42 CA AD 3F BD… 0,,6781,0:43.709.106,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,6782,0:43.724.111,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D BF 16 19 DB 60 05 87 4B 79 B0 C1 C5 08 B0 19… 0,,6786,0:43.725.108,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,6787,0:43.739.113,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6791,0:43.740.110,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,6792,0:43.740.113,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D BF 16 19 DB 60 05 87 4B 79 B0 C1 C5 08 B0 19… 0,,6796,0:43.741.110,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,6797,0:43.756.115,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5C AF 93 EC EC CC FF BD AF ED 45 4C 18 6E 77 22… 0,,6801,0:43.757.112,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,6802,0:43.771.117,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6806,0:43.772.114,2.833 us,,,,,[1 SOF],[Frame: 705] 0,,6807,0:43.772.118,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5C AF 93 EC EC CC FF BD AF ED 45 4C 18 6E 77 22… 0,,6811,0:43.773.114,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,6812,0:43.788.120,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 59 15 EB 46 28 AD F2 5D F8 0E A7 10 7B F8 27 A8… 0,,6816,0:43.789.117,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,6817,0:43.803.122,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6821,0:43.804.119,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,6822,0:43.804.122,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 59 15 EB 46 28 AD F2 5D F8 0E A7 10 7B F8 27 A8… 0,,6826,0:43.805.119,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,6827,0:43.820.124,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B 0D CA 40 F5 6F 75 EB 7C E4 2E F0 FA C3 6B D0… 0,,6831,0:43.821.121,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,6832,0:43.835.126,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6836,0:43.836.123,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,6837,0:43.836.126,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B 0D CA 40 F5 6F 75 EB 7C E4 2E F0 FA C3 6B D0… 0,,6841,0:43.837.123,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,6842,0:43.852.129,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 0E 63 B4 26 16 C6 49 1D 4F 59 1F C8 AE A1 96… 0,,6846,0:43.853.126,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,6847,0:43.867.131,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6851,0:43.868.128,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,6852,0:43.868.131,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 0E 63 B4 26 16 C6 49 1D 4F 59 1F C8 AE A1 96… 0,,6856,0:43.869.128,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,6857,0:43.884.133,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C 68 1B 79 0A A2 A9 9C 9B 4D 42 A3 90 51 F3 20… 0,,6861,0:43.885.130,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,6862,0:43.899.135,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6866,0:43.900.132,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,6867,0:43.900.135,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C 68 1B 79 0A A2 A9 9C 9B 4D 42 A3 90 51 F3 20… 0,,6871,0:43.901.132,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,6872,0:43.916.138,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 8B DF 36 2B 4F 0E 8E 95 A1 32 00 BC 38 91 DE… 0,,6876,0:43.917.134,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,6877,0:43.931.140,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6881,0:43.932.136,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,6882,0:43.932.140,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 8B DF 36 2B 4F 0E 8E 95 A1 32 00 BC 38 91 DE… 0,,6886,0:43.933.137,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,6887,0:43.948.142,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 65 37 9B A3 26 80 43 7D C7 8B F2 95 BC CB 6A B0… 0,,6891,0:43.949.139,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,6892,0:43.963.144,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6896,0:43.964.141,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,6897,0:43.964.144,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 65 37 9B A3 26 80 43 7D C7 8B F2 95 BC CB 6A B0… 0,,6901,0:43.965.141,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,6902,0:43.980.146,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6E 97 08 BE CE 8F 22 B4 19 32 81 BD 2E D0 E1 5C… 0,,6906,0:43.981.143,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,6907,0:43.995.148,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6911,0:43.996.145,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,6912,0:43.996.149,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6E 97 08 BE CE 8F 22 B4 19 32 81 BD 2E D0 E1 5C… 0,,6916,0:43.997.146,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,6917,0:44.012.151,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EA B5 72 5F 51 1D B8 9D CF 94 03 A7 2A 46 1B E1… 0,,6921,0:44.013.148,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,6922,0:44.027.153,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6926,0:44.028.150,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,6927,0:44.028.153,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EA B5 72 5F 51 1D B8 9D CF 94 03 A7 2A 46 1B E1… 0,,6931,0:44.029.150,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,6932,0:44.044.155,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C3 17 00 B2 04 65 2C DA 39 83 80 6F CD 2B 7E 1E… 0,,6936,0:44.045.152,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,6937,0:44.059.157,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6941,0:44.060.154,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,6942,0:44.060.158,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C3 17 00 B2 04 65 2C DA 39 83 80 6F CD 2B 7E 1E… 0,,6946,0:44.061.154,15.005.000 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,6947,0:44.076.160,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 38 67 41 12 3D C7 B8 24 0B 27 CB 6C 86 43 D9… 0,,6951,0:44.077.157,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,6952,0:44.091.162,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6956,0:44.092.159,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,6957,0:44.092.162,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 38 67 41 12 3D C7 B8 24 0B 27 CB 6C 86 43 D9… 0,,6961,0:44.093.159,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,6962,0:44.108.164,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 7E E1 5B AF 5A B4 9A B6 A7 95 CB 29 14 68 70… 0,,6966,0:44.109.161,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,6967,0:44.123.166,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6971,0:44.124.163,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,6972,0:44.124.166,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A 7E E1 5B AF 5A B4 9A B6 A7 95 CB 29 14 68 70… 0,,6976,0:44.125.163,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,6977,0:44.140.169,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 7F AA 49 AF C5 15 AA 86 AC 8F 50 7C E8 AA 20… 0,,6981,0:44.141.165,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,6982,0:44.155.171,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6986,0:44.156.168,2.833 us,,,,,[1 SOF],[Frame: 1089] 0,,6987,0:44.156.171,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 7F AA 49 AF C5 15 AA 86 AC 8F 50 7C E8 AA 20… 0,,6991,0:44.157.168,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,6992,0:44.172.173,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 6F 0A 18 7E C0 02 17 D4 5D 3A F6 D6 AB 7B 1D… 0,,6996,0:44.173.170,14.004.750 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,6997,0:44.187.175,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7001,0:44.188.172,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,7002,0:44.188.175,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 6F 0A 18 7E C0 02 17 D4 5D 3A F6 D6 AB 7B 1D… 0,,7006,0:44.189.172,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,7007,0:44.204.177,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 69 57 98 ED 6E C3 05 36 95 E4 F6 EC E2 C5 0B… 0,,7011,0:44.205.174,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,7012,0:44.219.180,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7016,0:44.220.176,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,7017,0:44.220.180,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 69 57 98 ED 6E C3 05 36 95 E4 F6 EC E2 C5 0B… 0,,7021,0:44.221.177,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,7022,0:44.236.182,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3D 2A D0 CB F6 61 FF B7 06 92 2B C6 BA 5F F8 42… 0,,7026,0:44.237.179,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,7027,0:44.251.184,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7031,0:44.252.181,16.005.041 ms,,,,,[17 SOF],[Frames: 1185 - 1201] 0,,7032,0:44.268.186,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 54 BC 18 C2 97 1B CC 80 D5 6E CB 85 F0 51 6C 7B… 0,,7036,0:44.269.183,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,7037,0:44.283.188,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7041,0:44.284.185,2.833 us,,,,,[1 SOF],[Frame: 1217] 0,,7042,0:44.284.189,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 54 BC 18 C2 97 1B CC 80 D5 6E CB 85 F0 51 6C 7B… 0,,7046,0:44.285.185,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,7047,0:44.300.191,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A 1F ED 17 49 33 06 4A FB C8 28 2C 17 C7 BC 8E… 0,,7051,0:44.301.188,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,7052,0:44.315.193,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7056,0:44.316.190,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,7057,0:44.316.193,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A 1F ED 17 49 33 06 4A FB C8 28 2C 17 C7 BC 8E… 0,,7061,0:44.317.190,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,7062,0:44.332.195,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C 80 D5 FD 88 DB C8 9A 7E 4E 73 E4 C6 EF 78 75… 0,,7066,0:44.333.192,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,7067,0:44.347.197,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7071,0:44.348.194,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,7072,0:44.348.197,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C 80 D5 FD 88 DB C8 9A 7E 4E 73 E4 C6 EF 78 75… 0,,7076,0:44.349.194,15.004.916 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,7077,0:44.364.200,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 47 60 BD 72 54 03 D4 F6 71 47 9B 8B F3 77 33… 0,,7081,0:44.365.197,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,7082,0:44.379.202,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7086,0:44.380.199,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,7087,0:44.380.202,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 47 60 BD 72 54 03 D4 F6 71 47 9B 8B F3 77 33… 0,,7091,0:44.381.199,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,7092,0:44.396.204,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C 8B D9 B6 FD 2D 3F 63 59 BE 92 34 A2 5E E8 EE… 0,,7096,0:44.397.201,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,7097,0:44.411.206,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7101,0:44.412.203,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,7102,0:44.412.206,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C 8B D9 B6 FD 2D 3F 63 59 BE 92 34 A2 5E E8 EE… 0,,7106,0:44.413.203,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,7107,0:44.428.209,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E E7 D3 76 A7 17 8D FB E3 BF 84 2F 16 C2 90 E8… 0,,7111,0:44.429.205,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,7112,0:44.443.211,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7116,0:44.444.208,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,7117,0:44.444.211,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E E7 D3 76 A7 17 8D FB E3 BF 84 2F 16 C2 90 E8… 0,,7121,0:44.445.208,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,7122,0:44.460.213,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 3D 06 12 DE 8B 56 7D C3 9C 5A 84 51 42 15 83… 0,,7126,0:44.461.210,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,7127,0:44.475.215,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7131,0:44.476.212,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,7132,0:44.476.215,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 3D 06 12 DE 8B 56 7D C3 9C 5A 84 51 42 15 83… 0,,7136,0:44.477.212,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,7137,0:44.492.217,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 47 9E 1E 7B DE FD 39 7A 05 47 C0 6D 57 85 11 A5… 0,,7141,0:44.493.214,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,7142,0:44.507.220,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7146,0:44.508.216,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,7147,0:44.508.220,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 47 9E 1E 7B DE FD 39 7A 05 47 C0 6D 57 85 11 A5… 0,,7151,0:44.509.217,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,7152,0:44.524.222,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 C2 77 FA F9 10 49 83 A9 D2 D1 3A 8B F2 A6 A1… 0,,7156,0:44.525.219,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,7157,0:44.539.224,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7161,0:44.540.221,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,7162,0:44.540.224,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 C2 77 FA F9 10 49 83 A9 D2 D1 3A 8B F2 A6 A1… 0,,7166,0:44.541.221,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,7167,0:44.556.226,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB 93 78 D7 69 B2 50 C3 56 29 3A 06 26 69 1C 3C… 0,,7171,0:44.557.223,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,7172,0:44.571.229,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7176,0:44.572.225,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,7177,0:44.572.229,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB 93 78 D7 69 B2 50 C3 56 29 3A 06 26 69 1C 3C… 0,,7181,0:44.573.225,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,7182,0:44.588.231,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AD D1 CD 02 16 52 F2 D0 0F 51 0D 95 7E 47 22 00… 0,,7186,0:44.589.228,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,7187,0:44.603.233,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7191,0:44.604.230,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,7192,0:44.604.233,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AD D1 CD 02 16 52 F2 D0 0F 51 0D 95 7E 47 22 00… 0,,7196,0:44.605.230,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,7197,0:44.620.235,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 41 1B 9A 14 AB 81 3F 39 6D A7 A0 13 11 19 F0… 0,,7201,0:44.621.232,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,7202,0:44.635.237,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7206,0:44.636.234,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,7207,0:44.636.237,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 41 1B 9A 14 AB 81 3F 39 6D A7 A0 13 11 19 F0… 0,,7211,0:44.637.234,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,7212,0:44.652.240,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 4F C1 84 6E 73 8D A1 88 2C CF 81 D1 70 C0 9D… 0,,7216,0:44.653.237,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,7217,0:44.667.242,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7221,0:44.668.239,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,7222,0:44.668.242,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 4F C1 84 6E 73 8D A1 88 2C CF 81 D1 70 C0 9D… 0,,7226,0:44.669.239,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,7227,0:44.684.244,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D6 6B CF 0B 9F 92 3B 6B DC C5 BA 3C 72 8C 65 C9… 0,,7231,0:44.685.241,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,7232,0:44.699.246,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7236,0:44.700.243,2.833 us,,,,,[1 SOF],[Frame: 1633] 0,,7237,0:44.700.246,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D6 6B CF 0B 9F 92 3B 6B DC C5 BA 3C 72 8C 65 C9… 0,,7241,0:44.701.243,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,7242,0:44.716.249,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 69 20 57 E7 9F D7 44 E8 5A 99 57 E3 38 C1 F7 35… 0,,7246,0:44.717.245,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,7247,0:44.731.251,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7251,0:44.732.248,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,7252,0:44.732.251,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 69 20 57 E7 9F D7 44 E8 5A 99 57 E3 38 C1 F7 35… 0,,7256,0:44.733.248,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,7257,0:44.748.253,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EE FF 7D CA 09 C4 51 96 57 40 FA 88 0C 12 67 3C… 0,,7261,0:44.749.250,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,7262,0:44.763.255,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7266,0:44.764.252,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,7267,0:44.764.255,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EE FF 7D CA 09 C4 51 96 57 40 FA 88 0C 12 67 3C… 0,,7271,0:44.765.252,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,7272,0:44.780.257,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B7 94 A3 61 B9 7A EF 7E 62 AB 57 5E 5C 3B 6B 5E… 0,,7276,0:44.781.254,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,7277,0:44.795.260,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7281,0:44.796.256,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,7282,0:44.796.260,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B7 94 A3 61 B9 7A EF 7E 62 AB 57 5E 5C 3B 6B 5E… 0,,7286,0:44.797.257,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,7287,0:44.812.262,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 1B E5 1A A2 1E 16 35 1B 63 85 18 EF 67 B8 9C… 0,,7291,0:44.813.259,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,7292,0:44.827.264,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7296,0:44.828.261,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,7297,0:44.828.264,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 1B E5 1A A2 1E 16 35 1B 63 85 18 EF 67 B8 9C… 0,,7301,0:44.829.261,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,7302,0:44.844.266,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6F 73 23 65 C2 26 F8 49 4D 54 04 1C D2 71 A3 63… 0,,7306,0:44.845.263,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,7307,0:44.859.268,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7311,0:44.860.265,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,7312,0:44.860.269,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6F 73 23 65 C2 26 F8 49 4D 54 04 1C D2 71 A3 63… 0,,7316,0:44.861.265,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,7317,0:44.876.271,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 2F 45 F9 44 08 D9 61 2B 73 BD 18 F9 E8 85 B3… 0,,7321,0:44.877.268,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,7322,0:44.891.273,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7326,0:44.892.270,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,7327,0:44.892.273,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 2F 45 F9 44 08 D9 61 2B 73 BD 18 F9 E8 85 B3… 0,,7331,0:44.893.270,15.004.916 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,7332,0:44.908.275,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 20 B1 B2 F5 CE 94 1A 01 CD 21 8B 83 FB 28 BE A8… 0,,7336,0:44.909.272,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,7337,0:44.923.277,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7341,0:44.924.274,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,7342,0:44.924.277,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 20 B1 B2 F5 CE 94 1A 01 CD 21 8B 83 FB 28 BE A8… 0,,7346,0:44.925.274,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,7347,0:44.940.280,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 96 2F 77 B2 16 25 2F DD 57 2D 79 B7 30 C1 E2… 0,,7351,0:44.941.277,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,7352,0:44.955.282,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7356,0:44.956.279,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,7357,0:44.956.282,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 96 2F 77 B2 16 25 2F DD 57 2D 79 B7 30 C1 E2… 0,,7361,0:44.957.279,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,7362,0:44.972.284,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 93 6A E4 32 32 A6 01 6F EA 74 55 36 54 71 77… 0,,7366,0:44.973.281,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,7367,0:44.987.286,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7371,0:44.988.283,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,7372,0:44.988.286,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 93 6A E4 32 32 A6 01 6F EA 74 55 36 54 71 77… 0,,7376,0:44.989.283,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,7377,0:45.004.289,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 D8 B3 3B F8 57 01 DF A3 45 F6 1B 58 AE DA CC… 0,,7381,0:45.005.285,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,7382,0:45.019.291,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7386,0:45.020.288,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,7387,0:45.020.291,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 D8 B3 3B F8 57 01 DF A3 45 F6 1B 58 AE DA CC… 0,,7391,0:45.021.288,15.004.916 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,7392,0:45.036.293,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 70 80 2D AE 7E 15 1F 43 F0 0B D1 A3 E0 34 4D… 0,,7396,0:45.037.290,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,7397,0:45.051.295,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7401,0:45.052.292,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,7402,0:45.052.295,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 70 80 2D AE 7E 15 1F 43 F0 0B D1 A3 E0 34 4D… 0,,7406,0:45.053.292,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,7407,0:45.068.298,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 38 53 88 2D F7 F2 0A 21 B0 10 6C 8E 3C 8D 16… 0,,7411,0:45.069.294,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,7412,0:45.083.300,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7416,0:45.084.296,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,7417,0:45.084.300,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 38 53 88 2D F7 F2 0A 21 B0 10 6C 8E 3C 8D 16… 0,,7421,0:45.085.297,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,7422,0:45.100.302,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF 37 C9 86 52 E0 1B B4 0F 3A 3C 9F EF 18 93 46… 0,,7426,0:45.101.299,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,7427,0:45.115.304,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7431,0:45.116.301,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,7432,0:45.116.304,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF 37 C9 86 52 E0 1B B4 0F 3A 3C 9F EF 18 93 46… 0,,7436,0:45.117.301,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,7437,0:45.132.306,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 91 95 04 EC 45 07 95 46 C0 62 85 89 33 03 7D A1… 0,,7441,0:45.133.303,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,7442,0:45.147.308,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7446,0:45.148.305,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,7447,0:45.148.309,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 91 95 04 EC 45 07 95 46 C0 62 85 89 33 03 7D A1… 0,,7451,0:45.149.305,15.004.916 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,7452,0:45.164.311,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 72 AF 78 A2 E0 54 F8 FB 37 E2 35 E3 08 63 99 A7… 0,,7456,0:45.165.308,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,7457,0:45.179.313,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7461,0:45.180.310,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,7462,0:45.180.313,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 72 AF 78 A2 E0 54 F8 FB 37 E2 35 E3 08 63 99 A7… 0,,7466,0:45.181.310,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,7467,0:45.196.315,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C A9 12 30 3F 62 A5 DB 73 6B F8 5F 58 1B D4 03… 0,,7471,0:45.197.312,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,7472,0:45.211.317,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7476,0:45.212.314,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,7477,0:45.212.317,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C A9 12 30 3F 62 A5 DB 73 6B F8 5F 58 1B D4 03… 0,,7481,0:45.213.314,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,7482,0:45.228.320,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 83 C1 63 3B 10 5D 90 8C 6C B9 E8 7E 33 E3 16… 0,,7486,0:45.229.317,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,7487,0:45.243.322,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7491,0:45.244.319,2.833 us,,,,,[1 SOF],[Frame: 129] 0,,7492,0:45.244.322,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 83 C1 63 3B 10 5D 90 8C 6C B9 E8 7E 33 E3 16… 0,,7496,0:45.245.319,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,7497,0:45.260.324,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 1A C1 27 D7 03 2C 3B D7 5C 04 7D 4D 72 C6 EE… 0,,7501,0:45.261.321,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,7502,0:45.275.326,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7506,0:45.276.323,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,7507,0:45.276.326,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 1A C1 27 D7 03 2C 3B D7 5C 04 7D 4D 72 C6 EE… 0,,7511,0:45.277.323,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,7512,0:45.292.329,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 81 5A B3 B4 DE C8 1D F8 24 3D 7B 14 24 4A 88… 0,,7516,0:45.293.325,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,7517,0:45.307.331,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7521,0:45.308.328,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,7522,0:45.308.331,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 81 5A B3 B4 DE C8 1D F8 24 3D 7B 14 24 4A 88… 0,,7526,0:45.309.328,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,7527,0:45.324.333,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF 17 78 49 58 CB F2 39 5D 45 EA C1 20 4E D3 88… 0,,7531,0:45.325.330,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,7532,0:45.339.335,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7536,0:45.340.332,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,7537,0:45.340.335,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF 17 78 49 58 CB F2 39 5D 45 EA C1 20 4E D3 88… 0,,7541,0:45.341.332,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,7542,0:45.356.337,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F B0 3A A2 60 45 95 AC E7 E6 DD D7 F3 22 A6 21… 0,,7546,0:45.357.334,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,7547,0:45.371.340,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7551,0:45.372.336,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,7552,0:45.372.340,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F B0 3A A2 60 45 95 AC E7 E6 DD D7 F3 22 A6 21… 0,,7556,0:45.373.337,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,7557,0:45.388.342,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F9 A4 91 D1 15 54 64 87 D6 60 A9 05 E0 1F D8 D1… 0,,7561,0:45.389.339,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,7562,0:45.403.344,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7566,0:45.404.341,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,7567,0:45.404.344,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F9 A4 91 D1 15 54 64 87 D6 60 A9 05 E0 1F D8 D1… 0,,7571,0:45.405.341,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,7572,0:45.420.346,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 60 3E 87 84 A8 A3 5F 47 AE A5 25 14 57 F1 7A… 0,,7576,0:45.421.343,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,7577,0:45.435.348,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7581,0:45.436.345,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,7582,0:45.436.349,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 60 3E 87 84 A8 A3 5F 47 AE A5 25 14 57 F1 7A… 0,,7586,0:45.437.345,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,7587,0:45.452.351,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB 21 73 CC DA E8 45 5E ED 2C 08 F9 5E CE 3B 11… 0,,7591,0:45.453.348,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,7592,0:45.467.353,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7596,0:45.468.350,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,7597,0:45.468.353,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB 21 73 CC DA E8 45 5E ED 2C 08 F9 5E CE 3B 11… 0,,7601,0:45.469.350,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,7602,0:45.484.355,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C 97 5A B5 C5 37 5D 3E 58 64 26 29 E5 D2 EC 66… 0,,7606,0:45.485.352,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,7607,0:45.499.357,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7611,0:45.500.354,16.005.041 ms,,,,,[17 SOF],[Frames: 385 - 401] 0,,7612,0:45.516.360,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F C0 8D 89 92 E0 4D A9 76 90 15 7B A4 9B 5F 7F… 0,,7616,0:45.517.357,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,7617,0:45.531.362,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7621,0:45.532.359,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,7622,0:45.532.362,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F C0 8D 89 92 E0 4D A9 76 90 15 7B A4 9B 5F 7F… 0,,7626,0:45.533.359,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,7627,0:45.548.364,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC AD A7 26 19 A9 87 48 39 F8 42 60 02 5D F0 1B… 0,,7631,0:45.549.361,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,7632,0:45.563.366,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7636,0:45.564.363,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,7637,0:45.564.366,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC AD A7 26 19 A9 87 48 39 F8 42 60 02 5D F0 1B… 0,,7641,0:45.565.363,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,7642,0:45.580.369,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 51 35 55 24 D0 F6 8B 4B 6D D2 59 97 4A 62 56… 0,,7646,0:45.581.365,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,7647,0:45.595.371,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7651,0:45.596.367,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,7652,0:45.596.371,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 51 35 55 24 D0 F6 8B 4B 6D D2 59 97 4A 62 56… 0,,7656,0:45.597.368,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,7657,0:45.612.373,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 C5 B5 9B B1 7D EA CC A4 E2 B8 E1 91 33 F1 2A… 0,,7661,0:45.613.370,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,7662,0:45.627.375,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7666,0:45.628.372,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,7667,0:45.628.375,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 C5 B5 9B B1 7D EA CC A4 E2 B8 E1 91 33 F1 2A… 0,,7671,0:45.629.372,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,7672,0:45.644.377,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D 11 CA 4B 1C BF A5 8D 88 14 E7 B5 8A 02 63 8C… 0,,7676,0:45.645.374,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,7677,0:45.659.379,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7681,0:45.660.376,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,7682,0:45.660.380,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D 11 CA 4B 1C BF A5 8D 88 14 E7 B5 8A 02 63 8C… 0,,7686,0:45.661.377,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,7687,0:45.676.382,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 0D 4C 97 53 5D EF B1 A5 D2 AB 44 3D 5C 11 FC… 0,,7691,0:45.677.379,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,7692,0:45.691.384,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7696,0:45.692.381,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,7697,0:45.692.384,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 0D 4C 97 53 5D EF B1 A5 D2 AB 44 3D 5C 11 FC… 0,,7701,0:45.693.381,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,7702,0:45.708.386,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 67 25 14 73 19 CA E9 E1 64 E1 02 8E C7 37 07… 0,,7706,0:45.709.383,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,7707,0:45.723.388,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7711,0:45.724.385,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,7712,0:45.724.389,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 67 25 14 73 19 CA E9 E1 64 E1 02 8E C7 37 07… 0,,7716,0:45.725.385,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,7717,0:45.740.391,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC C0 5F 3E D8 B3 66 96 21 98 22 40 95 B2 79 B3… 0,,7721,0:45.741.388,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,7722,0:45.755.393,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7726,0:45.756.390,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,7727,0:45.756.393,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC C0 5F 3E D8 B3 66 96 21 98 22 40 95 B2 79 B3… 0,,7731,0:45.757.390,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,7732,0:45.772.395,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 19 4D 6A F5 0F 6B 6C 8C 57 5C 44 00 6C 16 6D 19… 0,,7736,0:45.773.392,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,7737,0:45.787.397,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7741,0:45.788.394,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,7742,0:45.788.397,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 19 4D 6A F5 0F 6B 6C 8C 57 5C 44 00 6C 16 6D 19… 0,,7746,0:45.789.394,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,7747,0:45.804.400,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5C 69 EE 6A DC 5A 83 E1 69 48 F3 77 21 9C 1F C3… 0,,7751,0:45.805.397,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,7752,0:45.819.402,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7756,0:45.820.399,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,7757,0:45.820.402,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5C 69 EE 6A DC 5A 83 E1 69 48 F3 77 21 9C 1F C3… 0,,7761,0:45.821.399,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,7762,0:45.836.404,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 66 8F 96 7E 04 EB 7A B1 EB E5 9D 32 F7 60 A3 A4… 0,,7766,0:45.837.401,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,7767,0:45.851.406,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7771,0:45.852.403,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,7772,0:45.852.406,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 66 8F 96 7E 04 EB 7A B1 EB E5 9D 32 F7 60 A3 A4… 0,,7776,0:45.853.403,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,7777,0:45.868.409,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 31 78 71 F6 28 AA 72 8D 8B A1 7A 34 FD 37 AE… 0,,7781,0:45.869.405,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,7782,0:45.883.411,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7786,0:45.884.407,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,7787,0:45.884.411,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 31 78 71 F6 28 AA 72 8D 8B A1 7A 34 FD 37 AE… 0,,7791,0:45.885.408,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,7792,0:45.900.413,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 B5 F9 C0 5B 1F C1 F4 04 CB E6 E0 32 71 03 3C… 0,,7796,0:45.901.410,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,7797,0:45.915.415,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7801,0:45.916.412,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,7802,0:45.916.415,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 B5 F9 C0 5B 1F C1 F4 04 CB E6 E0 32 71 03 3C… 0,,7806,0:45.917.412,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,7807,0:45.932.417,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 F9 BD 4F 43 B0 27 3C 73 34 7C D6 2F EB 7D 98… 0,,7811,0:45.933.414,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,7812,0:45.947.419,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7816,0:45.948.416,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,7817,0:45.948.420,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 F9 BD 4F 43 B0 27 3C 73 34 7C D6 2F EB 7D 98… 0,,7821,0:45.949.416,15.004.916 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,7822,0:45.964.422,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D 7C B3 74 CA 7B D5 AF 56 FE 1E F4 B0 52 2E 28… 0,,7826,0:45.965.419,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,7827,0:45.979.424,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7831,0:45.980.421,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,7832,0:45.980.424,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D 7C B3 74 CA 7B D5 AF 56 FE 1E F4 B0 52 2E 28… 0,,7836,0:45.981.421,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,7837,0:45.996.426,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 0B 86 24 1D C3 98 A5 F1 0A B0 3F 3F 24 F6 42… 0,,7841,0:45.997.423,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,7842,0:46.011.428,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7846,0:46.012.425,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,7847,0:46.012.428,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 0B 86 24 1D C3 98 A5 F1 0A B0 3F 3F 24 F6 42… 0,,7851,0:46.013.425,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,7852,0:46.028.431,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 70 5F 2E FB 4F 5D F0 59 16 D7 2B 74 A0 8E 18 C7… 0,,7856,0:46.029.428,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,7857,0:46.043.433,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7861,0:46.044.430,2.833 us,,,,,[1 SOF],[Frame: 929] 0,,7862,0:46.044.433,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 70 5F 2E FB 4F 5D F0 59 16 D7 2B 74 A0 8E 18 C7… 0,,7866,0:46.045.430,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,7867,0:46.060.435,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 55 65 B4 53 20 6A 4B 58 43 C7 3D B5 BD 67 A4 C2… 0,,7871,0:46.061.432,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,7872,0:46.075.437,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7876,0:46.076.434,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,7877,0:46.076.437,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 55 65 B4 53 20 6A 4B 58 43 C7 3D B5 BD 67 A4 C2… 0,,7881,0:46.077.434,15.004.916 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,7882,0:46.092.440,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF 5B 3F 84 E5 71 F4 3F 09 5D 73 A8 4D 05 B8 A9… 0,,7886,0:46.093.436,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,7887,0:46.107.442,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7891,0:46.108.439,16.005.125 ms,,,,,[17 SOF],[Frames: 993 - 1009] 0,,7892,0:46.124.444,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 B3 72 1E BB 53 71 7E 6F D4 71 59 78 26 B6 0B… 0,,7896,0:46.125.441,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,7897,0:46.139.446,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7901,0:46.140.443,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,7902,0:46.140.446,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 26 B3 72 1E BB 53 71 7E 6F D4 71 59 78 26 B6 0B… 0,,7906,0:46.141.443,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,7907,0:46.156.448,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA 72 B8 05 01 19 0D 06 8A C7 19 AC 71 96 4B 73… 0,,7911,0:46.157.445,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,7912,0:46.171.451,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7916,0:46.172.447,2.833 us,,,,,[1 SOF],[Frame: 1057] 0,,7917,0:46.172.451,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA 72 B8 05 01 19 0D 06 8A C7 19 AC 71 96 4B 73… 0,,7921,0:46.173.448,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,7922,0:46.188.453,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 11 64 D6 22 CC C9 A5 C3 D1 BC 4B E6 5C 98 5C… 0,,7926,0:46.189.450,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,7927,0:46.203.455,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7931,0:46.204.452,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,7932,0:46.204.455,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 11 64 D6 22 CC C9 A5 C3 D1 BC 4B E6 5C 98 5C… 0,,7936,0:46.205.452,15.004.916 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,7937,0:46.220.457,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 68 F5 49 23 40 53 70 06 AC A8 94 D6 B2 C0 F1… 0,,7941,0:46.221.454,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,7942,0:46.235.459,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7946,0:46.236.456,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,7947,0:46.236.460,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 68 F5 49 23 40 53 70 06 AC A8 94 D6 B2 C0 F1… 0,,7951,0:46.237.456,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,7952,0:46.252.462,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 99 A6 DC D8 41 49 E9 9D C1 CC 7B D6 71 26 0A… 0,,7956,0:46.253.459,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,7957,0:46.267.464,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7961,0:46.268.461,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,7962,0:46.268.464,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 99 A6 DC D8 41 49 E9 9D C1 CC 7B D6 71 26 0A… 0,,7966,0:46.269.461,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,7967,0:46.284.466,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 38 5F 46 0B C9 6D 42 83 55 4B 1F 73 DD 77 10… 0,,7971,0:46.285.463,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,7972,0:46.299.468,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7976,0:46.300.465,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,7977,0:46.300.468,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 38 5F 46 0B C9 6D 42 83 55 4B 1F 73 DD 77 10… 0,,7981,0:46.301.465,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,7982,0:46.316.471,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 F2 31 E0 98 84 F0 60 64 1F 01 46 69 8A 02 7B… 0,,7986,0:46.317.468,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,7987,0:46.331.473,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7991,0:46.332.470,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,7992,0:46.332.473,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 F2 31 E0 98 84 F0 60 64 1F 01 46 69 8A 02 7B… 0,,7996,0:46.333.470,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,7997,0:46.348.475,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 12 B0 16 76 6A 8D B2 84 7C A9 AA 7B 0D 1F 16… 0,,8001,0:46.349.472,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,8002,0:46.363.477,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8006,0:46.364.474,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,8007,0:46.364.477,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 12 B0 16 76 6A 8D B2 84 7C A9 AA 7B 0D 1F 16… 0,,8011,0:46.365.474,15.004.916 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,8012,0:46.380.480,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 35 DA B9 EE A7 35 33 89 C2 2E 05 79 ED C1 68 71… 0,,8016,0:46.381.476,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,8017,0:46.395.482,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8021,0:46.396.479,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,8022,0:46.396.482,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 35 DA B9 EE A7 35 33 89 C2 2E 05 79 ED C1 68 71… 0,,8026,0:46.397.479,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,8027,0:46.412.484,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 43 F1 85 CE 65 12 12 DB 24 DF 87 6D 96 5A 85 0D… 0,,8031,0:46.413.481,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,8032,0:46.427.486,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8036,0:46.428.483,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,8037,0:46.428.486,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 43 F1 85 CE 65 12 12 DB 24 DF 87 6D 96 5A 85 0D… 0,,8041,0:46.429.483,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,8042,0:46.444.488,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 E8 71 6E 0A E9 73 3C A7 2A CB A1 91 4B 85 D0… 0,,8046,0:46.445.485,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,8047,0:46.459.491,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8051,0:46.460.487,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,8052,0:46.460.491,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 E8 71 6E 0A E9 73 3C A7 2A CB A1 91 4B 85 D0… 0,,8056,0:46.461.488,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,8057,0:46.476.493,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 4C 2E 19 7A 24 68 64 85 6E F2 BD FB EA 01 88… 0,,8061,0:46.477.490,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,8062,0:46.491.495,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8066,0:46.492.492,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,8067,0:46.492.495,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 4C 2E 19 7A 24 68 64 85 6E F2 BD FB EA 01 88… 0,,8071,0:46.493.492,15.004.916 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,8072,0:46.508.497,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 24 0B A0 98 46 88 ED 7A BE 06 64 F4 D2 33 AF 8D… 0,,8076,0:46.509.494,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,8077,0:46.523.499,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8081,0:46.524.496,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,8082,0:46.524.500,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 24 0B A0 98 46 88 ED 7A BE 06 64 F4 D2 33 AF 8D… 0,,8086,0:46.525.496,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,8087,0:46.540.502,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E8 89 78 69 EA 7A DF 0B 35 55 93 AD E4 57 99 22… 0,,8091,0:46.541.499,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,8092,0:46.555.504,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8096,0:46.556.501,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,8097,0:46.556.504,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E8 89 78 69 EA 7A DF 0B 35 55 93 AD E4 57 99 22… 0,,8101,0:46.557.501,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,8102,0:46.572.506,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 E1 E7 BD 55 7C 91 9A 6B A3 4F 3A E9 26 CD DA… 0,,8106,0:46.573.503,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,8107,0:46.587.508,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8111,0:46.588.505,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,8112,0:46.588.508,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 26 E1 E7 BD 55 7C 91 9A 6B A3 4F 3A E9 26 CD DA… 0,,8116,0:46.589.505,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,8117,0:46.604.511,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C 93 CE 2E 1E DE 6B 33 9B E4 1E AA 37 33 66 CB… 0,,8121,0:46.605.508,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,8122,0:46.619.513,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8126,0:46.620.510,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,8127,0:46.620.513,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C 93 CE 2E 1E DE 6B 33 9B E4 1E AA 37 33 66 CB… 0,,8131,0:46.621.510,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,8132,0:46.636.515,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF D6 B0 97 45 2B EC 68 97 9F FF 14 5A 30 8E FA… 0,,8136,0:46.637.512,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,8137,0:46.651.517,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8141,0:46.652.514,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,8142,0:46.652.517,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BF D6 B0 97 45 2B EC 68 97 9F FF 14 5A 30 8E FA… 0,,8146,0:46.653.514,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,8147,0:46.668.520,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 FC E1 28 8A B1 14 0F F8 9C D0 B5 9B 66 0A DB… 0,,8151,0:46.669.516,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,8152,0:46.683.522,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8156,0:46.684.519,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,8157,0:46.684.522,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 FC E1 28 8A B1 14 0F F8 9C D0 B5 9B 66 0A DB… 0,,8161,0:46.685.519,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,8162,0:46.700.524,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 AA CE 3A 61 9D C4 77 7F F9 97 18 66 54 82 E5… 0,,8166,0:46.701.521,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,8167,0:46.715.526,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8171,0:46.716.523,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,8172,0:46.716.526,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 AA CE 3A 61 9D C4 77 7F F9 97 18 66 54 82 E5… 0,,8176,0:46.717.523,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,8177,0:46.732.528,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 63 0F 3D 61 9A 3F DA 21 FB F3 34 3D 7D 91 56… 0,,8181,0:46.733.525,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,8182,0:46.747.531,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8186,0:46.748.527,16.005.041 ms,,,,,[17 SOF],[Frames: 1633 - 1649] 0,,8187,0:46.764.533,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0E 9F C4 ED B3 DA B9 A8 69 A1 D7 27 C5 D9 94 5B… 0,,8191,0:46.765.530,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,8192,0:46.779.535,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8196,0:46.780.532,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,8197,0:46.780.535,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0E 9F C4 ED B3 DA B9 A8 69 A1 D7 27 C5 D9 94 5B… 0,,8201,0:46.781.532,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,8202,0:46.796.537,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 C1 0E 01 EE 30 F1 13 66 7E 3C 68 60 27 86 B4… 0,,8206,0:46.797.534,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,8207,0:46.811.539,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8211,0:46.812.536,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,8212,0:46.812.540,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 C1 0E 01 EE 30 F1 13 66 7E 3C 68 60 27 86 B4… 0,,8216,0:46.813.536,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,8217,0:46.828.542,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A E7 BB E2 0B A2 D3 B0 23 67 68 F5 1D 9D 00 7D… 0,,8221,0:46.829.539,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,8222,0:46.843.544,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8226,0:46.844.541,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,8227,0:46.844.544,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A E7 BB E2 0B A2 D3 B0 23 67 68 F5 1D 9D 00 7D… 0,,8231,0:46.845.541,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,8232,0:46.860.546,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD F8 CC C1 4C 1F D5 A2 C4 E5 CA 09 85 79 28 F5… 0,,8236,0:46.861.543,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,8237,0:46.875.548,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8241,0:46.876.545,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,8242,0:46.876.549,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD F8 CC C1 4C 1F D5 A2 C4 E5 CA 09 85 79 28 F5… 0,,8246,0:46.877.545,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,8247,0:46.892.551,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 94 18 48 BE A4 7D 88 B4 D7 08 E4 F7 96 B0 1B… 0,,8251,0:46.893.548,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,8252,0:46.907.553,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8256,0:46.908.550,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,8257,0:46.908.553,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A 94 18 48 BE A4 7D 88 B4 D7 08 E4 F7 96 B0 1B… 0,,8261,0:46.909.550,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,8262,0:46.924.555,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C B2 D2 74 49 87 14 24 FD E3 90 C3 03 E1 1C 2A… 0,,8266,0:46.925.552,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,8267,0:46.939.557,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8271,0:46.940.554,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,8272,0:46.940.557,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C B2 D2 74 49 87 14 24 FD E3 90 C3 03 E1 1C 2A… 0,,8276,0:46.941.554,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,8277,0:46.956.560,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 47 97 9D 5C F6 3D 52 7C 15 94 D2 B7 D2 0F 3F 3A… 0,,8281,0:46.957.556,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,8282,0:46.971.562,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8286,0:46.972.559,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,8287,0:46.972.562,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 47 97 9D 5C F6 3D 52 7C 15 94 D2 B7 D2 0F 3F 3A… 0,,8291,0:46.973.559,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,8292,0:46.988.564,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 2A 2A FC 14 DB A2 71 C6 94 EC 58 B9 B9 45 89… 0,,8296,0:46.989.561,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,8297,0:47.003.566,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8301,0:47.004.563,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,8302,0:47.004.566,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 2A 2A FC 14 DB A2 71 C6 94 EC 58 B9 B9 45 89… 0,,8306,0:47.005.563,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,8307,0:47.020.568,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E9 DD E0 93 40 94 A5 5E 7C 20 03 21 4F D8 E2 B7… 0,,8311,0:47.021.565,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,8312,0:47.035.571,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8316,0:47.036.567,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,8317,0:47.036.571,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E9 DD E0 93 40 94 A5 5E 7C 20 03 21 4F D8 E2 B7… 0,,8321,0:47.037.568,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,8322,0:47.052.573,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 49 9A CC 62 68 79 81 19 93 D0 E2 A7 7A 24 59… 0,,8326,0:47.053.570,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,8327,0:47.067.575,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8331,0:47.068.572,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,8332,0:47.068.575,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 49 9A CC 62 68 79 81 19 93 D0 E2 A7 7A 24 59… 0,,8336,0:47.069.572,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,8337,0:47.084.577,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 8D 5A FA 40 A2 B7 6B 07 62 5A FD 20 F9 3F 5B… 0,,8341,0:47.085.574,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,8342,0:47.099.579,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8346,0:47.100.576,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,8347,0:47.100.580,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 8D 5A FA 40 A2 B7 6B 07 62 5A FD 20 F9 3F 5B… 0,,8351,0:47.101.576,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,8352,0:47.116.582,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F7 51 8E 04 26 EC 1B 99 35 68 3D 00 A0 08 5A 06… 0,,8356,0:47.117.579,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,8357,0:47.131.584,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8361,0:47.132.581,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,8362,0:47.132.584,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F7 51 8E 04 26 EC 1B 99 35 68 3D 00 A0 08 5A 06… 0,,8366,0:47.133.581,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,8367,0:47.148.586,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF 5A D0 95 29 B0 25 11 FA 18 8A B8 60 83 BE CE… 0,,8371,0:47.149.583,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,8372,0:47.163.588,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8376,0:47.164.585,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,8377,0:47.164.588,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF 5A D0 95 29 B0 25 11 FA 18 8A B8 60 83 BE CE… 0,,8381,0:47.165.585,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,8382,0:47.180.591,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 7F 12 E3 AC DA C6 B6 DC E0 BC 1C 0C 0A 24 C6… 0,,8386,0:47.181.588,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,8387,0:47.195.593,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8391,0:47.196.590,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,8392,0:47.196.593,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 7F 12 E3 AC DA C6 B6 DC E0 BC 1C 0C 0A 24 C6… 0,,8396,0:47.197.590,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,8397,0:47.212.595,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 88 3D 74 FD 72 B4 18 FC 9A 9A DC 43 93 14 3D… 0,,8401,0:47.213.592,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,8402,0:47.227.597,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8406,0:47.228.594,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,8407,0:47.228.597,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 88 3D 74 FD 72 B4 18 FC 9A 9A DC 43 93 14 3D… 0,,8411,0:47.229.594,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,8412,0:47.244.600,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 77 09 C3 AD 4C FF E5 3F 58 F1 18 C3 61 B7 B2 89… 0,,8416,0:47.245.596,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,8417,0:47.259.602,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8421,0:47.260.598,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,8422,0:47.260.602,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 77 09 C3 AD 4C FF E5 3F 58 F1 18 C3 61 B7 B2 89… 0,,8426,0:47.261.599,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,8427,0:47.276.604,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 35 83 90 7D B3 CB 1E 04 AC CE 0B 1B F5 4D EC… 0,,8431,0:47.277.601,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,8432,0:47.291.606,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8436,0:47.292.603,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,8437,0:47.292.606,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 35 83 90 7D B3 CB 1E 04 AC CE 0B 1B F5 4D EC… 0,,8441,0:47.293.603,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,8442,0:47.308.608,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 8F 29 77 FC 78 16 E3 DA 36 B0 8E CE 35 96 BE… 0,,8446,0:47.309.605,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,8447,0:47.323.610,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8451,0:47.324.607,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,8452,0:47.324.611,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 8F 29 77 FC 78 16 E3 DA 36 B0 8E CE 35 96 BE… 0,,8456,0:47.325.608,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,8457,0:47.340.613,50.729 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 9D F2 B1 F2 BE EE 6F 6A 08 6E 8F FF A0 BD 97… 0,,8461,0:47.341.610,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,8462,0:47.355.615,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8466,0:47.356.612,16.005.041 ms,,,,,[17 SOF],[Frames: 193 - 209] 0,,8467,0:47.372.617,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE 31 B4 72 9C 8A 35 A9 B1 75 CB D6 41 39 A9 FF… 0,,8471,0:47.373.614,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,8472,0:47.387.619,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8476,0:47.388.616,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,8477,0:47.388.620,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE 31 B4 72 9C 8A 35 A9 B1 75 CB D6 41 39 A9 FF… 0,,8481,0:47.389.616,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,8482,0:47.404.622,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 52 33 9F F5 CE 4F 38 49 D6 1D 37 9D C9 C7 78… 0,,8486,0:47.405.619,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,8487,0:47.419.624,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8491,0:47.420.621,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,8492,0:47.420.624,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 52 33 9F F5 CE 4F 38 49 D6 1D 37 9D C9 C7 78… 0,,8496,0:47.421.621,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,8497,0:47.436.626,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF FF 17 80 BE CB EE 5A D2 F9 5D 2C 69 9B 62 8D… 0,,8501,0:47.437.623,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,8502,0:47.451.628,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8506,0:47.452.625,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,8507,0:47.452.628,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF FF 17 80 BE CB EE 5A D2 F9 5D 2C 69 9B 62 8D… 0,,8511,0:47.453.625,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,8512,0:47.468.631,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 69 21 7D 17 0B DB 64 6D 9F 8F 15 64 B0 DB DF F8… 0,,8516,0:47.469.628,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,8517,0:47.483.633,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8521,0:47.484.630,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,8522,0:47.484.633,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 69 21 7D 17 0B DB 64 6D 9F 8F 15 64 B0 DB DF F8… 0,,8526,0:47.485.630,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,8527,0:47.500.635,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 01 CA AD B2 2B 69 F7 DB 6F 8C 5D 37 1E 91 03… 0,,8531,0:47.501.632,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,8532,0:47.515.637,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8536,0:47.516.634,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,8537,0:47.516.637,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 01 CA AD B2 2B 69 F7 DB 6F 8C 5D 37 1E 91 03… 0,,8541,0:47.517.634,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,8542,0:47.532.640,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 61 EE 17 92 3B 75 3F A9 2B 24 D3 57 EC 2A 81… 0,,8546,0:47.533.636,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,8547,0:47.547.642,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8551,0:47.548.638,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,8552,0:47.548.642,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 61 EE 17 92 3B 75 3F A9 2B 24 D3 57 EC 2A 81… 0,,8556,0:47.549.639,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,8557,0:47.564.644,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 3F 23 A5 E4 F9 63 23 8B D7 04 74 DB 89 57 D2… 0,,8561,0:47.565.641,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,8562,0:47.579.646,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8566,0:47.580.643,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,8567,0:47.580.646,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 3F 23 A5 E4 F9 63 23 8B D7 04 74 DB 89 57 D2… 0,,8571,0:47.581.643,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,8572,0:47.596.648,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A2 AA 08 EB 97 3F 8C 0A 19 F7 C2 42 41 1D 8A CC… 0,,8576,0:47.597.645,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,8577,0:47.611.650,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8581,0:47.612.647,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,8582,0:47.612.651,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A2 AA 08 EB 97 3F 8C 0A 19 F7 C2 42 41 1D 8A CC… 0,,8586,0:47.613.648,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,8587,0:47.628.653,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 09 8F AA 95 0A D1 53 7F 1E 60 15 CB 08 77 1E… 0,,8591,0:47.629.650,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,8592,0:47.643.655,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8596,0:47.644.652,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,8597,0:47.644.655,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 09 8F AA 95 0A D1 53 7F 1E 60 15 CB 08 77 1E… 0,,8601,0:47.645.652,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,8602,0:47.660.657,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3B 87 99 60 31 AA C5 A2 09 9C 61 36 04 62 71 0C… 0,,8606,0:47.661.654,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,8607,0:47.675.659,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8611,0:47.676.656,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,8612,0:47.676.660,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3B 87 99 60 31 AA C5 A2 09 9C 61 36 04 62 71 0C… 0,,8616,0:47.677.656,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,8617,0:47.692.662,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 DF 22 64 35 AB 09 45 8B 0D 84 1F E8 FF E6 15… 0,,8621,0:47.693.659,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,8622,0:47.707.664,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8626,0:47.708.661,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,8627,0:47.708.664,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 DF 22 64 35 AB 09 45 8B 0D 84 1F E8 FF E6 15… 0,,8631,0:47.709.661,15.004.916 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,8632,0:47.724.666,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 2C B3 55 23 D8 48 C3 05 E5 8C 59 69 E3 0F 47… 0,,8636,0:47.725.663,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,8637,0:47.739.668,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8641,0:47.740.665,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,8642,0:47.740.668,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 2C B3 55 23 D8 48 C3 05 E5 8C 59 69 E3 0F 47… 0,,8646,0:47.741.665,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,8647,0:47.756.671,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E F9 DA D2 70 DB 75 FD 63 B6 41 33 B1 68 CD AD… 0,,8651,0:47.757.667,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,8652,0:47.771.673,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8656,0:47.772.670,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,8657,0:47.772.673,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E F9 DA D2 70 DB 75 FD 63 B6 41 33 B1 68 CD AD… 0,,8661,0:47.773.670,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,8662,0:47.788.675,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 E8 2D 36 CA 38 AF 7A 8F 2C 03 6D C0 0C 10 7A… 0,,8666,0:47.789.672,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,8667,0:47.803.677,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8671,0:47.804.674,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,8672,0:47.804.677,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 E8 2D 36 CA 38 AF 7A 8F 2C 03 6D C0 0C 10 7A… 0,,8676,0:47.805.674,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,8677,0:47.820.679,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 58 93 0C D0 37 BA 15 57 13 DD BA 43 1F 05 35… 0,,8681,0:47.821.676,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,8682,0:47.835.682,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8686,0:47.836.678,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,8687,0:47.836.682,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 58 93 0C D0 37 BA 15 57 13 DD BA 43 1F 05 35… 0,,8691,0:47.837.679,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,8692,0:47.852.684,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 55 48 12 E3 14 49 CD 83 C0 74 30 39 8E 23 87… 0,,8696,0:47.853.681,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,8697,0:47.867.686,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8701,0:47.868.683,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,8702,0:47.868.686,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 55 48 12 E3 14 49 CD 83 C0 74 30 39 8E 23 87… 0,,8706,0:47.869.683,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,8707,0:47.884.688,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 7D BF 04 A6 FC C2 A8 1B 50 D7 CA D9 31 EB 17… 0,,8711,0:47.885.685,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,8712,0:47.899.690,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8716,0:47.900.687,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,8717,0:47.900.691,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 7D BF 04 A6 FC C2 A8 1B 50 D7 CA D9 31 EB 17… 0,,8721,0:47.901.687,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,8722,0:47.916.693,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 E3 DC DA D7 4F 8F 29 AE 25 53 6F 36 48 0E 11… 0,,8726,0:47.917.690,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,8727,0:47.931.695,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8731,0:47.932.692,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,8732,0:47.932.695,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 E3 DC DA D7 4F 8F 29 AE 25 53 6F 36 48 0E 11… 0,,8736,0:47.933.692,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,8737,0:47.948.697,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 20 58 1F 4F 72 71 2C 9E D3 A1 81 96 C3 94 5C 63… 0,,8741,0:47.949.694,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,8742,0:47.963.699,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8746,0:47.964.696,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,8747,0:47.964.699,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 20 58 1F 4F 72 71 2C 9E D3 A1 81 96 C3 94 5C 63… 0,,8751,0:47.965.696,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,8752,0:47.980.702,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 1E 5F F8 0C BF 01 71 9E 15 3F DC D7 BF D4 A5… 0,,8756,0:47.981.699,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,8757,0:47.995.704,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8761,0:47.996.701,16.005.041 ms,,,,,[17 SOF],[Frames: 833 - 849] 0,,8762,0:48.012.706,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3A 7B 83 81 70 B4 73 4D C9 30 6C 06 50 B2 89 68… 0,,8766,0:48.013.703,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,8767,0:48.027.708,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8771,0:48.028.705,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,8772,0:48.028.708,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3A 7B 83 81 70 B4 73 4D C9 30 6C 06 50 B2 89 68… 0,,8776,0:48.029.705,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,8777,0:48.044.711,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B B5 75 EE 4B 2B 7B 00 77 46 34 E0 8F 1A CB 9D… 0,,8781,0:48.045.707,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,8782,0:48.059.713,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8786,0:48.060.710,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,8787,0:48.060.713,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B B5 75 EE 4B 2B 7B 00 77 46 34 E0 8F 1A CB 9D… 0,,8791,0:48.061.710,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,8792,0:48.076.715,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 F2 C1 D6 65 4C 6C EA 60 37 48 5C 04 E8 56 7C… 0,,8796,0:48.077.712,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,8797,0:48.091.717,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8801,0:48.092.714,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,8802,0:48.092.717,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 F2 C1 D6 65 4C 6C EA 60 37 48 5C 04 E8 56 7C… 0,,8806,0:48.093.714,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,8807,0:48.108.719,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD CB 7E 16 B5 5B 61 D3 97 A7 5E 34 5B F7 ED 5B… 0,,8811,0:48.109.716,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,8812,0:48.123.722,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8816,0:48.124.718,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,8817,0:48.124.722,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD CB 7E 16 B5 5B 61 D3 97 A7 5E 34 5B F7 ED 5B… 0,,8821,0:48.125.719,15.004.916 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,8822,0:48.140.724,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 05 BF 21 49 13 48 CE 3A BF 85 5A BC 4F AA 73… 0,,8826,0:48.141.721,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,8827,0:48.155.726,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8831,0:48.156.723,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,8832,0:48.156.726,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 05 BF 21 49 13 48 CE 3A BF 85 5A BC 4F AA 73… 0,,8836,0:48.157.723,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,8837,0:48.172.728,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 E6 30 C4 8B BD C1 38 0D E2 AA 8B D8 35 BF A8… 0,,8841,0:48.173.725,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,8842,0:48.187.730,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8846,0:48.188.727,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,8847,0:48.188.731,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 E6 30 C4 8B BD C1 38 0D E2 AA 8B D8 35 BF A8… 0,,8851,0:48.189.727,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,8852,0:48.204.733,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 C5 BB 19 2C 22 7F 75 35 AD 95 A0 F6 B3 6D FB… 0,,8856,0:48.205.730,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,8857,0:48.219.735,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8861,0:48.220.732,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,8862,0:48.220.735,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 C5 BB 19 2C 22 7F 75 35 AD 95 A0 F6 B3 6D FB… 0,,8866,0:48.221.732,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,8867,0:48.236.737,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 4A 8B 35 AA 97 10 02 44 1D DD A1 82 13 2A 1B… 0,,8871,0:48.237.734,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,8872,0:48.251.739,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8876,0:48.252.736,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,8877,0:48.252.739,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 4A 8B 35 AA 97 10 02 44 1D DD A1 82 13 2A 1B… 0,,8881,0:48.253.736,15.004.916 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,8882,0:48.268.742,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C0 8D 3D 65 86 7E F3 B5 80 C9 CE 5B 30 E5 9B F9… 0,,8886,0:48.269.739,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,8887,0:48.283.744,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8891,0:48.284.741,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,8892,0:48.284.744,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C0 8D 3D 65 86 7E F3 B5 80 C9 CE 5B 30 E5 9B F9… 0,,8896,0:48.285.741,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,8897,0:48.300.746,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 03 AD DC 3C F2 60 8F FE DB 83 19 70 2A 9B A5 53… 0,,8901,0:48.301.743,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,8902,0:48.315.748,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8906,0:48.316.745,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,8907,0:48.316.748,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 03 AD DC 3C F2 60 8F FE DB 83 19 70 2A 9B A5 53… 0,,8911,0:48.317.745,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,8912,0:48.332.751,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3B 4E 92 82 49 A6 8F 1A 69 CE 82 07 5A 99 B1 9D… 0,,8916,0:48.333.747,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,8917,0:48.347.753,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8921,0:48.348.750,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,8922,0:48.348.753,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3B 4E 92 82 49 A6 8F 1A 69 CE 82 07 5A 99 B1 9D… 0,,8926,0:48.349.750,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,8927,0:48.364.755,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 C1 A1 61 56 11 59 7B 49 E4 20 63 E1 F9 1E B7… 0,,8931,0:48.365.752,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,8932,0:48.379.757,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8936,0:48.380.754,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,8937,0:48.380.757,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 C1 A1 61 56 11 59 7B 49 E4 20 63 E1 F9 1E B7… 0,,8941,0:48.381.754,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,8942,0:48.396.759,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 5E 4A 29 B7 68 21 49 02 62 B6 06 F0 71 49 7F… 0,,8946,0:48.397.756,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,8947,0:48.411.762,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8951,0:48.412.758,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,8952,0:48.412.762,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 5E 4A 29 B7 68 21 49 02 62 B6 06 F0 71 49 7F… 0,,8956,0:48.413.759,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,8957,0:48.428.764,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 55 20 CC F9 26 06 2B 8F 67 80 1B 3B A9 10 2F 79… 0,,8961,0:48.429.761,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,8962,0:48.443.766,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8966,0:48.444.763,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,8967,0:48.444.766,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 55 20 CC F9 26 06 2B 8F 67 80 1B 3B A9 10 2F 79… 0,,8971,0:48.445.763,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,8972,0:48.460.768,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A0 92 7F 03 52 A8 DA A1 5A AA 2D 30 E3 6E D7 18… 0,,8976,0:48.461.765,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,8977,0:48.475.770,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8981,0:48.476.767,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,8982,0:48.476.771,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A0 92 7F 03 52 A8 DA A1 5A AA 2D 30 E3 6E D7 18… 0,,8986,0:48.477.767,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,8987,0:48.492.773,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 DD 6D 63 98 60 58 54 B8 73 1D 3A A8 B6 FA A3… 0,,8991,0:48.493.770,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,8992,0:48.507.775,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8996,0:48.508.772,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,8997,0:48.508.775,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 DD 6D 63 98 60 58 54 B8 73 1D 3A A8 B6 FA A3… 0,,9001,0:48.509.772,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,9002,0:48.524.777,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 D8 34 FF F3 85 D9 54 91 0F CE 86 F2 44 F0 63… 0,,9006,0:48.525.774,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,9007,0:48.539.779,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9011,0:48.540.776,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,9012,0:48.540.779,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 D8 34 FF F3 85 D9 54 91 0F CE 86 F2 44 F0 63… 0,,9016,0:48.541.776,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,9017,0:48.556.782,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 CB 27 8E 68 62 7B 06 41 BD CB 8C B8 31 73 6D… 0,,9021,0:48.557.779,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,9022,0:48.571.784,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9026,0:48.572.781,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,9027,0:48.572.784,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 CB 27 8E 68 62 7B 06 41 BD CB 8C B8 31 73 6D… 0,,9031,0:48.573.781,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,9032,0:48.588.786,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE C4 1F 84 D5 15 1F 44 76 9D 56 5E 5D DA 77 A4… 0,,9036,0:48.589.783,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,9037,0:48.603.788,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9041,0:48.604.785,16.005.041 ms,,,,,[17 SOF],[Frames: 1441 - 1457] 0,,9042,0:48.620.791,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D B1 E8 4F A2 1A 69 89 B8 3D 1F 90 E9 59 80 3E… 0,,9046,0:48.621.787,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,9047,0:48.635.793,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9051,0:48.636.790,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,9052,0:48.636.793,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D B1 E8 4F A2 1A 69 89 B8 3D 1F 90 E9 59 80 3E… 0,,9056,0:48.637.790,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,9057,0:48.652.795,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C3 32 DE 5F E4 40 1B CA 6B 6A 93 02 D6 7B 9F B9… 0,,9061,0:48.653.792,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,9062,0:48.667.797,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9066,0:48.668.794,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,9067,0:48.668.797,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C3 32 DE 5F E4 40 1B CA 6B 6A 93 02 D6 7B 9F B9… 0,,9071,0:48.669.794,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,9072,0:48.684.799,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4F FD 6F 0F 24 49 63 DB 08 C5 AC 77 EB 5E 19 C9… 0,,9076,0:48.685.796,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,9077,0:48.699.802,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9081,0:48.700.798,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,9082,0:48.700.802,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4F FD 6F 0F 24 49 63 DB 08 C5 AC 77 EB 5E 19 C9… 0,,9086,0:48.701.799,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,9087,0:48.716.804,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 17 5F D7 CF A7 46 D1 79 48 37 67 E4 42 A0 FF… 0,,9091,0:48.717.801,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,9092,0:48.731.806,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9096,0:48.732.803,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,9097,0:48.732.806,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 17 5F D7 CF A7 46 D1 79 48 37 67 E4 42 A0 FF… 0,,9101,0:48.733.803,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,9102,0:48.748.808,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3B DD B7 F0 CD A9 E9 C9 B6 10 85 F1 84 42 56 F7… 0,,9106,0:48.749.805,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,9107,0:48.763.810,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9111,0:48.764.807,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,9112,0:48.764.811,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3B DD B7 F0 CD A9 E9 C9 B6 10 85 F1 84 42 56 F7… 0,,9116,0:48.765.807,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,9117,0:48.780.813,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 1F 36 E2 77 C8 8B EF B6 BA 9E CC A8 FB D5 37… 0,,9121,0:48.781.810,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,9122,0:48.795.815,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9126,0:48.796.812,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,9127,0:48.796.815,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 1F 36 E2 77 C8 8B EF B6 BA 9E CC A8 FB D5 37… 0,,9131,0:48.797.812,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,9132,0:48.812.817,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 65 DB 99 9E 17 4E 46 06 3E E5 E6 76 CD DB F3 DB… 0,,9136,0:48.813.814,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,9137,0:48.827.819,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9141,0:48.828.816,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,9142,0:48.828.819,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 65 DB 99 9E 17 4E 46 06 3E E5 E6 76 CD DB F3 DB… 0,,9146,0:48.829.816,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,9147,0:48.844.822,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B EB 7B 76 20 69 8A D7 7E 51 F8 CA 84 7B 2E 91… 0,,9151,0:48.845.819,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,9152,0:48.859.824,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9156,0:48.860.821,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,9157,0:48.860.824,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B EB 7B 76 20 69 8A D7 7E 51 F8 CA 84 7B 2E 91… 0,,9161,0:48.861.821,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,9162,0:48.876.826,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D ED 8A B9 02 A8 DF 0C 44 B3 3E BD B2 90 15 04… 0,,9166,0:48.877.823,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,9167,0:48.891.828,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9171,0:48.892.825,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,9172,0:48.892.828,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D ED 8A B9 02 A8 DF 0C 44 B3 3E BD B2 90 15 04… 0,,9176,0:48.893.825,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,9177,0:48.908.831,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA E7 C6 DA 19 22 9A 03 BA 51 C8 05 3E 8B 1E E3… 0,,9181,0:48.909.827,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,9182,0:48.923.833,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9186,0:48.924.830,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,9187,0:48.924.833,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA E7 C6 DA 19 22 9A 03 BA 51 C8 05 3E 8B 1E E3… 0,,9191,0:48.925.830,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,9192,0:48.940.835,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D E4 0D C5 CE 4A C2 99 7F 0C A2 B8 5D 81 27 21… 0,,9196,0:48.941.832,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,9197,0:48.955.837,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9201,0:48.956.834,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,9202,0:48.956.837,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D E4 0D C5 CE 4A C2 99 7F 0C A2 B8 5D 81 27 21… 0,,9206,0:48.957.834,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,9207,0:48.972.839,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 13 6F 02 EB D6 6E B1 46 26 6A 76 E1 A7 46 3B… 0,,9211,0:48.973.836,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,9212,0:48.987.842,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9216,0:48.988.838,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,9217,0:48.988.842,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 13 6F 02 EB D6 6E B1 46 26 6A 76 E1 A7 46 3B… 0,,9221,0:48.989.839,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,9222,0:49.004.844,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 49 5A 42 60 C5 BC 1E A0 7F F5 CC F2 69 FF 85… 0,,9226,0:49.005.841,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,9227,0:49.019.846,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9231,0:49.020.843,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,9232,0:49.020.846,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 49 5A 42 60 C5 BC 1E A0 7F F5 CC F2 69 FF 85… 0,,9236,0:49.021.843,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,9237,0:49.036.848,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 5E 16 AE 21 C9 67 9C 3D 02 DE B4 39 EF 8A 7B… 0,,9241,0:49.037.845,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,9242,0:49.051.850,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9246,0:49.052.847,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,9247,0:49.052.851,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 5E 16 AE 21 C9 67 9C 3D 02 DE B4 39 EF 8A 7B… 0,,9251,0:49.053.847,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,9252,0:49.068.853,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 31 D6 F5 4A 05 44 5D C4 26 8A 50 45 B5 E7 46… 0,,9256,0:49.069.850,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,9257,0:49.083.855,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9261,0:49.084.852,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,9262,0:49.084.855,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 31 D6 F5 4A 05 44 5D C4 26 8A 50 45 B5 E7 46… 0,,9266,0:49.085.852,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,9267,0:49.100.857,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 52 8F 32 AB 23 2A 8A 3D 33 40 E3 03 2A 18 D0 CA… 0,,9271,0:49.101.854,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,9272,0:49.115.859,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9276,0:49.116.856,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,9277,0:49.116.859,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 52 8F 32 AB 23 2A 8A 3D 33 40 E3 03 2A 18 D0 CA… 0,,9281,0:49.117.856,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,9282,0:49.132.862,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 0B 52 4A 64 E0 B2 0F 3D CC 5C D0 9A 14 A6 03… 0,,9286,0:49.133.859,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,9287,0:49.147.864,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9291,0:49.148.861,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,9292,0:49.148.864,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 25 0B 52 4A 64 E0 B2 0F 3D CC 5C D0 9A 14 A6 03… 0,,9296,0:49.149.861,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,9297,0:49.164.866,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F9 DE A1 26 72 4E 14 69 C1 F3 C0 CE D6 6F D1 E9… 0,,9301,0:49.165.863,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,9302,0:49.179.868,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9306,0:49.180.865,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,9307,0:49.180.868,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F9 DE A1 26 72 4E 14 69 C1 F3 C0 CE D6 6F D1 E9… 0,,9311,0:49.181.865,15.005.000 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,9312,0:49.196.871,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 91 86 DF 44 17 5D 5A 37 1C 94 85 29 9F F8 01 B9… 0,,9316,0:49.197.867,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,9317,0:49.211.873,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9321,0:49.212.869,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,9322,0:49.212.873,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 91 86 DF 44 17 5D 5A 37 1C 94 85 29 9F F8 01 B9… 0,,9326,0:49.213.870,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,9327,0:49.228.875,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 B5 B9 44 A1 00 F9 B1 61 A7 C3 AD 02 3B 27 6F… 0,,9331,0:49.229.872,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,9332,0:49.243.877,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9336,0:49.244.874,16.005.041 ms,,,,,[17 SOF],[Frames: 33 - 49] 0,,9337,0:49.260.879,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3F 26 7C 8A F4 97 6F 4E FF 60 10 C7 32 83 59 4A… 0,,9341,0:49.261.876,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,9342,0:49.275.881,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9346,0:49.276.878,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,9347,0:49.276.882,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3F 26 7C 8A F4 97 6F 4E FF 60 10 C7 32 83 59 4A… 0,,9351,0:49.277.879,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,9352,0:49.292.884,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA 4B DE E4 9D 23 40 79 E3 A9 26 B8 52 94 35 4A… 0,,9356,0:49.293.881,14.004.750 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,9357,0:49.307.886,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9361,0:49.308.883,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,9362,0:49.308.886,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA 4B DE E4 9D 23 40 79 E3 A9 26 B8 52 94 35 4A… 0,,9366,0:49.309.883,15.004.916 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,9367,0:49.324.888,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 C7 F8 32 88 53 F6 7B FE C7 25 6A 03 B1 0A A7… 0,,9371,0:49.325.885,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,9372,0:49.339.890,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9376,0:49.340.887,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,9377,0:49.340.891,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 C7 F8 32 88 53 F6 7B FE C7 25 6A 03 B1 0A A7… 0,,9381,0:49.341.887,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,9382,0:49.356.893,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D 9F BE 0D AE 40 64 05 20 CD 2A 20 26 E1 54 F2… 0,,9386,0:49.357.890,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,9387,0:49.371.895,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9391,0:49.372.892,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,9392,0:49.372.895,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D 9F BE 0D AE 40 64 05 20 CD 2A 20 26 E1 54 F2… 0,,9396,0:49.373.892,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,9397,0:49.388.897,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 77 2F 26 47 C7 EE 79 C7 E6 67 26 03 88 FF F4 AC… 0,,9401,0:49.389.894,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,9402,0:49.403.899,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9406,0:49.404.896,2.833 us,,,,,[1 SOF],[Frame: 193] 0,,9407,0:49.404.899,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 77 2F 26 47 C7 EE 79 C7 E6 67 26 03 88 FF F4 AC… 0,,9411,0:49.405.896,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,9412,0:49.420.902,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B A4 87 D0 BD 0C 92 F9 A8 0D 48 14 64 08 BE 68… 0,,9416,0:49.421.899,14.004.750 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,9417,0:49.435.904,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9421,0:49.436.901,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,9422,0:49.436.904,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0B A4 87 D0 BD 0C 92 F9 A8 0D 48 14 64 08 BE 68… 0,,9426,0:49.437.901,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,9427,0:49.452.906,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D F2 D5 85 8B 73 E4 AB 34 C7 00 13 5B 62 7B AB… 0,,9431,0:49.453.903,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,9432,0:49.467.908,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9436,0:49.468.905,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,9437,0:49.468.908,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D F2 D5 85 8B 73 E4 AB 34 C7 00 13 5B 62 7B AB… 0,,9441,0:49.469.905,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,9442,0:49.484.910,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 ED 20 C7 6B 9B 96 AB C1 2C 1F 0E CF 99 E8 A0 8E… 0,,9446,0:49.485.907,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,9447,0:49.499.913,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9451,0:49.500.909,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,9452,0:49.500.913,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 ED 20 C7 6B 9B 96 AB C1 2C 1F 0E CF 99 E8 A0 8E… 0,,9456,0:49.501.910,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,9457,0:49.516.915,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 21 97 76 0F 80 04 25 15 91 80 8F A1 C7 C8 AE 8B… 0,,9461,0:49.517.912,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,9462,0:49.531.917,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9466,0:49.532.914,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,9467,0:49.532.917,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 21 97 76 0F 80 04 25 15 91 80 8F A1 C7 C8 AE 8B… 0,,9471,0:49.533.914,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,9472,0:49.548.919,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 7F E1 6A 03 E8 49 9E E2 90 FB B3 F2 98 49 37… 0,,9476,0:49.549.916,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,9477,0:49.563.921,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9481,0:49.564.918,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,9482,0:49.564.922,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 7F E1 6A 03 E8 49 9E E2 90 FB B3 F2 98 49 37… 0,,9486,0:49.565.918,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,9487,0:49.580.924,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 FE B0 71 3B 46 86 ED 26 2C D7 07 49 5A 8B 6F… 0,,9491,0:49.581.921,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,9492,0:49.595.926,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9496,0:49.596.923,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,9497,0:49.596.926,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 FE B0 71 3B 46 86 ED 26 2C D7 07 49 5A 8B 6F… 0,,9501,0:49.597.923,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,9502,0:49.612.928,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A B4 A2 BC 34 AA 17 F5 41 F1 77 64 92 75 D2 A7… 0,,9506,0:49.613.925,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,9507,0:49.627.930,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9511,0:49.628.927,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,9512,0:49.628.930,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A B4 A2 BC 34 AA 17 F5 41 F1 77 64 92 75 D2 A7… 0,,9516,0:49.629.927,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,9517,0:49.644.933,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A F2 7F 10 65 7B 31 E1 74 00 A2 AE B5 8B 4F F9… 0,,9521,0:49.645.930,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,9522,0:49.659.935,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9526,0:49.660.932,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,9527,0:49.660.935,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A F2 7F 10 65 7B 31 E1 74 00 A2 AE B5 8B 4F F9… 0,,9531,0:49.661.932,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,9532,0:49.676.937,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6B DC 37 4D 74 4C 3E 40 F0 7E 70 5B 63 EB 70 A0… 0,,9536,0:49.677.934,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,9537,0:49.691.939,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9541,0:49.692.936,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,9542,0:49.692.939,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6B DC 37 4D 74 4C 3E 40 F0 7E 70 5B 63 EB 70 A0… 0,,9546,0:49.693.936,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,9547,0:49.708.942,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 34 F2 B2 47 0A 9A 67 A5 4B 4C 95 ED 4E 8D 0B… 0,,9551,0:49.709.938,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,9552,0:49.723.944,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9556,0:49.724.941,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,9557,0:49.724.944,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC 34 F2 B2 47 0A 9A 67 A5 4B 4C 95 ED 4E 8D 0B… 0,,9561,0:49.725.941,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,9562,0:49.740.946,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B2 28 E2 E5 68 86 B7 7E 2D 16 3F E6 34 EB 01 2A… 0,,9566,0:49.741.943,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,9567,0:49.755.948,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9571,0:49.756.945,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,9572,0:49.756.948,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B2 28 E2 E5 68 86 B7 7E 2D 16 3F E6 34 EB 01 2A… 0,,9576,0:49.757.945,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,9577,0:49.772.950,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B DB 19 1E 00 5F 77 47 CC 68 3B 3B 2C 9F C2 70… 0,,9581,0:49.773.947,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,9582,0:49.787.953,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9586,0:49.788.949,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,9587,0:49.788.953,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B DB 19 1E 00 5F 77 47 CC 68 3B 3B 2C 9F C2 70… 0,,9591,0:49.789.950,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,9592,0:49.804.955,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E 88 54 DF A5 C7 35 01 8F CE 64 E2 1D 28 36 6E… 0,,9596,0:49.805.952,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,9597,0:49.819.957,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9601,0:49.820.954,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,9602,0:49.820.957,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E 88 54 DF A5 C7 35 01 8F CE 64 E2 1D 28 36 6E… 0,,9606,0:49.821.954,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,9607,0:49.836.959,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0E 77 25 8E 74 95 1A F7 92 21 89 93 56 F6 6A 99… 0,,9611,0:49.837.956,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,9612,0:49.851.961,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9616,0:49.852.958,16.005.041 ms,,,,,[17 SOF],[Frames: 641 - 657] 0,,9617,0:49.868.964,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 4C 51 C6 B9 A1 89 91 68 68 E7 0E C5 5F 7C 9D… 0,,9621,0:49.869.961,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,9622,0:49.883.966,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9626,0:49.884.963,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,9627,0:49.884.966,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 4C 51 C6 B9 A1 89 91 68 68 E7 0E C5 5F 7C 9D… 0,,9631,0:49.885.963,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,9632,0:49.900.968,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C 17 88 0C DE 3D 36 A9 24 35 0B 23 64 9F EF 54… 0,,9636,0:49.901.965,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,9637,0:49.915.970,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9641,0:49.916.967,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,9642,0:49.916.970,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C 17 88 0C DE 3D 36 A9 24 35 0B 23 64 9F EF 54… 0,,9646,0:49.917.967,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,9647,0:49.932.973,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF F3 1D F8 2A 49 CF EE DA ED 83 AD 82 6F 28 45… 0,,9651,0:49.933.970,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,9652,0:49.947.975,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9656,0:49.948.972,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,9657,0:49.948.975,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF F3 1D F8 2A 49 CF EE DA ED 83 AD 82 6F 28 45… 0,,9661,0:49.949.972,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,9662,0:49.964.977,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B 89 28 D5 73 5E 8E 40 7E 8B 0D 50 44 C0 F7 0A… 0,,9666,0:49.965.974,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,9667,0:49.979.979,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9671,0:49.980.976,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,9672,0:49.980.979,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0B 89 28 D5 73 5E 8E 40 7E 8B 0D 50 44 C0 F7 0A… 0,,9676,0:49.981.976,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,9677,0:49.996.982,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 07 9A 9E 17 74 84 6F E0 CD F8 F0 E5 4B F9 D8… 0,,9681,0:49.997.978,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,9682,0:50.011.984,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9686,0:50.012.981,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,9687,0:50.012.984,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 07 9A 9E 17 74 84 6F E0 CD F8 F0 E5 4B F9 D8… 0,,9691,0:50.013.981,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,9692,0:50.028.986,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 CA F5 B2 1D DF 0B 74 0A A2 0B 43 58 3B 98 4D… 0,,9696,0:50.029.983,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,9697,0:50.043.988,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9701,0:50.044.985,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,9702,0:50.044.988,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 CA F5 B2 1D DF 0B 74 0A A2 0B 43 58 3B 98 4D… 0,,9706,0:50.045.985,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,9707,0:50.060.990,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 9A D6 6A CC C5 E6 3F 26 25 0B 11 5C 04 15 19… 0,,9711,0:50.061.987,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,9712,0:50.075.993,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9716,0:50.076.989,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,9717,0:50.076.993,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 9A D6 6A CC C5 E6 3F 26 25 0B 11 5C 04 15 19… 0,,9721,0:50.077.990,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,9722,0:50.092.995,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3C D9 86 A2 C5 DD 30 6B 75 9F 61 22 62 89 2A DE… 0,,9726,0:50.093.992,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,9727,0:50.107.997,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9731,0:50.108.994,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,9732,0:50.108.997,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3C D9 86 A2 C5 DD 30 6B 75 9F 61 22 62 89 2A DE… 0,,9736,0:50.109.994,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,9737,0:50.124.999,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 12 0D 98 BA C4 8D F5 49 EF 49 97 BE 3A 97 25… 0,,9741,0:50.125.996,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,9742,0:50.140.001,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9746,0:50.140.998,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,9747,0:50.141.002,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 12 0D 98 BA C4 8D F5 49 EF 49 97 BE 3A 97 25… 0,,9751,0:50.141.998,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,9752,0:50.157.004,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9F 71 49 FB 29 02 65 C4 7B E6 F7 2E 40 6C 4D 35… 0,,9756,0:50.158.001,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,9757,0:50.172.006,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9761,0:50.173.003,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,9762,0:50.173.006,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9F 71 49 FB 29 02 65 C4 7B E6 F7 2E 40 6C 4D 35… 0,,9766,0:50.174.003,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,9767,0:50.189.008,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 C0 1A 13 50 3E B1 BC 13 55 18 09 54 7A 9B B5… 0,,9771,0:50.190.005,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,9772,0:50.204.010,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9776,0:50.205.007,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,9777,0:50.205.010,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 C0 1A 13 50 3E B1 BC 13 55 18 09 54 7A 9B B5… 0,,9781,0:50.206.007,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,9782,0:50.221.013,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 3B 85 9C 3C 02 CC 6E 9B 85 37 F0 FE CD 65 D1… 0,,9786,0:50.222.010,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,9787,0:50.236.015,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9791,0:50.237.012,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,9792,0:50.237.015,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 3B 85 9C 3C 02 CC 6E 9B 85 37 F0 FE CD 65 D1… 0,,9796,0:50.238.012,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,9797,0:50.253.017,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B 4F AB 80 6E E0 CA EC B2 A7 74 6C 64 64 5F 75… 0,,9801,0:50.254.014,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,9802,0:50.268.019,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9806,0:50.269.016,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,9807,0:50.269.019,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B 4F AB 80 6E E0 CA EC B2 A7 74 6C 64 64 5F 75… 0,,9811,0:50.270.016,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,9812,0:50.285.022,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 87 FD 73 89 5D 62 F2 EE 28 71 C4 07 9A 68 1A B9… 0,,9816,0:50.286.018,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,9817,0:50.300.024,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9821,0:50.301.021,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,9822,0:50.301.024,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 87 FD 73 89 5D 62 F2 EE 28 71 C4 07 9A 68 1A B9… 0,,9826,0:50.302.021,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,9827,0:50.317.026,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 3D 2D 70 AE BA 60 A8 5E ED 42 4E F8 35 5C FB… 0,,9831,0:50.318.023,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,9832,0:50.332.028,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9836,0:50.333.025,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,9837,0:50.333.028,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 3D 2D 70 AE BA 60 A8 5E ED 42 4E F8 35 5C FB… 0,,9841,0:50.334.025,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,9842,0:50.349.030,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C 7B C4 EB E8 CA 34 27 40 1C 62 92 06 F6 7C C0… 0,,9846,0:50.350.027,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,9847,0:50.364.033,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9851,0:50.365.029,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,9852,0:50.365.033,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C 7B C4 EB E8 CA 34 27 40 1C 62 92 06 F6 7C C0… 0,,9856,0:50.366.030,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,9857,0:50.381.035,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 E1 74 6F 65 8F 45 5A 1B 9A D7 C6 77 45 DC 2C… 0,,9861,0:50.382.032,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,9862,0:50.396.037,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9866,0:50.397.034,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,9867,0:50.397.037,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 E1 74 6F 65 8F 45 5A 1B 9A D7 C6 77 45 DC 2C… 0,,9871,0:50.398.034,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,9872,0:50.413.039,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE 63 DA E1 21 78 0C 18 A8 32 A7 77 7B 1A 38 22… 0,,9876,0:50.414.036,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,9877,0:50.428.041,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9881,0:50.429.038,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,9882,0:50.429.042,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE 63 DA E1 21 78 0C 18 A8 32 A7 77 7B 1A 38 22… 0,,9886,0:50.430.038,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,9887,0:50.445.044,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AD 21 B1 E2 9D E8 60 54 B6 67 08 5D 4D BD 0F F4… 0,,9891,0:50.446.041,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,9892,0:50.460.046,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9896,0:50.461.043,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,9897,0:50.461.046,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AD 21 B1 E2 9D E8 60 54 B6 67 08 5D 4D BD 0F F4… 0,,9901,0:50.462.043,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,9902,0:50.477.048,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 71 CD C1 F6 97 86 C2 A0 DE CB A3 3A 72 3D 25… 0,,9906,0:50.478.045,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,9907,0:50.492.050,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9911,0:50.493.047,16.005.041 ms,,,,,[17 SOF],[Frames: 1281 - 1297] 0,,9912,0:50.509.053,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 09 62 4C 5C 39 4D 2D 7F 67 44 00 2B 99 91 92 5D… 0,,9916,0:50.510.050,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,9917,0:50.524.055,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9921,0:50.525.052,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,9922,0:50.525.055,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 09 62 4C 5C 39 4D 2D 7F 67 44 00 2B 99 91 92 5D… 0,,9926,0:50.526.052,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,9927,0:50.541.057,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3C 7E F2 A6 BD A4 E2 68 28 EB 30 2B CE 4F D7 0E… 0,,9931,0:50.542.054,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,9932,0:50.556.059,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9936,0:50.557.056,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,9937,0:50.557.059,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3C 7E F2 A6 BD A4 E2 68 28 EB 30 2B CE 4F D7 0E… 0,,9941,0:50.558.056,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,9942,0:50.573.062,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 B0 26 1C D3 0F A8 4E 44 2C 8A 9F 69 4D 7F B1… 0,,9946,0:50.574.058,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,9947,0:50.588.064,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9951,0:50.589.061,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,9952,0:50.589.064,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C6 B0 26 1C D3 0F A8 4E 44 2C 8A 9F 69 4D 7F B1… 0,,9956,0:50.590.061,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,9957,0:50.605.066,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 A7 87 18 D8 2F 87 E0 54 97 D1 C8 FF 89 37 34… 0,,9961,0:50.606.063,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,9962,0:50.620.068,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9966,0:50.621.065,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,9967,0:50.621.068,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 A7 87 18 D8 2F 87 E0 54 97 D1 C8 FF 89 37 34… 0,,9971,0:50.622.065,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,9972,0:50.637.070,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C0 6B 57 40 27 E0 F4 A9 5A 21 93 B3 87 C5 39 66… 0,,9976,0:50.638.067,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,9977,0:50.652.073,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9981,0:50.653.069,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,9982,0:50.653.073,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C0 6B 57 40 27 E0 F4 A9 5A 21 93 B3 87 C5 39 66… 0,,9986,0:50.654.070,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,9987,0:50.669.075,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 E6 31 50 F3 9D 5D 67 0B 9C B5 D2 9A EE 5E EF… 0,,9991,0:50.670.072,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,9992,0:50.684.077,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9996,0:50.685.074,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,9997,0:50.685.077,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 E6 31 50 F3 9D 5D 67 0B 9C B5 D2 9A EE 5E EF… 0,,10001,0:50.686.074,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,10002,0:50.701.079,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E A3 4D 38 80 DB E3 F1 04 6F 0E 4F 4B A7 20 11… 0,,10006,0:50.702.076,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,10007,0:50.716.081,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10011,0:50.717.078,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,10012,0:50.717.082,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E A3 4D 38 80 DB E3 F1 04 6F 0E 4F 4B A7 20 11… 0,,10016,0:50.718.078,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,10017,0:50.733.084,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 6B A3 45 A9 D9 CC A3 40 FC 50 4C 42 7B 0F D9… 0,,10021,0:50.734.081,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,10022,0:50.748.086,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10026,0:50.749.083,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,10027,0:50.749.086,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 6B A3 45 A9 D9 CC A3 40 FC 50 4C 42 7B 0F D9… 0,,10031,0:50.750.083,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,10032,0:50.765.088,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 A7 36 B0 2D AA 58 84 C4 98 43 67 6F A1 C3 57… 0,,10036,0:50.766.085,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,10037,0:50.780.090,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10041,0:50.781.087,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,10042,0:50.781.090,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 A7 36 B0 2D AA 58 84 C4 98 43 67 6F A1 C3 57… 0,,10046,0:50.782.087,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,10047,0:50.797.093,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 0B F6 B5 4A 36 C2 DD 75 D7 81 01 39 E7 38 5E… 0,,10051,0:50.798.090,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,10052,0:50.812.095,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10056,0:50.813.092,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,10057,0:50.813.095,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 0B F6 B5 4A 36 C2 DD 75 D7 81 01 39 E7 38 5E… 0,,10061,0:50.814.092,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,10062,0:50.829.097,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B7 29 D0 EF 80 7E 3F 41 74 82 E8 EC 90 12 86 4B… 0,,10066,0:50.830.094,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,10067,0:50.844.099,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10071,0:50.845.096,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,10072,0:50.845.099,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B7 29 D0 EF 80 7E 3F 41 74 82 E8 EC 90 12 86 4B… 0,,10076,0:50.846.096,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,10077,0:50.861.102,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF 5A FE 00 5C 4E 78 A4 7C 8E 22 98 6D CD ED F6… 0,,10081,0:50.862.098,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,10082,0:50.876.104,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10086,0:50.877.100,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,10087,0:50.877.104,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BF 5A FE 00 5C 4E 78 A4 7C 8E 22 98 6D CD ED F6… 0,,10091,0:50.878.101,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,10092,0:50.893.106,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA 0F 19 C3 0B DF 60 C6 15 48 7A 96 35 3C 56 FA… 0,,10096,0:50.894.103,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,10097,0:50.908.108,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10101,0:50.909.105,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,10102,0:50.909.108,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA 0F 19 C3 0B DF 60 C6 15 48 7A 96 35 3C 56 FA… 0,,10106,0:50.910.105,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,10107,0:50.925.110,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 3F 28 5B 51 A0 F4 A9 1E 3B BA 98 A2 BA 30 50… 0,,10111,0:50.926.107,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,10112,0:50.940.112,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10116,0:50.941.109,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,10117,0:50.941.113,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 3F 28 5B 51 A0 F4 A9 1E 3B BA 98 A2 BA 30 50… 0,,10121,0:50.942.110,15.004.916 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,10122,0:50.957.115,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 F9 DE 35 47 23 8E C7 16 69 02 1F B4 40 F0 28… 0,,10126,0:50.958.112,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,10127,0:50.972.117,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10131,0:50.973.114,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,10132,0:50.973.117,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 F9 DE 35 47 23 8E C7 16 69 02 1F B4 40 F0 28… 0,,10136,0:50.974.114,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,10137,0:50.989.119,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6E 2D 06 A9 F1 EA 3B 8C EA 05 19 89 75 33 8B 5F… 0,,10141,0:50.990.116,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,10142,0:51.004.121,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10146,0:51.005.118,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,10147,0:51.005.122,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6E 2D 06 A9 F1 EA 3B 8C EA 05 19 89 75 33 8B 5F… 0,,10151,0:51.006.118,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,10152,0:51.021.124,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D 1A 66 27 88 9A 4E B2 49 A9 F9 E1 F3 CA 2D 0F… 0,,10156,0:51.022.121,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,10157,0:51.036.126,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10161,0:51.037.123,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,10162,0:51.037.126,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 1A 66 27 88 9A 4E B2 49 A9 F9 E1 F3 CA 2D 0F… 0,,10166,0:51.038.123,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,10167,0:51.053.128,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F0 B6 73 CF 23 69 40 26 21 DE C4 B0 78 A3 43 B2… 0,,10171,0:51.054.125,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,10172,0:51.068.130,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10176,0:51.069.127,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,10177,0:51.069.130,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F0 B6 73 CF 23 69 40 26 21 DE C4 B0 78 A3 43 B2… 0,,10181,0:51.070.127,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,10182,0:51.085.133,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F 0C DA BC 78 60 33 30 60 EF F4 20 B8 71 E1 1C… 0,,10186,0:51.086.130,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,10187,0:51.100.135,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10191,0:51.101.132,16.005.041 ms,,,,,[17 SOF],[Frames: 1889 - 1905] 0,,10192,0:51.117.137,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 47 0F A2 15 57 A7 E7 B7 55 27 7B B9 62 6E E5 45… 0,,10196,0:51.118.134,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,10197,0:51.132.139,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10201,0:51.133.136,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,10202,0:51.133.139,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 47 0F A2 15 57 A7 E7 B7 55 27 7B B9 62 6E E5 45… 0,,10206,0:51.134.136,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,10207,0:51.149.142,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 C2 6F 6E 27 07 2C 43 A4 0A 60 C8 52 6A 68 B3… 0,,10211,0:51.150.138,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,10212,0:51.164.144,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10216,0:51.165.140,2.833 us,,,,,[1 SOF],[Frame: 1953] 0,,10217,0:51.165.144,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 C2 6F 6E 27 07 2C 43 A4 0A 60 C8 52 6A 68 B3… 0,,10221,0:51.166.141,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,10222,0:51.181.146,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E 8B FB 80 53 D9 E2 29 38 FA 13 5A 9C 4B 71 E9… 0,,10226,0:51.182.143,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,10227,0:51.196.148,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10231,0:51.197.145,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,10232,0:51.197.148,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E 8B FB 80 53 D9 E2 29 38 FA 13 5A 9C 4B 71 E9… 0,,10236,0:51.198.145,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,10237,0:51.213.150,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 76 E5 4D B5 7F D0 67 86 1D 5E B1 E6 3D A2 2E… 0,,10241,0:51.214.147,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,10242,0:51.228.153,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10246,0:51.229.149,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,10247,0:51.229.153,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 76 E5 4D B5 7F D0 67 86 1D 5E B1 E6 3D A2 2E… 0,,10251,0:51.230.149,15.005.000 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,10252,0:51.245.155,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0C B4 E2 F4 AF 86 66 88 23 9E BA E9 0D F1 69 8B… 0,,10256,0:51.246.152,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,10257,0:51.260.157,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10261,0:51.261.154,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,10262,0:51.261.157,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0C B4 E2 F4 AF 86 66 88 23 9E BA E9 0D F1 69 8B… 0,,10266,0:51.262.154,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,10267,0:51.277.159,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B7 0A 64 76 AB 99 AA 20 34 7B 7D A8 C9 C9 8B BC… 0,,10271,0:51.278.156,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,10272,0:51.292.161,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10276,0:51.293.158,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,10277,0:51.293.161,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B7 0A 64 76 AB 99 AA 20 34 7B 7D A8 C9 C9 8B BC… 0,,10281,0:51.294.158,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,10282,0:51.309.164,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D 08 82 C6 B9 BF 6A C4 59 99 D4 DD 9D 39 90 2F… 0,,10286,0:51.310.161,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,10287,0:51.324.166,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10291,0:51.325.163,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,10292,0:51.325.166,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D 08 82 C6 B9 BF 6A C4 59 99 D4 DD 9D 39 90 2F… 0,,10296,0:51.326.163,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,10297,0:51.341.168,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E8 3D 97 BA 83 4D 56 DC BB 89 4E 55 98 27 6E 41… 0,,10301,0:51.342.165,14.004.750 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,10302,0:51.356.170,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10306,0:51.357.167,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,10307,0:51.357.170,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E8 3D 97 BA 83 4D 56 DC BB 89 4E 55 98 27 6E 41… 0,,10311,0:51.358.167,15.004.916 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,10312,0:51.373.173,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B FC C3 92 42 49 25 51 E2 06 9A 3B AD A8 F5 16… 0,,10316,0:51.374.169,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,10317,0:51.388.175,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10321,0:51.389.172,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,10322,0:51.389.175,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B FC C3 92 42 49 25 51 E2 06 9A 3B AD A8 F5 16… 0,,10326,0:51.390.172,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,10327,0:51.405.177,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 1E F7 41 7A 92 97 58 DE F9 9F 7E D2 D9 25 E8… 0,,10331,0:51.406.174,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,10332,0:51.420.179,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10336,0:51.421.176,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,10337,0:51.421.179,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 1E F7 41 7A 92 97 58 DE F9 9F 7E D2 D9 25 E8… 0,,10341,0:51.422.176,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,10342,0:51.437.181,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D BD 8C BB D9 20 75 D0 A8 9B 1A 4E C3 3A B5 3E… 0,,10346,0:51.438.178,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,10347,0:51.452.184,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10351,0:51.453.180,2.833 us,,,,,[1 SOF],[Frame: 193] 0,,10352,0:51.453.184,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D BD 8C BB D9 20 75 D0 A8 9B 1A 4E C3 3A B5 3E… 0,,10356,0:51.454.181,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,10357,0:51.469.186,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D 38 63 A7 F7 4C 65 D2 2C 68 A2 BF 73 01 8E B8… 0,,10361,0:51.470.183,14.004.750 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,10362,0:51.484.188,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10366,0:51.485.185,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,10367,0:51.485.188,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D 38 63 A7 F7 4C 65 D2 2C 68 A2 BF 73 01 8E B8… 0,,10371,0:51.486.185,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,10372,0:51.501.190,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6B DC A4 16 EF 7D BC F5 B3 C3 A6 73 30 BA 00 DE… 0,,10376,0:51.502.187,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,10377,0:51.516.192,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10381,0:51.517.189,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,10382,0:51.517.193,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6B DC A4 16 EF 7D BC F5 B3 C3 A6 73 30 BA 00 DE… 0,,10386,0:51.518.189,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,10387,0:51.533.195,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD 35 94 D2 EB 22 8F EE A8 4C C4 A5 B0 2F 51 7C… 0,,10391,0:51.534.192,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,10392,0:51.548.197,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10396,0:51.549.194,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,10397,0:51.549.197,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD 35 94 D2 EB 22 8F EE A8 4C C4 A5 B0 2F 51 7C… 0,,10401,0:51.550.194,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,10402,0:51.565.199,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 99 8A 40 E4 EC 87 46 3D 8B 64 2E B5 47 89 21 24… 0,,10406,0:51.566.196,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,10407,0:51.580.201,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10411,0:51.581.198,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,10412,0:51.581.201,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 99 8A 40 E4 EC 87 46 3D 8B 64 2E B5 47 89 21 24… 0,,10416,0:51.582.198,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,10417,0:51.597.204,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D 60 EF 07 D1 5A 48 6A 2A 23 5E 86 13 92 C5 52… 0,,10421,0:51.598.201,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,10422,0:51.612.206,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10426,0:51.613.203,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,10427,0:51.613.206,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D 60 EF 07 D1 5A 48 6A 2A 23 5E 86 13 92 C5 52… 0,,10431,0:51.614.203,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,10432,0:51.629.208,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E 70 4E C3 93 AB 2F 92 BC 3A 4F 48 12 9F B4 89… 0,,10436,0:51.630.205,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,10437,0:51.644.210,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10441,0:51.645.207,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,10442,0:51.645.210,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E 70 4E C3 93 AB 2F 92 BC 3A 4F 48 12 9F B4 89… 0,,10446,0:51.646.207,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,10447,0:51.661.213,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 30 BD DC 25 FE 45 37 C7 39 A5 0D 17 7E E6 4F… 0,,10451,0:51.662.209,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,10452,0:51.676.215,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10456,0:51.677.212,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,10457,0:51.677.215,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 30 BD DC 25 FE 45 37 C7 39 A5 0D 17 7E E6 4F… 0,,10461,0:51.678.212,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,10462,0:51.693.217,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 29 9C 3F FB 18 1B 7F 93 3F F1 2F 15 52 03 F9… 0,,10466,0:51.694.214,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,10467,0:51.708.219,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10471,0:51.709.216,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,10472,0:51.709.219,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 29 9C 3F FB 18 1B 7F 93 3F F1 2F 15 52 03 F9… 0,,10476,0:51.710.216,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,10477,0:51.725.221,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 58 1C 43 A4 A7 F5 C4 98 53 CC 5C 3A D8 36 03… 0,,10481,0:51.726.218,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,10482,0:51.740.224,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10486,0:51.741.220,16.005.041 ms,,,,,[17 SOF],[Frames: 481 - 497] 0,,10487,0:51.757.226,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3B 71 17 4E 0D 01 D3 9C 34 49 2E 10 D1 9A 12 B4… 0,,10491,0:51.758.223,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,10492,0:51.772.228,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10496,0:51.773.225,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,10497,0:51.773.228,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3B 71 17 4E 0D 01 D3 9C 34 49 2E 10 D1 9A 12 B4… 0,,10501,0:51.774.225,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,10502,0:51.789.230,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 5B 72 BC B4 0E 2D 92 84 D7 D4 0D 33 80 20 42… 0,,10506,0:51.790.227,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,10507,0:51.804.232,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10511,0:51.805.229,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,10512,0:51.805.233,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 5B 72 BC B4 0E 2D 92 84 D7 D4 0D 33 80 20 42… 0,,10516,0:51.806.229,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,10517,0:51.821.235,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 B6 EA C9 BC B5 A5 19 A9 F1 D6 F2 64 26 08 E2… 0,,10521,0:51.822.232,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,10522,0:51.836.237,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10526,0:51.837.234,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,10527,0:51.837.237,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 B6 EA C9 BC B5 A5 19 A9 F1 D6 F2 64 26 08 E2… 0,,10531,0:51.838.234,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,10532,0:51.853.239,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB ED 73 FA BA FD 77 D8 3B 9D B0 90 EB BE 21 CC… 0,,10536,0:51.854.236,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,10537,0:51.868.241,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10541,0:51.869.238,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,10542,0:51.869.241,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB ED 73 FA BA FD 77 D8 3B 9D B0 90 EB BE 21 CC… 0,,10546,0:51.870.238,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,10547,0:51.885.244,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 39 E5 B0 5E 0B 75 1F 32 3C B7 3F 96 4C 11 9F… 0,,10551,0:51.886.241,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,10552,0:51.900.246,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10556,0:51.901.243,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,10557,0:51.901.246,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 39 E5 B0 5E 0B 75 1F 32 3C B7 3F 96 4C 11 9F… 0,,10561,0:51.902.243,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,10562,0:51.917.248,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 59 4B F4 29 69 EE C8 29 E1 B4 0B B3 0F 17 3A 24… 0,,10566,0:51.918.245,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,10567,0:51.932.250,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10571,0:51.933.247,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,10572,0:51.933.250,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 59 4B F4 29 69 EE C8 29 E1 B4 0B B3 0F 17 3A 24… 0,,10576,0:51.934.247,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,10577,0:51.949.253,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 A2 31 B6 00 88 D4 71 6A 9C A9 A5 59 DB C8 D7… 0,,10581,0:51.950.249,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,10582,0:51.964.255,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10586,0:51.965.252,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,10587,0:51.965.255,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 A2 31 B6 00 88 D4 71 6A 9C A9 A5 59 DB C8 D7… 0,,10591,0:51.966.252,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,10592,0:51.981.257,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 1E C5 D5 2A D7 B1 E8 D9 17 93 96 C8 C3 3F 1B… 0,,10596,0:51.982.254,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,10597,0:51.996.259,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10601,0:51.997.256,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,10602,0:51.997.259,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 1E C5 D5 2A D7 B1 E8 D9 17 93 96 C8 C3 3F 1B… 0,,10606,0:51.998.256,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,10607,0:52.013.261,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 E3 CD 8C D0 A2 A4 94 C1 0B F4 F0 22 01 48 84… 0,,10611,0:52.014.258,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,10612,0:52.028.264,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10616,0:52.029.260,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,10617,0:52.029.264,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 E3 CD 8C D0 A2 A4 94 C1 0B F4 F0 22 01 48 84… 0,,10621,0:52.030.261,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,10622,0:52.045.266,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 09 32 F4 63 DB 39 29 2C EA 84 90 CC 87 F8 27… 0,,10626,0:52.046.263,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,10627,0:52.060.268,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10631,0:52.061.265,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,10632,0:52.061.268,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 09 32 F4 63 DB 39 29 2C EA 84 90 CC 87 F8 27… 0,,10636,0:52.062.265,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,10637,0:52.077.270,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E AB 9D 40 E0 E8 0B 31 26 F2 DF 7E 6D 18 03 40… 0,,10641,0:52.078.267,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,10642,0:52.092.272,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10646,0:52.093.269,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,10647,0:52.093.273,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E AB 9D 40 E0 E8 0B 31 26 F2 DF 7E 6D 18 03 40… 0,,10651,0:52.094.269,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,10652,0:52.109.275,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A E2 9D 68 F0 BB 46 98 2A 72 B4 56 28 21 A4 8C… 0,,10656,0:52.110.272,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,10657,0:52.124.277,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10661,0:52.125.274,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,10662,0:52.125.277,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A E2 9D 68 F0 BB 46 98 2A 72 B4 56 28 21 A4 8C… 0,,10666,0:52.126.274,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,10667,0:52.141.279,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 02 65 2F 78 7F FA 05 B5 14 C3 7A 22 43 5B 71… 0,,10671,0:52.142.276,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,10672,0:52.156.281,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10676,0:52.157.278,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,10677,0:52.157.281,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 02 65 2F 78 7F FA 05 B5 14 C3 7A 22 43 5B 71… 0,,10681,0:52.158.278,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,10682,0:52.173.284,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9F C5 70 63 BA 60 CD B7 A9 F1 9C 19 02 68 60 48… 0,,10686,0:52.174.281,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,10687,0:52.188.286,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10691,0:52.189.283,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,10692,0:52.189.286,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9F C5 70 63 BA 60 CD B7 A9 F1 9C 19 02 68 60 48… 0,,10696,0:52.190.283,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,10697,0:52.205.288,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF 60 8A 92 B8 A7 11 3D A4 BB F2 4F 77 2C E1 AF… 0,,10701,0:52.206.285,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,10702,0:52.220.290,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10706,0:52.221.287,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,10707,0:52.221.290,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BF 60 8A 92 B8 A7 11 3D A4 BB F2 4F 77 2C E1 AF… 0,,10711,0:52.222.287,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,10712,0:52.237.293,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D 90 85 D3 F5 0D 28 C4 96 73 87 5F 55 6D 3C B0… 0,,10716,0:52.238.289,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,10717,0:52.252.295,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10721,0:52.253.292,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,10722,0:52.253.295,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D 90 85 D3 F5 0D 28 C4 96 73 87 5F 55 6D 3C B0… 0,,10726,0:52.254.292,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,10727,0:52.269.297,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 8E 74 90 6A 8D 04 95 B2 E9 5D 7B 19 32 CD 34… 0,,10731,0:52.270.294,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,10732,0:52.284.299,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10736,0:52.285.296,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,10737,0:52.285.299,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 8E 74 90 6A 8D 04 95 B2 E9 5D 7B 19 32 CD 34… 0,,10741,0:52.286.296,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,10742,0:52.301.301,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 E0 16 6D A0 17 F6 11 A4 64 AA B8 FA A9 B9 11… 0,,10746,0:52.302.298,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,10747,0:52.316.304,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10751,0:52.317.300,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,10752,0:52.317.304,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 E0 16 6D A0 17 F6 11 A4 64 AA B8 FA A9 B9 11… 0,,10756,0:52.318.301,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,10757,0:52.333.306,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 12 33 CF BB A2 B8 0E FC 1C BB 68 B0 74 78 43 65… 0,,10761,0:52.334.303,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,10762,0:52.348.308,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10766,0:52.349.305,16.005.041 ms,,,,,[17 SOF],[Frames: 1089 - 1105] 0,,10767,0:52.365.310,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 78 65 C7 8D 0D D6 9F 05 DC 73 B9 45 11 BD 96 BD… 0,,10771,0:52.366.307,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,10772,0:52.380.312,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10776,0:52.381.309,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,10777,0:52.381.313,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 78 65 C7 8D 0D D6 9F 05 DC 73 B9 45 11 BD 96 BD… 0,,10781,0:52.382.309,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,10782,0:52.397.315,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 1C E9 B4 3C 50 79 2C 6E 04 19 B5 88 E0 36 88… 0,,10786,0:52.398.312,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,10787,0:52.412.317,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10791,0:52.413.314,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,10792,0:52.413.317,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 1C E9 B4 3C 50 79 2C 6E 04 19 B5 88 E0 36 88… 0,,10796,0:52.414.314,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,10797,0:52.429.319,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 0C 3E BF BA 61 AC 52 CF 0A C5 B0 5B 31 D2 16… 0,,10801,0:52.430.316,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,10802,0:52.444.321,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10806,0:52.445.318,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,10807,0:52.445.321,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 0C 3E BF BA 61 AC 52 CF 0A C5 B0 5B 31 D2 16… 0,,10811,0:52.446.318,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,10812,0:52.461.324,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D6 6A 60 72 E8 35 CD 0B D5 99 79 FE 20 72 2A E1… 0,,10816,0:52.462.321,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,10817,0:52.476.326,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10821,0:52.477.323,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,10822,0:52.477.326,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D6 6A 60 72 E8 35 CD 0B D5 99 79 FE 20 72 2A E1… 0,,10826,0:52.478.323,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,10827,0:52.493.328,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 FF 82 9C 38 5A 9C 36 E6 3B 74 C3 31 F4 23 B8… 0,,10831,0:52.494.325,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,10832,0:52.508.330,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10836,0:52.509.327,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,10837,0:52.509.330,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 FF 82 9C 38 5A 9C 36 E6 3B 74 C3 31 F4 23 B8… 0,,10841,0:52.510.327,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,10842,0:52.525.333,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5C 1B F0 EC 10 46 82 A5 E1 87 A3 51 AB 6D 77 F9… 0,,10846,0:52.526.329,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,10847,0:52.540.335,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10851,0:52.541.331,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,10852,0:52.541.335,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5C 1B F0 EC 10 46 82 A5 E1 87 A3 51 AB 6D 77 F9… 0,,10856,0:52.542.332,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,10857,0:52.557.337,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 CF 1B 38 3E D0 24 B3 1E 8E 8B FD BD 79 30 68… 0,,10861,0:52.558.334,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,10862,0:52.572.339,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10866,0:52.573.336,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,10867,0:52.573.339,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 CF 1B 38 3E D0 24 B3 1E 8E 8B FD BD 79 30 68… 0,,10871,0:52.574.336,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,10872,0:52.589.341,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AE 1A 15 2B CD 84 6A A9 9B 68 CB A5 BE 33 D0 0B… 0,,10876,0:52.590.338,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,10877,0:52.604.343,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10881,0:52.605.340,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,10882,0:52.605.344,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AE 1A 15 2B CD 84 6A A9 9B 68 CB A5 BE 33 D0 0B… 0,,10886,0:52.606.341,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,10887,0:52.621.346,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 00 11 87 7B 5E 61 34 E4 DF B9 1F DC 40 44 05… 0,,10891,0:52.622.343,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,10892,0:52.636.348,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10896,0:52.637.345,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,10897,0:52.637.348,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 00 11 87 7B 5E 61 34 E4 DF B9 1F DC 40 44 05… 0,,10901,0:52.638.345,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,10902,0:52.653.350,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 AE 18 2E F6 34 F2 93 66 F3 D6 9E ED 1A 2B C2… 0,,10906,0:52.654.347,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,10907,0:52.668.352,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10911,0:52.669.349,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,10912,0:52.669.353,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 25 AE 18 2E F6 34 F2 93 66 F3 D6 9E ED 1A 2B C2… 0,,10916,0:52.670.349,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,10917,0:52.685.355,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF F5 E2 D2 0D 49 E3 FD 97 8E 79 9D 55 13 C4 B4… 0,,10921,0:52.686.352,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,10922,0:52.700.357,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10926,0:52.701.354,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,10927,0:52.701.357,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF F5 E2 D2 0D 49 E3 FD 97 8E 79 9D 55 13 C4 B4… 0,,10931,0:52.702.354,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,10932,0:52.717.359,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AA B6 C4 F9 9E 2A C1 E3 E2 A5 EB FF AA 14 91 C0… 0,,10936,0:52.718.356,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,10937,0:52.732.361,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10941,0:52.733.358,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,10942,0:52.733.361,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AA B6 C4 F9 9E 2A C1 E3 E2 A5 EB FF AA 14 91 C0… 0,,10946,0:52.734.358,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,10947,0:52.749.364,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 13 3A BC 18 A1 D9 25 E0 3C 01 44 A8 4D 8D 7F BD… 0,,10951,0:52.750.361,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,10952,0:52.764.366,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10956,0:52.765.363,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,10957,0:52.765.366,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 13 3A BC 18 A1 D9 25 E0 3C 01 44 A8 4D 8D 7F BD… 0,,10961,0:52.766.363,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,10962,0:52.781.368,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 4E D6 F2 6F 47 E0 1C C9 70 4B 1D 14 E7 70 84… 0,,10966,0:52.782.365,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,10967,0:52.796.370,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10971,0:52.797.367,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,10972,0:52.797.370,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 4E D6 F2 6F 47 E0 1C C9 70 4B 1D 14 E7 70 84… 0,,10976,0:52.798.367,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,10977,0:52.813.373,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 77 79 59 8B 3A 05 C5 E1 9E 36 BA 91 61 27 6C… 0,,10981,0:52.814.369,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,10982,0:52.828.375,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10986,0:52.829.371,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,10987,0:52.829.375,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 77 79 59 8B 3A 05 C5 E1 9E 36 BA 91 61 27 6C… 0,,10991,0:52.830.372,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,10992,0:52.845.377,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A6 02 F8 4B 7D CE 75 B1 E3 01 F4 CC 00 9A EC 50… 0,,10996,0:52.846.374,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,10997,0:52.860.379,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11001,0:52.861.376,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,11002,0:52.861.379,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A6 02 F8 4B 7D CE 75 B1 E3 01 F4 CC 00 9A EC 50… 0,,11006,0:52.862.376,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,11007,0:52.877.381,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E 0F C0 F5 01 8D 6B B4 96 00 A3 C7 CF 39 0D 3B… 0,,11011,0:52.878.378,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,11012,0:52.892.383,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11016,0:52.893.380,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,11017,0:52.893.384,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E 0F C0 F5 01 8D 6B B4 96 00 A3 C7 CF 39 0D 3B… 0,,11021,0:52.894.380,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,11022,0:52.909.386,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 67 A7 7A 84 5F F2 2C 6D 25 2D 5F 65 A7 4E 41… 0,,11026,0:52.910.383,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,11027,0:52.924.388,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11031,0:52.925.385,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,11032,0:52.925.388,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 67 A7 7A 84 5F F2 2C 6D 25 2D 5F 65 A7 4E 41… 0,,11036,0:52.926.385,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,11037,0:52.941.390,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1A 2D 21 ED E9 E7 68 51 4C 13 A8 E5 2D 5C E2 EE… 0,,11041,0:52.942.387,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,11042,0:52.956.392,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11046,0:52.957.389,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,11047,0:52.957.392,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1A 2D 21 ED E9 E7 68 51 4C 13 A8 E5 2D 5C E2 EE… 0,,11051,0:52.958.389,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,11052,0:52.973.395,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D4 9A 9B 2C 9B A0 7D 48 9F 15 76 79 A2 74 3A 33… 0,,11056,0:52.974.392,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,11057,0:52.988.397,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11061,0:52.989.394,16.005.041 ms,,,,,[17 SOF],[Frames: 1729 - 1745] 0,,11062,0:53.005.399,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 89 AE 78 DA 78 17 3B 67 C3 2A D8 BE BE FB 40 E7… 0,,11066,0:53.006.396,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,11067,0:53.020.401,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11071,0:53.021.398,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,11072,0:53.021.401,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 89 AE 78 DA 78 17 3B 67 C3 2A D8 BE BE FB 40 E7… 0,,11076,0:53.022.398,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,11077,0:53.037.404,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 29 4A 18 01 A2 F8 4D 82 D2 30 DD 96 48 FC 62… 0,,11081,0:53.038.400,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,11082,0:53.052.406,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11086,0:53.053.403,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,11087,0:53.053.406,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 29 4A 18 01 A2 F8 4D 82 D2 30 DD 96 48 FC 62… 0,,11091,0:53.054.403,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,11092,0:53.069.408,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 19 32 DF 8F CE 0E A0 41 1B 86 B7 6D 47 96 84 01… 0,,11096,0:53.070.405,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,11097,0:53.084.410,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11101,0:53.085.407,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,11102,0:53.085.410,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 19 32 DF 8F CE 0E A0 41 1B 86 B7 6D 47 96 84 01… 0,,11106,0:53.086.407,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,11107,0:53.101.412,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A 69 E3 46 29 32 21 48 DB 26 8C 03 D9 25 1F B1… 0,,11111,0:53.102.409,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,11112,0:53.116.415,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11116,0:53.117.411,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,11117,0:53.117.415,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A 69 E3 46 29 32 21 48 DB 26 8C 03 D9 25 1F B1… 0,,11121,0:53.118.412,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,11122,0:53.133.417,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 12 04 06 56 C9 2D ED C7 AF 6C AC B0 10 1E 23 88… 0,,11126,0:53.134.414,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,11127,0:53.148.419,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11131,0:53.149.416,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,11132,0:53.149.419,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 12 04 06 56 C9 2D ED C7 AF 6C AC B0 10 1E 23 88… 0,,11136,0:53.150.416,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,11137,0:53.165.421,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 A8 A8 BA 6B 8B 67 01 11 51 4D 2E E4 76 79 71… 0,,11141,0:53.166.418,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,11142,0:53.180.424,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11146,0:53.181.420,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,11147,0:53.181.424,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 A8 A8 BA 6B 8B 67 01 11 51 4D 2E E4 76 79 71… 0,,11151,0:53.182.420,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,11152,0:53.197.426,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 91 2A C4 A6 69 12 BF 90 36 BB 3A F0 41 66 38 CD… 0,,11156,0:53.198.423,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,11157,0:53.212.428,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11161,0:53.213.425,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,11162,0:53.213.428,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 91 2A C4 A6 69 12 BF 90 36 BB 3A F0 41 66 38 CD… 0,,11166,0:53.214.425,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,11167,0:53.229.430,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 8E 15 B0 F8 4D E9 A9 C2 B2 89 12 B7 7C 04 57… 0,,11171,0:53.230.427,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,11172,0:53.244.432,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11176,0:53.245.429,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,11177,0:53.245.433,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 8E 15 B0 F8 4D E9 A9 C2 B2 89 12 B7 7C 04 57… 0,,11181,0:53.246.429,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,11182,0:53.261.435,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0C 3A 6F 98 AD 5E DC 07 73 EA A6 ED F2 A6 C7 F5… 0,,11186,0:53.262.432,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,11187,0:53.276.437,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11191,0:53.277.434,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,11192,0:53.277.437,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0C 3A 6F 98 AD 5E DC 07 73 EA A6 ED F2 A6 C7 F5… 0,,11196,0:53.278.434,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,11197,0:53.293.439,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB AF 23 FC 31 7F 38 B0 9D DA BF 54 C2 51 56 AB… 0,,11201,0:53.294.436,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,11202,0:53.308.441,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11206,0:53.309.438,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,11207,0:53.309.441,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB AF 23 FC 31 7F 38 B0 9D DA BF 54 C2 51 56 AB… 0,,11211,0:53.310.438,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,11212,0:53.325.444,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 28 3C 63 D0 93 EA 5A 3C 7E 81 D8 ED C4 0C 5C 97… 0,,11216,0:53.326.440,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,11217,0:53.340.446,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11221,0:53.341.443,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,11222,0:53.341.446,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 28 3C 63 D0 93 EA 5A 3C 7E 81 D8 ED C4 0C 5C 97… 0,,11226,0:53.342.443,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,11227,0:53.357.448,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF BE 71 BF 68 64 CE 22 77 29 60 8B 7E CD 27 CE… 0,,11231,0:53.358.445,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,11232,0:53.372.450,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11236,0:53.373.447,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,11237,0:53.373.450,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF BE 71 BF 68 64 CE 22 77 29 60 8B 7E CD 27 CE… 0,,11241,0:53.374.447,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,11242,0:53.389.452,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E D8 D2 75 F2 91 32 0C B9 C0 11 F0 FD B6 DE 51… 0,,11246,0:53.390.449,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,11247,0:53.404.455,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11251,0:53.405.451,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,11252,0:53.405.455,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E D8 D2 75 F2 91 32 0C B9 C0 11 F0 FD B6 DE 51… 0,,11256,0:53.406.452,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,11257,0:53.421.457,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E DC 3D F9 90 98 B3 EA 29 64 62 DE E5 00 33 EF… 0,,11261,0:53.422.454,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,11262,0:53.436.459,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11266,0:53.437.456,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,11267,0:53.437.459,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E DC 3D F9 90 98 B3 EA 29 64 62 DE E5 00 33 EF… 0,,11271,0:53.438.456,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,11272,0:53.453.461,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B0 E3 C1 66 89 28 19 4D C7 3C 5B B6 23 EB 1A 2E… 0,,11276,0:53.454.458,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,11277,0:53.468.463,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11281,0:53.469.460,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,11282,0:53.469.464,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B0 E3 C1 66 89 28 19 4D C7 3C 5B B6 23 EB 1A 2E… 0,,11286,0:53.470.460,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,11287,0:53.485.466,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 55 36 78 41 70 85 E5 87 B3 04 DE 23 61 5B 1C 3F… 0,,11291,0:53.486.463,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,11292,0:53.500.468,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11296,0:53.501.465,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,11297,0:53.501.468,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 55 36 78 41 70 85 E5 87 B3 04 DE 23 61 5B 1C 3F… 0,,11301,0:53.502.465,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,11302,0:53.517.470,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 E7 88 64 A4 17 97 71 71 ED 42 DC CC EA 8E 81… 0,,11306,0:53.518.467,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,11307,0:53.532.472,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11311,0:53.533.469,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,11312,0:53.533.472,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 E7 88 64 A4 17 97 71 71 ED 42 DC CC EA 8E 81… 0,,11316,0:53.534.469,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,11317,0:53.549.475,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 60 A4 09 5D B6 75 6D 36 93 33 A8 77 21 EC 6A… 0,,11321,0:53.550.472,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,11322,0:53.564.477,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11326,0:53.565.474,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,11327,0:53.565.477,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 60 A4 09 5D B6 75 6D 36 93 33 A8 77 21 EC 6A… 0,,11331,0:53.566.474,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,11332,0:53.581.479,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 6D 05 D3 30 67 5D 14 3F 84 3A 3D 14 59 9C 4E… 0,,11336,0:53.582.476,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,11337,0:53.596.481,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11341,0:53.597.478,16.005.041 ms,,,,,[17 SOF],[Frames: 289 - 305] 0,,11342,0:53.613.484,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E9 93 AD C3 87 63 A5 B9 38 9B 3A 26 C7 26 68 59… 0,,11346,0:53.614.480,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,11347,0:53.628.486,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11351,0:53.629.483,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,11352,0:53.629.486,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E9 93 AD C3 87 63 A5 B9 38 9B 3A 26 C7 26 68 59… 0,,11356,0:53.630.483,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,11357,0:53.645.488,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 6F 6D D3 D8 88 CC 69 DE FD 5E 01 D1 71 8B 03… 0,,11361,0:53.646.485,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,11362,0:53.660.490,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11366,0:53.661.487,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,11367,0:53.661.490,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 6F 6D D3 D8 88 CC 69 DE FD 5E 01 D1 71 8B 03… 0,,11371,0:53.662.487,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,11372,0:53.677.492,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C 05 38 4F F1 C0 89 78 21 B5 95 64 72 94 15 0E… 0,,11376,0:53.678.489,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,11377,0:53.692.495,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11381,0:53.693.491,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,11382,0:53.693.495,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C 05 38 4F F1 C0 89 78 21 B5 95 64 72 94 15 0E… 0,,11386,0:53.694.492,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,11387,0:53.709.497,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 67 81 C1 35 D3 1C 85 2D 2F 41 5A 28 AD 6D 90… 0,,11391,0:53.710.494,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,11392,0:53.724.499,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11396,0:53.725.496,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,11397,0:53.725.499,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 67 81 C1 35 D3 1C 85 2D 2F 41 5A 28 AD 6D 90… 0,,11401,0:53.726.496,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,11402,0:53.741.501,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B DF C0 79 CD 18 82 85 C6 2B 0F 3E 86 0D 4B 76… 0,,11406,0:53.742.498,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,11407,0:53.756.503,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11411,0:53.757.500,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,11412,0:53.757.504,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B DF C0 79 CD 18 82 85 C6 2B 0F 3E 86 0D 4B 76… 0,,11416,0:53.758.500,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,11417,0:53.773.506,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AC 2E 88 4B 57 3F 90 F6 8B FC B2 81 47 FE 15 14… 0,,11421,0:53.774.503,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,11422,0:53.788.508,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11426,0:53.789.505,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,11427,0:53.789.508,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AC 2E 88 4B 57 3F 90 F6 8B FC B2 81 47 FE 15 14… 0,,11431,0:53.790.505,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,11432,0:53.805.510,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 87 D6 EA FE 0C 00 A6 AA 28 FF 96 B2 E7 33 07… 0,,11436,0:53.806.507,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,11437,0:53.820.512,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11441,0:53.821.509,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,11442,0:53.821.512,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 87 D6 EA FE 0C 00 A6 AA 28 FF 96 B2 E7 33 07… 0,,11446,0:53.822.509,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,11447,0:53.837.515,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C1 DD 29 31 A0 AE 97 0B 8A C5 E4 1C 68 12 AE 16… 0,,11451,0:53.838.512,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,11452,0:53.852.517,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11456,0:53.853.514,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,11457,0:53.853.517,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C1 DD 29 31 A0 AE 97 0B 8A C5 E4 1C 68 12 AE 16… 0,,11461,0:53.854.514,15.004.916 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,11462,0:53.869.519,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 DB 39 E3 F9 92 52 74 33 2D 5F 37 6C 45 67 02… 0,,11466,0:53.870.516,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,11467,0:53.884.521,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11471,0:53.885.518,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,11472,0:53.885.521,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 DB 39 E3 F9 92 52 74 33 2D 5F 37 6C 45 67 02… 0,,11476,0:53.886.518,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,11477,0:53.901.524,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 73 80 F2 90 F2 40 55 E4 B3 D3 55 3C 5D EA 22… 0,,11481,0:53.902.520,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,11482,0:53.916.526,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11486,0:53.917.523,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,11487,0:53.917.526,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 73 80 F2 90 F2 40 55 E4 B3 D3 55 3C 5D EA 22… 0,,11491,0:53.918.523,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,11492,0:53.933.528,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 39 46 60 59 8A BC 08 B0 57 43 1F 18 AA 11 8A… 0,,11496,0:53.934.525,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,11497,0:53.948.530,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11501,0:53.949.527,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,11502,0:53.949.530,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 39 46 60 59 8A BC 08 B0 57 43 1F 18 AA 11 8A… 0,,11506,0:53.950.527,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,11507,0:53.965.532,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 6A 63 4F 0F DB DA 23 7F CC E9 A4 12 19 F2 3A… 0,,11511,0:53.966.529,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,11512,0:53.980.535,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11516,0:53.981.531,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,11517,0:53.981.535,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E3 6A 63 4F 0F DB DA 23 7F CC E9 A4 12 19 F2 3A… 0,,11521,0:53.982.532,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,11522,0:53.997.537,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 55 7D 5F 8B 35 8C D8 A3 EA AC B6 12 0E CD B7… 0,,11526,0:53.998.534,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,11527,0:54.012.539,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11531,0:54.013.536,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,11532,0:54.013.539,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 55 7D 5F 8B 35 8C D8 A3 EA AC B6 12 0E CD B7… 0,,11536,0:54.014.536,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,11537,0:54.029.541,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 5D 7A 31 8B 71 D6 80 4A E8 03 15 F7 77 18 46… 0,,11541,0:54.030.538,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,11542,0:54.044.543,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11546,0:54.045.540,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,11547,0:54.045.544,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 5D 7A 31 8B 71 D6 80 4A E8 03 15 F7 77 18 46… 0,,11551,0:54.046.540,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,11552,0:54.061.546,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 91 D5 43 8D 64 BB C9 03 E0 09 42 2F 77 68 60… 0,,11556,0:54.062.543,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,11557,0:54.076.548,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11561,0:54.077.545,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,11562,0:54.077.548,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 91 D5 43 8D 64 BB C9 03 E0 09 42 2F 77 68 60… 0,,11566,0:54.078.545,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,11567,0:54.093.550,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C BD F2 9F 20 14 CE BA 74 74 61 30 AD 5E C0 1D… 0,,11571,0:54.094.547,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,11572,0:54.108.552,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11576,0:54.109.549,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,11577,0:54.109.552,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C BD F2 9F 20 14 CE BA 74 74 61 30 AD 5E C0 1D… 0,,11581,0:54.110.549,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,11582,0:54.125.555,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 80 01 05 56 BA 19 3F B8 F2 5A E8 11 65 CE 8B… 0,,11586,0:54.126.552,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,11587,0:54.140.557,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11591,0:54.141.554,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,11592,0:54.141.557,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 80 01 05 56 BA 19 3F B8 F2 5A E8 11 65 CE 8B… 0,,11596,0:54.142.554,15.004.916 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,11597,0:54.157.559,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 AC 5E ED 11 D6 CC 7C E2 2D C2 E0 0B D8 27 56… 0,,11601,0:54.158.556,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,11602,0:54.172.561,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11606,0:54.173.558,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,11607,0:54.173.561,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 AC 5E ED 11 D6 CC 7C E2 2D C2 E0 0B D8 27 56… 0,,11611,0:54.174.558,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,11612,0:54.189.564,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8B A1 02 05 F8 1B CD 5C 05 BE B3 F0 99 0D 47 EF… 0,,11616,0:54.190.560,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,11617,0:54.204.566,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11621,0:54.205.562,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,11622,0:54.205.566,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8B A1 02 05 F8 1B CD 5C 05 BE B3 F0 99 0D 47 EF… 0,,11626,0:54.206.563,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,11627,0:54.221.568,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 97 56 CC 23 95 31 5E 08 3D 95 71 BC AF 90 5A… 0,,11631,0:54.222.565,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,11632,0:54.236.570,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11636,0:54.237.567,16.005.041 ms,,,,,[17 SOF],[Frames: 929 - 945] 0,,11637,0:54.253.572,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 97 56 CC 23 95 31 5E 08 3D 95 71 BC AF 90 5A… 0,,11641,0:54.254.569,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,11642,0:54.268.574,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11646,0:54.269.571,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,11647,0:54.269.575,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 97 56 CC 23 95 31 5E 08 3D 95 71 BC AF 90 5A… 0,,11651,0:54.270.572,15.004.916 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,11652,0:54.285.577,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1F E2 D1 02 E0 D5 B3 D9 12 7D 3E 93 E8 D5 A0 D9… 0,,11656,0:54.286.574,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,11657,0:54.300.579,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11661,0:54.301.576,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,11662,0:54.301.579,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1F E2 D1 02 E0 D5 B3 D9 12 7D 3E 93 E8 D5 A0 D9… 0,,11666,0:54.302.576,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,11667,0:54.317.581,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D6 2D 06 3D F9 A1 DA D6 D9 36 DF A4 FD F5 75 EE… 0,,11671,0:54.318.578,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,11672,0:54.332.583,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11676,0:54.333.580,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,11677,0:54.333.584,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D6 2D 06 3D F9 A1 DA D6 D9 36 DF A4 FD F5 75 EE… 0,,11681,0:54.334.580,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,11682,0:54.349.586,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B3 D7 3E 80 A7 48 54 AF 58 1C 6F F1 21 BA FF C0… 0,,11686,0:54.350.583,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,11687,0:54.364.588,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11691,0:54.365.585,2.833 us,,,,,[1 SOF],[Frame: 1057] 0,,11692,0:54.365.588,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B3 D7 3E 80 A7 48 54 AF 58 1C 6F F1 21 BA FF C0… 0,,11696,0:54.366.585,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,11697,0:54.381.590,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC A4 9E 28 64 3A D9 38 0B 56 E7 8F 42 4B E2 6D… 0,,11701,0:54.382.587,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,11702,0:54.396.592,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11706,0:54.397.589,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,11707,0:54.397.592,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC A4 9E 28 64 3A D9 38 0B 56 E7 8F 42 4B E2 6D… 0,,11711,0:54.398.589,15.004.916 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,11712,0:54.413.595,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3C F5 85 5B 45 D4 30 C7 D9 57 C8 32 F1 93 5F 54… 0,,11716,0:54.414.592,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,11717,0:54.428.597,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11721,0:54.429.594,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,11722,0:54.429.597,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3C F5 85 5B 45 D4 30 C7 D9 57 C8 32 F1 93 5F 54… 0,,11726,0:54.430.594,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,11727,0:54.445.599,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF E3 A6 07 EA 72 A4 7B 41 67 95 74 2E C9 F0 C0… 0,,11731,0:54.446.596,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,11732,0:54.460.601,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11736,0:54.461.598,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,11737,0:54.461.601,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF E3 A6 07 EA 72 A4 7B 41 67 95 74 2E C9 F0 C0… 0,,11741,0:54.462.598,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,11742,0:54.477.604,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5B DA ED FB D8 54 23 73 77 07 25 AA E4 F1 17 B6… 0,,11746,0:54.478.600,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,11747,0:54.492.606,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11751,0:54.493.602,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,11752,0:54.493.606,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5B DA ED FB D8 54 23 73 77 07 25 AA E4 F1 17 B6… 0,,11756,0:54.494.603,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,11757,0:54.509.608,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A A7 CC 56 BC 72 B7 DF D9 A0 8B 65 DD 75 B8 0C… 0,,11761,0:54.510.605,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,11762,0:54.524.610,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11766,0:54.525.607,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,11767,0:54.525.610,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A A7 CC 56 BC 72 B7 DF D9 A0 8B 65 DD 75 B8 0C… 0,,11771,0:54.526.607,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,11772,0:54.541.612,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 49 92 E1 4C 02 2B B6 67 E5 EF 35 12 36 BE 68 07… 0,,11776,0:54.542.609,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,11777,0:54.556.614,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11781,0:54.557.611,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,11782,0:54.557.615,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 49 92 E1 4C 02 2B B6 67 E5 EF 35 12 36 BE 68 07… 0,,11786,0:54.558.611,15.004.916 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,11787,0:54.573.617,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7F D8 35 A5 EE 86 8D C2 B2 99 1A 20 E0 F5 7C DF… 0,,11791,0:54.574.614,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,11792,0:54.588.619,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11796,0:54.589.616,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,11797,0:54.589.619,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7F D8 35 A5 EE 86 8D C2 B2 99 1A 20 E0 F5 7C DF… 0,,11801,0:54.590.616,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,11802,0:54.605.621,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A 4A E0 9F 93 A2 59 06 DF D0 34 A8 F0 B2 6B FA… 0,,11806,0:54.606.618,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,11807,0:54.620.623,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11811,0:54.621.620,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,11812,0:54.621.623,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A 4A E0 9F 93 A2 59 06 DF D0 34 A8 F0 B2 6B FA… 0,,11816,0:54.622.620,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,11817,0:54.637.626,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 B6 EA 66 B2 63 66 3F BA 15 AF F7 B7 93 F3 39… 0,,11821,0:54.638.623,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,11822,0:54.652.628,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11826,0:54.653.625,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,11827,0:54.653.628,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 B6 EA 66 B2 63 66 3F BA 15 AF F7 B7 93 F3 39… 0,,11831,0:54.654.625,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,11832,0:54.669.630,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C 8C C0 E3 EC AD 69 2F F1 EF 05 30 94 B2 83 E8… 0,,11836,0:54.670.627,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,11837,0:54.684.632,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11841,0:54.685.629,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,11842,0:54.685.632,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C 8C C0 E3 EC AD 69 2F F1 EF 05 30 94 B2 83 E8… 0,,11846,0:54.686.629,15.004.916 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,11847,0:54.701.635,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7E CF E0 4F 06 F7 C1 28 A6 C2 C4 48 B7 B6 90 CF… 0,,11851,0:54.702.631,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,11852,0:54.716.637,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11856,0:54.717.634,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,11857,0:54.717.637,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7E CF E0 4F 06 F7 C1 28 A6 C2 C4 48 B7 B6 90 CF… 0,,11861,0:54.718.634,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,11862,0:54.733.639,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC A8 0C 95 76 CB 2B 5B 55 37 0C 72 14 EF 4E AB… 0,,11866,0:54.734.636,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,11867,0:54.748.641,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11871,0:54.749.638,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,11872,0:54.749.641,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EC A8 0C 95 76 CB 2B 5B 55 37 0C 72 14 EF 4E AB… 0,,11876,0:54.750.638,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,11877,0:54.765.643,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 F7 CE 2A DD FE C1 79 5D 79 53 E9 6E 6A 37 8F… 0,,11881,0:54.766.640,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,11882,0:54.780.646,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11886,0:54.781.642,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,11887,0:54.781.646,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 F7 CE 2A DD FE C1 79 5D 79 53 E9 6E 6A 37 8F… 0,,11891,0:54.782.643,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,11892,0:54.797.648,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 52 6B 32 91 E2 C8 C1 A1 75 28 41 72 F6 D8 6B B3… 0,,11896,0:54.798.645,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,11897,0:54.812.650,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11901,0:54.813.647,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,11902,0:54.813.650,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 52 6B 32 91 E2 C8 C1 A1 75 28 41 72 F6 D8 6B B3… 0,,11906,0:54.814.647,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,11907,0:54.829.652,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 FA 2E E2 A9 97 68 37 04 F4 3E 60 E0 73 FA 15… 0,,11911,0:54.830.649,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,11912,0:54.844.654,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11916,0:54.845.651,16.005.125 ms,,,,,[17 SOF],[Frames: 1537 - 1553] 0,,11917,0:54.861.657,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 B7 10 D5 6B 7F FA 36 63 7F 68 80 F4 BB 76 01… 0,,11921,0:54.862.654,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,11922,0:54.876.659,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11926,0:54.877.656,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,11927,0:54.877.659,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 B7 10 D5 6B 7F FA 36 63 7F 68 80 F4 BB 76 01… 0,,11931,0:54.878.656,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,11932,0:54.893.661,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 02 11 47 A7 D4 3B 76 1A E1 A8 E1 3B AC BE 66 C3… 0,,11936,0:54.894.658,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,11937,0:54.908.663,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11941,0:54.909.660,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,11942,0:54.909.663,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 02 11 47 A7 D4 3B 76 1A E1 A8 E1 3B AC BE 66 C3… 0,,11946,0:54.910.660,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,11947,0:54.925.666,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 52 5F FA 13 80 94 91 01 58 30 53 AE 11 93 6F… 0,,11951,0:54.926.663,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,11952,0:54.940.668,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11956,0:54.941.665,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,11957,0:54.941.668,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 52 5F FA 13 80 94 91 01 58 30 53 AE 11 93 6F… 0,,11961,0:54.942.665,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,11962,0:54.957.670,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 12 47 71 77 FB 26 2E 25 F0 EC B6 67 DD 12 10… 0,,11966,0:54.958.667,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,11967,0:54.972.672,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11971,0:54.973.669,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,11972,0:54.973.672,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9A 12 47 71 77 FB 26 2E 25 F0 EC B6 67 DD 12 10… 0,,11976,0:54.974.669,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,11977,0:54.989.675,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E7 16 5F 4E 67 DF D9 AA 91 3B C2 89 2A E9 52 10… 0,,11981,0:54.990.671,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,11982,0:55.004.677,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11986,0:55.005.674,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,11987,0:55.005.677,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E7 16 5F 4E 67 DF D9 AA 91 3B C2 89 2A E9 52 10… 0,,11991,0:55.006.674,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,11992,0:55.021.679,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9D 53 49 DC EE 71 63 CC 24 5D 33 4D 7D 1A 9E 8D… 0,,11996,0:55.022.676,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,11997,0:55.036.681,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12001,0:55.037.678,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,12002,0:55.037.681,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9D 53 49 DC EE 71 63 CC 24 5D 33 4D 7D 1A 9E 8D… 0,,12006,0:55.038.678,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,12007,0:55.053.683,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 11 6D A7 48 2D E2 F9 43 B4 6A 7A D9 D0 F6 51… 0,,12011,0:55.054.680,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,12012,0:55.068.686,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12016,0:55.069.682,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,12017,0:55.069.686,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 11 6D A7 48 2D E2 F9 43 B4 6A 7A D9 D0 F6 51… 0,,12021,0:55.070.683,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,12022,0:55.085.688,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 98 EF EC F0 E8 82 5C A7 A5 75 04 26 9E 6C DA… 0,,12026,0:55.086.685,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,12027,0:55.100.690,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12031,0:55.101.687,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,12032,0:55.101.690,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 98 EF EC F0 E8 82 5C A7 A5 75 04 26 9E 6C DA… 0,,12036,0:55.102.687,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,12037,0:55.117.692,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 54 2E 30 DC E1 E4 C9 63 97 9A 2B 21 E2 F9 4B 6D… 0,,12041,0:55.118.689,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,12042,0:55.132.694,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12046,0:55.133.691,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,12047,0:55.133.695,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 54 2E 30 DC E1 E4 C9 63 97 9A 2B 21 E2 F9 4B 6D… 0,,12051,0:55.134.691,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,12052,0:55.149.697,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1A 8D 2E A3 E6 A6 7D 50 C0 14 88 06 80 BF 76 D7… 0,,12056,0:55.150.694,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,12057,0:55.164.699,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12061,0:55.165.696,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,12062,0:55.165.699,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1A 8D 2E A3 E6 A6 7D 50 C0 14 88 06 80 BF 76 D7… 0,,12066,0:55.166.696,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,12067,0:55.181.701,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 03 A1 28 5F B4 36 A5 50 DD 8A 4F F3 81 D2 BE… 0,,12071,0:55.182.698,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,12072,0:55.196.703,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12076,0:55.197.700,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,12077,0:55.197.703,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 03 A1 28 5F B4 36 A5 50 DD 8A 4F F3 81 D2 BE… 0,,12081,0:55.198.700,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,12082,0:55.213.706,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C 73 D1 F9 0E 53 16 69 FA 43 3A 13 EF 2D 56 9B… 0,,12086,0:55.214.703,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,12087,0:55.228.708,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12091,0:55.229.705,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,12092,0:55.229.708,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C 73 D1 F9 0E 53 16 69 FA 43 3A 13 EF 2D 56 9B… 0,,12096,0:55.230.705,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,12097,0:55.245.710,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D B8 2C 45 27 5B 5C 22 AE 55 B3 09 9D C5 09 63… 0,,12101,0:55.246.707,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,12102,0:55.260.712,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12106,0:55.261.709,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,12107,0:55.261.712,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D B8 2C 45 27 5B 5C 22 AE 55 B3 09 9D C5 09 63… 0,,12111,0:55.262.709,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,12112,0:55.277.715,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E 1D CB 55 B0 1C 71 77 AF 92 01 47 83 67 A3 C2… 0,,12116,0:55.278.711,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,12117,0:55.292.717,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12121,0:55.293.714,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,12122,0:55.293.717,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E 1D CB 55 B0 1C 71 77 AF 92 01 47 83 67 A3 C2… 0,,12126,0:55.294.714,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,12127,0:55.309.719,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 77 A7 CA 82 80 7F 10 A1 02 7E 1F 9E 23 BA 6D 6C… 0,,12131,0:55.310.716,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,12132,0:55.324.721,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12136,0:55.325.718,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,12137,0:55.325.721,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 77 A7 CA 82 80 7F 10 A1 02 7E 1F 9E 23 BA 6D 6C… 0,,12141,0:55.326.718,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,12142,0:55.341.724,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D 11 72 EB F7 73 8A 60 70 5A E9 B1 99 57 CE 90… 0,,12146,0:55.342.720,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,12147,0:55.356.726,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12151,0:55.357.722,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,12152,0:55.357.726,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D 11 72 EB F7 73 8A 60 70 5A E9 B1 99 57 CE 90… 0,,12156,0:55.358.723,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,12157,0:55.373.728,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA C8 C1 72 5A 59 96 CB 80 54 D2 DB 8C 0A 5E 73… 0,,12161,0:55.374.725,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,12162,0:55.388.730,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12166,0:55.389.727,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,12167,0:55.389.730,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA C8 C1 72 5A 59 96 CB 80 54 D2 DB 8C 0A 5E 73… 0,,12171,0:55.390.727,15.004.916 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,12172,0:55.405.732,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D 9C 2F 59 73 4D 78 55 01 D0 AE D7 FB 09 1E 4E… 0,,12176,0:55.406.729,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,12177,0:55.420.734,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12181,0:55.421.731,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,12182,0:55.421.735,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D 9C 2F 59 73 4D 78 55 01 D0 AE D7 FB 09 1E 4E… 0,,12186,0:55.422.731,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,12187,0:55.437.737,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 88 A3 90 F4 8F 48 80 E2 26 73 75 61 DD 06 C8… 0,,12191,0:55.438.734,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,12192,0:55.452.739,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12196,0:55.453.736,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,12197,0:55.453.739,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 88 A3 90 F4 8F 48 80 E2 26 73 75 61 DD 06 C8… 0,,12201,0:55.454.736,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,12202,0:55.469.741,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC CA E3 65 0B 9F 7D 05 A6 16 A1 A6 CF 40 B2 65… 0,,12206,0:55.470.738,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,12207,0:55.484.743,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12211,0:55.485.740,16.005.041 ms,,,,,[17 SOF],[Frames: 129 - 145] 0,,12212,0:55.501.746,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC CA E3 65 0B 9F 7D 05 A6 16 A1 A6 CF 40 B2 65… 0,,12216,0:55.502.743,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,12217,0:55.516.748,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12221,0:55.517.745,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,12222,0:55.517.748,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E 6C 19 26 EB 30 0B 73 7C 7A DD 90 99 3E 42 85… 0,,12226,0:55.518.745,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,12227,0:55.533.750,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B C3 1C E2 4C B4 CB 63 47 12 E2 E4 1B FC EC D6… 0,,12231,0:55.534.747,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,12232,0:55.548.752,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12236,0:55.549.749,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,12237,0:55.549.752,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B C3 1C E2 4C B4 CB 63 47 12 E2 E4 1B FC EC D6… 0,,12241,0:55.550.749,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,12242,0:55.565.755,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A 4A B4 79 2D DE 85 D6 27 C5 06 DB DC EF 77 74… 0,,12246,0:55.566.751,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,12247,0:55.580.757,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12251,0:55.581.754,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,12252,0:55.581.757,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A 4A B4 79 2D DE 85 D6 27 C5 06 DB DC EF 77 74… 0,,12256,0:55.582.754,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,12257,0:55.597.759,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 0B 51 41 20 D6 5B 39 32 C8 B6 80 D9 8A E9 41… 0,,12261,0:55.598.756,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,12262,0:55.612.761,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12266,0:55.613.758,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,12267,0:55.613.761,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 0B 51 41 20 D6 5B 39 32 C8 B6 80 D9 8A E9 41… 0,,12271,0:55.614.758,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,12272,0:55.629.763,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B BC 67 12 5B 2B 99 59 44 D2 15 95 2E 0A BA 5D… 0,,12276,0:55.630.760,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,12277,0:55.644.766,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12281,0:55.645.762,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,12282,0:55.645.766,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B BC 67 12 5B 2B 99 59 44 D2 15 95 2E 0A BA 5D… 0,,12286,0:55.646.763,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,12287,0:55.661.768,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E7 C2 87 EF 15 B6 D6 9D E6 66 0D 29 A4 28 9A 3F… 0,,12291,0:55.662.765,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,12292,0:55.676.770,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12296,0:55.677.767,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,12297,0:55.677.770,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E7 C2 87 EF 15 B6 D6 9D E6 66 0D 29 A4 28 9A 3F… 0,,12301,0:55.678.767,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,12302,0:55.693.772,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E 63 EB 94 62 6A 75 DA 03 0B C1 D2 A4 0B CD 44… 0,,12306,0:55.694.769,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,12307,0:55.708.774,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12311,0:55.709.771,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,12312,0:55.709.775,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E 63 EB 94 62 6A 75 DA 03 0B C1 D2 A4 0B CD 44… 0,,12316,0:55.710.771,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,12317,0:55.725.777,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D 95 CD 2E A7 7B 61 DF BE 7E 92 E3 48 A5 82 6B… 0,,12321,0:55.726.774,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,12322,0:55.740.779,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12326,0:55.741.776,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,12327,0:55.741.779,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D 95 CD 2E A7 7B 61 DF BE 7E 92 E3 48 A5 82 6B… 0,,12331,0:55.742.776,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,12332,0:55.757.781,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B 0F 05 71 5F 64 57 4C 80 0A 63 C0 A6 91 90 89… 0,,12336,0:55.758.778,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,12337,0:55.772.783,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12341,0:55.773.780,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,12342,0:55.773.783,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B 0F 05 71 5F 64 57 4C 80 0A 63 C0 A6 91 90 89… 0,,12346,0:55.774.780,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,12347,0:55.789.786,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 70 FF 73 DE E3 40 F3 B0 81 F5 D3 34 3E 36 3F 8B… 0,,12351,0:55.790.783,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,12352,0:55.804.788,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12356,0:55.805.785,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,12357,0:55.805.788,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 70 FF 73 DE E3 40 F3 B0 81 F5 D3 34 3E 36 3F 8B… 0,,12361,0:55.806.785,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,12362,0:55.821.790,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB 6E 8F B6 68 13 F8 51 13 76 31 33 E8 F0 85 33… 0,,12366,0:55.822.787,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,12367,0:55.836.792,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12371,0:55.837.789,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,12372,0:55.837.792,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB 6E 8F B6 68 13 F8 51 13 76 31 33 E8 F0 85 33… 0,,12376,0:55.838.789,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,12377,0:55.853.795,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 21 E2 93 6D 49 A5 53 F7 59 74 01 2E CE FF AF… 0,,12381,0:55.854.791,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,12382,0:55.868.797,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12386,0:55.869.793,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,12387,0:55.869.797,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 21 E2 93 6D 49 A5 53 F7 59 74 01 2E CE FF AF… 0,,12391,0:55.870.794,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,12392,0:55.885.799,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 31 1C FF 17 D1 33 38 66 A0 1A 04 C3 FF F4 6E… 0,,12396,0:55.886.796,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,12397,0:55.900.801,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12401,0:55.901.798,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,12402,0:55.901.801,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 31 1C FF 17 D1 33 38 66 A0 1A 04 C3 FF F4 6E… 0,,12406,0:55.902.798,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,12407,0:55.917.803,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 D5 09 95 42 C1 CC 20 CD 2E 84 67 74 38 48 46… 0,,12411,0:55.918.800,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,12412,0:55.932.805,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12416,0:55.933.802,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,12417,0:55.933.806,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 D5 09 95 42 C1 CC 20 CD 2E 84 67 74 38 48 46… 0,,12421,0:55.934.803,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,12422,0:55.949.808,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 37 88 C4 94 5F EE 82 A0 AC A8 A6 2D 03 50 A8 DD… 0,,12426,0:55.950.805,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,12427,0:55.964.810,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12431,0:55.965.807,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,12432,0:55.965.810,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 37 88 C4 94 5F EE 82 A0 AC A8 A6 2D 03 50 A8 DD… 0,,12436,0:55.966.807,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,12437,0:55.981.812,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 55 D4 03 84 BE 79 51 D5 C1 C5 9D 50 EA BA CC… 0,,12441,0:55.982.809,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,12442,0:55.996.814,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12446,0:55.997.811,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,12447,0:55.997.815,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 55 D4 03 84 BE 79 51 D5 C1 C5 9D 50 EA BA CC… 0,,12451,0:55.998.811,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,12452,0:56.013.817,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 BA 9E 66 08 C8 53 3B B5 43 4F 10 DD D6 78 19… 0,,12456,0:56.014.814,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,12457,0:56.028.819,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12461,0:56.029.816,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,12462,0:56.029.819,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 BA 9E 66 08 C8 53 3B B5 43 4F 10 DD D6 78 19… 0,,12466,0:56.030.816,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,12467,0:56.045.821,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 28 B9 79 BE A2 B7 79 1F B8 42 E6 CA 96 44 E5… 0,,12471,0:56.046.818,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,12472,0:56.060.823,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12476,0:56.061.820,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,12477,0:56.061.823,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 28 B9 79 BE A2 B7 79 1F B8 42 E6 CA 96 44 E5… 0,,12481,0:56.062.820,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,12482,0:56.077.826,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 77 92 BC 20 4F 0E 17 4B C9 69 CC 85 50 89 74 BB… 0,,12486,0:56.078.823,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,12487,0:56.092.828,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12491,0:56.093.825,16.005.041 ms,,,,,[17 SOF],[Frames: 737 - 753] 0,,12492,0:56.109.830,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 7E 22 A0 2C 04 70 74 56 0D 4D FA 0B E8 E3 25… 0,,12496,0:56.110.827,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,12497,0:56.124.832,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12501,0:56.125.829,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,12502,0:56.125.832,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 7E 22 A0 2C 04 70 74 56 0D 4D FA 0B E8 E3 25… 0,,12506,0:56.126.829,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,12507,0:56.141.835,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 68 43 7B 3C 5B EC 32 A7 62 C4 6C 5F 2E 3F 7F… 0,,12511,0:56.142.831,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,12512,0:56.156.837,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12516,0:56.157.833,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,12517,0:56.157.837,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 68 43 7B 3C 5B EC 32 A7 62 C4 6C 5F 2E 3F 7F… 0,,12521,0:56.158.834,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,12522,0:56.173.839,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 94 D0 4B 76 10 94 64 50 07 DA 53 6A F9 18 CF… 0,,12526,0:56.174.836,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,12527,0:56.188.841,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12531,0:56.189.838,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,12532,0:56.189.841,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 94 D0 4B 76 10 94 64 50 07 DA 53 6A F9 18 CF… 0,,12536,0:56.190.838,15.004.916 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,12537,0:56.205.843,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 29 6D 0A 6C EC 3F 96 B8 31 B7 6D 17 94 34 6C 19… 0,,12541,0:56.206.840,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,12542,0:56.220.845,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12546,0:56.221.842,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,12547,0:56.221.846,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 29 6D 0A 6C EC 3F 96 B8 31 B7 6D 17 94 34 6C 19… 0,,12551,0:56.222.842,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,12552,0:56.237.848,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F0 48 7B 6E FA AF AA 11 82 DD E5 3A 5A 3B A0 DB… 0,,12556,0:56.238.845,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,12557,0:56.252.850,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12561,0:56.253.847,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,12562,0:56.253.850,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F0 48 7B 6E FA AF AA 11 82 DD E5 3A 5A 3B A0 DB… 0,,12566,0:56.254.847,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,12567,0:56.269.852,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF 47 FD 2F F0 DD 50 5A 86 3B B9 81 43 96 96 65… 0,,12571,0:56.270.849,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,12572,0:56.284.854,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12576,0:56.285.851,2.833 us,,,,,[1 SOF],[Frame: 929] 0,,12577,0:56.285.854,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF 47 FD 2F F0 DD 50 5A 86 3B B9 81 43 96 96 65… 0,,12581,0:56.286.851,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,12582,0:56.301.857,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 2F 0E 5B F2 A8 F0 CB 06 B2 9D 8A 00 6B 32 70… 0,,12586,0:56.302.854,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,12587,0:56.316.859,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12591,0:56.317.856,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,12592,0:56.317.859,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 2F 0E 5B F2 A8 F0 CB 06 B2 9D 8A 00 6B 32 70… 0,,12596,0:56.318.856,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,12597,0:56.333.861,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 34 44 F1 2F D4 F0 79 2D 00 65 3E 30 C7 72 3B… 0,,12601,0:56.334.858,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,12602,0:56.348.863,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12606,0:56.349.860,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,12607,0:56.349.863,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 34 44 F1 2F D4 F0 79 2D 00 65 3E 30 C7 72 3B… 0,,12611,0:56.350.860,15.005.000 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,12612,0:56.365.866,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 FB 05 89 92 CD B2 FC AB 7B 3A 19 35 54 32 9D… 0,,12616,0:56.366.862,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,12617,0:56.380.868,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12621,0:56.381.865,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,12622,0:56.381.868,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 FB 05 89 92 CD B2 FC AB 7B 3A 19 35 54 32 9D… 0,,12626,0:56.382.865,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,12627,0:56.397.870,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 50 F5 84 C3 A9 60 F7 A8 08 BC E2 86 55 74 F2… 0,,12631,0:56.398.867,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,12632,0:56.412.872,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12636,0:56.413.869,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,12637,0:56.413.872,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 50 F5 84 C3 A9 60 F7 A8 08 BC E2 86 55 74 F2… 0,,12641,0:56.414.869,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,12642,0:56.429.874,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C4 92 71 EE 62 BF F1 66 36 B6 FD 72 B2 32 BC BC… 0,,12646,0:56.430.871,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,12647,0:56.444.877,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12651,0:56.445.873,2.833 us,,,,,[1 SOF],[Frame: 1089] 0,,12652,0:56.445.877,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C4 92 71 EE 62 BF F1 66 36 B6 FD 72 B2 32 BC BC… 0,,12656,0:56.446.874,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,12657,0:56.461.879,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 76 7A 51 7D 3B A6 81 37 56 18 36 81 3F FE 19… 0,,12661,0:56.462.876,14.004.750 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,12662,0:56.476.881,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12666,0:56.477.878,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,12667,0:56.477.881,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 76 7A 51 7D 3B A6 81 37 56 18 36 81 3F FE 19… 0,,12671,0:56.478.878,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,12672,0:56.493.883,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 1F 05 E9 46 8F E7 9C FC 88 CA FB 51 1B 61 C3… 0,,12676,0:56.494.880,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,12677,0:56.508.885,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12681,0:56.509.882,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,12682,0:56.509.886,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 1F 05 E9 46 8F E7 9C FC 88 CA FB 51 1B 61 C3… 0,,12686,0:56.510.882,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,12687,0:56.525.888,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 40 47 63 B6 F9 EC 7A 39 C3 D8 80 44 FF 35 47… 0,,12691,0:56.526.885,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,12692,0:56.540.890,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12696,0:56.541.887,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,12697,0:56.541.890,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 40 47 63 B6 F9 EC 7A 39 C3 D8 80 44 FF 35 47… 0,,12701,0:56.542.887,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,12702,0:56.557.892,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 B7 32 A3 BB D4 0A 72 5D 15 99 6F 12 C2 78 F2… 0,,12706,0:56.558.889,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,12707,0:56.572.894,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12711,0:56.573.891,2.833 us,,,,,[1 SOF],[Frame: 1217] 0,,12712,0:56.573.894,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 25 B7 32 A3 BB D4 0A 72 5D 15 99 6F 12 C2 78 F2… 0,,12716,0:56.574.891,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,12717,0:56.589.897,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 E6 94 C1 A1 C3 B1 D0 FA 0B 63 69 99 E4 BC 05… 0,,12721,0:56.590.894,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,12722,0:56.604.899,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12726,0:56.605.896,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,12727,0:56.605.899,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 E6 94 C1 A1 C3 B1 D0 FA 0B 63 69 99 E4 BC 05… 0,,12731,0:56.606.896,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,12732,0:56.621.901,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 07 BA 49 3C 19 43 23 86 FB 41 FA 31 B1 93 7F 2C… 0,,12736,0:56.622.898,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,12737,0:56.636.903,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12741,0:56.637.900,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,12742,0:56.637.903,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 07 BA 49 3C 19 43 23 86 FB 41 FA 31 B1 93 7F 2C… 0,,12746,0:56.638.900,15.004.916 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,12747,0:56.653.906,50.937 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 88 62 6C 7F 28 14 AE 95 F9 63 FE C7 6B 09 CA F7… 0,,12751,0:56.654.902,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,12752,0:56.668.908,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12756,0:56.669.905,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,12757,0:56.669.908,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 88 62 6C 7F 28 14 AE 95 F9 63 FE C7 6B 09 CA F7… 0,,12761,0:56.670.905,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,12762,0:56.685.910,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 8B 9C 31 FE 85 0F 96 9D E8 30 5B 76 B6 31 39… 0,,12766,0:56.686.907,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,12767,0:56.700.912,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12771,0:56.701.909,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,12772,0:56.701.912,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 8B 9C 31 FE 85 0F 96 9D E8 30 5B 76 B6 31 39… 0,,12776,0:56.702.909,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,12777,0:56.717.914,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 35 8F C3 1A 29 A7 43 D7 35 97 4F 61 E2 65 12… 0,,12781,0:56.718.911,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,12782,0:56.732.917,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12786,0:56.733.913,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,12787,0:56.733.917,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 35 8F C3 1A 29 A7 43 D7 35 97 4F 61 E2 65 12… 0,,12791,0:56.734.914,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,12792,0:56.749.919,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D0 01 04 43 62 19 97 AE F6 46 BB 11 5B E6 C3 AC… 0,,12796,0:56.750.916,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,12797,0:56.764.921,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12801,0:56.765.918,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,12802,0:56.765.921,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D0 01 04 43 62 19 97 AE F6 46 BB 11 5B E6 C3 AC… 0,,12806,0:56.766.918,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,12807,0:56.781.923,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 69 9F 51 D3 EA E8 BD EC 2B 2A CC C5 B7 E6 83… 0,,12811,0:56.782.920,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,12812,0:56.796.925,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12816,0:56.797.922,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,12817,0:56.797.926,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 69 9F 51 D3 EA E8 BD EC 2B 2A CC C5 B7 E6 83… 0,,12821,0:56.798.922,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,12822,0:56.813.928,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E BB 9B D6 C9 98 10 23 67 5D DE 5A 30 C7 CB E7… 0,,12826,0:56.814.925,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,12827,0:56.828.930,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12831,0:56.829.927,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,12832,0:56.829.930,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E BB 9B D6 C9 98 10 23 67 5D DE 5A 30 C7 CB E7… 0,,12836,0:56.830.927,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,12837,0:56.845.932,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 6B A1 A6 50 5B F3 46 92 24 7B FF FA 1A 6E F5… 0,,12841,0:56.846.929,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,12842,0:56.860.934,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12846,0:56.861.931,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,12847,0:56.861.934,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 6B A1 A6 50 5B F3 46 92 24 7B FF FA 1A 6E F5… 0,,12851,0:56.862.931,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,12852,0:56.877.937,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 88 BA B0 FC E6 E3 DD 7B D6 7E D3 F7 10 89 70 07… 0,,12856,0:56.878.934,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,12857,0:56.892.939,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12861,0:56.893.936,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,12862,0:56.893.939,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 88 BA B0 FC E6 E3 DD 7B D6 7E D3 F7 10 89 70 07… 0,,12866,0:56.894.936,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,12867,0:56.909.941,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 50 DD 00 5C CB FC 1C 3E 01 44 EC D2 F0 11 BF D2… 0,,12871,0:56.910.938,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,12872,0:56.924.943,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12876,0:56.925.940,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,12877,0:56.925.943,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 50 DD 00 5C CB FC 1C 3E 01 44 EC D2 F0 11 BF D2… 0,,12881,0:56.926.940,15.004.916 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,12882,0:56.941.946,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 45 27 C2 D8 AF 15 EA EE 8F 68 02 BB 27 6B 70 51… 0,,12886,0:56.942.942,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,12887,0:56.956.948,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12891,0:56.957.945,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,12892,0:56.957.948,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 45 27 C2 D8 AF 15 EA EE 8F 68 02 BB 27 6B 70 51… 0,,12896,0:56.958.945,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,12897,0:56.973.950,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4C 73 66 C1 B3 48 38 9F 4A 34 69 02 BD 0F 0C 08… 0,,12901,0:56.974.947,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,12902,0:56.988.952,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12906,0:56.989.949,2.833 us,,,,,[1 SOF],[Frame: 1633] 0,,12907,0:56.989.952,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4C 73 66 C1 B3 48 38 9F 4A 34 69 02 BD 0F 0C 08… 0,,12911,0:56.990.949,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,12912,0:57.005.954,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 43 5E 6F 7F AB C6 88 78 1F BF 48 AC 25 67 D7 3C… 0,,12916,0:57.006.951,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,12917,0:57.020.957,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12921,0:57.021.953,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,12922,0:57.021.957,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 43 5E 6F 7F AB C6 88 78 1F BF 48 AC 25 67 D7 3C… 0,,12926,0:57.022.954,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,12927,0:57.037.959,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0C BC DC 6C A5 42 1F 17 AB 15 D8 0F 15 6A 55 E9… 0,,12931,0:57.038.956,14.004.750 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,12932,0:57.052.961,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12936,0:57.053.958,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,12937,0:57.053.961,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0C BC DC 6C A5 42 1F 17 AB 15 D8 0F 15 6A 55 E9… 0,,12941,0:57.054.958,15.004.916 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,12942,0:57.069.963,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 C5 C7 F2 34 6C 7E 69 9D 66 C6 78 B1 4D 34 07… 0,,12946,0:57.070.960,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,12947,0:57.084.965,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12951,0:57.085.962,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,12952,0:57.085.966,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E3 C5 C7 F2 34 6C 7E 69 9D 66 C6 78 B1 4D 34 07… 0,,12956,0:57.086.962,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,12957,0:57.101.968,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 BE DE A6 A8 7F 2F C8 9D 98 DE F5 F2 40 DC 95… 0,,12961,0:57.102.965,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,12962,0:57.116.970,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12966,0:57.117.967,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,12967,0:57.117.970,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 BE DE A6 A8 7F 2F C8 9D 98 DE F5 F2 40 DC 95… 0,,12971,0:57.118.967,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,12972,0:57.133.972,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 A7 9E 6A D6 31 B3 60 19 37 68 02 91 0A ED 03… 0,,12976,0:57.134.969,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,12977,0:57.148.974,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12981,0:57.149.971,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,12982,0:57.149.974,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 A7 9E 6A D6 31 B3 60 19 37 68 02 91 0A ED 03… 0,,12986,0:57.150.971,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,12987,0:57.165.977,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B B1 6B F4 0B 8A 01 47 76 BF 8B 2A 2E 30 90 C3… 0,,12991,0:57.166.974,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,12992,0:57.180.979,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12996,0:57.181.976,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,12997,0:57.181.979,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B B1 6B F4 0B 8A 01 47 76 BF 8B 2A 2E 30 90 C3… 0,,13001,0:57.182.976,15.004.916 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,13002,0:57.197.981,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 07 4E 82 40 74 7B D4 7E 54 FE D1 7C 0D 35 4A 7B… 0,,13006,0:57.198.978,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,13007,0:57.212.983,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13011,0:57.213.980,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,13012,0:57.213.983,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 07 4E 82 40 74 7B D4 7E 54 FE D1 7C 0D 35 4A 7B… 0,,13016,0:57.214.980,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,13017,0:57.229.986,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 BF 77 8C DE AF 68 29 9B BE 48 EB 93 18 76 8F… 0,,13021,0:57.230.982,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,13022,0:57.244.988,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13026,0:57.245.985,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,13027,0:57.245.988,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 BF 77 8C DE AF 68 29 9B BE 48 EB 93 18 76 8F… 0,,13031,0:57.246.985,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,13032,0:57.261.990,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B E3 22 CD 98 7B AE 34 F1 B3 FD 3D FC D6 F4 F8… 0,,13036,0:57.262.987,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,13037,0:57.276.992,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13041,0:57.277.989,2.833 us,,,,,[1 SOF],[Frame: 1921] 0,,13042,0:57.277.992,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B E3 22 CD 98 7B AE 34 F1 B3 FD 3D FC D6 F4 F8… 0,,13046,0:57.278.989,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,13047,0:57.293.994,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD CE B4 BE F0 04 36 4C 0E 74 AF 47 B5 E1 57 63… 0,,13051,0:57.294.991,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,13052,0:57.308.997,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13056,0:57.309.993,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,13057,0:57.309.997,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD CE B4 BE F0 04 36 4C 0E 74 AF 47 B5 E1 57 63… 0,,13061,0:57.310.994,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,13062,0:57.325.999,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 E8 F0 A1 E3 0E 00 DF 40 A5 DF 31 49 F4 A6 9C… 0,,13066,0:57.326.996,14.004.750 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,13067,0:57.341.001,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13071,0:57.341.998,16.005.125 ms,,,,,[17 SOF],[Frames: 1985 - 2001] 0,,13072,0:57.358.003,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 87 EC 76 25 F1 71 FA FD FD 75 34 E8 2A 08 E7… 0,,13076,0:57.359.000,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,13077,0:57.373.005,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13081,0:57.374.002,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,13082,0:57.374.006,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 87 EC 76 25 F1 71 FA FD FD 75 34 E8 2A 08 E7… 0,,13086,0:57.375.002,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,13087,0:57.390.008,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 26 6C 7C E3 C0 0B F5 64 89 A3 0E 60 D9 B1 67… 0,,13091,0:57.391.005,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,13092,0:57.405.010,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13096,0:57.406.007,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,13097,0:57.406.010,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 26 6C 7C E3 C0 0B F5 64 89 A3 0E 60 D9 B1 67… 0,,13101,0:57.407.007,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,13102,0:57.422.012,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 C7 CC 5B A5 D2 4F 09 04 04 79 FC 70 F1 B4 71… 0,,13106,0:57.423.009,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,13107,0:57.437.014,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13111,0:57.438.011,2.833 us,,,,,[1 SOF],[Frame: 33] 0,,13112,0:57.438.014,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 C7 CC 5B A5 D2 4F 09 04 04 79 FC 70 F1 B4 71… 0,,13116,0:57.439.011,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,13117,0:57.454.017,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 13 BE E8 BE 41 62 CD 47 1B D2 87 2D 75 DA 7D 17… 0,,13121,0:57.455.014,14.004.750 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,13122,0:57.469.019,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13126,0:57.470.016,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,13127,0:57.470.019,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 13 BE E8 BE 41 62 CD 47 1B D2 87 2D 75 DA 7D 17… 0,,13131,0:57.471.016,15.004.916 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,13132,0:57.486.021,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 D4 82 85 A2 15 29 01 8F DA 72 F7 73 25 64 63… 0,,13136,0:57.487.018,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,13137,0:57.501.023,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13141,0:57.502.020,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,13142,0:57.502.023,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 D4 82 85 A2 15 29 01 8F DA 72 F7 73 25 64 63… 0,,13146,0:57.503.020,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,13147,0:57.518.026,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D 39 F7 93 BC 66 06 1D 43 CB 57 65 48 2E EA 87… 0,,13151,0:57.519.022,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,13152,0:57.533.028,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13156,0:57.534.024,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,13157,0:57.534.028,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D 39 F7 93 BC 66 06 1D 43 CB 57 65 48 2E EA 87… 0,,13161,0:57.535.025,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,13162,0:57.550.030,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF 82 9D A9 C8 7A CF 59 D9 A5 38 B9 F7 FD 19 90… 0,,13166,0:57.551.027,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,13167,0:57.565.032,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13171,0:57.566.029,2.833 us,,,,,[1 SOF],[Frame: 161] 0,,13172,0:57.566.032,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF 82 9D A9 C8 7A CF 59 D9 A5 38 B9 F7 FD 19 90… 0,,13176,0:57.567.029,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,13177,0:57.582.034,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 02 02 09 66 F7 B1 3A 51 61 3D 22 EF 62 84 15… 0,,13181,0:57.583.031,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,13182,0:57.597.036,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13186,0:57.598.033,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,13187,0:57.598.037,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 02 02 09 66 F7 B1 3A 51 61 3D 22 EF 62 84 15… 0,,13191,0:57.599.034,15.004.916 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,13192,0:57.614.039,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A6 AB AF DE 7B 8C A2 C1 CE 9B 76 22 EE BB E5 15… 0,,13196,0:57.615.036,14.004.750 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,13197,0:57.629.041,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13201,0:57.630.038,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,13202,0:57.630.041,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A6 AB AF DE 7B 8C A2 C1 CE 9B 76 22 EE BB E5 15… 0,,13206,0:57.631.038,15.004.916 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,13207,0:57.646.043,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C 59 A8 E4 03 6B 89 98 9E 0A 32 21 24 7D 6B 37… 0,,13211,0:57.647.040,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,13212,0:57.661.045,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13216,0:57.662.042,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,13217,0:57.662.046,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C 59 A8 E4 03 6B 89 98 9E 0A 32 21 24 7D 6B 37… 0,,13221,0:57.663.042,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,13222,0:57.678.048,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B7 EE C3 09 35 73 48 31 95 67 D5 6A E4 E9 76 41… 0,,13226,0:57.679.045,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,13227,0:57.693.050,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13231,0:57.694.047,2.833 us,,,,,[1 SOF],[Frame: 289] 0,,13232,0:57.694.050,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B7 EE C3 09 35 73 48 31 95 67 D5 6A E4 E9 76 41… 0,,13236,0:57.695.047,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,13237,0:57.710.052,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 A3 58 A4 44 90 54 81 53 B5 6A 93 C2 AC 49 0A… 0,,13241,0:57.711.049,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,13242,0:57.725.054,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13246,0:57.726.051,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,13247,0:57.726.054,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 A3 58 A4 44 90 54 81 53 B5 6A 93 C2 AC 49 0A… 0,,13251,0:57.727.051,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,13252,0:57.742.057,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF E6 65 D0 CC C5 60 4C 18 9A CC 2A D4 1F EF 81… 0,,13256,0:57.743.054,14.004.750 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,13257,0:57.757.059,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13261,0:57.758.056,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,13262,0:57.758.059,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF E6 65 D0 CC C5 60 4C 18 9A CC 2A D4 1F EF 81… 0,,13266,0:57.759.056,15.004.916 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,13267,0:57.774.061,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 ED CF 00 AE 03 E9 D0 B6 AC 24 19 66 F4 D9 6A 7E… 0,,13271,0:57.775.058,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,13272,0:57.789.063,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13276,0:57.790.060,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,13277,0:57.790.063,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 ED CF 00 AE 03 E9 D0 B6 AC 24 19 66 F4 D9 6A 7E… 0,,13281,0:57.791.060,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,13282,0:57.806.066,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AB C3 FB 17 AE 53 28 FF 78 23 FF 35 BD 4E B5 A3… 0,,13286,0:57.807.062,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,13287,0:57.821.068,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13291,0:57.822.064,2.833 us,,,,,[1 SOF],[Frame: 417] 0,,13292,0:57.822.068,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AB C3 FB 17 AE 53 28 FF 78 23 FF 35 BD 4E B5 A3… 0,,13296,0:57.823.065,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,13297,0:57.838.070,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 82 52 19 CA EC 51 FA A7 31 4B 0F 44 51 D7 C5… 0,,13301,0:57.839.067,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,13302,0:57.853.072,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13306,0:57.854.069,2.833 us,,,,,[1 SOF],[Frame: 449] 0,,13307,0:57.854.072,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 82 52 19 CA EC 51 FA A7 31 4B 0F 44 51 D7 C5… 0,,13311,0:57.855.069,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,13312,0:57.870.074,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 12 0B EA 92 90 9C 78 BA D8 7C 15 A3 CF 16 1D… 0,,13316,0:57.871.071,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,13317,0:57.885.076,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13321,0:57.886.073,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,13322,0:57.886.077,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 12 0B EA 92 90 9C 78 BA D8 7C 15 A3 CF 16 1D… 0,,13326,0:57.887.073,15.004.916 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,13327,0:57.902.079,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 56 C2 A5 FF 14 74 F4 C0 AA 67 CA AF 87 7C F9 FC… 0,,13331,0:57.903.076,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,13332,0:57.917.081,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13336,0:57.918.078,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,13337,0:57.918.081,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 56 C2 A5 FF 14 74 F4 C0 AA 67 CA AF 87 7C F9 FC… 0,,13341,0:57.919.078,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,13342,0:57.934.083,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 58 78 A4 49 F6 67 8C F5 AC 54 4C B4 C5 35 0B… 0,,13346,0:57.935.080,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,13347,0:57.949.085,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13351,0:57.950.082,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,13352,0:57.950.085,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 58 78 A4 49 F6 67 8C F5 AC 54 4C B4 C5 35 0B… 0,,13356,0:57.951.082,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,13357,0:57.966.088,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 81 8E 0A EB F4 AD 65 F4 9B 02 00 84 52 12 E2… 0,,13361,0:57.967.085,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,13362,0:57.981.090,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13366,0:57.982.087,2.833 us,,,,,[1 SOF],[Frame: 577] 0,,13367,0:57.982.090,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 81 8E 0A EB F4 AD 65 F4 9B 02 00 84 52 12 E2… 0,,13371,0:57.983.087,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,13372,0:57.998.092,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 3E 68 F5 C5 84 49 DE 02 76 69 5D A4 09 3B 0A… 0,,13376,0:57.999.089,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,13377,0:58.013.094,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13381,0:58.014.091,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,13382,0:58.014.094,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 3E 68 F5 C5 84 49 DE 02 76 69 5D A4 09 3B 0A… 0,,13386,0:58.015.091,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,13387,0:58.030.097,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE C9 F5 A4 15 8C 6C 42 C5 B7 E9 94 43 38 8B 87… 0,,13391,0:58.031.093,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,13392,0:58.045.099,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13396,0:58.046.096,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,13397,0:58.046.099,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE C9 F5 A4 15 8C 6C 42 C5 B7 E9 94 43 38 8B 87… 0,,13401,0:58.047.096,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,13402,0:58.062.101,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 30 23 FB B0 B1 10 BC C0 AD 78 E6 AD 42 FD B7… 0,,13406,0:58.063.098,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,13407,0:58.077.103,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13411,0:58.078.100,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,13412,0:58.078.103,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 30 23 FB B0 B1 10 BC C0 AD 78 E6 AD 42 FD B7… 0,,13416,0:58.079.100,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,13417,0:58.094.105,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 3A AD 68 DC 67 3B 0D F3 82 93 CD 28 AF 2B 68… 0,,13421,0:58.095.102,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,13422,0:58.109.108,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13426,0:58.110.104,2.833 us,,,,,[1 SOF],[Frame: 705] 0,,13427,0:58.110.108,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 3A AD 68 DC 67 3B 0D F3 82 93 CD 28 AF 2B 68… 0,,13431,0:58.111.105,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,13432,0:58.126.110,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3C 92 D6 A4 E3 04 AB 02 A0 59 5C FA 91 27 09 34… 0,,13436,0:58.127.107,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,13437,0:58.141.112,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13441,0:58.142.109,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,13442,0:58.142.112,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3C 92 D6 A4 E3 04 AB 02 A0 59 5C FA 91 27 09 34… 0,,13446,0:58.143.109,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,13447,0:58.158.114,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 04 90 D7 54 BB 34 B2 D2 A7 CF CF FE 07 C5 AE… 0,,13451,0:58.159.111,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,13452,0:58.173.116,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13456,0:58.174.113,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,13457,0:58.174.117,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 04 90 D7 54 BB 34 B2 D2 A7 CF CF FE 07 C5 AE… 0,,13461,0:58.175.113,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,13462,0:58.190.119,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 B1 F3 BB ED D1 EE 25 3E 42 10 69 12 E6 11 DD… 0,,13466,0:58.191.116,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,13467,0:58.205.121,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13471,0:58.206.118,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,13472,0:58.206.121,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 B1 F3 BB ED D1 EE 25 3E 42 10 69 12 E6 11 DD… 0,,13476,0:58.207.118,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,13477,0:58.222.123,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA 02 5F 46 50 96 D5 F8 5D 7F ED 13 B6 16 2D F7… 0,,13481,0:58.223.120,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,13482,0:58.237.125,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13486,0:58.238.122,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,13487,0:58.238.125,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA 02 5F 46 50 96 D5 F8 5D 7F ED 13 B6 16 2D F7… 0,,13491,0:58.239.122,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,13492,0:58.254.128,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C 27 54 52 67 04 52 04 AC 87 A5 4D E3 5C 7B A3… 0,,13496,0:58.255.125,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,13497,0:58.269.130,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13501,0:58.270.127,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,13502,0:58.270.130,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C 27 54 52 67 04 52 04 AC 87 A5 4D E3 5C 7B A3… 0,,13506,0:58.271.127,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,13507,0:58.286.132,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C 61 05 89 02 D4 11 E7 9B B1 76 B0 F9 E6 F3 86… 0,,13511,0:58.287.129,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,13512,0:58.301.134,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13516,0:58.302.131,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,13517,0:58.302.134,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C 61 05 89 02 D4 11 E7 9B B1 76 B0 F9 E6 F3 86… 0,,13521,0:58.303.131,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,13522,0:58.318.137,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 45 DD 4F 9F E9 78 63 B3 1B 2A 2A CC 17 33 7F… 0,,13526,0:58.319.133,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,13527,0:58.333.139,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13531,0:58.334.136,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,13532,0:58.334.139,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 45 DD 4F 9F E9 78 63 B3 1B 2A 2A CC 17 33 7F… 0,,13536,0:58.335.136,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,13537,0:58.350.141,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 48 8D F6 26 C7 A9 F8 B7 2E 76 31 F7 4E CB 2B… 0,,13541,0:58.351.138,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,13542,0:58.365.143,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13546,0:58.366.140,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,13547,0:58.366.143,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 48 8D F6 26 C7 A9 F8 B7 2E 76 31 F7 4E CB 2B… 0,,13551,0:58.367.140,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,13552,0:58.382.145,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C B3 94 99 5B D6 DC 61 D8 D4 B2 7E 31 55 7C 62… 0,,13556,0:58.383.142,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,13557,0:58.397.148,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13561,0:58.398.144,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,13562,0:58.398.148,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C B3 94 99 5B D6 DC 61 D8 D4 B2 7E 31 55 7C 62… 0,,13566,0:58.399.145,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,13567,0:58.414.150,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 32 A5 DA 10 60 62 FA 8C 51 AF CF 3E 2F 76 94… 0,,13571,0:58.415.147,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,13572,0:58.429.152,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13576,0:58.430.149,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,13577,0:58.430.152,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 32 A5 DA 10 60 62 FA 8C 51 AF CF 3E 2F 76 94… 0,,13581,0:58.431.149,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,13582,0:58.446.154,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 84 33 8E 69 C3 D4 86 9D 17 39 12 2C 8C DF FF 2D… 0,,13586,0:58.447.151,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,13587,0:58.461.156,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13591,0:58.462.153,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,13592,0:58.462.157,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 84 33 8E 69 C3 D4 86 9D 17 39 12 2C 8C DF FF 2D… 0,,13596,0:58.463.153,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,13597,0:58.478.159,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 4D E5 3E 1B 27 2E EA C6 E1 39 B3 A7 34 6F A9… 0,,13601,0:58.479.156,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,13602,0:58.493.161,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13606,0:58.494.158,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,13607,0:58.494.161,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 4D E5 3E 1B 27 2E EA C6 E1 39 B3 A7 34 6F A9… 0,,13611,0:58.495.158,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,13612,0:58.510.163,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 60 59 D8 DF AA FC 8F FE 17 37 9B 11 66 22 D2 15… 0,,13616,0:58.511.160,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,13617,0:58.525.165,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13621,0:58.526.162,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,13622,0:58.526.165,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 59 D8 DF AA FC 8F FE 17 37 9B 11 66 22 D2 15… 0,,13626,0:58.527.162,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,13627,0:58.542.168,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 25 4E 12 E6 27 E4 8B F1 0B 0C 19 B0 36 D7 46… 0,,13631,0:58.543.165,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,13632,0:58.557.170,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13636,0:58.558.167,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,13637,0:58.558.170,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 25 4E 12 E6 27 E4 8B F1 0B 0C 19 B0 36 D7 46… 0,,13641,0:58.559.167,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,13642,0:58.574.172,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 A8 D4 6A 58 9E A9 38 21 EE 00 02 38 B5 16 3D… 0,,13646,0:58.575.169,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,13647,0:58.589.174,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13651,0:58.590.171,16.005.041 ms,,,,,[17 SOF],[Frames: 1185 - 1201] 0,,13652,0:58.606.177,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 29 07 12 B0 03 62 1B 0B BA 83 50 AF A0 46 6F… 0,,13656,0:58.607.173,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,13657,0:58.621.179,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13661,0:58.622.176,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,13662,0:58.622.179,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 29 07 12 B0 03 62 1B 0B BA 83 50 AF A0 46 6F… 0,,13666,0:58.623.176,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,13667,0:58.638.181,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9D D8 48 83 BA 37 1E 1D 31 49 DE 6B 7C 07 30 47… 0,,13671,0:58.639.178,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,13672,0:58.653.183,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13676,0:58.654.180,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,13677,0:58.654.183,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9D D8 48 83 BA 37 1E 1D 31 49 DE 6B 7C 07 30 47… 0,,13681,0:58.655.180,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,13682,0:58.670.185,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 3E B3 BA 57 FE 81 18 FF 23 4C 8B F0 06 76 48… 0,,13686,0:58.671.182,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,13687,0:58.685.188,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13691,0:58.686.184,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,13692,0:58.686.188,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 3E B3 BA 57 FE 81 18 FF 23 4C 8B F0 06 76 48… 0,,13696,0:58.687.185,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,13697,0:58.702.190,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD 1C 2D 60 9E E2 FD 6A 30 61 4A 62 9B A1 95 C9… 0,,13701,0:58.703.187,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,13702,0:58.717.192,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13706,0:58.718.189,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,13707,0:58.718.192,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD 1C 2D 60 9E E2 FD 6A 30 61 4A 62 9B A1 95 C9… 0,,13711,0:58.719.189,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,13712,0:58.734.194,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB 5C B3 F2 DD BA 0B 54 F6 C0 57 20 2C 5E 87 12… 0,,13716,0:58.735.191,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,13717,0:58.749.196,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13721,0:58.750.193,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,13722,0:58.750.197,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB 5C B3 F2 DD BA 0B 54 F6 C0 57 20 2C 5E 87 12… 0,,13726,0:58.751.193,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,13727,0:58.766.199,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E EE D2 F6 BD 67 96 87 F7 00 6E A2 27 EB 04 BE… 0,,13731,0:58.767.196,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,13732,0:58.781.201,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13736,0:58.782.198,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,13737,0:58.782.201,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E EE D2 F6 BD 67 96 87 F7 00 6E A2 27 EB 04 BE… 0,,13741,0:58.783.198,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,13742,0:58.798.203,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 F0 73 77 31 6F 29 7B 48 7E 5E 07 7A 7D FD D2… 0,,13746,0:58.799.200,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,13747,0:58.813.205,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13751,0:58.814.202,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,13752,0:58.814.205,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 F0 73 77 31 6F 29 7B 48 7E 5E 07 7A 7D FD D2… 0,,13756,0:58.815.202,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,13757,0:58.830.208,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 42 2F F4 9C 2E D2 B0 5C 10 96 8E 74 02 C0 3D… 0,,13761,0:58.831.205,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,13762,0:58.845.210,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13766,0:58.846.207,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,13767,0:58.846.210,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 42 2F F4 9C 2E D2 B0 5C 10 96 8E 74 02 C0 3D… 0,,13771,0:58.847.207,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,13772,0:58.862.212,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 65 6A FA F4 E9 C5 C7 50 03 60 76 2D B6 B6 36… 0,,13776,0:58.863.209,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,13777,0:58.877.214,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13781,0:58.878.211,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,13782,0:58.878.214,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 65 6A FA F4 E9 C5 C7 50 03 60 76 2D B6 B6 36… 0,,13786,0:58.879.211,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,13787,0:58.894.217,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 AC 61 B3 38 A1 AD 0D 46 AE 56 C3 CA BA E0 2D… 0,,13791,0:58.895.213,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,13792,0:58.909.219,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13796,0:58.910.216,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,13797,0:58.910.219,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 AC 61 B3 38 A1 AD 0D 46 AE 56 C3 CA BA E0 2D… 0,,13801,0:58.911.216,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,13802,0:58.926.221,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 8F 31 EF 29 1E DC F6 A0 9F E7 38 86 E6 71 6F… 0,,13806,0:58.927.218,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,13807,0:58.941.223,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13811,0:58.942.220,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,13812,0:58.942.223,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 8F 31 EF 29 1E DC F6 A0 9F E7 38 86 E6 71 6F… 0,,13816,0:58.943.220,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,13817,0:58.958.226,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C 96 55 0C EF EE 2D B6 D5 1C 30 F8 17 27 20 65… 0,,13821,0:58.959.222,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,13822,0:58.973.228,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13826,0:58.974.224,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,13827,0:58.974.228,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C 96 55 0C EF EE 2D B6 D5 1C 30 F8 17 27 20 65… 0,,13831,0:58.975.225,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,13832,0:58.990.230,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA 2A FE 82 FB D3 30 86 5A 95 3B FD 7A E6 FC 48… 0,,13836,0:58.991.227,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,13837,0:59.005.232,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13841,0:59.006.229,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,13842,0:59.006.232,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA 2A FE 82 FB D3 30 86 5A 95 3B FD 7A E6 FC 48… 0,,13846,0:59.007.229,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,13847,0:59.022.234,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 3D E4 AC A5 8F BE 64 B4 8E 1C 8F 0D B9 CC EB… 0,,13851,0:59.023.231,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,13852,0:59.037.236,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13856,0:59.038.233,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,13857,0:59.038.237,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 3D E4 AC A5 8F BE 64 B4 8E 1C 8F 0D B9 CC EB… 0,,13861,0:59.039.233,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,13862,0:59.054.239,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5B 88 B0 5B 4B B7 62 93 88 3B 9E 9A 68 C5 B8 51… 0,,13866,0:59.055.236,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,13867,0:59.069.241,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13871,0:59.070.238,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,13872,0:59.070.241,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5B 88 B0 5B 4B B7 62 93 88 3B 9E 9A 68 C5 B8 51… 0,,13876,0:59.071.238,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,13877,0:59.086.243,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 E9 4F 96 CE 41 56 96 64 2C EE 3F 1F A2 C2 B4… 0,,13881,0:59.087.240,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,13882,0:59.101.245,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13886,0:59.102.242,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,13887,0:59.102.245,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 E9 4F 96 CE 41 56 96 64 2C EE 3F 1F A2 C2 B4… 0,,13891,0:59.103.242,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,13892,0:59.118.248,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9F B3 21 90 58 D7 97 59 2E 9B B8 A3 FB 83 2A 14… 0,,13896,0:59.119.245,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,13897,0:59.133.250,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13901,0:59.134.247,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,13902,0:59.134.250,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9F B3 21 90 58 D7 97 59 2E 9B B8 A3 FB 83 2A 14… 0,,13906,0:59.135.247,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,13907,0:59.150.252,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF FC 6D 0D 80 1C 4B 33 F5 E8 FF 90 93 6A 9B 56… 0,,13911,0:59.151.249,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,13912,0:59.165.254,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13916,0:59.166.251,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,13917,0:59.166.254,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF FC 6D 0D 80 1C 4B 33 F5 E8 FF 90 93 6A 9B 56… 0,,13921,0:59.167.251,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,13922,0:59.182.257,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D9 78 5A 3B 72 26 04 F2 56 5D B8 8B 43 05 88 0F… 0,,13926,0:59.183.253,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,13927,0:59.197.259,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13931,0:59.198.255,16.005.041 ms,,,,,[17 SOF],[Frames: 1793 - 1809] 0,,13932,0:59.214.261,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC C1 CD 25 8B 42 D1 54 51 5B 31 20 57 C1 2F AC… 0,,13936,0:59.215.258,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,13937,0:59.229.263,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13941,0:59.230.260,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,13942,0:59.230.263,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC C1 CD 25 8B 42 D1 54 51 5B 31 20 57 C1 2F AC… 0,,13946,0:59.231.260,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,13947,0:59.246.265,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 F8 9D 3B 32 9C 97 C2 84 B0 28 CE 0B F1 F9 31… 0,,13951,0:59.247.262,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,13952,0:59.261.267,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13956,0:59.262.264,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,13957,0:59.262.268,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 F8 9D 3B 32 9C 97 C2 84 B0 28 CE 0B F1 F9 31… 0,,13961,0:59.263.265,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,13962,0:59.278.270,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E E5 BB 92 B1 3D D8 C2 17 FD 99 5A 93 AA 8C 79… 0,,13966,0:59.279.267,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,13967,0:59.293.272,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13971,0:59.294.269,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,13972,0:59.294.272,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E E5 BB 92 B1 3D D8 C2 17 FD 99 5A 93 AA 8C 79… 0,,13976,0:59.295.269,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,13977,0:59.310.274,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 F0 9E 99 40 C8 B6 E6 91 2E 49 73 6A 56 DC 0C… 0,,13981,0:59.311.271,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,13982,0:59.325.276,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13986,0:59.326.273,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,13987,0:59.326.277,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 F0 9E 99 40 C8 B6 E6 91 2E 49 73 6A 56 DC 0C… 0,,13991,0:59.327.273,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,13992,0:59.342.279,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 FC FB AD EF 9D EA C2 A9 98 AE E5 F9 01 55 9B… 0,,13996,0:59.343.276,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,13997,0:59.357.281,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14001,0:59.358.278,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,14002,0:59.358.281,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 FC FB AD EF 9D EA C2 A9 98 AE E5 F9 01 55 9B… 0,,14006,0:59.359.278,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,14007,0:59.374.283,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 FF F0 06 56 33 E4 D2 2E 41 74 94 CE 15 A5 E0… 0,,14011,0:59.375.280,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,14012,0:59.389.285,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14016,0:59.390.282,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,14017,0:59.390.285,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 FF F0 06 56 33 E4 D2 2E 41 74 94 CE 15 A5 E0… 0,,14021,0:59.391.282,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,14022,0:59.406.288,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 4D 33 94 8F 10 68 AF EA 2D DE 3B EF E2 BA F5… 0,,14026,0:59.407.285,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,14027,0:59.421.290,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14031,0:59.422.287,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,14032,0:59.422.290,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 4D 33 94 8F 10 68 AF EA 2D DE 3B EF E2 BA F5… 0,,14036,0:59.423.287,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,14037,0:59.438.292,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 00 3E D6 04 E6 DD E1 A6 51 75 3F 1B 33 85 19… 0,,14041,0:59.439.289,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,14042,0:59.453.294,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14046,0:59.454.291,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,14047,0:59.454.294,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 00 3E D6 04 E6 DD E1 A6 51 75 3F 1B 33 85 19… 0,,14051,0:59.455.291,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,14052,0:59.470.296,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 F4 1B 2E 9F 51 EB CD 16 25 19 82 65 DD 49 20… 0,,14056,0:59.471.293,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,14057,0:59.485.299,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14061,0:59.486.295,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,14062,0:59.486.299,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 F4 1B 2E 9F 51 EB CD 16 25 19 82 65 DD 49 20… 0,,14066,0:59.487.296,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,14067,0:59.502.301,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 AA 10 C9 DD E6 C5 DF 1B B5 8B 6E 62 E5 F9 91… 0,,14071,0:59.503.298,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,14072,0:59.517.303,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14076,0:59.518.300,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,14077,0:59.518.303,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 AA 10 C9 DD E6 C5 DF 1B B5 8B 6E 62 E5 F9 91… 0,,14081,0:59.519.300,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,14082,0:59.534.305,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 8B 25 E1 1F DF 7F 0F 4B FA AA 7E E9 DF 05 4F… 0,,14086,0:59.535.302,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,14087,0:59.549.307,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14091,0:59.550.304,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,14092,0:59.550.308,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 8B 25 E1 1F DF 7F 0F 4B FA AA 7E E9 DF 05 4F… 0,,14096,0:59.551.304,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,14097,0:59.566.310,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 40 7E A4 49 6E B2 75 FA 3F 2F 69 1E 36 6D B4… 0,,14101,0:59.567.307,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,14102,0:59.581.312,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14106,0:59.582.309,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,14107,0:59.582.312,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 40 7E A4 49 6E B2 75 FA 3F 2F 69 1E 36 6D B4… 0,,14111,0:59.583.309,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,14112,0:59.598.314,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 60 AA 33 F6 B2 45 81 76 60 FA D1 73 7F 16 17… 0,,14116,0:59.599.311,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,14117,0:59.613.316,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14121,0:59.614.313,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,14122,0:59.614.316,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 60 AA 33 F6 B2 45 81 76 60 FA D1 73 7F 16 17… 0,,14126,0:59.615.313,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,14127,0:59.630.319,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FB 0F 98 87 94 E2 61 6B D6 7C 4B A5 5B 91 AD 93… 0,,14131,0:59.631.316,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,14132,0:59.645.321,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14136,0:59.646.318,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,14137,0:59.646.321,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FB 0F 98 87 94 E2 61 6B D6 7C 4B A5 5B 91 AD 93… 0,,14141,0:59.647.318,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,14142,0:59.662.323,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 6A 89 B2 10 92 FA C0 8A C5 47 1C 2E BF D3 A7… 0,,14146,0:59.663.320,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,14147,0:59.677.325,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14151,0:59.678.322,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,14152,0:59.678.325,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 6A 89 B2 10 92 FA C0 8A C5 47 1C 2E BF D3 A7… 0,,14156,0:59.679.322,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,14157,0:59.694.328,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 1A E3 E5 5A B0 EA 93 EE A4 4E 8F C4 1B 10 DC… 0,,14161,0:59.695.324,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,14162,0:59.709.330,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14166,0:59.710.327,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,14167,0:59.710.330,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 1A E3 E5 5A B0 EA 93 EE A4 4E 8F C4 1B 10 DC… 0,,14171,0:59.711.327,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,14172,0:59.726.332,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 C5 C6 83 04 27 B5 55 05 EA 73 BE 1D 9C BD A1… 0,,14176,0:59.727.329,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,14177,0:59.741.334,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14181,0:59.742.331,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,14182,0:59.742.334,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 C5 C6 83 04 27 B5 55 05 EA 73 BE 1D 9C BD A1… 0,,14186,0:59.743.331,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,14187,0:59.758.336,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A D5 35 BF E1 A4 27 4B F8 E7 79 97 BE 42 DB 40… 0,,14191,0:59.759.333,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,14192,0:59.773.339,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14196,0:59.774.335,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,14197,0:59.774.339,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A D5 35 BF E1 A4 27 4B F8 E7 79 97 BE 42 DB 40… 0,,14201,0:59.775.336,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,14202,0:59.790.341,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 DA 4C D8 9C 5E 5D 41 16 B5 70 81 01 B0 A8 90… 0,,14206,0:59.791.338,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,14207,0:59.805.343,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14211,0:59.806.340,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,14212,0:59.806.343,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 DA 4C D8 9C 5E 5D 41 16 B5 70 81 01 B0 A8 90… 0,,14216,0:59.807.340,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,14217,0:59.822.345,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B 6E C6 DF 08 51 55 9F 46 F1 C6 2B CE 33 F7 8D… 0,,14221,0:59.823.342,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,14222,0:59.837.347,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14226,0:59.838.344,16.005.041 ms,,,,,[17 SOF],[Frames: 385 - 401] 0,,14227,0:59.854.350,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 CC A5 F7 CD A5 BC EF 7E D3 2F B7 95 C9 FD CB… 0,,14231,0:59.855.347,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,14232,0:59.869.352,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14236,0:59.870.349,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,14237,0:59.870.352,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 CC A5 F7 CD A5 BC EF 7E D3 2F B7 95 C9 FD CB… 0,,14241,0:59.871.349,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,14242,0:59.886.354,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 72 95 34 65 17 8B BE DF A1 4F 64 5A BD A4 55 5E… 0,,14246,0:59.887.351,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,14247,0:59.901.356,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14251,0:59.902.353,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,14252,0:59.902.356,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 72 95 34 65 17 8B BE DF A1 4F 64 5A BD A4 55 5E… 0,,14256,0:59.903.353,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,14257,0:59.918.359,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 A5 C8 AD FD AA 19 A0 77 B7 91 C9 B7 0C 44 AF… 0,,14261,0:59.919.356,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,14262,0:59.933.361,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14266,0:59.934.358,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,14267,0:59.934.361,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 A5 C8 AD FD AA 19 A0 77 B7 91 C9 B7 0C 44 AF… 0,,14271,0:59.935.358,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,14272,0:59.950.363,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 8F 13 AC C2 39 B2 0C F5 D5 9E E4 7D C8 B5 8C… 0,,14276,0:59.951.360,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,14277,0:59.965.365,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14281,0:59.966.362,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,14282,0:59.966.365,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 8F 13 AC C2 39 B2 0C F5 D5 9E E4 7D C8 B5 8C… 0,,14286,0:59.967.362,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,14287,0:59.982.368,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 5B 36 4B 1F 41 DB 0A 21 3D 47 DF 7C 3D 0D 8E… 0,,14291,0:59.983.364,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,14292,0:59.997.370,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14296,0:59.998.367,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,14297,0:59.998.370,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 5B 36 4B 1F 41 DB 0A 21 3D 47 DF 7C 3D 0D 8E… 0,,14301,0:59.999.367,15.004.916 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,14302,1:00.014.372,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 CB 3C D5 38 B7 0A 34 87 45 B7 E2 77 06 38 88… 0,,14306,1:00.015.369,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,14307,1:00.029.374,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14311,1:00.030.371,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,14312,1:00.030.374,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 CB 3C D5 38 B7 0A 34 87 45 B7 E2 77 06 38 88… 0,,14316,1:00.031.371,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,14317,1:00.046.376,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 7C A7 C1 75 66 AB FE 8E 8A 35 3C E5 72 16 2D… 0,,14321,1:00.047.373,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,14322,1:00.061.379,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14326,1:00.062.375,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,14327,1:00.062.379,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 7C A7 C1 75 66 AB FE 8E 8A 35 3C E5 72 16 2D… 0,,14331,1:00.063.376,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,14332,1:00.078.381,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C0 94 D3 29 9D 93 91 41 1A 87 87 86 ED FF 0C 47… 0,,14336,1:00.079.378,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,14337,1:00.093.383,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14341,1:00.094.380,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,14342,1:00.094.383,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C0 94 D3 29 9D 93 91 41 1A 87 87 86 ED FF 0C 47… 0,,14346,1:00.095.380,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,14347,1:00.110.385,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD 04 75 3B CE 1B 91 C5 8A 4D C1 9D 20 B1 A7 82… 0,,14351,1:00.111.382,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,14352,1:00.125.387,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14356,1:00.126.384,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,14357,1:00.126.388,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD 04 75 3B CE 1B 91 C5 8A 4D C1 9D 20 B1 A7 82… 0,,14361,1:00.127.384,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,14362,1:00.142.390,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 9B CE 5C BB CF 54 58 B5 05 2A E3 88 8D 33 05… 0,,14366,1:00.143.387,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,14367,1:00.157.392,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14371,1:00.158.389,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,14372,1:00.158.392,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 9B CE 5C BB CF 54 58 B5 05 2A E3 88 8D 33 05… 0,,14376,1:00.159.389,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,14377,1:00.174.394,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FE A9 0C 64 CB 65 C8 5E 5E 0C 00 28 6C F9 8E D4… 0,,14381,1:00.175.391,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,14382,1:00.189.396,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14386,1:00.190.393,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,14387,1:00.190.396,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FE A9 0C 64 CB 65 C8 5E 5E 0C 00 28 6C F9 8E D4… 0,,14391,1:00.191.393,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,14392,1:00.206.399,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 13 7B B5 84 19 C8 52 42 BF 8F 97 B5 C9 73 95 DC… 0,,14396,1:00.207.396,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,14397,1:00.221.401,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14401,1:00.222.398,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,14402,1:00.222.401,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 13 7B B5 84 19 C8 52 42 BF 8F 97 B5 C9 73 95 DC… 0,,14406,1:00.223.398,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,14407,1:00.238.403,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 E8 30 29 0B 41 D2 6D B0 10 00 33 87 6A 9C CD… 0,,14411,1:00.239.400,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,14412,1:00.253.405,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14416,1:00.254.402,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,14417,1:00.254.405,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 E8 30 29 0B 41 D2 6D B0 10 00 33 87 6A 9C CD… 0,,14421,1:00.255.402,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,14422,1:00.270.408,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 29 86 33 8A CE E2 E7 F9 0B E4 97 B7 B2 70 93 73… 0,,14426,1:00.271.404,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,14427,1:00.285.410,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14431,1:00.286.407,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,14432,1:00.286.410,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 29 86 33 8A CE E2 E7 F9 0B E4 97 B7 B2 70 93 73… 0,,14436,1:00.287.407,15.004.916 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,14437,1:00.302.412,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 A9 1C AC 75 DB 1E D4 81 77 FF 29 65 A3 F8 9B… 0,,14441,1:00.303.409,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,14442,1:00.317.414,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14446,1:00.318.411,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,14447,1:00.318.414,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 A9 1C AC 75 DB 1E D4 81 77 FF 29 65 A3 F8 9B… 0,,14451,1:00.319.411,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,14452,1:00.334.416,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A 7E 1A 71 94 AC FF 8B 9A A4 FA 2A 31 19 BA B2… 0,,14456,1:00.335.413,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,14457,1:00.349.419,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14461,1:00.350.415,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,14462,1:00.350.419,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A 7E 1A 71 94 AC FF 8B 9A A4 FA 2A 31 19 BA B2… 0,,14466,1:00.351.416,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,14467,1:00.366.421,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 57 E7 8A 23 7D 7B 9A 4D 03 99 8A 5D 48 08 D8… 0,,14471,1:00.367.418,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,14472,1:00.381.423,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14476,1:00.382.420,2.833 us,,,,,[1 SOF],[Frame: 929] 0,,14477,1:00.382.423,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 57 E7 8A 23 7D 7B 9A 4D 03 99 8A 5D 48 08 D8… 0,,14481,1:00.383.420,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,14482,1:00.398.425,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 AD CD F1 93 1C 5D 6F AF 81 97 5E 3E BC 93 09… 0,,14486,1:00.399.422,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,14487,1:00.413.427,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14491,1:00.414.424,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,14492,1:00.414.428,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 AD CD F1 93 1C 5D 6F AF 81 97 5E 3E BC 93 09… 0,,14496,1:00.415.424,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,14497,1:00.430.430,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 3A 4A 65 5B 4B 00 47 78 01 F2 74 2F 9E 70 7A… 0,,14501,1:00.431.427,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,14502,1:00.445.432,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14506,1:00.446.429,16.005.125 ms,,,,,[17 SOF],[Frames: 993 - 1009] 0,,14507,1:00.462.434,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 A8 5B 71 D7 90 78 2B 52 B5 A6 51 26 D7 50 33… 0,,14511,1:00.463.431,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,14512,1:00.477.436,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14516,1:00.478.433,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,14517,1:00.478.436,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 25 A8 5B 71 D7 90 78 2B 52 B5 A6 51 26 D7 50 33… 0,,14521,1:00.479.433,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,14522,1:00.494.439,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC DB E5 83 0B BE 60 B4 F2 AD 1D 48 06 48 F8 C0… 0,,14526,1:00.495.436,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,14527,1:00.509.441,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14531,1:00.510.438,2.833 us,,,,,[1 SOF],[Frame: 1057] 0,,14532,1:00.510.441,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC DB E5 83 0B BE 60 B4 F2 AD 1D 48 06 48 F8 C0… 0,,14536,1:00.511.438,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,14537,1:00.526.443,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 FF 94 46 AE B5 9B B7 49 0F D9 AE C4 C1 38 B6… 0,,14541,1:00.527.440,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,14542,1:00.541.445,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14546,1:00.542.442,2.833 us,,,,,[1 SOF],[Frame: 1089] 0,,14547,1:00.542.445,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 FF 94 46 AE B5 9B B7 49 0F D9 AE C4 C1 38 B6… 0,,14551,1:00.543.442,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,14552,1:00.558.448,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 49 E6 39 E3 26 AD 68 9B 47 5D CE 67 17 A0 7A… 0,,14556,1:00.559.444,14.004.750 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,14557,1:00.573.450,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14561,1:00.574.447,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,14562,1:00.574.450,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 49 E6 39 E3 26 AD 68 9B 47 5D CE 67 17 A0 7A… 0,,14566,1:00.575.447,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,14567,1:00.590.452,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 E1 FB 10 18 9C EC 4A A6 C6 6F 00 18 AA 61 0E… 0,,14571,1:00.591.449,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,14572,1:00.605.454,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14576,1:00.606.451,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,14577,1:00.606.454,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 E1 FB 10 18 9C EC 4A A6 C6 6F 00 18 AA 61 0E… 0,,14581,1:00.607.451,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,14582,1:00.622.456,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D3 77 EC B5 99 22 BE 0D 3F D7 4E 04 9F 8F 4E 4C… 0,,14586,1:00.623.453,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,14587,1:00.637.458,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14591,1:00.638.455,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,14592,1:00.638.459,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D3 77 EC B5 99 22 BE 0D 3F D7 4E 04 9F 8F 4E 4C… 0,,14596,1:00.639.456,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,14597,1:00.654.461,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 47 85 B0 96 B0 19 C8 6A B0 97 98 AB 92 79 A4 1D… 0,,14601,1:00.655.458,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,14602,1:00.669.463,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14606,1:00.670.460,2.833 us,,,,,[1 SOF],[Frame: 1217] 0,,14607,1:00.670.463,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 47 85 B0 96 B0 19 C8 6A B0 97 98 AB 92 79 A4 1D… 0,,14611,1:00.671.460,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,14612,1:00.686.465,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2F D1 91 A5 A9 AF C5 91 F6 47 A9 2D 68 B4 CE 9F… 0,,14616,1:00.687.462,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,14617,1:00.701.467,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14621,1:00.702.464,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,14622,1:00.702.468,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2F D1 91 A5 A9 AF C5 91 F6 47 A9 2D 68 B4 CE 9F… 0,,14626,1:00.703.464,15.004.916 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,14627,1:00.718.470,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 E1 03 91 F8 25 C3 B6 FC 79 C2 74 38 9C CE 45… 0,,14631,1:00.719.467,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,14632,1:00.733.472,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14636,1:00.734.469,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,14637,1:00.734.472,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 E1 03 91 F8 25 C3 B6 FC 79 C2 74 38 9C CE 45… 0,,14641,1:00.735.469,15.004.916 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,14642,1:00.750.474,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B B9 99 12 66 4A E0 4D 65 B3 10 11 7C AC 0D 92… 0,,14646,1:00.751.471,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,14647,1:00.765.476,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14651,1:00.766.473,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,14652,1:00.766.476,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B B9 99 12 66 4A E0 4D 65 B3 10 11 7C AC 0D 92… 0,,14656,1:00.767.473,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,14657,1:00.782.479,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 22 60 42 B2 B5 44 B1 D9 D2 43 01 E1 02 01 3D… 0,,14661,1:00.783.476,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,14662,1:00.797.481,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14666,1:00.798.478,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,14667,1:00.798.481,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A 22 60 42 B2 B5 44 B1 D9 D2 43 01 E1 02 01 3D… 0,,14671,1:00.799.478,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,14672,1:00.814.483,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D C2 29 56 03 85 A6 C4 50 5A 83 75 E6 27 96 D8… 0,,14676,1:00.815.480,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,14677,1:00.829.485,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14681,1:00.830.482,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,14682,1:00.830.485,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D C2 29 56 03 85 A6 C4 50 5A 83 75 E6 27 96 D8… 0,,14686,1:00.831.482,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,14687,1:00.846.488,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 97 C0 8A 17 B0 97 51 9D 7A FB E1 84 22 98 19 DF… 0,,14691,1:00.847.484,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,14692,1:00.861.490,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14696,1:00.862.486,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,14697,1:00.862.490,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 97 C0 8A 17 B0 97 51 9D 7A FB E1 84 22 98 19 DF… 0,,14701,1:00.863.487,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,14702,1:00.878.492,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 26 00 57 A6 06 25 C8 83 6C D5 C9 4F 0E 31 2B… 0,,14706,1:00.879.489,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,14707,1:00.893.494,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14711,1:00.894.491,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,14712,1:00.894.494,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 26 00 57 A6 06 25 C8 83 6C D5 C9 4F 0E 31 2B… 0,,14716,1:00.895.491,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,14717,1:00.910.496,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 2B D1 35 C3 23 73 5A 9D 0E 91 E0 C6 D1 DE 29… 0,,14721,1:00.911.493,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,14722,1:00.925.498,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14726,1:00.926.495,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,14727,1:00.926.499,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 2B D1 35 C3 23 73 5A 9D 0E 91 E0 C6 D1 DE 29… 0,,14731,1:00.927.496,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,14732,1:00.942.501,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D DF 3E 35 FE A1 A7 A9 12 54 04 9D 80 72 20 53… 0,,14736,1:00.943.498,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,14737,1:00.957.503,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14741,1:00.958.500,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,14742,1:00.958.503,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D DF 3E 35 FE A1 A7 A9 12 54 04 9D 80 72 20 53… 0,,14746,1:00.959.500,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,14747,1:00.974.505,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D8 23 26 D9 4D E3 89 38 D6 6C 8F 1B C3 C3 46 17… 0,,14751,1:00.975.502,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,14752,1:00.989.507,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14756,1:00.990.504,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,14757,1:00.990.508,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D8 23 26 D9 4D E3 89 38 D6 6C 8F 1B C3 C3 46 17… 0,,14761,1:00.991.504,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,14762,1:01.006.510,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 B1 A7 B8 8F C6 A8 5B D4 91 92 76 74 4C 03 CF… 0,,14766,1:01.007.507,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,14767,1:01.021.512,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14771,1:01.022.509,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,14772,1:01.022.512,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 B1 A7 B8 8F C6 A8 5B D4 91 92 76 74 4C 03 CF… 0,,14776,1:01.023.509,15.004.916 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,14777,1:01.038.514,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C CB BD A7 42 6A EC 1A 21 58 4A C6 C8 3E D7 86… 0,,14781,1:01.039.511,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,14782,1:01.053.516,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14786,1:01.054.513,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,14787,1:01.054.516,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C CB BD A7 42 6A EC 1A 21 58 4A C6 C8 3E D7 86… 0,,14791,1:01.055.513,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,14792,1:01.070.519,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 47 6B 3D 7E F8 88 75 34 22 5E A4 AF EF 55 22 FA… 0,,14796,1:01.071.515,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,14797,1:01.085.521,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14801,1:01.086.518,16.005.041 ms,,,,,[17 SOF],[Frames: 1633 - 1649] 0,,14802,1:01.102.523,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 00 69 F6 C1 E2 5D 97 AB 26 85 37 F9 95 9F 61… 0,,14806,1:01.103.520,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,14807,1:01.117.525,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14811,1:01.118.522,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,14812,1:01.118.525,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 00 69 F6 C1 E2 5D 97 AB 26 85 37 F9 95 9F 61… 0,,14816,1:01.119.522,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,14817,1:01.134.527,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 12 3F 7D 76 7B CC 31 5D 0B 16 A3 AD 9D F4 B8 A7… 0,,14821,1:01.135.524,14.004.750 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,14822,1:01.149.530,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14826,1:01.150.526,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,14827,1:01.150.530,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 12 3F 7D 76 7B CC 31 5D 0B 16 A3 AD 9D F4 B8 A7… 0,,14831,1:01.151.527,15.004.916 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,14832,1:01.166.532,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 EB B8 2D CD 5E BC 97 56 59 F8 9A 22 24 9A 3A… 0,,14836,1:01.167.529,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,14837,1:01.181.534,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14841,1:01.182.531,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,14842,1:01.182.534,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 EB B8 2D CD 5E BC 97 56 59 F8 9A 22 24 9A 3A… 0,,14846,1:01.183.531,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,14847,1:01.198.536,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 ED C3 61 39 92 E3 F7 43 24 77 93 85 B1 5F 6E… 0,,14851,1:01.199.533,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,14852,1:01.213.538,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14856,1:01.214.535,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,14857,1:01.214.539,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 ED C3 61 39 92 E3 F7 43 24 77 93 85 B1 5F 6E… 0,,14861,1:01.215.535,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,14862,1:01.230.541,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E AE 84 53 38 06 8B 31 28 3B 03 6B 89 60 33 5F… 0,,14866,1:01.231.538,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,14867,1:01.245.543,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14871,1:01.246.540,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,14872,1:01.246.543,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E AE 84 53 38 06 8B 31 28 3B 03 6B 89 60 33 5F… 0,,14876,1:01.247.540,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,14877,1:01.262.545,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 2B 86 F9 F9 29 42 DF FB E5 DB A0 63 6C 01 1D… 0,,14881,1:01.263.542,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,14882,1:01.277.547,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14886,1:01.278.544,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,14887,1:01.278.548,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 2B 86 F9 F9 29 42 DF FB E5 DB A0 63 6C 01 1D… 0,,14891,1:01.279.544,15.004.916 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,14892,1:01.294.550,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 B8 89 67 00 AA D2 77 3F 0E 8E 37 72 61 4F B8… 0,,14896,1:01.295.547,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,14897,1:01.309.552,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14901,1:01.310.549,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,14902,1:01.310.552,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 B8 89 67 00 AA D2 77 3F 0E 8E 37 72 61 4F B8… 0,,14906,1:01.311.549,15.004.916 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,14907,1:01.326.554,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C7 E7 AB C9 E7 8B CA 69 61 00 AB A2 81 B6 05 5C… 0,,14911,1:01.327.551,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,14912,1:01.341.556,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14916,1:01.342.553,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,14917,1:01.342.556,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C7 E7 AB C9 E7 8B CA 69 61 00 AB A2 81 B6 05 5C… 0,,14921,1:01.343.553,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,14922,1:01.358.559,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 F2 8F 71 00 4B 5E DC D7 EC D3 A4 88 95 26 8B… 0,,14926,1:01.359.555,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,14927,1:01.373.561,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14931,1:01.374.558,2.833 us,,,,,[1 SOF],[Frame: 1921] 0,,14932,1:01.374.561,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 F2 8F 71 00 4B 5E DC D7 EC D3 A4 88 95 26 8B… 0,,14936,1:01.375.558,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,14937,1:01.390.563,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D 51 F7 D2 25 8A 93 B8 20 AF C2 4C 36 4A 35 43… 0,,14941,1:01.391.560,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,14942,1:01.405.565,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14946,1:01.406.562,2.833 us,,,,,[1 SOF],[Frame: 1953] 0,,14947,1:01.406.565,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D 51 F7 D2 25 8A 93 B8 20 AF C2 4C 36 4A 35 43… 0,,14951,1:01.407.562,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,14952,1:01.422.567,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 B4 76 DF 35 FB 35 3F 49 3C 48 42 64 2C 86 2C… 0,,14956,1:01.423.564,14.004.750 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,14957,1:01.437.570,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14961,1:01.438.566,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,14962,1:01.438.570,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 B4 76 DF 35 FB 35 3F 49 3C 48 42 64 2C 86 2C… 0,,14966,1:01.439.567,15.005.000 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,14967,1:01.454.572,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 17 0B 91 D2 11 79 4A 15 08 FC 30 DA A6 5C 6B 6A… 0,,14971,1:01.455.569,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,14972,1:01.469.574,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14976,1:01.470.571,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,14977,1:01.470.574,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 17 0B 91 D2 11 79 4A 15 08 FC 30 DA A6 5C 6B 6A… 0,,14981,1:01.471.571,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,14982,1:01.486.576,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CB 6C 49 70 38 03 C2 A9 AB 61 19 C8 0A 6C 0E C0… 0,,14986,1:01.487.573,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,14987,1:01.501.578,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14991,1:01.502.575,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,14992,1:01.502.579,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CB 6C 49 70 38 03 C2 A9 AB 61 19 C8 0A 6C 0E C0… 0,,14996,1:01.503.575,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,14997,1:01.518.581,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C 7A 36 91 19 50 72 A5 A5 B4 45 70 C6 15 41 5C… 0,,15001,1:01.519.578,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,15002,1:01.533.583,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15006,1:01.534.580,2.833 us,,,,,[1 SOF],[Frame: 33] 0,,15007,1:01.534.583,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C 7A 36 91 19 50 72 A5 A5 B4 45 70 C6 15 41 5C… 0,,15011,1:01.535.580,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,15012,1:01.550.585,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B3 8B 4C 44 7B 44 D3 D4 1A 5D 2F 47 38 74 B1 C8… 0,,15016,1:01.551.582,14.004.750 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,15017,1:01.565.587,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15021,1:01.566.584,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,15022,1:01.566.587,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B3 8B 4C 44 7B 44 D3 D4 1A 5D 2F 47 38 74 B1 C8… 0,,15026,1:01.567.584,15.004.916 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,15027,1:01.582.590,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 4E AA D4 EE B6 4E 77 64 6D 33 B6 AD 90 6B 85… 0,,15031,1:01.583.587,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,15032,1:01.597.592,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15036,1:01.598.589,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,15037,1:01.598.592,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 4E AA D4 EE B6 4E 77 64 6D 33 B6 AD 90 6B 85… 0,,15041,1:01.599.589,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,15042,1:01.614.594,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC 87 51 44 BA 92 ED 5D 20 CB A9 0B 1E E6 AC 56… 0,,15046,1:01.615.591,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,15047,1:01.629.596,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15051,1:01.630.593,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,15052,1:01.630.596,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC 87 51 44 BA 92 ED 5D 20 CB A9 0B 1E E6 AC 56… 0,,15056,1:01.631.593,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,15057,1:01.646.599,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 37 E9 D6 59 C9 4F A5 83 BE F8 F8 9E 4C 8D 20 FB… 0,,15061,1:01.647.595,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,15062,1:01.661.601,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15066,1:01.662.598,2.833 us,,,,,[1 SOF],[Frame: 161] 0,,15067,1:01.662.601,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 37 E9 D6 59 C9 4F A5 83 BE F8 F8 9E 4C 8D 20 FB… 0,,15071,1:01.663.598,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,15072,1:01.678.603,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 0A 23 44 E2 5F 80 44 2F 4E 5A 1A 66 90 A9 D2… 0,,15076,1:01.679.600,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,15077,1:01.693.605,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15081,1:01.694.602,16.005.020 ms,,,,,[17 SOF],[Frames: 193 - 209] 0,,15082,1:01.710.607,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 AA 21 83 3F BF 8F D7 AD 30 FD CA 18 F6 CF 2F… 0,,15086,1:01.711.604,14.004.750 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,15087,1:01.725.610,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15091,1:01.726.606,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,15092,1:01.726.610,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 26 AA 21 83 3F BF 8F D7 AD 30 FD CA 18 F6 CF 2F… 0,,15096,1:01.727.607,15.004.916 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,15097,1:01.742.612,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BB 89 2E 9D 28 EA 47 FD A3 D5 B2 57 F0 13 84 60… 0,,15101,1:01.743.609,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,15102,1:01.757.614,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15106,1:01.758.611,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,15107,1:01.758.614,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BB 89 2E 9D 28 EA 47 FD A3 D5 B2 57 F0 13 84 60… 0,,15111,1:01.759.611,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,15112,1:01.774.616,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C 56 DF DC 1F 04 01 78 81 25 F3 20 45 50 44 0C… 0,,15116,1:01.775.613,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,15117,1:01.789.618,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15121,1:01.790.615,2.833 us,,,,,[1 SOF],[Frame: 289] 0,,15122,1:01.790.619,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C 56 DF DC 1F 04 01 78 81 25 F3 20 45 50 44 0C… 0,,15126,1:01.791.615,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,15127,1:01.806.621,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 6B CD 07 97 CC 80 03 B4 F1 AC D1 2D 00 A4 4E… 0,,15131,1:01.807.618,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,15132,1:01.821.623,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15136,1:01.822.620,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,15137,1:01.822.623,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 6B CD 07 97 CC 80 03 B4 F1 AC D1 2D 00 A4 4E… 0,,15141,1:01.823.620,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,15142,1:01.838.625,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 45 8B BA A6 D5 57 16 89 66 78 D1 43 8A 23 05 4A… 0,,15146,1:01.839.622,14.004.750 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,15147,1:01.853.627,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15151,1:01.854.624,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,15152,1:01.854.627,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 45 8B BA A6 D5 57 16 89 66 78 D1 43 8A 23 05 4A… 0,,15156,1:01.855.624,15.004.916 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,15157,1:01.870.630,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 EE 3E 24 12 7D 8B 6A EF C4 74 6B 1E 00 EF 5B… 0,,15161,1:01.871.627,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,15162,1:01.885.632,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15166,1:01.886.629,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,15167,1:01.886.632,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 EE 3E 24 12 7D 8B 6A EF C4 74 6B 1E 00 EF 5B… 0,,15171,1:01.887.629,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,15172,1:01.902.634,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 40 7C 15 08 CD C6 35 FA A7 17 62 DF BB B3 9A… 0,,15176,1:01.903.631,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,15177,1:01.917.636,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15181,1:01.918.633,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,15182,1:01.918.636,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 40 7C 15 08 CD C6 35 FA A7 17 62 DF BB B3 9A… 0,,15186,1:01.919.633,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,15187,1:01.934.639,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EA D6 FD 6F 15 55 FA 4C FD 39 03 64 9C AD 94 27… 0,,15191,1:01.935.635,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,15192,1:01.949.641,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15196,1:01.950.638,2.833 us,,,,,[1 SOF],[Frame: 449] 0,,15197,1:01.950.641,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EA D6 FD 6F 15 55 FA 4C FD 39 03 64 9C AD 94 27… 0,,15201,1:01.951.638,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,15202,1:01.966.643,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D C2 32 30 B9 31 F9 E3 8A EF CA 24 23 AB 65 89… 0,,15206,1:01.967.640,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,15207,1:01.981.645,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15211,1:01.982.642,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,15212,1:01.982.645,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D C2 32 30 B9 31 F9 E3 8A EF CA 24 23 AB 65 89… 0,,15216,1:01.983.642,15.004.916 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,15217,1:01.998.647,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C7 68 68 B8 96 0A 4B C3 A4 62 CB CA 86 66 52 C9… 0,,15221,1:01.999.644,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,15222,1:02.013.650,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15226,1:02.014.646,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,15227,1:02.014.650,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C7 68 68 B8 96 0A 4B C3 A4 62 CB CA 86 66 52 C9… 0,,15231,1:02.015.647,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,15232,1:02.030.652,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 66 EB 57 27 C3 9B 53 59 8D 85 07 70 FA BB 61 B6… 0,,15236,1:02.031.649,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,15237,1:02.045.654,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15241,1:02.046.651,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,15242,1:02.046.654,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 66 EB 57 27 C3 9B 53 59 8D 85 07 70 FA BB 61 B6… 0,,15246,1:02.047.651,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,15247,1:02.062.656,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 5C 55 18 A4 B5 88 05 F1 D2 27 D8 A4 2E 11 C3… 0,,15251,1:02.063.653,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,15252,1:02.077.658,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15256,1:02.078.655,2.833 us,,,,,[1 SOF],[Frame: 577] 0,,15257,1:02.078.659,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 5C 55 18 A4 B5 88 05 F1 D2 27 D8 A4 2E 11 C3… 0,,15261,1:02.079.655,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,15262,1:02.094.661,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 02 5E 98 30 CD 42 33 13 5A B8 F8 FE 84 F9 81… 0,,15266,1:02.095.658,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,15267,1:02.109.663,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15271,1:02.110.660,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,15272,1:02.110.663,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 02 5E 98 30 CD 42 33 13 5A B8 F8 FE 84 F9 81… 0,,15276,1:02.111.660,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,15277,1:02.126.665,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7F F0 23 4F 3D 03 3C 3E D2 2A 67 B7 26 94 DE E8… 0,,15281,1:02.127.662,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,15282,1:02.141.667,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15286,1:02.142.664,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,15287,1:02.142.667,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7F F0 23 4F 3D 03 3C 3E D2 2A 67 B7 26 94 DE E8… 0,,15291,1:02.143.664,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,15292,1:02.158.670,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F7 5E 34 D9 4B BA 76 12 3C E6 08 B2 A4 66 17 35… 0,,15296,1:02.159.667,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,15297,1:02.173.672,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15301,1:02.174.669,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,15302,1:02.174.672,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F7 5E 34 D9 4B BA 76 12 3C E6 08 B2 A4 66 17 35… 0,,15306,1:02.175.669,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,15307,1:02.190.674,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 7E 31 9C 71 1D 60 A7 F8 56 87 A7 D0 78 3F F9… 0,,15311,1:02.191.671,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,15312,1:02.205.676,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15316,1:02.206.673,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,15317,1:02.206.676,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 7E 31 9C 71 1D 60 A7 F8 56 87 A7 D0 78 3F F9… 0,,15321,1:02.207.673,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,15322,1:02.222.679,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 EB 41 21 13 A1 FE DD 3C 16 16 3E 18 EE F8 CB… 0,,15326,1:02.223.675,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,15327,1:02.237.681,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15331,1:02.238.677,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,15332,1:02.238.681,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 EB 41 21 13 A1 FE DD 3C 16 16 3E 18 EE F8 CB… 0,,15336,1:02.239.678,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,15337,1:02.254.683,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 87 F0 9C 48 E5 60 AB 95 07 79 2C 8C 17 98 0C 79… 0,,15341,1:02.255.680,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,15342,1:02.269.685,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15346,1:02.270.682,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,15347,1:02.270.685,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 87 F0 9C 48 E5 60 AB 95 07 79 2C 8C 17 98 0C 79… 0,,15351,1:02.271.682,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,15352,1:02.286.687,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0E BC 64 B6 A0 F3 DA 64 B1 8B AB 9F D8 EC 44 92… 0,,15356,1:02.287.684,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,15357,1:02.301.689,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15361,1:02.302.686,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,15362,1:02.302.690,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0E BC 64 B6 A0 F3 DA 64 B1 8B AB 9F D8 EC 44 92… 0,,15366,1:02.303.687,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,15367,1:02.318.692,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C4 D9 CD 17 D3 82 72 08 C9 E9 0D 22 8F 06 06 1D… 0,,15371,1:02.319.689,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,15372,1:02.333.694,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15376,1:02.334.691,16.005.041 ms,,,,,[17 SOF],[Frames: 833 - 849] 0,,15377,1:02.350.696,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A FE BD 5A 72 35 1F 8B A8 59 47 70 25 6B B7 10… 0,,15381,1:02.351.693,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,15382,1:02.365.698,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15386,1:02.366.695,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,15387,1:02.366.699,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A FE BD 5A 72 35 1F 8B A8 59 47 70 25 6B B7 10… 0,,15391,1:02.367.695,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,15392,1:02.382.701,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 A9 12 61 72 D7 E1 28 C4 BE 7B 66 10 F3 F5 1D… 0,,15396,1:02.383.698,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,15397,1:02.397.703,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15401,1:02.398.700,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,15402,1:02.398.703,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 A9 12 61 72 D7 E1 28 C4 BE 7B 66 10 F3 F5 1D… 0,,15406,1:02.399.700,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,15407,1:02.414.705,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 AC DA 91 A2 42 EA 19 91 F5 66 77 7A 0F 2B 65… 0,,15411,1:02.415.702,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,15412,1:02.429.707,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15416,1:02.430.704,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,15417,1:02.430.707,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 AC DA 91 A2 42 EA 19 91 F5 66 77 7A 0F 2B 65… 0,,15421,1:02.431.704,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,15422,1:02.446.710,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 C0 EE E5 20 A5 0F 8E 0B 44 55 32 E7 32 D7 57… 0,,15426,1:02.447.707,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,15427,1:02.461.712,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15431,1:02.462.709,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,15432,1:02.462.712,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 C0 EE E5 20 A5 0F 8E 0B 44 55 32 E7 32 D7 57… 0,,15436,1:02.463.709,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,15437,1:02.478.714,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 74 03 73 C4 7D C3 21 03 39 51 D3 E3 4C 64 B0… 0,,15441,1:02.479.711,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,15442,1:02.493.716,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15446,1:02.494.713,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,15447,1:02.494.716,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 74 03 73 C4 7D C3 21 03 39 51 D3 E3 4C 64 B0… 0,,15451,1:02.495.713,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,15452,1:02.510.719,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 4D 4A CD 19 40 D8 68 94 27 E2 E6 11 C7 26 7A… 0,,15456,1:02.511.715,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,15457,1:02.525.721,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15461,1:02.526.717,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,15462,1:02.526.721,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 4D 4A CD 19 40 D8 68 94 27 E2 E6 11 C7 26 7A… 0,,15466,1:02.527.718,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,15467,1:02.542.723,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B 4B CB 9B 06 FE E2 49 12 B4 57 63 A2 FB F8 CA… 0,,15471,1:02.543.720,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,15472,1:02.557.725,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15476,1:02.558.722,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,15477,1:02.558.725,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B 4B CB 9B 06 FE E2 49 12 B4 57 63 A2 FB F8 CA… 0,,15481,1:02.559.722,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,15482,1:02.574.727,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E C6 DC BD 80 6A 7C 39 C5 2C 30 D0 C7 80 69 C4… 0,,15486,1:02.575.724,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,15487,1:02.589.729,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15491,1:02.590.726,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,15492,1:02.590.730,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E C6 DC BD 80 6A 7C 39 C5 2C 30 D0 C7 80 69 C4… 0,,15496,1:02.591.726,15.004.916 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,15497,1:02.606.732,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 30 A8 92 05 AE E3 E2 B1 37 65 40 94 FC E8 DF… 0,,15501,1:02.607.729,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,15502,1:02.621.734,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15506,1:02.622.731,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,15507,1:02.622.734,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 30 A8 92 05 AE E3 E2 B1 37 65 40 94 FC E8 DF… 0,,15511,1:02.623.731,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,15512,1:02.638.736,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B0 96 69 4B 73 CC 00 69 63 B5 94 D3 AE 3D 81 83… 0,,15516,1:02.639.733,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,15517,1:02.653.738,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15521,1:02.654.735,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,15522,1:02.654.739,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B0 96 69 4B 73 CC 00 69 63 B5 94 D3 AE 3D 81 83… 0,,15526,1:02.655.735,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,15527,1:02.670.741,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C4 6E 8F 0E FF 4C 55 5C 0E 51 46 E8 23 E7 1B 0E… 0,,15531,1:02.671.738,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,15532,1:02.685.743,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15536,1:02.686.740,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,15537,1:02.686.743,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C4 6E 8F 0E FF 4C 55 5C 0E 51 46 E8 23 E7 1B 0E… 0,,15541,1:02.687.740,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,15542,1:02.702.745,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 07 D9 53 79 72 51 EB 6A B8 01 CC 2E 47 D9 9D… 0,,15546,1:02.703.742,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,15547,1:02.717.747,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15551,1:02.718.744,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,15552,1:02.718.747,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 07 D9 53 79 72 51 EB 6A B8 01 CC 2E 47 D9 9D… 0,,15556,1:02.719.744,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,15557,1:02.734.750,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 78 14 0A 66 1C 91 2A 0F ED 0E F5 39 DF 18 82 B2… 0,,15561,1:02.735.746,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,15562,1:02.749.752,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15566,1:02.750.749,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,15567,1:02.750.752,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 78 14 0A 66 1C 91 2A 0F ED 0E F5 39 DF 18 82 B2… 0,,15571,1:02.751.749,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,15572,1:02.766.754,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CE A8 58 E1 23 5C 1E 41 3E 46 D0 EE AC B0 14 27… 0,,15576,1:02.767.751,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,15577,1:02.781.756,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15581,1:02.782.753,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,15582,1:02.782.756,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CE A8 58 E1 23 5C 1E 41 3E 46 D0 EE AC B0 14 27… 0,,15586,1:02.783.753,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,15587,1:02.798.758,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 7B 95 D9 83 9F 40 93 63 60 D0 0D AE 31 04 C0… 0,,15591,1:02.799.755,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,15592,1:02.813.761,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15596,1:02.814.757,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,15597,1:02.814.761,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 7B 95 D9 83 9F 40 93 63 60 D0 0D AE 31 04 C0… 0,,15601,1:02.815.758,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,15602,1:02.830.763,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A1 D7 94 02 18 69 EA 9C C7 83 A3 7E 38 C1 4C 36… 0,,15606,1:02.831.760,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,15607,1:02.845.765,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15611,1:02.846.762,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,15612,1:02.846.765,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A1 D7 94 02 18 69 EA 9C C7 83 A3 7E 38 C1 4C 36… 0,,15616,1:02.847.762,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,15617,1:02.862.767,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D0 BB BA 54 F9 64 FF AC 6A 0F A6 CF A2 24 B2 D3… 0,,15621,1:02.863.764,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,15622,1:02.877.769,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15626,1:02.878.766,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,15627,1:02.878.770,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D0 BB BA 54 F9 64 FF AC 6A 0F A6 CF A2 24 B2 D3… 0,,15631,1:02.879.766,15.004.916 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,15632,1:02.894.772,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3D 5F 9E 1B 85 0D 43 F9 E4 C1 08 3D 17 E5 15 1D… 0,,15636,1:02.895.769,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,15637,1:02.909.774,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15641,1:02.910.771,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,15642,1:02.910.774,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3D 5F 9E 1B 85 0D 43 F9 E4 C1 08 3D 17 E5 15 1D… 0,,15646,1:02.911.771,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,15647,1:02.926.776,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A1 19 E0 85 16 84 E2 B4 12 80 39 8F 90 D7 3E 9E… 0,,15651,1:02.927.773,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,15652,1:02.941.778,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15656,1:02.942.775,16.005.041 ms,,,,,[17 SOF],[Frames: 1441 - 1457] 0,,15657,1:02.958.781,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 9A 1D FA 02 6E 49 4A 6A 05 84 E0 A3 67 AF 33… 0,,15661,1:02.959.778,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,15662,1:02.973.783,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15666,1:02.974.780,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,15667,1:02.974.783,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 9A 1D FA 02 6E 49 4A 6A 05 84 E0 A3 67 AF 33… 0,,15671,1:02.975.780,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,15672,1:02.990.785,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A4 CE 09 8A 58 A7 13 17 AA 5D 55 7E 74 F2 7A 29… 0,,15676,1:02.991.782,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,15677,1:03.005.787,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15681,1:03.006.784,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,15682,1:03.006.787,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A4 CE 09 8A 58 A7 13 17 AA 5D 55 7E 74 F2 7A 29… 0,,15686,1:03.007.784,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,15687,1:03.022.790,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 8C 28 96 5F 15 5B C5 7F E1 0E 21 83 5B 9A 70… 0,,15691,1:03.023.786,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,15692,1:03.037.792,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15696,1:03.038.789,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,15697,1:03.038.792,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 8C 28 96 5F 15 5B C5 7F E1 0E 21 83 5B 9A 70… 0,,15701,1:03.039.789,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,15702,1:03.054.794,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 67 62 B2 F8 6D 8A E5 B3 34 7D 0D DA 8A D8 0A… 0,,15706,1:03.055.791,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,15707,1:03.069.796,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15711,1:03.070.793,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,15712,1:03.070.796,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 67 62 B2 F8 6D 8A E5 B3 34 7D 0D DA 8A D8 0A… 0,,15716,1:03.071.793,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,15717,1:03.086.798,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 EF 58 F8 2C 03 56 2D AF CF 4B 02 06 38 29 49… 0,,15721,1:03.087.795,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,15722,1:03.101.801,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15726,1:03.102.797,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,15727,1:03.102.801,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 EF 58 F8 2C 03 56 2D AF CF 4B 02 06 38 29 49… 0,,15731,1:03.103.798,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,15732,1:03.118.803,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 43 AA DD CC F6 DD 98 60 07 8A 02 E1 DE 4F E7… 0,,15736,1:03.119.800,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,15737,1:03.133.805,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15741,1:03.134.802,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,15742,1:03.134.805,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 43 AA DD CC F6 DD 98 60 07 8A 02 E1 DE 4F E7… 0,,15746,1:03.135.802,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,15747,1:03.150.807,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 32 0D 46 52 22 B6 1B 28 2A BE 93 EB DC 14 15… 0,,15751,1:03.151.804,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,15752,1:03.165.809,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15756,1:03.166.806,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,15757,1:03.166.810,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 32 0D 46 52 22 B6 1B 28 2A BE 93 EB DC 14 15… 0,,15761,1:03.167.806,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,15762,1:03.182.812,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 3E 3D 93 18 65 92 3C AE 1D 34 91 0C 8C 9F 36… 0,,15766,1:03.183.809,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,15767,1:03.197.814,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15771,1:03.198.811,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,15772,1:03.198.814,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 3E 3D 93 18 65 92 3C AE 1D 34 91 0C 8C 9F 36… 0,,15776,1:03.199.811,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,15777,1:03.214.816,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B6 A4 F4 CA E6 71 1E B4 3E 02 55 58 3D 2C E4 03… 0,,15781,1:03.215.813,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,15782,1:03.229.818,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15786,1:03.230.815,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,15787,1:03.230.818,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B6 A4 F4 CA E6 71 1E B4 3E 02 55 58 3D 2C E4 03… 0,,15791,1:03.231.815,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,15792,1:03.246.821,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E D2 F4 CB 98 28 F0 F5 FE 17 9A EB C3 14 F7 6F… 0,,15796,1:03.247.818,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,15797,1:03.261.823,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15801,1:03.262.820,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,15802,1:03.262.823,50.729 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E D2 F4 CB 98 28 F0 F5 FE 17 9A EB C3 14 F7 6F… 0,,15806,1:03.263.820,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,15807,1:03.278.825,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 28 C5 A5 B3 FD 53 C7 07 D2 A3 90 C9 B8 BA B4 AE… 0,,15811,1:03.279.822,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,15812,1:03.293.827,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15816,1:03.294.824,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,15817,1:03.294.827,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 28 C5 A5 B3 FD 53 C7 07 D2 A3 90 C9 B8 BA B4 AE… 0,,15821,1:03.295.824,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,15822,1:03.310.830,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D8 95 37 7A 21 A7 F3 95 56 FD 07 97 46 5E 43 0E… 0,,15826,1:03.311.826,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,15827,1:03.325.832,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15831,1:03.326.829,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,15832,1:03.326.832,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D8 95 37 7A 21 A7 F3 95 56 FD 07 97 46 5E 43 0E… 0,,15836,1:03.327.829,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,15837,1:03.342.834,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 BB 1A C4 51 D4 AD CC 45 9B 7E B2 96 95 B6 D1… 0,,15841,1:03.343.831,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,15842,1:03.357.836,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15846,1:03.358.833,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,15847,1:03.358.836,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 BB 1A C4 51 D4 AD CC 45 9B 7E B2 96 95 B6 D1… 0,,15851,1:03.359.833,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,15852,1:03.374.838,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 B5 14 25 71 89 2D B1 BF 0F 2F 18 44 50 1D 7E… 0,,15856,1:03.375.835,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,15857,1:03.389.841,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15861,1:03.390.837,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,15862,1:03.390.841,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 B5 14 25 71 89 2D B1 BF 0F 2F 18 44 50 1D 7E… 0,,15866,1:03.391.838,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,15867,1:03.406.843,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 81 BE 30 80 EB 56 D1 91 75 59 77 33 D3 B3 05… 0,,15871,1:03.407.840,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,15872,1:03.421.845,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15876,1:03.422.842,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,15877,1:03.422.845,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 81 BE 30 80 EB 56 D1 91 75 59 77 33 D3 B3 05… 0,,15881,1:03.423.842,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,15882,1:03.438.847,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2F 64 DD E4 37 00 F2 A3 8B CB 42 51 50 86 F6 F0… 0,,15886,1:03.439.844,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,15887,1:03.453.849,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15891,1:03.454.846,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,15892,1:03.454.850,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2F 64 DD E4 37 00 F2 A3 8B CB 42 51 50 86 F6 F0… 0,,15896,1:03.455.846,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,15897,1:03.470.852,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 03 04 8D 4C F5 7F 85 F8 72 27 93 51 76 8A F4 5C… 0,,15901,1:03.471.849,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,15902,1:03.485.854,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15906,1:03.486.851,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,15907,1:03.486.854,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 03 04 8D 4C F5 7F 85 F8 72 27 93 51 76 8A F4 5C… 0,,15911,1:03.487.851,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,15912,1:03.502.856,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 03 27 DE 89 32 91 FC 59 2F 73 52 72 3B 89 77 3E… 0,,15916,1:03.503.853,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,15917,1:03.517.858,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15921,1:03.518.855,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,15922,1:03.518.859,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 03 27 DE 89 32 91 FC 59 2F 73 52 72 3B 89 77 3E… 0,,15926,1:03.519.855,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,15927,1:03.534.861,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 29 07 11 66 78 97 08 3B 8A 7C B4 C6 51 2A D7 28… 0,,15931,1:03.535.858,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,15932,1:03.549.863,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15936,1:03.550.860,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,15937,1:03.550.863,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 29 07 11 66 78 97 08 3B 8A 7C B4 C6 51 2A D7 28… 0,,15941,1:03.551.860,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,15942,1:03.566.865,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C CA A0 F2 21 8B B4 C0 4D 70 4F 6C 18 93 7D 46… 0,,15946,1:03.567.862,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,15947,1:03.581.867,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15951,1:03.582.864,16.005.041 ms,,,,,[17 SOF],[Frames: 33 - 49] 0,,15952,1:03.598.870,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 A8 BB 58 27 50 E8 1C A2 D1 3F 86 54 98 51 1A… 0,,15956,1:03.599.866,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,15957,1:03.613.872,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15961,1:03.614.869,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,15962,1:03.614.872,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 A8 BB 58 27 50 E8 1C A2 D1 3F 86 54 98 51 1A… 0,,15966,1:03.615.869,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,15967,1:03.630.874,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 02 9D 34 D6 7A FD 42 6E AF 0F 76 93 64 FD 30… 0,,15971,1:03.631.871,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,15972,1:03.645.876,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15976,1:03.646.873,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,15977,1:03.646.876,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 02 9D 34 D6 7A FD 42 6E AF 0F 76 93 64 FD 30… 0,,15981,1:03.647.873,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,15982,1:03.662.878,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D CA 16 BD D8 19 A6 C6 39 80 84 C6 44 41 9B 69… 0,,15986,1:03.663.875,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,15987,1:03.677.881,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15991,1:03.678.877,2.833 us,,,,,[1 SOF],[Frame: 129] 0,,15992,1:03.678.881,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D CA 16 BD D8 19 A6 C6 39 80 84 C6 44 41 9B 69… 0,,15996,1:03.679.878,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,15997,1:03.694.883,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B0 DF FF C6 42 B9 CF 7B 00 A9 B7 B7 3E B3 22 49… 0,,16001,1:03.695.880,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,16002,1:03.709.885,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16006,1:03.710.882,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,16007,1:03.710.885,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B0 DF FF C6 42 B9 CF 7B 00 A9 B7 B7 3E B3 22 49… 0,,16011,1:03.711.882,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,16012,1:03.726.887,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 67 2B 25 A2 FB 68 DA 16 E4 51 4A 45 76 CF 56… 0,,16016,1:03.727.884,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,16017,1:03.741.889,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16021,1:03.742.886,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,16022,1:03.742.890,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 67 2B 25 A2 FB 68 DA 16 E4 51 4A 45 76 CF 56… 0,,16026,1:03.743.886,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,16027,1:03.758.892,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 43 51 E7 62 43 A9 76 DA 40 B6 17 8D 3E C3 D6 90… 0,,16031,1:03.759.889,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,16032,1:03.773.894,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16036,1:03.774.891,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,16037,1:03.774.894,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 43 51 E7 62 43 A9 76 DA 40 B6 17 8D 3E C3 D6 90… 0,,16041,1:03.775.891,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,16042,1:03.790.896,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 E2 CD C7 09 5D C4 13 E4 63 D7 B7 D5 3A 69 E2… 0,,16046,1:03.791.893,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,16047,1:03.805.898,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16051,1:03.806.895,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,16052,1:03.806.898,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 E2 CD C7 09 5D C4 13 E4 63 D7 B7 D5 3A 69 E2… 0,,16056,1:03.807.895,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,16057,1:03.822.901,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 36 56 7F DD 07 EF 1A 17 BD B0 56 D5 93 A3 33… 0,,16061,1:03.823.898,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,16062,1:03.837.903,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16066,1:03.838.900,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,16067,1:03.838.903,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 36 56 7F DD 07 EF 1A 17 BD B0 56 D5 93 A3 33… 0,,16071,1:03.839.900,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,16072,1:03.854.905,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 6D FF 35 11 FC DA EF A3 20 F2 47 95 0E DD C6… 0,,16076,1:03.855.902,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,16077,1:03.869.907,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16081,1:03.870.904,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,16082,1:03.870.907,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 6D FF 35 11 FC DA EF A3 20 F2 47 95 0E DD C6… 0,,16086,1:03.871.904,15.004.916 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,16087,1:03.886.910,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 68 42 B0 9E 4E 78 96 1A A9 1E F2 CB DB 35 9F… 0,,16091,1:03.887.906,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,16092,1:03.901.912,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16096,1:03.902.908,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,16097,1:03.902.912,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 68 42 B0 9E 4E 78 96 1A A9 1E F2 CB DB 35 9F… 0,,16101,1:03.903.909,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,16102,1:03.918.914,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D0 19 26 82 87 70 8F FE 95 37 4D 00 AE 17 11 DF… 0,,16106,1:03.919.911,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,16107,1:03.933.916,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16111,1:03.934.913,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,16112,1:03.934.916,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D0 19 26 82 87 70 8F FE 95 37 4D 00 AE 17 11 DF… 0,,16116,1:03.935.913,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,16117,1:03.950.918,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A1 53 5D B6 4A E2 67 5F 84 21 01 29 67 5F 1C A2… 0,,16121,1:03.951.915,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,16122,1:03.965.920,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16126,1:03.966.917,2.833 us,,,,,[1 SOF],[Frame: 417] 0,,16127,1:03.966.921,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A1 53 5D B6 4A E2 67 5F 84 21 01 29 67 5F 1C A2… 0,,16131,1:03.967.918,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,16132,1:03.982.923,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B6 C1 46 F9 09 A8 F5 D5 66 9D D5 9F 24 98 84 48… 0,,16136,1:03.983.920,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,16137,1:03.997.925,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16141,1:03.998.922,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,16142,1:03.998.925,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B6 C1 46 F9 09 A8 F5 D5 66 9D D5 9F 24 98 84 48… 0,,16146,1:03.999.922,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,16147,1:04.014.927,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E7 91 98 AD E2 C7 B3 4B 09 D9 80 39 F8 E9 D3 B6… 0,,16151,1:04.015.924,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,16152,1:04.029.929,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16156,1:04.030.926,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,16157,1:04.030.930,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E7 91 98 AD E2 C7 B3 4B 09 D9 80 39 F8 E9 D3 B6… 0,,16161,1:04.031.926,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,16162,1:04.046.932,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 78 DC 02 19 2B FB 86 AE D0 BC 47 9D DD A1 D0 22… 0,,16166,1:04.047.929,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,16167,1:04.061.934,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16171,1:04.062.931,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,16172,1:04.062.934,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 78 DC 02 19 2B FB 86 AE D0 BC 47 9D DD A1 D0 22… 0,,16176,1:04.063.931,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,16177,1:04.078.936,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5B C5 D2 08 35 9D 82 66 F0 EB CE 9D 2E 00 7B EF… 0,,16181,1:04.079.933,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,16182,1:04.093.938,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16186,1:04.094.935,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,16187,1:04.094.938,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5B C5 D2 08 35 9D 82 66 F0 EB CE 9D 2E 00 7B EF… 0,,16191,1:04.095.935,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,16192,1:04.110.941,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 9A F0 24 13 DF CC 45 FF 9D 3B D1 06 D6 8A EE… 0,,16196,1:04.111.938,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,16197,1:04.125.943,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16201,1:04.126.940,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,16202,1:04.126.943,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 9A F0 24 13 DF CC 45 FF 9D 3B D1 06 D6 8A EE… 0,,16206,1:04.127.940,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,16207,1:04.142.945,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 2E 67 21 35 E1 3F 58 1F 0D 26 0D 27 1C FD 63… 0,,16211,1:04.143.942,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,16212,1:04.157.947,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16216,1:04.158.944,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,16217,1:04.158.947,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 2E 67 21 35 E1 3F 58 1F 0D 26 0D 27 1C FD 63… 0,,16221,1:04.159.944,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,16222,1:04.174.949,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 35 E5 7A 44 C7 F5 C8 6C A9 4B 0D 57 1D 04 B8… 0,,16226,1:04.175.946,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,16227,1:04.189.952,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16231,1:04.190.948,16.005.041 ms,,,,,[17 SOF],[Frames: 641 - 657] 0,,16232,1:04.206.954,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6B D3 53 DD 36 C7 29 20 19 6E 73 EF 42 5E B0 83… 0,,16236,1:04.207.951,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,16237,1:04.221.956,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16241,1:04.222.953,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,16242,1:04.222.956,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6B D3 53 DD 36 C7 29 20 19 6E 73 EF 42 5E B0 83… 0,,16246,1:04.223.953,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,16247,1:04.238.958,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 40 A0 72 11 6C 73 82 D3 71 05 A3 4C 95 4A C2… 0,,16251,1:04.239.955,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,16252,1:04.253.960,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16256,1:04.254.957,2.833 us,,,,,[1 SOF],[Frame: 705] 0,,16257,1:04.254.961,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 40 A0 72 11 6C 73 82 D3 71 05 A3 4C 95 4A C2… 0,,16261,1:04.255.957,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,16262,1:04.270.963,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 65 AB 3E B4 33 02 76 C1 73 9B 5E E1 1C 65 EA 5B… 0,,16266,1:04.271.960,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,16267,1:04.285.965,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16271,1:04.286.962,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,16272,1:04.286.965,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 65 AB 3E B4 33 02 76 C1 73 9B 5E E1 1C 65 EA 5B… 0,,16276,1:04.287.962,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,16277,1:04.302.967,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 12 1A 6B 35 2D 54 7E 80 FA 3D F0 4F E2 52 4A 15… 0,,16281,1:04.303.964,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,16282,1:04.317.969,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16286,1:04.318.966,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,16287,1:04.318.969,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 12 1A 6B 35 2D 54 7E 80 FA 3D F0 4F E2 52 4A 15… 0,,16291,1:04.319.966,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,16292,1:04.334.972,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 2F 2A A9 6C 53 56 59 99 31 67 2D E8 10 4D 56… 0,,16296,1:04.335.969,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,16297,1:04.349.974,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16301,1:04.350.971,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,16302,1:04.350.974,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 2F 2A A9 6C 53 56 59 99 31 67 2D E8 10 4D 56… 0,,16306,1:04.351.971,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,16307,1:04.366.976,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 07 C9 02 C9 F3 BF 6D 76 26 B8 B9 76 97 34 11 47… 0,,16311,1:04.367.973,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,16312,1:04.381.978,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16316,1:04.382.975,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,16317,1:04.382.978,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 07 C9 02 C9 F3 BF 6D 76 26 B8 B9 76 97 34 11 47… 0,,16321,1:04.383.975,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,16322,1:04.398.981,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 63 B5 C3 84 7A 91 C3 E7 DC 28 EA 24 2D 5C CD… 0,,16326,1:04.399.977,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,16327,1:04.413.983,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16331,1:04.414.980,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,16332,1:04.414.983,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 63 B5 C3 84 7A 91 C3 E7 DC 28 EA 24 2D 5C CD… 0,,16336,1:04.415.980,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,16337,1:04.430.985,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E8 28 D9 F5 6E BD DD 44 A9 AC 72 CB 22 C5 28 D9… 0,,16341,1:04.431.982,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,16342,1:04.445.987,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16346,1:04.446.984,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,16347,1:04.446.987,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E8 28 D9 F5 6E BD DD 44 A9 AC 72 CB 22 C5 28 D9… 0,,16351,1:04.447.984,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,16352,1:04.462.989,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 F6 36 CC 16 61 DB A0 3E 04 46 1D 89 67 2A C8… 0,,16356,1:04.463.986,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,16357,1:04.477.992,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16361,1:04.478.988,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,16362,1:04.478.992,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 F6 36 CC 16 61 DB A0 3E 04 46 1D 89 67 2A C8… 0,,16366,1:04.479.989,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,16367,1:04.494.994,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 BB 2B 60 9D 1E A1 9E 89 8D 47 12 62 E3 DC B7… 0,,16371,1:04.495.991,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,16372,1:04.509.996,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16376,1:04.510.993,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,16377,1:04.510.996,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 BB 2B 60 9D 1E A1 9E 89 8D 47 12 62 E3 DC B7… 0,,16381,1:04.511.993,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,16382,1:04.526.998,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 33 4F C2 7D 86 98 10 76 35 7F 94 76 97 C6 7B… 0,,16386,1:04.527.995,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,16387,1:04.542.000,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16391,1:04.542.997,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,16392,1:04.543.001,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9A 33 4F C2 7D 86 98 10 76 35 7F 94 76 97 C6 7B… 0,,16396,1:04.543.997,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,16397,1:04.559.003,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 05 E1 17 18 00 FD C0 C0 F2 0B 7C DA 3F 39 F5… 0,,16401,1:04.560.000,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,16402,1:04.574.005,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16406,1:04.575.002,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,16407,1:04.575.005,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 05 E1 17 18 00 FD C0 C0 F2 0B 7C DA 3F 39 F5… 0,,16411,1:04.576.002,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,16412,1:04.591.007,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3A A8 19 0F 41 52 78 F1 66 A6 EB 0E 9C A4 E1 EB… 0,,16416,1:04.592.004,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,16417,1:04.606.009,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16421,1:04.607.006,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,16422,1:04.607.009,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3A A8 19 0F 41 52 78 F1 66 A6 EB 0E 9C A4 E1 EB… 0,,16426,1:04.608.006,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,16427,1:04.623.012,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D BC 57 07 DA 03 0E 75 77 34 C0 54 F4 84 B7 2A… 0,,16431,1:04.624.009,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,16432,1:04.638.014,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16436,1:04.639.011,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,16437,1:04.639.014,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D BC 57 07 DA 03 0E 75 77 34 C0 54 F4 84 B7 2A… 0,,16441,1:04.640.011,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,16442,1:04.655.016,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 B0 08 B4 2B 1E 96 0A C8 96 4F 3D C1 02 9E 1C… 0,,16446,1:04.656.013,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,16447,1:04.670.018,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16451,1:04.671.015,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,16452,1:04.671.018,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 B0 08 B4 2B 1E 96 0A C8 96 4F 3D C1 02 9E 1C… 0,,16456,1:04.672.015,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,16457,1:04.687.021,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 7A 6D 21 03 BD 40 40 42 D8 8A DF 33 72 05 96… 0,,16461,1:04.688.017,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,16462,1:04.702.023,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16466,1:04.703.020,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,16467,1:04.703.023,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 25 7A 6D 21 03 BD 40 40 42 D8 8A DF 33 72 05 96… 0,,16471,1:04.704.020,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,16472,1:04.719.025,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 CE AA B6 3D 29 1F 46 87 66 FC AA 65 EA 4F E1… 0,,16476,1:04.720.022,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,16477,1:04.734.027,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16481,1:04.735.024,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,16482,1:04.735.027,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 CE AA B6 3D 29 1F 46 87 66 FC AA 65 EA 4F E1… 0,,16486,1:04.736.024,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,16487,1:04.751.029,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 02 BA 7C C6 3C 94 98 7F B2 01 C4 BA 66 AE F0 8E… 0,,16491,1:04.752.026,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,16492,1:04.766.032,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16496,1:04.767.028,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,16497,1:04.767.032,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 02 BA 7C C6 3C 94 98 7F B2 01 C4 BA 66 AE F0 8E… 0,,16501,1:04.768.029,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,16502,1:04.783.034,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C DD C6 96 D1 CA 68 AC 02 8B 0D BB 88 64 2E E9… 0,,16506,1:04.784.031,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,16507,1:04.798.036,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16511,1:04.799.033,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,16512,1:04.799.036,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C DD C6 96 D1 CA 68 AC 02 8B 0D BB 88 64 2E E9… 0,,16516,1:04.800.033,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,16517,1:04.815.038,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 72 36 1D 5C 09 1D 8F 9B 25 99 A0 78 DD BB DB 30… 0,,16521,1:04.816.035,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,16522,1:04.830.040,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16526,1:04.831.037,16.005.041 ms,,,,,[17 SOF],[Frames: 1281 - 1297] 0,,16527,1:04.847.043,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C5 6F AC 62 10 7D DB 19 4F ED 81 8D 53 7A 56 62… 0,,16531,1:04.848.040,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,16532,1:04.862.045,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16536,1:04.863.042,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,16537,1:04.863.045,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C5 6F AC 62 10 7D DB 19 4F ED 81 8D 53 7A 56 62… 0,,16541,1:04.864.042,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,16542,1:04.879.047,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C7 6E 59 BF 13 D8 D0 8C 61 21 E0 D1 EB E3 47 84… 0,,16546,1:04.880.044,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,16547,1:04.894.049,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16551,1:04.895.046,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,16552,1:04.895.049,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C7 6E 59 BF 13 D8 D0 8C 61 21 E0 D1 EB E3 47 84… 0,,16556,1:04.896.046,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,16557,1:04.911.052,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 C3 54 80 E1 3C DD E7 46 9F C8 96 76 18 C7 71… 0,,16561,1:04.912.049,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,16562,1:04.926.054,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16566,1:04.927.051,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,16567,1:04.927.054,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 C3 54 80 E1 3C DD E7 46 9F C8 96 76 18 C7 71… 0,,16571,1:04.928.051,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,16572,1:04.943.056,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 F8 AF BC 93 D2 A5 F0 94 46 CC E8 94 49 E1 35… 0,,16576,1:04.944.053,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,16577,1:04.958.058,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16581,1:04.959.055,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,16582,1:04.959.058,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 F8 AF BC 93 D2 A5 F0 94 46 CC E8 94 49 E1 35… 0,,16586,1:04.960.055,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,16587,1:04.975.061,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 FF CA D0 DD 10 DD 07 F1 5E 5A 60 E6 83 97 4F… 0,,16591,1:04.976.057,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,16592,1:04.990.063,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16596,1:04.991.060,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,16597,1:04.991.063,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 FF CA D0 DD 10 DD 07 F1 5E 5A 60 E6 83 97 4F… 0,,16601,1:04.992.060,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,16602,1:05.007.065,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C 09 CB A1 D1 9D BE F5 70 00 68 D4 BD DC 62 0B… 0,,16606,1:05.008.062,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,16607,1:05.022.067,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16611,1:05.023.064,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,16612,1:05.023.067,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C 09 CB A1 D1 9D BE F5 70 00 68 D4 BD DC 62 0B… 0,,16616,1:05.024.064,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,16617,1:05.039.069,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 F7 D8 44 05 D2 E0 FA ED 57 3E E2 04 4C 9C 83… 0,,16621,1:05.040.066,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,16622,1:05.054.072,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16626,1:05.055.068,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,16627,1:05.055.072,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 F7 D8 44 05 D2 E0 FA ED 57 3E E2 04 4C 9C 83… 0,,16631,1:05.056.069,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,16632,1:05.071.074,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 43 B7 E4 91 64 E3 7D 97 55 52 28 BD 18 8D F6… 0,,16636,1:05.072.071,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,16637,1:05.086.076,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16641,1:05.087.073,2.833 us,,,,,[1 SOF],[Frame: 1537] 0,,16642,1:05.087.076,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 43 B7 E4 91 64 E3 7D 97 55 52 28 BD 18 8D F6… 0,,16646,1:05.088.073,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,16647,1:05.103.078,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF FE 78 73 92 02 A1 33 AF ED 9C 7C 46 7E F5 DC… 0,,16651,1:05.104.075,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,16652,1:05.118.080,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16656,1:05.119.077,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,16657,1:05.119.081,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF FE 78 73 92 02 A1 33 AF ED 9C 7C 46 7E F5 DC… 0,,16661,1:05.120.077,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,16662,1:05.135.083,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 64 D5 30 FD C1 4B 46 2D B0 A8 93 C4 F8 EE B7 9F… 0,,16666,1:05.136.080,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,16667,1:05.150.085,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16671,1:05.151.082,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,16672,1:05.151.085,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 64 D5 30 FD C1 4B 46 2D B0 A8 93 C4 F8 EE B7 9F… 0,,16676,1:05.152.082,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,16677,1:05.167.087,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 E7 47 D8 24 F7 B7 52 4C 2A 58 EE F1 31 67 26… 0,,16681,1:05.168.084,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,16682,1:05.182.089,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16686,1:05.183.086,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,16687,1:05.183.089,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 E7 47 D8 24 F7 B7 52 4C 2A 58 EE F1 31 67 26… 0,,16691,1:05.184.086,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,16692,1:05.199.092,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 F9 A1 C2 93 3D 0D 74 CC A9 E3 63 17 A6 44 DE… 0,,16696,1:05.200.089,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,16697,1:05.214.094,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16701,1:05.215.091,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,16702,1:05.215.094,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 F9 A1 C2 93 3D 0D 74 CC A9 E3 63 17 A6 44 DE… 0,,16706,1:05.216.091,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,16707,1:05.231.096,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 43 62 4C F0 DD B4 9A 93 17 5D E7 CF C0 D1 C2… 0,,16711,1:05.232.093,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,16712,1:05.246.098,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16716,1:05.247.095,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,16717,1:05.247.098,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 43 62 4C F0 DD B4 9A 93 17 5D E7 CF C0 D1 C2… 0,,16721,1:05.248.095,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,16722,1:05.263.101,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 D6 8A 38 3B 70 C5 06 9F 4E F5 5C 23 0B 88 2A… 0,,16726,1:05.264.097,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,16727,1:05.278.103,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16731,1:05.279.100,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,16732,1:05.279.103,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 D6 8A 38 3B 70 C5 06 9F 4E F5 5C 23 0B 88 2A… 0,,16736,1:05.280.100,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,16737,1:05.295.105,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0E 8E 51 74 85 A3 D4 23 9B 79 65 75 E7 1C 62 B8… 0,,16741,1:05.296.102,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,16742,1:05.310.107,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16746,1:05.311.104,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,16747,1:05.311.107,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0E 8E 51 74 85 A3 D4 23 9B 79 65 75 E7 1C 62 B8… 0,,16751,1:05.312.104,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,16752,1:05.327.109,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 54 DE 2E 46 B4 52 CD F0 D3 5D 8C 49 50 65 5B 48… 0,,16756,1:05.328.106,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,16757,1:05.342.111,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16761,1:05.343.108,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,16762,1:05.343.112,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 54 DE 2E 46 B4 52 CD F0 D3 5D 8C 49 50 65 5B 48… 0,,16766,1:05.344.109,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,16767,1:05.359.114,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D F7 45 A4 E2 87 A9 62 D9 7D E0 E5 FA 8F FE 09… 0,,16771,1:05.360.111,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,16772,1:05.374.116,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16776,1:05.375.113,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,16777,1:05.375.116,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D F7 45 A4 E2 87 A9 62 D9 7D E0 E5 FA 8F FE 09… 0,,16781,1:05.376.113,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,16782,1:05.391.118,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 E8 4A D4 D8 49 15 8A 46 0A 83 B3 07 9A 08 91… 0,,16786,1:05.392.115,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,16787,1:05.406.120,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16791,1:05.407.117,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,16792,1:05.407.121,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 E8 4A D4 D8 49 15 8A 46 0A 83 B3 07 9A 08 91… 0,,16796,1:05.408.117,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,16797,1:05.423.123,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 B7 C5 E8 41 51 AC 7C 6C F0 B7 5B 08 3E 4F B0… 0,,16801,1:05.424.120,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,16802,1:05.438.125,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16806,1:05.439.122,16.005.041 ms,,,,,[17 SOF],[Frames: 1889 - 1905] 0,,16807,1:05.455.127,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 53 16 70 52 72 DE BA 90 BA 26 5A 8E BC A3 4B… 0,,16811,1:05.456.124,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,16812,1:05.470.129,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16816,1:05.471.126,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,16817,1:05.471.129,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 53 16 70 52 72 DE BA 90 BA 26 5A 8E BC A3 4B… 0,,16821,1:05.472.126,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,16822,1:05.487.132,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EA BD A1 09 F4 93 1B 18 92 E2 AA B3 FB 59 13 58… 0,,16826,1:05.488.129,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,16827,1:05.502.134,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16831,1:05.503.131,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,16832,1:05.503.134,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EA BD A1 09 F4 93 1B 18 92 E2 AA B3 FB 59 13 58… 0,,16836,1:05.504.131,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,16837,1:05.519.136,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF F4 A6 18 88 5E 35 53 BD 0F EC 8E 97 F0 B0 81… 0,,16841,1:05.520.133,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,16842,1:05.534.138,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16846,1:05.535.135,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,16847,1:05.535.138,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BF F4 A6 18 88 5E 35 53 BD 0F EC 8E 97 F0 B0 81… 0,,16851,1:05.536.135,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,16852,1:05.551.141,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 DE E0 EB 28 99 6E 7F 82 60 E3 3D BA 21 E2 27… 0,,16856,1:05.552.137,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,16857,1:05.566.143,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16861,1:05.567.139,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,16862,1:05.567.143,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 DE E0 EB 28 99 6E 7F 82 60 E3 3D BA 21 E2 27… 0,,16866,1:05.568.140,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,16867,1:05.583.145,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 4E 32 0D FB B1 87 D4 E4 F2 18 8E B7 17 DD AE… 0,,16871,1:05.584.142,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,16872,1:05.598.147,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16876,1:05.599.144,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,16877,1:05.599.147,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 4E 32 0D FB B1 87 D4 E4 F2 18 8E B7 17 DD AE… 0,,16881,1:05.600.144,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,16882,1:05.615.149,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C C7 76 8B B0 4A B1 C7 6A CC 88 93 AE 16 3D 67… 0,,16886,1:05.616.146,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,16887,1:05.630.151,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16891,1:05.631.148,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,16892,1:05.631.152,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C C7 76 8B B0 4A B1 C7 6A CC 88 93 AE 16 3D 67… 0,,16896,1:05.632.149,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,16897,1:05.647.154,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 FC E7 D0 33 01 36 18 BD 52 C9 2B 1A 1D F1 7B… 0,,16901,1:05.648.151,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,16902,1:05.662.156,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16906,1:05.663.153,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,16907,1:05.663.156,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 FC E7 D0 33 01 36 18 BD 52 C9 2B 1A 1D F1 7B… 0,,16911,1:05.664.153,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,16912,1:05.679.158,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 F1 A9 EA 73 92 80 5C 9A 57 16 FB 78 87 F3 4A… 0,,16916,1:05.680.155,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,16917,1:05.694.160,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16921,1:05.695.157,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,16922,1:05.695.160,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 F1 A9 EA 73 92 80 5C 9A 57 16 FB 78 87 F3 4A… 0,,16926,1:05.696.157,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,16927,1:05.711.163,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 50 F0 8B 82 AA 4A AF C4 3A 9C 3C A0 44 25 21… 0,,16931,1:05.712.160,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,16932,1:05.726.165,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16936,1:05.727.162,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,16937,1:05.727.165,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A 50 F0 8B 82 AA 4A AF C4 3A 9C 3C A0 44 25 21… 0,,16941,1:05.728.162,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,16942,1:05.743.167,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FE DA 18 41 D9 64 DC 35 05 06 B6 DF C7 8A A2 61… 0,,16946,1:05.744.164,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,16947,1:05.758.169,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16951,1:05.759.166,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,16952,1:05.759.169,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FE DA 18 41 D9 64 DC 35 05 06 B6 DF C7 8A A2 61… 0,,16956,1:05.760.166,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,16957,1:05.775.172,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C3 AB D2 9F 6E A0 88 E4 CA 05 86 54 AE 89 32 43… 0,,16961,1:05.776.168,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,16962,1:05.790.174,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16966,1:05.791.171,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,16967,1:05.791.174,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C3 AB D2 9F 6E A0 88 E4 CA 05 86 54 AE 89 32 43… 0,,16971,1:05.792.171,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,16972,1:05.807.176,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 97 6B 0D EC 05 54 26 67 95 DD 53 5D 3C 31 F1… 0,,16976,1:05.808.173,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,16977,1:05.822.178,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16981,1:05.823.175,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,16982,1:05.823.178,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 97 6B 0D EC 05 54 26 67 95 DD 53 5D 3C 31 F1… 0,,16986,1:05.824.175,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,16987,1:05.839.180,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 04 F3 DE DD 31 E6 97 11 89 92 25 82 DA 1E 1B… 0,,16991,1:05.840.177,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,16992,1:05.854.183,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16996,1:05.855.179,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,16997,1:05.855.183,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 04 F3 DE DD 31 E6 97 11 89 92 25 82 DA 1E 1B… 0,,17001,1:05.856.180,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,17002,1:05.871.185,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FD 03 90 EE 8B 24 2C F3 EA 53 CF 9F 2A A4 B5 B6… 0,,17006,1:05.872.182,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,17007,1:05.886.187,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17011,1:05.887.184,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,17012,1:05.887.187,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FD 03 90 EE 8B 24 2C F3 EA 53 CF 9F 2A A4 B5 B6… 0,,17016,1:05.888.184,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,17017,1:05.903.189,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C A8 60 EB 4C D1 74 0A DD 6A 16 B1 8F CA BF 5D… 0,,17021,1:05.904.186,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,17022,1:05.918.191,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17026,1:05.919.188,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,17027,1:05.919.192,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C A8 60 EB 4C D1 74 0A DD 6A 16 B1 8F CA BF 5D… 0,,17031,1:05.920.188,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,17032,1:05.935.194,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 76 9F 28 0E B4 FE 34 96 42 2F 20 E4 2E 3E C3… 0,,17036,1:05.936.191,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,17037,1:05.950.196,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17041,1:05.951.193,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,17042,1:05.951.196,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 76 9F 28 0E B4 FE 34 96 42 2F 20 E4 2E 3E C3… 0,,17046,1:05.952.193,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,17047,1:05.967.198,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D0 BA A2 13 AF DF 3E 80 AB 88 C2 F7 78 45 AA B5… 0,,17051,1:05.968.195,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,17052,1:05.982.200,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17056,1:05.983.197,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,17057,1:05.983.200,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D0 BA A2 13 AF DF 3E 80 AB 88 C2 F7 78 45 AA B5… 0,,17061,1:05.984.197,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,17062,1:05.999.203,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 10 02 58 A6 E5 D0 28 0F 48 CF 49 65 E3 B0 66… 0,,17066,1:06.000.200,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,17067,1:06.014.205,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17071,1:06.015.202,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,17072,1:06.015.205,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 10 02 58 A6 E5 D0 28 0F 48 CF 49 65 E3 B0 66… 0,,17076,1:06.016.202,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,17077,1:06.031.207,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 57 A0 4F F8 97 12 9A DA 47 70 4E F6 FC 84 63… 0,,17081,1:06.032.204,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,17082,1:06.046.209,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17086,1:06.047.206,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,17087,1:06.047.209,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 57 A0 4F F8 97 12 9A DA 47 70 4E F6 FC 84 63… 0,,17091,1:06.048.206,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,17092,1:06.063.212,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D BC 07 94 71 65 5C BA 46 73 97 98 20 41 C5 FA… 0,,17096,1:06.064.208,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,17097,1:06.078.214,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17101,1:06.079.211,16.005.041 ms,,,,,[17 SOF],[Frames: 481 - 497] 0,,17102,1:06.095.216,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 67 B1 8F 38 2F DB 76 8C 52 26 3E 5B AC 8A F4… 0,,17106,1:06.096.213,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,17107,1:06.110.218,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17111,1:06.111.215,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,17112,1:06.111.218,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 67 B1 8F 38 2F DB 76 8C 52 26 3E 5B AC 8A F4… 0,,17116,1:06.112.215,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,17117,1:06.127.220,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 53 B6 58 13 27 F2 14 6F 48 1A FF 78 6A 26 03… 0,,17121,1:06.128.217,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,17122,1:06.142.223,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17126,1:06.143.219,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,17127,1:06.143.223,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 53 B6 58 13 27 F2 14 6F 48 1A FF 78 6A 26 03… 0,,17131,1:06.144.220,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,17132,1:06.159.225,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B BD CD 61 06 AF 73 47 01 17 A9 5D 0A 56 99 B8… 0,,17136,1:06.160.222,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,17137,1:06.174.227,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17141,1:06.175.224,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,17142,1:06.175.227,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B BD CD 61 06 AF 73 47 01 17 A9 5D 0A 56 99 B8… 0,,17146,1:06.176.224,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,17147,1:06.191.229,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 CB 27 64 A0 A0 EE C1 2D 2F 44 B2 E0 C1 2B EC… 0,,17151,1:06.192.226,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,17152,1:06.206.231,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17156,1:06.207.228,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,17157,1:06.207.232,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 CB 27 64 A0 A0 EE C1 2D 2F 44 B2 E0 C1 2B EC… 0,,17161,1:06.208.228,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,17162,1:06.223.234,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FE C2 4A C2 FA 11 7E AE 12 60 05 1A D8 2E 0B F5… 0,,17166,1:06.224.231,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,17167,1:06.238.236,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17171,1:06.239.233,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,17172,1:06.239.236,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FE C2 4A C2 FA 11 7E AE 12 60 05 1A D8 2E 0B F5… 0,,17176,1:06.240.233,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,17177,1:06.255.238,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 30 2E F6 D8 80 A8 C2 8B A5 C2 96 3F 3B C6 8A CE… 0,,17181,1:06.256.235,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,17182,1:06.270.240,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17186,1:06.271.237,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,17187,1:06.271.240,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 30 2E F6 D8 80 A8 C2 8B A5 C2 96 3F 3B C6 8A CE… 0,,17191,1:06.272.237,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,17192,1:06.287.243,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F7 85 59 4A FA 12 1F DD 24 5F F3 1C CD 17 FF 15… 0,,17196,1:06.288.240,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,17197,1:06.302.245,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17201,1:06.303.242,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,17202,1:06.303.245,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F7 85 59 4A FA 12 1F DD 24 5F F3 1C CD 17 FF 15… 0,,17206,1:06.304.242,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,17207,1:06.319.247,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 47 D7 4A 0E D1 E6 51 B8 E0 70 08 56 0D 46 8C… 0,,17211,1:06.320.244,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,17212,1:06.334.249,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17216,1:06.335.246,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,17217,1:06.335.249,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 47 D7 4A 0E D1 E6 51 B8 E0 70 08 56 0D 46 8C… 0,,17221,1:06.336.246,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,17222,1:06.351.252,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 E5 46 FF F1 DE 45 01 CB 2B 51 83 F1 40 FF BA… 0,,17226,1:06.352.248,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,17227,1:06.366.254,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17231,1:06.367.251,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,17232,1:06.367.254,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C6 E5 46 FF F1 DE 45 01 CB 2B 51 83 F1 40 FF BA… 0,,17236,1:06.368.251,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,17237,1:06.383.256,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E 71 F2 21 96 C6 35 E0 27 00 D9 9A 8D DD 55 CD… 0,,17241,1:06.384.253,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,17242,1:06.398.258,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17246,1:06.399.255,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,17247,1:06.399.258,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E 71 F2 21 96 C6 35 E0 27 00 D9 9A 8D DD 55 CD… 0,,17251,1:06.400.255,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,17252,1:06.415.260,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 5E 81 10 BC CA 3F 77 40 6B 81 74 A2 C2 7B 1B… 0,,17256,1:06.416.257,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,17257,1:06.430.263,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17261,1:06.431.259,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,17262,1:06.431.263,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 5E 81 10 BC CA 3F 77 40 6B 81 74 A2 C2 7B 1B… 0,,17266,1:06.432.260,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,17267,1:06.447.265,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 9F EF C6 D6 BA 2B 8D 03 CD E0 FA 22 EF C5 46… 0,,17271,1:06.448.262,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,17272,1:06.462.267,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17276,1:06.463.264,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,17277,1:06.463.267,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 9F EF C6 D6 BA 2B 8D 03 CD E0 FA 22 EF C5 46… 0,,17281,1:06.464.264,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,17282,1:06.479.269,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 1E 3F 5F CB D3 EA 1C D2 AC 00 A4 52 9A 1F F4… 0,,17286,1:06.480.266,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,17287,1:06.494.271,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17291,1:06.495.268,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,17292,1:06.495.272,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 1E 3F 5F CB D3 EA 1C D2 AC 00 A4 52 9A 1F F4… 0,,17296,1:06.496.268,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,17297,1:06.511.274,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 8F 1D C2 3A E2 66 AA 46 80 C1 F5 E4 96 BF B8… 0,,17301,1:06.512.271,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,17302,1:06.526.276,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17306,1:06.527.273,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,17307,1:06.527.276,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 8F 1D C2 3A E2 66 AA 46 80 C1 F5 E4 96 BF B8… 0,,17311,1:06.528.273,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,17312,1:06.543.278,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 BB 1F 5C 0A 7E 97 A3 0E 33 66 3E 8F A8 A6 EF… 0,,17316,1:06.544.275,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,17317,1:06.558.280,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17321,1:06.559.277,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,17322,1:06.559.280,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 BB 1F 5C 0A 7E 97 A3 0E 33 66 3E 8F A8 A6 EF… 0,,17326,1:06.560.277,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,17327,1:06.575.283,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 D8 18 51 38 D4 05 B2 C6 B5 D4 5B 2F BD 3C CC… 0,,17331,1:06.576.280,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,17332,1:06.590.285,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17336,1:06.591.282,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,17337,1:06.591.285,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 D8 18 51 38 D4 05 B2 C6 B5 D4 5B 2F BD 3C CC… 0,,17341,1:06.592.282,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,17342,1:06.607.287,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E8 6C 70 32 30 7D 5E 85 99 42 63 50 8C DC 98 71… 0,,17346,1:06.608.284,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,17347,1:06.622.289,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17351,1:06.623.286,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,17352,1:06.623.289,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E8 6C 70 32 30 7D 5E 85 99 42 63 50 8C DC 98 71… 0,,17356,1:06.624.286,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,17357,1:06.639.292,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 59 B2 3B 82 0F 4C DA C0 0F 88 5E D9 D2 B1 9E 00… 0,,17361,1:06.640.288,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,17362,1:06.654.294,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17366,1:06.655.291,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,17367,1:06.655.294,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 59 B2 3B 82 0F 4C DA C0 0F 88 5E D9 D2 B1 9E 00… 0,,17371,1:06.656.291,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,17372,1:06.671.296,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 BD 54 2C E8 99 16 2D 1D EA C6 9D BC A2 FA 65… 0,,17376,1:06.672.293,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,17377,1:06.686.298,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17381,1:06.687.295,16.005.041 ms,,,,,[17 SOF],[Frames: 1089 - 1105] 0,,17382,1:06.703.300,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 20 9F 6D 57 E5 AE 21 90 1D 25 4F 1E FC 1B 9A… 0,,17386,1:06.704.297,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,17387,1:06.718.303,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17391,1:06.719.299,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,17392,1:06.719.303,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 20 9F 6D 57 E5 AE 21 90 1D 25 4F 1E FC 1B 9A… 0,,17396,1:06.720.300,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,17397,1:06.735.305,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 1A 3A 59 EF 4B 52 5C D8 B9 EB 7B D4 BC 5E BF… 0,,17401,1:06.736.302,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,17402,1:06.750.307,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17406,1:06.751.304,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,17407,1:06.751.307,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 1A 3A 59 EF 4B 52 5C D8 B9 EB 7B D4 BC 5E BF… 0,,17411,1:06.752.304,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,17412,1:06.767.309,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 59 89 84 26 D2 DF 13 C3 E4 DD 5A D9 03 82 CD 27… 0,,17416,1:06.768.306,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,17417,1:06.782.311,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17421,1:06.783.308,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,17422,1:06.783.312,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 59 89 84 26 D2 DF 13 C3 E4 DD 5A D9 03 82 CD 27… 0,,17426,1:06.784.308,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,17427,1:06.799.314,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 7B 27 66 6F D3 28 CA F7 16 44 9A 61 EE 15 22… 0,,17431,1:06.800.311,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,17432,1:06.814.316,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17436,1:06.815.313,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,17437,1:06.815.316,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 7B 27 66 6F D3 28 CA F7 16 44 9A 61 EE 15 22… 0,,17441,1:06.816.313,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,17442,1:06.831.318,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 EC 00 46 AD D7 1C F9 C9 43 F7 FE 62 39 AD 0E… 0,,17446,1:06.832.315,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,17447,1:06.846.320,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17451,1:06.847.317,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,17452,1:06.847.320,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 EC 00 46 AD D7 1C F9 C9 43 F7 FE 62 39 AD 0E… 0,,17456,1:06.848.317,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,17457,1:06.863.323,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 C7 1A AF 5F 25 11 39 12 E3 60 F6 EA EE 34 0A… 0,,17461,1:06.864.320,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,17462,1:06.878.325,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17466,1:06.879.322,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,17467,1:06.879.325,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 C7 1A AF 5F 25 11 39 12 E3 60 F6 EA EE 34 0A… 0,,17471,1:06.880.322,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,17472,1:06.895.327,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC 0E 9B 14 35 B0 1F A0 9C 8A 3B 22 E1 98 39 D8… 0,,17476,1:06.896.324,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,17477,1:06.910.329,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17481,1:06.911.326,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,17482,1:06.911.329,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EC 0E 9B 14 35 B0 1F A0 9C 8A 3B 22 E1 98 39 D8… 0,,17486,1:06.912.326,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,17487,1:06.927.332,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B2 C5 AA 25 6E 47 1E F9 FE BD 17 43 BC CC 2B F1… 0,,17491,1:06.928.328,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,17492,1:06.942.334,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17496,1:06.943.330,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,17497,1:06.943.334,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B2 C5 AA 25 6E 47 1E F9 FE BD 17 43 BC CC 2B F1… 0,,17501,1:06.944.331,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,17502,1:06.959.336,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 56 4D 6C 3A CB D1 1D 1F C7 12 F3 96 8A B3 D4 0D… 0,,17506,1:06.960.333,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,17507,1:06.974.338,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17511,1:06.975.335,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,17512,1:06.975.338,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 56 4D 6C 3A CB D1 1D 1F C7 12 F3 96 8A B3 D4 0D… 0,,17516,1:06.976.335,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,17517,1:06.991.340,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C DE 8F 01 AB 23 8F 10 8C E4 82 88 CE F1 56 77… 0,,17521,1:06.992.337,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,17522,1:07.006.342,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17526,1:07.007.339,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,17527,1:07.007.343,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C DE 8F 01 AB 23 8F 10 8C E4 82 88 CE F1 56 77… 0,,17531,1:07.008.340,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,17532,1:07.023.345,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E7 2C 8A 61 81 4B 0E 14 AD 3A E8 0C B5 94 C3 D8… 0,,17536,1:07.024.342,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,17537,1:07.038.347,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17541,1:07.039.344,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,17542,1:07.039.347,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E7 2C 8A 61 81 4B 0E 14 AD 3A E8 0C B5 94 C3 D8… 0,,17546,1:07.040.344,15.004.916 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,17547,1:07.055.349,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 37 B1 D0 5A C7 69 B1 1B EC 29 34 78 82 37 D2 00… 0,,17551,1:07.056.346,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,17552,1:07.070.351,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17556,1:07.071.348,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,17557,1:07.071.352,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 37 B1 D0 5A C7 69 B1 1B EC 29 34 78 82 37 D2 00… 0,,17561,1:07.072.348,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,17562,1:07.087.354,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E 18 C8 AA F2 A0 14 A3 09 56 62 51 CD 88 73 7D… 0,,17566,1:07.088.351,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,17567,1:07.102.356,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17571,1:07.103.353,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,17572,1:07.103.356,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E 18 C8 AA F2 A0 14 A3 09 56 62 51 CD 88 73 7D… 0,,17576,1:07.104.353,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,17577,1:07.119.358,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 C4 C4 21 3A 83 97 7C 45 48 E1 AC 4B CC DD BF… 0,,17581,1:07.120.355,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,17582,1:07.134.360,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17586,1:07.135.357,2.833 us,,,,,[1 SOF],[Frame: 1537] 0,,17587,1:07.135.360,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 C4 C4 21 3A 83 97 7C 45 48 E1 AC 4B CC DD BF… 0,,17591,1:07.136.357,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,17592,1:07.151.363,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D6 28 E0 C0 43 9B DC 36 22 67 D3 0E E8 D3 ED ED… 0,,17596,1:07.152.360,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,17597,1:07.166.365,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17601,1:07.167.362,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,17602,1:07.167.365,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D6 28 E0 C0 43 9B DC 36 22 67 D3 0E E8 D3 ED ED… 0,,17606,1:07.168.362,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,17607,1:07.183.367,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A6 97 F6 92 F5 DE FB 31 80 32 75 61 BD DD 65 54… 0,,17611,1:07.184.364,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,17612,1:07.198.369,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17616,1:07.199.366,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,17617,1:07.199.369,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A6 97 F6 92 F5 DE FB 31 80 32 75 61 BD DD 65 54… 0,,17621,1:07.200.366,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,17622,1:07.215.371,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CE 1C B9 AE 4D B4 B0 D4 21 E4 80 EA 5B E6 51 9B… 0,,17626,1:07.216.368,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,17627,1:07.230.374,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17631,1:07.231.370,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,17632,1:07.231.374,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CE 1C B9 AE 4D B4 B0 D4 21 E4 80 EA 5B E6 51 9B… 0,,17636,1:07.232.371,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,17637,1:07.247.376,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 9E A6 BB 81 4E AE 01 72 F2 74 1E 5E 0D 92 7E… 0,,17641,1:07.248.373,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,17642,1:07.262.378,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17646,1:07.263.375,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,17647,1:07.263.378,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E3 9E A6 BB 81 4E AE 01 72 F2 74 1E 5E 0D 92 7E… 0,,17651,1:07.264.375,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,17652,1:07.279.380,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 2A 13 44 49 11 59 D8 26 E4 97 5D 70 79 8B 72… 0,,17656,1:07.280.377,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,17657,1:07.294.382,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17661,1:07.295.379,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,17662,1:07.295.383,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 2A 13 44 49 11 59 D8 26 E4 97 5D 70 79 8B 72… 0,,17666,1:07.296.379,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,17667,1:07.311.385,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D 43 F9 BE AD 1A 48 54 FE 9E 63 E9 F2 60 C8 C6… 0,,17671,1:07.312.382,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,17672,1:07.326.387,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17676,1:07.327.384,16.005.041 ms,,,,,[17 SOF],[Frames: 1729 - 1745] 0,,17677,1:07.343.389,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 43 F9 BE AD 1A 48 54 FE 9E 63 E9 F2 60 C8 C6… 0,,17681,1:07.344.386,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,17682,1:07.358.391,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17686,1:07.359.388,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,17687,1:07.359.392,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 43 F9 BE AD 1A 48 54 FE 9E 63 E9 F2 60 C8 C6… 0,,17691,1:07.360.388,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,17692,1:07.375.394,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A 2F 48 F7 B8 48 C5 82 7D 43 2B B0 7B B6 F8 CD… 0,,17696,1:07.376.391,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,17697,1:07.390.396,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17701,1:07.391.393,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,17702,1:07.391.396,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A 2F 48 F7 B8 48 C5 82 7D 43 2B B0 7B B6 F8 CD… 0,,17706,1:07.392.393,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,17707,1:07.407.398,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 66 30 E5 CF 47 05 27 B9 C1 EE 30 AC CA 5D 50 09… 0,,17711,1:07.408.395,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,17712,1:07.422.400,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17716,1:07.423.397,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,17717,1:07.423.400,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 66 30 E5 CF 47 05 27 B9 C1 EE 30 AC CA 5D 50 09… 0,,17721,1:07.424.397,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,17722,1:07.439.403,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6E 73 1C F0 5A 69 AE E7 E7 8E 9F 3E 6D 41 4F B6… 0,,17726,1:07.440.399,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,17727,1:07.454.405,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17731,1:07.455.402,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,17732,1:07.455.405,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6E 73 1C F0 5A 69 AE E7 E7 8E 9F 3E 6D 41 4F B6… 0,,17736,1:07.456.402,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,17737,1:07.471.407,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 FF 6F FB 95 C4 A0 A6 0E 3E 47 15 5B 7D A4 7C… 0,,17741,1:07.472.404,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,17742,1:07.486.409,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17746,1:07.487.406,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,17747,1:07.487.409,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 FF 6F FB 95 C4 A0 A6 0E 3E 47 15 5B 7D A4 7C… 0,,17751,1:07.488.406,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,17752,1:07.503.411,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C B0 B0 D0 6E 80 14 51 A7 23 F1 75 5F 86 45 17… 0,,17756,1:07.504.408,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,17757,1:07.518.414,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17761,1:07.519.410,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,17762,1:07.519.414,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C B0 B0 D0 6E 80 14 51 A7 23 F1 75 5F 86 45 17… 0,,17766,1:07.520.411,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,17767,1:07.535.416,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 07 06 70 1E 70 9F 21 13 5B B2 95 35 04 CC BF BC… 0,,17771,1:07.536.413,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,17772,1:07.550.418,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17776,1:07.551.415,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,17777,1:07.551.418,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 07 06 70 1E 70 9F 21 13 5B B2 95 35 04 CC BF BC… 0,,17781,1:07.552.415,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,17782,1:07.567.420,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF 34 03 10 11 8F 0B D2 EA D2 A6 51 90 03 A2 03… 0,,17786,1:07.568.417,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,17787,1:07.582.422,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17791,1:07.583.419,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,17792,1:07.583.423,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF 34 03 10 11 8F 0B D2 EA D2 A6 51 90 03 A2 03… 0,,17796,1:07.584.419,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,17797,1:07.599.425,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9F E9 85 F6 72 F2 8C 4F 2B 63 FA D1 38 D8 2C 42… 0,,17801,1:07.600.422,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,17802,1:07.614.427,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17806,1:07.615.424,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,17807,1:07.615.427,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9F E9 85 F6 72 F2 8C 4F 2B 63 FA D1 38 D8 2C 42… 0,,17811,1:07.616.424,15.005.000 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,17812,1:07.631.429,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD E1 68 01 65 83 25 59 78 5B 6D 73 D7 32 2D BA… 0,,17816,1:07.632.426,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,17817,1:07.646.431,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17821,1:07.647.428,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,17822,1:07.647.431,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD E1 68 01 65 83 25 59 78 5B 6D 73 D7 32 2D BA… 0,,17826,1:07.648.428,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,17827,1:07.663.434,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E BD CF 63 62 71 AE 5D B8 87 FE C3 DB 5D FB 42… 0,,17831,1:07.664.431,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,17832,1:07.678.436,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17836,1:07.679.433,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,17837,1:07.679.436,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E BD CF 63 62 71 AE 5D B8 87 FE C3 DB 5D FB 42… 0,,17841,1:07.680.433,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,17842,1:07.695.438,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 27 70 D2 A0 C8 47 8F 99 51 DF 86 0D 0F B8 85… 0,,17846,1:07.696.435,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,17847,1:07.710.440,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17851,1:07.711.437,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,17852,1:07.711.440,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 27 70 D2 A0 C8 47 8F 99 51 DF 86 0D 0F B8 85… 0,,17856,1:07.712.437,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,17857,1:07.727.443,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 2D 06 DA 2A 83 D6 C9 BD 81 6F DB 56 92 F6 30… 0,,17861,1:07.728.439,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,17862,1:07.742.445,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17866,1:07.743.442,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,17867,1:07.743.445,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 2D 06 DA 2A 83 D6 C9 BD 81 6F DB 56 92 F6 30… 0,,17871,1:07.744.442,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,17872,1:07.759.447,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 D3 79 8A C8 0D 6D 4C 8D 54 B9 7C D8 C7 92 1C… 0,,17876,1:07.760.444,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,17877,1:07.774.449,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17881,1:07.775.446,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,17882,1:07.775.449,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 D3 79 8A C8 0D 6D 4C 8D 54 B9 7C D8 C7 92 1C… 0,,17886,1:07.776.446,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,17887,1:07.791.451,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 C4 D8 48 BE 1E 6D 51 7B 23 F4 E3 96 18 A6 F5… 0,,17891,1:07.792.448,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,17892,1:07.806.454,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17896,1:07.807.450,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,17897,1:07.807.454,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 C4 D8 48 BE 1E 6D 51 7B 23 F4 E3 96 18 A6 F5… 0,,17901,1:07.808.451,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,17902,1:07.823.456,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC 1E 73 E6 DF 74 79 15 89 C8 C3 D5 85 B3 44 4E… 0,,17906,1:07.824.453,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,17907,1:07.838.458,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17911,1:07.839.455,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,17912,1:07.839.458,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EC 1E 73 E6 DF 74 79 15 89 C8 C3 D5 85 B3 44 4E… 0,,17916,1:07.840.455,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,17917,1:07.855.460,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1A FC 3B 71 24 4E 77 2D 9E A3 E3 D4 2F FB D0 E3… 0,,17921,1:07.856.457,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,17922,1:07.870.462,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17926,1:07.871.459,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,17927,1:07.871.463,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1A FC 3B 71 24 4E 77 2D 9E A3 E3 D4 2F FB D0 E3… 0,,17931,1:07.872.459,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,17932,1:07.887.465,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C B1 3F 6F E5 4E 26 DB 93 B8 75 1D F4 99 CA B4… 0,,17936,1:07.888.462,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,17937,1:07.902.467,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17941,1:07.903.464,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,17942,1:07.903.467,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C B1 3F 6F E5 4E 26 DB 93 B8 75 1D F4 99 CA B4… 0,,17946,1:07.904.464,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,17947,1:07.919.469,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B F8 23 4A 15 77 C0 2C 5B EF 4B A9 15 B3 E6 77… 0,,17951,1:07.920.466,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,17952,1:07.934.471,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17956,1:07.935.468,16.005.041 ms,,,,,[17 SOF],[Frames: 289 - 305] 0,,17957,1:07.951.474,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 0C 3A A1 27 6B 96 4F 48 AD 5C C6 AD 64 DD 5E… 0,,17961,1:07.952.471,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,17962,1:07.966.476,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17966,1:07.967.473,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,17967,1:07.967.476,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 0C 3A A1 27 6B 96 4F 48 AD 5C C6 AD 64 DD 5E… 0,,17971,1:07.968.473,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,17972,1:07.983.478,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 78 B1 68 00 4E AF D5 6D 21 22 5D BE 95 11 51… 0,,17976,1:07.984.475,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,17977,1:07.998.480,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17981,1:07.999.477,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,17982,1:07.999.480,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 78 B1 68 00 4E AF D5 6D 21 22 5D BE 95 11 51… 0,,17986,1:08.000.477,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,17987,1:08.015.483,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 0F 74 29 F9 DF 04 99 D4 B1 FC DF C2 7E EF 47… 0,,17991,1:08.016.479,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,17992,1:08.030.485,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17996,1:08.031.482,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,17997,1:08.031.485,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 0F 74 29 F9 DF 04 99 D4 B1 FC DF C2 7E EF 47… 0,,18001,1:08.032.482,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,18002,1:08.047.487,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B6 0D B8 3B 7A B0 30 8C D9 24 AC 21 0A 3C 06 44… 0,,18006,1:08.048.484,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,18007,1:08.062.489,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18011,1:08.063.486,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,18012,1:08.063.489,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B6 0D B8 3B 7A B0 30 8C D9 24 AC 21 0A 3C 06 44… 0,,18016,1:08.064.486,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,18017,1:08.079.491,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 88 66 C8 A9 EB 09 54 1C 4D 27 69 44 90 2F 0D 23… 0,,18021,1:08.080.488,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,18022,1:08.094.494,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18026,1:08.095.490,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,18027,1:08.095.494,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 88 66 C8 A9 EB 09 54 1C 4D 27 69 44 90 2F 0D 23… 0,,18031,1:08.096.491,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,18032,1:08.111.496,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D D7 31 5D C3 99 84 66 A4 F1 A8 E7 17 2D 48 A7… 0,,18036,1:08.112.493,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,18037,1:08.126.498,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18041,1:08.127.495,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,18042,1:08.127.498,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D D7 31 5D C3 99 84 66 A4 F1 A8 E7 17 2D 48 A7… 0,,18046,1:08.128.495,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,18047,1:08.143.500,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F0 3E 0E E4 C8 D6 4E BF 31 37 E6 4C 00 8F 8C 44… 0,,18051,1:08.144.497,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,18052,1:08.158.502,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18056,1:08.159.499,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,18057,1:08.159.503,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F0 3E 0E E4 C8 D6 4E BF 31 37 E6 4C 00 8F 8C 44… 0,,18061,1:08.160.499,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,18062,1:08.175.505,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F7 72 86 17 F1 47 C9 C0 9F 64 35 99 23 3C 82 0F… 0,,18066,1:08.176.502,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,18067,1:08.190.507,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18071,1:08.191.504,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,18072,1:08.191.507,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F7 72 86 17 F1 47 C9 C0 9F 64 35 99 23 3C 82 0F… 0,,18076,1:08.192.504,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,18077,1:08.207.509,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F0 61 02 18 18 28 9D EF 9F E3 77 89 F2 2F 54 A9… 0,,18081,1:08.208.506,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,18082,1:08.222.511,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18086,1:08.223.508,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,18087,1:08.223.511,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F0 61 02 18 18 28 9D EF 9F E3 77 89 F2 2F 54 A9… 0,,18091,1:08.224.508,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,18092,1:08.239.514,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CB 4C 67 95 7F 74 F5 F8 5E 54 47 00 2B BB 3E 60… 0,,18096,1:08.240.511,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,18097,1:08.254.516,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18101,1:08.255.513,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,18102,1:08.255.516,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CB 4C 67 95 7F 74 F5 F8 5E 54 47 00 2B BB 3E 60… 0,,18106,1:08.256.513,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,18107,1:08.271.518,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 5B C0 7A E8 9A 4D 91 5A 2A AD CD 48 37 62 A6… 0,,18111,1:08.272.515,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,18112,1:08.286.520,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18116,1:08.287.517,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,18117,1:08.287.520,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 5B C0 7A E8 9A 4D 91 5A 2A AD CD 48 37 62 A6… 0,,18121,1:08.288.517,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,18122,1:08.303.523,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 0B 80 06 FA BC 06 59 AF 89 36 EE 59 7A 6D C0… 0,,18126,1:08.304.519,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,18127,1:08.318.525,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18131,1:08.319.521,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,18132,1:08.319.525,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 0B 80 06 FA BC 06 59 AF 89 36 EE 59 7A 6D C0… 0,,18136,1:08.320.522,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,18137,1:08.335.527,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AE E1 38 0E 2A 74 D7 FB 1F 72 94 84 C8 07 2C AB… 0,,18141,1:08.336.524,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,18142,1:08.350.529,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18146,1:08.351.526,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,18147,1:08.351.529,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AE E1 38 0E 2A 74 D7 FB 1F 72 94 84 C8 07 2C AB… 0,,18151,1:08.352.526,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,18152,1:08.367.531,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 58 53 C0 65 E1 48 CF 67 F1 58 48 30 19 08 02… 0,,18156,1:08.368.528,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,18157,1:08.382.533,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18161,1:08.383.530,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,18162,1:08.383.534,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 58 53 C0 65 E1 48 CF 67 F1 58 48 30 19 08 02… 0,,18166,1:08.384.531,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,18167,1:08.399.536,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 B1 B2 66 62 FA CD 71 BE E8 C4 57 D6 7D 22 E8… 0,,18171,1:08.400.533,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,18172,1:08.414.538,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18176,1:08.415.535,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,18177,1:08.415.538,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 B1 B2 66 62 FA CD 71 BE E8 C4 57 D6 7D 22 E8… 0,,18181,1:08.416.535,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,18182,1:08.431.540,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 D2 B0 A2 86 FC F3 94 E2 C2 9A 8B E0 16 B4 31… 0,,18186,1:08.432.537,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,18187,1:08.446.542,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18191,1:08.447.539,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,18192,1:08.447.543,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 D2 B0 A2 86 FC F3 94 E2 C2 9A 8B E0 16 B4 31… 0,,18196,1:08.448.539,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,18197,1:08.463.545,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 37 0B 9B 51 FB 95 A7 FB 0F 19 51 2F DB BA AD 02… 0,,18201,1:08.464.542,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,18202,1:08.478.547,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18206,1:08.479.544,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,18207,1:08.479.547,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 37 0B 9B 51 FB 95 A7 FB 0F 19 51 2F DB BA AD 02… 0,,18211,1:08.480.544,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,18212,1:08.495.549,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 AE 5B 45 E0 78 8D 6D 0C 30 C6 88 71 D9 E3 65… 0,,18216,1:08.496.546,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,18217,1:08.510.551,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18221,1:08.511.548,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,18222,1:08.511.551,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 AE 5B 45 E0 78 8D 6D 0C 30 C6 88 71 D9 E3 65… 0,,18226,1:08.512.548,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,18227,1:08.527.554,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 A5 BB FD 97 80 4E F8 4B D5 B9 72 3F E7 02 A4… 0,,18231,1:08.528.551,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,18232,1:08.542.556,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18236,1:08.543.553,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,18237,1:08.543.556,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 A5 BB FD 97 80 4E F8 4B D5 B9 72 3F E7 02 A4… 0,,18241,1:08.544.553,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,18242,1:08.559.558,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA D4 0F 83 00 D7 AB C9 71 6C DA BB 7E 5D 0B 3A… 0,,18246,1:08.560.555,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,18247,1:08.574.560,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18251,1:08.575.557,16.005.041 ms,,,,,[17 SOF],[Frames: 929 - 945] 0,,18252,1:08.591.563,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA D4 0F 83 00 D7 AB C9 71 6C DA BB 7E 5D 0B 3A… 0,,18256,1:08.592.559,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,18257,1:08.606.565,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18261,1:08.607.561,16.005.041 ms,,,,,[17 SOF],[Frames: 961 - 977] 0,,18262,1:08.623.567,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 33 11 6B D5 3B D9 EF ED DB 1F 25 56 30 92 0E… 0,,18266,1:08.624.564,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,18267,1:08.638.569,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18271,1:08.639.566,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,18272,1:08.639.569,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9A 33 11 6B D5 3B D9 EF ED DB 1F 25 56 30 92 0E… 0,,18276,1:08.640.566,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,18277,1:08.655.571,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EE 12 5E A3 AF B6 80 8B 15 97 87 43 AF DF 86 09… 0,,18281,1:08.656.568,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,18282,1:08.670.573,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18286,1:08.671.570,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,18287,1:08.671.574,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EE 12 5E A3 AF B6 80 8B 15 97 87 43 AF DF 86 09… 0,,18291,1:08.672.570,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,18292,1:08.687.576,50.937 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 62 0F 75 FB CF BB E0 2F 90 BE DE 5F DE E3 EC… 0,,18296,1:08.688.573,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,18297,1:08.702.578,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18301,1:08.703.575,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,18302,1:08.703.578,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC 62 0F 75 FB CF BB E0 2F 90 BE DE 5F DE E3 EC… 0,,18306,1:08.704.575,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,18307,1:08.719.580,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C 8F C2 0F 91 9A 21 07 6A 65 B0 5E E8 10 19 D7… 0,,18311,1:08.720.577,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,18312,1:08.734.582,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18316,1:08.735.579,2.833 us,,,,,[1 SOF],[Frame: 1089] 0,,18317,1:08.735.582,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C 8F C2 0F 91 9A 21 07 6A 65 B0 5E E8 10 19 D7… 0,,18321,1:08.736.579,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,18322,1:08.751.585,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 2D 07 2B 76 C1 DF 74 84 B4 A7 00 98 2E 90 8C… 0,,18326,1:08.752.582,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,18327,1:08.766.587,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18331,1:08.767.584,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,18332,1:08.767.587,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 2D 07 2B 76 C1 DF 74 84 B4 A7 00 98 2E 90 8C… 0,,18336,1:08.768.584,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,18337,1:08.783.589,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 66 6B A6 C6 E4 E1 64 D3 3F 38 86 ED D0 21 13 5C… 0,,18341,1:08.784.586,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,18342,1:08.798.591,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18346,1:08.799.588,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,18347,1:08.799.591,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 66 6B A6 C6 E4 E1 64 D3 3F 38 86 ED D0 21 13 5C… 0,,18351,1:08.800.588,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,18352,1:08.815.594,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 BB 43 CE 14 FD FC 7F A1 10 1F 77 71 71 AC 3D… 0,,18356,1:08.816.590,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,18357,1:08.830.596,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18361,1:08.831.593,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,18362,1:08.831.596,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 BB 43 CE 14 FD FC 7F A1 10 1F 77 71 71 AC 3D… 0,,18366,1:08.832.593,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,18367,1:08.847.598,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B 92 EB EE 8F 64 E3 42 B8 12 8A C8 90 B9 36 28… 0,,18371,1:08.848.595,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,18372,1:08.862.600,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18376,1:08.863.597,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,18377,1:08.863.600,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B 92 EB EE 8F 64 E3 42 B8 12 8A C8 90 B9 36 28… 0,,18381,1:08.864.597,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,18382,1:08.879.602,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 53 4D 73 22 25 86 EB 68 3E B7 DF 76 03 5E 27 FD… 0,,18386,1:08.880.599,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,18387,1:08.894.605,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18391,1:08.895.601,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,18392,1:08.895.605,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 53 4D 73 22 25 86 EB 68 3E B7 DF 76 03 5E 27 FD… 0,,18396,1:08.896.602,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,18397,1:08.911.607,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC 08 38 32 3F 86 D8 2E 25 82 9D A6 A3 67 40 A1… 0,,18401,1:08.912.604,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,18402,1:08.926.609,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18406,1:08.927.606,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,18407,1:08.927.609,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC 08 38 32 3F 86 D8 2E 25 82 9D A6 A3 67 40 A1… 0,,18411,1:08.928.606,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,18412,1:08.943.611,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 65 FB 75 33 D9 68 93 64 18 D2 DB 9C 46 93 BB 9C… 0,,18416,1:08.944.608,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,18417,1:08.958.613,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18421,1:08.959.610,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,18422,1:08.959.614,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 65 FB 75 33 D9 68 93 64 18 D2 DB 9C 46 93 BB 9C… 0,,18426,1:08.960.610,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,18427,1:08.975.616,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 35 F1 4E 7D 3F E0 42 99 57 FF 42 14 8D 29 3A 74… 0,,18431,1:08.976.613,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,18432,1:08.990.618,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18436,1:08.991.615,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,18437,1:08.991.618,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 35 F1 4E 7D 3F E0 42 99 57 FF 42 14 8D 29 3A 74… 0,,18441,1:08.992.615,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,18442,1:09.007.620,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 D4 27 C6 CE 85 DC 90 1F D7 71 AC FF FB 31 67… 0,,18446,1:09.008.617,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,18447,1:09.022.622,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18451,1:09.023.619,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,18452,1:09.023.622,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 D4 27 C6 CE 85 DC 90 1F D7 71 AC FF FB 31 67… 0,,18456,1:09.024.619,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,18457,1:09.039.625,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FE 34 08 49 7B E8 8D 30 31 A6 4D 83 29 52 63 C8… 0,,18461,1:09.040.622,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,18462,1:09.054.627,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18466,1:09.055.624,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,18467,1:09.055.627,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FE 34 08 49 7B E8 8D 30 31 A6 4D 83 29 52 63 C8… 0,,18471,1:09.056.624,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,18472,1:09.071.629,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC 9B 3F 15 93 AC D0 05 84 92 64 D3 B7 C9 70 28… 0,,18476,1:09.072.626,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,18477,1:09.086.631,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18481,1:09.087.628,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,18482,1:09.087.631,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC 9B 3F 15 93 AC D0 05 84 92 64 D3 B7 C9 70 28… 0,,18486,1:09.088.628,15.004.916 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,18487,1:09.103.634,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 21 0B 29 4C 5B 9C 02 A2 8F 5B 5E 42 19 8D 09 C8… 0,,18491,1:09.104.630,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,18492,1:09.118.636,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18496,1:09.119.633,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,18497,1:09.119.636,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 21 0B 29 4C 5B 9C 02 A2 8F 5B 5E 42 19 8D 09 C8… 0,,18501,1:09.120.633,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,18502,1:09.135.638,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 C4 56 C3 81 59 15 A6 5F B2 ED A1 55 40 6F 16… 0,,18506,1:09.136.635,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,18507,1:09.150.640,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18511,1:09.151.637,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,18512,1:09.151.640,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 C4 56 C3 81 59 15 A6 5F B2 ED A1 55 40 6F 16… 0,,18516,1:09.152.637,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,18517,1:09.167.642,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 99 CC F3 EA 8B 83 34 3A 49 A9 9A 3F 5C B3 44… 0,,18521,1:09.168.639,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,18522,1:09.182.645,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18526,1:09.183.641,16.005.125 ms,,,,,[17 SOF],[Frames: 1537 - 1553] 0,,18527,1:09.199.647,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C ED 54 00 F2 EF BA C9 73 8F D7 B7 2F 33 83 00… 0,,18531,1:09.200.644,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,18532,1:09.214.649,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18536,1:09.215.646,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,18537,1:09.215.649,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C ED 54 00 F2 EF BA C9 73 8F D7 B7 2F 33 83 00… 0,,18541,1:09.216.646,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,18542,1:09.231.651,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 99 4D 00 99 00 C7 FD 83 9A D0 46 F2 33 77 A6… 0,,18546,1:09.232.648,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,18547,1:09.246.653,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18551,1:09.247.650,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,18552,1:09.247.654,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 99 4D 00 99 00 C7 FD 83 9A D0 46 F2 33 77 A6… 0,,18556,1:09.248.650,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,18557,1:09.263.656,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 45 99 05 8B 27 86 9E 4B 72 BF F7 0C ED 34 5D… 0,,18561,1:09.264.653,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,18562,1:09.278.658,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18566,1:09.279.655,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,18567,1:09.279.658,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 45 99 05 8B 27 86 9E 4B 72 BF F7 0C ED 34 5D… 0,,18571,1:09.280.655,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,18572,1:09.295.660,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 99 37 70 FA 41 7D C2 AB CB 50 66 12 00 2B 25 92… 0,,18576,1:09.296.657,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,18577,1:09.310.662,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18581,1:09.311.659,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,18582,1:09.311.662,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 99 37 70 FA 41 7D C2 AB CB 50 66 12 00 2B 25 92… 0,,18586,1:09.312.659,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,18587,1:09.327.665,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B 25 C9 B1 D5 DE 8B 77 44 0D 67 1F F2 A2 D1 4A… 0,,18591,1:09.328.662,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,18592,1:09.342.667,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18596,1:09.343.664,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,18597,1:09.343.667,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B 25 C9 B1 D5 DE 8B 77 44 0D 67 1F F2 A2 D1 4A… 0,,18601,1:09.344.664,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,18602,1:09.359.669,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D9 55 F0 13 AF 30 41 98 95 F8 9B 3F 9D DE 24 82… 0,,18606,1:09.360.666,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,18607,1:09.374.671,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18611,1:09.375.668,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,18612,1:09.375.671,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D9 55 F0 13 AF 30 41 98 95 F8 9B 3F 9D DE 24 82… 0,,18616,1:09.376.668,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,18617,1:09.391.674,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 56 39 BB 99 B8 DC 0C 7F BD 25 C6 24 90 EC 06 EB… 0,,18621,1:09.392.670,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,18622,1:09.406.676,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18626,1:09.407.673,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,18627,1:09.407.676,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 56 39 BB 99 B8 DC 0C 7F BD 25 C6 24 90 EC 06 EB… 0,,18631,1:09.408.673,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,18632,1:09.423.678,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 0C 2C C4 C4 0E C7 C1 D0 07 1C 06 27 47 B3 FD… 0,,18636,1:09.424.675,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,18637,1:09.438.680,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18641,1:09.439.677,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,18642,1:09.439.680,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 0C 2C C4 C4 0E C7 C1 D0 07 1C 06 27 47 B3 FD… 0,,18646,1:09.440.677,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,18647,1:09.455.682,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 64 14 7D 9D 6F AB A5 6C 57 60 79 B1 3E 9D EF C5… 0,,18651,1:09.456.679,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,18652,1:09.470.685,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18656,1:09.471.681,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,18657,1:09.471.685,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 64 14 7D 9D 6F AB A5 6C 57 60 79 B1 3E 9D EF C5… 0,,18661,1:09.472.682,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,18662,1:09.487.687,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 7B B4 45 E4 62 08 8F 91 D3 7E 8B 29 81 81 4D… 0,,18666,1:09.488.684,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,18667,1:09.502.689,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18671,1:09.503.686,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,18672,1:09.503.689,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 7B B4 45 E4 62 08 8F 91 D3 7E 8B 29 81 81 4D… 0,,18676,1:09.504.686,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,18677,1:09.519.691,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 FD 5A FB 23 33 AF 8E D4 D1 31 BF D4 66 54 BA… 0,,18681,1:09.520.688,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,18682,1:09.534.693,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18686,1:09.535.690,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,18687,1:09.535.694,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 FD 5A FB 23 33 AF 8E D4 D1 31 BF D4 66 54 BA… 0,,18691,1:09.536.690,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,18692,1:09.551.696,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 B3 08 77 1C 13 C6 07 BB A7 EF 99 5B 5C A6 82… 0,,18696,1:09.552.693,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,18697,1:09.566.698,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18701,1:09.567.695,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,18702,1:09.567.698,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 B3 08 77 1C 13 C6 07 BB A7 EF 99 5B 5C A6 82… 0,,18706,1:09.568.695,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,18707,1:09.583.700,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 20 B9 B9 39 BC 48 F1 CF 16 A6 95 27 DA F1 C5… 0,,18711,1:09.584.697,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,18712,1:09.598.702,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18716,1:09.599.699,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,18717,1:09.599.702,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 20 B9 B9 39 BC 48 F1 CF 16 A6 95 27 DA F1 C5… 0,,18721,1:09.600.699,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,18722,1:09.615.705,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C 79 56 6D AD C1 3B 25 61 98 6A 9A 11 A8 5D BB… 0,,18726,1:09.616.702,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,18727,1:09.630.707,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18731,1:09.631.704,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,18732,1:09.631.707,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C 79 56 6D AD C1 3B 25 61 98 6A 9A 11 A8 5D BB… 0,,18736,1:09.632.704,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,18737,1:09.647.709,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E B9 02 08 A7 61 67 FA 7E 62 B0 81 76 B4 7D 74… 0,,18741,1:09.648.706,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,18742,1:09.662.711,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18746,1:09.663.708,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,18747,1:09.663.711,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E B9 02 08 A7 61 67 FA 7E 62 B0 81 76 B4 7D 74… 0,,18751,1:09.664.708,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,18752,1:09.679.714,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 76 CF 8B 96 8E 79 07 89 EC 4A 18 59 45 11 59… 0,,18756,1:09.680.710,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,18757,1:09.694.716,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18761,1:09.695.713,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,18762,1:09.695.716,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 76 CF 8B 96 8E 79 07 89 EC 4A 18 59 45 11 59… 0,,18766,1:09.696.713,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,18767,1:09.711.718,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 6B 0F BC 19 ED CB 29 29 D0 37 2B 3D 90 C9 E0… 0,,18771,1:09.712.715,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,18772,1:09.726.720,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18776,1:09.727.717,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,18777,1:09.727.720,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 6B 0F BC 19 ED CB 29 29 D0 37 2B 3D 90 C9 E0… 0,,18781,1:09.728.717,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,18782,1:09.743.722,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3F CB D7 4A E9 15 A2 30 02 BB A2 BD F2 A6 C4 4F… 0,,18786,1:09.744.719,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,18787,1:09.758.724,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18791,1:09.759.721,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,18792,1:09.759.725,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3F CB D7 4A E9 15 A2 30 02 BB A2 BD F2 A6 C4 4F… 0,,18796,1:09.760.722,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,18797,1:09.775.727,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 2C 0D 49 8E 0D DE 77 B0 32 9B 49 0F 10 E2 96… 0,,18801,1:09.776.724,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,18802,1:09.790.729,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18806,1:09.791.726,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,18807,1:09.791.729,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 2C 0D 49 8E 0D DE 77 B0 32 9B 49 0F 10 E2 96… 0,,18811,1:09.792.726,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,18812,1:09.807.731,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4F 6E 71 5B E1 87 D8 60 73 46 EC 05 55 49 AC AD… 0,,18816,1:09.808.728,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,18817,1:09.822.733,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18821,1:09.823.730,16.005.041 ms,,,,,[17 SOF],[Frames: 129 - 145] 0,,18822,1:09.839.736,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4F 6E 71 5B E1 87 D8 60 73 46 EC 05 55 49 AC AD… 0,,18826,1:09.840.733,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,18827,1:09.854.738,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18831,1:09.855.735,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,18832,1:09.855.738,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4F 6E 71 5B E1 87 D8 60 73 46 EC 05 55 49 AC AD… 0,,18836,1:09.856.735,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,18837,1:09.871.740,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF 7D 6E 49 70 CB 2D 33 08 D2 8C 28 67 24 C4 E4… 0,,18841,1:09.872.737,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,18842,1:09.886.742,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18846,1:09.887.739,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,18847,1:09.887.742,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF 7D 6E 49 70 CB 2D 33 08 D2 8C 28 67 24 C4 E4… 0,,18851,1:09.888.739,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,18852,1:09.903.745,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D 77 BD 62 FC 3B 04 DC 1B 6D 7D 8F F4 BE 3C 05… 0,,18856,1:09.904.742,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,18857,1:09.918.747,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18861,1:09.919.744,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,18862,1:09.919.747,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D 77 BD 62 FC 3B 04 DC 1B 6D 7D 8F F4 BE 3C 05… 0,,18866,1:09.920.744,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,18867,1:09.935.749,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 89 D1 3B E7 8B 10 15 A1 EA 2A 55 E8 04 F4 5A… 0,,18871,1:09.936.746,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,18872,1:09.950.751,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18876,1:09.951.748,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,18877,1:09.951.751,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 89 D1 3B E7 8B 10 15 A1 EA 2A 55 E8 04 F4 5A… 0,,18881,1:09.952.748,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,18882,1:09.967.754,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 84 50 7C E2 45 56 44 84 51 E8 C0 06 D8 67 E3… 0,,18886,1:09.968.750,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,18887,1:09.982.756,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18891,1:09.983.752,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,18892,1:09.983.756,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 84 50 7C E2 45 56 44 84 51 E8 C0 06 D8 67 E3… 0,,18896,1:09.984.753,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,18897,1:09.999.758,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 02 A0 EE 57 73 0F EF C1 9B 4E 3A F1 FA 65 35 F3… 0,,18901,1:10.000.755,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,18902,1:10.014.760,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18906,1:10.015.757,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,18907,1:10.015.760,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 02 A0 EE 57 73 0F EF C1 9B 4E 3A F1 FA 65 35 F3… 0,,18911,1:10.016.757,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,18912,1:10.031.762,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 25 0C 16 72 D6 D3 5E 3A 17 17 A6 F0 32 68 77… 0,,18916,1:10.032.759,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,18917,1:10.046.764,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18921,1:10.047.761,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,18922,1:10.047.765,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 26 25 0C 16 72 D6 D3 5E 3A 17 17 A6 F0 32 68 77… 0,,18926,1:10.048.762,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,18927,1:10.063.767,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 9E D7 B7 DD 5C 6D F7 87 94 52 1D D8 19 CF 43… 0,,18931,1:10.064.764,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,18932,1:10.078.769,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18936,1:10.079.766,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,18937,1:10.079.769,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 9E D7 B7 DD 5C 6D F7 87 94 52 1D D8 19 CF 43… 0,,18941,1:10.080.766,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,18942,1:10.095.771,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 8D 8C E3 93 41 E4 4F 05 B1 F0 57 65 44 7C 5F… 0,,18946,1:10.096.768,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,18947,1:10.110.773,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18951,1:10.111.770,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,18952,1:10.111.774,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 8D 8C E3 93 41 E4 4F 05 B1 F0 57 65 44 7C 5F… 0,,18956,1:10.112.770,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,18957,1:10.127.776,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 CA 6E DC CC 23 A7 08 E4 5E 1C F6 2B 02 15 C4… 0,,18961,1:10.128.773,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,18962,1:10.142.778,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18966,1:10.143.775,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,18967,1:10.143.778,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 CA 6E DC CC 23 A7 08 E4 5E 1C F6 2B 02 15 C4… 0,,18971,1:10.144.775,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,18972,1:10.159.780,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 0A 8D BD AF 36 EE 5D 6F 3C 75 E1 11 B6 78 63… 0,,18976,1:10.160.777,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,18977,1:10.174.782,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18981,1:10.175.779,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,18982,1:10.175.782,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 0A 8D BD AF 36 EE 5D 6F 3C 75 E1 11 B6 78 63… 0,,18986,1:10.176.779,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,18987,1:10.191.785,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2F AD D4 D8 D1 2F E5 82 E2 5C AC 2D A6 C2 88 48… 0,,18991,1:10.192.781,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,18992,1:10.206.787,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18996,1:10.207.784,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,18997,1:10.207.787,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2F AD D4 D8 D1 2F E5 82 E2 5C AC 2D A6 C2 88 48… 0,,19001,1:10.208.784,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,19002,1:10.223.789,50.916 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 11 90 93 FD 55 DB EF 2E D0 C0 ED FD CB 52 7F… 0,,19006,1:10.224.786,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,19007,1:10.238.791,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19011,1:10.239.788,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,19012,1:10.239.791,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 11 90 93 FD 55 DB EF 2E D0 C0 ED FD CB 52 7F… 0,,19016,1:10.240.788,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,19017,1:10.255.793,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D6 BC F5 5B 73 FA 49 CF 8E 76 55 C1 B1 0A CA 9E… 0,,19021,1:10.256.790,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,19022,1:10.270.796,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19026,1:10.271.792,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,19027,1:10.271.796,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D6 BC F5 5B 73 FA 49 CF 8E 76 55 C1 B1 0A CA 9E… 0,,19031,1:10.272.793,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,19032,1:10.287.798,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 03 48 A2 27 9A 84 B7 B5 5E C2 B2 A9 25 AA 49 5F… 0,,19036,1:10.288.795,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,19037,1:10.302.800,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19041,1:10.303.797,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,19042,1:10.303.800,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 03 48 A2 27 9A 84 B7 B5 5E C2 B2 A9 25 AA 49 5F… 0,,19046,1:10.304.797,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,19047,1:10.319.802,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 AF E9 C6 3A 38 14 F3 A3 8E 4C 8C FD F5 C9 62… 0,,19051,1:10.320.799,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,19052,1:10.334.804,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19056,1:10.335.801,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,19057,1:10.335.805,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 AF E9 C6 3A 38 14 F3 A3 8E 4C 8C FD F5 C9 62… 0,,19061,1:10.336.801,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,19062,1:10.351.807,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 7D 87 D9 E4 E3 95 FC 58 CF BF 3E 2C 4A 3F B7… 0,,19066,1:10.352.804,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,19067,1:10.366.809,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19071,1:10.367.806,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,19072,1:10.367.809,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 7D 87 D9 E4 E3 95 FC 58 CF BF 3E 2C 4A 3F B7… 0,,19076,1:10.368.806,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,19077,1:10.383.811,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 AE F7 54 C2 39 BD 5B 71 6C C7 AC 71 C8 36 C8… 0,,19081,1:10.384.808,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,19082,1:10.398.813,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19086,1:10.399.810,2.833 us,,,,,[1 SOF],[Frame: 705] 0,,19087,1:10.399.813,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 AE F7 54 C2 39 BD 5B 71 6C C7 AC 71 C8 36 C8… 0,,19091,1:10.400.810,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,19092,1:10.415.816,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3F 79 50 12 8E 43 7E 6B EC 7B E8 87 26 7C DC F5… 0,,19096,1:10.416.813,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,19097,1:10.430.818,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19101,1:10.431.815,16.005.041 ms,,,,,[17 SOF],[Frames: 737 - 753] 0,,19102,1:10.447.820,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 39 45 8F B8 17 67 A8 B3 77 87 1A CF E1 B5 FA 3F… 0,,19106,1:10.448.817,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,19107,1:10.462.822,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19111,1:10.463.819,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,19112,1:10.463.822,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 39 45 8F B8 17 67 A8 B3 77 87 1A CF E1 B5 FA 3F… 0,,19116,1:10.464.819,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,19117,1:10.479.825,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BB D3 F4 FB 57 C9 3A B4 B4 D0 50 9D C1 F6 58 48… 0,,19121,1:10.480.821,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,19122,1:10.494.827,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19126,1:10.495.824,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,19127,1:10.495.827,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BB D3 F4 FB 57 C9 3A B4 B4 D0 50 9D C1 F6 58 48… 0,,19131,1:10.496.824,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,19132,1:10.511.829,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 B9 E8 B6 6E 6D D1 7E 85 F2 C8 2A C5 DA 7A 0D… 0,,19136,1:10.512.826,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,19137,1:10.526.831,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19141,1:10.527.828,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,19142,1:10.527.831,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 B9 E8 B6 6E 6D D1 7E 85 F2 C8 2A C5 DA 7A 0D… 0,,19146,1:10.528.828,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,19147,1:10.543.833,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF 3B 8B 2A A0 A6 84 C8 AA F5 F1 BE 2B C3 F9 8C… 0,,19151,1:10.544.830,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,19152,1:10.558.836,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19156,1:10.559.832,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,19157,1:10.559.836,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF 3B 8B 2A A0 A6 84 C8 AA F5 F1 BE 2B C3 F9 8C… 0,,19161,1:10.560.833,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,19162,1:10.575.838,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 0C CA AD 2A 36 7E 63 3D 82 1A CE E2 02 A5 D5… 0,,19166,1:10.576.835,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,19167,1:10.590.840,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19171,1:10.591.837,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,19172,1:10.591.840,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 0C CA AD 2A 36 7E 63 3D 82 1A CE E2 02 A5 D5… 0,,19176,1:10.592.837,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,19177,1:10.607.842,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 2C 49 95 8C E3 41 4A D4 FF 80 F4 F6 E6 27 99… 0,,19181,1:10.608.839,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,19182,1:10.622.844,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19186,1:10.623.841,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,19187,1:10.623.845,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 2C 49 95 8C E3 41 4A D4 FF 80 F4 F6 E6 27 99… 0,,19191,1:10.624.841,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,19192,1:10.639.847,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 79 DE 60 8D E8 A7 41 25 A8 74 5F 42 A7 40 A1… 0,,19196,1:10.640.844,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,19197,1:10.654.849,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19201,1:10.655.846,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,19202,1:10.655.849,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 79 DE 60 8D E8 A7 41 25 A8 74 5F 42 A7 40 A1… 0,,19206,1:10.656.846,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,19207,1:10.671.851,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 99 96 78 6E C6 0C 1D BC 33 2B 17 9E 38 F0 B3 67… 0,,19211,1:10.672.848,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,19212,1:10.686.853,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19216,1:10.687.850,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,19217,1:10.687.853,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 99 96 78 6E C6 0C 1D BC 33 2B 17 9E 38 F0 B3 67… 0,,19221,1:10.688.850,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,19222,1:10.703.856,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 60 8D 79 6D 4D C9 FD 5E D1 31 68 E2 15 FC 07 9C… 0,,19226,1:10.704.853,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,19227,1:10.718.858,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19231,1:10.719.855,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,19232,1:10.719.858,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 8D 79 6D 4D C9 FD 5E D1 31 68 E2 15 FC 07 9C… 0,,19236,1:10.720.855,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,19237,1:10.735.860,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 60 6D ED E4 E1 10 28 C9 46 42 92 68 EB 94 36 83… 0,,19241,1:10.736.857,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,19242,1:10.750.862,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19246,1:10.751.859,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,19247,1:10.751.862,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 6D ED E4 E1 10 28 C9 46 42 92 68 EB 94 36 83… 0,,19251,1:10.752.859,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,19252,1:10.767.865,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD B8 DF 89 01 83 30 50 29 2D 34 87 F9 99 9E C4… 0,,19256,1:10.768.861,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,19257,1:10.782.867,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19261,1:10.783.864,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,19262,1:10.783.867,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD B8 DF 89 01 83 30 50 29 2D 34 87 F9 99 9E C4… 0,,19266,1:10.784.864,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,19267,1:10.799.869,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 52 69 71 6E CC 73 70 27 06 99 01 F6 76 60 25 35… 0,,19271,1:10.800.866,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,19272,1:10.814.871,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19276,1:10.815.868,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,19277,1:10.815.871,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 52 69 71 6E CC 73 70 27 06 99 01 F6 76 60 25 35… 0,,19281,1:10.816.868,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,19282,1:10.831.873,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 5E 05 FC 9D B4 3E D0 DA 5C C1 3D 23 42 CA 5F… 0,,19286,1:10.832.870,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,19287,1:10.846.876,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19291,1:10.847.872,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,19292,1:10.847.876,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 5E 05 FC 9D B4 3E D0 DA 5C C1 3D 23 42 CA 5F… 0,,19296,1:10.848.873,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,19297,1:10.863.878,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 9E 8A 53 D0 DC 4E 9B 64 DE DB 4B 82 B2 F4 46… 0,,19301,1:10.864.875,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,19302,1:10.878.880,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19306,1:10.879.877,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,19307,1:10.879.880,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 9E 8A 53 D0 DC 4E 9B 64 DE DB 4B 82 B2 F4 46… 0,,19311,1:10.880.877,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,19312,1:10.895.882,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 84 CD 2F AC 62 CA F6 E8 11 3C 7D 14 31 78 DE DC… 0,,19316,1:10.896.879,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,19317,1:10.910.884,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19321,1:10.911.881,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,19322,1:10.911.885,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 84 CD 2F AC 62 CA F6 E8 11 3C 7D 14 31 78 DE DC… 0,,19326,1:10.912.881,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,19327,1:10.927.887,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 8E 8F 67 9C FA C0 0B D7 65 E4 03 3E 1C B6 E0… 0,,19331,1:10.928.884,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,19332,1:10.942.889,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19336,1:10.943.886,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,19337,1:10.943.889,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 8E 8F 67 9C FA C0 0B D7 65 E4 03 3E 1C B6 E0… 0,,19341,1:10.944.886,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,19342,1:10.959.891,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 2A DE 3F 8A 78 2D B6 3B 27 A9 2D DF 2A 01 53… 0,,19346,1:10.960.888,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,19347,1:10.974.893,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19351,1:10.975.890,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,19352,1:10.975.893,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A 2A DE 3F 8A 78 2D B6 3B 27 A9 2D DF 2A 01 53… 0,,19356,1:10.976.890,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,19357,1:10.991.896,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AC 48 C2 EE B3 A2 B4 8A 83 B3 85 3E 9D 7D C7 5A… 0,,19361,1:10.992.893,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,19362,1:11.006.898,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19366,1:11.007.895,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,19367,1:11.007.898,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AC 48 C2 EE B3 A2 B4 8A 83 B3 85 3E 9D 7D C7 5A… 0,,19371,1:11.008.895,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,19372,1:11.023.900,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 42 3E B4 A5 A6 A6 88 96 95 E5 B4 75 29 C4 84… 0,,19376,1:11.024.897,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,19377,1:11.038.902,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19381,1:11.039.899,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,19382,1:11.039.902,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A 42 3E B4 A5 A6 A6 88 96 95 E5 B4 75 29 C4 84… 0,,19386,1:11.040.899,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,19387,1:11.055.905,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 E7 A6 25 8E 13 74 8C C3 FA 6F 18 02 D8 69 18… 0,,19391,1:11.056.901,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,19392,1:11.070.907,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19396,1:11.071.904,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,19397,1:11.071.907,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 E7 A6 25 8E 13 74 8C C3 FA 6F 18 02 D8 69 18… 0,,19401,1:11.072.904,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,19402,1:11.087.909,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 E2 61 AF 01 D3 36 D1 12 54 FE 24 BD 80 FD 6A… 0,,19406,1:11.088.906,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,19407,1:11.102.911,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19411,1:11.103.908,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,19412,1:11.103.911,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 E2 61 AF 01 D3 36 D1 12 54 FE 24 BD 80 FD 6A… 0,,19416,1:11.104.908,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,19417,1:11.119.913,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 8B 5A FF 44 67 7E 34 C8 9D 01 6A D3 F1 E8 51… 0,,19421,1:11.120.910,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,19422,1:11.134.916,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19426,1:11.135.912,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,19427,1:11.135.916,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 8B 5A FF 44 67 7E 34 C8 9D 01 6A D3 F1 E8 51… 0,,19431,1:11.136.913,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,19432,1:11.151.918,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 E8 D0 5D E8 4A D6 84 8E CE 87 30 AD 00 54 CC… 0,,19436,1:11.152.915,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,19437,1:11.166.920,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19441,1:11.167.917,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,19442,1:11.167.920,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 E8 D0 5D E8 4A D6 84 8E CE 87 30 AD 00 54 CC… 0,,19446,1:11.168.917,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,19447,1:11.183.922,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 BB F4 0C A1 9E 85 20 C0 71 66 4F 06 22 6C 62… 0,,19451,1:11.184.919,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,19452,1:11.198.924,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19456,1:11.199.921,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,19457,1:11.199.925,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 BB F4 0C A1 9E 85 20 C0 71 66 4F 06 22 6C 62… 0,,19461,1:11.200.921,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,19462,1:11.215.927,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E9 9E 04 A0 87 1B F3 08 A2 DB C4 2F 4E DF B3 25… 0,,19466,1:11.216.924,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,19467,1:11.230.929,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19471,1:11.231.926,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,19472,1:11.231.929,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E9 9E 04 A0 87 1B F3 08 A2 DB C4 2F 4E DF B3 25… 0,,19476,1:11.232.926,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,19477,1:11.247.931,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 14 B3 32 D9 CB 77 44 94 5D 86 AC 9A FC DA 0D… 0,,19481,1:11.248.928,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,19482,1:11.262.933,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19486,1:11.263.930,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,19487,1:11.263.933,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 14 B3 32 D9 CB 77 44 94 5D 86 AC 9A FC DA 0D… 0,,19491,1:11.264.930,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,19492,1:11.279.936,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 BE EC 97 B1 67 73 47 67 EF 9A A9 E4 F3 E6 74… 0,,19496,1:11.280.933,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,19497,1:11.294.938,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19501,1:11.295.935,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,19502,1:11.295.938,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 BE EC 97 B1 67 73 47 67 EF 9A A9 E4 F3 E6 74… 0,,19506,1:11.296.935,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,19507,1:11.311.940,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 4D E0 19 F6 7A 38 17 11 BF 94 DD 04 88 79 BE… 0,,19511,1:11.312.937,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,19512,1:11.326.942,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19516,1:11.327.939,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,19517,1:11.327.942,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 4D E0 19 F6 7A 38 17 11 BF 94 DD 04 88 79 BE… 0,,19521,1:11.328.939,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,19522,1:11.343.945,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C9 61 BB 82 E6 2F 87 63 F8 7C C6 27 10 26 7C 0B… 0,,19526,1:11.344.941,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,19527,1:11.358.947,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19531,1:11.359.943,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,19532,1:11.359.947,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C9 61 BB 82 E6 2F 87 63 F8 7C C6 27 10 26 7C 0B… 0,,19536,1:11.360.944,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,19537,1:11.375.949,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B DD 5B AC D6 71 2D BB E7 CB FB 31 C0 81 31 16… 0,,19541,1:11.376.946,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,19542,1:11.390.951,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19546,1:11.391.948,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,19547,1:11.391.951,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B DD 5B AC D6 71 2D BB E7 CB FB 31 C0 81 31 16… 0,,19551,1:11.392.948,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,19552,1:11.407.953,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A6 88 F9 0C 87 7D 79 82 0B 94 BA FD EF 2F 8C D1… 0,,19556,1:11.408.950,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,19557,1:11.422.955,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19561,1:11.423.952,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,19562,1:11.423.956,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A6 88 F9 0C 87 7D 79 82 0B 94 BA FD EF 2F 8C D1… 0,,19566,1:11.424.953,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,19567,1:11.439.958,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 0A B2 4F E0 02 61 0D B9 0E 5D EE 76 5A 12 3A… 0,,19571,1:11.440.955,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,19572,1:11.454.960,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19576,1:11.455.957,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,19577,1:11.455.960,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 0A B2 4F E0 02 61 0D B9 0E 5D EE 76 5A 12 3A… 0,,19581,1:11.456.957,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,19582,1:11.471.962,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 C8 43 34 79 28 F2 E6 E9 48 7D 98 18 56 6F 63… 0,,19586,1:11.472.959,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,19587,1:11.486.964,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19591,1:11.487.961,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,19592,1:11.487.965,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 C8 43 34 79 28 F2 E6 E9 48 7D 98 18 56 6F 63… 0,,19596,1:11.488.961,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,19597,1:11.503.967,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 FD B4 23 3B 70 24 10 58 47 66 0C 61 49 0D 26… 0,,19601,1:11.504.964,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,19602,1:11.518.969,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19606,1:11.519.966,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,19607,1:11.519.969,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 FD B4 23 3B 70 24 10 58 47 66 0C 61 49 0D 26… 0,,19611,1:11.520.966,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,19612,1:11.535.971,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 67 F5 56 FA 58 5A 3E 81 36 FE DF D3 1D 6F 84… 0,,19616,1:11.536.968,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,19617,1:11.550.973,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19621,1:11.551.970,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,19622,1:11.551.973,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 67 F5 56 FA 58 5A 3E 81 36 FE DF D3 1D 6F 84… 0,,19626,1:11.552.970,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,19627,1:11.567.976,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B 61 4F C7 1C 9B 25 BA D5 DF EA AC 3C 9F 55 85… 0,,19631,1:11.568.972,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,19632,1:11.582.978,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19636,1:11.583.975,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,19637,1:11.583.978,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0B 61 4F C7 1C 9B 25 BA D5 DF EA AC 3C 9F 55 85… 0,,19641,1:11.584.975,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,19642,1:11.599.980,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1F 89 9A B3 36 EB 27 8F 1C 7A B0 2E 93 04 71 35… 0,,19646,1:11.600.977,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,19647,1:11.614.982,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19651,1:11.615.979,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,19652,1:11.615.982,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1F 89 9A B3 36 EB 27 8F 1C 7A B0 2E 93 04 71 35… 0,,19656,1:11.616.979,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,19657,1:11.631.984,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 84 5E A8 C1 0B AF 16 D9 29 EE 65 23 FE F9 C0 BD… 0,,19661,1:11.632.981,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,19662,1:11.646.987,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19666,1:11.647.983,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,19667,1:11.647.987,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 84 5E A8 C1 0B AF 16 D9 29 EE 65 23 FE F9 C0 BD… 0,,19671,1:11.648.984,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,19672,1:11.663.989,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 50 A6 65 9F 73 55 82 3C C1 1C E0 4D FB 30 51… 0,,19676,1:11.664.986,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,19677,1:11.678.991,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19681,1:11.679.988,16.005.125 ms,,,,,[17 SOF],[Frames: 1985 - 2001] 0,,19682,1:11.695.993,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6A 15 0D 46 17 6C 6C 7B FF 71 D7 46 D9 B6 B8 A2… 0,,19686,1:11.696.990,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,19687,1:11.710.996,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19691,1:11.711.992,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,19692,1:11.711.996,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6A 15 0D 46 17 6C 6C 7B FF 71 D7 46 D9 B6 B8 A2… 0,,19696,1:11.712.992,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,19697,1:11.727.998,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 AD 97 37 22 80 A1 7F 83 77 EA AF 16 DB 47 9C… 0,,19701,1:11.728.995,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,19702,1:11.743.000,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19706,1:11.743.997,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,19707,1:11.744.000,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 AD 97 37 22 80 A1 7F 83 77 EA AF 16 DB 47 9C… 0,,19711,1:11.744.997,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,19712,1:11.760.002,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE 33 BD A2 B8 59 58 85 2C 41 F1 DE 6D 6C E5 FF… 0,,19716,1:11.760.999,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,19717,1:11.775.004,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19721,1:11.776.001,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,19722,1:11.776.004,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE 33 BD A2 B8 59 58 85 2C 41 F1 DE 6D 6C E5 FF… 0,,19726,1:11.777.001,15.004.916 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,19727,1:11.792.007,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6A DD 2B 58 66 06 2C E6 9E 01 CE B9 BA 54 C7 F8… 0,,19731,1:11.793.004,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,19732,1:11.807.009,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19736,1:11.808.006,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,19737,1:11.808.009,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6A DD 2B 58 66 06 2C E6 9E 01 CE B9 BA 54 C7 F8… 0,,19741,1:11.809.006,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,19742,1:11.824.011,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC 7B 1C 59 BE 2B 70 C3 B4 F6 A9 4F E1 74 5B A1… 0,,19746,1:11.825.008,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,19747,1:11.839.013,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19751,1:11.840.010,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,19752,1:11.840.013,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC 7B 1C 59 BE 2B 70 C3 B4 F6 A9 4F E1 74 5B A1… 0,,19756,1:11.841.010,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,19757,1:11.856.016,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A 0B F6 6E 3D CB 74 2F F0 EC 9A FD 84 CC 0B 9E… 0,,19761,1:11.857.012,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,19762,1:11.871.018,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19766,1:11.872.015,2.833 us,,,,,[1 SOF],[Frame: 129] 0,,19767,1:11.872.018,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A 0B F6 6E 3D CB 74 2F F0 EC 9A FD 84 CC 0B 9E… 0,,19771,1:11.873.015,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,19772,1:11.888.020,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E 69 60 99 31 E9 DC FF 64 CF 85 48 13 27 C9 08… 0,,19776,1:11.889.017,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,19777,1:11.903.022,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19781,1:11.904.019,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,19782,1:11.904.022,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E 69 60 99 31 E9 DC FF 64 CF 85 48 13 27 C9 08… 0,,19786,1:11.905.019,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,19787,1:11.920.024,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B 16 E7 4B F6 8D C7 B7 B3 14 C9 94 4C 9C 8A 00… 0,,19791,1:11.921.021,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,19792,1:11.935.027,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19796,1:11.936.023,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,19797,1:11.936.027,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B 16 E7 4B F6 8D C7 B7 B3 14 C9 94 4C 9C 8A 00… 0,,19801,1:11.937.024,15.004.916 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,19802,1:11.952.029,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 22 44 B2 54 44 A1 3B 99 0F DF 21 CC FE 56 E6… 0,,19806,1:11.953.026,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,19807,1:11.967.031,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19811,1:11.968.028,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,19812,1:11.968.031,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 22 44 B2 54 44 A1 3B 99 0F DF 21 CC FE 56 E6… 0,,19816,1:11.969.028,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,19817,1:11.984.033,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 74 D6 2E 62 9F E7 7F A6 B4 21 BE C2 6A E5 5E… 0,,19821,1:11.985.030,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,19822,1:11.999.035,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19826,1:12.000.032,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,19827,1:12.000.036,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 74 D6 2E 62 9F E7 7F A6 B4 21 BE C2 6A E5 5E… 0,,19831,1:12.001.032,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,19832,1:12.016.038,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 67 8F 2D C4 AF A3 E1 5C C4 A4 3E 68 00 90 D1… 0,,19836,1:12.017.035,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,19837,1:12.031.040,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19841,1:12.032.037,2.833 us,,,,,[1 SOF],[Frame: 289] 0,,19842,1:12.032.040,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 67 8F 2D C4 AF A3 E1 5C C4 A4 3E 68 00 90 D1… 0,,19846,1:12.033.037,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,19847,1:12.048.042,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 A1 5C EE DE 5C 90 9B 4D 61 0E 2C 73 A5 91 2B… 0,,19851,1:12.049.039,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,19852,1:12.063.044,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19856,1:12.064.041,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,19857,1:12.064.044,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 A1 5C EE DE 5C 90 9B 4D 61 0E 2C 73 A5 91 2B… 0,,19861,1:12.065.041,15.004.916 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,19862,1:12.080.047,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CB 0D C3 51 E4 74 17 86 8B 47 71 7D 44 45 4B 9B… 0,,19866,1:12.081.044,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,19867,1:12.095.049,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19871,1:12.096.046,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,19872,1:12.096.049,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CB 0D C3 51 E4 74 17 86 8B 47 71 7D 44 45 4B 9B… 0,,19876,1:12.097.046,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,19877,1:12.112.051,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 29 F5 B4 0B E2 AB B9 DD CA AC F9 E1 F2 34 8B 61… 0,,19881,1:12.113.048,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,19882,1:12.127.053,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19886,1:12.128.050,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,19887,1:12.128.053,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 29 F5 B4 0B E2 AB B9 DD CA AC F9 E1 F2 34 8B 61… 0,,19891,1:12.129.050,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,19892,1:12.144.056,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD B6 CA 57 21 C4 D3 BC 7C 63 E6 44 25 5D 75 65… 0,,19896,1:12.145.052,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,19897,1:12.159.058,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19901,1:12.160.055,2.833 us,,,,,[1 SOF],[Frame: 417] 0,,19902,1:12.160.058,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD B6 CA 57 21 C4 D3 BC 7C 63 E6 44 25 5D 75 65… 0,,19906,1:12.161.055,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,19907,1:12.176.060,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8F D0 37 0F 53 0E 76 05 F4 2C F7 F7 54 72 9A 85… 0,,19911,1:12.177.057,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,19912,1:12.191.062,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19916,1:12.192.059,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,19917,1:12.192.062,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8F D0 37 0F 53 0E 76 05 F4 2C F7 F7 54 72 9A 85… 0,,19921,1:12.193.059,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,19922,1:12.208.064,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 30 D1 F2 DB 5D 2E E6 1B 52 EC 80 6A 40 87 52… 0,,19926,1:12.209.061,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,19927,1:12.223.067,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19931,1:12.224.063,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,19932,1:12.224.067,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 30 D1 F2 DB 5D 2E E6 1B 52 EC 80 6A 40 87 52… 0,,19936,1:12.225.064,15.004.916 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,19937,1:12.240.069,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8F 99 57 D2 1C 65 7E 46 4D 22 89 4A F4 D7 14 24… 0,,19941,1:12.241.066,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,19942,1:12.255.071,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19946,1:12.256.068,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,19947,1:12.256.071,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8F 99 57 D2 1C 65 7E 46 4D 22 89 4A F4 D7 14 24… 0,,19951,1:12.257.068,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,19952,1:12.272.073,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC C9 B4 C2 8B ED A6 E1 74 22 F5 46 3D C0 0B FF… 0,,19956,1:12.273.070,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,19957,1:12.287.075,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19961,1:12.288.072,16.005.041 ms,,,,,[17 SOF],[Frames: 545 - 561] 0,,19962,1:12.304.078,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 9D 15 49 9C 27 DC 86 44 B9 89 2F 22 B9 CE 96… 0,,19966,1:12.305.075,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,19967,1:12.319.080,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19971,1:12.320.077,2.833 us,,,,,[1 SOF],[Frame: 577] 0,,19972,1:12.320.080,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 9D 15 49 9C 27 DC 86 44 B9 89 2F 22 B9 CE 96… 0,,19976,1:12.321.077,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,19977,1:12.336.082,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 8C 86 59 7D A0 27 D7 C8 A8 C5 8D D8 D1 A7 92… 0,,19981,1:12.337.079,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,19982,1:12.351.084,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19986,1:12.352.081,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,19987,1:12.352.084,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 8C 86 59 7D A0 27 D7 C8 A8 C5 8D D8 D1 A7 92… 0,,19991,1:12.353.081,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,19992,1:12.368.087,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BB 02 9B 66 6E 92 E6 5B 9A 1E 75 5C 55 B6 1D 86… 0,,19996,1:12.369.084,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,19997,1:12.383.089,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20001,1:12.384.086,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,20002,1:12.384.089,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BB 02 9B 66 6E 92 E6 5B 9A 1E 75 5C 55 B6 1D 86… 0,,20006,1:12.385.086,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,20007,1:12.400.091,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 A8 56 51 DC 9E A2 8E 24 A2 69 ED DC 22 09 94… 0,,20011,1:12.401.088,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,20012,1:12.415.093,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20016,1:12.416.090,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,20017,1:12.416.093,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 A8 56 51 DC 9E A2 8E 24 A2 69 ED DC 22 09 94… 0,,20021,1:12.417.090,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,20022,1:12.432.096,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 D0 2C 6A F0 56 CE CB 04 B1 94 41 12 5D BB E7… 0,,20026,1:12.433.092,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,20027,1:12.447.098,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20031,1:12.448.095,2.833 us,,,,,[1 SOF],[Frame: 705] 0,,20032,1:12.448.098,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 D0 2C 6A F0 56 CE CB 04 B1 94 41 12 5D BB E7… 0,,20036,1:12.449.095,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,20037,1:12.464.100,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 A8 AE 80 AE 7C 46 AE 67 1D 89 A3 11 8D 4C 8F… 0,,20041,1:12.465.097,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,20042,1:12.479.102,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20046,1:12.480.099,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,20047,1:12.480.102,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 A8 AE 80 AE 7C 46 AE 67 1D 89 A3 11 8D 4C 8F… 0,,20051,1:12.481.099,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,20052,1:12.496.104,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 96 CC 54 A9 38 F9 00 B3 DF 0A CC CE BE 39 51… 0,,20056,1:12.497.101,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,20057,1:12.511.107,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20061,1:12.512.103,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,20062,1:12.512.107,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A 96 CC 54 A9 38 F9 00 B3 DF 0A CC CE BE 39 51… 0,,20066,1:12.513.104,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,20067,1:12.528.109,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC 3C EC AA 7C 23 2E 93 8B A6 42 DA 84 4A 05 9F… 0,,20071,1:12.529.106,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,20072,1:12.543.111,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20076,1:12.544.108,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,20077,1:12.544.111,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC 3C EC AA 7C 23 2E 93 8B A6 42 DA 84 4A 05 9F… 0,,20081,1:12.545.108,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,20082,1:12.560.113,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD D0 50 FA 0E 95 FA CD 74 92 31 28 A5 64 87 2D… 0,,20086,1:12.561.110,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,20087,1:12.575.115,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20091,1:12.576.112,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,20092,1:12.576.116,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD D0 50 FA 0E 95 FA CD 74 92 31 28 A5 64 87 2D… 0,,20096,1:12.577.112,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,20097,1:12.592.118,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 B5 A4 CA 9F FC F7 C8 81 8E AA AB C6 85 DC BA… 0,,20101,1:12.593.115,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,20102,1:12.607.120,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20106,1:12.608.117,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,20107,1:12.608.120,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 B5 A4 CA 9F FC F7 C8 81 8E AA AB C6 85 DC BA… 0,,20111,1:12.609.117,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,20112,1:12.624.122,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D B6 80 1E D9 76 E0 65 70 14 95 FF 21 89 39 29… 0,,20116,1:12.625.119,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,20117,1:12.639.124,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20121,1:12.640.121,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,20122,1:12.640.124,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D B6 80 1E D9 76 E0 65 70 14 95 FF 21 89 39 29… 0,,20126,1:12.641.121,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,20127,1:12.656.127,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 72 2B 44 AF ED 17 62 A7 36 16 AD 65 69 C7 7E 79… 0,,20131,1:12.657.124,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,20132,1:12.671.129,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20136,1:12.672.126,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,20137,1:12.672.129,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 72 2B 44 AF ED 17 62 A7 36 16 AD 65 69 C7 7E 79… 0,,20141,1:12.673.126,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,20142,1:12.688.131,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 F4 AE 4F 47 D2 F6 97 1E C1 5D 16 99 F4 67 70… 0,,20146,1:12.689.128,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,20147,1:12.703.133,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20151,1:12.704.130,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,20152,1:12.704.133,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 F4 AE 4F 47 D2 F6 97 1E C1 5D 16 99 F4 67 70… 0,,20156,1:12.705.130,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,20157,1:12.720.136,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB 39 CC 87 8F 6B EE EB C8 72 C1 C1 6C 16 3F 8F… 0,,20161,1:12.721.132,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,20162,1:12.735.138,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20166,1:12.736.134,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,20167,1:12.736.138,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB 39 CC 87 8F 6B EE EB C8 72 C1 C1 6C 16 3F 8F… 0,,20171,1:12.737.135,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,20172,1:12.752.140,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 F4 6D 99 85 0D 86 C5 54 52 1B BD 52 81 C3 13… 0,,20176,1:12.753.137,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,20177,1:12.767.142,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20181,1:12.768.139,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,20182,1:12.768.142,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 F4 6D 99 85 0D 86 C5 54 52 1B BD 52 81 C3 13… 0,,20186,1:12.769.139,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,20187,1:12.784.144,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 47 8D C0 6D EB E2 D6 75 83 57 EC DE 26 D7 60… 0,,20191,1:12.785.141,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,20192,1:12.799.146,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20196,1:12.800.143,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,20197,1:12.800.147,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 47 8D C0 6D EB E2 D6 75 83 57 EC DE 26 D7 60… 0,,20201,1:12.801.144,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,20202,1:12.816.149,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 1A E6 CB 76 01 F4 DC 50 38 59 41 43 E2 A2 16… 0,,20206,1:12.817.146,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,20207,1:12.831.151,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20211,1:12.832.148,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,20212,1:12.832.151,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 1A E6 CB 76 01 F4 DC 50 38 59 41 43 E2 A2 16… 0,,20216,1:12.833.148,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,20217,1:12.848.153,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 58 10 07 7F 52 63 19 50 67 A7 87 53 49 0D 11… 0,,20221,1:12.849.150,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,20222,1:12.863.155,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20226,1:12.864.152,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,20227,1:12.864.156,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 58 10 07 7F 52 63 19 50 67 A7 87 53 49 0D 11… 0,,20231,1:12.865.152,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,20232,1:12.880.158,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF 52 1C E2 C1 CC 5A 39 96 59 CF 5B F3 95 95 1E… 0,,20236,1:12.881.155,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,20237,1:12.895.160,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20241,1:12.896.157,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,20242,1:12.896.160,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BF 52 1C E2 C1 CC 5A 39 96 59 CF 5B F3 95 95 1E… 0,,20246,1:12.897.157,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,20247,1:12.912.162,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 5D 7B 48 56 3A D4 97 B5 C9 04 E8 6F 7F 6E 86… 0,,20251,1:12.913.159,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,20252,1:12.927.164,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20256,1:12.928.161,16.005.020 ms,,,,,[17 SOF],[Frames: 1185 - 1201] 0,,20257,1:12.944.167,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 E9 99 B4 1A 38 05 69 AD 64 3A 04 F4 71 E6 9B… 0,,20261,1:12.945.164,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,20262,1:12.959.169,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20266,1:12.960.166,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,20267,1:12.960.169,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 E9 99 B4 1A 38 05 69 AD 64 3A 04 F4 71 E6 9B… 0,,20271,1:12.961.166,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,20272,1:12.976.171,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 C6 C2 51 5D 49 76 7A 74 47 3B 7E 17 4E 13 61… 0,,20276,1:12.977.168,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,20277,1:12.991.173,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20281,1:12.992.170,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,20282,1:12.992.173,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 C6 C2 51 5D 49 76 7A 74 47 3B 7E 17 4E 13 61… 0,,20286,1:12.993.170,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,20287,1:13.008.176,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 53 C4 23 BD 4F 74 8E B9 0A 41 26 D6 DC 1E 24… 0,,20291,1:13.009.172,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,20292,1:13.023.178,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20296,1:13.024.174,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,20297,1:13.024.178,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 53 C4 23 BD 4F 74 8E B9 0A 41 26 D6 DC 1E 24… 0,,20301,1:13.025.175,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,20302,1:13.040.180,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D E3 77 31 4C 2D EF CD 96 2B 55 7E EB C3 93 4F… 0,,20306,1:13.041.177,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,20307,1:13.055.182,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20311,1:13.056.179,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,20312,1:13.056.182,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D E3 77 31 4C 2D EF CD 96 2B 55 7E EB C3 93 4F… 0,,20316,1:13.057.179,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,20317,1:13.072.184,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF 07 9C 44 8B 04 EA AC 7F 02 99 89 6E 9C 78 A7… 0,,20321,1:13.073.181,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,20322,1:13.087.186,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20326,1:13.088.183,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,20327,1:13.088.187,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF 07 9C 44 8B 04 EA AC 7F 02 99 89 6E 9C 78 A7… 0,,20331,1:13.089.183,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,20332,1:13.104.189,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E BE 8A F7 08 C7 C5 C3 7C B3 20 AA 56 B1 8A 2E… 0,,20336,1:13.105.186,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,20337,1:13.119.191,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20341,1:13.120.188,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,20342,1:13.120.191,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E BE 8A F7 08 C7 C5 C3 7C B3 20 AA 56 B1 8A 2E… 0,,20346,1:13.121.188,15.004.916 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,20347,1:13.136.193,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 8F 6C A7 3B D8 D1 C2 0C 5C 92 84 C0 66 5A 2B… 0,,20351,1:13.137.190,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,20352,1:13.151.195,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20356,1:13.152.192,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,20357,1:13.152.195,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 8F 6C A7 3B D8 D1 C2 0C 5C 92 84 C0 66 5A 2B… 0,,20361,1:13.153.192,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,20362,1:13.168.198,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 49 82 96 5D 1C 67 62 F7 A4 9A F5 47 B6 36 93… 0,,20366,1:13.169.195,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,20367,1:13.183.200,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20371,1:13.184.197,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,20372,1:13.184.200,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 49 82 96 5D 1C 67 62 F7 A4 9A F5 47 B6 36 93… 0,,20376,1:13.185.197,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,20377,1:13.200.202,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 29 EB 13 90 68 28 CB 35 F9 4A 08 DE 72 3E 29… 0,,20381,1:13.201.199,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,20382,1:13.215.204,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20386,1:13.216.201,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,20387,1:13.216.204,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 29 EB 13 90 68 28 CB 35 F9 4A 08 DE 72 3E 29… 0,,20391,1:13.217.201,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,20392,1:13.232.207,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FB 65 78 5F BE 1C CE 81 48 4B 71 B7 10 11 D7 27… 0,,20396,1:13.233.203,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,20397,1:13.247.209,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20401,1:13.248.206,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,20402,1:13.248.209,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FB 65 78 5F BE 1C CE 81 48 4B 71 B7 10 11 D7 27… 0,,20406,1:13.249.206,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,20407,1:13.264.211,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 83 02 D3 BE 02 9E 0E 81 08 4F 84 2D E9 6A 0A E6… 0,,20411,1:13.265.208,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,20412,1:13.279.213,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20416,1:13.280.210,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,20417,1:13.280.213,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 83 02 D3 BE 02 9E 0E 81 08 4F 84 2D E9 6A 0A E6… 0,,20421,1:13.281.210,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,20422,1:13.296.216,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 9C 3D 1B 3E 6C 99 17 39 E5 45 FF B7 F1 4E 30… 0,,20426,1:13.297.212,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,20427,1:13.311.218,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20431,1:13.312.214,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,20432,1:13.312.218,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 9C 3D 1B 3E 6C 99 17 39 E5 45 FF B7 F1 4E 30… 0,,20436,1:13.313.215,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,20437,1:13.328.220,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 ED 92 7D F1 7E 70 9B E0 8F 79 40 45 33 89 57… 0,,20441,1:13.329.217,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,20442,1:13.343.222,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20446,1:13.344.219,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,20447,1:13.344.222,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 ED 92 7D F1 7E 70 9B E0 8F 79 40 45 33 89 57… 0,,20451,1:13.345.219,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,20452,1:13.360.224,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 DB A4 5C 1F 9D 26 A3 C5 21 D5 8A 25 59 99 AB… 0,,20456,1:13.361.221,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,20457,1:13.375.226,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20461,1:13.376.223,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,20462,1:13.376.227,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 DB A4 5C 1F 9D 26 A3 C5 21 D5 8A 25 59 99 AB… 0,,20466,1:13.377.223,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,20467,1:13.392.229,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C1 23 1E 4C 27 4D EC E1 8C 3E D5 9E 29 29 97 DD… 0,,20471,1:13.393.226,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,20472,1:13.407.231,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20476,1:13.408.228,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,20477,1:13.408.231,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C1 23 1E 4C 27 4D EC E1 8C 3E D5 9E 29 29 97 DD… 0,,20481,1:13.409.228,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,20482,1:13.424.233,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B 47 8C 4D 58 22 C5 E7 EB F9 54 71 B3 0A 5C 6B… 0,,20486,1:13.425.230,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,20487,1:13.439.235,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20491,1:13.440.232,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,20492,1:13.440.235,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B 47 8C 4D 58 22 C5 E7 EB F9 54 71 B3 0A 5C 6B… 0,,20496,1:13.441.232,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,20497,1:13.456.238,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 39 AB FC 72 F4 51 93 59 D1 6E 46 B7 42 67 BB E7… 0,,20501,1:13.457.235,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,20502,1:13.471.240,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20506,1:13.472.237,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,20507,1:13.472.240,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 39 AB FC 72 F4 51 93 59 D1 6E 46 B7 42 67 BB E7… 0,,20511,1:13.473.237,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,20512,1:13.488.242,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3B 59 06 9F FF A3 0B 67 B3 1E C3 47 F1 26 37 94… 0,,20516,1:13.489.239,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,20517,1:13.503.244,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20521,1:13.504.241,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,20522,1:13.504.244,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3B 59 06 9F FF A3 0B 67 B3 1E C3 47 F1 26 37 94… 0,,20526,1:13.505.241,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,20527,1:13.520.247,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A0 31 7A 6C 5F 37 F1 E5 6D 67 98 02 74 46 52 FE… 0,,20531,1:13.521.243,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,20532,1:13.535.249,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20536,1:13.536.246,16.005.041 ms,,,,,[17 SOF],[Frames: 1793 - 1809] 0,,20537,1:13.552.251,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D3 46 E6 F2 A2 60 38 B1 7C A6 99 4F 81 2D B1 3B… 0,,20541,1:13.553.248,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,20542,1:13.567.253,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20546,1:13.568.250,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,20547,1:13.568.253,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D3 46 E6 F2 A2 60 38 B1 7C A6 99 4F 81 2D B1 3B… 0,,20551,1:13.569.250,15.004.916 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,20552,1:13.584.255,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 07 FC 18 32 17 3C DF 42 39 8B DE 10 46 48 B4… 0,,20556,1:13.585.252,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,20557,1:13.599.258,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20561,1:13.600.254,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,20562,1:13.600.258,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 07 FC 18 32 17 3C DF 42 39 8B DE 10 46 48 B4… 0,,20566,1:13.601.255,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,20567,1:13.616.260,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 97 8A 86 DC ED 40 03 DE A4 81 F9 77 E9 E2 1E 9B… 0,,20571,1:13.617.257,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,20572,1:13.631.262,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20576,1:13.632.259,16.005.041 ms,,,,,[17 SOF],[Frames: 1889 - 1905] 0,,20577,1:13.648.264,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F 9D 62 3A 92 A7 E4 8D 2C 6B 45 E7 28 69 D9 D9… 0,,20581,1:13.649.261,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,20582,1:13.663.267,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20586,1:13.664.263,2.833 us,,,,,[1 SOF],[Frame: 1921] 0,,20587,1:13.664.267,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F 9D 62 3A 92 A7 E4 8D 2C 6B 45 E7 28 69 D9 D9… 0,,20591,1:13.665.263,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,20592,1:13.680.269,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 4B 95 5F 50 05 F3 A2 83 C9 DF 32 17 63 FB 32… 0,,20596,1:13.681.266,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,20597,1:13.695.271,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20601,1:13.696.268,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,20602,1:13.696.271,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 4B 95 5F 50 05 F3 A2 83 C9 DF 32 17 63 FB 32… 0,,20606,1:13.697.268,15.004.916 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,20607,1:13.712.273,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0E 33 4E 28 7A 72 3F 79 29 E6 63 1C 34 F8 6F 57… 0,,20611,1:13.713.270,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,20612,1:13.727.275,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20616,1:13.728.272,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,20617,1:13.728.276,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0E 33 4E 28 7A 72 3F 79 29 E6 63 1C 34 F8 6F 57… 0,,20621,1:13.729.272,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,20622,1:13.744.278,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A 88 63 FF 53 9F 63 84 B2 26 91 71 79 6F 18 72… 0,,20626,1:13.745.275,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,20627,1:13.759.280,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20631,1:13.760.277,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,20632,1:13.760.280,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A 88 63 FF 53 9F 63 84 B2 26 91 71 79 6F 18 72… 0,,20636,1:13.761.277,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,20637,1:13.776.282,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE 15 BF 71 4C 95 BC 45 26 D8 7C 80 0D FA 78 F8… 0,,20641,1:13.777.279,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,20642,1:13.791.284,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20646,1:13.792.281,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,20647,1:13.792.284,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE 15 BF 71 4C 95 BC 45 26 D8 7C 80 0D FA 78 F8… 0,,20651,1:13.793.281,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,20652,1:13.808.287,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 32 2F 65 B1 C0 97 A0 DA E4 B9 04 3B 47 47 22… 0,,20656,1:13.809.283,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,20657,1:13.823.289,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20661,1:13.824.286,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,20662,1:13.824.289,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 32 2F 65 B1 C0 97 A0 DA E4 B9 04 3B 47 47 22… 0,,20666,1:13.825.286,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,20667,1:13.840.291,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 88 16 89 95 61 AB 9D EA 12 A0 10 47 9A D1 6A 6D… 0,,20671,1:13.841.288,14.004.750 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,20672,1:13.855.293,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20676,1:13.856.290,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,20677,1:13.856.293,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 88 16 89 95 61 AB 9D EA 12 A0 10 47 9A D1 6A 6D… 0,,20681,1:13.857.290,15.004.916 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,20682,1:13.872.295,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 60 33 AB 7F A0 97 8C D8 D9 E9 D7 61 56 B2 E9 4C… 0,,20686,1:13.873.292,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,20687,1:13.887.298,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20691,1:13.888.294,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,20692,1:13.888.298,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 33 AB 7F A0 97 8C D8 D9 E9 D7 61 56 B2 E9 4C… 0,,20696,1:13.889.295,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,20697,1:13.904.300,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D F8 B7 CC D0 E6 01 05 81 19 08 71 AE 75 57 77… 0,,20701,1:13.905.297,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,20702,1:13.919.302,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20706,1:13.920.299,2.833 us,,,,,[1 SOF],[Frame: 129] 0,,20707,1:13.920.302,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D F8 B7 CC D0 E6 01 05 81 19 08 71 AE 75 57 77… 0,,20711,1:13.921.299,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,20712,1:13.936.304,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D E2 10 C3 04 10 84 3B 8D 36 E4 AD 21 E2 97 BB… 0,,20716,1:13.937.301,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,20717,1:13.951.306,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20721,1:13.952.303,2.833 us,,,,,[1 SOF],[Frame: 161] 0,,20722,1:13.952.307,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D E2 10 C3 04 10 84 3B 8D 36 E4 AD 21 E2 97 BB… 0,,20726,1:13.953.303,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,20727,1:13.968.309,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A DF EE 09 F5 F4 EB AE A2 80 89 D4 22 89 68 71… 0,,20731,1:13.969.306,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,20732,1:13.983.311,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20736,1:13.984.308,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,20737,1:13.984.311,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A DF EE 09 F5 F4 EB AE A2 80 89 D4 22 89 68 71… 0,,20741,1:13.985.308,15.004.916 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,20742,1:14.000.313,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 39 EC FC 01 A8 42 4A 30 BF C0 1C D4 71 1B FB 17… 0,,20746,1:14.001.310,14.004.750 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,20747,1:14.015.315,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20751,1:14.016.312,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,20752,1:14.016.315,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 39 EC FC 01 A8 42 4A 30 BF C0 1C D4 71 1B FB 17… 0,,20756,1:14.017.312,15.004.916 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,20757,1:14.032.318,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF B9 8B 71 DB F4 9C 48 B8 3F 46 F2 2A 3A 07 91… 0,,20761,1:14.033.315,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,20762,1:14.047.320,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20766,1:14.048.317,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,20767,1:14.048.320,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF B9 8B 71 DB F4 9C 48 B8 3F 46 F2 2A 3A 07 91… 0,,20771,1:14.049.317,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,20772,1:14.064.322,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 47 44 F8 65 20 F3 4B 67 5B F4 EE 63 DB DD C9… 0,,20776,1:14.065.319,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,20777,1:14.079.324,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20781,1:14.080.321,2.833 us,,,,,[1 SOF],[Frame: 289] 0,,20782,1:14.080.324,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 47 44 F8 65 20 F3 4B 67 5B F4 EE 63 DB DD C9… 0,,20786,1:14.081.321,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,20787,1:14.096.327,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 E0 BD A5 59 01 65 0A 31 C8 C6 D5 7D 69 68 48… 0,,20791,1:14.097.323,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,20792,1:14.111.329,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20796,1:14.112.325,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,20797,1:14.112.329,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 E0 BD A5 59 01 65 0A 31 C8 C6 D5 7D 69 68 48… 0,,20801,1:14.113.326,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,20802,1:14.128.331,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 D4 88 FA 6D D4 DC 95 E1 26 88 1F 73 A0 CD 9A… 0,,20806,1:14.129.328,14.004.750 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,20807,1:14.143.333,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20811,1:14.144.330,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,20812,1:14.144.333,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 D4 88 FA 6D D4 DC 95 E1 26 88 1F 73 A0 CD 9A… 0,,20816,1:14.145.330,15.004.916 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,20817,1:14.160.335,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 3F 16 1E 42 B3 54 61 9F B5 DC 2A A5 6A 05 DE… 0,,20821,1:14.161.332,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,20822,1:14.175.337,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20826,1:14.176.334,16.005.041 ms,,,,,[17 SOF],[Frames: 385 - 401] 0,,20827,1:14.192.340,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D BF CC 9C A0 9A 9A B0 D9 EC 6E 90 D8 B5 E4 5D… 0,,20831,1:14.193.337,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,20832,1:14.207.342,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20836,1:14.208.339,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,20837,1:14.208.342,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D BF CC 9C A0 9A 9A B0 D9 EC 6E 90 D8 B5 E4 5D… 0,,20841,1:14.209.339,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,20842,1:14.224.344,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 91 A2 A1 7F 6C F5 23 E1 4F 2A F8 42 74 A0 32… 0,,20846,1:14.225.341,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,20847,1:14.239.346,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20851,1:14.240.343,2.833 us,,,,,[1 SOF],[Frame: 449] 0,,20852,1:14.240.347,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 91 A2 A1 7F 6C F5 23 E1 4F 2A F8 42 74 A0 32… 0,,20856,1:14.241.343,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,20857,1:14.256.349,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 DB E5 E8 DA FA 2D 63 3E 41 4A 34 C2 F7 EA C0… 0,,20861,1:14.257.346,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,20862,1:14.271.351,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20866,1:14.272.348,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,20867,1:14.272.351,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 DB E5 E8 DA FA 2D 63 3E 41 4A 34 C2 F7 EA C0… 0,,20871,1:14.273.348,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,20872,1:14.288.353,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC 54 8B C2 FB 87 39 26 4D 99 29 22 7E C6 42 F7… 0,,20876,1:14.289.350,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,20877,1:14.303.355,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20881,1:14.304.352,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,20882,1:14.304.355,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC 54 8B C2 FB 87 39 26 4D 99 29 22 7E C6 42 F7… 0,,20886,1:14.305.352,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,20887,1:14.320.358,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6E 8E F9 6C D1 BF 3A 65 30 5E 71 0E 49 A9 5D 31… 0,,20891,1:14.321.355,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,20892,1:14.335.360,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20896,1:14.336.357,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,20897,1:14.336.360,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6E 8E F9 6C D1 BF 3A 65 30 5E 71 0E 49 A9 5D 31… 0,,20901,1:14.337.357,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,20902,1:14.352.362,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 39 73 BD A6 B8 74 2A 31 B2 2E 37 81 2D 8F C4 95… 0,,20906,1:14.353.359,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,20907,1:14.367.364,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20911,1:14.368.361,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,20912,1:14.368.364,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 39 73 BD A6 B8 74 2A 31 B2 2E 37 81 2D 8F C4 95… 0,,20916,1:14.369.361,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,20917,1:14.384.367,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 41 61 82 54 97 64 0E A3 93 30 38 39 02 CF 4D… 0,,20921,1:14.385.363,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,20922,1:14.399.369,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20926,1:14.400.365,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,20927,1:14.400.369,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 41 61 82 54 97 64 0E A3 93 30 38 39 02 CF 4D… 0,,20931,1:14.401.366,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,20932,1:14.416.371,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 4D 91 D4 0F 75 94 95 D4 E2 72 6D BD CF DE 1F… 0,,20936,1:14.417.368,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,20937,1:14.431.373,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20941,1:14.432.370,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,20942,1:14.432.373,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 4D 91 D4 0F 75 94 95 D4 E2 72 6D BD CF DE 1F… 0,,20946,1:14.433.370,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,20947,1:14.448.375,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C 2D F7 42 1D F4 AD FB 0A 13 DE 16 81 EB EE 3F… 0,,20951,1:14.449.372,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,20952,1:14.463.377,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20956,1:14.464.374,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,20957,1:14.464.378,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C 2D F7 42 1D F4 AD FB 0A 13 DE 16 81 EB EE 3F… 0,,20961,1:14.465.374,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,20962,1:14.480.380,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 94 A4 8D E6 C8 EB FF FC 90 B5 E7 01 A7 17 E2… 0,,20966,1:14.481.377,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,20967,1:14.495.382,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20971,1:14.496.379,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,20972,1:14.496.382,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 94 A4 8D E6 C8 EB FF FC 90 B5 E7 01 A7 17 E2… 0,,20976,1:14.497.379,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,20977,1:14.512.384,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 06 7F 0A 8F B1 B8 55 AC F1 04 AE C8 70 33 37… 0,,20981,1:14.513.381,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,20982,1:14.527.386,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20986,1:14.528.383,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,20987,1:14.528.386,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 06 7F 0A 8F B1 B8 55 AC F1 04 AE C8 70 33 37… 0,,20991,1:14.529.383,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,20992,1:14.544.389,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D1 43 C7 1F 09 71 BD 90 01 01 A6 35 62 E0 FD 53… 0,,20996,1:14.545.386,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,20997,1:14.559.391,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21001,1:14.560.388,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,21002,1:14.560.391,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D1 43 C7 1F 09 71 BD 90 01 01 A6 35 62 E0 FD 53… 0,,21006,1:14.561.388,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,21007,1:14.576.393,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD E9 BA 67 6E CC 10 27 C7 37 F0 B4 FA 89 97 3B… 0,,21011,1:14.577.390,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,21012,1:14.591.395,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21016,1:14.592.392,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,21017,1:14.592.395,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD E9 BA 67 6E CC 10 27 C7 37 F0 B4 FA 89 97 3B… 0,,21021,1:14.593.392,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,21022,1:14.608.398,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 8A 13 25 18 69 82 95 8F 21 5E 7E C3 B6 10 FD… 0,,21026,1:14.609.394,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,21027,1:14.623.400,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21031,1:14.624.397,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,21032,1:14.624.400,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 8A 13 25 18 69 82 95 8F 21 5E 7E C3 B6 10 FD… 0,,21036,1:14.625.397,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,21037,1:14.640.402,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 30 E5 33 EB F7 A9 7F CC 16 33 87 50 CE 98 8C 10… 0,,21041,1:14.641.399,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,21042,1:14.655.404,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21046,1:14.656.401,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,21047,1:14.656.404,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 30 E5 33 EB F7 A9 7F CC 16 33 87 50 CE 98 8C 10… 0,,21051,1:14.657.401,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,21052,1:14.672.406,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D F3 87 0D F4 E7 9F 63 3E D0 88 D1 B1 A9 63 24… 0,,21056,1:14.673.403,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,21057,1:14.687.409,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21061,1:14.688.405,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,21062,1:14.688.409,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D F3 87 0D F4 E7 9F 63 3E D0 88 D1 B1 A9 63 24… 0,,21066,1:14.689.406,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,21067,1:14.704.411,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 45 E9 1D 4B 3D 33 1F A0 61 65 20 83 82 EB B8… 0,,21071,1:14.705.408,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,21072,1:14.719.413,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21076,1:14.720.410,2.833 us,,,,,[1 SOF],[Frame: 929] 0,,21077,1:14.720.413,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 45 E9 1D 4B 3D 33 1F A0 61 65 20 83 82 EB B8… 0,,21081,1:14.721.410,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,21082,1:14.736.415,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 E6 C0 28 7C 44 21 7C EE CC E9 08 2A C1 BC 0D… 0,,21086,1:14.737.412,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,21087,1:14.751.417,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21091,1:14.752.414,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,21092,1:14.752.418,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 E6 C0 28 7C 44 21 7C EE CC E9 08 2A C1 BC 0D… 0,,21096,1:14.753.414,15.004.916 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,21097,1:14.768.420,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C5 66 20 1A 85 55 45 0C D1 4F 9A 78 12 AA 7A 62… 0,,21101,1:14.769.417,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,21102,1:14.783.422,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21106,1:14.784.419,16.005.125 ms,,,,,[17 SOF],[Frames: 993 - 1009] 0,,21107,1:14.800.424,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 CC 29 A8 FA A0 81 CF 9A 91 1F B4 51 C1 A4 D4… 0,,21111,1:14.801.421,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,21112,1:14.815.426,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21116,1:14.816.423,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,21117,1:14.816.426,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 CC 29 A8 FA A0 81 CF 9A 91 1F B4 51 C1 A4 D4… 0,,21121,1:14.817.423,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,21122,1:14.832.429,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC F5 07 17 4E 6A 6D D9 98 9B C6 79 FB 7F F1 78… 0,,21126,1:14.833.426,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,21127,1:14.847.431,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21131,1:14.848.428,2.833 us,,,,,[1 SOF],[Frame: 1057] 0,,21132,1:14.848.431,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC F5 07 17 4E 6A 6D D9 98 9B C6 79 FB 7F F1 78… 0,,21136,1:14.849.428,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,21137,1:14.864.433,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6F C4 E6 66 07 55 80 8B A7 A8 CC 97 C9 83 56 53… 0,,21141,1:14.865.430,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,21142,1:14.879.435,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21146,1:14.880.432,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,21147,1:14.880.435,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6F C4 E6 66 07 55 80 8B A7 A8 CC 97 C9 83 56 53… 0,,21151,1:14.881.432,15.004.916 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,21152,1:14.896.438,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A4 C9 7C B0 EC FE 72 4A 36 35 F7 23 85 39 A4 79… 0,,21156,1:14.897.434,14.004.750 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,21157,1:14.911.440,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21161,1:14.912.437,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,21162,1:14.912.440,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A4 C9 7C B0 EC FE 72 4A 36 35 F7 23 85 39 A4 79… 0,,21166,1:14.913.437,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,21167,1:14.928.442,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 78 0C A9 A4 8C 95 30 CE 57 23 BE 4B AC FF 74… 0,,21171,1:14.929.439,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,21172,1:14.943.444,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21176,1:14.944.441,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,21177,1:14.944.444,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 78 0C A9 A4 8C 95 30 CE 57 23 BE 4B AC FF 74… 0,,21181,1:14.945.441,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,21182,1:14.960.446,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A EA 31 40 55 FF A6 C5 FA 22 37 69 EC 3F 3D E3… 0,,21186,1:14.961.443,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,21187,1:14.975.449,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21191,1:14.976.445,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,21192,1:14.976.449,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A EA 31 40 55 FF A6 C5 FA 22 37 69 EC 3F 3D E3… 0,,21196,1:14.977.446,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,21197,1:14.992.451,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B6 9C 6C EE D4 98 03 CE 55 77 13 95 DC 31 1C C4… 0,,21201,1:14.993.448,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,21202,1:15.007.453,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21206,1:15.008.450,2.833 us,,,,,[1 SOF],[Frame: 1217] 0,,21207,1:15.008.453,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B6 9C 6C EE D4 98 03 CE 55 77 13 95 DC 31 1C C4… 0,,21211,1:15.009.450,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,21212,1:15.024.455,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF 4E 6D BC 3E 2B B9 51 8F 9A B9 EB E4 20 DD 83… 0,,21216,1:15.025.452,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,21217,1:15.039.457,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21221,1:15.040.454,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,21222,1:15.040.458,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF 4E 6D BC 3E 2B B9 51 8F 9A B9 EB E4 20 DD 83… 0,,21226,1:15.041.454,15.004.916 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,21227,1:15.056.460,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A E7 8F BA D5 27 AD B1 41 F0 E3 73 6D 18 7C 17… 0,,21231,1:15.057.457,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,21232,1:15.071.462,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21236,1:15.072.459,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,21237,1:15.072.462,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A E7 8F BA D5 27 AD B1 41 F0 E3 73 6D 18 7C 17… 0,,21241,1:15.073.459,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,21242,1:15.088.464,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 69 5A CF EE 0E AA 62 40 09 2A 1C EC 94 D1 A4 2D… 0,,21246,1:15.089.461,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,21247,1:15.103.466,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21251,1:15.104.463,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,21252,1:15.104.466,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 69 5A CF EE 0E AA 62 40 09 2A 1C EC 94 D1 A4 2D… 0,,21256,1:15.105.463,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,21257,1:15.120.469,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A2 7C 12 43 0A C2 EB 80 4A ED 40 D8 6C 39 95 34… 0,,21261,1:15.121.466,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,21262,1:15.135.471,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21266,1:15.136.468,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,21267,1:15.136.471,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A2 7C 12 43 0A C2 EB 80 4A ED 40 D8 6C 39 95 34… 0,,21271,1:15.137.468,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,21272,1:15.152.473,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4B E4 EB 24 2A 68 D3 6F BD 53 5F 21 02 6C 9B 8F… 0,,21276,1:15.153.470,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,21277,1:15.167.475,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21281,1:15.168.472,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,21282,1:15.168.475,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4B E4 EB 24 2A 68 D3 6F BD 53 5F 21 02 6C 9B 8F… 0,,21286,1:15.169.472,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,21287,1:15.184.478,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B FF F4 08 0D C5 18 F3 CA CD 83 0F 1C 8D E7 C3… 0,,21291,1:15.185.474,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,21292,1:15.199.480,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21296,1:15.200.477,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,21297,1:15.200.480,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B FF F4 08 0D C5 18 F3 CA CD 83 0F 1C 8D E7 C3… 0,,21301,1:15.201.477,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,21302,1:15.216.482,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D CA E9 F3 D2 F1 A1 99 AA C2 97 C8 55 22 D4 AC… 0,,21306,1:15.217.479,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,21307,1:15.231.484,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21311,1:15.232.481,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,21312,1:15.232.484,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D CA E9 F3 D2 F1 A1 99 AA C2 97 C8 55 22 D4 AC… 0,,21316,1:15.233.481,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,21317,1:15.248.486,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 90 FF 08 CA F0 AD E5 5A A9 48 26 BB 82 22 81… 0,,21321,1:15.249.483,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,21322,1:15.263.489,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21326,1:15.264.485,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,21327,1:15.264.489,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 90 FF 08 CA F0 AD E5 5A A9 48 26 BB 82 22 81… 0,,21331,1:15.265.486,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,21332,1:15.280.491,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A 0F 28 5F 1D 84 82 BB 4F E8 5F 32 A9 E0 05 71… 0,,21336,1:15.281.488,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,21337,1:15.295.493,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21341,1:15.296.490,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,21342,1:15.296.493,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A 0F 28 5F 1D 84 82 BB 4F E8 5F 32 A9 E0 05 71… 0,,21346,1:15.297.490,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,21347,1:15.312.495,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4F D2 0D B9 0C 7B 01 D3 C2 74 18 AB E8 61 22 B3… 0,,21351,1:15.313.492,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,21352,1:15.327.497,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21356,1:15.328.494,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,21357,1:15.328.498,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4F D2 0D B9 0C 7B 01 D3 C2 74 18 AB E8 61 22 B3… 0,,21361,1:15.329.494,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,21362,1:15.344.500,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C FC 8C 98 C5 D9 FC 19 60 EA FF 74 8C A8 73 A6… 0,,21366,1:15.345.497,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,21367,1:15.359.502,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21371,1:15.360.499,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,21372,1:15.360.502,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C FC 8C 98 C5 D9 FC 19 60 EA FF 74 8C A8 73 A6… 0,,21376,1:15.361.499,15.004.916 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,21377,1:15.376.504,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 94 D0 29 8F A8 1F 36 71 D2 09 29 9B EC 9E 36… 0,,21381,1:15.377.501,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,21382,1:15.391.506,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21386,1:15.392.503,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,21387,1:15.392.506,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 94 D0 29 8F A8 1F 36 71 D2 09 29 9B EC 9E 36… 0,,21391,1:15.393.503,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,21392,1:15.408.509,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7E 6F 7E 4D 0A 19 3E 84 F2 F4 23 D6 99 C8 5F 7C… 0,,21396,1:15.409.506,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,21397,1:15.423.511,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21401,1:15.424.508,16.005.041 ms,,,,,[17 SOF],[Frames: 1633 - 1649] 0,,21402,1:15.440.513,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 99 79 FA 26 25 B1 E2 FE 60 5C 3F E4 3E 8C C9… 0,,21406,1:15.441.510,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,21407,1:15.455.515,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21411,1:15.456.512,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,21412,1:15.456.515,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 99 79 FA 26 25 B1 E2 FE 60 5C 3F E4 3E 8C C9… 0,,21416,1:15.457.512,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,21417,1:15.472.518,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 87 DA 4D F9 23 FD BD 60 20 43 FA AD A7 4B 13… 0,,21421,1:15.473.514,14.004.750 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,21422,1:15.487.520,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21426,1:15.488.517,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,21427,1:15.488.520,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 87 DA 4D F9 23 FD BD 60 20 43 FA AD A7 4B 13… 0,,21431,1:15.489.517,15.004.916 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,21432,1:15.504.522,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 4F 07 2A 20 D2 5D C4 D9 1B 24 9A 6F D5 DE 94… 0,,21436,1:15.505.519,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,21437,1:15.519.524,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21441,1:15.520.521,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,21442,1:15.520.524,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC 4F 07 2A 20 D2 5D C4 D9 1B 24 9A 6F D5 DE 94… 0,,21446,1:15.521.521,15.004.916 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,21447,1:15.536.526,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E7 99 96 87 38 8E 44 E3 7C 26 BA 0A 12 46 CA 82… 0,,21451,1:15.537.523,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,21452,1:15.551.528,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21456,1:15.552.525,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,21457,1:15.552.529,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E7 99 96 87 38 8E 44 E3 7C 26 BA 0A 12 46 CA 82… 0,,21461,1:15.553.526,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,21462,1:15.568.531,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 24 77 1E 0E 4F 2E D3 35 B5 81 18 B8 44 1D 88 23… 0,,21466,1:15.569.528,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,21467,1:15.583.533,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21471,1:15.584.530,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,21472,1:15.584.533,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 24 77 1E 0E 4F 2E D3 35 B5 81 18 B8 44 1D 88 23… 0,,21476,1:15.585.530,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,21477,1:15.600.535,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC 18 AF F9 3F 69 90 47 94 7A AA D8 BF 1A B6 0F… 0,,21481,1:15.601.532,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,21482,1:15.615.537,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21486,1:15.616.534,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,21487,1:15.616.538,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC 18 AF F9 3F 69 90 47 94 7A AA D8 BF 1A B6 0F… 0,,21491,1:15.617.534,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,21492,1:15.632.540,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 E8 0A C9 05 FA 44 20 9C BC 21 91 41 36 32 57… 0,,21496,1:15.633.537,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,21497,1:15.647.542,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21501,1:15.648.539,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,21502,1:15.648.542,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 E8 0A C9 05 FA 44 20 9C BC 21 91 41 36 32 57… 0,,21506,1:15.649.539,15.004.916 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,21507,1:15.664.544,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 E8 93 28 C8 9E 42 FC 8A 63 75 72 7C 20 7A E2… 0,,21511,1:15.665.541,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,21512,1:15.679.546,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21516,1:15.680.543,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,21517,1:15.680.546,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 E8 93 28 C8 9E 42 FC 8A 63 75 72 7C 20 7A E2… 0,,21521,1:15.681.543,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,21522,1:15.696.549,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 47 0C E3 8B C1 8B EB 8D 1B F5 7E C3 77 42 94… 0,,21526,1:15.697.546,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,21527,1:15.711.551,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21531,1:15.712.548,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,21532,1:15.712.551,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 47 0C E3 8B C1 8B EB 8D 1B F5 7E C3 77 42 94… 0,,21536,1:15.713.548,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,21537,1:15.728.553,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 83 EC 17 C7 F4 D3 28 FB 8D EA 13 18 FC BE 4B C9… 0,,21541,1:15.729.550,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,21542,1:15.743.555,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21546,1:15.744.552,2.833 us,,,,,[1 SOF],[Frame: 1953] 0,,21547,1:15.744.555,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 83 EC 17 C7 F4 D3 28 FB 8D EA 13 18 FC BE 4B C9… 0,,21551,1:15.745.552,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,21552,1:15.760.558,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 45 A9 2A FB 11 B6 59 2B 60 AC B7 9A 05 0B 6B… 0,,21556,1:15.761.554,14.004.750 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,21557,1:15.775.560,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21561,1:15.776.556,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,21562,1:15.776.560,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E3 45 A9 2A FB 11 B6 59 2B 60 AC B7 9A 05 0B 6B… 0,,21566,1:15.777.557,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,21567,1:15.792.562,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 DB 28 70 76 41 D7 4F 9C 59 99 7B 75 3E 25 AC… 0,,21571,1:15.793.559,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,21572,1:15.807.564,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21576,1:15.808.561,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,21577,1:15.808.564,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 DB 28 70 76 41 D7 4F 9C 59 99 7B 75 3E 25 AC… 0,,21581,1:15.809.561,15.005.000 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,21582,1:15.824.566,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 61 8F D9 89 25 6C 31 42 7E 34 92 B6 EF 24 24… 0,,21586,1:15.825.563,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,21587,1:15.839.568,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21591,1:15.840.565,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,21592,1:15.840.569,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 61 8F D9 89 25 6C 31 42 7E 34 92 B6 EF 24 24… 0,,21596,1:15.841.566,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,21597,1:15.856.571,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 53 28 AC FB 39 17 19 45 DD 5B 3D C2 9A D6 86 99… 0,,21601,1:15.857.568,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,21602,1:15.871.573,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21606,1:15.872.570,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,21607,1:15.872.573,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 53 28 AC FB 39 17 19 45 DD 5B 3D C2 9A D6 86 99… 0,,21611,1:15.873.570,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,21612,1:15.888.575,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 17 EA 04 90 1F A4 55 E9 AB F5 2D 34 73 79 42… 0,,21616,1:15.889.572,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,21617,1:15.903.577,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21621,1:15.904.574,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,21622,1:15.904.577,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 17 EA 04 90 1F A4 55 E9 AB F5 2D 34 73 79 42… 0,,21626,1:15.905.574,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,21627,1:15.920.580,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E F3 AE 8F A7 67 2D F6 B2 FC 29 D5 8D 3F 97 97… 0,,21631,1:15.921.577,14.004.750 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,21632,1:15.935.582,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21636,1:15.936.579,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,21637,1:15.936.582,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E F3 AE 8F A7 67 2D F6 B2 FC 29 D5 8D 3F 97 97… 0,,21641,1:15.937.579,15.004.916 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,21642,1:15.952.584,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 58 55 F6 2E 37 4F C6 9B 87 03 97 AC DD BB 40… 0,,21646,1:15.953.581,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,21647,1:15.967.586,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21651,1:15.968.583,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,21652,1:15.968.586,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 58 55 F6 2E 37 4F C6 9B 87 03 97 AC DD BB 40… 0,,21656,1:15.969.583,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,21657,1:15.984.589,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F4 3A 34 E1 66 90 2A BA 8F B3 B8 CD EA 03 44 42… 0,,21661,1:15.985.585,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,21662,1:15.999.591,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21666,1:16.000.588,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,21667,1:16.000.591,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F4 3A 34 E1 66 90 2A BA 8F B3 B8 CD EA 03 44 42… 0,,21671,1:16.001.588,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,21672,1:16.016.593,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD AA 61 68 B4 EE 54 6A 7C 51 11 99 AB 6A F5 55… 0,,21676,1:16.017.590,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,21677,1:16.031.595,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21681,1:16.032.592,16.005.041 ms,,,,,[17 SOF],[Frames: 193 - 209] 0,,21682,1:16.048.597,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A B9 ED 0C 75 DC 6B 35 00 B0 BF 38 82 24 09 2D… 0,,21686,1:16.049.594,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,21687,1:16.063.600,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21691,1:16.064.596,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,21692,1:16.064.600,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A B9 ED 0C 75 DC 6B 35 00 B0 BF 38 82 24 09 2D… 0,,21696,1:16.065.597,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,21697,1:16.080.602,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE EE 0E CF E6 93 70 86 4F E8 6F 74 39 EB 83 75… 0,,21701,1:16.081.599,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,21702,1:16.095.604,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21706,1:16.096.601,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,21707,1:16.096.604,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE EE 0E CF E6 93 70 86 4F E8 6F 74 39 EB 83 75… 0,,21711,1:16.097.601,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,21712,1:16.112.606,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D9 76 1B 8D 0A C5 DA 9E AC 53 57 FB 64 78 F9 DB… 0,,21716,1:16.113.603,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,21717,1:16.127.608,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21721,1:16.128.605,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,21722,1:16.128.609,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D9 76 1B 8D 0A C5 DA 9E AC 53 57 FB 64 78 F9 DB… 0,,21726,1:16.129.605,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,21727,1:16.144.611,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A6 9C 30 01 30 1C C2 B1 0B 70 12 D9 82 99 B6 B6… 0,,21731,1:16.145.608,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,21732,1:16.159.613,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21736,1:16.160.610,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,21737,1:16.160.613,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A6 9C 30 01 30 1C C2 B1 0B 70 12 D9 82 99 B6 B6… 0,,21741,1:16.161.610,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,21742,1:16.176.615,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 A3 00 13 41 FE EC BB 10 DD 44 C2 40 C8 4C 67… 0,,21746,1:16.177.612,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,21747,1:16.191.617,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21751,1:16.192.614,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,21752,1:16.192.617,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 A3 00 13 41 FE EC BB 10 DD 44 C2 40 C8 4C 67… 0,,21756,1:16.193.614,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,21757,1:16.208.620,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 91 50 A7 10 C2 25 7F 09 05 97 B8 B8 19 85 0E 97… 0,,21761,1:16.209.617,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,21762,1:16.223.622,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21766,1:16.224.619,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,21767,1:16.224.622,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 91 50 A7 10 C2 25 7F 09 05 97 B8 B8 19 85 0E 97… 0,,21771,1:16.225.619,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,21772,1:16.240.624,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B2 7C 67 F8 00 C5 1A AB CD D6 D1 0A EE 7C B3 E3… 0,,21776,1:16.241.621,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,21777,1:16.255.626,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21781,1:16.256.623,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,21782,1:16.256.626,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B2 7C 67 F8 00 C5 1A AB CD D6 D1 0A EE 7C B3 E3… 0,,21786,1:16.257.623,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,21787,1:16.272.629,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B0 1B 3B D7 1B AB 9B 3F 5D DF 76 B5 BD E1 E8 7F… 0,,21791,1:16.273.625,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,21792,1:16.287.631,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21796,1:16.288.628,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,21797,1:16.288.631,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B0 1B 3B D7 1B AB 9B 3F 5D DF 76 B5 BD E1 E8 7F… 0,,21801,1:16.289.628,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,21802,1:16.304.633,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 32 05 5B 89 B3 43 64 2F 95 1B CA A1 84 50 A1… 0,,21806,1:16.305.630,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,21807,1:16.319.635,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21811,1:16.320.632,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,21812,1:16.320.635,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 32 05 5B 89 B3 43 64 2F 95 1B CA A1 84 50 A1… 0,,21816,1:16.321.632,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,21817,1:16.336.637,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 BF F5 06 1A CC 68 7F 2D 80 93 87 C3 79 59 5E… 0,,21821,1:16.337.634,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,21822,1:16.351.640,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21826,1:16.352.636,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,21827,1:16.352.640,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 BF F5 06 1A CC 68 7F 2D 80 93 87 C3 79 59 5E… 0,,21831,1:16.353.637,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,21832,1:16.368.642,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 4A 11 8E 57 8F 24 D9 64 C1 31 37 F8 1D F2 73… 0,,21836,1:16.369.639,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,21837,1:16.383.644,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21841,1:16.384.641,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,21842,1:16.384.644,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 4A 11 8E 57 8F 24 D9 64 C1 31 37 F8 1D F2 73… 0,,21846,1:16.385.641,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,21847,1:16.400.646,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 73 70 59 D6 53 2A 68 B0 B4 3F 82 66 D6 F5 72… 0,,21851,1:16.401.643,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,21852,1:16.415.648,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21856,1:16.416.645,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,21857,1:16.416.649,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 73 70 59 D6 53 2A 68 B0 B4 3F 82 66 D6 F5 72… 0,,21861,1:16.417.645,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,21862,1:16.432.651,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 18 4D F0 26 76 F6 E0 24 AC FB 70 74 2C 2B CF… 0,,21866,1:16.433.648,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,21867,1:16.447.653,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21871,1:16.448.650,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,21872,1:16.448.653,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 18 4D F0 26 76 F6 E0 24 AC FB 70 74 2C 2B CF… 0,,21876,1:16.449.650,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,21877,1:16.464.655,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D 34 62 BA 2F AB 8D 0B BF 7B B0 B4 97 1F CF B1… 0,,21881,1:16.465.652,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,21882,1:16.479.657,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21886,1:16.480.654,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,21887,1:16.480.657,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D 34 62 BA 2F AB 8D 0B BF 7B B0 B4 97 1F CF B1… 0,,21891,1:16.481.654,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,21892,1:16.496.660,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A0 5D EE 79 55 AF 08 74 DA C6 61 2C FB B0 2E 4D… 0,,21896,1:16.497.657,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,21897,1:16.511.662,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21901,1:16.512.659,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,21902,1:16.512.662,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A0 5D EE 79 55 AF 08 74 DA C6 61 2C FB B0 2E 4D… 0,,21906,1:16.513.659,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,21907,1:16.528.664,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 95 FA 53 4D FC 22 ED 83 8A 14 8F 1C E8 0F 93… 0,,21911,1:16.529.661,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,21912,1:16.543.666,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21916,1:16.544.663,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,21917,1:16.544.666,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 95 FA 53 4D FC 22 ED 83 8A 14 8F 1C E8 0F 93… 0,,21921,1:16.545.663,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,21922,1:16.560.669,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 A3 9B DE 66 EA 58 17 B2 BB AC F5 F8 DD 50 89… 0,,21926,1:16.561.665,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,21927,1:16.575.671,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21931,1:16.576.668,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,21932,1:16.576.671,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 A3 9B DE 66 EA 58 17 B2 BB AC F5 F8 DD 50 89… 0,,21936,1:16.577.668,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,21937,1:16.592.673,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 F0 A2 3E D5 B0 13 D5 51 BC 1C 62 19 AE 19 75… 0,,21941,1:16.593.670,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,21942,1:16.607.675,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21946,1:16.608.672,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,21947,1:16.608.675,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 F0 A2 3E D5 B0 13 D5 51 BC 1C 62 19 AE 19 75… 0,,21951,1:16.609.672,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,21952,1:16.624.677,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7E A3 7E 11 5F 8A 2B 95 97 97 35 F3 09 BA F3 CD… 0,,21956,1:16.625.674,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,21957,1:16.639.680,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21961,1:16.640.676,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,21962,1:16.640.680,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7E A3 7E 11 5F 8A 2B 95 97 97 35 F3 09 BA F3 CD… 0,,21966,1:16.641.677,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,21967,1:16.656.682,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 85 A8 2E FC BD 67 99 55 5E 4D 20 D9 51 C9 AB… 0,,21971,1:16.657.679,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,21972,1:16.671.684,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21976,1:16.672.681,16.005.041 ms,,,,,[17 SOF],[Frames: 833 - 849] 0,,21977,1:16.688.686,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 79 66 BF CC D2 1D F6 80 17 90 C2 CB C9 50 10… 0,,21981,1:16.689.683,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,21982,1:16.703.688,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21986,1:16.704.685,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,21987,1:16.704.689,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 79 66 BF CC D2 1D F6 80 17 90 C2 CB C9 50 10… 0,,21991,1:16.705.685,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,21992,1:16.720.691,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 20 8A F5 C7 9D DD C3 9E 0C 32 1D 36 D2 1D 8E 64… 0,,21996,1:16.721.688,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,21997,1:16.735.693,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22001,1:16.736.690,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,22002,1:16.736.693,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 20 8A F5 C7 9D DD C3 9E 0C 32 1D 36 D2 1D 8E 64… 0,,22006,1:16.737.690,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,22007,1:16.752.695,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 91 42 67 39 E2 34 47 2B 97 8B DE F3 09 5B 04 20… 0,,22011,1:16.753.692,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,22012,1:16.767.697,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22016,1:16.768.694,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,22017,1:16.768.697,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 91 42 67 39 E2 34 47 2B 97 8B DE F3 09 5B 04 20… 0,,22021,1:16.769.694,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,22022,1:16.784.700,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 85 75 3A EE 9C E6 F6 74 5A 97 86 5D 90 68 48… 0,,22026,1:16.785.697,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,22027,1:16.799.702,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22031,1:16.800.699,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,22032,1:16.800.702,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 85 75 3A EE 9C E6 F6 74 5A 97 86 5D 90 68 48… 0,,22036,1:16.801.699,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,22037,1:16.816.704,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 C2 E7 8E A2 64 38 A7 F4 20 C4 D5 25 CE 4D 2E… 0,,22041,1:16.817.701,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,22042,1:16.831.706,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22046,1:16.832.703,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,22047,1:16.832.706,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 C2 E7 8E A2 64 38 A7 F4 20 C4 D5 25 CE 4D 2E… 0,,22051,1:16.833.703,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,22052,1:16.848.709,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 13 1B 35 37 BB 4E F4 3D 3D 41 14 55 B4 B2 85 25… 0,,22056,1:16.849.705,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,22057,1:16.863.711,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22061,1:16.864.708,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,22062,1:16.864.711,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 13 1B 35 37 BB 4E F4 3D 3D 41 14 55 B4 B2 85 25… 0,,22066,1:16.865.708,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,22067,1:16.880.713,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 88 70 0E 10 4D 9F 00 42 65 9F DB 73 E6 11 D2… 0,,22071,1:16.881.710,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,22072,1:16.895.715,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22076,1:16.896.712,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,22077,1:16.896.715,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 88 70 0E 10 4D 9F 00 42 65 9F DB 73 E6 11 D2… 0,,22081,1:16.897.712,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,22082,1:16.912.717,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 DA 25 D2 EE ED D1 5D B9 79 68 A8 72 D6 A9 91… 0,,22086,1:16.913.714,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,22087,1:16.927.719,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22091,1:16.928.716,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,22092,1:16.928.720,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 DA 25 D2 EE ED D1 5D B9 79 68 A8 72 D6 A9 91… 0,,22096,1:16.929.717,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,22097,1:16.944.722,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A D0 4D F6 3D 8D A3 CD 4E 9A 71 49 B3 0E 57 D7… 0,,22101,1:16.945.719,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,22102,1:16.959.724,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22106,1:16.960.721,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,22107,1:16.960.724,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A D0 4D F6 3D 8D A3 CD 4E 9A 71 49 B3 0E 57 D7… 0,,22111,1:16.961.721,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,22112,1:16.976.726,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 19 47 62 00 6B DF 34 E8 4D 49 E0 7F 70 91 DB 9E… 0,,22116,1:16.977.723,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,22117,1:16.991.728,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22121,1:16.992.725,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,22122,1:16.992.729,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 19 47 62 00 6B DF 34 E8 4D 49 E0 7F 70 91 DB 9E… 0,,22126,1:16.993.725,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,22127,1:17.008.731,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 E4 9F AC 74 89 5A 33 6B DF DA 2F 5B 20 15 41… 0,,22131,1:17.009.728,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,22132,1:17.023.733,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22136,1:17.024.730,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,22137,1:17.024.733,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 E4 9F AC 74 89 5A 33 6B DF DA 2F 5B 20 15 41… 0,,22141,1:17.025.730,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,22142,1:17.040.735,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 4F 23 A0 B6 BD B3 A6 E2 48 CC E3 D1 D2 D3 83… 0,,22146,1:17.041.732,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,22147,1:17.055.737,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22151,1:17.056.734,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,22152,1:17.056.737,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 4F 23 A0 B6 BD B3 A6 E2 48 CC E3 D1 D2 D3 83… 0,,22156,1:17.057.734,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,22157,1:17.072.740,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 72 28 5C FC BB D9 43 6E 9F 41 CA 03 88 04 E8… 0,,22161,1:17.073.737,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,22162,1:17.087.742,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22166,1:17.088.739,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,22167,1:17.088.742,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 72 28 5C FC BB D9 43 6E 9F 41 CA 03 88 04 E8… 0,,22171,1:17.089.739,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,22172,1:17.104.744,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D 68 80 B2 E5 18 7D D1 08 64 A2 53 3D 77 71 AE… 0,,22176,1:17.105.741,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,22177,1:17.119.746,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22181,1:17.120.743,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,22182,1:17.120.746,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D 68 80 B2 E5 18 7D D1 08 64 A2 53 3D 77 71 AE… 0,,22186,1:17.121.743,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,22187,1:17.136.749,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 55 45 F1 6E 59 23 EA 3E 9D E3 79 53 05 E6 B7 B5… 0,,22191,1:17.137.745,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,22192,1:17.151.751,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22196,1:17.152.747,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,22197,1:17.152.751,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 55 45 F1 6E 59 23 EA 3E 9D E3 79 53 05 E6 B7 B5… 0,,22201,1:17.153.748,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,22202,1:17.168.753,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 93 69 F0 07 98 B2 48 4B 55 EF 94 EB CE FD 7F… 0,,22206,1:17.169.750,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,22207,1:17.183.755,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22211,1:17.184.752,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,22212,1:17.184.755,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 93 69 F0 07 98 B2 48 4B 55 EF 94 EB CE FD 7F… 0,,22216,1:17.185.752,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,22217,1:17.200.757,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 52 3A AD 17 5F 96 50 0B D2 02 96 D7 61 2F 77… 0,,22221,1:17.201.754,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,22222,1:17.215.759,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22226,1:17.216.756,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,22227,1:17.216.760,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 52 3A AD 17 5F 96 50 0B D2 02 96 D7 61 2F 77… 0,,22231,1:17.217.757,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,22232,1:17.232.762,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 59 BF 75 3F C3 23 21 74 0D BF 3F DA 3C 6C 55 A4… 0,,22236,1:17.233.759,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,22237,1:17.247.764,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22241,1:17.248.761,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,22242,1:17.248.764,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 59 BF 75 3F C3 23 21 74 0D BF 3F DA 3C 6C 55 A4… 0,,22246,1:17.249.761,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,22247,1:17.264.766,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC 66 64 36 91 B1 68 06 4A CF 14 D2 EE D3 71 9C… 0,,22251,1:17.265.763,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,22252,1:17.279.768,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22256,1:17.280.765,16.005.041 ms,,,,,[17 SOF],[Frames: 1441 - 1457] 0,,22257,1:17.296.771,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 A6 97 12 B0 75 09 AE C2 57 EC 5C A5 D5 78 6E… 0,,22261,1:17.297.768,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,22262,1:17.311.773,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22266,1:17.312.770,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,22267,1:17.312.773,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 A6 97 12 B0 75 09 AE C2 57 EC 5C A5 D5 78 6E… 0,,22271,1:17.313.770,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,22272,1:17.328.775,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 87 EF E1 C3 79 05 1B BC 3A 7E 9F 9A B3 52 01… 0,,22276,1:17.329.772,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,22277,1:17.343.777,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22281,1:17.344.774,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,22282,1:17.344.777,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 87 EF E1 C3 79 05 1B BC 3A 7E 9F 9A B3 52 01… 0,,22286,1:17.345.774,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,22287,1:17.360.780,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E A7 33 B7 87 45 D0 50 16 1F A6 16 77 E5 CF AA… 0,,22291,1:17.361.776,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,22292,1:17.375.782,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22296,1:17.376.779,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,22297,1:17.376.782,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E A7 33 B7 87 45 D0 50 16 1F A6 16 77 E5 CF AA… 0,,22301,1:17.377.779,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,22302,1:17.392.784,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 1D 11 E1 EF B2 B4 E4 C4 D2 D7 56 B4 83 F8 35… 0,,22306,1:17.393.781,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,22307,1:17.407.786,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22311,1:17.408.783,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,22312,1:17.408.786,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 1D 11 E1 EF B2 B4 E4 C4 D2 D7 56 B4 83 F8 35… 0,,22316,1:17.409.783,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,22317,1:17.424.788,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE BA BD 83 3F 5C A2 7A 72 F1 8A 31 24 8B F8 DF… 0,,22321,1:17.425.785,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,22322,1:17.439.791,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22326,1:17.440.787,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,22327,1:17.440.791,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE BA BD 83 3F 5C A2 7A 72 F1 8A 31 24 8B F8 DF… 0,,22331,1:17.441.788,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,22332,1:17.456.793,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 2B 44 73 4D 7C 6D 4F 06 B1 D4 60 C6 71 E4 05… 0,,22336,1:17.457.790,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,22337,1:17.471.795,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22341,1:17.472.792,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,22342,1:17.472.795,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 2B 44 73 4D 7C 6D 4F 06 B1 D4 60 C6 71 E4 05… 0,,22346,1:17.473.792,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,22347,1:17.488.797,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 6D F3 E7 F3 47 B2 E6 44 1B 92 40 10 AE 12 84… 0,,22351,1:17.489.794,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,22352,1:17.503.799,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22356,1:17.504.796,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,22357,1:17.504.800,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 6D F3 E7 F3 47 B2 E6 44 1B 92 40 10 AE 12 84… 0,,22361,1:17.505.796,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,22362,1:17.520.802,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AD E9 AB D2 8E AA 62 E7 41 42 89 3A A0 A7 8F 34… 0,,22366,1:17.521.799,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,22367,1:17.535.804,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22371,1:17.536.801,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,22372,1:17.536.804,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AD E9 AB D2 8E AA 62 E7 41 42 89 3A A0 A7 8F 34… 0,,22376,1:17.537.801,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,22377,1:17.552.806,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 22 85 5E E6 EC 30 C3 BA 8E C6 DB 17 AD F9 95… 0,,22381,1:17.553.803,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,22382,1:17.567.808,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22386,1:17.568.805,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,22387,1:17.568.808,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 22 85 5E E6 EC 30 C3 BA 8E C6 DB 17 AD F9 95… 0,,22391,1:17.569.805,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,22392,1:17.584.811,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 24 49 0D BB 8E 8F D0 90 E0 72 81 E4 97 6C C3 DA… 0,,22396,1:17.585.808,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,22397,1:17.599.813,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22401,1:17.600.810,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,22402,1:17.600.813,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 24 49 0D BB 8E 8F D0 90 E0 72 81 E4 97 6C C3 DA… 0,,22406,1:17.601.810,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,22407,1:17.616.815,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 A2 AC 73 66 1D 33 65 7F A6 6F 16 F9 D4 08 38… 0,,22411,1:17.617.812,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,22412,1:17.631.817,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22416,1:17.632.814,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,22417,1:17.632.817,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 A2 AC 73 66 1D 33 65 7F A6 6F 16 F9 D4 08 38… 0,,22421,1:17.633.814,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,22422,1:17.648.820,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 89 5E DB FC B4 1B AC 8A E4 79 38 95 B2 05 45 32… 0,,22426,1:17.649.816,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,22427,1:17.663.822,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22431,1:17.664.819,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,22432,1:17.664.822,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 89 5E DB FC B4 1B AC 8A E4 79 38 95 B2 05 45 32… 0,,22436,1:17.665.819,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,22437,1:17.680.824,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 E1 F0 F6 D1 E2 F8 1B 03 1F D6 76 43 2E 66 DA… 0,,22441,1:17.681.821,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,22442,1:17.695.826,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22446,1:17.696.823,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,22447,1:17.696.826,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 E1 F0 F6 D1 E2 F8 1B 03 1F D6 76 43 2E 66 DA… 0,,22451,1:17.697.823,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,22452,1:17.712.828,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D CC C4 A4 BB 48 52 5C 46 FE 52 59 2C 9C 5E BB… 0,,22456,1:17.713.825,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,22457,1:17.727.831,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22461,1:17.728.827,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,22462,1:17.728.831,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D CC C4 A4 BB 48 52 5C 46 FE 52 59 2C 9C 5E BB… 0,,22466,1:17.729.828,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,22467,1:17.744.833,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 33 F1 74 FA 9D A3 EF CA 8F B3 59 33 9B 91 BD 67… 0,,22471,1:17.745.830,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,22472,1:17.759.835,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22476,1:17.760.832,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,22477,1:17.760.835,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 33 F1 74 FA 9D A3 EF CA 8F B3 59 33 9B 91 BD 67… 0,,22481,1:17.761.832,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,22482,1:17.776.837,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 29 5A 9E 9B 85 94 8B 05 C0 21 63 49 8B 63 8C… 0,,22486,1:17.777.834,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,22487,1:17.791.839,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22491,1:17.792.836,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,22492,1:17.792.840,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 29 5A 9E 9B 85 94 8B 05 C0 21 63 49 8B 63 8C… 0,,22496,1:17.793.836,15.004.916 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,22497,1:17.808.842,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 13 80 95 DD 59 55 E5 D8 99 AA 17 F5 43 6D 64 4B… 0,,22501,1:17.809.839,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,22502,1:17.823.844,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22506,1:17.824.841,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,22507,1:17.824.844,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 13 80 95 DD 59 55 E5 D8 99 AA 17 F5 43 6D 64 4B… 0,,22511,1:17.825.841,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,22512,1:17.840.846,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E 29 42 0B 6C 60 AC E1 98 BA EF 0C 31 B6 0A 32… 0,,22516,1:17.841.843,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,22517,1:17.855.848,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22521,1:17.856.845,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,22522,1:17.856.849,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E 29 42 0B 6C 60 AC E1 98 BA EF 0C 31 B6 0A 32… 0,,22526,1:17.857.845,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,22527,1:17.872.851,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 49 D6 29 39 4D 9E B7 BA 86 2C 4B 98 20 57 46 D8… 0,,22531,1:17.873.848,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,22532,1:17.887.853,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22536,1:17.888.850,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,22537,1:17.888.853,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 49 D6 29 39 4D 9E B7 BA 86 2C 4B 98 20 57 46 D8… 0,,22541,1:17.889.850,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,22542,1:17.904.855,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AD 1B 7C 84 88 E7 FE E7 C4 2B D3 D6 BA 65 DA 94… 0,,22546,1:17.905.852,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,22547,1:17.919.857,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22551,1:17.920.854,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,22552,1:17.920.857,50.479 us,64 B,,01,01,OUT txn,13 00 16 0F 00 06 0C 00 CA 3D CE FC D4 F7 41 57 01 0D E7 84 BA 65 DA 94… 0,,22556,1:17.921.854,15.004.916 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,22557,1:17.936.860,50.520 us,64 B,,01,01,OUT txn,05 00 16 01 00 02 0C 00 CA 3D CE FC D4 F7 41 57 01 0D E7 84 BA 65 DA 94… 0,,22561,1:17.937.856,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,22562,1:17.951.862,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22566,1:17.952.859,41.008.500 ms,,,,,[42 SOF],[Frames: 65 - 106] 0,,22567,1:17.983.866,4.583 us,,,01,02,[1 IN-NAK], 0,,22568,1:17.994.154,,,,,, / , 0,,22569,1:18.761.750,,,,,, / , 0,,22570,1:18.941.662,,,,,, / , 0,,22571,1:19.007.714,,,,,, / , 0,,22572,1:19.491.928,,,,,, / , 0,,22573,1:19.523.678,,,,,, / , 0,,22574,1:19.524.077,31.007.125 ms,,,,,[32 SOF],[Frames: 1636 - 1667] 0,,22575,1:19.555.084,12.979 us,8 B,,00,00,SETUP txn,80 06 00 01 00 00 40 00 0,,22579,1:19.556.081,2.833 us,,,,,[1 SOF],[Frame: 1668] 0,,22580,1:19.556.084,20.250 us,18 B,,00,00,IN txn,12 01 00 02 00 00 00 40 35 12 21 AB 01 00 01 02 00 01 0,,22584,1:19.557.081,1.002.958 ms,,,,,[2 SOF],[Frames: 1669 - 1670] 0,,22585,1:19.558.085,7.645 us,0 B,,00,00,OUT txn, 0,,22589,1:19.559.081,2.833 us,,,,,[1 SOF],[Frame: 1671] 0,,22590,1:19.559.159,,,,,, / , 0,,22591,1:19.585.387,,,,,, / , 0,,22592,1:19.586.085,31.007.125 ms,,,,,[32 SOF],[Frames: 1698 - 1729] 0,,22593,1:19.617.093,12.979 us,8 B,,00,00,SETUP txn,00 05 01 00 00 00 00 00 0,,22597,1:19.618.090,2.833 us,,,,,[1 SOF],[Frame: 1730] 0,,22598,1:19.618.093,8.250 us,0 B,,00,00,IN txn, 0,,22602,1:19.619.090,29.006.854 ms,,,,,[30 SOF],[Frames: 1731 - 1760] 0,,22603,1:19.648.098,13.000 us,8 B,,01,00,SETUP txn,80 06 00 01 00 00 12 00 0,,22607,1:19.649.094,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,22608,1:19.649.097,20.229 us,18 B,,01,00,IN txn,12 01 00 02 00 00 00 40 35 12 21 AB 01 00 01 02 00 01 0,,22612,1:19.650.094,2.812 us,,,,,[1 SOF],[Frame: 1762] 0,,22613,1:19.650.097,7.666 us,0 B,,01,00,OUT txn, 0,,22617,1:19.651.094,1.002.958 ms,,,,,[2 SOF],[Frames: 1763 - 1764] 0,,22618,1:19.652.098,13.083 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 FF 00 0,,22622,1:19.653.095,2.812 us,,,,,[1 SOF],[Frame: 1765] 0,,22623,1:19.653.098,35.562 us,41 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 09 04 00 00 02 03 00 00 00 09 21 10 01 00 01… 0,,22627,1:19.654.095,1.003.041 ms,,,,,[2 SOF],[Frames: 1766 - 1767] 0,,22628,1:19.655.098,7.666 us,0 B,,01,00,OUT txn, 0,,22632,1:19.656.095,1.002.958 ms,,,,,[2 SOF],[Frames: 1768 - 1769] 0,,22633,1:19.657.099,13.083 us,8 B,,01,00,SETUP txn,80 06 00 03 00 00 FF 00 0,,22637,1:19.658.095,2.812 us,,,,,[1 SOF],[Frame: 1770] 0,,22638,1:19.658.098,10.895 us,4 B,,01,00,IN txn,04 03 09 04 0,,22642,1:19.659.095,1.002.958 ms,,,,,[2 SOF],[Frames: 1771 - 1772] 0,,22643,1:19.660.099,7.666 us,0 B,,01,00,OUT txn, 0,,22647,1:19.661.096,1.002.958 ms,,,,,[2 SOF],[Frames: 1773 - 1774] 0,,22648,1:19.662.099,13.083 us,8 B,,01,00,SETUP txn,80 06 02 03 09 04 FF 00 0,,22652,1:19.663.096,2.833 us,,,,,[1 SOF],[Frame: 1775] 0,,22653,1:19.663.099,28.250 us,30 B,,01,00,IN txn,1E 03 53 00 46 00 43 00 33 00 30 00 20 00 4A 00 6F 00 79 00 73 00 74 00… 0,,22657,1:19.664.096,1.002.958 ms,,,,,[2 SOF],[Frames: 1776 - 1777] 0,,22658,1:19.665.099,7.645 us,0 B,,01,00,OUT txn, 0,,22662,1:19.666.096,1.002.958 ms,,,,,[2 SOF],[Frames: 1778 - 1779] 0,,22663,1:19.667.100,13.000 us,8 B,,01,00,SETUP txn,80 06 00 06 00 00 0A 00 0,,22667,1:19.668.097,2.812 us,,,,,[1 SOF],[Frame: 1780] 0,,22668,1:19.668.100,4.562 us,,,01,00,IN txn (STALL), 0,,22671,1:19.669.097,1.556.218.812 s,,,,,[1557 SOF],[Frames: 1781 - 1289] 0,,22672,1:21.225.316,13.000 us,8 B,,01,00,SETUP txn,80 06 00 01 00 00 12 00 0,,22676,1:21.226.313,2.812 us,,,,,[1 SOF],[Frame: 1290] 0,,22677,1:21.226.316,20.250 us,18 B,,01,00,IN txn,12 01 00 02 00 00 00 40 35 12 21 AB 01 00 01 02 00 01 0,,22681,1:21.227.313,2.812 us,,,,,[1 SOF],[Frame: 1291] 0,,22682,1:21.227.316,7.645 us,0 B,,01,00,OUT txn, 0,,22686,1:21.228.313,1.002.958 ms,,,,,[2 SOF],[Frames: 1292 - 1293] 0,,22687,1:21.229.318,13.000 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 09 00 0,,22691,1:21.230.313,2.812 us,,,,,[1 SOF],[Frame: 1294] 0,,22692,1:21.230.317,14.312 us,9 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 0,,22696,1:21.231.314,2.833 us,,,,,[1 SOF],[Frame: 1295] 0,,22697,1:21.231.317,7.666 us,0 B,,01,00,OUT txn, 0,,22701,1:21.232.314,1.002.958 ms,,,,,[2 SOF],[Frames: 1296 - 1297] 0,,22702,1:21.233.317,12.979 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 29 00 0,,22706,1:21.234.314,2.833 us,,,,,[1 SOF],[Frame: 1298] 0,,22707,1:21.234.317,35.583 us,41 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 09 04 00 00 02 03 00 00 00 09 21 10 01 00 01… 0,,22711,1:21.235.314,2.812 us,,,,,[1 SOF],[Frame: 1299] 0,,22712,1:21.235.317,7.666 us,0 B,,01,00,OUT txn, 0,,22716,1:21.236.314,1.002.958 ms,,,,,[2 SOF],[Frames: 1300 - 1301] 0,,22717,1:21.237.318,13.000 us,8 B,,01,00,SETUP txn,00 09 01 00 00 00 00 00 0,,22721,1:21.238.315,2.895 us,,,,,[1 SOF],[Frame: 1302] 0,,22722,1:21.238.318,8.229 us,0 B,,01,00,IN txn, 0,,22726,1:21.239.315,1.002.958 ms,,,,,[2 SOF],[Frames: 1303 - 1304] 0,,22727,1:21.240.318,13.000 us,8 B,,01,00,SETUP txn,21 0A 00 00 00 00 00 00 0,,22731,1:21.241.315,2.812 us,,,,,[1 SOF],[Frame: 1305] 0,,22732,1:21.241.318,4.583 us,,,01,00,IN txn (STALL), 0,,22735,1:21.242.315,2.003.083 ms,,,,,[3 SOF],[Frames: 1306 - 1308] 0,,22736,1:21.244.319,12.979 us,8 B,,01,00,SETUP txn,81 06 00 22 00 00 A3 00 0,,22740,1:21.245.315,2.812 us,,,,,[1 SOF],[Frame: 1309] 0,,22741,1:21.245.319,51.062 us,64 B,,01,00,IN txn,05 01 09 04 A1 01 A1 02 75 08 95 04 15 00 26 FF 00 35 00 46 FF 00 09 30… 0,,22745,1:21.246.316,2.833 us,,,,,[1 SOF],[Frame: 1310] 0,,22746,1:21.246.319,31.750 us,35 B,,01,00,IN txn,02 06 00 FF 75 01 95 08 25 01 45 01 09 01 81 02 C0 A1 02 75 08 95 08 46… 0,,22750,1:21.247.316,1.002.958 ms,,,,,[2 SOF],[Frames: 1311 - 1312] 0,,22751,1:21.248.321,7.645 us,0 B,,01,00,OUT txn, 0,,22755,1:21.249.316,1.999.280.291 s,,,,,[2000 SOF],[Frames: 1313 - 1264] [Periodic Timeout] 0,,22756,1:21.280.324,1.984.279.979 s,,,01,01,[63 IN-NAK],[Periodic Timeout] 0,,22757,1:23.249.594,1.999.280.291 s,,,,,[2000 SOF],[Frames: 1265 - 1216] [Periodic Timeout] 0,,22758,1:23.296.603,1.984.279.979 s,,,01,01,[63 IN-NAK],[Periodic Timeout] 0,,22759,1:25.249.871,1.999.280.291 s,,,,,[2000 SOF],[Frames: 1217 - 1168] [Periodic Timeout] 0,,22760,1:25.312.883,1.984.279.958 s,,,01,01,[63 IN-NAK],[Periodic Timeout] 0,,22761,1:27.250.149,1.999.280.291 s,,,,,[2000 SOF],[Frames: 1169 - 1120] [Periodic Timeout] 0,,22762,1:27.329.163,1.984.279.979 s,,,01,01,[63 IN-NAK],[Periodic Timeout] 0,,22763,1:29.250.426,1.999.280.291 s,,,,,[2000 SOF],[Frames: 1121 - 1072] [Periodic Timeout] 0,,22764,1:29.345.443,1.984.279.958 s,,,01,01,[63 IN-NAK],[Periodic Timeout] 0,,22765,1:31.250.704,1.798.252.375 s,,,,,[1799 SOF],[Frames: 1073 - 823] 0,,22766,1:31.361.723,1.664.235.541 s,,,01,01,[53 IN-NAK], 0,,22767,1:33.048.954,,,,,,Capture stopped,[Fri 03 Jun 2016 12:01:34 BST] fwupd-1.2.14/plugins/ebitdo/data/update.tdc000066400000000000000000017532571402665037500205760ustar00rootroot00000000000000TPDC?cQW u  .% 1cQW 7  l @`.   `  9!- -`-  `8 @8`@ 8 @@q@@8m]`@ 8q8x8q`\8J qA$@q8q>`68̏8ֵq ' YxAa@vH@H@ `   @@3K@ @F@@`6@@`@` J À@;@ݔCB1@7JEJ J `J@ A-FU@BpB@B i @' `AOBAR `CEa¨R J 8yyaR @@[=G RU )@@ `@# /@ Kc.@PW S$A\AT>T `T\i\B=@BA~`8@B i\b1@AA\XE( 5E\@z{a\ sya"d@x@ !\"@"@BKBBJ >@JBp@U `  Jb@AAJ#`E53@@`;J J 8||E @=D=`=$8&`v` 8CZ`qi8aE@=|=`v!E@谀@g%BEJTJ  a ` B@x@A B@B E?k>`VM@aR @=m=#MFL@{l@ U!@B! B J B" "@B@Ua@ i@x@AAJFE&?aJPEJ@7aJ$ @p=#U`=$WE@@3b% @ @!@@@`@LHAR&J]J J `J @D-GH'`B$ @x@A AR( A@܀@R@aR) @=8= H*@T݀@H@ `@!H+ qT `T\i\, B@BHB@x@AA\- @EVbq \a\K^=#/ |@˘@~!\"0B7BBJ1 B@Bo@a@x@AAJ2`CE!SWaJaJ3 9@ptX`=J! 4@@a)"A5JHJ `J6 B@Bt dR@x@A AR7 .@E/ʀ aR8 ;R7р=l.n@nˀ@M"$;: /@CK )". .E !"!@  As@; = ; >@BN @x@A-As< @EYbZas= ;CZ`=$.@lB@!s ?BAB@BZ@x@AAJ/`EaJB Bm B@B@x@AAJn`CEJaJo 9 f$J#p <@i@!.# _4AqJյJ qr +@B@x@A ARs`CEpgaaRt 9q= cAu@T@ Z ,v B@B@JK[@x@Aw @A;,ha$APax 2k`=#`bVy@@!V |NzJ J  V{ +@ Bq! BS@x@A AR| .@EՀVaR} ; ՠ=ڀ= V(&R&Q~ < 8@׀@6N PT|րT!/UBQ@x@AA\Eclbq\a\ =+=&Q@@ QBBQ B@JB9@x@AAJ`CELmaJQaJ@JX n`=J&QC@ @ d @x@A AS @E}obh@@aS @=E=#@@A~S& C +B B@B@x@AAJ`CE;paJaJ 9W=J#@@$)AJ/J  +@RB@x@A AR`CEqaRbr aR 9R!=R#,"@Y@M@k  B@sB,-@x@A-As @Emrasdmsas \ =do=s#s$ <@n@ {%e @ B<B B@B@x@AAJE)saJTj:{ = =J$@@ @{'%A @J Z +@B@x@A AR`CE,taR @  AR 9  =" <@h@ "$R B J B@JB )@x@AAJ @E[uJJ" ; {^="@\@! =  A SJ  +@ B@x@A AR`CE9vaR Y!R 9j= R@ ~Z0 B@B@B@x@A @AҀP! ; =gwaA /%?$ <@Ϗ@  "6J;J$V B@B5@x@A AR`CEIxaVt!R 9O=R$R7@ K@ J$ 7K  ;G &u@  fC +iJcwkBSS<@x@A%AkETyakAk =€=k&`@ @ s!k @ `Bw B B@JB@x@AAJ`CEa|zaJv@ } #AJ 9Jp`=1@@!~R$ @^@!*ϰ@G :H ! XO +@Bo  @x@ADA .@E2aLk-lE@-b1A YS  A! .@FQ@ " @K@;0>A @@FP$ B@Bp@x@ADAaZ E TaLA( aL21@@=@: =Va&h=" R|@ & V^`{ @6N@ @  @*rA :@@M &) SiCS B@`BBA': `#TS@x@ADA @`E_aL3@$@d?Z!Q R@ B@x@ADRAKuV "B@l@dCk@m D B@x@ADREKaR"KF@@dG$RH BR@x@ADRIKBaR"J@`@dRK#L BR@x@ADRMKKa"RN@DC@dOB#RP BR@x@ADRQKn'RR@ي@dSE$RT B@x@ADRUKۺaR"KV@nҀ@dRWѠ#X B@x@ADRYK"aR"Z@@d[p#R\ BR@x@ADR]K1j'R^@a@d_ #R` BR@x@ADRaKñ'Rb@/@dc d BR@x@ADReKYaR"Kf@@dg0$Rh BR@x@ADRiK@aR"Kj@Y8@dRk7#l BR@x@ADRmKt'Rn@@do[#Rp BR@x@ADRqKhaR"r@ǀ@dsƀ$Rt BR@x@ADRuK]'Rv@@dRw#x BR@x@ADRyKC_QaR"Kz@V@d{#R| BR@x@ADR}KئE'R~@D@d#R BR@x@ADRKn9'R@@dF  BR@x@ADRK6.aR"@o-@d,$R BR@x@ADRK}"aR"K@u@dRpt# BR@x@ADRK.'R@@d#R BR@x@ADRK 'R@/@d$R B/@x@ADRKYTUA"K@K@dR0@ B@x@ADRKaR"K@Z@dƒ#R BR@x@ADRK'R@ڀ@d[#R BR@x@ADRK+'R@"@d!  BR@x@ADRKraR"K@j@di$R BR@x@ADRKCaR"K@@dR# BR@x@ADRK'R@E`@d#R BR@x@ADRKoIa"@@@dF$R B@x@ADRKaR"K@o@dRۇ# B@x@ADRKؕaR"@Ѐ@dqϠ#R BR@x@ADRK. 'R@@d#R BR@x@ADRKg~'R@4_@d^ BR@x@ADRKZraR"K@Ŧ@d1$R BR@x@ADRKfaR"K@Z@dR# BR@x@ADRK>['R@5@d\#R BR@x@ADRKO'R@}@d|$R B@x@ADRKCaR"K@ŀ@dRĠ# B@x@ADRKD8aR"K@ @d#R BR@x@ADRK\,'R@ET@dS#R BR@x@ADRKo 'R@ۛ@dG  BR@x@ADRK aR"K@t@d $RBR@x@ADRK3 aR"K@+@dRq*# BR@x@ADRK/{Z"R@r@d#R BR@x@ADRK'R@0@d$R B@x@ADRKZ aR"K@@dR1# B@x@ADRKQaR"@[I@dH#R BR@x@ADRK'R@@d\#RBR@x@ADRK'R@؀@dנ!(  BR@x` =R !&{(aR"K@ @d$R BR@x@ADR KDpaR"K @g@dR "( BR@x@ADR Kڷ'R@F@d!Q R BR@x@ADRKp'R@@dG$R B@x@ADRKGaR"K@p>@dR="  B@x@ADRK|aR"K@@dr!Q R BR@x@ADRK/p'R@̀@d$R BR@x@ADR!KeaR""@1@d# /$ B M/t@x@ADR%K[eY'R&@\@d'2$R( B@x@ADR)KMaR"K*@[@dR+ǣ#, BR@x@ADR-KA'R.@@d/]#R0 BR@x@ADR1K<6'R2@3@d32$R4 B@x@ADR5K*aR"6@{@dR7z#8 B@x@ADR9KEaR"K:@€@d;#R< BR@x@ADR=K'R>@F @d? $R@ BR@x@ADRAKpZ'RB@Q@dCH D BR@x@ADREKC"KF@q@dGݘ$RH BR@x@ADRIKaR"KJ@@dRKrࠂ#L BR@x@ADRMK01'RN@(@dO#RP BR@x@ADRQKx'RR@1p@dSo$RT B@x@ADRUK[aR"KV@Ʒ@dRW2#X B@x@ADRYKaR"KZ@\`@d[#R\ BR@x@ADR]KOa"R^@F@d_]$R` BR@x@ADRaK'Rb@@dc򍀂$d BK@x@ADReKޝaR"Kf@ր@dgՀ$h B@x@ADRiKE&aR"j@@dRk  @LAl BR@x@ADRmKm'Rn@Ge@dod!J Rp BR@x@ADRqKpzaR"r@ܬ@dsH$Rt B{@x@ADRuKn'Rv@q@dRw" x B@x@ADRyKDcaR"Kz@<@d{s;!Q R| BR@x@ADR}K0W'R~@@d$R BR@x@ADRKK'R@2ˀ@dʀ$ B@x@ADRK\@aR"@@d3$ B@x@ADRKb4aR"K@\Z@dRY  BR@x@ADRK('R@@d^#R BR@x@ADRK'R@@d耂$R BR@x@ADRK9aR"K@1@dR0# BR@x@ADRKFaR"K@x@d#R BR@x@ADRK`"R@G@d$R BR@x@ADRKq'R@@dRH  B@x@ADRKXaR"K@rO@dRN$ B@x@ADRKaR"@@dRs# BR@x@ADRK1'R@ހ@d #R BR@x@ADRK.'R@2&@d%$R BR@x@ADRK\vaR"K@m@dR3# BR@x@ADRKaR"K@]@dɴ#R BR@x@ADRK'R@`@d^$R BR@x@ADRKMa"@D@dRC  B@x@ADRKaR"K@@d#R B@x@ADRKFxaR"@Ӏ@d#R BR@x@ADRK#m'R@H@d#R BR@x@ADRKqka'R@b@dI$R BR@x@ADRKUaR"K@r@dRީ# BR@x@ADRKIaR"K@@dt#R BR@x@ADRK1B>'R@9@d #R BR@x@ADR K@ p,w4 @AC=DEGBHIKGLMOLPQSQTUWVXZ[[\^ _` | x t p l h d ` \ X T P L H D @ < 8 4 0 , ( $     !'                                  | x t p l h d ` \ X T P L H D @ < 8 4 0 , ( $   A @ avm k@Y@ `@JyX F B@aB b {@x@AD>EowaL@@==AC.)`A =^`{ @ V@ @a<.whU@Tp `mi B@B |!@x@ADAESaLAxA'AA =@`= S@@ SK=y !7 `S B@B @x@ADAEˁ kBQA =ҍa"@ɀ@'# }(:J0%Lg%IC%w0s*CZ B@Bt @x@ADAEDaLR'`c =Ϙ`="@ƀ@K=>  B@B !@x@ADAE(aL) a/a =="@X@ 1_C  B@Bu @x@ADAEA觠A[c @ B@B@x@ADAEbaL 7?D =l="@d@ !$1=oc a B@B@x@ADAEZad = %`=!I @n@ ' @ADj$5LgI65:J6 xD! " B@Bx@x@ADA#Eր7c$ ="$_%@^@ ! @=& c @' B@B@x@ADA(EӀ@Y ?a) =y݀="*@Ԁ@ /C+H$, B@By@x@ADA-E/bod. =ߕ`="/@G@ #"2R{yI2 cZA5 `$6 B@B@x@ADA7E}DaL@Yc8 =EN="9@E@A/XC:+; B@Bx@x@ADA<Eaa= =X@!I>@`@ +Q2.8wrm͸ @ 8A? @ B@BA@x@ADAAEoL z=B =aI$_C@ `@E @>ADh E B@B 5$@x@ADAFESL A8!!G = = H@@%A/CI `+J B@B@x@ADAKEpb !L =w`="M@n@&{$5pow 0y:ae6~вN!XANY$+O B@B@x@ADAPED)aL8 AQ =t%`=+$_R@k@ @>AS=A[T B@B@x@ADAUE(&&aL!(!(AV =/="W@X'@ *$҃/tCX Y B@B@x@ADAZE "1A[ =g1a!I\@߀@ +AYHL~x cҼN8kA[]. ^ B@B P@x@ADA_E2aL2(@c` =W<`=$_a@܀@ ! @>Ab c B@B@x@ADAdE=aLA0A8Ce =Ҡ="f@-@#V/2DgAh B@B@x@ADAiER>aB!)Aj =0YI`="k@P@&޺[b g/q0ɷ[E O$R4a& ]Al$m B@B@x@ADAnE JaLR0`co =,VT`="p@M@ '>AqL Ar B@B@x@ADAsEUaL) aaCt =="u@ @&/Cvnw B@B@x@ADAxEYÀbqAy =`a%z@m@%AB޴62\VpB Hqɐ&dY treA{ A| B@B|+@x@ADA}E{aaLrc~ =k`=$_@3]@]@d A B@BA@x@ADAExlaL C =AVx="@y@ &+#V(-`C A/I@GD B@B{@x@ADAE.4maA =:x`=!I@B2@ +,W @ $fxL>lAyO#" ^Ի ma& r1$+ B@B@x@ADAE쀈AA =7a @2/@dA.A[ B@B@x@ADAE}逈 0A =E="@@ /r B@BA@x@ADAEbAA =`=$_@@ &Ɗwtא1'oD:a& j A B@B@x@ADAEn]aLc =`= @@dAg  B@B@x@ADAERZaL j =d="@[@&$/)  B@B:@x@ADAEaD =`="@@&mhX5;ˌhiҖYl[V/a& AY B@B@x@ADAEC΁ c =a"@@dA=  B@B@x@ADAE'ˡ @Y  C =Ԁ="@Ẁ@&/XC  B@B@x@ADAEbA =_`= A@„@ AA!s.&'xzv33Xa& *+A. A B@B@x@ADAE?aL A =Z`= A@@dAA[ B@B@x@ADAE;aL 1A =E= @-=@ /C< B@ B@x@ADAEl a =@a"@@ ?ا{+2B:W U߬xA- cs B@x@ADA @EaLA1 a = ?=,`=+ @@dA Œ!]a B@B@x@ADAEҬ4 Ax!!D =@="@@ /Cn B@B@x@ADAEYhaA"&,A =o`= +@mf@&g] '=7pU 3=*P?a/7#N/a& =Ae$ B@B@x@ADAE aL2@c =l`= @]c@dAb$ B@B@x@ADAEaL A9AC =p'="@@&$/CC+ B@B:@x@ADAE.ف BQA =7"@B׀@%Awm8j.vs:Ԉυx9mBUAր$ B@Bg@x@ADAEaLR)`!A =`="@2Ԁ@dAӀ A B@B@x@ADAE|aL) aac =M="@@ }A/yC B@B}A@x@ADAEJab)qc =P`= A@H@ +th;"w܀:Fq{AI3a& S?AG + B@BA@x@ADAEnaL r1a =M'`= A@E@dAgDA[m@ B@BA@x@ADAER @sd =3 @츀@}妠V~&jwm&H@ U@ gCX$ B@B@x@ADAECs4aL1c>`= @ܵ@d<  B@B`x@9 A @E'p?aLm =b2K`= @)@Ъ1C. + B@DB@x@ADA E J =V/V  @&@d+ $ B@B@x@ADAE @Y+Axc =@="@,@+=ဂ B@B@x@ADAEWb )C =3b`=A @@ :L ;3jRUtiO(ũQD$ B@B:@x@ADAETcaLd =+m`= @@d疀 m B@Bm@x@ADAEQnaL)  c =[="@S@ gm/|C mR"e! B@B@x@ADA"EX oau# = z`= $@l @ +"SѻNV]#_|ŭU*a& A% & B@B+@x$ 9ADA' @EŀmF+( =a )@`@dA*Ac @+ B@DB@x@ADA,E A- = ẁ=".@À@ W/EC/C 0 B@BW@x@ADA1E-~b*A2 =ڄ`= 3@A|@&+? B@B$@x@ADA@E A"1"DA =a B@@ @ h+td^F挼nL[s,[."'FdAC젂@4D B@B sd  @'@x@ADAEEmaL2@dF =`= G@@ @=9~Hf适$I B@AbB@x@ADAJEQaL AAcK =="L@@+ @/LHCM N B@B4@x@ADAOE_aB QDP =f`="+Q@]@& ja`!-lI)QTƘ` ARXS B@B+@x@ADATEBaLR`dU =c`=$_V@Z@ @=W< X B@B@x@ADAYE&aL) a acZ = ="[@V@&A/rC\ ] B@BW@x@ADA^EЁ b:q D_ =^a%`@΀@ ~ ,Lо:6() aSnX>]b[z@Aa- Ab B@B@x@ADAcEaLrdd =U`=$_e@ˀ@dAf g B@BA@x@ADAhEaL :ci =ȏ="j@,@ :/Ckl B@Bw@x@ADAmEAa*tn =CH`= o@?@% 4_H͇pȒqe 'ؒdp-q B@B@x@ADArE Ads =/Ea t@<@dAu;A[v B@B$@x@ADAwE *cx =a"y@~ /fdzm{ B@B@x@ADA|EXLAj} =Z@ ~@l@ +A'>oݩ3VxY:0 9@ DF+د$ B@B+@x@ADAEjaLd =`= @\@dA$ B@BA@x@ADAEgaL c =!sq= @h@ W/CB B@BW@x@ADAE-#a:c =)`="+@A!@!0<&k^0jxq ;F3٣۲ h&a& /A @0 B@B@x@ADAEۀa =&)a @1@ # @>  B@B@x@ADAE{؀) :a =@="@ـ@$/ٌCA[ B@Bc'@x@ADAE*b2}P = 5`=G d@@ AIBQJ;:RzJ_ W&@Z A  B@B$@x@ADAEmL6aLT =@`=$_@ @+ @=$f  B@B@x@ADAEQIAaL 3+A =S="@J@ :&+/XC A B@B@x@ADAEBa A = M`=!I@@&­c`&.i:ʫXa W$+ B@B@x@ADAEB A d =Xa$_@W`@ @>A;A[ B@B@x@ADAE&L!!c = À="@V@&/   B@B@x@ADAEuYbA"1D =]|d`= @s@&:p2A)4^Tĝ?^s,u_W"-a& F+- B@B@x@ADAE.eaL2@d =Uyo`=$_@p@dA$ B@B@x@ADAE*paL AAAc =4= @+,@&/RC+ B@B@x@ADAE怈B#QD =6{a"+@@ v̠-t.ɸӟ>/^H&Ia& اA$ B@B@x@ADAE|aLR`d =*`= @@dA  B@B@x@ADAEЛaL) a#ac =="@@ /.Cl B@B"Y@x@ADAEWWab+qj =!A ^`= +@kU@ {{mԟLP| z"BS(*y "AT  B@B@x@ADAEaLrd =Z`= @\R@dAQA[ B@BA@x@ADAE aL +c =w="@ @ .CB B@B@x@ADAE,ȁ #f+ =Ϊa @@ƀ@ q,gebjQvG51'L|'䫼a& 1AŠ@0`)  ` B@Bq@x@ADAEaLAd = ˵`= @4À@ # @J  A B@B$@x@ADAE{}aL #c =?="@~@#V%o/C B@B@x@ADAE9aA;3M =?`=!I@7@ +T7p@pR3ơW=|0\ a& A6$ B@B+@x@ADAEld =<$_@4@dAe3$ B@BA@x@ADAEP r =="@@&.@C  B@B@x@ADAEשbc = P`=H  @맀@&c' wjVQ"uK깓xRW5A AW+ B@Bc'@x@ADAEAbaLc =`= @ۤ@ @=g ; A B@B@x@ADA E%_aL4c =\!`="@@ *''W dr_H:Ƶ'C]N0EL2@d1 =*aI$_2@[@dA3$4 B@B@x@ADA5E+aL A B@B@x@ADA?E%8aLR`d@ =pB`= A@0h@ ! @JABg AC B@B@x@ADADEz"CaL a$acE =O,="F@#@/|9CGH B@Bt @x@ADAIEށ b qjJ =Na";@܀@$҃5ȋMΣ S./g6v?d @`a& ALۀ +M B@B$@x@ADANElOaLAYirdO =Y`=$_`= @3P@ـ@dAQeؠ@mR B@BA@x@ADASEPZaLE cT =AV =+ AU@@Ġ/nCV W B@Bw@x@ADAXEN[a$IAY = Uf`= Z@L@ +' @Ġ LYC )c:#T,jr#%A[V$+\ B@B+@x@ADA]EAgaLd^ =Rq`= _@I@dA`: a B@B}@x@ADAbE%raL Acc = ="d@U@ /] Ce +f B@BW@x@ADAgE A$ˆH)d̴1X5A@4A B@BM@x@ADAEk;aL2?A =`="@~@ #$>1e} `au-j B@B @x@ADAEO8aL A%AA =B= @9@ @/.C + B@B @x@ADAE BQf+ =a"@@&uEFo/x}JطQL)d/a& gAV+ B@B@x@ADAE@aLR:$ =~`=$_@@ &+ @>J: ` B@B@x@ADAE$aL aac =="@T@&/KC  B@B@x@ADAEdab%q%C = gk \ #%@b@zLg ~MV*xRpgD_lp |A+ + B@BzL@x@ADAE aLr5$ =Sh`=$_@_@ @>AA[ B@B@x@ADAEaL %%A =#=+)@*@ * @A/PC B@B Y@x@ADAEՀ=A = 5!a!I@Ӏ@&('y0~eGP# * xlA- B@B@x@ADAE"aLg/$ = {-,`=$_@Ѐ@dAϠ B@B@x@ADAEϊ-aL =c =="@@ /@Ck B@B@x@ADAEVF.aA5C =M9`="@jD@ ,W$+A-əir}ndBYd;D}&bAC$ B@Bt+@x@ADAE+J$ =IDa"@ZA@dA@ B@B 'M@x@ADAE c =mE"+@~&/C@ +`m0< B@B@x@ADAE+L=C = Pa"@?@&Z{gdFEHЬKii:ut%DK(A  B@BA@x@ADAEoQaLA$$ = ׺[`="@3@dA  B@B@x@ADAEyl\aL c =Jv="@m@&A/YCA B@B@x@ADAE(]a5%C = 5.h`="@&@&l A؄9;$K:3݁=]8ƭJA%  B@B@x@ADAEk%A = +s @#@ ,W @>d"  B@B@x@ADAEO 6&A ==+"A @ހ@ @҃/C   B@B@x@ADA E՘tbp !  @A =`="@햀@ CX5ܝUzAT fB vndR@ AY$+ B@BM@x@ADAE@QaL d = ~`=$_@ٓ@ ! @>9 Œ B@B@x@ADAE$NaL)Ax!!c =@W="@TO@&/~C  B@B@x@ADAE aA">1>D = _`="@@ :#+zG mܡ /$ oW_4ћ.A+ B@B@x@ADA$h`E 2@d! =S a""@@ PA# $ B@DB@x@ADA%E  A6AiA& =Ȁ="+'@)@&/C( +) B@B@x@ADA*EzbBQc+ = 0`=",@x@&-Av Bq;cL)Z;4A- A-. B@Bx B w6@x@ADA/E2aLR6`c0 = {(~`=)1@u@ &+ @JA2t 3 B@B@x@ADA4E/aLaqG5 =`="6@i@A}:Q&lۙjgmRS俕8G>7zL7耂 8 B@B@x@ADA9EaLrd: = `=";@Y@ P<倂 = B@BW@x@ADA>EaL c? =p= )@ԡ@+/zLA@+B B@Bw+@x@ADACE*\a}.DD =b`= E@>Z@&j.="<6%bbf GaGFY$AG B@BM@x@ADAHEaLdI =_`= J@2W@ P1?VA[L B@B}@x@ADAMEyaL .cN =E="O@@ !#VAĠ/MCP+`B@x@ADARÉ WcS = 5a"T@ˀ@ 'AT? CA R<%A#6| z#AUʀ$V B@B@x@ADAWEjaLaX = `="Y@Ȁ@ !$>:Zdǀ [ B@BW@x@ADA\EN]@T a] ==G@Bgb^@~@ /C_ +W` B@B@x@ADAaE=a6IAb =D `="c@;@&ݾm.a/\'d bxV[ !;a& AdU#bXe B@Bg@x@ADAfE? cg =}Aa$_h@8@ # @(&_a,Ai9 j B@@B@x@ADAkE#  6cl =="m@W@&/)Aná$o B@B@x@ADApEb>cq =^#`="r@@ 'AT]S#eCW E9@V~|Kp9:a&  As* t B@B+@x@ADAuEg$aLޗv =R.`=$_w@@ ! @>Ax y B@B@x@ADAzEc/aL A?a{ =m=+"|@)e@ &+ @/ C}d~ B@B@x@ADAE0aIA5 (&;`=!I@@ @ m;ߠθk }q 2!/0= IA@2i7 A B@BM@x@ADAE׀  c =#Fa$_@@ `@>A  B@B@x@ADAEԀ?!!C =ހ="@ր@҃/CnՀ$ B@B`@x@ADAEUGbA"'1'A =R`="@i@ 'g=x"sT1 R]hB!d"  ֧AՍ$ B@B@x@ADA?j`EHSaL2@d =]`="@X@ !$>A  B@DB@x@ADAEE^aL) AA'Ac =lO="+@F@&/ li? B@B@x@ADAE*_aB?Qa =j`="@>i`@ '# *;C"EnB"D;  B@B(@x@ADAELR`d =uaI$_@.t`@ `@>A  B@B@x@ADAExL a?ac =M="@@ (!$A/C B@Bg@x@ADAEqvbbqIA =x`="@p@ 'iuc?T#J *T(a& Ao  B@B@x@ADA=`Ej*aLrd =u`= @m@ ! @>Agl  B@DB@x@ADAEN'aL C ="1=+"+@~(@ /C' B@B(@x@ADAE ?/A =a @@ # @A dn}Wmx?Urˡ-a& \AT$+ B@B@x@ADAE?aLd>`=$_@݀@ # @>A$8  B@B@x@ADAE#aL ?c =="@S@ !A/SpC  B@B@x@ADAESaAj =ZZ`="@Q@ 4Zt~^t& rRc΃y{ ea& )A* B@B@x@ADAE aLd =RW`="@N@ !# a,g A B@@B@x@ADAEaL$c ==$_+@, @&/C +W B@B@x@ADAEĀoCc =0a"@€@&$zN<ܭ<Yff9Bk'4"Aa& CA@0 B@B@x@ADAE|aL(a =+`=$_@@ ' @JA羀  B@B@x@ADAEyaLa =="@{@&/oCmz$ B@BW@x@ADAET5a7M =<`="@l3@ '&lӠz%EvX-'X%1-`a& A2  B@B+@x@ADAE퀈 z% =8$_@X0@ ! @=A/  B@B@x@ADAE 0A =k=+"@@ `@/kC?  B@Bs$@x@ADAE)bA =֬`="@=@ ##!#f9tƬPü'F-FA$+ B@ B(@x@ADAE^aL E B@x@ADAEx[aL !!c =@e="@\@ !/C B@BW@x@ADAEa"1 C = `="@@%t%e 1~⌥ܬ撓(ٹ'ւ bA $ B@BDK@x@ADA Eiπ2@A =a$_ @@ &+ @>Ac  B@B ; @x@ADAEM AAc =&ր=$_@}̀@Gag/NC +A B@B@x@ =A @EԇbB Qa =%`="@腀@&$ڶ-,Ӓ+Jg:&[ec` zT@0 @ J B@DB"Y@x@ADAE>@&aLR.- =|0`=$_@؂@ ' @>A8  B@B@x@ADAE"=1aL a ac =F="!@R>@ !$Ġ/D" # B@B@x@ADA$E bq% =]A;cA[< B@B@x@ADA=E`aL 0r> =(=+"?@@ ! @A/C@iWA B@B@x@ADABETځ A C =ka"D@h؀@&=Et4c?n?֊tM7&K\ E׀$F B@B 6h@x@ADAGElaL0cH =v`="I@WՀ@ &+>JԀ K B@B@x@ADALEwaL6hgM =Q`=$_N@=I@ A-)}Gom\we[h^{UDDOH WP B@B@x@ADAQEaL$R =N`="S@-F@ ! @+=TE U B@B@x@ADAVEwaL? cW =D ="+X@@ $#VǶ/bCYZ B@B@x@ADA[E 8C\ =ša"]@@ #_oЙμ1/ {xrb,a& A^~ _ B@B@x@ADA`EitaL Aa =`=$_b@@ # @=cb d B@BW@x@ADAeEMqaL 9Af ="{=+"g@}r@ ! @Ġ/tCh i B@B@x@ADAjE,a))Ak =3`="l@*@ +P}O&'hJٍ@) X2Lj(wAmS$+n B@BW@x@ADAoE>  dp =|0$_q@'@ ! @>Ar7A[s B@B@x@ADAtE" !)!cu ==+$_v@R@ /Cw x B@Bm@x@ADAyEbA"1Dz =]`="{@@$[zJ|XRq=r 7m>{<a& TA|)} B@B$@x@ADA~EVaL2@d =U`=+$_@@ # @>A A B@B 2^@x@ADAERaL) AAAc =\= @'T@PA/xCS B@B@x@ADAE~aBQj =&`="@ @%AH\zGnY᣶SQl E.a& > @0 B@Bo @x@ADAEƀR`d =$_@ @ `@>A  B@B@x@ADAEÀ aa9G =̀="@Ā@ &+$/fDhA[ B@B4@x@ADAESbb qj =`="@g}@JA4{Ϡ&ʬi} a&  |  B@BJ@x@ADAE7aLrd =&H@Qd_@Wz@$=y  B@B@x@ADAE4aL  c =k>="@5@ /A&>A B@B@x@ADAE( j =a"@<@&Jv,M5JEGPnlea& mf+퀂$+ B@B@x@ADAEaLAd =`=$_@-@ PAꠂA[d B@B@x@ADAEwaL c =@=+)@@ /lW B@B@x@ =A @E`aA c = ?=g'`="@_@&gtkvPY]wsL T VJ~^$ B@B@x@ADAEh(aLa =d2`="@\@ #$JAb[  B@B@x@ADAEL3aL  M = = @|@&g/zC +W B@B@x@ADAEс 9 A =>a"@π@&$!`V}[8m-}x77;TKeCS+ B@BE@x@ADAE=?aLc ={I`=$_@̀@ PA7  B@B@x@ADAE!JaL@Y 9c =="@Q@&$/C  B@BM@x@ADAEBKa9!D =TIV`="@@@ #&lMMQ[Xy}Wˀ_`Aa& cA( + B@B@x@ADAE  =PFa @=@ PA  B@B@x@ADAE  :"C =ba+"+@'~&/C B@B@x@ADAE}L*A = &m'@@ YĪ6>bԥl1`DKrA - B@Bu@x@ADAEknaLA d =x`= @@ @+=$᭠A[ B@B@x@ADAEhyaL !*!c =r="@i@ &+$/Y^Ch B@B@x@ADAES$zaA"1 =+`="@g"@&Ӧ` (Z` sa& BA!$ B@Bm@x@ADAE܀2@d ='a"@Z@>A[ B@B @x@ADAEـ AAc =n=$_A@ڀ@&/\=  B@B@x@ADA  E(bB:Q2IA =`="@<@ A O{χExL @ yD%zc B@DBE@x` =A @EMaLR`d = ?=И`=$_@,@ ! @>A A B@B@x@ADA EvJaL a:ac =KT=" @K@ A/CUC  B@B @x@ADAEab"qG = `="@@ #&lk(-fU/SDGya& ~A}  B@Bv@x@ADAEhrd = $_@@ # @>Aa  B@B@x@ADAEL "c =ŀ=+"@|@ :! @J/RC  B@BJ@x@ADAEvb IA =}`=" @t@ $X4^=U7>xc:a396^+U*A!R$+" B@B@x@ADA#E=/aL d$ ={z`=$_%@q@ ! @>A&6 j@' B@B@x@ADA(E!,aL) c) =5="*@Q-@ /+ , B@B@x@ADA-E Aom. =\a"/@@ +#+A_BFAв9Y?69fa& ?0(1 B@ B+@x@ADA2EaLd3 =P`="4@@ #=$5 !U 6 B@B@x@ADA7EaL c8 =Ʀ="9@&@&g/G?: ; B@B@x@ADA<E}Xax= =5_`=$_>@V@&k\O־"[u^S(/ua& &QIA?U@ B@B@x@ADAAEaLdB =%\`@$_C@S@ &+ @JADR AE B@B@x@ADAFE aL cG =="H@@ &+#V/}CIgJ B@B@x@ADAKERɁ :L =  a"M@fǀ@ #AƭFQii~ӭN˺_$ $ANƀ +O B@B@x@ADAPEaLAQ =`=$_R@VĀ@ # @=ASÀ T B@BA@x@ADAUE~aL ;#AV =u="W@@ ! @҃/nCX=Y B@B@x@ADAZE':a A[ =@)`=!I\@;8@ m25!vJYMaXғ! A]7$+^ B@Bm@x@ADA_E d` ==4$_a@,5@ ! @>b4 c B@B@x@ADAdEv !;!e =B="f@@ /Cgh B@B@x@ADAiE5bA"1cj =@`="k@@ +#$!Nee?@Rik9JLEa& Al}$m B@B+@x@ADA&`EgcAaL2;@co =K`=+$_p@@ # @>Aq`Ar B@DB@x@ADAsEK`LaL; AQ3*t = "X`= u@@ ! @H~{8rPmM\9V=6C CvR$w B@B{  @x@ADAxE<ԁ R`dy =zc"z@@ $ @>{6 W| B@B@x@ADA}E ѡ @Y aac~ = +ڀ="+@PҀ@ !B/nC  B@B@x@ADAEdbb#q+D =Wo`="@@AĠzꬷXoQ`c!jxeD' yA'  B@BA@x@ADA. EEpaLrdDIOz`= @@@VԒ&+ @> & DBW@x@@A EA{aL #cK="@&C@`ڥ/CBc DB@x@ADA @E|D =%a"I+@`@B'#+,$5MP0 zk L AF@2 @ ` !@DB@x@ADAE絁LAdM%aIG@`@d_AW@`@ #>A!  B@B@x@ADAE˲L c = =+"@@ @+A/kCgN B@B@x@ADAERnbAom =u`="@fl@EE$*8?fhT~\:T;kFS L ZAk$ B@BA@x@ADAE&aLd =q`="@Ui@ !>AhA[d B@B@x@ADAE#aL c = l-="@$@h&+/vC<  B@B@x@ADAE'߁ { =a!I+@;݀@&_Vi`cėh~YLa& A܀+ B@BwN[P@x@ADAEaLd =`=$_@+ڀ@ # @=ـ  B@B@x@ADAEuaL c =F="@@KqO!#V/C B@B@x@ADAEOaq om =V`="@N@ +'SW]1ı _Ki--|B#2L@!~#;YAM + B@B+@x@ADAEgaLOm =S`=$_@K@ ! @>A`J  B@BA@x@ADAEKaL ,A = ="@{@ /uC  B@B@x@ADAE $c =a @往@ +# @$+SV+ɰ|-Zd+hC%} L ۖA lQ$+ B@B@x@ADAEA @ B@B}@x@ADAE  i AxAAc =@="@%@|Z/瀂 W B@B@x@ADAE|bB,Q J =$`=%@@p# @A\[co -QApa& t\@0 B@BM@x@ADAEZaLR`d =`=$_@@ # @>A A B@B@x@ADAEWaL a,ac = a="@X@A/ʈf  B@BM@x@ADAEQ abq4D =+`="@e@ +"^+.iipM2_5XE.a& rM  B@Bu$@x@AD>"Eˀrd m6$_@U@ ! @= `iF B@B`x@9 A @EȀc = ?=lҀ="@ɀ@&/3^C@  ! BA@x@ADA E&7bc = B`=% @:@C# @҃P[E̴벀Ž/2&NLV 6A $+ B@B`@x@ADAE/5 A0 B@B@x@ADA1E}aL d2 =$="3@O@#V/IA4 A5 B@B@x@ADA6Eց  c7 =[݈a"8@Ԁ@ 'AK}d" - dmN0r\D41a& f=9& : B@B@x@ADA;EaL< =Nړ`=$_=@р@ ! @>A> ? B@B 'A@x@ADA@EaL F+A =ŕ="B@%@ W&+ @/liCD B@B|@  @'@x@ADAE@/ @{GalcF =,N`=!IG@E@ # @`[qV ɥ^o-@ zF+HD-I B@DBg@x@ADAJEA CK =$K$_L@B@ # @>AMAA[ ` $N B@B@x@ADAOE !!dP = a"Q@~ !/CRfS B@B@x@ADATEQLA" 1CU ='V@e@ +ۢ~*l*=401oJ a& 'AWѵ$X B@B@X+@x@ADAY@/ EpaL2 @f+Z =`=+$_[@X@ ! @>A\AG `@  ] B@DB@x@ADA^EmaL AAd_ ={w="`@n@7/C Ca;b B@B@x@ADAcE&)aB Qcd =/`=%e@:'@&  ly’'!0̅*D}f&$g B@B+@x@ADAhEဈR`ai =,$_j@*$@ # @>Ak# l B@B@x@ADAmEtހ) aadn =A="o@߀@&A/U@rm B@x@ADAr! @Ebb=qas =`="t@@ '$F;t'B|Nb QNrLgF+u{ v B@DBAjA@x@ADA] EfRaLraE`=$_B9@@ @=z!G_ 0& DB@x@ADAq( EJOaL =aT|Y="~zP@ :&+ @/C-C g4 DB@x@ADA? E a=%M`="A@@&$*ox+퐭&FpԿ@ AP$+ B@DBb@x@ADAE;Á Ac =y b@$_K{@@ # @=4@G B@B@x@ADAiq E 6h=%C@mI^a" @y@ !pc õkDWXD'[hDy@ bC& DBd@x@ADAo E4aLAN `="@v@ )A> AG B@DBAbB!:@x@ADAE0!aL d =:="@$2@ !B/C1 B@BF @x@ADAE{쀈Wc =,,a!I+@@ iзƒuDF!"LKMZ$*`~n! jA适$ B@BJ@x@ADAE-aL5a ='7`=$_@@ ! @J怂  B@Bg#@x@ADAEɡ8aL) d =="@@ /OFCeW B@BM@x@ADAEP]9a5c =cD`="@d[@ #$AjEcB)rli^7'W|C2v! nw$Z `XF B@BP@x@ADAEEaL-.IA =`O`=$_@TX@ # @>AW  B@BA@x@ADAEPaL A =g="@@ ! @/}P; B@BS@x@ADAE%΁ p}.c =[a!I@=̀@ +l=;}NN>Ju%fx! F+ˀ$+ B@BA@x@ADAE\aL6  =f`=$_@*ɀ@ @>Ȁ  B@B@x@ADAEtgaL !!d =@="@@ .>C B@B@x@ADAE>haA"61c = Es`="@=@&-[!D% QHr38yYL-' A{<$ B@B@x@ADAEe2@a =B~a"@:@dA^9A[ B@BsB,X@x@ADAEI AAd =="@y@&/TC  B@B@x@ADAEЯbBQc =y`= A@䭀@ @h$Y}Q4be f;/A 'p(raEAPA[ B@Bp@B J@x@ADA@/ E:haLR`c =x`=)A@Ԫ@ @=:4AG B@DB@x@ADAEeaL) aaa =n="@Nf@+/iC  B@B@x@ADAE abqa =R'`="@@&T>]\)d?$*.]ݥJİAm,a& iA%  B@B@x@ADAEف r&a =M$$_@@ @>  B@B@#A@x@ADA@/ EՁ  a =߀="@$׀@&/Cր B@DB@x@ADAEzb}&a =/`=%@@ }p1=lkckQ̼63n A- B@B}@x@ADAEIaLA>U =#`=$_@@dAދ  B@B@x@ADAEFaL c =P=" @G@&/CeA B@Bx!+A@x@ADAVEPaAc `="@d@ +5o}yodHuK6꘧Ng5ֳ'ĸ9m- A`$ B@B+@x@ADAE &D =`" @S`@dA A[ B@B@x@ADA EL d =j="@θ@&/C:W B@B@x@ADAE%sbc =y`= A@9q@&$"+<O0[N113І]ܠN' = 0 `= (@ހ@dA)^ A* B@BA@x@ADA+EI aL J, == -@y@ Wg/EqC. A/ B@BW@x@ADA0ET a?c1 = [`="+2@R@ +dF FDsaUΛFT}L>&̉h  43O$+4 B@B+@x@ADA5E: aLA c6 = xX"`= 7@O@dA83 9 B@B@x@ADA:E #aL?!!a; =="<@R @ /v`=> B@B@x@ADA?EŁ A"1a@ =M.a"A@À@&l:g+ނ(|<O/F+B%@4C B@Bp@x@ADADE~/aL2@'GE =Q9`="F@@dAG H B@B@x@ADAIEz:aL AAAcJ =="K@#|@g/(CL{M B@B@x@ADANEz6;akJBQcO =3=F`=!I:P@4@%-"yܵN)?s, زa& AQ3$R B@B-@x@ADASER`DT =":Q AU@~1@+ @+=V0 +W B@B@x@ADAXE뀈) aadY =="Z@@ A/C[d\ B@B@x@ADA]EORbbqc^ =]`="+_@c@ d ƾ*5o>Z.Aj{[2M" sA`Ϥ a B@B@x@ADAbE_^aLIderf+c =h`=$_`= @3d@W@ @>e 5`B@x@ADAgE\iaLADdh =@jf= i@]@ :/DKj:Ak B@B@x@ADAlE$jacm =u`="+n@8@')Cؑ 1)寋cb(Ja& VCo$+p B@B@x@ADAqEЀcr =$_s@)@dAt u B@B@x@ADAvEs̀ aw =?׀="x@΀@ .>yz B@B@x@ADA{EbAa| =`="}@@&lA^ WvlObaLg,(t`aL$c =H="@|?@/tC$ B@B@x@ADAE oc =a!I@`@&H%$~ovLL&UlVm7AS$ B@Bm@x@ADAE9L*'c ={aI @@+ @+=7  B@B@x@ADAEaLa == @Q@ `A/C + B@B`@x@ADAEjaG =\q`="+@h@ ^)c٪4vy?0;@C9?!78a& Ar(  B@B@x@ADAE#aL zA =Ln`=$_+@e@ @> A B@BA@x@ADAEaL A =)= @#!@ M/r  B@B@x@ADAEyۀ0A =.a"+@ـ@&:ѺL[U#ˀ #O< +BoЕ F+؀$+ B@B@x@ADAEaL A ="`=$_@~ր@dAՀ  B@B@x@ADAEȐaL9o# 8!01A =R`="@cJ@ :fl/RChɾ;8v?;ȷ .I  B@B @x@ADAEaL28@f+ =O`="@VG@dFA[+ B@B}@x@ADAEaLAxAC)A =@i ="@@ 1/49 B@B@x@ADAE$ B8Qc =d@ A@8@Ġ欫tna'ivsk_?g6_9Q M0giS$ B@B@x@ADAEuaLR `C = `= @(@dA[ B@B@x@ADAErr aL) aaA =?|= @s@&/o B@B@x@ADAE-ab qc =4`="+@ ,@  Ġo[cEY9JN∂Y2Ga& py+ + B@Bt @x@ADAEd怈r0 =1$ @(@dA] A B@BW@x@ADAEH  D =="@x@ WA/p  B@B@x@ADAEΞ%b0c =0`="+@✀@ +t~ }\lR(zzfLy!haPN$+ B@B+@x@ADAE9W1aLA(C =w;`= @ә@dA2  B@B@x@ADAETE8 R)`!LW? =vG @@>@dAA2 B B@B@x@ADACE ) aadD = E@L~ W/oCF G B@B@x@ADAHELb)qcI =Ka"+J@@!+z;lfWiHfd8qH!a& tAK# AL B@B-@x@ADAMEmaLr jN =`= O@@ @=P ` zLQ B@B@x@ADAREiaL dS =s="T@"k@+ @A/CUjA[+V B@B@x@ADAWEx%a cX =-,`="AY@#@ A&|x[ |Q<<~t'iMAG"L?a& .OAZ"-A[ B@B@x@ADA\E݀1f+] =!)$_^@} @ @=_ ` B@B@x@ADAaEڀ Jb =="c@ۀ@ A/cCdce B@B{@x@ADAfEMb1cg = e@"+h@a@oJ l|Y`Z?aHpM))@L@ Ai͓ +j B@B@x@g =Ak @ENaL!)Cl =`=$_m@Q@dAnAo B@DB@x@ADApEKaL Aq =tU="r@L@ /ޗs8t B@B@x@ADAuE#a!cv = `=*w@7@ AQXJ7+ =,, 9Ǫ饈wa& Dx@0y B@B@x@ADAzE1t{ = & |@'@ # @JA}A[~ B@B@x@ADAEq) d =6ƀ="@@ +!#V-/C  B@B@x@ADAEw'b1c =~2`="+@ v@ TоoB\Ztj-fmA (*Axu  B@B@x@ADAEc03aLG ={=`=$_+@r@ ! @>\  B@BA@x@ADAEG->aL a = 7=$_@w.@&g/wC A B@B@x@ADAE a =~Ia"+@@ #!00s=774;u6bta& GAM$+ B@B@x@ADAE8JaL*  =uT`=$_@@dA1  B@B@x@ADAEUaL!!c =駀="@L@ A/YkC  B@B@x@ADAEYVa"*1c =S`a`=$_@W@&aיbz{mgkxLZSԀ a& J" A B@Bym@x@ADAE baL2@a =O]l`= @T@dAA[W B@B@x@ADAEmaLAAd =="@!@ /D B@B@x@ADAExʀB2Qz =$xa"@Ȁ@ A8h0fOڏ(jm Ĕ5Aǀ$ B@B@x@ADAEyaLR`c = ΃`="@|ŀ@dAĠA[ B@B@x@ADAEaL) a2ac = = @@ /lCb B@B@x@ADAEM;abqc =A`="@a9@ L rWm\Y܇4\|5A8 A B@B@x@ADAEra => A@Q6@dA5 A B@B@x@ADAEDå)a =ʲ @6@ U.Nu8ڼpCv E e3:A *UC@2 B@@iB$@x@ADAEdaLf+ =`= @'@dA[ B@B@x@ADAEqaaLAxc =@5k="@b@ +:/yC + B@Bom@x@ADAEac =#`= @ @ [ڀ-*Fo66j* |spta& gAw$ B@B}@x@ADAEbՀW:A = a @@ +# @$=[  B@B@x@ADAEFҁ  d =܀="@vӀ@ !#VĠ/ KC  B@B@x@ADAE͍bWc =`="@ዀ@ [=%DL/a&  AM B@B@x@ADAE7FaLf+ =u`="@ш@ !> 1A[ B@B@x@ADAECaL) d =L=$_@KD@ /4!C  B@B@x@ADAE c =Oa"@`@ #!+4X3o&hȻ{ a& D?"  B@BC@x@ADAE L +IA =JaI$_@`@dA   BA@x@ADAEL  =="@!@A/rD B@B@x@ADAEwob c =#vf@"@m@&Զo?Ckh<43bZX o G l- B@B@x@ADA E'aL c =s`= @|j@dAiA[A B@B@x@ADAE$aL !!a =.="A@%@&/ b B@B(@x@ADAEL "1a =a +@`ހ@&$r˹ >{{nY2e@a& =F+݀$ B@BA@x@ADAEaL2#@#IA = '`= @Tۀ@dAڠ  B@B}@x@ADAE(aL AAc =d="!@˖@ /˔C"7# B@B@x@ADA$E"Q)aB#Qc% =W4`="&@6O@ 4N3M 8je `f5r}oA'N$( B@B@x& 9ADA) @E 5aLR+`;D* =T?`="+@%L@ /m$m=$,K - B@DB@x@ADA.Ep@aL) aad/ === 0@@&$/C1 2 B@B@x@ADA3E b+qc4 = Ka"5@ @ r#!ĠJA #DN][7ctbDQ#q A6w 7 B@Bc'@x@ADA8EbzLaLrom9 =V`=$_:@@ ' @>;[ < B@B@x@ADA=EFwWaL d> =="?@vx@ #! @҃/"Y@ A B@B&@x@ADABE2XacC =|9c`="D@0@ + 8#;83{ZEnJI XSa& 9WDEL$+F B@B+@x@ADAGE7 IAH = t6n$_I@-@ ! @>AJ0K B@B@x@ADALE  dM =="AN@K@ W/{CO P B@B@x@ADAQEobcR =Rz`="S@@&bجX5q6AX e ZL>a& xAT!$U B@B@x@ADAVE \{aLA "  db3omW =J`=$_`= @3zLX@@ # @>AY@Z B@B@x@ADA[EXaLd\ =AVb="]@ Z@ !A/C^Y_ B@B@x@ADA`EwaA3ca =`="b@@:tVp'jk{7$jVնc /XAc@2d B@BJ@x@ADAeÈ;jf =a"g@z@ PAh i B@B@x@ADAjEɀ)+Axdk =@Ӏ="+l@ʀ@ /tCma n B@B@x@ADAoELbq ;cp =`="q@d@&,y x8hH)Y=ЈW0X)a& (rЂ s B@B@x@ADAtE=aLu =`=)v@P@ @JAw x B@BA@x@ADAyE:aL Omz =kD= {@;@ 3/D|7A} B@B@x@ADA~E! Ac =a"@5@ P'A^ `ڔ;Klc߸.hznE:Q6=A$ B@B@x@ADAEaL4 c =`=$_@%@ ! @>  B@B@x@ADAEpaL !!a =<="A@@&//C  B@BwJ@x@ADAEfa"41a = m`=$_@ e@ +# @߸0f_ #z1e] qvd$ B@B}@x@ADAEaaL2@a =j`=$_@a@ ' @>ZA[9 B@B@x@ADAEEaL AAa =&="@u@ !/q  B@BA@x@ADAEׁ ABQa =ta"@Հ@&XK}܀\jrR ,LBa& "F+L@0 B@Bm@x@ADAE6aLR<`j = t`="@Ҁ@ &+>A0  B@B@x@ADAEaL) aac =ߖ=$_+@J@ /FC  B@B@x@ADAEHabAڳ  B@B@x@ADAEn*a  Ad =x="A@o@&/fCa B@B@x@ADAEK*+a<$C =06`=$_@_(@&$ӁOƀ$(K\ɫ3+? b 4T  XF B@Bm@x@ADAESNaLxa& v  B@B+@x@ADAEaĀ< C =p)@@ @>AZ  B@B@x@ADAED ; -5A =|"@z@.'#)AD-u >o_pKs3?zK  B@B.@x@ =A @E65}aL A =s`="@w@ $ @>/  B@DB@x@ADAE2aL !-!c =;="@J3@ !#VW/  B@B A  @'@x@ADA%`@ "51a = @=Qa!I@@ AĠ4o' aH'*2nME2 IA $A B@B@x` =A @E aLW2@d = ?=M`=$_@@ ! @>  B@B}@x@ADA EaLA5Ac ==" @@ /;C  B@B@x@ADAEv^aWB=Qj =&e`="@\@$Ġ=^zQ-뜃i.)<禐a& GA[$ B@B@x@ADAEaLR`d = b`="@yY@ #>AX  B@B '@x@ADAEaL) Aa=ac == @@&/NkC` B@B@x@ADAEKρ bq-LW = a" @_̀@ ĠWyK{dOԌx3cCPN7*M!̀ bN " B@B @x@ADA#EaLrd$ =!`=$_A%@Oʀ@ PA&ɀ ' B@B@x@ADA(EaL c) =j="*@ʅ@ &+$/S+5, B@B@x@ADA-E @aD. = F`="/@4>@&Fh~iX}_C% cJƶߊ#F+0= b 1 B@B@x@ADA2Ed3 = C 4@$;@ # @=5: 6 B@B@x@ADA7Eo C8 =P=+$_+9@@&/?C: ` ; B@B @x@ADA<Ebom= =`=">@ @ :'!Q`ABAczUmi,\>2h,t]B?U + ̬@G?u$+@ B@B}@x> 9ADAA @E`iaLdB = ?=`=$_C@@ ! @=DY E B@B #@x@ADAFEDfaL cG = p="H@tg@ &+A/:CI AJ B@B@x@ADAKE!aAF+L = {( "M@@&*VkpnCQYmhzğ,b ANKO B@Bm@x@ADAPE5ځ d4 {%a"R@@ #>AS3 AT B@B@x@ADAUEס @Y cV ==$_+W@I؀@%/ $CX Y B@B@x@ADAZEb-t[ = \ `="\@@ '!҃Awwm qoD_/Z/"Aq A] ` ^ B@B@x@ADA_E K!aLF+` =H+`=$_5@@ ! @=Ab c B@B@x@ADAdEG,aL.e =Q=H bQf@I@ &+A/yCgHh B@B}b@x@ADAiEu-ao(vL+@s&zj =) 8`="k@@ #^ 'j=n:Ds. Go1 eX@ 6hl m B@BA@x@ADAnEເ do = C$_p@yB`@ # @K=q r B@B@x@ADAsEĸLAx!&!ct =@€=+"u@@ M! @/-6hv`w B@BA@x@ADAxEJtDb">1Gy = zO`="z@^r@&Ww ^@'07U.u|`* {q$+| B@B@x@ADA}E,PaL2@d~ =wZ`=$_@Oo@ &+ @>An - B@B@x@ADAE)[aL A>Ac =e3="@*@ /"IA5 B@B@x@ADAE B>Qc = fa"@4@&@S^AX0GS%t0z=S A $ B@B@x@ADAEgaLR`a = q`="@#@ >A߀  B@B@x@ADAEnraL) a>aa =;=)+@@&/xC J B@B@x@ADAEUsab6qj =\~`="@ T@ '!҃y@ZWo2~7-Z+Daca& AuS + B@BXZ@x@ADAE_aLrc =Y`=$_@P@ ! @=Y  B@B@x@ADAEC aL 6c = ="@s @&$/C  B@B@x@ADAEƁ a =͕a"@Ā@&=,xِ4\4EOY-MAJ e# $ B@B@x@ADAE5aLd =rʠ`=$_@@ ,W @>A.  B@B@x@ADAE|aL c ==+'u@I}@ @҃/C  B@B@x@ADAE7a&a =L>`="@5@ +'AIҚ&-8D.x𘦇Ƀ8W)a& 6A- B@B@x@ADAE Ad =H;$_@2@ ! @>AA[ B@B@x@ =A @E &c ==+"@@ &+ @/\C퀂!]a B@DBA@x@ADAEubA6a =)`="@@ #55څD4/qb<\+=dhC!7W [0A$ B@B<@x@ADAE`aLd =!`=+$_@|@ # @=٢ A B@B@x@ADAE]aL6h6c =g="@^@ ! @A/Cc + B@B@x@ADAEJa&c =`="@^@&FMsĪؕD(Nz%*P%>a& "Y  B@ BA@x@ADAEр]P =$_@N@ &+ @>A  B@B@x@ADAE΀ 'a =a؀="@π@&A.\(4A B@BA@x@ADAEb7C =ː`="@3@ #+A2lFox!´ 6^YSa& 0iF+  B@B@x@ADAEBaLJ c =Ǎ`=$_@#@ ' @>A  B@B@x@ADAEn?aLz!!C =;I="@@@ A/EC  B@B@x@ADAE "1c = i@"@ `@ P _xf3OGRQj@ At$+ B@BP@x@ADAE_LA2@C =aI$_@@ ! @>AXA[p@ B@B@x@ADAECaL AA'Ag ==+'u@s@  `@/B>C  B@B@x@ADAEkaABQd vr"`="@i@ +#!A%P j/0:9(0_n AJ B@B+`x@9 A @E4$#aLR'`c =ro-`="@f@ #>A.  B@DB@x@ADA E!.aL6haqF+ =L9`=" @ڀ@  `@9~vnӀCo & ̒=B*2_>pC   B@B@x@ADAE :aLrd =KD`="@׀@ $ @>  B@B@x@ADAEEaL? C =="+@@mՙ/\C+ B@B@x@ADAEtMFa'f+ = TQ`="@K@ MXZ04Y4nI8EIa&  ;AJ  B@B@x@ADAERaLd = Q\`=$_@xH@ ! @> G ! B@BW@x@ADA"E]aL 'c# = =+ $@@Ġ/C%_& B@BW@x@ADA'EI ?F+( =ha")@]@ &֥cdD{:"e"! `*ɻ$++ B@B@x@ADA,EviaLWd- =s`=$_.@N@ PA/A[0 B@B@x@ADA1EstaL?c2 =h}=+ 3@t@ ! @+/m`48$5 B@B@x@ADA6E/uaC?a7 =5`="8@7-@ + *;/ Xò~;]Kh0 FI =Wa& R]F+9,$: B@B@x@ADA;E瀈d< =2a"=@**@ !$>A>) A? B@B}@x@ADA@Em䀈}?GA =>=$_+B@@&/qCC +AD B@B@x@ADAEEi@T/AF =`="G@ @JAa SRKl#0qQdS"@ OAHx I B@B@x@ADAJE^XaLA zA$# =`=$_L@@ ' @>MX N B@B@x@ADAOEBUaL P =_="Q@rV@ !$/rR S B@B; @x@ADATEa(CU =}`="V@@ }YE:xA=kaȳwdrnt߁w. *6a& DWI X B@B@x@ADAYE4Ɂ  AZ =q$_[@ @ ! @>A\- ] B@B@x@ADA^EƁ  !(!c_ =π=+$_`@Hǀ@ /Ca b B@B@x@ADAcEb"1ad =O`="e@@ #!Abq N /lAf-g B@B@x@ADAhE :aLA2@Di =G`=$_j@|@ # @>AkA[l B@B@x@ADAmE6aLAAcn =@="o@8@ !A/SAz쀂 A{ B@B@x@ADA|E§aL) Aa8ac} =="~@@ /QC^ B@B@x@ADAEIcab(q =i`=%@]a@ # @AWBe_RA\6"֩z^a& vA`@0 B@Bc@x@ADAEaLrD =fr$_@M^@ # @>A]  B@B@x@ADAEaL (c =\"="@@҃/xZC3A[ B@Bq B Ƕ@x@ADAEԁ (a = a"@2Ҁ@&1 'A?z"\i5w`@R5! SAр  B@Bm@x@ADAE aLD =`=$_@"π@ &+ @>A΀  B@B #@x@ADAEmaL (c =B=+$_@@ `@/1 A B@BE@x@ADAEDaC =K$`="@C@!A{SHAKEb l  #]ڞ*DsB@4` -& B@Bp+@x@ADAE^AA =H/$_@?@ `@>AW  B@B@x@ADAEB/aL A =0`=+"@r/`@ @A/1C  B@B@x@ADAEɵLA0}P =u;aI"@ݳ@&}ќuޅK$b.WK+C5ʨ0a& .I B@BJ@x@ADAE3nב  B@B@x@ADAELvaL !1!c =V="@M@ /C^3 !k B@B@x@ADAEHwa" 1c =`="@\@ +#ʴ ?X:P%scsa& GiA$+ B@B$@x@ADAEA2@a = $_@M@ # @>A c @ B@B@x@ADAE A Aa =hǀ=+"@Ǿ@ ! @A/-&3 B@BA@x@ADAEybABQz =`="@2w@ A26xuU%yN4 lɜIW3a& ov$ B@Bm@x@ADAE1aLR`c =|`=+$_@!t@ `@>AsA[ B@B@x@ADAEl.aL aac =48="@/@&/&GG B@B@x@ADAE b9qOm =a$_@@ # @J.j[-6mbFPGάUr?As瀂$ B@B{@x@ADAE]aLrA =`= !@@ ' @>AW A B@B@x@ADA¨EAaL) 9c I="@q@&A/C  B@DBA@x@ADAEZa)om =ta`=" @X@ +"M)ŕ}!f8k&j_L Oa& A H  B@B+@x@ADA E3aL#B =p^`=$_ .`@3y @ U@ ! @>A,  B@BA@x@ADAEaL )c =I='u .`@3a @ G@ W&+ @/WC  B@B@x@ADAEˁ c =IFa!I .`@3a @ ɀ@ @AR'#1$Y<"qEqc1c 2$#%7{s &@/W B@B@x@ADAEaLA)D =IF`=$_ .`@3a @ ƀ@ # @>A@ B@B@x@ADA!EaL m" =I="#@@/D$% B@B@x@ADA&Es B@B+@x@ADA?Ea8@ =$&`="A@1@ }#LC HHJh=ieGO!AB C B@Bg@x@ADADEր dE =!1$_ .`@3f+F @ !@ @+>G H B@BW@x@ADAIEl !!cJ =I8݀="K@Ԁ@ }/ L M B@B@x@ADANE2b"*1*GO ==`=$_ .`@3cP @ @&aH9PEWu,JkBA wDQr$+R B@B@x@ADASE]G>aLA2@dT =IH`=$_ .`@3aU @ @ &+ @>VV@W B@B@x@ADAXEADIaL A*AcY =I N="Z@qE@ /C[ \ B@B@x@ADA]E AB:QD^ =Ua"_@T`@-D~ t4,Q9I1|W3a& IA`G ed a B@B W @x@ADAbE2LR`dc = p`aI"d@_`@ #>Ae+ f B@B@x@ADAgELa:ach =="i@F@&~/Cj k B@B@x@ADAlEpabb qcm =Iwl`= An@n@Ġk-N-9-3S= :'4|\'~;a& Aop B@Bs@x@ADAqE)maLrar =Etw`=$_ .`@3iAs @ k@ PAt Au B@B@x@ADAvE%xaL) A aw =I/="x@'@ /L/Cy&z B@BM@x@ADA{Erဈ*om| =a"}@߀@ #$WĠB[v^[/y>}CjZHa& BA~ހ A B@Bg@x@ADAEݙaLc =`= @v܀@ PAۀ  B@BA@x@ADAEaL *c ==" .`@3f+ @ @ ! @/GnC] B@B@x@ADAEGRaj =IX`=!I .`@3a @ [P@ mX< .ѩ{a;p֠α%z*as yIAO$+ B@Bm@x@ADAE aLAd =IU`= @LM@ ! @>LA[ @ `  B@B@x@ADAEaL c =b="@@&$/eC2 B@B@x@ADAEÁ R =ɲa"@0@ #$Aٜʬ\j#?0[hA{7a& [  B@B@x@ADAE{aLgd =ƽ`="@$@ 'K`.C@Td  B@B m4@x@ADAEkxaL c =?="@y@ !A/4 B@B1@x@ADAE3a D =:`=!I .`@3iA @ 2@ #ZB'?P1bjR7na:i/"Ӏs iAr1$ B@B@x@ADAE\쀈A =I7$_ .`@3a @ .@ ! @+=V A B@B@x@ADAE@ @Y)   A = + ="@p@ .iA  B@B@x@ADAEǤbA =w`="@ۢ@&|r*wxw kM*ZQ ofa& GG + B@Bx  @x@ADAE2]aL d =o`=$_ .`@3d @ ˟@ # @>A+  B@B@x@ADAEZaL !!c =Ic= @F[@&/14C  B@B@x@ADAEa"13D =]`="A@@ :!}ApOIQ@x/ QD]A- B@B@x@ADAE΁ 2@d =Dl@$_ .`@3d @ @ PA@ B@B@x@ADAEʁ AAc = {Ԁ=" .`@3a @ ̀@ U&+ @/ Cˀ B@B@x@ADAEqbB#Q#D =I&`="@@ ҃R%CZTbSa& A񃀂  B@B @x@ADAE>aLR`d =`=+ @u@ # @=ՀA[ B@B@x@ADAE;aL i  a#ac =@E="@<@&/C\ B@B@x@ADAEG b+qc ='a% .`@3f+ @ [@%A X?s4`Y|ժoS_EHa`E}A$ B@B+@x@ADAE(aLra =I2`=$_ .`@3a @ K@ PA A B@B@x@ADAE3aL) +a =Ij="@ŭ@&/R.1 B@B:@x@ADAEh4a3a =n?`="@0f@ #$Az); FO>WzB w6VgDe A B@B@x@ADAE @aLa =kJ`= @ c@ PAb  B@BA@x@ADAEkKaL 3a = 3'=" .`@3f+ @ @@ :! @/KC* B@B@x@ADAE؁ ;;MJVa!I!x`@3a ~@ ׀@ ʿB6Riz5aWs Aqր$+ B@B@x@ADAE\WaLc =Ia`= @Ӏ@"Y @> UA[ B@B@x@ADA E@baL ;c = = @p@ W/NC  B@B@x@ADAEIca#a ={Pn`="@G@%(9_i0Y~[5x?66ّ[AF$ B@B}@x@ =A @E1oaLd =sMy`=+$_+@D@dA*A B@DB@x@ADAE #c =za"@E@ /+C  B@B|@x@ADA E r;t! =La +"@@ Ap аC-2<\P˒)%A# $ B@B@x@ADA%EsaL"& = D`= '@@ ! @JA( ) B@B@x@ADA*EoaL) A4a+ = {y=",@q@ /l C-p. B@B@x@ADA/Eq+aa0 =2`="1@)@ #$҃s;(W!a E7% 8 B@B@x@ADA9E !4!$P: = =";@@ !/+ <\ = B@B@x@ADA>EFb"1c? =`=!I@@Z@ c‘CyJ2 ?ud%Aƙ$+B B@Bm@x@ADACETaL24@cD =`=$_E@K@ ! @>F G B@B@x@ADAHEQaL6hAPA[Q B@B@x@ADAREj€ a/CUWV B@B @x@ADAWE}aLbqaX =`=!IY@|@AĠz]lQq#6=o60`AZq{$[ B@B@x@ADA\E[6aLrd] =`=$_^@x@d:_UA[` B@B@x@ADAaE?3aL/cb = =="c@s4@m(/dߡ +c 0<e B@B@x@ADAfEa%9`u%A>$ B@B+@x@ADAEOm =DL @J<@dA;A[ B@B@x@ADAE) = A =]M @~ W/F0 B@BW@x@ADAEL5A =ϸXa"+@/@&뻲dw9*-t3$ A$ ?a& 3D + B@Bm@x@ADAEjYaL A =õc`= @@dA A B@B@x@ADAEjgdaL !!c =>q= @h@ `/C B@B@x@ADAE"ea"1f+ =)p`="A@!@& -|V9xa8 &?.'h +~`Ap $ B@B@x@ADAE[ۀ2@d =&{a @@dATA[A B@B@x@ADAE?ء @Y AAc = ="@oـ@ -&/LC  B@Bw:@x@ADAEœ|bBQG =z`= +@ّ@&S+qX#'&~VA":yHouAE$ B@B@x@ =A @E0LaLR`d =n`= @Ɏ@dA)A `@  iF B@DB@x@ADAEIaLaaC =R="@DJ@ /cC A B@B@x@ADAEaAb5qj =K `= @@ +QwMѽp§AP(aux@a& 7A B@B+@x@ADAE rd =C @`@dAA[ B@B@x@ADAE鹁L) A5c =À="@@&$/vC B@B$@x@ADAEpub=a =|`=H@Q @s@$ g{Y o߱}>cׂچX#DTg;mr + B@Bp@x@ADAE-aLd =y`= @tp@ @}aL) A == @n~@&/SC  B@B@x@ADAE8 a-&a =y?`="+@6@ e&7(=eC@N#{za& AI  B@B@x@ADAE/  d =m<a !@3@dA") # B@B@x@ADA$E ) !!c% =="&@C@ d-/h2C' a ( B@B`@x@ADA)E n@TA"1.F+* = 0G+`= ++@@ +֕ Ui{G46% I[>@=' xA, - B@B+@x@ADA.Eb,aL2@d/ =B6`= 0@@dA1A[c @2 B@B@x@ADA3E^7n@T AAc4 =h="5@`@ W/}TC6_7 B@B@x@ADA8Eo8aBQD9 =!C`= :@@&jnݞ‹SEIL(M@ I&l yA;-< B@B@x@ADA=EҀAR`d> =Nn@ ?@s@dA@ A B@B@x@ADABEπ aaiAC =ـ="U6@Ѐ@ / CEZF B@B@x@ADAGEEObAbqcH =IZ`= I@Y@ +AُMƐRŋ7ps;4')KgV5S WmAJň$K B@B@x@ADALEC[aLrcM =e`= N@I@dAOA[P B@B@x@ADAQE@faL) cR =\J= S@A@ /ACT/U B@BA@x@ADAVE >&IAW =rn@"+X@.q`@&sK63)ӈExM+*nވ>"@ lUAY AZ B@B@x@ADA[EL.c\ =|aI ]@@ &+ @>^~ _ B@B@x@ADA`Eh}aL aa =5="b@@&/yCcd B@B@x@ADAeEl~a.af =s`= +g@k@ +# @.WݭvfKAJ,e8лP2a& ©Ahoj i B@B+@x@ADAjEZ%aLf+k =p`=$_`3l@g@dAmS An B@B@x@ADAoE>"aL `.cp u`="q@ۀ@liҡbga֟P|}"eկYi8Ma& |*CrD$s B@B@x@ADAtE/aLcu =q`= v@؀@dw(A[Wx B@B}@x@ADAyEaL@YAx>>Jz =@㜀="{@G@  +6h/C| +} B@B@x@ADA~ENad =JU`= @L@ W Ġ["F%0sGab$=M;8A$ B@B @x@ADAEaL>?C =BR`= @I@dH mW B@BW@x@ADAEn@T) c' = = @@ /? B@B| o@x@ADAEoa =a"+@@&#0ME%p͢ߥQ&sXψ:ǮD＀  B@B+@x@ADAEwaL a =`= @s@dAӹ  B@B@x@ADAEtaL?!!/F+ =~="@u@ XZ/1C]$ B@BW@x@ADAED0a"1c =6`="@X.@ Aߛ|*{Vą\(Kf>C9v a& A-  B@B@x@ADAE耈 2@c =3 @I+@dA*A[A B@B@x@ADAE AAAc =[=+ @@P/yC/  B@BP@x@ADAEb}Qa =§`="@-@ y-mhL%c 4r@pa& A@0 B@Bq@x@ADAEYaLAR`a = o@ @@ ' @$=C}@A B@B@x@ADAEhV aL a7aIA =,`="@W@$Ġ/tCm B@B}@x@ADAE aAbqc =`="@@&OB՟W=Dl UC[0-a& To$ B@B@x@ADAEYʀr7c =!a"@ @dAW  B@B 5@ADA @E=ǡ @ '7D = р=$_:@mȀ@&/T١ + B@DB@x@ADAEĂ"bd =l-`="@؀@!+$ȨvRz6ÑK` bUgb+a& L;F+D@0%9`  B@B+@x@ADAE.;.aLA'c =8`=)@}@ @>A( A B@B@x@ADAE89aL 'c =A="@B9@A/C  B@BW@x@ADAE a =QDa"@@ #"F jEi?xm(a& *8A  B@B@x@ADAEEaL'a =AO`=$_@@+ @>^ f!@+ B@B@x@ADAEPaLg/t ==+$_@@%$.C  B@B}b@x@ADAEndQap +@sc = +k\`= @b@&Gp 4*J(:lȋ݀ XZa- B@B(@x@ADAE]aL/c =hg`=$_@z_@ @>A^A[ B@B@x@ =A @EhaL?M =#="@@ 4#V҃/D]$ B@DB@x@ADAEDՁ #d =sa!I@\Ӏ@ +A3.^5D3@SUU AҀ$ B@B+@x@ADAEtaL?C =!~`=$_@HЀ@dAπ$ B@BA@x@ADA  EaL 8A =g`= @‹@&/!?. B@DB@x` =A @EFaL"A =L`="+@-D@!+xЂ똳MF'ˡ$]& ` DC@0A B@DB@x@ADA E c  Ia @A@dA }@  B@B@x@ADAEg) !(!,a"@~%/CA[ B@B@x@ADA`DEL"1D ='@@&S<PZV{[W[9 An  B@B@x@ADAEYoaL2(@c `= @@+ @+=gR  `'%` H B@BA@x@ADAE=laL AAj = v=+'u @mm@ /C! $sd" B@B@x@ADA#E'a")A$ =p.`= %@%@&O9SZ{nU s 1cE{A&C$+' B@Bu$@x@ADA(E. AR`c)l+a$_*@"@ @"`C >+' ` , B@B@x@ADA-E݁ aaC. =="/@Bހ@ /T0 1 B@B@x@ADA2EbAbqA3 =H`="4@@ +x-jψԩd1>FP`Ya& f=56 B@B+@x@ADA7EQaLr8&+8 A`="9@@dA:$; B@B@x@ADA<EMaL AA= =W= >@O@&/li?N@ B@B@x@ADAAEn a :B =`="C@@&4x9>1PwExI6˯1AJ!a& 8GD$E B@B@x@ADAFEcG a)AH@r@dAI J B@B@x@ADAKE@Yl CL =Ȁ="M@쿀@& /CNXO B@B@x@ADAPECzbAQ =p@"R@Wx@ A]/U,T[P,Cz@ kASw mT B@B@x@ADAUE2aLcV } `= W@Hu@dAXt Y B@BA@x@ADAZE/ aL A a[ =Z9=+ A\@0@&/C].^ B@B@x@ADA_E D` =a a@,@ 8v0eO'$T1\MZC#MNAb蠂@4-c B@B@x@ADAdEaL Ae "`= f@ @ 0 @Jg|堂 h B@B@x@ADAiEg#aL Aj = +="k@@#V/1(Cl cm B@B@x@ADAnE[$aAco =b/`="p@Z@ P >l#%J 7XJ/AqmY r B@Bm@x@ADAsEX0aLCt _:`="u@V@dAvQ w B@BA@x@ADAxE<;aL  Ay = =$_mz@l@&/{ $s| B@B@x@ADA}É c~ =wFa"@ʀ@&QܖP5[C)zrgsRa& DC B@B@x@ADAE-GaL 9C kQ`=)@ǀ@ @=g'  B@B@x@ADAERaL6h!1d =HD^`="@;@10=nM`q9WU'LF\3M a& 8~C  B@B@x@ADAE 2@c @Ai"@8@ )A @>}7  B@B@x@ADAE A9Ac = = @@ ! @W/RC  B@B@x@ADAEmjbBQa =u`="@@ (zE(twѫHeva& A  B@B}@x@ADAEfvaLR9`G`=$_@q@ ! @>ѨA[j@ B@B@x@ADAEcaL aaa =m="@d@&/7CX B@B@x@ADAEBalbqa =%`="@V@&K^,qſ\Ω\D אW'A  B@B@x@ADAE׀ra "a"@F@ '>A B@B@x@ADAEԀ !F+ =Zހ=)+@Հ@ ! @+A/ C- B@B@x@ADAEbA =Ж`="@,@ 'AYM3{r*NH==xMˏa& A$ B@B@x@ADAEHaLc`=$_@@ ! @>A| A B@B@x@ADAEfEaL) t = ;O="@F@ /3C B@B"Y@x@ADAEad =`="@`@ +#Z^|5G@j.i8ބm*N0B-m A B@B+@x@ADAEXLc aI$_@`@ # @>AQ  B@BA@x@ADAEA&A[ B@B}@x@ADAE'aL91D =0= @A(@P$/AM  B@B@x@ADAEaL) !"!*C =="@ @&/bCW B@By"Y@x@ADAEBā "1d@a"@V€@ #p6M5oJ\RKjLѺ7{ =A A B@B`x@9 A @E|aL2"@c =$`=$_A@F@ ' @>  B@DBA@x@ADA Ey%aL A*Aa =]=+" @z@ ! @/C - B@Bw@x@ADAE5&aBQd =;1`=!I@+3@&p"chF I{)ҝa& eA2$+ B@B@x@ADAE퀈R*`c =8<$_@0@ &+ @>A{/  B@B@x@ADAEf a a IA =6="@@&/C  B@B@x@ADAE=bbqd =H`="@@ #$$96ofhWPyT?l}*A l$! B@B+@x@ADA"EW^IaLr c# =!AS`=$_$@@ ' @>A%P & B@B@x@ADA'E;[TaL t( =e=")@k\@ ! @A/3C* A+ B@B@x@ADA,EUad- =j``=".@@ Ig(;kvDFx9a& >mA/B@0:+0 B@B-@x@ADA1E,ρ c2 =ka$_3@@ ! @>A4& A5 B@B@x@ADA6E̡ @Y 2a7 =Հ="8@@̀@ /AC9 : B@B"Y@x@ADA;Elbd< = Gw`="=@@&h5;: {-cHi8 A> ? B@B@x@ADA@E@xaL2cA =?`=$_B@@ @>AC D B@BA@x@ADAEE@&/CH=I B@B@x@ADAJEldK =%a L@@ 4' @:N%n?|?Ќ_a& AM-N B@B 4@x@AD>OEװaL:cP = `=$_Q@t@ ! @Aac Yb B@BA@x@ADAcEaL =Ad =](= e@@ ! @҃/Cf,Wg B@B@x@ADAhEځ ANi = a"j@+؀@ 'K$p5.Ecȯ(i*0f݅2; [JAk׀$l B@B@x@ADAmEaL7l#n =`= o@Հ@ ! @>Ap{Ԁ q B@B@x@ADArEeaL) !! As =.="t@@ /fCu"Yv B@B#@x@ADAwEJa"1dx =Q`="y@I@ +#~fC5ԧ H5{4# AzlH { B@B+@x@ADA|EWaL2@3C} =!AN`=$_ +@@3~@E@ # @>AP  B@BA@x@ADAE:aL AAc =AV =+"@k@ ! @A/ס  B@B{@x@ADAE BQc =ja"@չ@ -@}!X 6^D3$ƵDA$ B@B@x@ADAE,taLR`c =i`="@Ŷ@>A%  B@B@x@ADAEqaL a#a+F+ =z=$_@@r@ /S4 A B@B@x@ADAE,ab3q#A = B3r #@*@ u'8C|N:ߘƏnlfyѪT66 xD- B@B @x@ADAE rd =?0a+$_@'@ # @=$&A[ B@B@x@ADAEဈ3c == @@ ! @A/C  B@B@x@ADAElbA;c =`="@@ P_w$OhXɆ@p0 v)] {?cA욀$ B@BP@x@ADAEUaLa = &`=$_@p@ ! @>AЗ A B@B@x@ADAER'aL; ;a =3`="A@U @ ` ՙj$ID@ a꺳qH^C  B@BJ@x@ADAEƀW IA =>a"@D @ !>  B@B@x@ADAEÀ? Wc =à='u@Ā@ $ @Ƕ/FrC+ B@Bv@x@ADAE?bc = ˅J`="@*}@&p_VpܯCjFc=`f Nͥ3_v VTA|  B@B@x@ADAE7KaL ~ =‚U`=H@d_@z@ #>zy  B@B}@x@ADAEe4VaL d =5>=$_@5@&/xC B@B@x@ADAE p;  c =aa"@@A)</m`&ufOie$\a& VAo퀂$+ B@B@x@ADAEVbaLW v; =l`=+$_@@ PAOA[d B@B@x@ADAE:maL 8 == @j@  `@ A/0zC  a B@B@x@ADAE`naAc =ygy`="@^@ Ġu flTqzp?jڿa& AA B@B @x@ADAE+zaL $D =id`= @[@ # @=%  B@B@x@ADAEaL) !!d =="@?@/HC  B@B@x@ADAEс "1c =Jؐa"@π@ '&l &B/싋nn8fuusa& ǸA + B@B@x@ADAEaLA ?i2@,D = 5>՛`="@̀@ !>* ˀ  B@B@x@ADAE䆜aLAAd =="a @3@@ &+ @/1C B@B@x@ADAEkBaBQc = I`="@@@ # n!*˫AjĜg'!0o! Rom?  B@B@x@ADAER<`a =Fa+$_@@o=@ # @Q 2@Bq !@<  B@@B@x@2ADA@> @ +@aad 6@  " 9@~A/uV  B@@B@x@ADA@> @@L}  ' 9@T@  -Zu:ztB@ZbK]J4f OC] $+ B@@B @x@ADA @> @kaLArIA 6 >  `=+$_ 9@D@ ! @>A CǶ0z`@B@x@ADA@> @haL/d 6I\r= @i@ &+ @/5/$ B@@B@x@ADA@> @$aAc 6*`="@*"@ +#ADʜ笂D2'V5Qf\C@@ ܲD!$ B@@B@x@ADA@> @܀<G 6 >  '$_ 9@@ # @=z  B@@B@x@ADA!@> @dـ) Ad" 6I-="#@ڀ@&/*C$$% B@@B@x@ADA&@> @b @UMaLA.O / B@@B@x@ADA0@> @9JaL d1 6T= 2@iK@&/C3  4 B@@B@x@ADA5@> @ac6 6 >  q v"7 9@@&, ,>^@LI`;|IA8@ 9 B@@B@x@ADA:@> @+   l a+$_< 9@@ PA=$ > B@@B@x@ADA?@> @aL a@ 6IĀ= A@?@ :! @C/ޗB C B@@B:@x@ADAD@> @vac(>&@ cTG@1f!@+H B@@B@x@ADAI@> @!T/aLA,IAJ 6 5=>z(`=+ K@q@ @>ALp M B@@B@x@ADAN@> @+)aLAO 65="P@-@ /DQ,R B@@B@x@ADAS@> @k瀈A-cT 6 >  4a$_U 9@@&DF"Nt4 @՟5aL CY 6I?`=$_Z@o@ @>[᠂A+\ B@@B@x@ADA]@> @@aL A!!d^ 6="_@靀@&/dC`U c a B@@B@x@ADAb@> @@XAa" 1cc 6 >  ^L`="d 9@TV@ As4Hz?w;5MboO!ꅜ=$3Kdmo1eU+f B@@B@x@ADAg@> @MaL2@ah 6 >  [W`="i 9@DS@ !>AjR k B@@B@x@ADAl@> @ XaL AAdm 6I[= n@@ A/)o*p B@@B@x@ADAq@> @Ɂ BQcr 6ca"s@)ǀ@ +#!,ɓ"DYΣ-mfF[C@ F+tƀ +u B@@B$@x@ADAv@> @daLR`aw 6 >  n`=+$_Ax 9@Ā@ # @>AyyÀ z B@@BA@x@ADA{@> @d~oaL aad| 6I,="}@@ W! @/C~ B@@Bt-&@x@ADA@> @9pabqc 6 {=@{`="@7@&[)zR0, \۠&f0t ɆAj$+ B@@B@x@ADA@> @U Ar55M 6=a+$_@4@ &+ @`.f+N@T B@@B@x@ADA@> @9  d 6=$_@i@&A/?C  B@@BA@x@ADA@> @bA5c 6 >  t`=" 9@Ԩ@ #!$:%PFα+tzs FH ZA@ B@@B3R@x@ADA@> @*caLf+4 5=h`=$_@ĥ@ ' @>A# B@@B@x@ADA@> @`aL F+ 6i="@>a@&/vC : B@@B`@x@ADA@> @a-A 6="`="@@3RĠ^Ft+\ʨ8j A@4 B@@B@x@ADA@> @Ӂ c 6a"@@ &+>A ` B@@B@x@ADA@> @Ѐ) -C 6ڀ="@Ҁ@ ; &+/Cр B@@B$@x@ADA@> @jb a 6/`=!I@~@ # @_֤zKW ^?e>~W@ Aꉀ  B@@B  @x@ADA@> @DaL-d 6`=$_@v@ # @>A҆ ` B@@B@x@ADA@> @AaL  D 6K="@B@ ! @҃/ =CU B@@B[p@x@ADA@> @? =A 6a"@S`@ @pӗ2&x^bjB?-@ BA$+ B@@B@x@ADA@> @LA 6aI+$_@C`@ ! @>AA ` B@@B +&,+@x@ADA@> @L 6taI$_@,l@ '# X1ZxiS*XYpT޺wkRUCk@4 B@@BW@x@ADA@> @&aL D 6q`="@i@ @>xhA B@@B@x@ADA@> @c#aL !!d 6(-="+@$@ $#V>/jC $M !$ B@@Bq:@x@ADA@> @ށ W"1c 6t@K"@܀@ -&?)HKຝrT֢ٓvG)q1Aj B@@B-&@x@ADA@> @TaL2>@a 6`="@ـ@>N  B@@B@x@ADA@> @8aL) AAd 6 ="@h@ -&/ǹC  B@@B@x@ADA@> @OaB>Qc 6pV`=%@M@$AhR|DWG66)hς:fkB'? `̿ B@@B@x@ADA@> @* aL}.`IA 6gS*`=$_@J@ &+ @>A#  B@@B g@W@x@ADA@> @+aL aad 6= @>@P/ѯD G B@@B| @x@ADA@> @ b.qc 6@6a"@@$ĠIDp O P)lƴvgmKsΧ#`@ 5A- B@@B}@x@ADA@> @x7aLrc 6 >  A ] B@@B@x@ADA@> @uBaLa 6I= @w@ ! @Ġ/Cv B@ ,B@x@ADAEi1Caa ="8N`="A@}/@ 'vܿ芹9mHV^}%9"*jykÊۥA.  B@B@x@ADAE逈:>6IA =5Y$_@34 @n,@ ! @>A +  B@B@x@ADA E怈 Ac =AV="@@ /-CT B@B; @x@ADAE?Zb>c =e`="@S@&=CpMZ+! VяXهi?a& "$ B@B@x@ADAEZfaL>a =p`=+$_@C@ # @=  `m} B@B@x@ADAEWqaL) d =aa="@X@&/X;D)A B@B@x@ADA Era>c! =}`=)"@(@g'㶱k !'W|H8o`B?;08FmA# A$ B@Bm@x@ADA%Eˀt& =a$_`3iA'@@ PA(x ) B@B}@x@ADA*EcȀ@Y d+ = +?Ҁ= ,@ɀ@ &+ @+/C- . B@B@x@ADA/E郉bc0 =`="1@@&$Zt-ƢKd7'RVT05M+K^Ha& [[2i$+3 B@B$@x@ADA4ET'M5 =`= 6@~@ # @=A7M 8 B@B@x@ADA9E89aL : =C=$_+;@h:@"YĠ/< $se`#= B@B@x@ADA>E ?c? =ga"A@@@ '!A}J(a 94DeY#/ԀF+A>@4a B B@B@x@ADACE)aL7 DD =`=$_E@@ @=F" G B@B@x@ADAHE aLA!!dI =ֳ="J@A@ &+/"KL B@B@x@ADAMEeakV"71cN =@l`="O@c@ <>,GS~0t!&y0^` FEDP$Q B@B@x@ADARE`2@S = @i`="T@`@>AU_ V B@B m> @x@ADAWEaL) AAAdX = `$="Y@@ /iCZ~[ B@Bu  J@x@ADA\EiրBQc] =a%^@}Ԁ@&'8 >9Mf0S DU<*a& b_Ӏ ` B@B@x@ADAaEԎaLR/`ab =`=$_c@mр@ &+ @JAdЀ e B@BA@x@ADAfEaL aadg == h@茀@%/ iT j B@B@x@ADAkE>Gab/qcl =M`="m@RE@ +#!$iTFn2%V,<Pkc juda2)a& DfF+nD$o B@B+@x@ADApEr7Mq =J$_r@FB@ ' @>AsA t B@B@x@ADAuE dv =U"w@~ W! @҃/x) y B@BW@x@ADAzELc{ =̾ u@"A|@'@ PBi.;˿$ i:@ w  @  B@B@x@ADAEbmaL d =7w="@n@ /p  B@B@x@ADAE(aA'c =/!`="@&@&JǷ m7_VTaJUt?>= x˦6hMDi B@B@x@ADAES 7c =,,a"@#@ #>AM  B@B@x@ADAE7ށ @Y) a = ="@k߀@"YA/Cס + B@B}@x@ADAE-bo$7a =o8`=*@֗@&.Z_-e+|9AQFB{a& ]*B  B@B@x@ADAE)R9aL7/M =jC`=$_@Ɣ@ PA& + B@B@x@ADAE ODaLc =X="@AP@&/0<$ B@B@x@ADAE Ea7c =CP`="@@&N,LC4 N…iJ%oq8ƾa&   B@B@x@ADAE $0D =;[ @@dA  B@B@x@ADAE AAA =ɀ="@@ +*'mA/~ + B@B}@x@ADAEh{\b08 =%g`=!I@|y@&J؟3#%!G)OLs}D4ga& dmJx$+ B@B 4@x@ADAE3haL C =r`= @mv@ PAu  B@̠B@x@ADAE0saL !0!c =:="@1@ /S B@B@x@ADAE> "1a =~a"@R@ P#$+KǘLZ =j( F+ϓB@[Wa& /ZD适$ B@BP@x@ADAEaL2@D =`="@E@dA怂  B@B @x@ =A @EaL) AAc =X="@@ }/?( B@DB@x@ADAE]aB$)* =c`=$_A@'[@&M%Wvs\ƌtw(LPa& iSZ + B@B@x@ADAE}aLR`A =``= A@X@ &+ @"`^=JwW  B@B@x@ADAEaaL aaA =.="@@&/wiS  B@BDK@x@ADAÉ bqf+ =ԭa"@ˀ@ #$AK+_yk=R{dqS#g riSh  B@B@x@ADAESaLrC =Ѹ`=$_+@Ȁ@ ' @>L  B@B@x@ADAE7aL; c =zE`=+"@<@ ! @/Y= B@B@x@ADAE(  a =fB"@9@d!  B@B@x@ADAE @Y D =="@<@  +=  B@B@x@ADAEb c =;`=+$_@@  ǶZ{y,3@C s@{skдG = K@1  B$@x@ADAEgaL8C =`= @@d  B@BW@x@ADAEdaL) A =n="@f@ /oC }eA[ B@B@x@ADA Eh ac ='`= @|@ 3"p) j/_yh Ha& koA W B@B| @x@ADAE؀8C =$ @p@ +# @>A[ B@B@x@ADAEՀ A =߀="@ր@ !#VA/CCR B@B@x@ADAE=v@T8c = `="+@Q@ASꏮŊl~KjK43ͱ,K@  hA  B@Bx@x@ADAEI aL( C =`=$_!@A@ &+ @>" # B@B@x@ADA$EFaL F}A% =\P=$_&@G@ /DC'(( B@B@x@ADA)Ea})c* =#`="++@&@ +#!Ġ-{pV =vE`= ?@m@dA@K A B@B6h@x@ADABE6(FaL AAdC =2="D@f)@ W/wpCEҡ WF B@B@x@ADAGE B QcH =rQa +I@@ yyqE^cLɧ##7iͲa& gJ=K B@B+@x@ADALE'RaLR!`GM =e\`= N@ހ@dAO!A[P B@B$@x@ADAQE ]aL aaAR =ܢ= S@;@&A/)T U B@B@x@ADAVET^ab!qcW =B[i`="+X@R@ +Y_'.JDOd@h "ha& 7F+Y +Z B@B+@x@ADA[E jaLr9!C\ =:Xt`= ]@O@dA^N _ B@B@x@ADA`E uaL Aa == b@ @ WA/4Cc} d B@B@x@ADAeEgŀ9cf = ̀a"+g@{À@%o҃~U0_^v{1^jQ2hOZAh€-i B@B#@x@ADAjE}aL#9Ck =ɋ`= l@l@dAm˿ n B@B@x@ADAoEzaL dp == q@{@&A/CrRs B@Bt @x@ADAtE=6aAcu =<`="+v@Q4@ Ġ_*8 DRb=$P\3rn9Aw3$x B@BC@x@ADAyEDz =9a {@A1@dA|0A[} B@B@x@ADA~E뀈 !8 = S="@@"Yg/C' B@B@x@ADAEbc =`= +@&@&75:Fzb7.zsncā>R2A$ B@B@x@ADAE|_aLc =`= @@dAvA[ B@B@x@ADAE`\aL) a = )f= @]@&/C  B@B@x@ADAEaa =`="+@@&m : z%6پSݏ|_a& Ag  B@B@x@ADAERЁ G = @@dAK  B@BA@x@ADAE6́  a = ׀="@f΀@ /gC  B@B@x@ADAEba =m`="+@І@&$ĥBV%YbfVb bQΘ.<- B@B@x@ADAE'AaL D =e`= @@dA  B@B@x@ =A @E >aL!!c = ?=G= A@;?@ /Y  B@B@x@ADAE A" 1c =:a"+@`@! ƹA#l^o2P9.K"Pf"{mSF+@4WA B@BqJ@x@ADAEL2@ =>w@ @@ @=#@ B@B@x@ADAEaLAAd = ="@@&/C| B@B@x@ADAEgjaBQc =q `=$_ 4@{h@ g"*F"H O: Iw4a%rg@4 B@B@x@ADAE"aLR2`c =n`=$_@ke@ ' @>d  B@B@x@ADAEaL) aaa =z)= @ @ +! @$/}rQA[ B@B@x@ADAE<ہ b2qa =$a"+@Pـ@ b zhҺDW*x ^L疧tn|,)F+؀  B@B@x@ =A @E%aLrf+ =/`=$_@@ր@ ! @>AՀ  B@DB@x@ADAE0aL c = W=$_@@&/C'A B@B@x@ADAEL1a2a =R<`="+@%J@ &#!gʿ&~ٰQCl w(a& AI$+ B@B@x@ADAE|=aLa =OG`=$_@G@dAuF  B@B@x@ADAE`HaL 2a = 8 ="@@ iSA/SC  B@B@x@ADAE漁 2"U =Sa$_@@ ̖85>Q^ L"0{@9HA]|a& yf A B@BJw@x@ADAEQuTaL$c =^`= @귀@dAJA[W B@B@x@ADAE5r_aL2c =|="@es@ W/3y ? B@BA@x@ADAE-`a :D =e4k`= @+@ /c$zDNHtasAC͞F+ A B@DB ?@x` =A @E 6h c = ?=9a"@@ +!#VW#%Eʢ/߱3ehN>'\j C  B@B@x@ADA EVaLW2D =`="A @@ $ @>   B@B@x@ADAESaL W8 = ]="@U@ ! @ՙ/C|T  B@B@x@ADAEfaa =`="@z @Ġ=Re}1C52n%$}NI6Cda& w$  B@B@x@ADAEǀ a =$_@k @ &+ @> A[ B@B@x@ADAE !!a =}΀=" @ŀ@Ġ/>}P!Q " B@B@x@ADA#E;b"1a$ =䆱`="%@O~@ #+^'( N`kp(ɗ1\BE]BF+&}@4' B@B}@x@ADA(E8aLW2@a) =胼`=$_*@C{@ ' @>A+zA[A, B@B}@x@ADA-E5aL AAa. =N?="/@6@/nC0&1 B@BA@x@ADA2E k @sBQa3 =a"4@%@ARaM40ccj5In@ \A5$6 B@B@x@ADA7E{aLR`a8 =`="9@@dA:u렂A[; B@B@x@ADA<E_aL)Axaaa= =@,=$_+>@@ &+ @+A/C? @ B@B@x@ADAAEaabqaB =h`="C@_@ :Ar<SbC[Q*įzkCC'͟3a& M[ADf AE B@B:@x@ADAFEQaLraG =e`=)H@\@ @= IJ AJ B@B@x@ADAKE4aLaL =!=$_M@e@0<Ġ/wCNѡ O B@B-&@x@ADAPEҁ aQ =ga"R@Ѐ@& _:e j <09na&  AS;$T B@B@x@ADAUE&aLaV =cx@$_W@̀@dAX@AY B@B@x@ADAZE aL Aa[ = ґ="A\@:@&/_ C] r^ B@B@x@ADA_ECaa` ==J`= +a@A@&|+R#n6hX'f Ab`4c B@BiS@x@ADAdE ae =9Ga f@>@dAg= Ah B@B@x@ADAiEaj =x@"k@~ /Cl{m B@B@x@ADAnEfLAao =&&'p@z@y҃z*2()ւ~ɭxaq!Aq汀$r B@By@x@ADAsEl'aLat =1`="u@j@dAvʮA[w B@B@x@ADAxEi2aL) Aay =s= z@j@ #/ C{P| B@B  @x@ADA}E;%3aq a~ =+>`="+@S#@  aN9D'xkxj?d W7na& A" A B@B@x@ADAE݀A =(Ia A@? @dA A B@B@x@ADAEڀ a =V="@ۀ@ :/XC% B@Bc'@x@ADAEJbAa =̜U`="@$@ "yX ۍ~R:VU"a& A  B@B@x@ =A @E{NVaL a =``= @@dAtAA B@DB@x@ADAE_KaaL !!a =7U=+ A@L@ /(C  B@B@x@ADAEba"1a = m`= @@ msZ>鮾H6*5ݸAxa& Ae$+ B@Bu@x@ADAEP 2@a = xa @@dAI  B@B@x@ADAE4 AAa =ŀ="@d@ /:C  B@Bt@x@ADAEwybABQa =c~`="@u@$ U!FtsoOag#ܣ^Ş/zA;A[A B@B@x@ADAE%0aLR`a ={`="@r@$=gA[ B@B@x@ADAE -aL) Aaaa =6="P@9.@+ @g/C  B@B@x@ADAE bqa =Ha"@@ ARqkA7*Z#ϯ Ara& ~A怂  B@B7@x@ADAEaLCra =8`=$_@@ @=   B@B@x@ADAEޝaL ) a =="@@ A/3RC~ B@B@x@ADAEeYaa =``="@yW@&$Ǧ@ +_ fsx/"Һ I_"a&  oAV  B@B$@x@ADAEaLa = ]`=$_`3@iT@ &+ @>AS  B@B@x@ADAEaL a ==+)`3a@@& /CCP B@B @x@ADAE:ʁ a =a @NȀ@&Z,sl_Łҫ/@M߹%#k[pAǀ$+ B@B@x@ADAEaLAa =`=$_@>ŀ@ ' @>AĠA[W B@B@x@ADAEaL a =U="@@ !#VJ/C% B@B@x@ADAE;aAa =A`="@$9@g:軓Jx ( у ,ja& A8@0 @  4 B@Bm@x@ADAEza =>a"@6@ PAt5  B@B@x@ADAE^ a =#="+@@ />C + B@B@x@ADAEba =`="@@ #!+ncFAXVŷC͆b pAe`4 B@B@x@ADAEOdaLA =y@)@馀@ @>AI  B@B@x@ADAE3aaL aj="@cb@&/m\C  B@B`x@9 A @Eaa =j#`="@@ VPSS¬{Άа_$` : + B@DB@x@ADA E%Ձ $ a =b $_ @@ ! @>   B@B@x@ADAE ҁ  !!a =ۀ=+'u@9Ӏ@& /   B@Bwg@x@ADAEb"1a =<(`="@@ +#!CJiO;n#a& -+ B@Bu@x@ADAEE)aLA2@a =3`=$_@@ ,W @>A󇠂A[ B@B@x@ADAEB4aLAAa =L="@D@ !/)5IA zC! B@B@x@ADA"EeABQa# = @a"$@y?`@&` a(^,Kib1ca& A%@0A& B@Bm@x@ADA'E϶LR`a( =KaI")@hJ`@ &+>A*A[+ B@BA@x@ADA,EL; aqa- =uWaI".@Nm@ /`C/l 0 B@B@x@ADA1E'XaLra2 =rb`="3@>j@ !>4i 5 B@B@x@ADA6E$caL Wa7 =M.="8@%@ $P59$A[: B@B4@x@ADA;E a< =na"=@#ހ@ #',3 B)B<1\uN^OV>Ns>D>݀ ? B@B@x@ADA@EzoaLaA =y`=$_WB@ۀ@ # @W>Csڀ D B@B@x@ADAEE^zaL aF ="=+"G@@ ! @/CH mI B@Bom@x@ADAJEP{aaK =W`="L@N@ ĠK>M0>y0&_$-jlbVa& #AMd$+N B@B@x@ADAOEO aLAaP =T`=$_Q@K@ &+ @>ARHA[S B@B@x@ADATE3aLaU ==+$_V@c@ /^CW X B@B@x@ADAYE AaZ =jȝa"[@ο@$ĠM@<}?x;pX2$8"Ċ?3a& T}A\9 A] B@B@x@ADA^E$zaL:a_ =fŨ`="`@@ #>Aa! b B@B@x@ADAcEwaLad =Հ= e@8x@PA/QCf g B@BP@x@ADAhE2ak?ai =?9`="j@0@&Y҉:j/fJ )Lpa& -k+l B@B@x@AD>mE An =76$_o@-@ PAp, Aq B@B@x@ADArE瀈) as =="t@ @&A/Duy耂v B@B}@x@ADAwEdb-&ax =`="y@|@ +#&lWA˺Derw?D |Abr|?a& vAz蠀 A{ B@B+@x@ADA|E[aL a} = `= ~@h@ PAȝ `au" iF B@BA@x@AD&+EXaL !!a =b="@Y@ !A/CO B@B@x@ADAE9a"1a =`="@M@&TpE2R!eJsVZKCca& A$+ B@B@x@ADAÈ2@a = a"@A@ &+$+=C B@B}@x@ADAEɀ AAa =TӀ=$_@ʀ@&/5,C$ B@B@x@ADAEbBQa =`="@"@&^\X;}Yyg C&Za& A A B@B@x@ADAEy=aLR`a = z +$_@@ ' @>r B@BA@x@ADAE]:aL aaa =*D="@;@ !$҃/C  B@B@x@ADAE bqa =a"@@ ?'NJytN\ j#{!S[uAd B@Bm@x@ADAENaLra =`=$_@@ ! @>AH  B@B@x@ADAE2aL) a =="@b@ ?/YC  B@B@x@ADAEfaa =im*`="@d@ +#]Tz`+.+z?xÔ naa& $A9 A B@B+@x@ADAE$+aLa =aj5`=$_@a@ # @>A  B@BA@x@ADAE6aL a =%=+"@8@&/д  B@B@x@ADAEׁ a =GAa"@Հ@ 1+"^8Cx%+<ѕa& ռD- B@B1@x@ADAEBaLa =6L`="@Ҁ@ !=р B@B@x@ADAE݌MaL a == @ @&/#Cy B@B@x@ADAEcHNaa =OY`="@wF@M#!1ư׹dlрL,o-EQeˎAE$ B@B@x@ADAEZaLa = Ld`=+$_@gC@ PABA[  B@B@x@ADAEp= a =e"@~ ! @/=CR$ B@Bt@x@ADAE9LAa =p'@M@%QVMNGg ,г̹ANA@4P B@B@x@ADAEqqaL ={`="@=@ &+$>A  B@B@x@ADAEn|aL) a =Lx="@o@ /C#A[ B@B@x@ADAE*}aa =0`="@"(@ +#+A:Cy֖Y*DFdzva& _JA'  B@B+@x@ADAEy  a =-$_@%@ @>r$  B@BA@x@ADAE\߀?!!a =*=+"@@0AG  B@B@x@ADAE2PaL AAAa =Y= @bQ@ WA.uL  W B@B@x@ADA E aBQa =h`="@ @&v|kѬx*9ߍrvYa& *MC8$ B@B@x@ADAE#ā R`a =aa+$_@@ # @>AA[W B@BA@x@ADAE aaa =ʀ= @7€@ ! @Q`BA/4@G B@B@x@ADAE|bAbqa =F`="@z@ AX@3@OxyH_K^ኜ_~Ua& ңA$ B@B@x@ADA!E4aL$ra" =:`=$_#@w@ ! @Q`AB?A$v % B@B@x@ADA&E1aL) Aa' =;="(@ 3@ /$A)x2* B@B@x@ADA+Ec퀈a, =a"-@w@&C6 /zs4Z iyO]fbe׏a& ZA.ꀂ m/ B@Bm@x@ADA0EΥaLa1 = `="2@g@ #>A3瀂A[A4 B@B@x@ADA5EaL a6 =z=)+7@ᣀ@ ! @+/s8M9 B@B@x@ADA:E8^aa; =d`="<@L\@ '$ T&'um"XWY1P!2 5{=[ > B@B@x@ADA?EaLa@ =a{ +$_A@BX AC B@B@x@ADADE aLaE =`="F@%̀@&g eElSF4RcI:f!%rYG̀$H B@Bg@x@ADAIExaLWaJ =`="`3SK@ʀ@ &+ @>Luɠ M B@B@x@ADANE\ aL}aO =5= P@@ W$ @>/JQ +R B@B@x@ADASE?!ao(aT =F,`="U@=@ iS#B:kNJK;5;\OpIAVg$W B@BiS@x@ADAXEM  zAY =C7$_Z@:@ # @= [G \ B@BW@x@ADA]E1 @Y WA!^ = ="_@a@ !/teC` Wa B@Bs@x@ADAbE8bWAc =dC`="d@̮@g(t!p'k遾:KfU8ZyB&a& nAe8 +f B@BW@x@ADAgE"iDaL Ah =`N`=+$_i@@ &+ @>Aj k B@B@x@ADAlEfOaL!!Am = o=$_n@6g@ :/BCo p B@B:@x@ADAqE!Pa"1Ar =>([`="s@@ #!ĠفbvVeb@^Ⱥ!}AHt u B@B@x@ADAvEف 2@Aw =9%fa+$_x@@ # @>AyA[z B@B}@x@ADA{E AAA)A| =="}@ ؀@ ! @ |1iO~x׀  B@B@x@ADAEbgb}!! =! r`="@v@ Nֲ)>!.d hگ$ՃD GYս 'F+⏀- B@BA@x@ADAEJsaLAR`A = }`=+$_@f@ ! @"`C >Aƌ ` B@B@x@ADAEG~aL aaA =zQ=$_@H@ /2 AM B@B@x@ADAE8aAbqA = `="@L@ +#!AIHgj uf M51t8a& {A$ B@B @x@ADAErA =$_@<`@ # @>AA[ B@B@x@ADAEL A = S€="@@ W!A/C" B@B0<@x@ADAE tbA =z`="@!r@ ҸR-Bo5Tk {c3AN\2 f+q$ B@B@x@ADAEw,aLA =w`=+$_@o@ ! @>Aqn  B@B@x@ADAE[)aL) A =/3="@*@&/WlW  B@B@x@ADAE A =a%@@ # @A5wx料 r e-5t<yF+b  B@B@x@ADAEMaLA =`=$_@߀@ ' @>AF  B@BA@x@ADAE1aL A =="@a@ :! @҃/3C  B@B@x@ADAEUaA =g\`="@S@ y.8%~G \Gq  ja& A7- B@B@x@ADAE"aLgA =`Y`=+$_@P@ ! @>AA[ B@B@x@ADAE aLA = ="@6 @ /=C  B@B@x@ADAEƁ AA =Ea%@Ā@&m~حI6,R$fG 91 SU @3A B@B@x@ADAE~aL A =5`=$_@@ # @>A A B@B@x@ADAE{aL A =="@ }@&/Dw| B@B@x@ADAEb7aa =>`="@v5@&?ӗwA9; I sJO9&A4$ B@B@x@ADAE a = ; | "@f2@ PA1  B@B@x@ADAE쀈) !!a =="@@ #&+'+/CL B@BR.@x@ADAE7 b"1F+ = `=!I@K@ # @ n1({Qxq0  A A B@B@x@ADAE`aL2@a =!`=)@?@ # @>A  B@B@x@ADAE]"aL AAa =Vg="@^@%/C"J B@B`@x@ADAE #aBQa =.`="@ @ m al̎֓ؑS[; @Zۑia& rA$+ B@B; @x@ADAEwрR`a = 9a+$_@@ ! @`z.Ap   B@B@x@ADAE[΀ aaa =#؀="@π@&$/=^A  B@B@x@ADAE:bbqIA =E`= + @@& 㘁gEVg @"CXa& A a A B@B@x@ADA ELBFaL$rA =P`=$_@愀@ PAEA[ B@B@x@ADAE0?QaLa =H="@`@@ !#V+/C  B@B@x@ADAE a = c]a"@\`@ +'A^9v_!|۝%:OxS`Ni1A  A7+ B@B@x@ADAE!LD =_gaI"@@ !$>A  B@B@x@ADA EhaL) Aa! =ι=""@5@&/,# $ B@B@x@ADA%EkiaC& =9rt`=$_'@i@ # @qbgj:_?{ya& %o( A) B@B@x@ADA*E#uaLa+ = 4o`=$_,@f@ ' @>-e . B@BA@x@ADA/E aL a0 =*="1@ "@ ! @$/3%o2w!3 B@B@x@ADA4Ea܀D5 = a"6@uڀ@ m ">R H|=Q a& xIA7ـ-8 B@B@x@ADA9E̔aLA: = `=$_;@e׀@ ! @>A<ր = B@B @x@ADA>EaL a? =x=$_@@@&/CALB B@B@x@ADACE6MaaD =S`="AE@JK@& zo&AKGA[L B@B@x@ADAMEaL aN =R ="O@@ !$A/ԁCP!Q B@By + +P 5@ADAR @E aS =ĺa"T@ @ P'Ba71V-'XoG%l]1` qAU$V B@DBP@x@ADAWEvvaL aX = `="Y@@ !>AZp A[ B@B@x@ADA\EZsaL) !!a] =+}="^@t@&/#]C_ ` B@B@x@ADAaE.a"1ab =5`=$_c@,@&~[?lS: ʵ-uz. CN. Ada e B@B@x@ADAfEL 2@ag =2a$_h@)@ `@=giE j B@B@x@ADAkE0  AAal == m@`@ ! @A/I/Cn o B@B@x@ADApEbBQaq = f`="r@ʝ@ 'A)jP!{4'DR ;As6t B@B@x@ADAuE!XaLR`av =^`= w@@ ! @>Ax y B@B@x@ADAzEUaL aaa{ =^="+|@5V@&/C} ~ B@B@x@ADAEabqa =8} "A@@Hu#!\-r(d@>Bʭ?u%xL3 q@ A   B@B@x@ADAEŀa =π="@ ǀ@ !A/`Cvƀ8\ !kW B@Bg@x@ADAEa bAa =`="@u@%M`KyOx=)մa& A~$ B@B@x@ADAE9aLa = #`=+$_@h|@ &+ @>A{  B@B 5@x@ADAE6$aL) Aa =@="@7@ /CK B@B@x@ADAE6 a =/a%@J@ +# @AELnw"r0 A @ XF B@BA@x@ADAE0aLa =:`=$_@:@ # @>A쀂  B@B$@x@AD>E;aL a =Q="@@ !A.9C  B@B@x@ADAE cAo]  B@B@x@ADAEZSaL a =&"=$_@@ ; /sC N B@B@x@ADAEӁ a = ^aG   @р@ P# @{ @ou|.ky^W‚P%bA`$+ B@B@x@ADAEK_aL)A =i`=$_@΀@ # @>AD N B@B@x@ADAE/jaLa =="@_@ !A/MC  B@B} m@x@ADAEDkaAa =!fKv`="@B@ +ac&IOYȮ&þʩ7[ A6 B@B+@x@ADAE  a = ^Ha"@?@ !>A  B@B@x@ADAE @Y A!!a =a"@4~ /qC  B@B@x@ADAEL"1a = 4a%@@ # @A,hy MBQ ZsfČm8s A @2 B@Bo@x@ADAEmaL2@a =3`=$_@@ # @>Aﯠ  B@B@x@ADAEjaLAAa =t="@ l@ +! @/PCuk ` B@B@x@ADAE`&aBQa = -`="+@t$@ +B6+O28܎(,+Mg#  B@B+@x@ADAEހR`a = *$_@d!@ ! @>A  B@B@x@ADAE Aaaa ={="@܀@ W/NK  B@B@x@ADAE5bbqa =ޝ`="+@I@+Ae7&C}Njj֒h TW^a& g+@0+ B@B+@x@ADAEOaLAra = ޚ`=$_@:@dA   B@x@ADAELaL a =HV="@M@ /1 W B@B/@x@ADAE aAa = `="+@@&<nΏ"2.\{={/龜1kx ~J $ B@B@x@ADA Eua = a @@ PAn + B@B 5@x@ADAEY a =*ǀ="@@&$/ A B@B$@x@ADAExba = `= +@v@ # @Wr_Qϔ*F[3t!P2 zD`@2 B@BA@x@ADAEJ1aLa = |`= @s@ ' @>D  B@B@x@ADAE..aL a =7="!@^/@ +!/VC" # B@B[@x@ADA$E a% =a~ "&@@& e,9o+~ 7{I2ηv)@ A'5 ( B@B @x@ADA)E aLa* = ] `=$_W+@@ &+ @>, - B@B@x@ADA.EaL a/ =̨=$_0@4@&*/\C1 2 B@B@x@ADA3EZapt a4 = ;a`= 5@X@&8gA=Ǹ$ 'lC}In6LrF@ rt6-7 B@B@x@ADA8EaLAA9 =3^%`=$_:@U@dA;T < B@B@x@ADA=E&aLa> == ?@ @ A/z:@uA B@Bs  *@x@ADABE`ˀAaC = p1a"+D@tɀ@ '!z~[Z)hpFW4"!f%oEȀ AF B@By@x@ADAGEʃ2aL aH =<`= I@cƀ@dAJŠA[K B@B@x@ADALE=aL A!!aM =z="N@ށ@ /"/IAOJP B@B@x@ADAQE5<>~@T"1aR =BI`=$_S@I:@ "YIP| -.M{}@XmD' peAT9$U B@BJ@x@ADAVE2@aW =?T X@97@dAY6A[Z B@B@x@ADA[E) AAa\ =T="]@@&$/x^_ B@B@x@ADA`E UbBQaa =``="+b@@ uo ~]:֫{P[O< R-YJ.CZKDc Ad B@B"Y@x@ADAeEueaaLR`af =k`= g@@dAhn Ai B@BA@x@ADAjEYblaL aaak =!l= l@c@&/gCm n B@B@x@ADAoEmaqap =$x`="+q@@! iWn6 x#,.Y+^V )-Ar_@1s B@B@x@ADAtEJց rau =!a v@@dAwC x B@B@x@u =Ay @E.Ӂ Eaz =܀="{@bԀ@/!6|ΡA} B@DB@x@ADA~Eba =i`=!I@Ȍ@ A=*a+ƺ_BM KX晌'wA4  B@B@x@ADAEGaLa =a`= @@+ @+=7 + B@B@x@ADAEDaL Aa =3`= @`@ՙT—̀n˅Ql{@5ZM{L68a& T{C @0 B@B@x@ADAELa =2aI"@`@ @>}A[ B@B@x@ADAEشLAxa =@= @@ @K/WCt  B@B@x@ADAE_pba = w`="@sn@ $ĠJI3J(,Ǽ YB(JEp%]{0a& 4Am  B@B@x@ADAE(aLWa =t`=$_@ck@ @=j  B@BW@x@ADAE%aL a =z/=$_@&@PĠ/CJW B@B:@x@ADAE4 a =a"+@H߀@3RĠlȚ~NsxuD_8WI%`u$W#W7ހ$+ B@B`@x@ADAEaLA =`=$_@9܀@dA۠ A B@B@x@ADAEaL a =W="@@vĠ/LD B@B]}@x@ADAE Raa =X`= +@P@gĠG`rTqGw3!}wv a& AO$ B@B}@x@ADAEt aL-& a =U`= @ M@dAmLA[A B@B@x@ADAEXaL !!a =$="@@  Ġ/oC  B@BA@x@ADAE A"1a = "@@ lٶ-?cY4^,!56@ VA_ B@B@x@ADAEI{aL2@a =`="@㽀@dACA[ B@B@x@ADAE-xaL) AAa == @]y@&/C A B@B@x@ADAE3aBQa =i:`="@1@ :.v㿄/=sELҠ + a& 4 bNJ? B@B:@x@ADAE R`a =\7' A@.@dA A B@BA@x@ADAE @Y) aaa =="@3@ 4A/D  B@B@x@ADAE(bbqa =53`="@@&$6=ދV}ÜZQB|X!$-5#C(  B@Btm@x@ADAE\4aLra =1>`= @@dA힠A[ B@B@x@ADAEY?aL a =c="A@[@ /*CtZ B@B@x@ADAE^@aa = K`= @r@ AG{9zGmW:ru lnڑ A- B@B@x@ADAÈa = Va @f@dAA[ B@B}@x@ADAEʀ a =uԀ="@ˀ@ /eI B@B@x@ADA  E4WbAa =b`="@H@ +4wI:zHBk\ʵ*w5jM@ oXZ$ B@DB@x` => @E>caL$a = ?=܉m`="@8@dA B@B@x@ADA E;naL) a =KE= @<@&/^  B@B@x@ADAE a =ya"@@&xiPV):&i<im,̢q7a& IA  B@B@x@ADAEszaLa =`= A@@dAq  B@B@x@ADAEWaL a = ="@@&/yu  B@B@x@ADAEgaa = n`=" @e@&RQ ~G"f=YѐvFߨD!^ " B@Bx@x@ADA#EI aLA$ =k`= %@b@ * @ >J&B ' B@B@x@ADA(E-aL a) =&=+ A*@]@ /AC+ , B@B7@x@ADA-E؁ p-&a. =lߨa /@ր@&A?9mȋ ƒ`Gha& `A07-1 B@B@x@ADA2EaL a3 =\ܳ`=$_ 4@Ӏ@ # @>J5A[6 B@B@x@ADA7EaL!!a8 =җ="9@2@ !#V/: ; B@B#@x@ADA<EIaA"1a= =5P@">@G@&Ons,ρpAzG/gO(@ iA? @ B@Bm@x@ADAAEaL2@aB =1M`="C@D@dDCA[E B@B@x@ADAFE) AAAaG =$_+H@@&/iAIs J B@B@x@ADAKE^ BQaL =  a"M@r@&k ;kź3HJ IAN޷ O B@Bm@x@ADAPEraLR`aQ =`="R@b@dAS´ T B@B@x@ADAUEoaLaaaV =qy="W@p@$Q`%c A/ @GXH `+Y B@B?@x@ADAZE3+abqa[ =1`="\@G)@&i WDZW85lrh¯:+a& J]( ^ B@B@x@ADA_E〈ra` =. a@7&@ Pb% c B@B@x@ADAdE Aae =N=+$_Af@@&/5Jg h B@B@x@ADAiEbaj =."k@@&} QW@ g<9,mLP*XYa& [F+l$+m B@B@x@ADAnEsTaLAao =`= p@@ 0 @= qlA[r B@B@x@ADAsEWQaL at =#[="u@R@ !$҃.1wCv w B@B@x@ADAxE aAay =`="z@ @ +''# az~bW^\;k^ʅ}5< A״{3;XA{^| B@B+@x@ADA}EHŁ a~ =)a"@@ !>A  B@BA@x@ADAE,  a =ˀ="A@\À@&/RtC  B@B@x@ADAE}*ba =c5`="@{@&5cg_w,&]u'ia& ^A3+ B@B@x@ADAE66aLa =[@`=$_@x@ ' @=A  B@B V@x@ADAE3AaL 4) a =<="@54@&/C + B@BW@x@ADAE a =4La"@@ '&lAos#e&IMTqc%=N\[>OHa& "Y  B@B@x@ADAEMaL9A =0W`=$_@@ ! @=耂  B@BA@x@ADAEףXaL a ==+"@@li/s(s B@B@x@ADAE]_Yaa =fd`= @q]@ @Ġ/EDa+s腳\hVK*#4a& _F+\@0W B@B@x@ADAEeaLA a =co`=$_@aZ@ PAYA[ B@B@x@ADAEpaL?!!a =t="@@ !/CLA[ B@B7@x@ADAE2Ё "1a = {a"@F΀@&A Δ!(1D0M b#À  B@Bg@x@ADAE|aL2@a =ӆ`="@6ˀ@ PAʠA[ B@B@x@ADAEaL AAAa =J=$_+@@ /CA B@BW@x@ADAEAaBQa =G`="@?@ &#! A/w%/W-y0 =R7>$ B@BJ@x@ADAErR`a =Da A@ <@ `@>l;  B@B@x@ADAEV) aaa =#a"@~&/ C  B@B@x@ADAEݱLbqa ='@@ +hj22otU6Tqw'6`z k PccMF+] A B@B+@x@ADAEHjaLA  gxra =`= @嬀@ ! @>E  B@BA@x@ADAE,gaLAxa =@p=+'ua @3@\h@&/C  B@B@x@ADAE"aa =AVg)`= @ @ v# @҃gس;WߣEX uXUp7@ d2- B@BA@x@ADAEہ $a =@逃^&$_@@ PA B@B@x@ADAE؁  a =="@1ـ@ W!/d  B@B@x@ADAEba =?`="@@&Ep-~C ѣ4M~~ӂ!l0na& 6F+$ B@BE@x@ADAEKaLAa =0`= @@ &+ @+=덠 C B@BA@x@ADAEHaL$a =R=$_+@ J@ /-CvI B@B@x@ADAE]ao a  `="@u@ +#!A48S- !l<vHBM&Oůy ͽA$ B@B{ XGa`x@9 A @EǼa = $_@e`@ # @>  B@DB@x@ADA ELa =À=" @ߺ@ W!A/;C K + B@BW@x@ADAE2uba = { #@Js@ 7ɆR:<F1/ÜY Ar  B@B @x@ADAE- aL zA { =x`=$_@6p@ ! @>Ao  B@BA@x@ADAE*aL A! = Q4="@+@ /A%k & B@B@x@ADA'EV+aL !!A( ==")@@% /d* + B@BpW@x@ADA,EV,a"1A- =]7`=".@T@ĠrxT75chY' t]n7a& "Y/\-0 B@B@x@ADA1EG8aL2@A2 =ZB`=$_3@Q@ PA4@A[*5 B@B@x@ADA6E+ CaLAA)A7 = = 8@[ @ &+ @+J/(9 : B@B@x@ADA;Eǁ AB!!< =fNa"=@ŀ@ #l0?bsk_XH2? B@B@x@ADA@EOaLR`AA =ZY`= B@€@ # @"`(=$C D B@B@x@ADAEE}ZaL) Aaa BF = 5ц="G@0~@&/AH I B@B@x@ADAJE8[abqAK =3?f`="L@6@ c;]l~3^H.%oa& AM mN B@BtA@x@ADAOE r45#P =/R2 S B@B@x@ADATE퀈!s!U =='uV@@ &+ @+A/[CWqX B@Bu@x@ADAYE\r!cAZ = }`="[@p@ #A',;\}MraV9hc:SvK V\ܦ ] B@B@x@ADA^Ea~aLA_ =`=+$_`@d@ # @=Aa b B@B =Q@x@ADAcE^aL AAd =sh="e@_@ ! @Ġ/DfGg B@B@x@ADAhE1aAi = `="j@E@&WZ$={$J~p*j- m052a& k$+l B@B+@x@ADAmEҀAAn =a+$_o@5@ &+ @>ApA[q B@B@x@ADArEπ As = Iـ=$_t@Ѐ@ />uv B@B@x@ADAwEbAx =`="y@@ +#!'xIX9]E Nӈ0gEuR/MYa& ͤF+z${ B@B@x@ADA|EqCaLA} =`=$_~@ @ # @>Ak A B@B@x@ADAEU@aL A =J="@A@&/;C +W B@B@x@ADAE A =a"@`@ P:`E"!rGxqf$[a& A\@0 B@BP@x@ADAEFL A =aI"@@ !>A@  B@B@x@ADAE*aL  A = ﺀ='u+@Z@ &+ @+/r4C A B@B@x@ADAElaa =^s`="@j@ +#ATd`D+ry0  1  B@B@x@ADAE%aLg a =Yp`=+$_@g@ @=  B@B@x@ADAE"aL !!a = 5+="@0#@ 6h/!   B@B @x@ADAE݁ "1a =;a"@ۀ@ PX`>_G%Wz {KI~+8jF+- B@BP@x@ADAEaLA2@a =/`=+$_@؀@ ! @>AנA[ B@B@x@ADAEՒaLAAa == @@  `@A/$FCq B@B-@x@ADAE\NaABQa = U @"@pL@ +#!A!sE^,^;sY @ xK$ B@BA5@x@ADAE aLR`a =R`=$_@`I@ # @>AHA[ B@B@x@ADAEaL Aaaa = w ="@@%J/P~F B@B*@x@ADAE1 bqa =!a"@E@&|Z7]>Xd&)fΫbN% ݴ 8Jwy`F+A[ B@Bm@x@ADAEw"aLra =,`="@5@ &+>A A B@B@x@ADAEt-aLa =69`='u+@.@A:Mv{_S[Ŝ5?LrO|a& ҧC-  B@BA@x@ADAEq耈a =3Da"@+@ Pj*  B@B}@x@ADAEU a =%= @@.$C  B@B@x@ADAE۠Eba =P`="@@AĠ&H9B`]Vt~ ̿9Va& #[  B@B%o@x@ADAEFYQaLa =[`=+ @ߛ@ P? A B@BW@x@ADAE*V\aL) a =_="@ZW@ [!$/Q#  B@B@x@ADAE]aa =]h`=!I@@%Q5U$KmYJbV^va2I NF+0  B@B:@x@ADAEʁ :a =Ys @ @ PAA[ B@B@x@ADAEƁ a =Ѐ="@/Ȁ@ /{ǀ B@B}@x@ADAEtba =.`="@@ #$+Aŵ}̤3*RnF nAa& 3D@0 B@B@x@ADAE:aLA' =2`=+ @}@ # @>| A B@B@x@ADAE7aL) Aa =A="@9@ @/ŎCp8!&  B@B@x@ADAE[a = a" @o@$mK絊c<>n!R"#a& _A   B@Bx   @'@x@ADA EƫaL a =`=+$_@_@ &+ @>:퀂  B@AbBA@x@ADAEaL !!a =v=$_@ک@PĠ/CF"dg B@B@x@ADAE0da"1a =j`="@Db@ #!Ġ LS]ﱥҫD=\vpk02a& Moma$+ B@B@x@ADAEaL2@a =g`=$_@4_@ PA^  B@B@x@ADA!EaL i AxAAa" =@G#="#@@vĠ/u$% B@B@x@ADA&EՁ BQa' =aG@e]@g (@Ӏ@&Ag%sd7qQq0^!BA ZF+)Ҁ$* B@B+@x@ADA+EpaLR`a, =`= -@ Ѐ@ &+ @+= .iϠA[IN/ B@B@x@ADA0ETaL aaa1 =!="2@@ &+/}vC3A4 B@B}@x@ADA5EEaAbqa6 =L`="7@C@ #A_>سf!"@yPl/s fra& A8[$9 B@B@x@ADA:EE ra; =Ia"<@@@ #>=? > B@B}@x@ADA?E) @Y) a@ =a"A@Y~ }!/SCB zC B@Bg@x@ADADELaE =]a!IF@Ĵ@&MjklW\DlmUdvʛ8qR,TAG0 H B@B@x@ADAIEoaLaJ =X`=$_K@@ &+ @JAL M B@BA@x@ADANEk@TaO =u= P@/m@&/bxCQl R B@B@x@ADASE'aaT =9. `="U@%@ +#!҃\ijZiHw!>ZYa& "AV$W B@B+@x@ADAXE߁ aY =-+$_Z@"@ ' @>A[! \ B@B@x@ADA]E Aa^ =="_@ހ@& /ԄC`p݀ a B@B@x@ADAbEZbac =#`="Ad@n@&f~z2`'!N6z%? Feڕ-f B@B@x@ADAgEP$aLah =.`=$_i@_@ &+ @>jA[Y ` k B@B@x@ADAlEM/aL am =yW="n@N@ &+/sWDoEp B@B@x@ADAqE0 0aAar =;`="s@D@&1xq(rz47W~(_Lk,} yAt$u B@B@x@ADAvEAw = Fa"x@7@ #>Ay z B@B@x@ADA{E~) a| =NȀ="}@@&/r~ B@B|}@x@ADAEzGba =R`=%@x@%J[2q<|(+ >+}Dw@4g@` + B@B@x@ADAEo2SaL a =}]`=$_@ u@ PAit  B@B$@x@ADAES/^aL !!a =9="@0@ &+#VA/QC  B@Bq@x@ADAE "1a =ia"@@ +#'OC'  B@B@x@ADAE)uaL AAa =="+@Y@&/aC  B@BA@x@ADAE[vaBQa =`b`= @Y@ ]|t{կVR.(`8$*  b _YA  !G/-+ B@B@x@ADAEaLAR`a =X_`=$_@V@ ! @> j@ B@B@x@ADAEaLaaa =="@.@ WA/uC B@B@x@ADAÈAbqa =9Әa"@ʀ@ #$Q $Ø ??$Bzڠz W%oA$ B@B+@x@ADAEaLra =-У`="@ǀ@ #>Aƀ  B@B@x@ADAEӁaL) Aa =="@@&/Co f5 B@B@x@ADAEZ=aa = D`=$_@n;@ vp_.O]Y+t6kO˥a& *A:  B@B@x@ADAEa A$_@^8@ @+=7A[c @ B@DB M@x@ADAE a =u="@@&/ܱCD B@BJ@x@ADAE/ba =۴`="@C@& UeS jKXC=gylh+na& A  B@B@x@ADAEfaLa =ױ`=$_@3@dA  B@B@x@ADAE~caL a =Fm= @d@ W* @+$/nC+ B@B@x@ADAEaa =%`="A@@&.[?q? ]sM?g)mh-8gDd S jA$+ B@Bg@x@ADAEo׀*a =" @ @ * @>Ah A B@B@x@ADAESԀ6ha =a"@@ } &rSq~oqYx& yL@?C^$ B@B@x@ADAEDHaLZ: = "@ኀ@ ! @>=@ B@B6h@x@ADAE(EaL a =N="@XF@+ |11C  B@B@x@ADAEaWa =! ``=)@ `@ # @Ġr qKsL˱!q #A/A B@B@x@ADAEL a = WaI$_@`@ P W B@B@x@ADAEL) !!a =ʿ="@-@ !/XC B@B@x@ADAEqb"1a =9x%`="@o@ }8d"ɥѼK\\Ȕ\T5,_a& q A  B@B}@x@ADAE)&aL2@a =,u0`= @l@ ! @+= k  B@BW@x@ADA E&1aL AAa =0=$_+@(@PA/!Co' j B@BP@x@ADAEY BQa =E4xaa? =;`="+@@2@ '!Ġ1`dFi{V-A9 S_f|e^gAY AB B@BA@x@ADACED aD =8 E@/@dAF= AG B@BA@x@ADAHE(  aI =="J@X@+/gK L B@B@x@ADAMEbaN =[`="+O@£@&vj|{ 5̗%8I64 GJP.-Q B@B+@x@ADARE^aLaS =V`= T@@ @+>}UA[AV B@B@x@ADAWEZaL aX =d="Y@-\@ W/CZ[[ B@Bt @x@ADA\Eaa] =4`=%W^@@ PA5ڹ53.yhqDڼG,zeCQsa& ۘA_$` B@BP@x@ADAaE΁ Ab =0a$_c@@dAdA[e B@B@x@ADAfEˀ ag =Հ="h@̀@ /-Ciǹ"aWj B@B}@x@ADAkEYbal = `= m@m@&Ce$߇mZ T5T +Ka& L%Anل$o B@B@x@ADApE?aL aq =`= r@]@dAsA[c @t B@B@x@ADAuE<  B@B@x@ADAE'aLa =="@W@ A/A  B@BA@x@ADAEJaAa =^Q'`="@H@ xiz 5UW"=:Oia& ̫A. B@B@x@ADAE(aLa =VN2`="@E@>A[ B@B@x@ADAE ) Aa = 3 @,@&/:C B@B@x@ADAEa =0>a"@@%&U|kO:&GCJ:Baa& \ m B@Bu@x 9ADA @Es?aLa =+I`="@@dA絀  B@DB@x@ADAEpJaL a =z="@r@ A/qc'mq"f5 B@B@x@ADAEX,Kaa =3V`="@l*@ +A.k373fE),Fβ~a& F+)  B@B+@x@ADAE䀈 a =0a)m@a'@dA&A[c @ B@B@x@ADAE a = p=+ A@@ / CC  B@Bt@x@ADAE-bba =m`="@A@ &ְE+hZ0I񀞣xyha& A$+ B@B@x@ADAEUnaLA =ڠx`= @5@dAA[ B@B@x@ADAE|RyaL a =P\="@S@ /uC+ B@B*@x@ADAEzaa =`="@ @&(еf ۝6 vwZA $ B@B@x@ADAEmƀ a =a"@ @dAgA[ B@B@x@ADAEQÀ) !!a =̀= A@Ā@ W/jC $-a B@BW@x@ADAE~b"1a =`="@|@&3R:awfTDJ(~vx_@a& X  B@B+@x@ADAEB7aL2@a =`= A@y@dA<  B@B@x@ADAE&4aLAAa = =="@V5@&/]D  `BW@x@ADAE BQa =aa"@@ e@^c=a?!4=}VA] )j!v"` 2d-  B@B+@x@ADAE``a =U`= @@dA A[  B@B@x@ADA EaLqaqa =3g`= A @^@A ڹi'ٔ["Je?1D' 6DØ B@B}@x@ADAEaLra =+d`="@[@dZA[ B@B@x@ADAEaL Wa =="@@  +1/Cm B@BW@x@ADAEXрa =a"@lπ@ Ġ0f~ =`= ?@@ ! @`> @f A B@BA@x@ADABEQhaL6haC =!r=+ AD@i@ W/?CE F B@BW@x@ADAGE#a}aH =*)`= I@!@$A*H}u!{ !JW$+K B@B$@x@ADALEB܁ AAM ='4a$_N@@ # @>O;A[P B@B}@x@ADAQE&ف  AaR =="S@Vڀ@ }!#V҃/ICT AU B@BP@x@ADAVE5bAaW =d@`="X@@ 'tI$*[ w*wȈ)AY, +Z B@B@x@ADA[EMAaL} a\ =UK`="]@@ !>A^ _ B@BA@x@ADA`EILaLA!!aa =S="+b@/K@ /ߎCcJd B@B@x@ADAeEMa"1af =6 X`="g@@%$G\=R|ҷ?:3߶ c#& =.Ah$i B@B@x@ADAjE콁 2@ak =* ca$_l@@ # @=DKmb` `au iFn B@B@x@ADAoEк @Y AAAap =Ā="q@@A/Crls B@BP@x@ADAtEWvdbBQau =!A}o`="v@kt@&**ۢqƔXElܟg~ iAws Ax B@B"@x@ADAyE.paLR`az =yz`=$_{@[q@ PA|p } B@BA@x@ADA~E+{aL aaa =5=+ A@,@ &+ @+Ġ/!ECB B@ B@x@ADAE, l + bqa = a!I@@@ P# @@^| !O[1?"֒Psg ?䀂$+ B@B@x@ADAEaLAra = `= @4@ # @>Aဂ  B@B@x@ADAE{aL a =G="@@&$/XrD B@Bg@x@ADAEXaa =^`="@V@&Ibhyz$Yrnd_&vK-a& 3AU$ B@B@x@ =A @ElaLa =[`=$_@S@ &+ @>eRAY B@DB@x@ADAEP aL a == @@ &+ @A/C W B@B@x@ADAEȁ a = ϵ #@ƀ@&Z@kbZ ?[_"àD*YS AW B@B@x@ADAEAaLa =`=$_@À@ # @=;  B@B@x@ADAE%~aLa =="@Y@&/&Cš + B@B@x@ADAE9aCa =h@`="@7@ '+Q&5h=Z1[~1S|a& A0  B@B@x@ADAE a =X=$_@4@ ! @>A  B@BA@x@ADAE a ==+"@/@ &+ @A/ -C  B@B@x@ADAEba =.`="@@ZЕ)%`ő5c79[|FA$ B@B[@x@ADAEbaL$A =-`=$_@@ # @=A夀  B@B@x@ADAE_aL A! =i="@a@ v! @$/Cl`A[m B@BB@x@ADAEVa=! =!A"`="@j@&$ƶ $ƒ]@kSjT` ; A$ B@B@x@ADAEӀ$ 6! bLG@Qd_@Z@ PA  B@BA@x@ADAEЀaQL !!A =rڀ=$_@р@ /CA" B@B@x@ADAE,`"1A =ܒ`="@@@ #!+A=trC=dNK2YCa& A`Ȣ B@B@x@ADAED`2@A = ԏ`=)@0@ # @>A` B@B@x@ADAEzA`) AA&+ =GK="@B@&.U"C B@By m@x@ADAE B"! =+a"@*`@ -&w íL?Xa+=Xߑ`! $A`Ge#*;` XF B@B@x@ADAEkLR ! =6`"@5`@ !>e` B@B@x@ADAEOL aaA =$='u+@@ &+ @#`C /@G  B@B@x@ADAEm7bbqA =tB`="@k@A5} M>q>p'Hi AV` B@Bm@x@ADA, @EA&C`rA =~qM`=+$_@h@ # @=:` B@DB@x` =A @E%#N`` A =,="@U$@&/+C  B@DB@x@ADA Eށ A =[Ya" @܀@ '!_)wx65Z%sק;K[a& nyA +- B@B@x@ADAEZaLA1 Td`=+$_@ـ@ ! @=  B@B@x@ADAEeaL`A =ǝ="@*@ &+ @A/UC B@B@x@ADAEOf`AA ==Vq`="@M@ #ojn ~Ga& ?`Ȣ B@Bm@x@ADAEr`A =)S|`=$_ @J@ # @=!I`" B@B@x@ADA#E}` AoA$ =Lj`="%@j@ !^1r5uA9"=Ϭ@ mD&ֽ`' B@B@x@ADA(Ex`A) = Ó`="*@Z@ $>+`, B@B@x@ADA-Eu`?AxA. =@q="/@v@ ! @^/C0@+1 B@B@x@ADA2E+1aA3 = 7`="4@?/@ R3O8I7x? #qfg-skC %[5.`6 B@B@x@ADA7E逈 A8 =4`+$_9@3,@ ! @>:+`; B@B}@x@ADA<Ez YA= =F=$_>@@ /#D? @ B@B@x@ADAAE`aB = 5`="C@@$'# Z],ib'+Wc6Vf/C+AD$+E B@B$@x@ADAFEkZaL aG =`=+$_H@@ # @>AId J B@B@x@ADAKEOWaL`) !!aL =$a= M@X@ ! @/CN AO B@B@x@ADAPEaA"1aQ =`="R@@ +'i!} dmd4 K;YxzqASV`ȢT B@B3R@x@ADAUE@ˁ 2@aV = ~`$_W@ @ ! @>AX9 AY B@B@x@ADAZE$ȁ aQ AAa[ =р="\@Tɀ@ W /_C] ^ B@BA@x@ADA_EaBQa` =[`="a@@&ʭ+io]7E|hRtc Ab+`Ȣc B@B@x@ADAdE<`R`ae =W`="f@~@ #>Ag`h B@B $M@x@ADAiE8`) aaaj =B='u+k@):@&/&Cl9m B@B@x@ADAnEbqao = 1a"p@@ '!a;u?+$W*[f," 1 ;J? RAq`mr B@B@x@ADAsE`rat = {(bL+$_u@@ ! @=:vaw B@BA@x@ADAxEϩ aL ay =="z@@ &+ @A/Z~C{k| B@B@x@ADA}EUe aa~ = l`="@ic@ Ġ?#c#tۉW8XDwO`( a& +tAb` B@B @x@ADAE`a = h `=$_@Y`@ # @=_  B@B@x@ADAE!` a =t$=$_@@&/XC@ B@B@x@ADAE*ց a = ,a"A@>Ԁ@&? BẢjq@ۦdTeHz= ݞAӀ` B@B@x@ADAE-`a =7`=$_@/р@ PAРA[ B@B@x@ADAEy8` a =I="@@ &+$+$/+C B@B@x@ADAEG9aa = 5MD`="@E@ S#A S`wG۴y~@Ӣ" L ?AD$ B@B@x@ADAEja =JOa"@B@ #$=AdA  B@B@x@ADAENaQ a =P`8"+@~~&/ɊC "#a W B@B@x@ADAEշLa =~['@鵀@ ;`1Ţ a6bq V`<*@a& "U  B@B@x@ADAE@p\aL" = }f`=+$_@ٲ@ @>9  B@BA@x@ADAE$mgaL a =v= @Tn@YA/N  B@B6h@x@ADAE(haa = j/s`="@&@%A"d5 E ~6e8 ͞* B@B :  @'@x@ADAE  a =R,~$_@#@ PA  B@AbB@x@ADAE݁  !!a =="@)߀@ !+/iIAހ B@B @x@ADAEb"1a =,`="@@<$4,U#HYiGFձ\c Zih a& `j@ B@B+@x@ADAEQ`2@a =(`= @@ * @JA㓠A[ B@B #+@x@ADAEN` AAa =X="@O@ /FEj!7CZW B@B@x@ADAEU akHBQa = `="@i@ #+>pucA3hͭ@xEE^kyRa& ,F+$ B@Bm@x@ADAE€R`a =@"@\@ #>  B@B@x@ADAEaQc aaa =sɀ="@@&/ C? B@B@x@ADAE*{`bqa =ׁ`=$_W@>y@ +-68z,m zԔ0\-a& x + B@B+@x@ADAE3aLra =~`=$_@.v@ ! @JAu  B@B mA@x@ADAEx0aL`a =A:=#= @1@ &+#V/k B@BiS@x@ADAE a =`"@@AX 7WݺC5wqlƩUa& yF+适  B@B$@x@ADAEjaLa =`=$_@@ # @=c怂  B@B@x@ADAENaL a>7="@~@%/C A B@B`x@9 A @E\aa =}c`= @Z@ ' @6UHI̓t09#wV; ʞ` uAT@0- B@DB@x@ADA E?`a =``=$_ @W@ @= 8A[ B@B@x@ADAE#`a ==H@A@S@ &+K/(C  B@B@x@ADAÉ Aa =ja"@ˀ@ +b}¨P1?uˣ|@L u/A* B@B@x@ADAE@Ta =R `="@Ȁ@K=  B@B@x@ADAE aLbܤb Aa =Ԍ="@(@BȠ~’/ W! B@B m@x@ADA"E> aa# =0E`=%$@<@ uO)%So6HYWCtH{a& D%; & B@B@x@ADA'E( ='B"$_)@9@ ! @JA*8 + B@B@x@ADA,E a- ==".@@&$/C/iA0 B@B@x@ADA1ET#ba2 = .`="3@h@ #$g XOrq,ӡÔ\cA9NKa& 4A4Ԭ 5 B@B@x@ADA6Eg/aL a7 =9`=$_8@X@ ' @>A9 : B@B@x@ADA;Ed:aL !!a< =sn="=@e@ ! @/qC>?? B@B@x@ADA@E) ;a"1aA =&F`="AB@=@&"_ q?׿ԥFb Al C^͞DKC$+D B@B@x@ADAEE؀A2@aF =#Q$_G@.@ &+ @>HA[I B@B@xG 9ADAJ @ExՀ; AQaK =]a"L@@ W Ƕ:{psM0lPhM,!T|a(DM$N B@DB@x@ADAOEiI^aLR`aP =h`="Q@@ ! @>RbA[S B@B6h@x@ADATEMFiaL aaaU =P="V@}G@ $Ƕ/zW X B@B@x@ADAYEjaWbqaZ =u`=!I[@t`@&uK+{wF4˝&-7r(A˫| \D\T] B@B@x@ADA^E>Lra_ =|aI `@`@ # @>a8 b B@B@x@ADAcE"L) ad =="e@R@&/JpCf g B@B}@x@ADAhErbai =Vy`="j@p@ '$AeLl`7H\V|&~)ngb>Ak) l B@B@x@ADAmE+aLan =Qv`=$_+o@m@ ! @=:p q B@BW@x@ADArE'aL as =1="t@()@ &+ @A/HCu(v B@B@x@ADAwE~〈ax = /a!Iy@@ 1# @~[aӗ^4[[sքf 2/uAz-{ B@B:@x@ADA|E雤aLa} ='`=$_~@ހ@ # @=݀  B@B@x@ADAE͘aL a =="@@ ! @/YCiow B@B>@x@ADAESTaa =[`="+@gR@ !IH:ZOs*mj Kׁa& AQ  B@B`@x@ADAE aLa =W`=$_@WO@dANA[c @ B@B@x@ADAE aL a =n="@ @ /iC> B@B@x@ADAE)Ł q a =a%@AÀ@ }0ċ8 ⪋51֜/`Ra& A€$ B@B@x@ADAE}aLA =`= @-@dAA[ B@B@x@ADAEwzaL) a =D="@{@&/oC B@B@x@ADAE5aa =<`="@4@%Ż,"u5m{l_഍CA~3 + B@B@x@ADAEi*(a =9 @1@dAb0 A B@B 4#@x@ADAEM !!a =)="@}@/oC  B@B @x@ADAEӦbk ƴ@s"1a =@ A@礀@ } `@mJ5Dݡ*N6Be@ \S$+ B@B}@x@ADAE>_aL2@a ={ `= @ء@dA7A[A B@B@x@ADAE"\ aLAxAAa =@e="@R]@ /6D  B@B{ +`@x@ADAEaBQa =Y`= @@ `@ =e~[0']N2/MA($ B@B@x@ADAEЁ R`a =U$a @@dA A[ B@B@x@ADAÉ aaa =ր="@'΀@ /C̀ B@B@x@ADAE~%bAbqa =.0`= @@&<`ۃp*S:~d1$˧-)0a& <A$ B@B@x@ADAE@1aLra =&;`= @@dA₠A[ B@B@x@ADAE= + B@B@x@ADAESa =!AHa"+@gG`@ A;NIi΂Z#*U *d B V A B@B@x@ADAE!aL) a = = @Q@&/5C A B@B@x@ADAE a =Xåa"+@@&$RڡZ-0n"Y .mIa& sA#(  B@B@x@ADA!EuaL a" =P`= #@@dA$ % B@B@x@ADA&EqaL!!a' ={="(@&s@ A/_C)rm* B@B@x@ADA+E}-a"1a, =94`="-@+@ 3AXmc`XTs:[3/?A ~ L %A.* / B@Bc@x@ADA0E倈2@a1 =%1 2@(@dA3'A[ 4 B@B@x@ADA5E AAAa6 ==+ A7@@^ /UD8h 9 B@B@x@ADA:ERbBQa; = `="<@f@ +4TΆDcLeͧ< f|u %D=қ$+> B@B+@x@ADA?EVaLAR`a@ =`= A@Z@dAB C B@B@x@ADADESaL aaaE =m]="F@T@ A/ӖCG=#aH B@BW@x@ADAIE(aAbqaJ =`="K@< @&'hb{Aˌ1smĦv/sa& ?AL $M B@B{+@x@J =AN @EǀraO =a"P@, @dAQ $R B@DBA@x@ADASEvĀ aT =C΀= AU@ŀ@&/ɣCVW B@B@x@ADAXEbaY =h"Z@~@ PADvV^]w4D{oV3\a& A[}}$\ B@Bt@x@ADA]Eg8aLa^ =`=G@f@ A_@{@dA`az a B@B@x@ADAbEK5aL6hac =`= d@@$6h]Oi=Y>Łrְ鞹MCeR `au jf B@B$@x@ADAgE=aLah =z%`= Wi@@dj6A[k B@B@x@ADAlE!&aL am =="n@Q@/@aCo dp B@B@x@ADAqEa'aar =Th2`= s@_@  Ġ2_@kj{U !$4^v*At' u B@B @x@ADAvE3aL/aw =e=`= x@\@dyA[z B@B@x@ADA{E>aLa| = ="}@&@  Ġ/[C~ B@B@x@ADAE}ҀWa =,Ia"@Ѐ@ĠOo$IcŬw^ 9 @dA=  B@B@x@ADAE) !!a = yma"@~A/ZPC< B@B+@x@ADAE'LA"1a =Ϻx'@;@ ;ݷͩɶBVmZ KY5,;<A A B@B[p@x@ADAElyaL2@a =`= @,@dA  B@BA@x@ADAEviaL AAa =:s=+ A@j@ /tCA[ B@Bo@x@ADAE$aBQa =+`= @#@&$6wȋﶺ̨7qn4[ )0@Ra& xA|"$+ B@B@x@ADAEg݀R`a =( @ @ +# @*=?` A B@B}@x@ADAEK aaa =  ="@{ۀ@&$/C A B@B$@x@ADAEѕbl1bqa =`="@哀@ AeۙNF>v]J_Ea& Dt m B@B@x@ADAE/aLa ={`= @r@ &+ @+=Aq  B@B@x@ADAE,aL a =6=+$_+@-@ /7Cg  B@BsA@x@ADAEQ a =a @e@ @A"Q>2R!xZa& d倂$ B@Bq@x@ADAEaLa =`=$_@U@ # @>   B@B@x@ADAEaL`Aa = h="@Ԟ@&$/lj@  B@B:@x@ADAE&Yaa = _8,"@:W@&} J™ ]'! nìcQZy&O F+V- B@Bx@x@ADAEaLA =\`=$_@*T@ PA$S  B@B@x@ADAEuaL Aa =B= @@  `@+g/!C B@B@x@ADAEɁ Aa =a" @Ȁ@ (#!A6onF&jvF;y$cTsyw$ |ǀ$ B@B@x@ADA EfaL a ='`= @ŀ@ # @>A`Ā  B@B@x@ADAEJ(aL`A!!a = ="@~@&/w$ +c  B@B@x@ADAE:)a"1a =A4`="@8@&1IZB`żizϕ X[4բa& F+Q  B@B@x@ADAE; 2@a =y>?a"@5@ &+>5  B@B@x@ADA $`E @AAa! =='u"@O@&/)C# A$ B@DB@x@ADA%E@bBQa& =OK`="'@@!Ab^!g=޴9{bX#1AAa& (& ) B@B@x@ADA*EdLaLR`a+ =NV`=$_,@@ PA- . B@B@x@ADA/E`WaL aaa0 =j="1@%b@&/%dC2aA[3 B@B@x@ADA4E{Xabqa5 =##c`="6@@ '! 1JD]&PEFj60jHa"ha& 7A7@08 B@B@x@ADA9EԀgra: =( na ;@@ @=<A[= B@B@x@: =A> @Eр@ a? =ۀ="@@Ҁ@ &+ @/Af$B B@DB@x@ADACEQo AaD =z`="E@e@ PR2#*=3@*A4S'O*~Lk@ DFъ$G B@BP@x@ADAHEE{aLaI =`=$_J@U@ @=KA[L B@B@x@ADAMEBaL aN =gL="O@C@ }/WCP;WQ B@B}@x@ADARE& aS =a"T@:`@&% RJd=\К OYhR)a& &:AU$V B@Bm@x@ADAWELaX =aI"Y@-`@ &+>AZ [ B@B@x@ADA\EtLa) a] =E=)+^@@&/C_ +` B@B@x@ADAaEna ab =u`="c@m@ 4#!ޡ&rNio*Ȃ~ʏa& bAdl e B@BJ@x@ADAfEf'aLA bag =r`=+$_`= @3h@j@ ' @>Aici j B@BA@x@ADAkEJ$aL@Yal =AV.="m@~%@ `! @g/Cn o B@B@x@ADApE߁ paq =a"r@݀@&D]Z7);͈H\ ma& AsT$+t B@B@x@ADAuE;aLA zAv =y`=+$_w@ڀ@ &+ @>Ax4 y B@B m@x@ADAzEaL A A{ = =$_|@O@&/sC} ~ B@B@x@ADAEPaA =RW`="@N@ m#!J&Daí;'o3>6QO<}9HA%@4- B@Bm@x@ADAE aL A =NT`=$_@K@ ' @>A K  B@B@x@ADAEaL; !1A =7`="@@ -&!K?&|oN`2YJ]G=}Dra& J/ B@BC@x@ADAEyaL2@A =#`="@~@ $>߻  B@B}@x@ADAEvaL AA)A =="@w@"YW/F+$eA B@B$ADA @EP2aB%! =9@L"@d0@ WK#@y&R5J$P59FR@ ./  B@DBA@x@ADAEꀈ}`A =5a+$_@T-@ ! @"`C >,  B@B@x@ADAE aaA =o= @@ W&+ @/+9!;  B@ B@x@ADAE%b}qA =ک`="@9@&2S{%j IM)!e],! A$+ B@B} m F@x$aDA @E[aLrA =ͦ)`=$_@)@ # @=  B@DB@x@ADAEtX*aL A =Eb="@Y@&/A B@B@x@ =A @E+aA = ?=6`="@@ '+ @d * &T5V*"5 Dz@3m+ B@B@x@ADAEèA =A$_@@ @>A^A[ B@B@x@ADAEIɁ  A = Ӏ=+"@3{@yʀ@ &+ @A/C  B@B@x@ADAEЄBbA =AVM`="@䂀@ }w/&Gyg&ߞ F!9Ma& *AP B@Bg@x@ADAE:=NaLA = |X`=+$_@@ @=A4  B@B *@x@ADAE:YaL) A =C=$_@N;@ }/ܔC  B@B@x@ADAE #2 =Rda"@@ : н Hdh- jU{a& T% A B@B:@x@ADAEeaLA =Mo`=+$_@@ ! @>A  B@BA@x@ADAEpaLA == @$@$ @^` /D ` @ XF B@B@x@ADAEzfqaA =/m|`="@d@&}Յs4[b{8V/TEAc$ B@B@x@ADAE}aLA&+ ="j`=$_@~a@ # @Q`AB?A`  B@B@x@ADAEaL A A$%="@@&/Ae# B@B@x@ADAEOׁ a =ޓa"@cՀ@ '&l k,ϙ蠎S -~8Ѕa& hAԀ- B@B@x@ADAEaL a =!ڞ`=G u^@TҀ@ !>AѠ  B@B@x@ADAEaL !!a =j=H@h@!"+@΍@ &+ @/DC:%  B@x@ADAE%HaA"1a =N`="@9F@ #!v%Ȯ@nh\.<FJA% AE$ B@B@x@ =A @EaL2@a =K`="@(C@ #> B  B@DB@x@ADA Es) AAa =7a" @~%/ CA[e B@B$@x@ADAELBQa =a$_+@@& jII7☌z`'-Xa& BAz  B@B@x@ADAEdqaLR`a =`=$_@@  `@>A^  B@B@x@ADAEHnaL aaa =x="@xo@%/C  B@B  }@x@ADA%`DE)abqa =|0`="!@'@&$q;F&,IZoicf>RA"O # B@Bu@x@ADA$E: ra% =w- &@$@ PA'3 ( B@B@x@ADA)E߁  a* =="+@N@ W!'+/nC, - B@BW@x@ADA.Eba/ =Y`=!I0@@&$*4Awduҧa.p q]a& wA1$-2 B@B@x@ADA3ESaLCa4 =M`= 5@@ PA6A[W7 B@B@x@ADA8EOaLa9 =Y=":@#Q@&/SC;P< B@B@x@ADA=Ez aAa> =* @"?@ @&ze{1tOHyI_˘@ r@$A B@B@x@ADABEÀaC =&a"D@@ '$=E F B@B@x@ADAGE A[H =ʀ="I@@&/*rJd +WK B@B@x@ADALEO|baM = `= AN@cz@ m' @$k7MtL>@~p[cpdW%+"\M&~ba& 3/Oy P B@BA@x@ADAQE4!aLaR =+`=$_S@Sw@ ! @=Tv U B@B@x@ADAVE1,aL aW =j;="X@2@&/œIAY9AZ B@B@x@ADA[E$ a\ =7a"]@8@ #4G gKLN 9!lea& ^ꀂ _ B@Bj@x@ADA`E8aL"a =B`=$_b@(@ PAc瀂 d B@B@x@ADAeEsCaL af =C="g@@ ! @+/ hi B@B@x@ADAjE]Daak =dO`=!Il@ \@&(h~-?4*1)Àk=6a& F+my[$+n B@B@x@ADAoEdPaLA ap =aZ`= q@X@ &+ @>Ar]A[s B@B@x@ADAtEH[aL !!au =="v@x@ /Cw x B@B@x@ADAyE΁ A"1az =fa"{@̀@&+_wGh;;,pi' Ť~70a& A|N A} B@B@x@ADA~E9gaL2@a =wq`="@ɀ@ #>2  B@B@x@ADAEraLAAa =퍀="@M@&/C A B@B@x@ADAE?saBQa =TF~`= A@=@&.Tߥ5d(6n~wZ{ĉlSa& y$ B@Bwg@x@ADAE R`a =LC$_@:@ `@JA `auiF B@B@x@ADAE @Y) Aaaa =="@"@ }cV/ 2D B@B}@x@ADAEybbqX =%`="@@ #$$w%t!Vj^lh4ݏ>a& {A ` B@B@x@ADAEhaLra =!`= @}@ # @>Aݪ  B@BA@x@ADAEeaL6ha ='`="@b@$6hFLQƹhh_|>X|ڲUJdk INC`4 B@Bx@x@ADAEـa =$a"@S@ $>$ B@B@x@ADAE a =n=%A@׀@ ! @B/bC9 + B@B@x@ADAE#ba =И`="@7@ &+ =6$5 #dT-otxO@a& 'A$ B@BA@x@ADAEJaLa =`=+$_@+@ ! @>A[+ `AW B@Bq @x@ADAErGaL a =:Q="@H@ /уC B@B@x@ADAEaWa = `=$_@ @ [p# @A*I탭o(EDz +}`Ay@4+ B@B@x@ADAEca =$_@`@ # @>A]  B@B@x@ADAEGL) a = €="@w@ +!A/-C  B@B@x@ADAEsba =~z`="+@q@& (s^@~ PD =%M5U#a& BN@0 B@B@x@ADAE9,aL4A =vw`=$_@n@ &+ @>A2  B@B@x@ADAE)aLa =2=$_@M*@DK/*D  B@B@x@ADAE a =\ T"+@@-ABtoKf-%Zǽ;ab [#$ B@B@x@ADAE aL a =K`=`gd_@߀@dA  B@B@x@ADAEaL A!!a =£="@"@&/DA B@B@x@ADAExUa"1a =%\"`= +@S@ m' @Ġ t CX;Mg!#$aR\0Dza& BAR- B@B@x@ADAE #aL2@a =!Y-`=)@|P@dAOA[ B@B@x@ADAE .aL AAa =="@ @ /Cc B@B@x@ADAENƁ ABQa =9a$_@bĀ@ +j?&% \;Pd2xu}a& ʟAÀ$ B@B+@x@ADA&  E~:aLR`a =D`= @R@dAA B@DB@x` =A @E{EaL) aaa =i= @|@ /!\ " B@B@x@ADA#EG]taL'  a$ =@g="%@w^@ -&!#V$/ \C& A' B@B@x@ADA(Euaa) =~`=!I*@@&qI)e{.@lM50K tYYpa& Jw+M$+, B@B@x@ADA-E8с Aa. =za$_/@@ &+ @>01A[1 B@B 4M@x@ADA2E΁ a3 =׀="4@Lπ@ /DD5 6 B@B@x@ADA7EbAa8 =S`= 9@@ P# @$P>U Tz*ODJzka& :#; B@B@x@ADA<E BaLa= =K`=$_>@@dA?A[@ B@B@x@ADAAE>aL AaB =H="C@!@@&/TDD? +WE B@B@x@ADAFExuaG =0a"+H@`@&$>;<n7e08#|VKNa& Z)AIJ B@B-@x@ADAKEⲁLAL = aI M@|@dAN O B@B@x@ADAPEƯaL aQ = ="R@@0<>.CSbT B@B@x@ADAUEMkaAaV =q`= +W@ai@ @ A{On첧tldd_u0F^a&Խa& lAXh +Y B@B$@x@ADAZE#aL a[ =n`= \@Rf@dA]eA[A^ B@B@x@ADA_E aL !!a` =`*="a@!@/r@Cb8 c B@Bo mDK@x@ADAdE"܁ lzL"1ae =a!If@6ڀ@ +s]b(qht&r3 gـ$+h B@B+@x@ADAiEaLA2@aj =`= k@&׀@+ @+=6hl֠A[Am B@B@x@ADAnEqaL AAao =A="p@@ /$q Ar B@BW@x@ADAsELaABQat =S`="u@ K@&=-p`^BN5\->1b]iR a& \vwJ +w B@Bm@x@t =Ax @EbaLR`ay =P@L"z@G@>{[ | B@DB@x@y =A} @EFaL aaa~ = ?= = @v@&/7IA  B@B@x@ADAEͽ bqa =} a"@Ề@&|{4'@b|zc^7a& xM B@B@x@ADAE7v aLra =u`=$_@Ѹ@dA1  B@B@x@ADAEsaL@Y a =|="@Ot@&/V + B@B@x@ADAE.aa =J5$`="@,@&lWtoeEZwE,@f֙u)HpBa& F+"  B@B@x@ADAE a =2/ @)@dAA[+ B@BA@x@ADAE @Y a ==+"+@!@&//C䀂 B@B@x@ADAEw0ba =(;`="@@ +c!x 2w{8"nR1)'! `A$+ B@B+@x@ADAEW} A B@B@x@ADAEaLaaa =ɒ= @ @ \- @A/cC B@Bt @x@ADAEwDaAbqa = +K`="@B@ +#KA/T74vK`BOb A$ B@B+@x@ADAEra =Ha`6h@{?@ #>> A B@B@x@ADAE) Aa'a"@~ !/)a B@B`x@9 A @ELLa ='@`@gĠkW@'Z!9fqVY._V([7` F+̲ A B@DBt@x@ADA EmaLa = `=" @O@ &+>A   B@B@x@ADAEjaL a =_t=$_@k@ /Y3C6A[ B@B:@x@ADAE!&aa =,`="+@5$@ #!1P]g Қ^Xt5y&0a"Zva& A#  B@Bq@x@ =A @Eހa =)@L$_@%!@ # @>A  B@DB@x@ADAEp a =8="@܀@ ! @$/^C  ! B@B@x@ADA"Eba# = `="$@ @ ^M8oOK e΋Ӝ nA%v$+& B@B@x@ADA'EaOaLa( =`=$_)@@ ! @>A*^A[+ B@B@x@ADA,EELaL a- =V=$_.@uM@ /C/ 0 B@B@x@ADA1EaAa2 =x&`="3@@ +#!kẸ@PLB{2;I`6a& A4L5 B@B+@x@ADA6E6 A7 = t 1a$_8@@ # @>A9/ : B@B@x@ADA;E  a< =ƀ="=@J@ DK!A/> A? B@B@x@ADA@Ex2b-&aA =Q=`="B@v@&H6-XĘCgoWe 5xv*t#É?C%D B@B@x@ADAEE 1>aL aF =M|H`="G@s@ &+>AH AI B@B~@x@ADAJE-IaL !!aK =7=$_+L@/@ /kM.A[N B@B@x@ADAOEv逈A"1aP ='Ta"Q@@&  J6uׁ98^>~[vV]eDra& IAR怂 S B@B@x@ADATEUaLA2@aU =_`=$_V@z@ @JAW。 X B@BA@x@ADAYEŞ`aL AAaZ == [@@҃/xC\a] B@B @x@ADA^EKZaaBQa_ =al`="`@_X@ m'!%c# )~?AtK&̼걏ܬAaW$+b B@B-&@x@ADAcEmaLR`ad =!]w`=$_e@OU@ ! @=`fT  `iq g B@B@x@ADAhExaL aaai =k="j@@ A/4Ck6l B@B@x@ADAmE!ˁ Abqan =уa"o@5ɀ@%JZ\Nx|"mia:GQU7Ohaa& hApȀ Aq B@B @x@ADArEaLras =Ύ`=$_t@%ƀ@ # @>uŠ v B@B@x@ADAwEoaL ax =C="y@@ !$A/#DCz ${ B@B@x@ADA|E;aa} =B`="~@ :@&  `Hz5oHX0}ܿAv9$ B@B+@x@ADAE`Aa = ?a"@6@ PAZ A B@B H@x@ADAED 4) a ==$_+@t@ /*C  B@B@x@ADAEˬba = x`="@ߪ@ #!+F?([Q;0P0 #JQ:-9(Z׭ (AK A B@B$@x@ADAE6eaLa =s`=+)@ϧ@ # @>A/  B@BA@x@ADAEbaL a =k="@Jc@ :! @A/.C  B@B@x@ADAEaa = U$`="@@ ms5G#i@(oN=ߕd A - B@B@x@ADAE ց a =H!$_@@ ! @> B@B@x@ADAEҁ f a =܀="@#Ԁ@&$/CӀ  B@BA@x@ADAEuba = "`="@@&An-;u3_ K*<]ZfJʆ sFA$ B@B+@x@ADAEFaLA = `=$_@z@ ' @>Aو  B@B@x@ADAECaL a =M="@D@ !A/jC` B@Bli@x@ADAEK a = a"@_`@ 'Mf'NI-O_b%gr7 {A$ B@B@x@ADAEL a =@"@R`@ !>A  B@B@x@ADAEL?!!a =f="+@͵@&/C9 + B@B@x@ADAE pb"1a = v`="@4n@!s#i@&!İxCnJhSv>7Am  B@BO@x@ADAE(aL2@a = s`=+$_@$k@ `@>Aj  B@BA@x@ADAEn%aL AAAa =3/="@&@ 0AY  B@B@x@ADAED3aL6haqa =X?`="@O@ } GWU'{bnEOGlCJ$ B@Bg@x@AD>E5 @aLra =wUJ`="+@L@ ! @>2  B@B@x@ADAEKaL a ==%@I@Ƕ/C + B@B@x@ADAE a = PVa"@@$Ġ6on',C `RjhhN1p'7]6ڊS IRA  B@B$@x@ADAE {WaLa = Ha`="@@ )A>  B@B}@x@ADAEwbaL) a =="@y@ !$+/~Cx+ B@B@x@ADAEu3caa =!:n`=!I+(@1@ ' @S)8ZKqTzay f c=A0  B@BsW@x@ADAE뀈a =7y$_@y.@ ! @=`-  B@B@x@ADAE耈a ==" @@ /C c$ B@B:@x@ =A @EJzbCa =`="@b@ AvMg^=.? d[` XAΡ  B@DBA@x@ADAE\aLa =`=$_@R@ # @>A  B@B@x@ADAEYaL@Yk Aa =ic='u@Z@ W! @/tC9  B@B@x@ADAEapga =`="A@7@& f# iu) }&ua& A#A$+ B@B@x@ADA!ÈA zA" =$_#@$@ PA$ W% B@B@x@ADA&Enʀ  A' = :Ԁ="(@ˀ@ /C) * B@B@x@ADA+EbkA, =`="-@ @A dv 4{}ɋ 8Js F,A7A.uA[-/ B@B:@x- 9ADA0 @E_>aL A1 =`=+ 2@@ # @=3XA4 B@DBM@x@ADA5EC;aL !!A6 =E="7@s<@A/H~8 9 B@B@x@ADA:E "1A; =va!I<@@& ƹjYݝ9/޽ jgmUjC#=J> B@B@x@ADA?E4̒@T2@A@ =r`=$_A@@ PAB. C B@B@x@ADADEaL) AAAE =ᵀ=H g F@H@&/TGG H B@B@x@ADAIEgaB'?!J =Kn`="K(@e`@ #$ v`DtA=MVܻNU'nAQqߛWo=Z2 5@GL( M B@B @x@ADANE a }`AO =Gk`= P@b@ PAQ R B@B@x@ADASEaL aaAT =&="+U(@@ ! @/CV!]enW B@B@x@ADAXEt؀bqAY =!a!IZ@ր@&{ÒBI%Q;2pݔAB3Lc a& ʭA[Հ-\ B@B@x@ADA]EߐaLrA^ =W _@yӀ@ &+ @>`Ҡ c @a B@B@x@ADAbEÍaL Ac == d@@&/oC!FGp B@B@x@ADAqE Ar =ha"s@~ !A.kCt4u B@B @x@ADAvELAw =)a!Ix@3@&$} uШN:>.-yʃ2a& Ay@0]`8` XFz B@B@x@ADA{Er*aL*!/m| =4`=$_}@#@ &+ @>A~ `6h B@B@x@ADAEmo5aL) A =2y="@p@ /GC  B@B@x@ADAE*6aA =1A`="@)@ 4#$A}8cLe,hs$%{-e*[Ma& #2At( ` B@B 4@x@ADAE_〈A =.L$_@%@ @>AX  B@BA@x@ADAEC  A =="@s@9~/5  B@B@x@ADAEɛMbA =vX`=$_@ݙ@&kܤ}æs0^xGFDmBrAI B@B@x@ADAE4TYaL@)A =qc`=$_@Ζ@ `@>A-  B@B@x@ADAEQdaL OA =Z= @HR@&/"9C  B@B@x@ADAE ealA =Gp`="@ @!A5"Lĥ/Q|BKWkFA @4 B@B@x@ADAE ŀJ a =K{a+ @@ ,W @>AA[ B@B@x@ADAE !!a =ˀ="@À@A/4C€ B@B@x@ADAEt}|bA"1a =!`=!I@{@ +' @@F=d.G!$' } |t* a& -Az$ B@BA@x@ADAE5aL2@F+ =`=$_@xx@ @= wA[ B@B@x@ADAE2t) AAAa =<="@3@ W /^C^ B@BA@x@ADAEI BQa =c"@]@ vm`ZHj*#^R"ڋ֥VԂ a& 9"A뀂  B@B@x@ADAEaLR`a =`="@M@>A耂 A B@B@x@ADAEaL aaa =d`="@Ǥ@&m.C3 B@BUD@x@ADAE_aLbqa =e`=)@2]@ +pNÓ/:OHMlcٲ&bHA\  B@BA@x@ADAEaLra =b`=$_@&Z@dAYA[A B@B@x@ADAEmaL a =5="@@ WA/xC  B@B`@x@ADAEρ a =a @΀@&H0%E79 ~O!2t)=e(As̀$+ B@BIXUG@x@ADAE^aLa =`= @ʀ@dAW GA B@B$@x@ADAEBaL a =="@r@ /1C  B@B@x@ADAE@aAa =G`= @>@ PA)??/Rz̀,Se&1#(PAI B@B@x@ADAE3 R =qD @;@ 4 @J-A[ B@B@x@ADAE @Y a =="@G@&/|C +W B@B@x@ADAEba =J`="@@ dzXCĘS\:60SK}⫀77a) B@B@x@ADAEjaLa =FT+$_@@dA `  B@B@x@ADAEfaL; a =$)`= @ @;qN Ӝ4I.њaߑҾbO33a& ԸD   B@B@x@ADA Eڀ!G =&a @w@dA[-Ġ B@BW@x@ADAE a =="@؀@/"C^  B@BF@x@ADAEH bl}a =+`= @\@&[r- 3 B"z0/&I< b l^A Ȑ$A B@B$@x@ADAEK,aL a =6`= @L@dA[ B@B@x@ADA EH7aL !!a! =cR=""@I@  Ġ/rC#3$ B@B@x@ADA%E8aW"1a& = C`= '@1@ A2ɼd&~rBR&:ya& 7nA( +) B@B@x@ADA*E2@a+ =N ,@"M`@dA- A. B@B@x@ADA/ElL AAa0 =9À="1@@ 2.BC23 B@B@x@ADA4EtObBQa5 ={Z`="+6@s@ sw;!>&ߍT6na& )A7sr$8 B@B@x@5 =A9 @E]-[aLR`a: =xe`= ;@o@dA<W = B@DB@x@ADA>EA*faL) aaa? = 4="@@q+@&/CA AB B@B@x@ADACE bqaD =uqa AE@@ b9^ u2`=H@ r@\6@&*ױ?+'EYz+n!@ =NAs5$t B@Bm@x@ADAuEav =;a w@L3@+ @+`.Ax2 y B@B J@x@ADAzE퀈) a{ =b="|@@&/bA}2~ B@B@x@ADA߄Eba ɯ`="@1@ +a͌Т "HnNx`|H7<Xd A B@DB+@x@ADAEaaL" =Ŭ`="@!@dA ] B@B@x@ADAEk^aL a =4h= A@_@&/d B@B@x@ADAEaa = `="@@7gX 2c9),ꄐ̇' /30Qa& Or  B@B}  @x@ADAE]Ҁ a =)W@@dAV  B@B@x@ADAEAρ  !!a =ـ="@qЀ@ /YUD  B@B; @x@ADAENJb"1a =w`="@ۈ@ A^@ 1&~m@(^5Cca& JG$+ B@B@x@ADAE2CaL2@a =p @ @̅@dA+  B@B@x@ADAE@ aLAAa =I= A@FA@ /$UA[ B@B@x@ADAE@taL a =  ~="@pu@&/C  B@Bx@x@ADAE/aw6a =6`="@-@ #+gŽtj]{2421)f-a& /AK B@B@x@ADAE1 A =o3a"@*@ '>A+ ]^B$@x@ADAE ^) a =="+@E@ ! @+A/ZC  B@B@x@ADAEbAa =P`="@@ md,M? , Z,a& A  m B@B@x@ADA EYaL a =D`=$_ @@ ! @>A  B@BA@x@ADAEUaL !!a = {_=$_@W@ / CV B@BA@x@ADAEqa"1a =`="@@&J3ϻhtxCe^cG\Nj2z\A- B@B@x@ADAEɀ2@a =$_@u @ # @>A  B@B@x@ADAEƀ6hAQa = !@Z@ ! @ xeǍ ֟sE*yW՘`ۦ%+t )_C"@3m# B@Bu@x@ADA$E:aLWR`a% =`="&@K}@ P'| ( B@B@x@ADA)E7aL aaa* =^A="++@8@ !P/sC,1+- B@B@x@ADA.E Wbqa/ =a"0@0@AĠ_:ya& 9NA1$2 B@Bx@x@ADA3EaLra4 =`="5@#@ &+$+=:6퀂 7 B@B}@x@ADA8EjaLa9 = ;=$_ +@@3:@@ /; +< B@B@x@ADA=Ecaa> =AVj@"?@b@ #!Ġ >aR Ű[1>;T#mޖ7aU '3D@qa A B@B@x@ADABE\aLWaC =g `=$_D@^@ # @>EU F B@BA@x@ADAGE? aLbH AaH =#="I@p@ Jw! @/CJܡ K B@B@x@ADALEԁ aM =va"N@Ҁ@ +j`r5 ՙy r*h7=+XǨJg?OF$P B@B+@x@ADAQE1aLaR =n"`=$_S@π@ ! @>AT* U B@*B@x@ADAVE#aL aW =Ⓚ="AX@E@ W/j?Y Z B@B@x@ADA[EE$aa\ =TL/`="]@C@ M#+Ag8Z6;t1#fGO{ PF+^-_ B@B+@x@ADA`E #aa =DI:$_b@@@ # @>Ac? d B@B@x@ADAeEaf = ;a+"g@~ ! @A/cChAi B@B@x@ADAjEqLAak =-F'l@@\FᇣQmwRMad\ L lm񳀂$n B@B@x@ADAoEnGaL8p =Q`=+$_q@t@ ! @>Arհ s B@B}@x@ADAtEkRaL Aau =u=$_v@l@&/ w[ +x B@B@x@ADAyEF'Saaz =-^`="{@Z%@ m#!A8>$y0h`6G@u8G$5:f+|$+} B@BY@x@ADA~E߀ a =*i$_@J"@ ' @>A!  B@B@x@ADAE܀ !!a =i="A@݀@&/DlW0 B@B@x@ADAEjb"1a =Ǟu`="@/@ P+̈́jh˥3 $Mo1+:#a& J + B@B}@x@ADAEPvaL2@a =Û`=$_@@ ! @>A  B@B@x@ADAEjMaL AAa =3W="@N@v/AC B@B@x@ADAEaBQa =`="@@ +#A{^a4߹@Dge(G~+] pAp$+ B@B@x@ADAE[AR`a = $_@@ PAT  B@B@x@ADAE?  aaa =Ȁ=+"@o@ ! @+/  B@B@x@ADAEybAbqa =v`="@w@ %.4f֞+ށU}?NsMDF B@B@x@ADAE02aLra =r}`=+ @t@ ! @>A)A[ B@BM@x@ADAE/aL a =8=$_@D0@ /<;C  B@B@x@ADAE a =Ha"@@ #!҃ IyUĴAQAT}"a& A B@Btm@x@ADAEaLa =C`=$_@@ # @>䀂  B@B@x@ADAEaL) a =="@@ !A/5C B@BJ@x@ADAEp[aa =(b`="@Y@%*}k#볉krPX$AX  B@B@x@ADAEaLa =_`=$_@tV@ &+ @>AU  B@B@x@ADAEaL a ==+$_@@ :/bC[ B@B:@x@ADAEÉ a =a"@Yʀ@ #!A:%<DM2kPo4ova& Aɀ$+ B@B@x@ADAEaLa =`="@Qǀ@ #>Aƀ  B@B@x@ADAEaL a =\="@Ă@ ! @A/IC0 B@BW@x@ADAE=aa =C@"@.;@ NoGpKpJ2pWa H#CA:$ B@B@x@ADAEA =@ a+$_@8@ ! @>A~7A[ B@BA@x@ADAEi a =1="@@ /C B@B@x@ADAEb-&a =`=%@@ +# @AwyY:6a'l^Q$IMDa& ,zAt$ B@B@x@ADAEZfaL a =$`=$_@@ # @>AT A B@B@x@ADAE>c%aL) !!a =m="@nd@'.VC  B@B@x@ADA+  E&a"1a =u%1`="@@&K}uP#6ywt @ UAE  B@DB@x` =A @E0ׁ 2@a =m"<$_`k3C@@ &+ @>A)  B@DBA@x@ADA Eԡ @Y AAa =݀=" @DՀ@ &+/AyC   B@B{@x@ADAE=bBQa =KH`=!I@@&Nk9 ;`Fhe䚍I a& A$ B@B@x@ADAEHIaLR`a =BS`=$_@@ # @>A  B@B@x@ADAEDTaL aaa =N= @F@&N.@"CEA B@B@x@ADAEoUabqa = ``=" @_`@&gz_,m%-_eNAq5L:З)A!-m" B@Byg@x@ADA#EڸLra$ =kaI+$_%@sj`@ PA& ȸ' B@B@x@ADA(EL a) =="*@@ &+$+J/aC+ZA, B@B@x@ADA-EEqlbAa. =ww`=!I/@Yo@ ?# @҃-!hQL-\/ 8"_$a& QA0n$1 B@B*@x@ADA2E)xaLa3 =t`= 4@Il@ # @>A5k 6 B@B@x@ADA7E&aL) a8 =d0="9@'@ -&!/0}C:/; B@BP@x@ADA<E a= =a">@.@ Ԛ,}Hvyt:3Mo};fEa& A?߀ +@ B@B+@x@ADAAEaLaB =`="C@݀@ !>D~܀ E B@B@x@ADAFEhaL @saG =Y`=$_H@Q@ Ƕxx;g*ؾ@#00 dV# flCIoP J B@ BpW@x@ADAKEZ aL^L =V`="M@M@ @JNS O B@B}@x@ADAPE>aLޗaQ == R@r @[p @Ga/@CSޡ T B@B@x@ADAUEÁ aV =yʽa"W@@AĠA)JM0ݖHb´ .AQGa& (CXD$AY B@BA@x@ADAZE/|aL"[ =m`=+$_\@Ⱦ@ P](A[&` ^ B@B@x@ADA_EyaLAxa` =@む="a@Cz@ !/Cb c B@Bm@x@ADAdE4aWae =R;`=!If@2@JĠ2ߏAmGe3-kD ڗAgh B@BJ@x@ADAiE  aj =B8 k@/@ `@>l.A[d@m B@B@x@ADAnE逈A!!ao =="p@@vՙ/,Cqꀂ$r B@B(@x@ADAsEob"1at =`="u@@ #$Ġ iF)2!H&%4m71z[KjY^:  Av@3+w B@B@x@ADAxE]aL 2@ay =`=+ z@s@ ' @>{ӟ A| B@B@x@ADA}EZaL) AAAa~ =d="@[@ !/CY+ B@B 4@x@ADAEDaBQa =:u!IA@X@ V-ǯl#tTSqzERa& %A  B@B@x@ADAE΀R`a =a$_@H@ ! @>A  B@BA@x@ADAE aaa =cՀ= @̀@PA/ӘC/  B@B@x@ADAEbbqa =`="@-@!A4kgQM.vyqȒb\m<8iNa& VA@0A B@ BA@x@ADAE?aLra =Ŋ&`=$_@!@ ' @>A!U B@B@x@ADAEh<'aL a =,F="@=@ W! @Ġ/C  B@B@x@ADAE a =2a"A@@&A*Ħi6:Af8s6kAn + B@B$@x@ADAEY3aLa ==`=$_@@ PAR  B@B@x@ADAE=>aL a =="@m@ /3C  B@B@x@ADAEh?aa =toJ`="@f@ }#+AM²|We1M09a& AD B@B}@x@ADAE.!KaLa =llU`="@c@dA(  B@B@x@ADAEVaLVa ='="@F@&/UC + B@B@x@ADAEف Ca =Jaa$_@׀@  :o^s3>}'Į=}"[A  B@B@x@ADAEbaLa =Il`= A@Ԁ@dA W B@B@x@ADAEmaLa == @@"Y/hwC  B@B@x@ADAEnJnaa =.Qy`="@H@ #18ڿTQV#5 G'Q/|:AG$ B@BM@x@ADAEzaL$ zA =N`= @rE@dAD  B@B@x@ADAE  i Ax%A =   @@PĠ/̒CY  B@B@x@ADAEC A =a"A@W@%(J/頂 `+0 B@B@x@ADA1EaL AA2 =="3@쥀@ 3/94X5 B@B@x@ADA6EC`aA7 =f`= +8@W^@ XdqqBꎁi\-.R#2NT #]9]A[ : B@B+@x@ADA;EaLA< =c(`=$_=@K[@ @>>ZA[? B@Bx@x@ADA@E)aL) AA =Z= B@@ /CC-D B@B@x@ADAEEс ;7$F =4a"+G@,π@ ` ]um63w!jլ!o4ūiuEv#AH΀ I B@BE@x@AD>JE5aL AK =?`=$_L@̀@  @>AM|ˀ N B@BA@x@ADAOEg@aL  AP = /= Q@@ @/CR f5S B@B@x@ADATEAAaaU =HL`="V@@@&gm0g]?:=YNxAA' &0Y];a& nAWm?$+X B@B@x@ADAYEXA CZ =EWa$_[@<@dA\Q ] B@B@x@ADA^E< 6h!1a_ =kca"`@ְ@+C铭Çc8:&&hYAiՃ"Cl+B9WCaB@4d b B@Bq6h@x@ADAcE-kdaL2@ad =n`="e@ǭ@#V+/{Ck l B@Bm@x@ADAmE#paWBQ!8& =H*{`="o@!@AĠ+om؈i^qpElC%.K_a&  'Apq B@B@x@ADArE܁ R`as =@'a"t@@>u v B@B}@x@ADAwE؀) aaax = ='uAy@ڀ@ / Czـ{ B@B@x@ADA|Embbqa} =`="~@@  Ġl8Ox!dr D.wmm%

Kv> x0U! *F+s$ B@B:@x@ADAE.aLa =y`=$_@q@ &+>{p  B@B@x@ADAEf+aL a =.5="@,@ /C B@B@x@ADAE Aa =a"@@ m#+.KW?G1$sra& Jwm䀂$ B@B@x@ADAEWaLF+ =`="@@ #>AQ  B@B@x@ADAE;aL) a =="+@k@ ! @QC /4P  B@B:@x@ADAEWaa =v^`="@U@  (3Yej(Şnba& AB m B@B|@x@ADAE,aLD =j[`=$_@R@ ! @=&  B@B@x@ADAE aLa =="@@@%/C  B@BH@x@ADAEȁ a = ? #@ƀ@+A)1 hmʩF6 N]A  B@B@x@ADAEaL a =`=$_@À@ '>A€  B@B@x 9ADA @E}aL A!!a ==+"@@&$/vC~Am B@DBo@x@ADAEl9a"1a =!@`= @7@&A9Rt3-_7lEg7-ja& A6@0 B@ B<@x@ADAEA b2@a = =*a @t4@ PA3  B@B6h@x@ADAEAxAAa =@="@@ &++ |1CWA B@BW@x@ADAEB+bABQa =! 6`="@V@ #AWs@UU<]" aHF($iȼ 7§$ B@B@x@ADAEb7aLR`a =A`="@E@ #$>AA[+ B@BA@x@ADAE_BaL aaa =Ui="fi @3@`@ @Ġ/[D,@A B@BW@x@ADAECabqa = !N`="@+@ t9F`YWCRSB6) %A@2 B@B@x@ADAEӀra =Y$_@@ ! @>{  B@B@x@ADAEeЀ a =*ڀ="-@р@ &+/^ C  B@Bq@x@ADAEZba =e`="@@ P#AjcO#:PXWS5xdAl + B@BA@x@ADAEWDfaLa =!Ap`=$_ @@ @>A P  B@BA@x@ADA E;AqaL a =K="@kB@ `/[C  B@B~@x@ADAE a =v}a"@|`@ mU}_5أꬶͷoZ A%A[ B@B@x@ADAELa =什=+'u@@@&/M  B@B(@x@ADA!Emba" =Ct`="#@k@&A]z1qրJwFd6-K:SF D$ A% B@B @x@ADA&E&aLa' =?q`="(-@h@ '>A)gA[* B@B@x@ADA+-`DE"aLa, =,= -@$@ ! @+/7C.#C/ B@BW@x@ADA0Elހa1 =a"2@܀@&$FCd B/wh`5x[pEmA3ۀ$4 B@B@x@ADA5E֖aL"6 = `=$_7@pـ@ PA8؀ A9 B@B @x@ADA:EaL) a; =="<@ꔀ@&/AC=V> B@B@x@ADA?EAOaa@ =U`="A@UM@3RJ κtta0^iR{vbϴ3IQs ABL AC B@B @x@ADADEaL aE =R`= F@EJ@ ' @= GI H B@B@x@ADAIEaL !!aJ =\=+)+K@@&/CL,M B@B@xK 9ADAN @E "1aO = ?=a"P@*@&V?Ze΋UIf>r9< AQ@2WR B@B@x@ADASExaL2@aT =`="U@@ PAVz W B@B@x@ADAXEeuaL AAaY =1= Z@v@&.@C[\ B@B@x@ADA]E0aBQa^ =7`="_@.@&^|- 'V]uo AMA`k$a B@kBq@x@ADAbEV R`ac =4a+)d@+@ PAeOA[+f B@BA@x@ADAgE:  aaah == i@j@ ! @/3Cj k B@B@x@ADAlEbAbqam =n @"n@՟@&\ GQzPQ\tI&@ 6AoAp B@B@x@ADAqE+Z aLrar =i`= s@Ŝ@ PAt% u B@B@x@ADAvEWaL) aw =`=H@gD_+x@?X@&/|Cy z B@B@x@ADA{Eaa| =>!`="}@@+҃ȗV#1^=qZw"Hvx|@ 0Oa& 3A~  B@B 4@x@ADAEˁ a =,a"@ @ '$=  B@B@x@ADAEǀ; a =8"@@ @A/ŖC뀀  B@B@x@ADAE;9aLa =C`="@o~@ P}  B@BW@x@ADAE8DaL a =~B="@9@=VA[z B@B@x@ADAE@ a = Oa"@T@ &+!Ƕճ}>ՠQ+|c(hrW D@2 B@B@x@ADAEPaLWa = Z`="@D@$=  B@B@x@ADAE[aL a =S="@@ &+/C+W B@B@x@ADAEe\ao a =kg`=!I+@.c@ e-=6ߤuK|2x<5YykAb$ B@B @x@ADAEhaLA = hr`=$_@`@ @> z_  B@BW@x@ADAEdsaL) a =5$="@@A/GC B@B"Y@x@ADAEՁ ka = {~a"@Ӏ@%>HTXo!n͊lo)u7 %Ak  B@B$@x@ADAEUaL a =ى`="@Ѐ@ &+>AO  B@B@x@ADAE9aL !!a =="@i@ :A/ӪC  B@B@x@ADAEFa"1a = qM`= A@D@ # @̤(d:8 VBKm{NҖRbW A@  B@B@x@ADAE+ `2@a = lJa$_A@A@ # @>A$  B@B}@x@ADAE  AAa ="@?~ W! @A/C  B@B|   @' 5@ADA @@LlABQa =I'@@ <[E0W2_T:Dג.f` Ade B@DB|@x@ADAEpaLAR`a = 5>`=+$_@@ ! @>A  B@B@x@ADAElaLaaa =v=+$_@n@ /Vam B@B@x@ADAEk(aAbqa =/`= @&@%r{Agt.gf[W$ p(D%  B@B@x@ADAEra =,$_@o#@ # @>A" + B@B@x@ADAE݀F Aa =="@ހ@&/CY$ B@B}@x@ADAE@ba = `="@T@&[T#sw%7t ѧYtj[ "#A$ B@Bx@x@ADAEQaLa =  +$_@D@ PA`  B@B@x@ADAEN`) a =_X="@O@&/C*A B@B@x@ADAE aa =`="A@)@&*Vr٠eu }$f=Ձ H-a& BA. A B@B@x@ADAE€a =  a @@ PAy  B@BA@x@ADAEd a =,ɀ= W @@ W! @҃/nC   B@Bt@x@ADA Ezla = { `="@x@&IL+g56h&%DXB1G:^Oc Aj$+ B@B@x@ADAEU3 aL$a =~`= @u@ PAN  B@B@x@ADAE90aL a =:=$_@i1@ W/tC A B@B@x@ADAE a =p"a"A@@&5² |á z[ݦa& \?$ B@B@x@ADA E*#aLA! =h-`= "@@ # @= ## ` $ B@B@x@ADA%E.aLa& =ת="'@>@ !$A/ ʈ( A) B@BuA@x@ADA*E\/aa+ =Ac:`=",@Z@ gJY4kJZv&KV;Mya& OF+-. B@B@x@ADA/E;aL a0 ==`E`="1@W@ !K=2V 3 B@B@x@ADA4EFaL) A!!a5 =="6@@&/lcC78 B@B@x@ADA9Ej̀"1a: =Qa%;@~ˀ@ # @+¶fcf?9}@< q@D+ha& CA<ʀ += B@B@x@ADA>EՅRaL2@a? =\`=$_@@rȀ@ @>AAǠ i7 AB B@B@x@ADACE]aL AAaD =="E@郀@ A/FTG B@B@x@ADAHE?>^aBQaI =Di`=$_J@S<@&|i/0RS۝Mí߾a& DK; L B@B@x@ADAMER`aN =At$_O@C9@dAP8 AQ B@B@x@ADARE aaaS =Z="T@@&/@CU* V B@B@x@ADAWEubbqaX =ŵ`="+Y@(@&~O(Hϴ,w< p= Ga& AZ$+[ B@B@x@ADA\EgaLra] =`= ^@@dA_x ` B@B@x@ADAaEcdaL ab =0n="c@e@ A/Cd e B@By@x@ADAfEaAag =&`="h@@ iW v+[U7 rNN}[J/A@y~a& Crijj B@B@x@ADAkET؁ al =#a"m@@dAnN o B@B@x@ADApE8ա @Yk aq = ߀="r@hր@%g/rs t B@BW@x@ADAuEbav =l`= w@ӎ@&$*y]ySnj7Q0DM h|8F+x? +y B@Bt+@x@ADAzE)IaLa{ =g`= A|@Ë@dA}#A[~ B@B@x@ADAE FaLa =O= @=G@&$/1C  B@B@x@ADAEaa =<`="+@`@! ARk2u(ArkNp`$b A  B@ Bp@x@ADAELA&rEa =@aI @`@ @=  B@B@x@ADAE㶁L Aa = ="a @3@@+ @g/^C@+ B@B@x@ADAEirba =AVy`="+@}p@ v.⩗h7>`s%MR׹ͯroAo-+ B@B@x@ADAE*aLA =v`=$_@nm@ @=l  B@B@x@ADAE'aLqa =`="@W@18k6chv4 VCM#YcC$ B@B@x@ADAEaL a = "@Bހ@dݠ@+ B@B@x@ADAEaL !!a =a="@@  +/)+ B@B@x@ADAETaW"1a =Z `=)+@(R@+ @+ĠG;v;fxgE9m-ZDQ A B@BA@x@ADAE~ aLW2@a =W`='u@O@ @=xNA[-]  B@B@x@ADAEb aL) AAa ='="@ @%/V4C  B@B@x@ADAEā BQa =$a"+@€@ aĠR_X0So~~+pa& uAi  B@BA@x@ADAET}%aL}`a =/`=$_@@ @>M  B@B Y@x@ADAE8z0aL aaa == @h{@ A/YC  B@B -&@x@ADAE51abqa =o<<`="+@3@gĠGqw&.%geNϏ}Ra& A>@0 B@B}@x@ADAE) ra =f9Ga$_@0@dA"  B@B@x@ADAE @Y a =="@=@ /GC  B@Bw@x@ADAEHba =@S`= @@Ġ_Ng٪;‰*Rv (BXȢZ;a& .^  B@B@x@ADAE^TaLa = @^`= @@dA  B@B`@x@ADAE[_aLa =e="@]@ /؊C~\ B@B@x@ADAEi`aa =k`= @}@ +SIqc$]3M}bI 5 Ea& 2A$ B@BA@x@ADAEπa =v @m@dAA[ B@B 5@ADA @È) Aa =ր="@̀@ W/CS B@DB@x@ADAE>wba =`="+@R@ mH-CjzQqHV v\ua& dZA + B@B@x@ADAE@aLa =拍`= @B@dA A/  B@x@ADAE=aL8a =]G= @>@ /vC-  B@B@x@ADAE a =a"@'@ b\u&lyS L6N@ "* $ B@B @x@ADA E~aL" =`= @@dAwA[A B@B!B5B@x@ADAEbaL ޗa =*="A@@g/z A B@Bs $@x@ADAEiaa =p`= +@g@ T.0c+!Km1Jwb Σ$a& Dh$ B@B@x@ADAES"aL a =m`= @d@dALA[ B@B@x@ADAE7aL@Y !!a =)="!@k @ WA/ C"# B@BA@x@ADA$EڀA"1a% =va"&@؀@& .}Pv' o<<|co'>$( B@B@x@ADA)E(aL2@a* =f`="+@Հ@ &+$g>," - B@B@x@ADA.E aL) AAa/ =ᙀ= 0@<@& /1 2 B@B@x@ADA3EKaBQa4 =;R`="5@I@W!(_6P݊OҾ㾃2vE젾x6@4-7 B@B@x@ADA8EaLA )R`a9 =O`=$_`= @3v:@F@ ' @>;E < B@B@x@ADA=EaLaaa> =AV ="?@@/~@} A B@Bq@x@ADABEhbqaC =a"D@|@ 'sSiC:-VhAJж K B@B@x@ADALEqaL+AxaM =@{=+"N@r@&$/OS3';P B@B@x@ADAQE=-aaR =3`= S@Q+@ @A],E'[\"U cLj!lQOmT*A[a@U B@B+@x@ADAVE倈AaW =0a$_X@E(@ PAY'A[Z B@B@x@ADA[E  a\ =P="]@@A/6h^(_ B@BW@x@ADA`EbAaa =&`="b@'@ '.UqwGgžb9t.2<TVc$d B@B@x@ADAeE}V'aLaf =1`="g@@ !$+=hv i B@BA@x@ADAjEaS2aL$ak =.]="+l@T@ &+ @/Vm$n B@B@x@ADAoE3ao)  +ap =>`="q@ @ 1#wʂ~#mlc^1 Yg\׏@ rl $s B@B@x@ADAtERǀau =!AIa$_v@ @ # @=wP Ax B@B@x@ADAyE6ā az = ΀="{@jŀ@ !/`LW|֡ +} B@B@x@ADA~EJba =uU`="@}@ ]rs`pZ鱙WΐU [WhMB>A  B@B(@x@ADAE(8VaLA =e``=$_@z@ ! @>A!  B@BA@x@ADAE 5aaLAx@! =@>=+$_@<6@&/lD  B@B@x@ADAE lAA =;la"@@!҃rZYˀTی ^s܄DgA>a& @0C B@Bm@x@ADAEmaL A =:w`="@@ '>Aꀂ B@B@x@ADAExaL !!A =="@@ ! @A/}A[ B@B@x@ADAEgaya"1A =h`="@{_@&/YsMxUЮ Nm@k{>ޔ&p– a& s^$ B@B@x@ADAEaL2@A =e`=+$_@k\@ PA[A[ B@B@x@ADAEaL AA)A = =$_@@ /R: B@B@x@ADAE=ҁ $B%! =؛a"@QЀ@&䈣H&suaZV|/~aU Jπ`4 B@B@x@ADAEaLR`A =զ`= @À@ # @"`C >À  B@B@x@ADAEaL) aaA =\="@@ !$/;A' B@B@x@ADAECabqA =I`="@&A@ 'e }@ebrΌ))%fa& xA@  B@B@x@ADAE}rA =F$_@>@ @>v=  B@BA@x@ADAE`+0 8A = a+"@@ /3Cg  B@B@x@ADAERlaLA =`="@ﮀ@ @>K  B@B}@x@ADAE6iaLAxA =@s= @fj@ +^l&0 s|zݐ>B,X4G  B@BW@x@ADAE$aA =h+`="@"@gՙLcGփ"& DFsrLas[a& /D<$A B@Bg@x@ADAE'݁ WA =e(a+$_ @3@@ # @> A[- B@B@x@ADAE ځ A = = @;ۀ@ ! @W/٩C  B@B@x@ADAEbWA =F`="@@ AĠJy-ޅ'wt]ڸ=wє/>a& + B@B{@x@ADAEMaLA =:@$_@@ ! @>A  B@B@x@ADAEJaL) AA = T=H gj@L@ /݆D|K B@B@x@ADAEgaA = `="@{@ #+ QA [92ȶيA-S` M~a& %/A + B@B@x@ADAEѾ A = a"@k@ #>AA[A B@B@x@ADAE  A =ŀ="+@开@ :! @+A/CQ B@B@x@ADA0  EAu⠂Ad B@DB@x@ADAE`KaL AAa = 1="@@ ! @Ġ/0C  B@B@x@ADAEXLaABQa =_W`=" @V@&Ncbju Ҥ Dk4g: Ip!f A" B@B@x@ADA#EQXaLR`a$ =\b`=$_%@S@ `@>A&J ' B@B@x@ADA(E5caLaaa) =="*@e@&/U+ , B@Bli@x@ADA-Ecabqa. =hn`="/@ǀ@ #+A].{a߾~Hk f'," a& F+0<1 B@B@x@ADA2E&oaLra3 =dy`="4@Ā@ '$>A5 A6 B@B@x@ADA7E zaL) Aa8 = ӈ="9@:@ !A/SC: ; B@BH@x@ADA<E:{aa= =>A`=!I>@8@ q_dWL c̅tmia& m? A@ B@B@x@ADAAE aB ==>a$_C@5@ ! @>AD4 E B@B@x@ADAFE aG = = H@@ /mI| J B@B@x@ADAKEfbaL =`="M@z@7'ps@4>6?z W|%њ1Aa& _gN樀-+O B@B7@x@ADAPEcaLaQ =`=$_R@j@ # @>ASʥ T B@B@x@ADAUE`aL aV =j= W@a@4҃/CIAXQY B@B@x@ADAZE;aa[ ="`="A\@O@&AnhQv133Pݒ`8&IA]$^ B@B+@x@ADA_EԀ(a` =$_a@@@ PAb c B@B@x@ADAdEр ae =Sۀ="f@Ҁ@ &+$Q l@C /d"@Gg&h B@B@x@ADAiEbaj =œ`="k@%@& "!mISYt. \y;Aـ  B@B@x@ADAEߔaLaaa =="@@ /lKC{ B@B@x@ADAEfPaAbqa =W*`="@zN@ +UyQŝPDRZ>va&  AM$ B@B@x@ADAE+aLra =T5`="@iK@dAJ  B@B@x@ADAE6aL) Aa =="@@&/CP B@B 5@ADA @E; a = ?=Aa A@O@&$fS;COx[5\ @ ?g A  B@B@x@ADAEyBaLa =L`=)@?@dAA[ B@B@x@ADAEvMaL a =Z= @w@ A/_C% B@B@x@ADAE2Naa =8Y`="+@$0@ A(yyBʖDQÖ~:Xo쏻!A/  B@B"Y@x@ADAE{ꀈa =5d @-@dAt,  B@B@x@ADAE_ a =/="@@ /=C  B@B@x@ADAEeba =p`="+@@ w OKi̅Pt=IG줟}a& aAe$+ B@B@x@ADAEP[qaLAa ={`= @Ꝁ@dAI  B@B@x@ADAE4X|aL6ha =o`="@@ A~",ptV M % h!9(o@:Fa& 5C:  B@B@x@ADAE%́ 6| =ga"@@dA[W B@B`@x@ADAE Ɂ ? a =Ҁ="@9ʀ@  + /WC  B@B@x@ADAE@TWa =E`= @@AĠhC{<[2bl_.?HĦ>:5A B@B@x@ADAECz:A[ B@B@x 9ADA @Ee"1a = a"+@y@!PKvdPSjw8,gMw$9*` A  B@DB?@x@ADAEЭaL2@a1  `= @i@ @= W B@BW`x@9 A @EaL AAa =x="$@䫀@+ @A/1PA B@DB@x@ADA E:faBQa =l`="+ @Nd@ )m l?1m4l*`L*ftya& $qD c$+ B@B@x@ADAEaLR`a =i`=$_@?a@ @=`A[ B@B@x@ADAEaL aaa =U%="@@/]C%W B@Bw @x@ADAEׁ bqa =a%@#Հ@ CH{n:Z;۬ }a` f=a& `AԀ$ B@BJ@x@ADAEzaLra =`= @Ҁ@dA sѠ ! B@B@x@ADA"E^aL a# =2="$@@ /C% & B@BC@x@ADA'EGaa( =N`= )@E@%o҃G/PZ;CeTy7`*9e=:TkSeA*e+ B@BC@x@ADA,EOaLa- =K@ .@B@dA/I@0 B@B@x@ADA1E3 ) a2 =  3@c~vA/C4 5 B@BtA@x@ADA6ELa7 =wa"+8@ζ@&/[k2p/{ 25ψa& {9: : B@B@x@ADA;E%qaLa< =b`="=@@dA> ? B@B@x@ADA@En aLaA =w="B@8o@ A/DC D B@B@x@ADAEE)!aaF =@0,`= +G@'@&>4D/y-e>0r;OP(;Ip$H/PAH I B@B@x@ADAJE aK = 7-7a AL@$@dAM# AN B@B@x@ADAOE AaP =="Q@@&/|CRz߀ S B@B@x@ADATEd8bpliaU =%C`= V@|@ +Ͳ{:5T2.F@a& "xAW藀-X B@B+@x@ADAYERDaLAZ =N`= [@l@dA\ȔA[] B@B@x@ADA^EOOaL a_ =Y="`@P@ A/TCaOb B@B7@x@ADAcE: PaAad =[`="e@N @&Pé`Ut857v#\Af$g B@B@x@ADAhEÀ ai =fa"j@>@dAk$l B@BA@x@ADAmE !!an =Mʀ= o@@ +/SCp$A[+Aq B@Bg@x@ADArE|gb"1as =Âr`="t@#z@& Ēqbf6r24~o1֞Yxg:Auy+v B@B|@x@ADAwEy4saL2@ax =}`= Ay@w@ * @g>zsv { B@B@xy 9ADA| @E]1~aL AAa} =.;="~@2@& />C  B@DB`@x@ADAE BQa =a"@@&AvzQ};7V6?Tq13*M"]NAd + B@B@x@ADAEOaLR`a =`=$_@@ ' @>H  B@BA@x@ADAE3aL aaa ==+ A@c@&A.VC  B@B@x@ADAE]aqa =nd`= @[@ +' @gFQa*yy2yGza& ֚A9- B@B+@x@ADAE$aLra =ba`=$_@X@ ! @= A[W B@B@x@ADAEaL@Y a =="@<@ W&+g/ C B@BW@x@ADAE΁ a =Bոa"@̀@&@Gcz9؀D5GG"57R'A A B@B@x@ADAEaLa =7`="@ɀ@ #>AȠ  B@B@x@ADAE݃aL a ==$_+@ @ ! @+/Cy B@B@x@ADAEd?aa =F`="@x=@&$%2 r]ox Qk0Na& eA<$ B@B@x@ADAEa = Ca$_`3r@h:@ PA9  B@B@x@ADAE) a ={="@@& //ICN B@B>@x@ADAE9ba =`="@M@ #+uñ cir ^gP,,a& s A + B@B@x@ADAEhaLAa =`= @=@ ' @=  B@BA@x@ADAEeaL a =Qo=+"+@f@ ! @J/!C$ B@B@x@ADAE!aa ='`="@"@ vI<C#A1,"f'kmta& L*$ B@Bv@x@ADAEyـA) =$ x-"@@ !>r  B@B P @'@x@ADAE] a =1= @׀@ H/*  B@AbB@x@ADAE ba =`="@@XZbl(ck _@ݏ4O p8`F+c$ B@cB Jw@x@ADAENJaLA a =!`=+$_@猀@ # @=AG  B@BA@x@ADAE2G"aL) !!a =Q= @bH@ ! @A/LC % !kW B@B @x@ADAE#aA"1a =q .`="@@ +'a10[v19h(xD!OY-a& ?A9 B@B@x@ADAE# 2@a =a9$_@8`@ ! @>A A B@B@x@ADAEL) AAa =="@7@ /cC  B@B@x@ADAEs:bBQa =:zE`="@q@ m#5)C5Oaeeq2ث0OPF+a& :d + B@Bm@x@ADAE+FaLR`a =6wP`=+$_2@n@ # @>Am  B@B@x@ADAE(QaLaaa =2="@ *@ @A/dx)!& B@BJ@x@ADAEc䀈bqa = \a" @w@ CbF[ì Ǔ@= va& WG ဂ  B@B@x@ADA EΜ]aLra = g`=+$_@g߀@ @>Aހ  B@B W,X@x@ADAEhaL Aa =v=$_@⚀@v @/IAN!6  B@B@x@ADAE8Uiaa =[t`="@LS@2iQ+*ŷ:b3%ga& AR@0oc@m B@B@x@ADAE uaLAa = X`=+$_@A.qA[/ B@B@x@ADA0E\{aL a1 = ="2@|@ &+/~3 4 B@B@x@ADA5E6aa6 = =`="7@4@ #AkP[F${nϊ}n#coLF+8c9 B@B"Y@x@ADA:EM A9*gxa; =:a"<@1@>=K > B@B@x@ADA?E1 @Y Axa@ =@ ="a @3 4A@a@ /CB C B@B@x@ADADEbaE =AVm`="F@̥@ {~p gTdpͦ޶(AG8 H B@B@x@ADAIE#`aLJ =``=H@SQK@@ !>AL M B@BA@x@ADANE]aL+@saO =@f= P@7^@%/Q NR B@B@x@ADASEa.aT =F`="U@@&P\>DHj;c_Jka& jhDV-W B@BP@x@ADAXEЁ  aY =5$_Z@@ ' @>A[\ B@B@x@ADA]E !!a^ =׀="_@ π@&/DC`x΀ a B@B@x@ADAbEbb$"1ac =`="Ad@v@ +'&lE'دh'kpQ6AJ^7v,)a& ~&Aeↀ$f B@B 4@x@ADAgEAaL2@ah = `=$_i@g@ ! @= jƃ k B@B@x@ADAlE>aL AAam =H="n@?@ &+/ CoM+p B@B@x@ADAqE8 BQar =@"s@L`@ +#LsfH8J4i ݾ@~x]^aOAt$u B@B@x@ADAvELR`aw = aI"x@?@ #>Ay z B@B g@x@ADA{E aL) aaa| =V="}@@&/ 6C~" B@B@x} 9ADA @E k abqa =q`=$_@!i@ C^oƈxH%g<ݳd?"L` sAh + B@DB@x@ADAEx#aLra =n#`=$_@f@ ! @JAqe  B@B$@x@ADAE[ $aL a =,*="@!@ W&+#VA/C  B@B@x@ADAEہ a =/a"@ـ@&  lBjU飅NH]=a& mQAb  B@B"Y@x@ADAEM0aLa =:`=$_@ր@ # @=F  B@B@x@ADAE1;aL a ==$_@a@ `! @J/}C  B@Bt @x@ADAELA񷀂 A B@B@x@ADAErjaL@Y) Aa =|="@t@ W!A/FC{s +W B@B@x@ADAEb.kaa = 5v`=!I@v,@&JHj1`7h vt#(|a& `A+  B@B 4@x@ADAE怈A = 2$_@f)@ &+ @>(  B@B@x@ADAE〈 a =u="@@ /CLA[ B@B@x@ADAE7ba = 쥍`="@K@ #$*+k Gv*.0{÷#QAD$ + B@B@x@ADAEWaL a =ߢ`=$_@;@ @>A  B@B@x@ADAETaLA5!!a =Z^="@U@&$/| &  B@B@x@ADAE alXZ"1a =`="A@ @&N@t{~T| 5J{o~簔Ss*GF+ $+ B@B@x@ADAEwȀ@a =$_@ @ @>Ap  B@B@x@ADAE[ŀ AAAa =/π="@ƀ@ A/C W B@B@x@ADAE‱bABQa =`="@~@ PAwޯh)Hv.<&[:fAa + B@B\@x@ADAEL9aLR`a =`=+$_@{@dAEA[W B@B :%o@x@ADAE06aLaaa =@="@`7@&/LJC  B@B@x@ADAE bqa = ta @@&{"͘{4=%i95VsԗA7 B@B@x@ADAE!aL-ra =c`= @@dA3   B@B@x@ADAEaL) Aa =ް="@5@&/"C  B@B@x@ADAEbaa =Ai`=" @`@&δ6LtGWc oZ2`^Bja& Y  A B@B@x@ADA EaLAa = 4f`= @]@dA\ A B@B@x@ADAEaL a =!= @ @&/ w B@B@x@ADAEaӀa =@ @uр@ P@1IBjN! N@ DF+Ѐ- B@BP@x@ADAE̋aLa = `= @f΀@dA͠A[ B@B@x@ADA EaLda! =J`= "@NB@ :ڇv%qu4*h}8 oJ ̋KqC#A$$ B@B@x@ADA%Ea& =G%a '@>?@d(>A[) B@B@x@ADA*E:a+ =Y&a",@~  + /-% . B@B@x@ADA/E Lo:a0 =1a 1@$@ &l| d`ٱgi?6)8YC- a& D2$3 B@Bm@x@ADA4Evm2aL zA5 =<`= 6@@d7p 8 B@B@x@ADA9EZj=aL) WA!: =#t= ;@k@Ġ.-C< = B@B@x@ADA>E%>aWA? =,I`="+@@#@A[O ypqIu!Ad%31`AAa +B B@B@x@ADACEKށ A AD =)Ta E@ @dAFE G B@B@x@ADAHE/ۡ @Y !!AI =="J@_܀@&A/MK L B@BW@x@ADAMEUb"1AN =!o``= +O@ʔ@&AbG҇-u}DE.< DP6 Q B@B@x@ADARE!OaaL2@AS =^k`= T@@dAU V B@B@x@ADAWELlaL AA)AX =U="Y@5M@A/ CZ [ B@Bw g@xY 9ADA\ @EmaBQA] =<x`= ^@@ Ԃ)rs%dcs5_IPY$A_ -` B@DB @x@ADAaE AR`Ab =8 a c@@dAdA[e B@B}@x@ADAfEڼaaAg =ƀ="h@ @ /r$Civj B@Bu@x@ADAkEaxbAbqAl =`= m@uv@ V}9fCWeH.&=. `}D Anu$o B@B+@x@ADApE0aLrAq = |`= r@es@dAsr$t B@BA@x@ADAuE-aL AAv =7= w@.@ W/LCxKy B@BW@x@ADAzE6aA{ =`="+|@J@&$߂zY٥8 ֭7FO4A}怂$~ B@B@x@ADAEaLAA =`= @:@dA。  B@B@x@ADAEaL) A =M="@@&$/C  B@B+@x@ADAE ZaA = ``="@X@ A f:Qa="bifjĕy6 AW m B@B@x@ADAEvaLA =]`= @U@dAoTA[ B@BA@x@ADAEZaL A =#=G@ A@@ A/]C  B@B@x@ADAEʁ A = a @Ȁ@ @2`C {Λv"8@+PE萜 + `@2N B@B @x@ADAEKaLA = `= @ŀ@ @;ہD A B@B@x@ADAE/") A =="@_@+ @$/C]  B@Bp@x@ADAE;aA =]B`="@9@lYk 2!$}k74ڍB4<\+[a& {A5@0 B@B@x@ADAE  A = ^?a$_@6@ @=  B@B@x@ADAE @Y AZA =="@4@ WA/WC  B@BA@x@ADAEba = ; #@@ P 5sH1gjvAq1h aί A  B@B@x@ADAEdaL a =3`=$_@@+ @>A呂  B@B@x@ADAEaaL) !!a =k="@ c@&/XCub B@B@x@ADAE`a"1a = $`="@t@ XDTSj¬I %:7bC A A B@B|@x@ADAEՀ2@a = !'$_@c@ ' @>A  B@BA@x@ADAEҀ [AAa ={܀=+ A@Ӏ@ ! @/ `K  B@B@x@ADAE5(bBQa =3`="@I@&e`L*7䗂jpa& :o$ B@B}@x@ADAEF4J&A      R`A3  ݑ> H =`G `= @3@9@ $`6@*@ `@Q @C K`z.@G@T `@  iF B@B B @'@x@AD>EC?aLADaaa =@@[XM= @D@ `@ | e`L*7䗂jp?A  `XA B@B  @x@ADAE l#Cbqa = Ka"@J`@ #!K'#!ж$fj~&EDJCl&Փ~6A@4 B@B@x@ADAEuLra =VaIG @U`@ #$=n  B@B@x@ADAEYLAxa =@.="+@@ ! @Ġ/C  B@B@x@ADAEoWbka =vb`="@m@ +S(x#5NYg WeX #A` B@B@x@ADAEJ(caLa =sm`=$_@j@ ! @>AD  B@B 'A@x@ADAE.%naL) a =/="@^&@&/ՎC 4  B@x@ADAE a =eya"@ހ@%A>RQ1KDQŝYgnI-IA5  B@Bx @x@ADAEzaLa =a`="@ۀ@ '>A   B@B !@x@ADA EaLOm =П=)+ @3@ ! @OmA/@G !k B@B@x@ADAEQaa =7X`="@O@ ' ꒐x|eT j#lmPa& %A  B@Bu @x@ADAE aLa =2U`=$_@L@ ! @K=K  B@B@x@ADAEaL Aa =="@ @ /Cu B@Bt@x@ADAE_€F+ =ɨa"!@s@ #!҃V¥tgʯ|9E췍tA'ü g@ᇶ( B@B@x@ADA)EwaL a* =="+@x@ ! @҃/C,J- B@B@x@ADA.E53aAa/ =9`="0@I1@&XxIgTL5 (? T`j4.A10 2 B@Bx+@x@ADA3E뀈 a4 =6$_5@9.@ &+ @>A6-A[7 B@B@x@ADA8E耈 !!a9 =O=":@@%A/hC;< B@B@x@ADA=E b"1a> =`="?@@ #+Ay eRERYjdůRa& A@$A B@B|@x@ADABEt\aL2@aC =`=+$_D@ @ ' @>AEn AF B@B$@x@ADAGEXYaL) AAaH =-c="I@Z@&A/CJ K B@BA@x@ADALEaBQaM =`="N@@ >hńIvi] ; kQ浞eAO_ AP B@B@x@ADAQEJ́ R`aR =a+$_S@@ ! @>ATC U B@BA@x@ADAVE.ʁ  aaaW =Ԁ= X@^ˀ@ &+ @/CY AZ B@B@x@ADA[Ebbqa\ =d "]@ȃ@ #AlBŷC8%c2u.]ˊa0@ iA^4-_ B@B@x@ADA`E>aLraa =\`=$_b@@ # @Qz:=$cd B@B@x@ADAeE;aL 4Axaf =@D="g@3<@ !/Ah !owi B@B@x@ADAjE ak =>a"l@@%0#xB^9mݍ./V_a& Frm $n B@B@x@ADAoEaLap =2(`=$_q@@ &+ @>ArA[c @s B@B@x@ADAtEث)aLau ==+$_v@@ /Dwtx B@B@x@ADAyE_g*aaz =n5`="{@se@ +#!X:hg; (+hPRdk? &entba& A|d$} B@B+@x@ADA~E6aLa = k@`="@fb@ #>Aa  B@B@x@ADAEAaL) Aa =z&="@@ W! @A/QCI+ B@B@x@ADAE4؁ a =La"@Hր@&<֤Y\' 4Z{x IN a& (/AՀ  B@B@x@ADAEMaLa =W`=$_@8Ӏ@ &+ @>AҀ  B@B$@x@ADAEXaL a =KX`="@@ /C B@B@x@ =A @E IYaLa =Od`="@G@&$T4ҧŮx9R= ,7` ~nAF  B@DB@x@ADAEteaLA =Lo`=$_@ D@ # @>AmC  B@B@x@ADAEX a =(p)@~ ! @/BC  B@B@x@ADAE޹La ={a!I@@ +' @%>Bi7Kv1^l3^$+ B@BA@x@ADAEIr|aL a =`=$_@㴀@ ! @=$BA[ B@B@x@ADAE-oaL) !!a =x=+"@]p@&/   B@B@x@ADAE*aA"1a =l1`="@(@ P#!҃_FP]-?1Ժ;a& F+4 B@BA@x@ADAE 2@a =\.a"@%@ '>A  B@B@x@ADAE  AAa =="@2@ W! @A/FC W B@B@x@ADAEbBQa =5`="@@ v'TRgRM\{3'BJB$6A  B@Bs @x@ADAESaLAR`a =1`=$_@@ ! @>A핵`  B@B@x@ADAEP` aaa =Z="@R@ W/VCsQ B@B@x@ADAE^ abqa = `="@r @+Ala盱vT\6LINN1)h .A + B@Bp@x@ADAEĀra =$_@b@ # @>A  B@BA@x@ADAE a =qˀ="@€@%/}I  B@B@x@ADAE3}ba =٦@$_@G{@ ' @>EOxc**3KO_xXo@ aDz$+ B@B@x@ADAE5aLa =܀`=$_@8x@ ! @=w  B@B@x@ADAE2aL a =V<="@3@ &+/CA B@B@x@ADAE a =a"@@ #-H&ǩ.v1N+cy%M1A` + B@ByA@x@AD>Es`a =`=G@3R@@ #>Al蠂  B@B *@x@ADAEWaL a =#="@@ !/YC $ B@B$@x@ADA5  E^aa =e@K!I@\@&[aԲ~1U|bUH࠷sǻ@ цA^ B@DB@x` =A @EH aLa =b`=$_@Y@ &+ @JAB  B@DB@x@ADA E,aL) a ==" @\@&/'C   B@B@x@ADAEρ o7a =ca"@̀@ #$2`bQ>/vܙD8>ˏia& jA7 A B@Bt .@x@ADAE aLA =[*`=$_@ʀ@ ' @>A  B@B@x@ADAE+aL a =ʎ="@2@ ! @/!FC A B@B@x@ADAE@,aAa =9G7`=!I @>@ 3iԆ9,-.2ВԢ*\-= sA!" B@Bx@x@ADA#E  a$ =0DBa$_%@;@ ! @>&: ' B@B@x@ADA(E !!a) =="*@@ /C_C+s , B@Bw@x@ADA-E]Cb"1a. = N`= /@q@&AqM>'.94ot?VFC"0y@a& B0ݮ+1 B@B@x@ADA2EiOaL2@a3 =Y`=$_4@a@ # @>A5A[G6 B@B@x@ADA7EfZaL AAa8 =tp=H@4 9@g@ ! @A/!tC:H; B@BA@x@ADA<E3"[aABQa= =(f`=!I>@G @ ' @`Yߪ7f"BNKB_Ex")a& d?$@ B@B{+@x@ADAAEڀR`aB =%q$_C@7@ ! @=DA[E B@B@x@ADAFE׀) aaaG =V="H@؀@ /?dIJ B@B@x@ADAKErbbqaL =}`="M@@&4%N' 6Fh7EJPi&gzF+N O B@Bv@x@ADAPErK~aLraQ =`=+$_R@ @dASl T B@B@x@ADAUEVHaL@Y) aV =+R="W@I@&%o/VCX$Y B@B}@x@ADAZEaa[ = `=)\@@ ' @$jX8!8=5z^ua& "oA]] ^ B@B@x@ADA_EHaLa` =`= a@`@dAbA Ƕc B@B@x@ADAdE,L WXae =c{aI"f@r@ A)b PFo ǜc)\k@Cg2$h B@B}@x@ADAiE-aLaj =[x`= k@o@ ! @>lA[m B@B@x@ADAnE*aLAxao =@3="p@1+@  +/7lCq r B@B@x@ADAsEakat =8`= u@@JĠH71Ik|0G ;AbGx[n,a& ccAv w B@B@x@ADAxEaLay =0`=$_+z@@d{߀ | B@B@x@ADA}E֚aLa~ == @@&/OCr B@B@x@ADAE]Vaa =]`="+@qT@ '!Ġ>W#LvHɁc* $ƸCa+ga& ~AS$ B@B@x@ADAEaLA =Z`= @aQ@dAP  B@B@x@ADAE aL) Aa =w="@ @&/]RCG B@B@x@ADAE2ǁ a =a$_@Fŀ@ -`j0aJbɮUg2o@a&  ZAĀ A B@B@x 9ADA @EaL a =`= @:€@dAA B@DB`@x@ADAE|aL !!a =Q="@}@ /vCa B@BP@x@ADAE8a"1a =>  @6@ F`@^\ݺ TW ,^0_D 2ɾҦ E qA5$+ B@B}@x@ADAEr2@a =; @ 3@dAk2 A B@B@x@ADAEV AAa =="@@ /C Ø B@B}@x@ADAEܨbBQa =!`="@@&>gn'?־+ HVa& [A\$ B@B 5@ADA @EGa"aLR`a =,`= @ᣀ@dA@ `a/ ir B@DB@x@ADAE+^-aL) aaa = g= @[_@  `@$>.C  B@Bt@x@ADAE.aAbqa =n 9`="@@%Agsw1o){H~^z}'&tذ"`2 B@B@x@ADAEҁ ra =ZDa @@dA ` B@B@x@ADAEϡ @Y a = 5؀="@0Ѐ@ /M`  B@B@x@ADAEEba =3P`="@@ +B/.Ұ\t=1-0:AQa& F+  B@B+@x@ADAEBQaLa =/[`="@@dA넠A[ B@B@x@ADAE?\aLa =I= A@A@ /?/Cq@ B@B@x@ADAE\a = ha"@pg`@ gejP`v-6?N1ZF5Uj a& A  B@B@x@ADAEdzLa =raI A@`@dA  B@B@x@ADAEsaL Aa = w= @۱@ }/n:CG B@B@x@ADAE1ltaa =r`="@Ej@&Aa8 FVʺ-i\9bHQP [pi$+ B@B@x@ADAE$aL`a =o`= @:g@dAf  B@B@x@ADAE!aL a = M+= @"@ /~D B@B@x@ADAE݁ Aa =a"@ۀ@ mA1)8qo/7?/a&  Aڀ$ B@B@x@ADAEqaLA ~6`= @ ؀@dAjנ  B@B`x@9 A @EUaL a =&="@@ /.'C  B@DB@x@ADA EMa-&a =T`=" @K@ U -0' e&7Z y,ra& WA ` B@B+@x@ADAEFaL a = Q`="@H@ # :=C@ A B@B x@x@ADAE*aL) !!a = ` = A@Z@&A/C  B@B@x@ADAE A"1a =ja"@ż@ J*0Z;zHKʰ& [tJa& \A1  B@B @x@ADAEwaL2@a =Y`=$_ 4@@ &+ @>  ! B@BA@x@ADA"EtaL AAa# = 5}= $@0u@ ?&+ @g/C% & B@B +4@x@ADA'E/aBQa( =66`=")@-@ +#=䬥d Ar83kA*-+ B@B+@x@ADA,E R`a- =.3$_.@*@ # @=A/)A[06@-B@x@ADA1E䀈aaa2 =="A3@@ W!/C4q倂5 B@B@x@ADA6E[bbqa7 =`="8@o@&$[[Kb;hŸQ !(r*!sQdlA 9۝ A: B@@iB$@x@ADA;EXaL$ra< =`=$_=@`@ &+ @>A>A[? B@B@x@ADA@EU* AaA =s_="B@V@ /DCFD B@B@x@ADAEE1aaF = `="G@E@&$OAVd,?´vaGM[+AAH$I B@B$@x@ADAJEɀaK =a"L@4 @ #>AM N B@B@x@ADAOEƀ) aP =PЀ=)+Q@ǀ@&$/_CRS B@B@x@ADATEbaU =#`="V@@&!XחY.*'mPb8a& AW AX B@Bm@x@ADAYEq:$aLaZ =.`=$_[@ }@ PA\j| ] B@B@x@ADA^ET7/aL a_ =)A="`@8@&/5,Ca b B@B@x@ADAcE ad =:a"e@@ #&lW$m K3jVn_TSCqAf[ g B@B@x@ADAhEF;aLai =E`= j@@ PAk? l B@B@x@ADAmE*FaL@Yk an ==+"+o@^@ ! @/Cpʨ q B@B@x@ADArEcGaas =ajR`="t@a@&$xZ;r&V]C61:ݲB3a& vu0-Pv B@B@x@ADAwESaLA m =Yg]`= y@^@ &+ @>zA[{ B@B@x@ADA|E^aL6ha} =6i`=+$_~@Ҁ@$Ƕ%BTQ[1 W/R~)2‹UDLD$ B@B@x@ADAEjaL a =2t`="@π@ &+>΀  B@B}@x@ADAEԉuaL>!!a == @@+/Ct  B@B@x@ADAE[EvaW"1a = L`="@oC@&;2„( 1h3.xT-xza& hAB  B@BM@x@ADAE2@a =I$_+@_@@ P?  B@B@x@ADAE WAAa =va"@~&/CE"diK B@B@x@ADAE0LBQa6 ܼ'@D@A.廒=Zy}b6ɑ4ۥ\ nA  B@BC@x@ADAEnaLR`a =ع`= @4@ PA  B@BW@x@ADAEkaL aaa =Hu="@l@ -&&+)A/~ B@B@x@ADAE'abqa =-`="@%@&@ȶ.IsjV d~Rx91a& $$+ B@B@x@ADAEp߀ra =* @"@ # @=m!A[g@ᇶ B@B $`@x@ADAET܀ a ==+$_@݀@&/'G  B@B}@x@ADAEۗbAa =`="@@ '!AD©Ue*s"wa& ;}AZ  B@B@x@ADAEEPaLa =`=+$_@ޒ@ ! @=>  B@BA@x@ADAE)MaLa =V="@YN@ &+A/QC + B@B`@x@ADAEaa =d`=!I@@ # @V3.At]ll1A0 B@B<@x@ADAE a =X $_@@ # @>A A B@B@x@ADAE $a =ǀ="@2@ !/C + B@BC@x@ADAEybo ƴ̟a =5`="@w@&M3h-;"` AJ6لA  B@B@x@ADAE1aLa =1}@$_@t@ &+ @>As ` 6h B@BA@x@ADAE.aL}a =8=$_@0@&//Ct/  B@Bt @x@ADAEZꀈa = a"A@r@&v>Qu?3^2΢oa& JA瀂$ B@B@x@ADAEŢaL$ zA =`=$_@^@ ' @>A䀂  B@B@x@ADAEaLAx@! =@q= @٠@&/>fCEW B@B@x@ADAE/[a$&! =a%`="@CY@ '!Q.Q%eI |lQ[Lna& AX$ B@B@x@ADAE&aL A =^0`=+$_@3V@ ! @=UA[ B@B@x@ADAE~1aL !!A =R="7@@ &+/ C B@B  @x@ADAÉ "1A =A iƀ A B@B@x@ADA ESHaL) AA' = ="@@&/wC  B@B@x@ADAEA>  B@B@x@ADAE( @Y5 aaA =="@X@/>C  B@B@x@ADA!E`bbqA" =!dk`=$_#@ë@&@~Inu?/i6mKljh4iT[p\A$/ % B@B|@x@ADA&EflaLrA' =[v`=$_(@@ PA) * B@B@x@ADA+EbwaL A, =l= -@.d@ ! @/xpC.cA/ B@B@x@ADA0ExaA1 ==%`="2@@ m'AF`3Ev`sikSKP"a& A3-4 B@B@x@ADA5Eց A6 =-"a+ 7@@ ! @>A8A[9 B@B@x@ADA:EӀ A; =݀="<@Հ@ /eC=oԀ a> B@B@x@ADA?EZbkgA@ =`=$_A@n@ +# @ak|K[0~ADM%â? dBڌ$C B@B+@x@ADADEGaL FE =`=$_F@^@ @>G$H B@B@x@ADAIEDaL AJ =yN="K@E@ W/(LDM B@B:@x@ADANE/aAO =`="P@C`@&ujG.ӧ8ujaˏ_}0a& +F+Q$R B@B@x@ADASELAT =aI"U@3`@dAV AW B@B@xU 9ADAX @E}L@h5 AY =M="Z@@&/ݰC[A\ B@DB@x@ADA]EqbA^ =w`=)_@o@ AZN#AEvC A`n a B@B@x@ADAbEo)aL!)Ac =t`=)d@l@dAehkA[f B@BA@x@ADAgES&aL A_-Ah =0="i@'@ #V$/>X:j k B@B@x@ADAlE am =a!In@߀@ @ƃ'Us zqNRa& qAoY@0p B@B@x@ADAqEDaL Cr =`= s@܀@ @=t=u B@B@x@ADAvE(aL !!aw =저="x@X@+ @/D]Cy z B@B@x@ADA{ERa"1a| =_Y`="+}@P@ AJ5'KyB@Hg_wa& A~.$ B@B+@x@ADAE aL2@a =WV $_@M@ @=  B@B@x@ADAEaLAAa ==$_@- @ /M)CA B@B@x@ADAEÀBQa =4a"+@@ aAL؜^]Ap 10;Veaa& FA$ B@B@x@ADAE{aLR`a =,`=$_@@dA轀  B@B@x@ADAExaL) Aaaa =="@z@&/* ny B@B@x@ADAEY4abqa = ;'`= +@m2@  nQUF+3b 6/괌/a& D1  B@B@x@ADAE쀈ra =82@ @a/@ @+=A. m B@B7@x@ADAE逈; Om =>a"7@B@g̥ͥ~/7ZP:a9l/C  B@Bg@x@ADAE]?aLa =֨I`="@2@d  B@BW@x@ADAE}ZJaL a =Qd= +@[@ +ՙ/,~ B@B@x@ADAEKaa =V`="@@ Ġr4eߡOdZU^צZHWf`4<@ *D$A B@B@x@ADAEn΀a =@逃aa"@@dg  B@B@x@ADAERˀ a =Հ= @̀@ /Jw  B@B@x@ADAEنbbWa =m`="@턀@ Ġȭwɷ DvdDS+^5)a& xDY B@B @x@ADAEC?naLa =x`= A@݁@dA}b  B@BA@x@ADAEaL !!a ='= @@PA/2Cn"f5 B@B@x@ADAEXـ"1a =a"@l׀@ +<8 4Ew8u=QjXT"a& Aր$+ B@Bt@x@ADAEÑaL2@a =ݾ`=$_`3@]Ԁ@dAӀ  B@B@x@ADAEaL AAa =p= @׏@ W/Rt C B@B@x@ADAE-JaBQa =P`="@AH@&A>|uf5}g  B@B@x@ADAEQpaL) a =z= A@q@ W/]~ + B@B@x@ADAE+aa =2`="+@)@ +#!'# u;ŊM _, 5/6NrX  B@B+@x@ADA EC a! =/ $_"@&@ # @>#< $ B@B@x@ADA%E& @Y a& = +="'@W@&/x( ) B@BW@x@ADA*Eba+ =U`=",@@ \TX*㈍3jsQ̗z7%ӭja&  J-- . B@B@x@ADA/EUaLa0 =`=$_1@@ @>A2 3 B@B@x@ADA4EQaL a5 =[=+'u6@,S@ W&+ @$/C7R8 B@BA@x@ADA9E aa: =3)`=!I;@ @&A de^^ (lԁ2PgJkL!a& r<-= B@B+@x@ADA>E)aLa? =+4`=$_@@@ # @=`A `@  B B@B@x@? =AC @E€ aD =̀="E@Ā@ !#V/xVDFmÀG B@DBW@x@ADAHEX~5bAaI =@`="J@l|@ +A{RBs=p9b na& AK{$L B@B+@x@ADAME6AaL#AN =K`="O@[y@ !>APx Q B@B@x@ADARE3LaL aS =!o==$_+T@4@  @ /[CUB +V B@B@x@ADAWE- aX =Wa"Y@A@&0) Am3jsUA|NWa& bAZ쀂+[ B@Bug@x@ADA\EXaL a] =b`=$_^@1@ ' @=A_适 ` B@B@x@ADAaE{caL !!ab =D="c@@&/Cde B@B@x@ADAfE`dak U@s"1ag =fo`="h@^@&-)3 䗷psDcUuQ63@ dbAi] +j B@B@x@ADAN6EmpaL2@al cz`=$_m@[@ PAnfZ o B@DB@x@AD>pEQ{aLAxAAaq =@%=+)r@@&-/NCs t B@B@x@ADAuEЁ BQav =׆a"w@΀@ #!uԁw)esS۟΀{b?a& 7xW$+y B@BJw@x@ADAzEBaL-R`a{ =ԑ`= |@ˀ@ PA};A[~ B@B@x@ADAE&aLaaa ==+"@V@ ! @/  B@B@x@ADAEAabqa =aH`="@?@ ; ~q*1AYz|oK'?F+, A B@B}+@x@ADAE ra = UEa"@<@ !$> B@B@x@ADAE a =$_`3@+~ /VC B@B|@x@ADAELa =2'@@ #!W#}{M]HN,~ml]H\a& bA$ B@B<@x@ADAEjaLa =*`="@@ #>欀  B@B@x@ADAEgaL) a =q="@i@&/Clh B@B@x@ADAEW#aa =!A *`="@k!@ +W]o^> o, 7~3 YA e#XF B@B+@x@ADAEۀa =&$_@[@ ! @+=$  B@BA@x@ADAE a =w=+'u@ـ@ &+ @/SuCB  B@B@x@ADAE,ba =ٚ`="@@@ #Az:Je[KGxt/pz?،$"Hռa& ȅA$ B@BP@x@ADAELaLa = ؗ`=+$_@4@ # @=  B@B W@x@ADAE{IaL kfa = `="@@ @W%[qאx+RQ&P3WCb Qwbt! MC@2 B@B @x@ADAElA =r"@@ $ @QC >e`  B@B@x@ADAEP Axa =@Ā="@@ @/ W B@B@x@ADAEuba = |`="@s@ "Y&+ `HHy#݈3#^+ l}C]W@2 B@Bo + m@x@ADAEA.aL a =y`=$_@p@ ! @>;  B@B@x@ADAE%+aL) !!a =4="@U,@ &+/QC  B@Bq@x@ADAE "1a =\*a"@@ #aFIٮ8<D Pe&A, A B@BJ@x@ADAE+aL2@a = T5`="@@ m#>A  B@B@x@ADAE6aLAAa =ʥ="@*@A/C B@B@x@ADAEW7aBQa =.^B`=$_@U@ 'I9&hG]gzc7,pJ}(+M+՘sa& A  B@B@x@ADAECaLR`a =)[M`=$_@R@ ! @JAQ  B@B@x@ADAE NaL Aaaa == @@Ġ/Cl  B@B@x@ADAEVȀ}qa =!A Ya"@jƀ@ +#!҃Joaڵ͍ dŀ$+ B@B+@x@ADAEZaLAra =d`=+$_@ZÀ@ PA A[m9  B@x@ADAE}eaL/a =y="@~@ !/DE$ B@B@x@ADAE,9faAa =?q`=!I@@7@ +w쵙" ?NNL@>`lsiqa& #A 6$ B@B@x@ADA Ea = <| @04@ ! @+=:3  B@B@x@ADAEz Aa =C="@@ W/SC( B@B@x@ADAE}ba =`="@@&GjyH>0ob A$ B@B@x@ADAEkbaLa =`=+$_+@@ `@>e A B@B@x@ADAEO_aLbH a =i="!@`@ !'/BC" # B@B@x@ADA$Eaa% =!`=!I&@@ ' @/ёőG-hΟa^_"'V ( B@B@x@ADA)EAӁ a* =a +@@ ! @=A,: - B@B}@x@ADA.E%Ё  a/ =ـ="0@Uр@ / D1 2 B@B@x@ADA3Eba4 =g`="5@@҃F%öyt8E32//=1wa& z6+-7 B@B@x@ADA8EDaLA9 =S`=$_:@@ # @>A;< B@B@x@ADA=E@aL a> =J= ?@*B@ C! @A//D@AA B@B"Y@x? 9ADAB @EaC = ?=)a"AD@`@ 'fJMe| "GD N iAE@2F B@B@x@ADAGE봁L aH = {)aI$_I@`@ g! @>AJ K B@B@x@ADALEϱL !!aM =="N@@ /)OkP B@B@x@ADAQEVmb"1aR =t`="+S@jk@&$z"`BDC=(F;I Ea& LDTj$U B@BP@x@ADAVE%aL2@aW = p`=$_X@Zh@ @>AYg Z B@B@x@ADA[E"aL) AAa\ =m,="]@#@&$/`C^@_ B@B$@x@ADA`E+ށ BQaa =a +b@?܀@ ' @Am)VPZu'ض=SEy=tx7O }ہcۀ d B@B@x@ADAeEaLR`af =@$_g@/ـ@dAhؠ@i B@B @x@ADAjEy aL aaak =F="l@@ A/Dmn B@B@xl 9ADAo @EO abqap = 5=U`="+q@M@ +Qz"$k:_ $ArL s B@DBt @x@ADAtEkaLrau = R `= v@J@dAwdI x B@B@x@ADAyEO!aL az = = {@@ W//| } B@B@x@ADA~Eտ a =,a"+@齀@&&W%ȃlO1+ ~]+DU$+ B@B@x@ADAE@x-aLa =~7`= @ں@c9 A B@B@x@ADAE$u8aLa =~= @Tv@ /<~  B@B@x@ADAE09aAa = W7D`="+@.@ +A+5#sZ)K8ش&F؊S VO+ B@B+@x@ADAE a =S4Oa @+@dAA[ B@B@x@ADAE  Aa =="@)@ W/G怂 B@B@x@ADAEPba = -[`= +@@&]>5Tr SN8l1.Vg[$"A$ B@B@x@ADAEY\aL%oa = {,f`= @@dA蛠 B@B@x@ADAEVgaL) a =`= @W@&/Cj B@B@x@ADAEUhaa =s`="+@i@ A#&M8lFJ+ R + j*DA  B@B@x@ADAEʀ" = ~ @Y @dA  B@BA@x@ADAE a =lр="@Ȁ@ A/C@  B@B@x@ADAE*bpHa = ډ`="@B@&ƨ[ԑvtLϋI_}v A$+ B@B@x@ADAE;aL a =҆`= @/~@dA}  B@B@x@ADAEy8aL !!a =AB="A@9@&/wCA B@Bs:@x@ADAE $"1a =a @@&|˽Bj!XJ>׆97(vL2 a& A + B@Bm@x@ADAEjaL2@a =`= @@dAcA[ B@B@x@ADAENaL AAa =="@~@ A/C - B@B@x@ADAEdaBQa =k`="@b@ +Gk=~u4"^U"Ʋ֑uBKrY{a& vU B@B@x@ADAE?aLR`a =}h`="@_@dA9  B@B@x@ADAE#aLqaqa =Z`="@Ӏ@i]&7aFT -= ^a&  D*  B@B@x@ADAEaLWra =R`="@Ѐ@d W B@B@x@ADAEaL? a =ɔ="@)@  + |1C B@BO@x@ADAEFaa =! /M`="@D@&?}v{1] g:} ^AC  B@B@x@ADAEa ='J W@A@d@A[ B@B@x@ADAE a =a+ m@~&Ġ/JCj  B@BO@x@ADA:  ETLa = a"@h@A-^VY"$:#O+um3ԯZ@ _cAԴ$+ B@DB+@x` =A @Eo@Wa = `= @X@dAA B@DB@x@ADA El aL a =sv=" @m@ A/OC ?A B@B@x@ADAE*( aAa =.`="@>&@ Ġa9C$w_nxW& a& A%$ B@B @x@ADAEa =+"a"@-#@ #$=(" A B@B@x@ADAEx݀6ha =Q= A@ހ@%/9li +A B@B@x@ADAE#ba =.`=" @@ $ĠS81(;k`3_(ȯg xWxD! " B@B@x@ADA#EiQ/aLA$ =9`=$_%@@ ! @>C&c ' B@B@x@ADA(EMN:aL Aa) =X="*@}O@ :&+$/7C+ A, B@Bu@x@ADA-E ;aa. =F`="/@@ #+)B۠cl= Mdht}?0T 1 B@B@x@ADA2E?  a3 =| Q$_4@@ # @=A58 6 B@B C@x@ADA7E# A!!a8 =Ȁ=+"9@W@&$/?:á ; B@B{ @x@ADA<EzRb"1a= =Z]`=">@x@&$ոgw?7raO mu; DiIa& Zr?)-+@ B@By@x@ADAAE3^aL2@aB =V~h`=$_C@u@ &+ @>AD A[E B@B}@x@ADAFE/iaL AAAaG =9="H@(1@%/rI0J B@BW@x@ADAKE~뀈BQaL =*ta"M@@ #&lia\G%/paE#a& ; N耂 +O B@B}@x@ADAPEuaLR`aQ ='`="R@@ PAS堂A[T B@B +'XZ@x@ADAUE͠aL aaaV =="+W@@ ! @K/:XiY B@B@x@ADAZET\abqa[ =c`="\@hZ@ qK^Ӥ&$jʑ&U#hFa& A5]Y$^ B@BJ@x@ADA_EaLra` =!``=)a@\W@ ! @>AbV `ݟ/c B@B@x@ADAdEaL) ae =!o="f@@ W/Gag>h B@BW@x@ADAiE)́ aj =ӣa"k@=ˀ@ +#+AQ% L6J5CJҙe]$:,((Tlʀ Am B@B+@x@ADAnEaLao =Ю`=$_p@-Ȁ@ # @>qǀ r B@BA@x@ADAsEwaL at =A=+"u@@ ! @ /Cv w B@B@x@ADAxE=aay =D`="z@<@&$v55?IAb8  B@B@x@ADAEM$a =!=$_@@&$/9C  B@B@x@ADAEӮbCa =`="@묀@&A yJ0ڦ\kj!mid$XGe"a& zLW-0 @ B@B+@x@ADAE>gaLa =`=+$_@۩@ ' @=;A[a@၊ B@B@x@ADAE"daLa = m= @Ve@ ! @A/(zL¡$ B@B@x@ADAEao#a =U&`="@@ `'lIp8©a l3]{v]a& Mд-@0 B@Bm@x@ADAE؁ $ zA =Q#$_@@ ! @>A  B@B@x@ADAEԁ  A! =ހ="@'ր@ /jCՠ  B@Bq g@x@ADAE~b4! =*@"@@ m#z6PrEpA\&ͨ#Gf@ AǶ B@B+@x@ADAEHaL A =& `=+$_@@ @>A⊀  B@B@x@ADAEE aL !!A =O=G@b@F@&A/P9Ch B@B @x@ADAESa"1A =`="@g`@ LD{D]/G8tjoS%V VWa& Lm  B@B@x@ADAEL2@A =!$aI+$_@[#`@ ! @>A  B@B@x@ADAEL AA' =j='u@ҷ@ A/m> B@B@x@ADAE(r%b"! =x0`="@Aaݠ  B@B@x@ADAELSaL A =!="@|@ /  B@B@x@ADAESTaA =Z_`="@Q@ #7YOL Ik%xg ga& F+S B@B@x@ADAE= `aLA =Wj`="@N@ #>7  B@B8@x@ADAE! kaL) A =="@Q @ !A/C  B@B@x@ADAEā A =Uva!I@€@  #D_D/NZf=jb{z;Ψ(a zYA( A B@B@x@ADAE}waLA =Pȁ`=$_@@ ! @JA  B@BA@x@ADAEyaLqA =9<`= @3@&li&!?׭0/xmt'a& C2  B@Bli@x@ADAE퀈A;%9"@0@ &+ @>/  B@B`x@9 A @E A =="@@ $#Vli/Ch  B@DB@x@ADA ERbA =`=" @f`@&.(GղW`MI R%a& %A ңCA B@B@x@ADAE^a W-5A =`=$_@W@ # @=A[ B@B@x@ADAE[aL  A =ue="@\`~ !. C= B@B@x@ADAE(a Wa =`="@<@ '# ,Vx% EPD VwèľCK 3H{oA$ B@B$@x@ADAEπ a =a"@+@ PA  A! B@B}@x@ADA"Ev̀) !!a# =Bր="$@̀@ :/RCC%̀& B@B@x@ADA'Eb"1a( =`=%)@@&uk̀-NSv<,L<Va& PXA*} + B@B@x@ADA,Eg@aL2@a- =`=).@@J @JA/aA[A0 B@B@x@ADA1EK=aL AAIA2 =G="3@{>@&/tC4 5 B@BC@x@ADA6E BQa7 =a"8@@ dAEWfxC#J׿.ݲ2+

6 A? B@B@x@ADA@E!aL aaDA == B@Q@ `@E.CC D B@B@x@ADAEEi@TbqaF =Tp:Q G@g@ :2>$}jtk[[тxw gH'-I B@B:@x@ADAJE"aLAraK =Pm`="AL@d@dAM N B@B@x@ADAOEaLaP =(="Q@& @ /8RS B@B@x@ADATE}ڀAaU =)a"V@؀@ }@|5b߻ 'u֮ W׀ AX B@Bg@x@ADAYEaLaZ =%`="[@Հ@dA\ԠA[] B@B 1@x@ADA^Eˏ&aL Aa_ = `="`@@ W/?agb B@BA@x@ADAcERK'aad =R2`= Ae@fI@%o$oUL9d'ZDeTMWIJfH$g B@B%o@x@ADAhE3aLai =N=`= j@VF@dAkEA[l B@B@x@ADAmE>aL) an = u = o@@&/Cp<q B@B@x@ADArE' as =Ia"+t@;@ -&A 201$#e>l9eAu Av B@BR.@x@ADAwEtJaLax =ϿT`= y@+@dAz { B@B@x@ADA|EvqUaL a} =F{= ~@r@ A/PIC B@B@x@ADAE,Val  Ua =3a`="+@+@!Ahh KäbʆfRXEp0:W@ PA|*@1 B@B[@x@ADAEg倈" =0la @(@dA`'A[ B@B@x@ADAEK @sa =@="@{@$/C  B@B+@x@ADAEѝmba =x`=!I:@囀@ mAfW'ÛSYpa,W{da& 8AQ$ B@BP@x@ADAEJ  B@B@x@ADAEÁ ) AAa =̀="@%ŀ@&/CĀ B@B@x@ADAE|bBQa =1`="+@}@&^0B3Z@a`J a& g|  B@B@x@ADAE7aLR`a =$`=$_@z@dAy  B@B@x@ADAE4aL aaa = >="@5@ A/gf B@B@x@ADAEQ bqa = a"@e@ #O=<>*g&gA+Sa& F+퀂  B@B@x@ADAEaLra =`= @V@dAꠂ A B@B@x@ADAEaL a = t=+ W@Ц@g-&/C< B@B@x@ADAE&aaa =g`= @:_@ m^4Kv<f5u_g (]!(a& EA^$+ B@B@x@ADAEaLa =d`= @.\@dA[A[ B@B@x@ADAEuaL a =A ="@@ A/iC B@B@x@ADAEс Aa =a"@Ѐ@ P~1q`Vx? m j(ta% q(|π$ B@Ba@x@ADAEfaLa =`="@̀@dA_̀$ B@BA@x@ADAEJaL a =!A= @z@&/uD  B@Bg@x@ADAEBaa =I@"@@@ v'A!<>7pzKMNh@ ]Q B@B+@x@ADAE; a =yFa @=@dA5  B@B@x@ADAE @Y a =a"@O~&A/  B@BW@x@ADAELa =N'@@&l:H`y, y,JtjSx^a& #nF+& @J  B@Bp W c'@x@ADAElaL A#1 ='`= <@@ ,W @>  B@BA@x@ADAEh(aL a =r=+"@%j@ @҃/BCi!&Ţ B@B@x@ADAE{$)aa =(+4`=!I @"@ PAddDSwy-2P2a& iA !- B@B@x@ADA E܀ a =#(?$_@@ ! @>  B@B@x@ADAE !!a =="@ڀ@ }&+#V/iAf  B@Bg@x@ADAEP@b"1a =K`="@d@Aӂr "aD}4؉bQÊ>В@.Y B@BA@x@ADAEMLaL2@a =V`=$_@3H@T@ # @= A[ B@B@x@ADA!EJWaLqAQa" =AV c`="#@:@ !ZZr5YGp%kF6f B[ć!a& IA$$% B@ B@x@ADA&ER`a' = na"(@*@ P) * B@B@x@ADA+Et aaa, =Aŀ="-@@ !B//C.+/ B@B@x@ADA0Evobbqa1 =}z`="2@u@ War(ľ{f'V{a-Ra& dA3{t 4 B@BA@x@ADA5Ef/{aLWra6 =z`="7@q@ !M8_ 9 B@B@x@ADA:EI,aL a; =6=$_A<@y-@ /7HC= > B@Bm@x@ADA?E a@ =a"A@@ +#!QC A'ڑBfwz+eR0N03 + x@GBP C B@BA@x@ADADE;aLaE =x`=+$_F@@ # @>G4 H B@BW@x@ADAIEaL aJ =릀="K@O@ W! @ՙ/fTCL M B@B@x@ADANEXaaO =M_`="P@V@   DU22Wf{$  ֐^xa& '`Q%@0mR B@B@x@ADASEaLaT =N\`=+$_U@S@ @>V A[W B@B@x@ADAXE aLaY ==$_Z@$@ /`[\ B@B@x@ADA]E{ɀAa^ ='a"_@ǀ@$Ats}!9QLdfA]ޟ4Af ްȣa& OF+`ƀ$a B@B@x@ADAbEaLac =#`=$_d@Ā@ @>AeàA[f B@B g1@x@ADAgE~aL Aah =="i@@&/Cjek B@B@x@ADAlEP:aq am = A`="n@h8@ĠMJ@h'&z;R?va& Ao7$p B@B@x@ADAqEAr ==a"s@W5@ *>At4 Au B@B@x@ADAvE) aw =s= Ax@@.0Cy:z B@B@x@ADA{E%bAa| =ұ`="}@9@ +#!K˛IWc>W>B)e A~  B@BA@x@ADAEcaL a =ͮ`=+$_A@)@ ,W @>A  B@BA@x@ADAEt`aL !!a =A^A[ B@B@x@ADAEIс  AAa =ۀ="@yҀ@ / C  B@B@x@ADAEόbBQa =`="@㊀@ #+AQ07e@߱)ު0 p5 L AAO A B@B@x@ADAE:EaLCR`a =x)`=$_@ԇ@ # @>A3  A B@B@x@ADAEB*aLaaa =K="@NC@ !A/\mC  B@B@x@ADAE bqa =M6a"@5`@ +iKsicӮ=BcKP`e"a& q3R%@0 B@B+@x@ADAELra =AaI"@@`@ !>A  B@B@x@ADAEL@Y Aa ==$_+@'@ /9~ B@B@x@ADAEznBba ='uM`="@l@&nLU\QF#qLb#V ymBa& F+k  B@Bm@x@ADAE&NaLAa ="rX`=$_@~i@ @JAh  B@B@x@ADAE#YaL a =-="@$@&/OCd B@B@x@ADAEO߁ a =da"@c݀@&SyrQj.Gٝ>'k Ѵ.a& om܀  B@B@x@ADAEeaLa =o`=$_@Sڀ@ PAـ  B@B@x@ADAEpaL a =j= A@Ε@& /u: B@B @x@ADAE$Pqaa =V|`= @8N@&x f*9.w>ooyAa& aM$+ B@BA@x@ADAE}aLa =S`= @)K@ ,W @JAJA[Y B@B@x@ADAEsaL a =7="@@#V/g B@B@x@ADAE Aa =Ǔa"@@ +'$ΨX#\A>F'd$X$+P/]A[ B@B6h@x@ADAEHvaL a =="@xw@ &+/ʳC W B@B@x@ADAE1aa =|8`=!IW@/@ m# @+{ك@c` 1uvAa& /AO+ B@BA@x@ADAE9  a =w5$_@,@ # @>3 A B@B@x@ADAE @Y) !!a =="@M@ W!/ٜC=  B@B@x@ADAEb"1a =P`="@@ הiǃ~8L6d4شv]܅5Ba& !A$ + B@B@x@ADAE[aL2@a =L`=$_ @@ ! @>A   B@B@x@ADA EWaL AAa =a=$_@#Y@ /l CX B@B@x@ADAEyaBQa =.`="A@@ #!лTdjϢ$|駪vVA  B@B@x@ADAEȀ aaa =Ҁ="@ɀ@B҃/d B@BM@x@ADA ENbbqa! =`=""@b@!=_ C=n8 ͞JzF+#΁@4$ B@B@x@ADA%EnI/D?HS@ w`A2$3 B@B[p@x@ADA4E aLa5 =`=$_6@(@dA7A[8 B@B@x@ADA9EraL6ha: =l `=";@ d@ WnIJjg3 Եdy[IGд^C<yc = B@Bt  B@x@ADA>Ed!aLWa? =i+`= @@`@ +$ @JA] B B@B@x@ADACEG,aL? aD =%= +E@x@+0/CF G B@B@x@ADAHEց aI =7a+ J@Ԁ@ ' X]U~tz)x a& xAKN L B@B @x@ADAME98aL aN =zB`=$_O@р@dP6 Q B@B@x@ADARECaL i AxaS =@="T@M@&Ġ/CU V B@B@x@ADAWEGDaaX =\NO`= Y@E@&h(_[![pC8:Joa& =BZ#-[ B@B@x@ADA\EPaL"] =PKZ`= ^@B@dA_A[` B@B}@x@ADAaE ab =[a"c@"~ *#VĠ/˴Dde B@B@x@ADAfEyL-&ag =)fa!Ih@@ Qgbm4} ڊ c](ϝ1Ai$j B@B@x@ADAkEpgaL al =!q`= m@}@dAnݲA[o B@B@x@ADApEmraL) A!!aq =w= r@n@ /ǬCsct B@B@x@ADAuEN)sa"1av =/~`="+w@b'@&X,V-K8)I62ibm1Ya& Ax& y B@B@x@ADAzEဈ2@a{ =,a |@R$@dA}# ~ B@B@x@ADAEހ AAa =i="@߀@ /3C8 B@BH@x@ADAE#bBQa =ܠ`= +@7@ ACݘ`O2~g0[A  B@B@x@ADAERaL$R`a =ϝ`= @+@dA A B@B@x@ADAErOaL aaa =FY="@P@&/wC B@B@x@ADAE abqa =`= @ @ >2 FR"(*\2M|Y8ia& x$+ B@Bu X@x@ADAEcÀra =a @@dA\!FGA B@B$@x@ADAEG  a =ʀ="@w@҃/  B@B@x@ADAE{ba =`= @y@ >=e<4 61½1 ;>a& F+M A B@B @x@ADAE84aLa =v`= @v@dA1  B@B@x@ADAE1aLa =:= @L2@ /\C  B@B@x@ADAE a =Oa"+@@&q>UX=,>9hhRy3a& A# B@B@x@ADAE aLa =K`= @@dA  B@B@x@ADAEaL) Aa =="@!@&/k B@BA@x@ADAEx]aa =0d`="@[@&˘(o \y#4ʺDZ A B@B@x@ADAEaLa = a`= @|X@dAW * B@BA@x@ADAEaL6ha = =+ A@@>/~Cg  B@B@x@ADAEM΁ a = @ @à@&(ťSңɸb">s @3Zo's@ Aˀ$ B@B@x@ADAE aLA =`= @Qɀ@dAȀ  B@B@x@ADAEaL Aa =l= @̄@ /C8A B@B@x@ =A @E"?aa =E"`="@6=@&$ؕ7z!VF^CN}SYd"soA<$ B@DB$@x@ADAE$ a =B-a @&:@ 4 @>C9 A B@B@x@ADAEq@Y !!a =>= @@ /$ B@B@x@ADAE.bA"1a =9`="@ @ #!vQԭE~cJY-n[a& 9Dx$ B@Bm@x@ADAEbh:aL2@a =D`=$_ 4@@ # @>C\ A B@B@x@ADAEFeEaL) AAa = o="@vf@&/ύC A B@B@x@ADAE FaBQa =}'Q`="@@&y%q-/DP~}]&Ênͨzh@ iAM m(B@x@ADAE7ف R`a =@逃y$\a"@@ &+>A1  B@B[p@x@ADAE֡ @YAaaa =߀='u+@K׀@&/   B@B@x@ADA E]bbqa =Kh`=" @@!A0VёuYw3ӳQ{q&( m #ja& "  B@B(@x@ADA@@E JiaL-ra Ns`=$_@@ PA  B@DB@x@ADAEFtaL Aa =P="@!H@ ! @$/}GA[ B@B(@x@ADAEwuaa =4 `="@@&?/d7BQPds9/*A%5a& #IA`- B@B@x@ADAE⺁ Aa = ` !@{`@ PA" # B@B@x@ADA$EƷL a% ==$_&@@&/ըC'b( B@B@x@ADA)EMsbAa* =z`="+@`q@ #!+K҃Lr'Qv\gԶF3a& A,p A- B@B3R@x@ADA.E+aLa/ = v`= 0@Qn@ ' @>1m 2 B@B@x@ADA3E(aL@Y) a4 =o2="5@)@ !A/m6;$7 B@B@x@ADA8E"  a9 =a":@:@& 'މ2Y/sRr;w>a#f!ް0];D;ဂ$< B@B@x@ADA=EaLa> =`="?@)߀@ &+>@ހ AA B@B$@x@ADABEpaLaC ===$_AD@@&/nCE +F B@B@x@ADAGETaaH =[`="I@S@ #!A)fx;|Q*({h&}c\$Ua& AJ{R K B@B@x@ADALEb aL zAM =X`=+$_N@O@ ' @>AO[ P B@BA@x@ADAQEF aL A,CAR = ="S@v @ ! @ /YCT U B@B@x@ADAVEŁ AW =|a"X@À@ ʠ!MpOl}F-K?Nja& AYLZ B@B@x@ADA[E7~aL A\ = t`=$_]@@ ! @>A^0 _ B@B@x@ADA`E{aL; !1Aa =R=`="b@4@  ਻X'P?TQt$U_!9Y?͞c!$Ad B@B@x@ADAeE 2@Af =J:@"g@1@ ! @>h@ȰoĠi B@B@x@ADAjE  AA)Ak =='ul@ @ $ @K; /QF+m쀂n B@B@x@ADAoEwbB!!p = + `="q@@JĠ4zBnvd0L]^y Iu NAr$s B@B@x@ADAtE_ aLR`Au =#`="v@~@ #>wۡ x B@B}@x@ADAyE\aL) aaAz =f`="{@]@ !/C|af5} B@B@x@ADA~ELaLbqA =$`=!I+@`@Ġ-9DAi4@r֤89FA  B@B6h@x@ADAEЀrA =/$_@P@ PA  B@B@x@ADAÈ A =c׀="@΀@A/pjC6 B@Bv@x@ADAE!0bA =ُ;`="@5@ #$)`C B{>"I.g65Rrv + g  B@BC@x@ADAEAA  B@B@x@ADAEp>GaL A>DHG`="+>@?G`@ 1! @Ġ/5>  B@B@x@ADAEGa A> S`="A@ R`@ 'g+%hQJEvV5TUe -Dv@2j@ B@B @x@ADAEaLA =]aI$_@@ @>ZA[ B@B@x@ADAEE^aL A =="@u@&A'.ۿC  B@BA@x@ADAEj_aAA =xqj`="@h@&CQbCv@>֐fNr|a& K A B@B@x@ADAE6#kaLA = tnu`="@e@ '>A/ B@B@x@ADAE vaLA =)="@J!@&/  B@B@x@ADAEہ A =Na*`3@ـ@& ]c׷:i bz/ҁ:3. 7F+! B@B@x@ADAE aL@8 =Iߌ`=$_@ր@ * @JA A B@B@x@ADAEaL) A A =="@@&/6C B@B@x@ADAEvLaa =&S`="@J@ P#$$6VVՓ3=Zn[*R 'AI A B@BP@x@ADAEaL C =P`=$_@zG@ PAF  B@BA@x@ADAEaL !!a = ="@@"Y҃/: Ca B@B@x@ADAEK "1C =İa @_@3RĠ-m5 G,V4V.ӛ_4g;0 A˺$ B@B@x@ADAEuaL2@a =`= @P@ &+ @+=g  B@B@x@ADAEraL AAa =v|= @s@  `@/^C6 B@B@x@ADAE .a$BQa = 4`="@4,@ #!+hBNx5ݨz̧yP \A+$ B@B@x@ADAE怈R`a = 1a+$_@()@ # @>( B@B @x@ADAEo〈 aaa =7="@@ !/ pC  B@B@x@ADAEbAbqa =`=!I@ @&&p7Mfu V a& VqAv$ B@B@x@ADAE`WaLra = `=$_@@& @>AZ B@B@x@ADA?  EDTaL) a = ^= @tU@&/]C  B@DB@x` =A @Eaa = ?=w`="+@ @&S]Jg_!)g_KDN =# lAK  B@B@x@ADA E5ȁ a =s@$_ @ @dA /  B@B@x@ADAEš @Y a =΀="@Iƀ@&/q@C  B@B@x@ADAEbR = U`= +@~@ `@҃F f՟$HAMmsSg,݅ A  B@B@x@ADAE 9aLa = L`= @{@dA  B@B@x@ADAE5aL a =?=" @7@ /sC!6" B@B@x@ADA#Eua$ =*%a %@@&瑘dzK ـ9Ӷ"V)|: S)A&-' B@B-&@x@ADA(E&aLAa) = 0`= *@y@dA+렂 , B@B$@x@ADA-EĦ1aL a. =="/@@ /C0`1 B@B@x@ADA2EKb2aAa3 = h=`= 4@_`@&x+мGݡ"g؈78e y ΃A5_ +6 B@B$@x@ADA7E>aLJ8? eH`= 9@O]@ :* @=:\ A; B@B@x@ADA@@&/8C?5@ B@B@x@ADAAE Ӂ aB = Ta"+C@4р@&[5fΝ.{Lv8:"O;D4 UADЀ@1oE B@B@x@ADAFEUaL aG = _`=G d_H@$΀@dAÌ ` @ iFJ B@B@x@ADAKEn`aL) !!aL =6="M@@&/CN O B@B@x@ADAPECaa"1aQ =Jl`= +R@ B@ ' @҃Қ$E;֊d-6$MX*SuA AT B@B@x@ADAUE`2@aV = Gwa)W@>@dAXY dY B@BA@x@ADAZED @Y AAa[ = xa"\@t~ /QD] ^ B@B#@x@ADA_EʴLBQa` ={a$_a@޲@&Jh.g!5?X & 'cRFK|N@ jAbJc B@Bt @x@ADAdE5maLR`ae =r`= f@ί@dAg. h B@B@x@ADAiEjaL aaaj =s= k@Ik@&J/j(l m B@BJ@x@ADAnE%a$bqao =P,`="+p@#@&5zDlK W(`OПga& Dq$r B@B@x@ADAsE ށ rat =H) u@ @dAv w B@B@x@ADAxEځ 6hay =a"z@@ AWkS6) nsB^sfz#y2Q̋$C{@.C!]` W| B@B#@x@ADA}ENaLa~ =`= @y@dِ  B@B@x@ADAEKaL a =U="@L@ m/ҢC_A[c @ B@B@x@ADAEJaWa = `="@^@&@rlsqLJ¾!ԹS'ܻIa& A@0 B@B@x@ADAEa = a"@R@ #$C >: ` B@B6h@x@ADAE a =]ƀ= W@Ƚ@&/>8A4A[ B@B@x@ADAExba =~`="@3v@ Ae>3vs^e[ -͑  Na& +|Au A B@B@x@ADAE0aLa ={`=$_`3%o@#s@ @>r  B@BW@x@ADAEn-aL a =27= @.@+ @A/PC  B@B@x@ADAE a =a"@@k5-T~=ORJehTT0ȷ#aU Ct怂$+ B@B}  T@x@ADAE_aLAA =@$_@@ PAXA[ B@B@x@ADAECaLbH a ==$_@s@9~/sC GāB6h@x@ADA @EYaAa =``="@W@7Ġ/*lSVY1g-MV#oNk` eF+I A B@DB@x@ADAE4aL6h a =r]`= @T@ * @>A-A[ B@B@x@ADAEaL!!a =="@H@&/C  B@B@x@ADAEʁ "1a =K'a"@Ȁ@gĠmv&v4GF UקU8SԊA B@B}@x@ADAE (aL2@a =G2`="@ŀ@ PA  B@B$@x@ADAE3aL) AAAa == A@@A/"C!]f5 B@B@x@ADAEt;4aBQa =$B?`="@9@ '!ĠcÄz($-\͊BIJP_ ia& A8  B@B@x@ADAER`a =?J)W@x6@ ! @=`5  B@BA@x@ADAE aaa =="@@ }&+/V_  B@B@x@ADAEIKbbqa =V`="@]@ +#(nDr"(ٌ M2 a&  nDɩ$ B@B+@x@ADAEdWaLra =a`=$_@M@ # @>  B@B@x@ADAEabaL a =ak="A@b@ W!/BC4A B@B@x@ADAEca$a =#n`="@2@ 6a۠>Fg*ȶ}#/rv$źa& fA@0j@ B@B@x@ADAEՀa = y$_@#@ @>AA[ B@B@x@ADAEmҀ a =1܀=+$_@Ӏ@ /uJC  B@B@x@ADAEzbAa@@`="@@ PX+`GbܷXÛQmR"#^p iAt@2 B@B`x@9 A @E^FaLa =`=+$_@@ @>AX  B@DB@x@ADA EBCaL) a = M= @rD@ @҃/1iA   B@B@x@ADAE a =ya"@`@ m3O}v5v{` (hq;x\a& IF+I  B@Bw@x@ADAE3La =qaI$_@`@ ! @>A-  B@B$@x@ADAELa =轀="@G@ }A.C  B@B}@x@ADAEobo  +@a =bv`="@m@ &#+ |?9rǣ3 V2A " `"YXF! B@B@x@ADA"E (aLA# =Fs`=$_$@j@ # @>A% & B@B@x@ADA'E$aLAxa( =@.=+")@&@ ! @A/C*%+ B@BGa@x@ADA,Esa- =a".@ހ@ :ARxf+mK̄T@Ra& 6h/ݠ@00 B@Bp@x@ADA1EޘaLA a2 =`=$_3@xۀ@ @Q`B?A4ڠ `5 B@B@x@ADA6E•aL !!a7 ==+$_8@@ /B6h9^: B@B@x@ADA;EIQa"1a< =W`="=@]O@ Wuw4T*ƦUfv;`a& #>N A? B@Bu@x@ADA@E aL2@aA =T`="B@LL@>ACK D B@B@x@ADAEEaL AAaF =`= G@@ /#H3I B@Bt@x@ADAJE BQaK = a"L@2@&B+ ȖO=h&ahV H*CM$N B@B@x@ADAOEzaLR`aP =j8$_Q@"@ &+ @JAR AS B@B@x@ADA+x`ElwaL) aaaU =9="V@x@ /CWX B@DBy@x@ADAYE2abqaZ =9`="[@1@ #&l %zm!@@B؊3r!MJz}x5a& SA\s0 A] B@B|@x@ADA^E^뀈ra_ =6$_`@-@ # @>AaW b B@BA@x@ADAcEB 8 ad =="e@v@&/Cf g B@B@x@ADAhEȣbai =y)`=$_j@ܡ@ +Ϊ=)FfeO!g2EGΨv)$kH$+l@aBx@x@ADAm@ B 3\*aLan =p4`=$_o@̞@ ! @>p, q B@DB@x@ADArEY5aL as =b= t@GZ@ W&+ @$/-Du v B@B@x@ADAwE6alax =NA`="y@@&|<ĺf9x#צ;oa& kAz${ B@B@x@ADA|É a} =FLa+$_~@@ # @=A[ B@B@x@ADAEɁ a =Ӏ="@ˀ@ !/"CʀA B@B@x@ADAEsMbka =#X`=!I@@ +' @|Ɩh d.Kꍌkư3Fa& A󂀂$ B@B@x@ADAE=YaLa =c`=$_`3*@w@ ! @=  B@B@x@ADAE:daL) Aa =D="@;@&$|-.C]A B@B:@x 9ADA @EH a =oa"@\@ #r6\ %xݻ0_,wg + B@DB@x@ADAEpaLA =z`="@L@ '>A  B@B@x@ADAE{aL a =m`="@3f+@1e`@Aՙob}O큍SzVbu<l,h[ɠtEDd  B@BA@x@ADAEa  a =AVj`="@%b@ $ @Ja  B@B g@x@ADAElaLAx!!a =@8&= @@/C B@B@x@ADAEׁ "1a =ޞa"@ր@*ĠnYЌa!G<_YkUa& XArՀ$A B@B-&@x@ADAE]aL(@a =۩`=+$_@Ҁ@ PVA[ B@B@x@ADAEAaL AAa =="@q@ &+$+Ġ/C  B@B-&@x@ADAEHaWBQa =uO`=!I@F@ W# @T+ A B@B@x@ADAE ) aaa ="@F~ /;C B@Bt 9~@x@ADAELbqa = Ua"+@@ 6hFҥFI5 D$3 }$ B@B+@x@ADAEraLra =E`="@@dA  B@B@x@ADAEnaL) a =x="@p@ }b/|Do B@B@x@ADAEr*aa =1`= +@(@ +^Z`惗O젤Ҩ1ʢa& A' A B@B+@x@ADAE Aa =.a)@v%@dA$  B@BA@x@ADAE a =="@@ W/LC]  B@BsA@x@ADAEGba =`= @[@ @W| ˡѝphԽb 5Z'N]Xka& Aǘ$ B@B@x@ADAESaLa = @K@ @=  B@B ,XE@x@ADAEPaL a =ZZ="+@Q@+ @/LdDZzڋC>vD zLDK $ B@B`@x@ADAEĀAa =$__ @%@ @=?  B@B@x@ADAEk a =<ˀ="@€@ A.DK B@B@x@ADAE| bAa =+`="+ @{@ gCd}UR(5pQXa& F+ rz$ B@B@x@ADA E\5,aLC[ =6`=$_@w@dAV  B@BA@x@ADAE@27aL) a =  <="@p3@ }/[  B@Bg@x@ADAE o a =Ba"@@ {xs3|F~GZB(@Da& DK + B@B+@x@ADAE1CaL a =sM`="@@dA+A[/ B@B@x@ADA!ENaL!!a" =⬀= +#@E@&/$ % B@B@x@ADA&E^OaA"1a' =YeZ`="(@\@ d0KF-n\gc6SCa& RD) * B@B P@x@ADA+E[aL2@a, =Dbe`= -@Y@dA."O` / B@B@x@ADA0Ef AAAa1 = {= 2@@&/l~C3 4 B@B@x@ADA5EqπBQa6 =1qa"7@̀@ 6hG$RL*X1g&U_ B@B@x@ADA?E}aL aaa@ == A@@ /`CB\C B@B@x@ADADEG@~aAbqaE =F`="F@Z>@ q“= t̩cDި{C {F AG= +H B@By@x@ADAIEraJ =Ca K@K;@dAL:A[M B@B@x@ADANE aO =f="P@@&$/!CQ1R B@B?@x@ADASEbaT =Է`="U@0@ CbLݴ]™", 4_ysN^5V$W B@B@x@ADAXEiaLaY =Ĵ`="Z@ @dA[ \ B@B$@x@ADA]EjfaL) a^ = ;p= A_@g@&/ `a B@B@x@ADAbE!aac =(`="d@ @&A֊8;pN\# *R$D~?Ca& {MF+eq Af B@B@x@ADAgE\ڀah =% Ai@@dAjU k B@BA@x@ADAlE@ׁ  am =  ="n@p؀@&-/aCo p B@B@x@ADAqEƒbar =r`="s@ڐ@&JQt#yeubxo7槾({f` AtFu B@Bg@x@ADAvE1KaLaw =n`=`} x@ʍ@ 0$Jy* z B@B@x@ADA{EHaL@YL a| =Q="A}@II@ }!)-/j:C~  B@B@x@ADAEa$a =L `=!I}@@&AT.FR]IPe[H\l"o[n$ B@B@x@ADAE " =Da$_@`@ `@>JA[+ B@B@x@ADAE긁L a =€="@@ /ioD B@BA@x@ADAEqtbAa =){`="@r@&E⇩b} J=^Ka& NAq$ B@B@x@ADAE,aL a =x "@to@ #$>Jn  B@B@x@ADAE) aL!!a =3= @*@&%o/2bC_ + B@B@x@ADAEF "1a =a"@Z@& vJIF 6!F OmA   B@B{$@x@ADAEaL@a = `=$_A@J@ PA߀  B@B@x@ADAE!aL AAAa =e="@ě@&$/C0A B@B@x@ADAEV"aBQa =\-`="@/T@&bAQ|l[>OTb]*5a& .S  B@Bm@x@ADAE.aLR`a =Y8`= @Q@ PAP  B@B@x@ADAEj 9aL6haqa =D`=+ A@ŀ@ @1vSpRr޺&ZKS o%]Ѱa& DpĠ@0 B@B-@x@ADAE[EaLra =O`= @@ PXA[ B@B AC@x@ADAE?|PaL a =="A@o}@ g! @Ga/`C  a B@B@x@ADAE7Qaa =z>\`="@5@AĠ꽡 ⪳YXSVN[ͿB wlAE  B@BA@x@ADAE0 Wa =n;ga"@2@ P)A[c @ B@B@x@ADAE a ==$_@D@ /  B@B}@x@ADAEhba =Ps`="@@ #!+^5S쎗}[2Tsi+Va& &D B@B@x@ADAEataLa =C~`= A@@ # @>:  B@BA@x@ADAE]aLa =g="@_@&/6C^ + B@B@x@ADAEpaCa =$ `="@@A(n`=!'[ia&*2D4XUD  B@B@x@ADAEрa =$_@x@ &+ @>:  B@BA@x@ADAE΀a =؀="@π@ &+/D_  B@B@x@ADAEEba =`=!I@]@ # @ĠhN2 ݮdb^,ݽ>\bT L uAɇ$ B@B@x@ADAEBaL z 9 퍭`=$_@I@ # @>A  B@B@x@ADAE?aL A 9 \I="@@@ @/CB0m B@B@x@ADAE A =a"@.`@&vJj̈=gd^|7_6h]A$ B@BDK@x@ADAELA A =aI+$_ @@ &+ @K=1 ~A[ B@B@x@ADA EiaL !!A =1="@@ &+/UqC B@B@x@ADAEka"1A =r`=!I@j@ +# @A#36R+{Y6Q*Qko3%8a& Api$ B@BY@x@ADAEZ$aL2@A = o`=$_@f@ # @>AT  B@B@x@ADAE>!aL) AA( = +="@n"@ !A/vF  B@B@x@ADA E܁ B"!! =ua""@ڀ@&s\WxJF#q|lIBOK}P#E $ B@B@x@ADA%E/aLR`A& =m`="'@׀@ &+>A() ) B@B@x@ADA*EaLaaA+ =ۛ=",@C@&A/ }- A. B@BA@x@ADA/EMabqA0 =ST@%1@K@ +# @%`C KA*PJ:A7G 8 B@B@x@ADA9E aL AA: = =";@@ ! @҃/YcC<= B@B@x@ADA>EoA? =a"@@@ Ad5NJaM@ #ַG2 jAAﻀ-+B B@BA@x@ADACEvaLAD ="`=$_E@s@ ! @>AFӸ G B@B@x@ADAHEs#aL AI =}=$_J@t@ W/VCKZL B@B@x@ADAMED/$aAN =5/`="AO@X-@ +#!AëҟnT2C!KkRa& v[AP, AQ B@B@x@ADARE瀈AAS =2:$_T@I*@ # @=$U) AV B@B@x@ADAWE䀈 AX =d="Y@@ !A.wCZ/[ B@B@x@ADA\E;bA] =¦F`="^@.@ k T&gS]<1aӬM2M7a& iS_@0` B@B@x@ADAaEXGaLAb =Q`=+$_c@@dAd~ e B@B@x@ADAfEhURaL) Ag =,_="h@V@ /WiSiA[j B@B@x@ADAkESaAl =^`=%m@@ 1%9- 4e`yxz(pDF+no o B@B@x@ADApEZɀ Aq =i r@ @ +# @JAsSA[t B@B@x@ADAuE=Ɓ   Av =Ѐ="w@nǀ@&/hCx #ay B@B@x@ADAzEājba{ =xu`="+|@@ $,Sϟ*:4w7\Iwa&  A}D ~ B@B@x@ADAE/:vaL a =l`=$_+@|@ ! @>(  B@B@x@ADAE7aL !!a =@= @C8@ &+ @/C  B@B@x@ADAE "1a =Fa"+@@&A`Lt jʿ]r ˇ=ˇ;ia& *\A- B@B+@x@ADAEaL2@a =B`=$_@@dA쀂  B@B@x@ADAE觘aLAAa ==$_@@ / B@B@x@ADAEocaABQa ='j`="+@a@ P a+$v(4B/ .>ó0$g{c~a& `$ B@B Lc'@x@ADAEaLR`a =g`= @s^@dA] lM@W B@B@x@ADAEaL Aaaa ="="@@ }/ Y W B@B@x@ADAEDԁ bqa =ڻa +@XҀ@ mк>xEt_.-kuJpa |5IAр B@B+@x@ADAEaLra =`= @Lπ@dAΠA[ B@B[p@x@ADAEaL a =[= @Š@&A/C. B@B@x@ADAEEaa =K`="+@-C@&X(HIef|.m6Ŏ=/a& oB  B@Bm@x@ADAEa =H @@@dA}? A B@BA@x@ADAEh W$ =8 @~&/ D  B@B@x@ADAELa =aG@ @@& WOGpNc"oHSa={An$+ B@B@x@ADAEYnaLAa =`=+ @@dAR  B@B@x@ADAE=kaL@Y a = u="@ml@& /Z A B@B@x@ADAE&aa =|-@ +@$@ m `@  qe\Fs Ao1B_0WyJgx@ DC + B@Bm@x@ADAE.߁ a =p* a @!@dA'!A[ B@B@x@ADAE܀; a =R @@ g8/vR&>[O2ԷL`d7?XC@4 B@B@x@ADAEPaL(J =A$`="@@d  B@B}@x@ADAEL%aL a =V= @N@ +/uCM B@B@x@ADAEn&aa = 1`="@@ĠSX'oHxj&`["0p޵"6 ѲA  B@Bu@x@ADAEW a = ލ/ǒc s<; < B@B@x@ADA=E a> =@@$_?@7@ ! @=@6 A B@BA@x@ADABE aC = =bD@@Ġ/BE F B@B@x@ADAGEmbaH =`="I@@&BGJQpV F4 b~J$ma& F+J-+K B@B@x@ADALEeaLaM =`=$_N@u@ `@>AOѧ P B@B}@x@ADAQEbaL aR =l='uS@c@&/aTXU B@B@x@ADAVEBaaW =$`="X@V@ `'!AFE+Q@¼8;Oea& TDY$Z B@BJ@x@ADA[Eր"\ =!a ]@F@ ! @=A^A[z_ B@BA@x@ADA`EӀ aa =b݀="b@Ԁ@ &+ @A/Cc-d B@B@x@ADAeEbaf =ȕ`="g@,@&Nq!5'ٚU=Fi{Dފ~a&  Ah$i B@BJ@x@ADAjEGaL ak =`=$_l@@ # @=m| n B@B@x@ADAoEfDaL) !!ap = 3N="q@E@&/Q%Crs B@B@x@ADAtE "1au =@"v@`@ '+A'^?w@kt{]V\}Ss@ 1Awm mx B@B@x@ADAyEWL2@az =aI"{@`@ !>A|Q } B@B@x@ADA~E;L AAa =="+@k@ &+ @+҃/C  B@Bu@x@ADAEpbBQa =sw`="@n@ +#ֺ+"FC @_Puç)͞B  B@B@x@ADAE-)aLR`a =jt&`=+$_@k@ # @=A&  B@B@x@ADAE&'aL aaa =/="@A'@ W! @Ġ/sD  B@B@x@ADAE bqa =S2a"@߀@ ?^ ?_ҬRב?@F 66A- B@Bv@x@ADAE3aLAra =@=`=+$_@܀@ ! @>A۠A[  B@B@x@ADAE>aLa = ="@@ /li B@B@x@ADAEmR?aAa =YJ`=%@P@ +# @ҏ:fF䖿ѰP9vO֬GDa& 5O  B@B+@x@ADAE KaLa =VU`=$_@qM@ # @>ALA[ B@B@x@ADAEVaL Aa =="@@ W!A/W B@B@x@ADAEBÁ a =aa"@V@*Ġ\ ~3f>N;ck#_W }IA$ B@B@x@ADAE{baLa =l`="@E@ &+>A A B@B@x@ADAExmaL) a =b=$_+@y@&/OQC, B@BM@x@ADAE4naa =:y`="@+2@ +#!hQ8Ƶ[/<&ZtR{Մ2+E`FA1 A B@BA@x@ADAE쀈a =7a+$_@/@ ' @>A{.  B@BA@x@ADAEf a =2="@@ W! @҃/ jC  B@B@x@ADAE줅bo2U@sa =`="@@&lp20}^BcPܘqޠ1K.Z[i,Ap$ B@B@x@ADAEW]aLA = `=$_@3@@ &+ @>AP  B@B@x@ADAE;ZaLADa =@d=$_@k[@&/EC A B@B@x@ADAEala =r`="A@@&Y;L^ұyr;fa& UAA$ B@B@x@ADAE,΁  a =j$_@@ ' @>A%A[- B@B@x@ADAEˁ !!a =Ԁ="@@̀@ !$A/C A B@B@x@ADAEb"1a = C`="@@&T,-ƝeRԮ6_N$s AC B@B@x@ADAE?aL2@a =`=+$_@@ PA  B@BDK@x@ADAE;aL6hAQa =!`=$_@@ ՙ mW!%O;Ƌ={>a& kC  B@B@x@ADAE֯aLR`a = `= @p@ ! @+=}  B@B@x@ADAnEaL? Waaa =="+@ꭀ@ $#VUD/SGCV+ B@DB@x` =A @EAhabqa =n`="@Uf@ Ġ:YKR\ع{Լ^Q-%d.cGfv` ߽Ae  B@DB @x@ADA E aLra =k`=$_+ @Ec@ # @= b  B@BW@x@ADAEaL@Y a =\'=$_@@W/)C0  B@B@x@ADAEف a =@ @*׀@ĠY&Z'[HVt*~(rI@ lր$+ B@B@x@ADAEaLAa =`=$_@Ԁ@4 @>AzӀ  B@B@x@ADAEeaL a =1=  @@ &+ @/;D!A" B@B@x@ADA#EIaAa$ =P`="+%@G@ ,W{'fo(Da""bstY+?J&k +' B@B@x@ADA(EVaLa) =M(`=$_*@D@dA+OA[, B@B@x@ADA-E:  a. = )a"/@j@ /v0 1 B@B@x@ADA2E a3 =r4a$_4@ո@ADFCb9S>o8Cַ ? FF+5A6 B@B@x@ADA7E+s5aLa8 =i?`= 9@ŵ@dA:%A[; B@B@x@ADA<Ep@aL) a= =y= >@?q@&/+C? @ B@B@x@ADAAE+AaaB =F2L`="+C@)@ A_%9`4 EƘm3@a& 8AD +E B@B@x@ADAFE AG =>/W H@&@dAI% AJ B@BA@x@ADAKE aL =="M@@ :A/ΟCNဂ O B@B|C@x@ADAPEkXbaQ =c`="+R@@&5;"9w9ˮ_0a& >AS뙀$T B@B@x@ADAUETdaL aV =n`= W@p@dAXϖA[AY B@B@x@ADAZEQoaL !!a[ =[=G@6h\@R@ W/ȚC]VA^ B@B@x@ADA_E@ pal-L "1a` ={`= a@T @&Ū%nGC+'vTH֨hy  B@B@x@ADAEc؀ !!a =-= A@ـ@&/dC  B@B@x@ADAE+bA"1a = {6`="@@ +#!'# 12uaeT᧜mIԱ(qAj$ B@B+@x@ADAEUL7aL2@a =A`=$_n @3@@ ,W @> N  B@B 5@ADA @E9IBaL AAa =AV= S="@iJ@ W! @ /` + B@BW@x@ADAECalABQa =t N`="@@%gM![Q˓b8 Sra& OD?- B@B@x@ADAE* R`a =hYa$_@X`@ @>A#A[u B@B@x@ADAELaaa =À=$_@>@ :/)cC  B@B@x@ADAEuZbAbqa =I|e`="@s@ +A㞦Nrt^ ~uI Tu1y>a& ,A B@B)@x@ADAE-faLra ==yp`=$_@p@ # @>AoA[ B@B@x@ADAE*qaL AaE4="@,@ !$A/C+ ` @ XF B@BA`x@9 A @Ej怈a =|a"@~@&J#*DIY&]pyru'|l]:S ` A。+ B@DB+@x@ADA EԞ}aLa =`=" @q@ &+>A  A B@B6h@x@ADAEaL a ==$_+@蜀@&/?CT B@B@x@ADAE?Waa =]`="@SU@ #!+MCHTc`-ٜta& 'AT + B@B@x@ADAEaLa =Z`=+$_@CR@ ' @>AQ  B@BA@x@ADAE aL; a =Ϋ`="@(ƀ@&/ C ŀ$! B@B@x@ADA"EaLa# = ˶`="$@À@ $ @>%x€$& B@B@x@ADA'Ec}aL6ha( =8="+)@~@mW5* ++ B@B@x@ADA,E8aa- =?`=".@6@ &+$:J/HHł}C+{͒ #_hb$c -/i$0 B@B@x@ADA1ET WA2 =<$_3@3@ ! @=; 4MA[5 B@BN[B@x@ADA6E8  Wa7 ==b#8@h@ &+/o"Y9 $s`: B@Bu   @'@x@ADA;EbWa< =k`="=@ӧ@gĠf0G'0]P b/lcmL$aUnf zIA>?? B@AbB@x@ADA@E)baL aA =g`=+$_B@¤@ # @>AC# WD B@B}@x@ADAEE _aL!!aF =h="G@A`@&/yVCH +I B@B@x@ADAJEa"1aK =M!`=%L@@ P' @nsZi玟>mAO_1.m/Ϸژ b AM  N B@B@x@ADAOEҁ 2@aP =<$_Q@@ ! @=AR S B@B@x@ADATEπ AAAaU =ـ="V@р@ &+A/gCW~ЀAX B@BA@x@ADAYEibBQaZ =! @"[@}@ #oĠ>G[}|4=:|!s| ]$q ~A\鈀 ] B@B@x@ADA^EC aLR`a_ =`=$_`@m@ # @>Aaͅ b B@B@x@ADAcE@aL aaad =J="e@A@ ! @҃/CfTg B@B@x@ADAhE> bqai =!a"Aj@R `@ nQ#u_ESrH`U?Kq Ak$+l B@B@x@ADAmELran =+aI$_o@G@ ! @>ApA[p@` q B@B@x@ADArE,aL as =Y="t@@PĠ/u)v B@BP@x@ADAwEm-aAax =s8`="y@(k@ +#+App![5̿uc}VOA  3Dzj${ B@@iBr@x@ADA|E~%9aLCa} =pC`="~@h@ '>AwgA[d@ B@B6h@x@ADAEb"DaL a = >,="@#@ !/C  B@B@x@ADAE݁ a =Oa!I@ۀ@ 4 ҦQoW#/ ,hfAi B@B<@x@ADAESPaLa =Z`=$_@؀@ ! @JAM A B@B@x@ADAE7[aL$a =="@k@&A/Cס + B@B+@x@ADAEN\aCa =nUg`="@L@&rO+c8,BX5Me_0xd%a& CB  B@ B@x@ADAE)haLAa =jRr`=$_@I@ ' @>A&  B@B@x@ADAE saL~ Aa = = @A@ ! @/C  B@B@x@ADAE pa =@~a!I@@ ' @ghe%Yx[ms2-4ՈHɎoǧ8dA A B@B"@x@ADAEwaL$ zA = ;É`=$_@@ -&! @=  B@B@x@ADAEtaL A! =~="@v@ .BC~uA[ B@B@x@ADAEh0a$A =7`=$_@|.@&A^cbq]]BnK!^a& m-$ B@B@x@ADAE耈 A =4a$_@l+@ +# @>A*A[ B@B@x@ADAE倈 !!A =="@@ !#VA/#t S B@BA@x@ADAE>b"1A =`=!I@R@&'pҠGQ߆ TXO Z9\;a& Y$ B@B@x@ADAEYaL2@A =椸`=$_@B@ PAA[ B@B@x@ADAEVaL) AA)A =Y`= @W@%$/( B@Bx$@x@ADAEaB%! =`="+@'@ m#! v`2;-*ɽoV0XrSU^r + *@G m B@B+@x@ADAE}ʀR`A =a @ @dAw A B@B@x@ADAEaǀ aaA =*р="@Ȁ@&A/C  B@BW@x@ADAEbbqA =`=$_@@ 1y mLT|ǒ^|ifW8@ CAh  B@B@x@ADAES;aLrA =@逃`= @}@dALA[A B@B@x@ADAE78aL A =B="@g9@ W/oC  a B@BW@x@ADAE A =ra @@ yHmQ{#~ST* a& TA=- B@B @x@ADAE(aLAA =j`= @@dA!A[c @A B@B@x@ADAE aLA =ܲ="@<@ /C  B@B @x@ADAEd@TAA =?k `="F@b@&styՅDNJ9MYxNWy  A  B@B@x@ADAE aLA =;h`="@_@dA^  B@BA@x@ADAEaL$0 =#= @@ W/4 C } B@BW@x@ADA EhՀA ="a"@|Ӏ@ &A;q$Nw-/煩}^b77 AҀ$ B@B+@x@ADAEҍ#aLA =-`= A@lЀ@dAπ  B@B@x@ADAE.aL) A =="@拀@&A/CR B@B@x@ADAE=F/aA =L:`="@QD@ +fl $?oN&ۓuʴG:luSAMaASAC A B@B+@x@ADA!E!'" =IE #@BA@dA$@A[% B@B :*@x@ADA&E  A' =XFa+ A(@~&/eC)( * B@B@x@ADA+ELa, =ǽQa"-@&@ +#Jw,[Kw^R/ LlIa& HA.$/ B@B@x@ADA0E}oRaL a1 =\`= 2@@dA3v A4 B@B@x@ADA5Eal]aL6h!1D6 =.i`= :7@%@g  :'kOH\ƭd^QCI# q7a& :nC8g$A9 B@Bg@x@ADA:ER W2@a; =+ta+ <@"@ $ @A= =KA[> B@B@x@ADA?E6݁  AAa@ == A@fހ@ ! @/BCB  aC B@B@x@ADADEubBQaE =e`="F@і@ xhNm!"]Qw}w+ cC|AG=@4a@H B@Bp Jz3R@x@ADAIE'QaLR`aJ =`= +K@@ ! @> L! M B@B@x@ADANE NaL) aaGO =W="P@;O@ /CQ R B@B@x@ADASE abqaT =R`="U@@DS#+"t)Ա~G6ݭ4e ɠM*a& '\AV +W B@B@x@ADAXE raY =: a"Z@@>A[ \ B@B@x@ADA]Eྀa^ = Ȁ="_@@ #/pC`|a B@B@x@ADAbEgzbac =`="d@{x@  ;z0$! <D@dEkf3xa& hgew f B@B@x@ADAgE2aLah =~`=$_i@ku@ ! @JAjt k B@B@x@ADAlE/aL Aam =~9= n@0@/goRp B@B@x@ADAqE< ar =a"s@P@%Afȩ TM'iD/ #A^@qT:;E|a& 6F+t耂$+u B@Bm@x@ADAvEaLAaw =`=$_x@@@ ' @>Ay堂A[Oz B@B@x@ADA{EaL a| = X= }@@ :! @Ġ/~' B@B@x@ADAE\aa =b`="@%Z@&-1]Ùf-H*`̮2VDY` A B@B@x@ADAE|`a =_`=$_@W@ PAuVA[ B@B@x@ADAE`aL a = (="@@ W/C  B@BA@x@ADAÉ a =a"@ʀ@ +#+>N17LD`71{a& Wlig B@B@x@ADAEQaLU =@"@ǀ@ #$=K A B@B 8@x@ =A @E5aL) a =="+@e@ ! @A/dD  B@DB@x@ADAE=aa =mD `="@;@ rGd5#<}W%6{A< A B@B@x@ADAE'  a =dAa+$_@8@ ! @>  B@BA@x@ADAE @Y !!a = +=$_@;@ /C  B@B@x@ADAEb"1a =I$`="@@ H#!a(w/T"p~xjwoua& }P$ B@BE@x@ADAEf%aL2@a =9/`=$_@@ # @>A  B@B@x@ADAEc0aL AAa = m="@e@ H!A.a}|dA B@B@x@ADAEf1aBQa =&<`="@z@ mLgt^TG+>` *0| rq 0\ua- B@Bm@x@ADAE׀; R`a =#G$_@k@ ! @>AA[ B@B@x@ADAEԀ aaa =}ހ=+$_@Հ@ /gQA B@B@x@ADAEAu  B@B @x@ADAE_L) a =0="@@Ġ/MC  B@B} `@x@ADAEqwba =x`="@o@ #8*tr,̗IԸ$a& RRAf A B@B@x@ADAEQ*aLa =u`=$_@l@ PAJ  B@BA@x@ADAE5'aL a = 1="@e(@&/~C  B@B@x@ADAE a =da @@ -XSeHgXH0>OT c[gA;@4W B@B@x@ADAE&aLa =c`= @݀@ @JAB9 Gab|B@x@ADAE aL a =Ρ= @:@ &+ @A/hKC  B@B@x@ADAESaa =EZ`=" @Q@&AfbqW}"4_I@SxkA $ B@B@x@ADA E aL" =9W`= ! @d_ +@N`@ #>M  B@B@x@ADAEa a =I="@ @ !/C{ N B@BA@x@ADAEfĀa =a !:!I +@z€@ AҰš1^)c^lLC hA$ B@B*@x@ADAE|aL a =I`=  $_ +@j@ ! @>ʾ  B@B@x@ADA EyaL) A!!a! =I=""@z@ :/$C#P$ B@B[p@x@ADA%E;5a"1a& =;`="'@O3@ #$7 QQ/ۺ\cA-/ . B@B@x, 9ADA/ @Eꀈ AAa0 =IU="1@@ !/MC2%3 B@DB@x@ADA4EbBQa5 =`=  !I6 +@$@ [Exm 0ƈqezvEP2Il  ?A7 8 B@B@x@ADA9E{^aLAR`a: =I@H@ $_;@@ ! @>A<t = B@B ::;@x@ADA>E_[aL aaa? =+e=  @ +@\@ W/A B B@BW@x@ADACEabqaD =I`= E@@ m# @ANKչr?J8@;N0¹oFe$+G B@B P@x@ADAHEPρ raI =a J +@@dAKI L B@B@x@ADAME4́ aN =Iր="O@d̀@ / GP Q B@B@x@ADAREbAaS =p&`= !:T +@υ@&׫qlڻ~] :/3soF3sAU;V B@B@x@ADAWE%@'aLaX =Ic1`=  )AY +@@dAZ$[ B@B@x@ADA\E =2aL; a] =IH=`="^@@&g/_C_$` B@B$@x@ADAaE>aLab =8H`= c +@@dd e B@B@x@ADAfEޭIaLag =IpU`= +h +@yg@&3k;%V0_DU&k'R@6 if mj B@B@x@ADAkE!VaLmal =I m``= +m@id@d+nc zo B@B+@x@ADApEaaL maq =(= >-r +@@&+/`sPt B@B@x@ADAuE:ځ t av =Ila w@R؀@&^C߆ NYO#~Ea& 9Gx׀ Ay B@B:@x@ADAzEmaLޗ{ =w`= |@>Հ@d}Ԁ ~ B@B@x@ADAExaL a =U= @@&/uC% B@B$@x@ADAEKyaa =Q`= + +@#I@ buϻ/_ݥ7L|  H$ B@B+@x@ADAEzaL a =IN`=  +@F@dAsE  B@B@x@ADAE^aL !!a =I; =  +@@&>/O A B@B>@x@ADAE廁 "1a =I›a"+@@ +!je^Afz5 @5'a& Ve B@B+@x@ADAEOtaL2@a =`=   +@鶀@dAI  B@B@x@ADAE3qaL) AAa =Iz="@cr@ W/-  B@B@x@ADAE,aBQa =f3`="@*@ ?-+vt.l^`('d۠H aJ:  B@ B#@x@ADAE$ R`a =0a  +@'@dA  B@B4@x@ADAE aaa =I= W +@8@&&/C  B@B@x@ADAEbbqa =I?`="@@ +fkd?8!\[vj$Oyӝ]bYa& =-A  B@ B+@x@ADAEUaLra =7`= @@dA󗀂  B@B@x@ADAERaLa =\= @T@ WA/C~S  B@B@x@ADAEdaa =`="@x @&Cwqq=JOԃM6A - B@B@x@ADAEƀa = a }' +@h @ &+ @IC a> 4 ` B@B@x@ADAEÀ a =Ì= ! +@Ā@ /AO B@B@x@ADAE:bAa =I`="@M}@&AdBȐ6( :Vy quVԫDA| + B@B@x@ADAE7aLa =^ "$_ +@>z@ # @>vy  B@B@x@ADAE4aL@YL a =IT>="@5@ !$0v^'.\|`۪V3NuA퀂$ B@B@x@ADAEyaLa =`= S'$_ +@@ ! @>Asꀂ  B@B$@x@ADAE]aL) a =I*=  " +@@&/1CN B@B@x@ADAE`aa =Ig(`="@^@ `#!v82?.%g@AH  B@BA@x@ADAE24aL a = ="@c@ ! @A/bCϡ  B@ϠB|Z2@x@ADAEс a =i?a"@π@ +eu3hdۜFX_%b(/9π$B+@x@ADAa7E$@aL a =aJ`= A +@̀@ !>A  B@B@x@ADAEKaL !!a =IՐ="f dm$_@8@ W/'D  ]` @ ) B@B@x@ADA EBLa$"1a =CIW`=" @@@&A5N}?BWB):tt0`n[9At,Da& ]A- B@B|+@x@ADAE 2@a =7Fb$_@=@ # @`.A<@T B@B@x@ADAEAAa =ca &+  +@ ~ ! @A/5Cy B@BW@x@ADAEdLABQa =In'@x@ ''΅ܐq1g/ljep{A IA䰀$ B@@iB@x@ADAEkoaLR`a =y`=+$_!@k@ ! @>A"ǭ # B@B&@x@ADA$EhzaL Aaaa% =r="&@i@ `.eC'N ( B@B@x@ADA)E9${abqa* =*`="+@M"@ #!҃4I{01M)Rcyru 2;kADXA,!A- B@B(@x@ADA.E܀ra/ ='$_0@=@ # @>A1 A2 B@B$@x@ADA3Eـ a4 =T= gA5 +@ڀ@ !A/tC6#7 B@B@x@ADA8Eba9 =I`= : +@"@ ܛ?dӷp(J 4#]Kۣ A; +< B@Bm@x@ADA=EyMaLa> =I`=  $_? +@@ ! @>A@r A B@B@ 5@ADAB @E]JaL aC =I)T="D@K@ /OE F B@DB@x@ADAGEaZ:H = `="I@@&! )L[[^B +b%8/h#_R *4DJc$+K B@B@x@ADALEN aM = a  +$_N +@@ # @>AOGP B@B@x@ADAQE2  aR =IĀ=  )S +@b@&/CT U B@B@x@ADAVEvbaW =Ii}`= X +@t@&HVÁY_U@ot!Zjy%os ̳AY8$Z B@B@x@ADA[E#/aL a\ =Iez`=G@,W$_]@q@ `@>A^ `:iF_ B@BA@x@ADA`E,aLaa =5=  b +@7-@ `@/:Cc +`d B@B@x@ADAeE af =ICa g@@&$1ꋃ4:I?\D5A`8&ha& Ahi B@B@x@ADAjEaLJk =6`= l@@ # @=mဂ n B@B@x@ADAoEܜaL )Acap =_`= =Q q +@{V@$ՙ|Ts׷/3]&Џ"Ts舔Þ 8(rU s B@B@x@ADAtEaLW au =I \@"v@gS@ PwR x B@B@x@ADAyE aL?Ax!!az =@}= { +@@ !&l/(|M} B@B@x@ADA~E8Ɂ "1a =Ia!I@Lǀ@&MF3w2sّ̍̎YJtrƀ  B@B@x@ADAEaL2@a =`= i}&+'u + -<Ā@ PÀ  B@BW@x@ADAE~aL AAa =IW= !:  +@@}/͞#N B@B@x@ADAE :aBQa =I@*`=  +@!8@%oAWE'Kr 4]'CTY *7b LW7$+ B@BE@x@ADAExWR`a =I=5a+ @5@ ' @=Wq4A[ B@B@x@ADAE\ aaa =$= &+ +@@ ! @Ġ/C  B@BtY(`@x@ADAE6bAbqa =IA`=!I@@ +' @7pA}«Pf+%/9 dS^&(a& Bc B@B+@x@ADAEMcBaLra =L`=$_@祀@ ! @=FA[ `po   B@B@x@ADAE1`MaL a =i=  +@aa@&/sD  B@B@x@ADAENaa =Id"Y`=  +@@ 1#%ɱދwD gJVƨrQ'+ 9E8 B@B+@x@ADAE"ԁ a =I`da"@@ '>A  B@B@x@ADAEѡ @Y) a = +ڀ= =Q +@6Ҁ@A/E  B@Bf=@x@ADAEeba =IFp`=!I@@*U0A?$'GDuhz>̝a& F+  B@B*@x@ADAEDqaLa =9{`=  $_ +@@ 6h&+ @JA񆠂  B@B}@x@ADAEA|aL a =IK=  +@ C@ :&+#V/FCxB B@B@x@ADAEba =Ia  +@v`@ #V9 %$em_<Ip4# A- B@B@x@ADAE͵La =I aI  $_ +@f`@ @<  B@B@x@ADAEL6ha =I=  " +@峀@ /{CQ  B@B@x@ADAE7nb$a =It`="A@Kl@&A ,'G!Sf)%La& Ak$ B@BA@x@ADAE&aLA =q`=$_@ 6]n}b}c A܀$ B@B@x@ADAEwaL a =I`= @ڀ@dAqـ  B@B@x@ADAE[aLQ!!a =(= = +@@&/C +A B@B@x@ADAEOa"1a =IV`= @M@ {Eb~)MhSsBC a& Ab  B@B@x@ADAELaL2@a =S`= @J@dAFA[ ` B@B@x@ADAI  E0aL AAAa == @`@&/i3C  B@DB@x` =A @E BQa = ?=oab#Q+@˾@&yZ#31fTBZjLa* uA57  B@B@x@ADA E"yaLR`a =_`= @@dA   B@B@x@ADAEvaL aaa == @6w@ ` `@/Ga  B@B@x@ADAE1abqa =48`="@/@ Aҳw[\/An(zF+ @4d B@BP@x@ADAE ra = 95  @,@dA+  B@B@x@ADAzE怈a ="A @ @ /VC!w瀂" B@DB@x@ADA#Eb bAa$ =`="%@u@&A 9H' 2? EҘ \&ៀ ' B@B  !@x@ADA(EZaL$a) = `=G * *@e@ @=+Ŝ ,I , B@x@ADA-EW aL Aa. =a=H@! A/@X@&$/0L1 B@B6h@x@ADA2E7!aa3 = ,`="4@K@&,yVm;%aj]]qL&,fas IF+5$6 B@B@x@ADA7Eˀa8 =7a"9@;@dA: A[; B@B@x@ADA<EȀ$a= =ZҀ= >@ɀ@&/C?% +@ B@B@x@ADAAE 8bmaB =C`="C@$@&ag~bv}t[%hPa& AD E B@B@x@ADAFEwXE Y B@B@x@ADAZE0faL A![ =="A\@`@ }/"A] ^ B@Bs@x@ADA_Eega$=!` =clr`= +a@c@&Hk))7+=c[T_-Ld 5Ab6dW+c B@B@x@ADAdE!saL Ae =ci}`=$_Af@`@ # @K<Jg h B@B@x@e =Ai @E~aL!!Aj =$="k@5@ !#V/îCl cm B@DB@x@ADAnEց "1Ao =D݉a"p@Ԁ@ P'?J0OHlV~<2,a& Aq r B@ BPq 5@ADAs @ aL2@At = 9=4ڔ`="u@р@ !>AvР w B@B cNA$X@x@ADAxEڋaL AAA'y ==" @@30z@ @ :/*C{vG| B@B@x@ADA}EaGaB%!~ =AV N`="@uE@! v`lWĠ, I w2Ipl,N!T%X2 @GD  B@Bo  @x@ADAER`A =K$_@eB@ # @>AA A B@B$@x@ADAE) aaA =ta"@~ !A/CK  B@Bq@x@ADAE6LbqA =澸'@J@&$Onq[`sFUI_PJrgt۾@a& O+  B@B@x@ADAEpaLr &+ =޻`=$_@:@ PA  B@BA@x@ADAEmaL; A =/`="@'@ /1&$ B@B@x@ADAEvဈ(# =,a"@$@ !#VJo#$ B@B@x@ADAEZ A =&='u@߀@ :$ @B5 W B@B@x@ADAE@TA = 0`="@@W}nIp-3Ҍ(g$N"cDL74  B@B@x@ADAE @Yy A =ɀ="@4@ W/z5C  B@By@x@ADAE{ bA =;`="@y@&>;*UZum*.9î J  B@B@x@ADAE3aL A =3!`=+$_@v@ # @>Au  B@B@x@ADAE0"aLWvA =:='u@ 2@ ! @A/;vu1 B@B`@x@ADAE`쀈a = -a"@t@ +'AP|EVDQg8XyjB#"j F+适  B@B@x@ADAEˤ.aL a =  8`=+$_@h@ ! @>A怂  B@B}@x@ADAE9aL A!!a ={="@ߢ@ /3J=K B@B@x@ADAE5]:a"1a =cE`="@I[@&WsN:e5Ya3,o<@-a& EAZ$+ B@B@x@ADAEFaLA2@a = `P`=+$_@9X@ # @>AW Y B@B@x@ADAEQaL@YAAa =P="@@ !$A/iabqaJ@Et`="@<@ #$s׷\mRC֫~q454 ` B@B+`x@9 A @EJ ra = ?=Ba"@9@>AD  B@B + @x@ADA E. 4) a ==" @^@&A/gD  A B@B@x@ =A @Eba =j`=$_@ɭ@ _AOWeD|_/d\]:jj b 3A5`4 B@DB @x@ADAE haLa = ]`=$_@@ ! @+=A  B@BA@x@ADAEeaL a =n= @4f@UD/C  B@B@x@ADAE aa = ?'`="@@&n#^+iބJ|Wչ= uA  ! B@B@x@ADA"E؁ a# =2$$_$@@ ,W @>A% & B@B@x@ADA'E a( =߀=")@ ׀@&/1YC*uր A+ B@B@x@ADA,E_b$a- = `="A.@s@ '&l 6]o`$? B@B-@x@ADA@E AA =`"B@8`@dAC D B@B@x@ADAEEL) aF =W="G@@ C/CHI B@B@x@ADAJE sbaK =y`=%L@q@ gU.R}cm/!AMp N B@B  C@x@ADAOEt+aL aP =v`=)Q@n@dARnmA[S B@B@x@ADATEX(aL !!aU =92= V@)@ /CW X B@B@x@ADAYE "1aZ =@"+[@@ ּ[sIώvU ʞv(Mu/cpS@ 9A\_ ] B@B :@x@ADA^EJaL2@a_ = `= `@ހ@dAaC b B@B !(@x@ADAcE. aL AAad == e@^@ /Cf Ag B@B`@x@ADAhET aBQai =e[`="+j@R@ PH'^²%I_3Jhaߋn3 %a& k4-l B@BP@x@ADAmE aLR`an =]X#`= o@O@dAp q B@B@x@ADArE $aLaaas == t@3 @ 6h/Du !jv B@Bs@x@ADAwEŁ bqax =:/a"+y@À@&:8Lb,ԡ&8y%+a& :Az { B@B@x@ADA|E}0aLra} =6:`= ~@@dAA[c @ B@B@x@ADAEz;aLa =="@|@ / Ct{ B@B@x@ADAE_6,J?u.Ҁ,ȷXa& A3$ B@B@x@ADAEa =:R @c1@dA0A[ B@B@x@ADAE뀈) a =="@@ /|"CI+ B@B@x@ADAE4Sba =^`="+@H@ P1T9[qlǬq6u@}( =a& [oA  B@Bu@x@ADAE__aLa =ܪi`= @8@dA A B@B@x@ADAE\jaL a =Kf="@]@&/CC B@B@x@ADAE kaa =v`="@@ ?yPC~k{&|S,M ĝ(A  B@B@x@ADAEtЀa = @@dAmA[A B@B@x@ADAEX̀6ha = W@@A9Egw?<~BCW+a& `c'^$ B@B:@x@ADAEIAaLWA =`="@⃀@dBA[ B@B@x@ADAE->aL a =H="+@]?@  +`/c'  B@B  @x@ADAE a =ga"@`@JĠW:PXH8qmŷ;:! <5G4 B@B{@x@ADAEL a =\aI"@@ #'J A[W B@B@x@ADAEaL !!a =Ӹ= @2@vĠ/C  B@B`@x@ADAEja"1a =5q`="@h@Ġnm~*z ՕVQt!aW4ԃa& kA  B@BJ@x@ADAE"@T2@a =1n`=$_@e@ 4 @>d  B@B@x@ADAEaL) AAa =)="@!@ }&+$/Cs  B@B@x@ADAE^ۀBQa = a"@rـ@;*Ȫ++cml=5  A؀  B@B(@x@ADAEɓaLR`a =`=$_@bր@ # @=Հ  B@BA@x@ADAEaL aaa =q=+"@ݑ@ @A/ CIA[ B@BO@x@ADAE3Labqa =R`=!I@GJ@ ' @A ʭ*6~c=b`0|}AI$+ B@BC@x@ADAEaLra =O`=$_@;G@ ! @AB  B@B@x@ADAE, @Y a =="@\@&/C  B@B@x@ADAE&ba =c1`="@ǜ@ 6h#xn 3+8gjvQT4fmca& , A3 m B@BJ@x@ADAEW2aLa =[<`="@@ '$>A  B@B@x@ADA!ET=aLa" =]="+#@1U@ `! @A/&C$  % B@B@x@ADA&E>aq a' =AI`="(@ @&`ymM^1h>CP1Ka& aA) * B@BA@x@ADA+Eǁ A%m = 0Ta+$_-@ @ &+ @>A. / B@B@x@ADA0E Aa1 =΀=$_2@ƀ@%/(MC3sŀ 4 B@B@x@ADA5E]Uba6 =``="7@q~@!҃`m(FBh6j9E縍ua& 6A8}@0+9 B@B@x@ADA:E8aaLA a; =k`=+$_<@a{@ ' @>A=zA[> B@B@x@ADA?E5laL !!a@ =p?="A@6@A/"CBHC B@B@x@ADADE3 "1aE =wa!IF@F@&Aݸ߉0P)-4ħ&(.HL\ Ia& GAV_$W B@B @x@ADAXEraLR`aY =e`="Z@ ]@ #$=[l\ A\ B@B@x@ADA]EVaL) aaa^ =!="_@@& /kC` a B@B@x@ADAbEҁ bqac =٦a%d@Ѐ@ +' @^>\=#B__t0p` kVCYBa& Ae] `J XFf B@B+@x@ADAgEHaLrah =ֱ`=$_i@̀@ ! @=jA k B@BA@x@ADAlE+aL@Y am =="n@`@>A/Co̡ p B@Bg@x@ADAqECaar =_J`="s@A@ +#!҃ڞSNdKF z@/5jӾ, pzAt2$u B@B@x@ADAvE aw =ZG$_x@>@ PAy z B@B@x@ADA{E  a| ="}@1~ W! @/VC~ A B@BA@x@ADAEL$a =4a"A@@&/b<}1x2 Vmr][:lA B@B@x@ADAElaLA ba =0`= @@ &+ @>A뮠A[ B@B@x@ADAEiaLa =s="@k@ /M@CrjA B@B@x@ADAE]%aAa = ,`="@q#@ +#+Ag e>){@JqUH9a& "$ B@B@x@ADAE݀a =)a"@d @ #>A[ B@B@x@ADAEڀ Aa = s="@ۀ@&/bG B@B@x@ADAE2ba =ߜ@$_g @3@F@&**?x-;'-*SW3{(kY}';5}F+$ B@B@x@ADAENaLA =AVڙ`=$_@6@ `@JA A B@B$@x@ADAEKaL) a = IU="@L@&/GQC B@BA@x@ADAEaa = `="@@ #$҃H>}ZeZq?F8)a& *MC  B@B@x@ADAEr a = ' @ @ PAk  B@B@x@ADAEV !!a =ƀ="@@ !/{gC  B@B@x@ADAEw(b"1a =~3`=!I@u@ vzB>u)Ą"(vɨea& IA\$+ B@B@x@ADAEG04aL2@a ={>`= @r@ #! @+=@  B@B@x@ADAE+-?aL AAa = 7= @[.@%J/C  B@B9~@x@ADAE BQa =fJa"@@&g%toi[JdnIʗꊃ>$aa& ~A1$ B@B%o@x@ADAEKaL$R`a =ZU`=+$_+@@dAA[ B@B@x@ADAEVaLaaa = 5̧="@0@&/URC  B@B@x@ADAEYWak:@sbqa =?`b`= +@W@ ' @aa6T$j؟OcfcZ 2@ bjA B@B 9~@x@ADAEcaLra =/]m`= @T@dAS  B@B@x@ADAEnaL) CAxa =@="+@@ W/FCq B@BW@x@ADAE\ʀa =ya"+@pȀ@ M׋ZDg~4ȝjQ*~~&.{F!` Aǀ  B@B>@x@ADAEƂz`Aa =΄`= @`ŀ@dAĀ  B@B@x@ADAEaL a = ="@ڀ@&A5/h0CF B@B@x@ADAE1;aa =A`= +@E9@ +8]Jք·0T38fZ` LA8  B@B+@x@ADAEa =>a @66@dAL5  A B@B@x@ADAE a = T=b @@ WA/C  B@BW@x@ADAEba =`= @@& qfO"lbƚ3_6-ڛa& ;F; $+ B@B@x@ADA EqdaLAa =`= @@dAnA[A B@B@x@ADAEUaaL A =%k="@b@ /F{ A B@B@x@ADAEaAa =#`= @@ A/N߳%YN]ƍqCq}vVD[ + B@B@x 9ADA @EFՁ  =  @@dA?  B@DBA@x@ADA E*ҡ ) a! = +ۀ=""@ZӀ@ /# $ B@B@x@ADA%Eb.a& =e`="+'@ɋ@ P2wD] ]ƷT|a& D(5) B@B@x@ADA*EFaL a+ =Y`= ,@@dA- . B@B@x@ADA/EBaL) !!a0 =L="1@/D@&A//C2C3 B@BA@x@ADA4EA"1a5 =6a"6@`@ +H엱gsGgtFz;?1A< fA7 A8 B@B@x@ADA9EL2@a: =2aI ;@`@ ! @P=< = B@B =QV@x@ADA>EԳL AAa? ==+ W@@@ A/CAq B B@B@x@ADACE[obBQaD = v  E@om@&Mz8yH=;Y(sn^@ ytFl$G B@Bm@x@ADAHE'aLR`aI =s`=$_J@_j@ # @>Ki L B@B@x@ADAME$aL aaaN =v.= O@%@&/z:PF"dnQ B@B@x@ADARE0 $bqaS =a"T@Dހ@&a/c|'&| E 3Ia& F+U݀-V B@B@x@ADAWEaLraX = (`=$_Y@4ۀ@ PAZڠ [ B@B@x@ADA\E)aL a] =L= ^@@ &+ @+*/C_` B@B@x@ADAaEQ*aAab =W5`="c@O@ ># -11u`Qj0Sa& AdN$e B@Bm@x@ADAfEp 6aLag =T@`= h@ L@ # @=iiKA[g@ᇶj B@B@x@ADAkETAaL al =$="m@@&/jCn o B@B@x@ADApE aq =La"r@￀@& }y /fJspYa& :As[t B@B@x@ADAuEEzMaLav =W`="w@޼@ &+>x? y B@B$@x@ADAzE)wXaL a{ =='u|@Yx@&/C} +~ B@B@x@ADAE2Yaa =e9d`="@0@ #!A Oa ]vZ:.Ī 6A0  B@B@x@ADAE a =X6o$_@-@ PA  B@BA@x@ADAE @Y a =="@3@&/3C耂  B@B@x@ADAEpba =2{`="@@ ٢ ŨC4y(H}Voc/0(62j5O L ^A- B@B%o@x@ADAE[|aL =-`= @@ ! @+=靀  B@B@x@ADAEXaL a =b="A@Z@&%o/CpY B@Bt P@x@ADAEZaa = `="@n@ #A#;p$XGf aI &/Y`|B LxTOiA$ B@B+@x@ADAÈ a =$_A@_@ PAA[ B@B@x@ADAEɀA!!a =yӀ=+"@ʀ@ ! @+$/PECI$ B@B@x@ADAE0b"1a =`="@D@ hgVXZ>6oMZp<{a& =)A$ B@B@x@ADAE=aL2@a = ܈`=+ @7@ ! @>A  B@Bc@x@ADAE~:aL) AAAa =KD=$_@;@&/C'f5W B@B@x@ADAE BQa =a"@@&$ aO%i  B@B$@x@ADAESaL aaa =$="@@ !$ |1'_C  B@B@x@ADAEfabqa =! m`="@d@&6'z.q5۴(, BZ  B@Bm@x@ADAEEaLra =j`=$_@a@ PAB  B@B@x@ADAE)aL a =%=+$_@Y@&/^lD  B@ B 5@ADA @ ׁ a =da"@Հ@ +#!+.^ )e#M`UAD|&=_ A/-+ B@DBP@x@ADAEaLAa =X`= @Ҁ@ ' @>A lM W B@B@x@ADAEaLa =ϖ=+"@.@ ! @A/C B@B}W@x@ADAEHaAa =5O@"@F@&APesU<M0Q«`@ VmA  B@B@x@ADAE aLa =-L`="@C@ &+=BA[ B@B@x@ADAEda =$_@r@  Ƕj Fll{qFٶɥ!Iԋ A kC޶$ B@@iB@x@ADAEq aL(a =*`="@b@ !>³  B@B@x@ADAEn+aL[pa =ux="+@o@mǶ/CH +M  B@x@ADAE/*,ao )@ra =07`="@G(@AĠୗ7"wGJ ^kzZ@ Q '  B@B|A@x@ADAE } zA =-B$_+@3%@ P $  B@B@x@ADA E~ Ax@! =@N=" @@Ġ/   B@B@x@ADAECbWA =N`= A@@Ġ3YX,Aml](et>5*a& vqF+$ B@B@x@ADAEoSOaL8%A =Y`= @ @ -&PAhA[W B@B@x@ADAESPZaL !!A =Z= @Q@ W&+ @/]C  B@B@x@ADAE [a"1A =f`="!@ @&Aj+Xf,ιTAfҼ:L5q\$  UA"Y$# B@B@x@ADA$EDā 2@A% =qa+ &@@dA'= ( B@B@x@ADA)E( AA)A* =ʀ="+@X€@ /yC, - B@BA@x@ADA.E|rbAB%!/ =W}`=%W0@z@ @ v`C A{Y+pôOt[G2 fĒ + 1/@2+2 B@B@x@ADA3E5~aLR`A4 =`= 5@w@ PA6 7 B@B@x@ADA8E1aL) AaaA9 =;=":@-3@ /L C];2A[< B@Bq@x@ADA=E퀈bqA> =4a"?@@ # n=t/ :kdս,; a& {A@ A B@B@x@ADABEaLr C =,`="D@@$+>:E瀂 F B@B@x@ADAGEҢaL AH =="I@@ /iWCJnK B@B@x@ADALEY^aAM =e`=$_`3N@m\@ Hi`1dυH'.?7ϯ; p ',AO[ P B@BH@x@ADAQEaLAR =b`=$_S@aY@ @>TX U B@B6h@x@ADAVEaL AW =|="X@@ H/CYDZ B@BH@x@ADA[E.ρ A\ =a ]@B̀@ PKǷɔLh|a& _A^̀$+_ B@BP@x@ADA`EaLAa =`=H d_b@2ʀ@dAcɠ d B@B@x@ADAeE}aL Af =E=H@ g@@ /Chi B@Bt@x@ADAjE@aEg,Wk =F`="+l@>@ "DTD;!Vvh{#el G}mD@ S. A B@B@x@ADAEځ  a =V% @@dAA[ B@B@x@ADAEց !!a =="@,؀@ /xY׀ B@B@x@ADAEb"1a =,!`="+@@ \\Ma,s+5Ny)J`.ygga& R.  B@BP@x@ADAEJ"aL2@a =+,`= @@ # @$>猀  B@B@x@ADAEG-aL AAAa =Q= @I@ W! @J/R.nH"f5 B@B@x@ADAEX.aBQa = 9`="@l@   QtGq}DEKO@͠>iZa& J@0a@ B@B@x@ADAEûR`a =!Da$_@]C`@dA  B@B@x@ADAEL aaM =l€=$_@׹@ /CC B@B@x@ADAE.tEbAbqa =zP`="@Br@ ) ⫹ʬ4a^oR Aq$ B@B@x@ADAE,QaLra =w[`= @2o@ @=CnA[  B@B@x@ADAE|)\aL a =M3="@*@&$/  B@B:@x@ADAE a =ga"@@&lAݶW!Ӽ|cD%]uep:q`Jҕx⠂@4 B@B+@x@ADAEmhaLa =r`="@ @ &+>g߀  B@B6h@x@ADAEQsaL) a =="A@@ A/~  B@B@x@ADAEUtaJ =\`="@S@& 7Sv,TrD"ft%IAX  B@B @x@ADAECaLga =Y`=$_@P@ @+>A<  B@BA@x@ADAE' aL a = = @W @& / C  B@B@x@ADAEƁ a =]͖a"@Ā@  aA#0].Rj@RȐ#i7׭=?tA-- B@Bx@x@ADAEaLF+ =Uʡ`=$_@@ ! @= B@B@x@ADAE{aL a =Ņ="A@,}@&$/XC| B@B$@x@ADAE7a$a =7>`= +@5@ m# @%oWe~FM"J$ǨvL3]OݻZ A$ B@Bm@x@ADAE A pD =+;a$_@2@ ,W @>A1A[m B@B@x@ADAE쀈 a =="@@ !/WCm퀂 B@B@x@ADAN  EXbAa =`="@l@%$ɴ‹t"F= +13k0o;jU~~~$Aإ$ B@DB@x` =A @E`aL a =`="@[@ &+>A  B@DB@x@ADA E]aL ". `_@!1a =!`=$_+ @A@ C ǶuI'܆D/"Ζ'H3ְ T`_C  A B@B6h@x@ADAEр2@a ="@1@ ! @J  B@B@x@ADAE{΀?AxAAa =@H؀="+@π@m}/Jw B@B<@x@ADAEbBQa =`="@@ Ġ挆Y}'Ȩōѧm2Wj6zja& V}  B@B@x@ADAEmBaLR`a =`=$_ @@ P!f " B@BW@x@ADA#EQ?  aaa$ =I="%@@@ !'+/}& ' B@Bs@x@ADA(E bqa) = a"*@ `@&fn[u\U+4,9R;?x4a& YIA+W$+, B@B@x@ADA-EBLAra. =aI /@@ PA0;A[1 B@B@x@ADA2E&aLa3 = ﹀=+$_+4@V@ /'uC5 6 B@B"Y@x@ADA7EkaAa8 =er#`="9@i@ #!+AרVQܞ$i" p9`iߔa& A:, ; B@Bk@x@ADA<E$$aLa= = Uo.`=">@f@ #$>? @ B@B@x@ADAAE /aLaB =*="C@+"@ !A/[CD!E B@B@x@ADAFE܀aG =*:a!I+H@ڀ@ ,jVA] twpDx` AI@1J B@B6h@x@ADAKE;`aL =E`=$_M@׀@ ! @>:Nր AO B@B@x@ADAPEБFaL) aQ =="R@@ /EaSlA[T B@B@x@ADAUEWMGaaV =!ATR`="W@kK@ #$҃|FgLf\i4̡25NYdXJ Y B@B@x@ADAZESaLa[ =P]`=$_\@[H@ @>A]G ^ B@BA@x@ADA_E^aL a` =i ="a@@ /dbB c B@B@x@ADAdE, ae =ia f@@@ *T8 ξ9QY 7IAg$h B@B@x@ADAiEvjaLAj = t`=$_k@4@ ! @>Al m B@B6h@x@ADAnE{suaL ao =K}= p@t@/w/q(dr B@B@x@ADAsE/va$at =5`="u@-@&<|#.BڄJGCIP1 a?a& SDv,-w B@B@x@ADAxEl瀈 ay =2a+$_z@*@ ' @K= {e)A[c @| B@BA@x@ADA}EP䀈 !!a~ = ="@@ !$Ġ/-C  B@B@x@ADAEןbA"1a = `=!I@띀@ P' @Pt1(d- F={EA+v kAW B@B@x@ADAEAXaL2@a =`=$_@ۚ@ W! @=:$ B@B@x@ADAE%UaL AAa =^="@UV@&/ޗ  B@B@x@ADAEaBQa =\`="@@& 2ʟȁƅܺ3ghN2{b:J^a& @^D, B@B@x@ADAEɁ R`a = Ta"@ @dA A B@B$@x@ADAEš @Y aaa =π="@*ǀ@&/rCƀ B@B@x@ADAEbbqa =2`=)@@ `@$}vep!9)cuƱk.Q + B@B@x@ADAE9aLra =)`=)@|@dA{A[ B@BA@x@ADAE6aL a =@= @8@ /l7 B@B@x@ADAEVlda =!A a"@j@ mr+Db6ei~y>(; W vF+$+ B@B@x@ADAEaLa =`= @Z@ # @+>쀂  B@B@x@ADAEaL a =u= @ը@&$/LZCA B@B@x@ADAE+caa =i`="A@?a@&AOG]gp 5z,՜eA` + B@B+@x@ADAEaLa =f $_A@0^@dA]  B@B@x@ADAEzaL a =G"= @@ &+ @+$/IC B@B@x@ADAEԁ a = a"+@Ҁ@ A9̇krl?% ^;c iJa& &Aр$ B@B@x@ADAEkaLa =`= @π@dAe΀  B@B@x@ADAEOaL) a =="@@&/( C  B@B@x@ADAEDaoa =K%`=%@B@ @+m TRRkOrLt<2$^mZA[A B@Bp@x@ADAE@ A = H0 @?@ @=:  B@B @x@ADAE$ a =1"@T~ m @A/m W B@B@x@ADAELAa = WXRcPgSI +\ВON}%%<t Ք A B@B@x@ADAEOlaLR`a = v`= @Z@ @JA + B@B@x@ADAELwaL aaa =pV="@M@&/ @ B@B@x@ADAE+xabqa = `="@?@&RZ9Y[]9|$jž CF+$ B@B@x@ADAEra = a"@.@ *>  ! B@B$@x@ADA"Eya# =Jǀ= A$@@&/foC% +& B@B@x@ADA'Eyba( = 5`=")@w@ #!$]{HV:ԗon cF|~ bA*v + B@B * 5@ADA, @Ek1aLa- = ?=|`=$_.@t@ ,W @K=/ds `:  iF0 B@BA@x. 9ADA1 @EO.aL; a2 =`="3@@ ! @ g陴8id:q}ݰV ` f#C4U DB@x@ADA6 @E@aLa7 = ?=}`="8@@ $ @>99 : B@B@x@ADA;E$aL Wa< =="=@T@ !B/C> W? B@B@x@ADA@EZaaA =[a`=!IB@X@AĠQ]IvztG;~Nahͻ0HMAC*$AD B@B@x@ADAEEaLWaF =S^`=$_G@U@ &+ @K=HA[$I B@B@x@ADAJEaLaK =="L@)@ /hCMN B@BW@x@ADAOEˀWaP = 8a"Q@ɀ@ S#$Ġ_S#Ot A&$9O1^"/ L  LAR$S B@B3R@x@ADATEaLAU = {(`="V@ƀ@ #=Wŀ X B@B@x@ADAYE΀aL AaZ =="+[@@ 6h! @A/`C\j +W] B@B@x@ADA^EU@&DnxZ3@W&AHDa& Ap +q B@B@x@ADArEeaL2@as =Ұ`=$_t@.@ ' @>Au v B@B@x@ADAwEybaL AAax =El=+)y@c@ ! @/ Cz{ B@B@x@ADA|EaBQa} =$'`="~@@ 'N| V.EM)T&ZA 8A$+ B@@iB @x@ADAEjրR`a =!2a"@@ !>Ac  B@B6h@x@ADAEN aaa =݀="@~Ԁ@ /fC A B@Bt   @'@x@ADAEԎ3bbqa =}>`="@茀@!+l; \fZ+ zbt` 5T@1-]  B@AbBA5@x@ADAE?G?aLra =I`=+$_@؉@ # @>A8A[ B@BA@x@ADAE#DJaL) a =M="@SE@ @A/lD  B@B @x@ADAE a =ZVa"@U`@ 'I]gbG6I)D6= 5a& DA* B@B@x@ADAEVa a =Ra`=$_@``@ ! @>A  B@B@x@ADAEL) a =ž=g'gb@(@&/C B@B@x@ADAEpbba =3wm`="@n@&1)h(5Jr>)A"?Hmda& Am  B@B@x@ADAE(naLa ='tx`="@k@ PAj  B@B@x@ADAE%yaL a =/="@&@ !A/ 2CiA B@B0<@x@ADAET a =a!I+@h߀@&ex_΁HKq'E)V-@ժgAހ  B@Bm@x@ADAEaLra =`=)@\܀@ PAۀ  B@B@x@ADAEaL a =!k= @ӗ@&/C? B@B@x@ADAE)Raa =X`="@=P@&ӾO-j }Z M7Xl6y٠Ya& -1O$+ B@B@x@ADAE aLA =U`=+ @-M@ ' @=LA[ B@B@x@ADAExaL a =@="@@ !$҃/1 B@B@x@ADAE o-&a = ɳa!I@@&=>l9EN0wۦ՜c) F+ `L XF B@B 4@x@ADAEi{aL a =ƾ`=$_@@ PAbA[ B@B@x@ADAEMxaL !!a =="@}y@&/}C "#a B@B@x@ADAE3a"1a =:`="@1@%J}~py@E3W[x|b2](/a& QCT B@BP@x@ADAE>aL2@a =|7`="@.@ '$=A8 A B@B@x@ADAE" @Yc AAa =="@R@A/#  B@B@x@ADAEbBQa =b`= A@@ ' @Hۤ\&!Պ%Y]~%#+ D.) + B@B@x@ADAE]aL$R`a =U`=$_A@@ -&! @=  B@B@x@ADAEYaL Aaaa =c="P@([@ &+ @/4Z B@B@x@ADAE~abqa =*`="@@ m##L'M>՞))\@Z; v  F+$ B@Bm@x@ADAÈra =&@$_ @@dA   B@B@x@ADA E a =Ԁ="@ˀ@&$/Ci  B@B@x@ADAESbla =`="A@g@ PGMX"Tq \k^9wW5e?#a& _AӃ+ B@Bg@x@ADAE>aLA &F  ba =`= @X@dA  B@B@x@ADAE;aLAxa =@nE= @<@&/kx> B@B@x@ADA!E) Aa" =(a"+#@=@&$9rQYnFBgYtE4 O>? D$$% B@B$@x@ADA&E)aLa' =3`= (@-@dA) * B@B@x@ADA+Ew4aL) a, =C="-@@&/C.$/ B@B@x@ADA0Eg5aa1 =n@`= +2@f@&;Y gG&7[`R>la& pA3~e 4 B@B@x@ADA5Eh AaLa6 =kK`= 7@c@dA8bbA[C9 B@B@x@ADA:ELLaL6ha; ='= <@@&/X\C=$> B@B@x@ADA?E؁ a@ =Wa"+A@ր@&1zl_7mgtFRW¢x)4`+0 v7BS C B@B 4@x@ADADE>XaLAE ={b`= F@Ӏ@dAG7 H B@B@x@ADAIE"caLqaJ =PPo`= K@G@ @$'# F`8|O-;`jcNG8Ve L( +M B@B@x@ADANEpaL aO =Mz`= P@D@dQ R B@B@x@ADASE @Y W!!aT ={a"U@+@P/V W B@ B@x@ADAXE} "1aY = .a!I +@@3+Z@@AĠ+2}o%A[p B@B@x@ADAqEWaqar =բa"s@<@&b:,kE(i9!lߝca& kCt u B@Bu 4@x@ADAvETaLraw =ԟ`="x@0@dy Wz B@B@x@ADA{EvQaL? a| =?[="}@R@  +Ƕ/C~'d B@B@x@ADAE aa =`="@ @AǶK_P2c2hIWu`m'A}  B@B@x@ADAEhŀa ='u@@ * @W>a  B@BW@x@ADAEL a =̀=+ m@|À@&Ga/gC  B@BGa@x@ADAE}ba =`="@{@&3N(zr?y)c4oW7D> I+Oa& $AR$+ B@B@x@ADAE=6aLWa =`=$_@x@ ' @>6A[g@ᇶ B@B@x@ADAE!3aLa =<="@U4@ !$A/C + B@B@x@ADAE a =\a"@@ 'AZcSc&qyor#821+`6#A,$ B@B@x@ADAEaL 4a =T]"@@ !>A B@BA@x@ADAEaLa =˭="+@*@ g/P$ B@BJ@x@ADAE}_aa =.f`="@]@ `#!+qLE&| x(<Oqa& (D$ B@Bm@x@ADAEaLAJ A =%c`=$_@Z@ # @>AY A B@B@x@ADAEaL) A! =="@@&/CgA B@B@x@ADAERЁ 1! =*a"@f΀@ 2/e;GG"^p=7/"ɯa& 9À  B@BA@x@ADAE+aL A =5`=$_@Vˀ@ ! @>Aʀ  B@BA@x@ADAE6aL !!A =m=+'u@ц@ W&+ @/4C= B@Bw:@x@ADAE'A7a"1A =GB`="@;?@ +#AaGjm0NOaa& >$ B@B@x@ADAE2@A =DMa"@+<@ #>A;  B@B@x@ADAEv AA' =>N"@~ W! @ /   B@B@x@ADAEL$B!! =Y'@@&A`3aVL(߭@ks{a& QxF+|$ B@BA@x@ADAEgjZaLR3c! =d`=+$_@@ &+ @"`C >A` ` B@B@x@ADAEKgeaL a' ! =q=$_@{h@ /TA A B@B@x@ADAE"faAb1! =~)q`="@ @ +#!g quWw7)[n":oa& iAR B@Bs@x@ADAE<ہ rA =z&|$_@@ # @>A6 A B@B@x@ADAE ء @YN A = +="@Pـ@ W! @/iA  B@B@x@ADAE}bA = S`="@@';6!◻uW9t"<5 miA'  B@B@x@ADAELaLA =O`=+$_@@ &+ @>AK B@x@ADAEHaLAAR=" ~@%J@'.iAI B@B@x@ADAE|aA =I1 `=% @@ P# @ .`lWA 뮢"hqCK:F}Dn"D€2 ` S  B@BP@x@ADA E缀A =(a$_@`@ ' @ .`A>A S  B@B@x@ADAE˹L AA = À="@@& /s4Ag B@B@x@ADAEQubA =|`="@es@%>9BJ0qF^A So  B@B@x@ADA E*aL A! =t4= "@+@&9~/G#<$ B@B9~@x@ADA%E& A& =a"A'@:@ #! .`cAϹqH?F*:2+h#ÆO@+ ( S。$) B@B@x@ADA*EaL@4+ =`=$_,@+@ PA-ࠂA[. B@B@x@ADA/EuaL  A0 ==="1@@ !/@C]2W3 B@B@x@ADA4EVaa5 =]`="6@U@ +GDe Kg[cɕN#'^F.ot0<TMA7|T$8 B@B+@x@ADA9EfaL a: =Z`=";@R@ !$ .`E>A< S`Q = B@B(@x@ADA>EJ aL) !!a? = ="@@z @ W/aAA "#f5B B@B@x@ADACEǁ "1aD =~a%E@ŀ@ +# @ .`cAཥYe 1}ihHN.VNDSpQC[2 i@GF SQ G B@B+@x@ADAHEK S5 L B@B$@x@ADAME} aLAAaN =膀="O@O~@ !A/EAP Q B@B$@x@ADARE8 aBQaS =V?`="T@6@  KĠԈmܕ&s͚g}۰WO$Na& AU&6 V B@Bxg@x@ADAWER`aX =N< $_Y@3@ ! @ .`D>AZ S [ B@B@x@ADA\E  Aaaa] =="^@%@&J/0A_` B@B@x@ADAaE{!bbqOmb =(,`=%c@@ +# @ .`cĠ?BTa*jUȗ1dWGHn2 Mh@Gd S-e B@BA@x@ADAfEa-aL-rag =$7`=$_h@@ -&' @ .`A>Ai S UGߣ j B@B@x@ADAkE^8aL-&al = D`="m@e@ }!0<m̜nص].F5fY{= VSAn o B@BA@x@ADApEҀaq =Oa"r@T@dsA[oc@mt B@B@x@ADAuEπ av =kـ="w@Ѐ@/Cx;y B@B@x@ADAzE&PbWa{ =ߑ[`=$_+|@:@ 䑢l#O*Bt2)(vT0Wzha& )A}$~ B@B@x@ADAEC\aLa =Ύf`='u@*@d  B@B@x@ADAEt@gaL) a =IJ= @A@ Ġ/C B@B@x@ADAE a =sa"+@r`@ -c>AJ4jXqVwT#kA{ + B@B|@x@ =A @EfLa =}aI @@dA_ A B@DBA@x@ADAEJ~aL a == @z@ / 6C  B@B@x@ADAElaa =s`="+@j@&T9&M)"~B>m҈q1P B@B@x@AD>E;%aLR =xp`= @g@dA4 A B@B@x@ADAE"aL a =+="@O#@&/!2  B@B@x@ADAE݁ a =Va +@ۀ@ mAnlѿ:e0^qI]1Ys6^7a& A%$ B@B6h@x@ADAEaL a =R`= @؀@dA A[ B@B`@x@ADAEaL!!a =="@$@  cV .`$X/@G S NG B@B@x@ADAE{NaA"1a =+U`="@L@:9st*1.7-ĕb,7g0Gƨa& t K$ B@B@x@ADAEaL2@a =#R`="@I@'O&+$ .`C>: SH@g B@B@x@ADAEaL AAAa = = @@&/pt e + B@B@x@ADAEP BQa =a"@d@! .`cĠ#AaTd089M7\Z9g2  Sм@/ B@BR.@x@ADAEwaLR`a =`=$_$@T@dA  B@B@x@ADAEtaL aaa =c~=H@Qb@u@$/-:  B@B@x@ADAE%0abqa =6`="@9.@&Murmu4E$na& 1D-  B@Bm@x@ADAE耈ra =3 @*+@+ @ .`f+=UD S*@g B@B@x@ADAEt a =E=+$_@@&/!A  B@B@x@ADAEba =`="@@ ,W! .`cA,-B ?76r2 @G Sz$+ B@B@x@ADAEeY@Ta = `=$_@@ @ .`A> S^  B@B@x@ADAEIV aL a =`="@yW@ /YA  B@B 4@x@ADAE aa =`="@@ ! .`C-mCA '(1 @G SO$ B@Bg@x@ADAE:ʁ a =x"a$_@ @ &+ @ .`a=A S3@gA B@B@x@ADAEǁ a =Ѐ="@NȀ@ /MA R  B@x@ADAE#ba =].`="@@& y Up37]bԸ?G.=A% zA% B@B@x@ADAE;/aLa =M9`=$_@}@ # @"`D>A  A B@B@x@ADA E7:aL) Aa =A=" @#9@& /A8 B@B@x@ADAEza =*Ea"@@%AC q5bS5`[# a& t m B@B@x@ADAEFaLޗ ="P`="@~@ PA퀂  B@B@x@ADAEȨQaL a == A@@&/Tz:d B@B@x@ADAEOdRaa =k]`="!@cb@ #!gn'7;4z- %ub?[F+"a # B@B@x@ADA$E^aL a% =!gh`=+)A&@W_@ PA'^ ( B@B@x@ADA)EiaL !!a* =n#="+@@&$/?/C,:A- B@B$@x@ADA.E$Ձ "1a/ =ta"0@8Ӏ@&h%i!^~öVk[#XmnLa& 4A1Ҁ$+2 B@B@x@ADA3EuaLA2@a4 =`=+ 5@(Ѐ@ &+ @+=6ϠA[7 B@B@x@ADA8EsaL AAa9 =@= :@@ &+ @g/†C;< B@B@x@ADA=EEaABQa> = L`="?@D@&033PΘi8yk?+?JA@yC AA B@B@x@ADABEdR`aC =I$_D@@@ # @=E] F B@B@x@ADAGEH  aaaH =a"I@x~&/CJ K B@B@x@ADALE϶LbqaM ='N@㴀@% c>Јѱc$aC0@(?ib |AOO+P B@B@x@AD>QE9oaLraR ={`=+$_S@ֱ@ @>AT3 U B@B6h@x@ADAVElaLbH) aW =u=)X@Mm@ &+ @A/+PCY Z B@B@x@ADA[E'aa\ =M.`="]@%@EK=3ae J.ɳEa& A^$ A_ B@BJ@x@ADA`E aa =L+a$_b@"@ # @=Ac d B@BA@x@ADAeEܡ @Yaf =="g@#ހ@ 4! @%o/ Chݠ i B@B@x@ADAjEybak =!`="l@@ 0(|D!|* =CA9pam$n B@B@x@ADAoEPaLap =`=$_q@}@ @>Arݒ s B@B@x@ADAtEMaL Aau =W="v@N@&/gwdA[x B@B@x@ADAyEN a$az =`="A{@b@&f UE OxzbZ[ >q9SF+|- aA} B@B@x@ADA~Ea = $_@S@ ' @>A a ၊ B@B@x@ADAENa =؀ "@tA[ B@B@x@ADAEr/ aL a =>9="@0@ :!B/C + B@B@x@ADAE Wa =a!I@ @ Njm٘yxGS8JQca& fGy耂+ B@B@x@ADAEcaL a =#`=$_@@ ! @>]  B@B@x@ADAEG$aL !!a = ="@w@ /0JC A B@B @x@ADAE[%a"1a = {b0`="@Y@ #$AofU̗ɃVSA&A3[mR# 5AN  B@B@x@ADAE91aL2@a =v_;`=$_@V@ # @>A2  B@BA@x@ADAE6߭t,D#- B@B@x@ADAEHaLR`a =KR`=$_@3@ǀ@ @>AA[ B@B@x@ADAESaL aaa =AV= @"@ /C B@B@x@ADAEx=Tabqa =)D_`="@;@&bx 0W#Kt>ʗ/); yA:$ B@B6h@x@ADAEra =%Aja+$_@8@dA7  B@B`@x@ADAE/a = ="@@ /Cg$ B@B@x@ADAENkba =v`= +@b@ 1@U"7i?=caW.a& DKΫ$ B@B@x@ADAEfwaLa =`= @R@dAA[ B@B@x@ADAEcaL) Aa =mm= @d@ /^DK8A B@BP@x@ADAE#aa =%`="+@7@!WĠlԘUw1MeQ}ەdșa& "F+  B@Bg@x@ADAE׀a ="a @'@dA  B@B@x@ADAEqԀ a =6ހ="@Հ@/JC ՠA[ B@Bq$@x@ADAEba =`=!IW@ @&$Nm>+Q ݃"s@pUOa& x  B@B@x@ADAEcHaLa =`= @@+ @+= \A[ B@B@x@ADAEGEaL a =  O="@wF@ A/  A B@Bo@x@ADAEaa =`= @`@ $Z珺'Asm|Q-t6Q2a& ʛF+M$+ B@B@x@ADAS  E8LAA! =vaI$_+@`@ @>1A B@DB@x` =A @ELa =쿀="@L@ /A A B@B@x@ADAE&aL!!a =0= @!(@ /NR.' B@B@x@ADAEx "1a =$a"@@ #!|C J@l944 ?5 2^Ia& }F+߀$ B@B@x@ADAEaL2@a = `=G@gd_A @|݀@>A!܀ " B@B@x@ADA#EƗaL) AAa$ =="%@@ /JQC&b' B@B@x@ADA(EMSaBQa) =Y@"*@aQ@ K$*hoS_!lj -,&"EdBw@ q +P A, B@B@x@ADA-E aLR`a. =V`=$_/@PN@ ! @+=0M 1 B@BA@x@ADA2EaL aaa3 =h=+'u4@ @&/D58 6 B@Bw@x@ADA7E"ā bqa8 =a 9@6€@ m# @ ̓|~Nؼ_kRLa& 7:$; B@B@x@ADA<E|aLra= =%`=$_>@*@ ' @>A? @ B@B@x@ADAAEqy&aL i AxaB =@E="C@z@0<J/>D AE B@B-&@x@ADAFE4'a$aG =;2`="H@ 3@ m񡙪—U"ԬTBg*:]KF+Iw2@4+J B@Bm@x@ADAKEb퀈aL =8=a$_M@/@ @>AN[A[O B@BA@x@ADAPEF  aQ =  = R@v@ &+ @/ gCS T B@B@x@ADAUEͥ>bAaV =}I`="W@ᣀ@ ZH&"C[NjXb"M2AXMY B@B@x@ADAZE7^JaLa[ =uT`=$_\@Ѡ@ @Q`AB?A]0 ` ^ B@B@x@ADA_EaL a` =d="a@K\@&J/Ab c B@BJ@x@ADAdEVaae =Ra`="f@@&Z(_O_2q'v3Op42d` Ag"h B@B@x@ADAiE ρ 6haj =l`"k@@ &+>Al Am B@B6h@x@ADAnEˡ @Y ao =Հ=)+p@ ̀@&/Cq̀r B@B@x@ADAsEwmbat =  x`="u@@!O {ta"ڧ$rɗn> 3Av w B@B@x@ADAxE?yaLޗy = `=+$_z@{@ ,W @>{ہ | B@BA@x@ADA}EA  B@B@x@ADAEaL !!a =t="@ˮ@ -&/vC7 B@B-&@x@ADAE!ia$"1a =o`="@5g@ #)6q )6^I˄G.[a& Af$ B@B@x@ADAE!aL2@a =l`=$_@&d@ # @>AcA[ B@B@x@ADAEpaL AAa =<(=+"@@ ! @A/kC  B@B@x@ADAEف BQa =a"@ ؀@%A~o~M >#֙_|"I?a;p Aw׀$ B@Ba@x@ADAEaaLR`a =`="@Ԁ@ &+>A[  B@B @x@ADAEEaLqaqa =Q`=$_@H@ # 'y&%`\?>jt)a& "CL  B@B{W@x@ADAE6aLra = tN`="@E@ ! @+=0  B@B@x@ADAEaL? a = ="+@J@Ƕ/  B@B@x@ADAE a =Ua"@@ #$ĠWM#` CK9܎$͵Zja& cCD!  B@B@x@ADAE taLa =I`=$_@@ P  B@BW@x@ADAEpaL a =z="@ r@ ! @/oCq B@B@x@AD>Ev,aa = '3=!I@*@ĠO* ]$oޔ’Cí5 "C)- B@B@x@ADAE䀈Aa =0 @{'@ &+ @>A&A[ B@B@x@ADAEဈ a =="@@&/4Ca B@BA@x@ADAEKba =`="@_@&癖8D|& Fʂ~o(^&9a& ʯA˚ A B@B6h@x@ADAEUaLa ='`=+$_+@S@ ' @>A[ B@B m @'@x@ADAER(aL6ha =f\="@S@/AZ  B@B@x@ADAED4WaL@Y !!aT>="@x5@ !/UC$ B@B`x@9 A @E "1a =tba!I@@ ' @Au D !A62WܽiM]3)` lAK  B@DB @x@ADA E6caL2@a =sm`=$_ @@ 4 @=A /A[Ƕ B@B@x@ADAEnaL AAa =ޮ="@J@ /-@C  B@B6h@x@ADAE`oaBQa = Qgz`=$_@^@ b(ȞBcur| z]77;?Dt A $+ B@B@x@ADAE {aLR`a = Id`=$_@[@ +# @>A  B@B@x@ADAEaLaaa =="@@ !#V҃/[;C ! B@B:@x@ADA"EvрAbqa# ="ؑa!I$@π@ G ~wBHv^Wm0Mf*9%a& A%΀$& B@Bޗ@x@ADA'EaLra( = "՜`=G 1)@~̀@ !>A*ˀ$+ B@B@x@ADA,EĆaL Aa- == .@@&$/tC/`0 B@B$@x@ADA1EKBaa2 =  I`="+3@_@@ #!+(K%re%; A4?$5 B@B @x@ADA6EAa7 =Ea$_+8@O=@dA9< : B@B@x@ADA;E) a< =ua"=@~ A/XC>5? B@B@x@ADA@E LaA =͹a$_B@4@ +E*Y+` k r|0Fnj \YAC` mD B@B+@x@ADAEEk`aF = ȶ`= G@$@dAH I B@BA@x@ADAJEohaLgaK =;r="L@i@ W/uCM N B@BW@x@ADAOE#aaP =*`= Q@ "@ m(pvAOY{u>%#I٬Sda& ARy!$S B@B@x@ADATE`܀(aU = ' V@@dAW] X B@B@xV 9ADAY @EDف aZ =="[@xڀ@ }/BC\ ] B@DB@x@ADA^Eʔb#a_ =~`="`@⒀@&aى%l1B~4$$^ da*gt n?AaN$b B@B@x@ADAcE5MaLAd =s`= e@Ϗ@dAf. g B@B@x@ADAhEJaL A!i =S= j@IK@ /Ck pl B@B@x@ADAmEaAn = X  #o@@<S(9E[=šֆ^?La/c Ap q B@B<@x@ADArE  As =H a t@@dAu v B@B@x@ADAwE  !!Ax =Ā="y@@%/Cz +{ B@B@x@ADA|Euvb"1A} =)}`="~@t@ vU-4syBx*9ۓ7٪ga& &As B@B@x@ADAE.aL2@A =!z)`="@|q@ !$=p  B@B6h@x@ADAE+*aL AA)A =5= A@,@%/ C_A B@B@x@ADAEJ B"! =5a"@^@ #! v`C A󮏧g-)Ս?<Z9@0P + H@G䀂 + B@B@x@ADAE6aL$R`A =@`=$_@N@ ,W @>ဂ  B@B@x@ADAEAaL aaA =m="@ɝ@ W! @/kdC5 B@B@x@ADAEX3"  bqA =^M`="@3V@ mQXU.7Oƛݻ@d`*/MJ&f AU$+ B@Bm@x@ADAENaLrA =[X`=$_@#S@ ! @>AR  B@B@x@ADAEn YaL A =;=bq@d_@@&$/LC  B@B@x@ADAEȁ A =da"@ǀ@&:4f*DB^/S/RxRa& tƀ$ B@B@x@ADAE_eaLA =o`=$_@À@ ' @>AXA[ B@B@x@ADAEC~paL A ==+)@s@ ! @A/ d B@B@x@ADAE9qaA =v@|`="@7@ 'ݪahTj|QjUY,wn2\a& F+J B@B@x@ADAE4 A =r=a"@4@ !>A.  B@B$@x@ADAE qA =T"@@ : uk58$ -7t"e)ȵ9a& EB  B@B@x@ADAE caLA =G`="@@ !>  B@B@x@ADAE_aL? A =i="+@a@ :$#V>/vF+`!]iK B@B} @x@ADAEtaA =$"`="@@AĠpOot9u3Nva& a  B@BA@x@ADAEӀ A =$_+@x@ # @=:  B@BW@x@ADAE  A =ڀ=+$_@р@ ! @W/g_  B@B@x@ADAEIba =`="@]@&v ڞSWdxe_R6և8 A  B@B@x@ADA!E !@Ta" =JS+`=)#@J@ ! @+=A$ % B@BDK@x@ADA&E,aL Aa' == (@@$/]C)* B@B@x@ADA+Esa, =(7@"-@@ P#!A;?]vGZG a

@ KA=.$> B@B+@x@ADA?E逈a@ =4Z  A@M,@ ! @+=AB+@C B@B@x@ADADE怈 aE =`="F@@ W/AgCG3H B@Bu + M@x@ADAIE[baJ = ֨f`="K@2@A8h-yY^؜`dpM }AL$M B@B@x@ADANEZgaL"O =ƥq`="P@"@ #>Q R B@B@x@ADASElWraL) aT =@a="U@X@ !'+A/CVW B@B@x@ADAXEs aY = 0~`=!IAZ@@ ' @JW$d17sqp3b,9v A[s \ B@B@x@ADA]E^ˀ a^ =@$__@ @ -&! @=A`W Aa B@BA@x@ADAbEBȡ @Y !!Xc =҉a"d@rɀ@ #/Ce f B@B@x@ADAgEȃaL"1Ah =u`=$_i@܁@ # @A4spYS*h?fr%OR .,"0a& ajHk B@B@x@ADAlE3<@T2@am =p`=$_n@~@dAo, Ap B@B@x@ADAqE9aL AAar =B="s@G:@ / gt u B@B@x@ADAvE $BQaw =F@"+x@@ M&v$pt,+ϐ6HU>ͼ@ F+y -z B@B@x@ADA{EaL$R`a| =`= }@@dA~  B@B 0`@x@ADAE쩸aLaaa == @@ /DK B@B@x@ADAEse@TAbqa ='l`="+@c@&-}4b/ {ϱp?wJD,@ Db$ B@B@x@ADAEaLra =i`= @w`@ @>_  B@B@x@ADAEaL Aa =$="@@&/e(C] +Edau XF#^ B@B} +$@x@ADAEHց a =@"@\Ԁ@&$]yUta,.MhWR~T@ VӀ+ B@B@x@ADAEaLa =!`="@Pр@dAР  B@B #A@x@ADAEaL a =g= A@ƌ@&/;D2 B@B@x@ADAEG a =M`="@1E@&/SM"탊,>ոGc9AD + B@B 4@x@ADAEa =J)@!B@dAA  B@B@x@ADAEl a =@ @~ / C  B@B@x@ADAELa = #@@ +tfXP_*gCY9A49U #Ar$+ B@B+@x@ADAE]p aLa =`=+ @@ ! @>V  B@B@x@ADAEAmaL a =w="A@qn@ :/zC A B@B@x@ADAE(aa =t/"`= +@&@&a>հQbu 4XQۊho4a& AG$ B@B@x@ADAE2 /A =,-a$_A@#@ # @>+A[ B@B@x@ADAEށ a =="@F߀@ }!#V/C  B@B}@x@ADAE.ba =U9`="@@ 'g~~_+5 BFrLU x1a& A B@B@x@ADAER:aL a =ED`="@@ !>A  B@B$@x@ADAENEaL)7!!a = X="+@P@ S/CO B@B@x@ADAEr F "1a =&Q`="@@ #!+*.gU^M QɫuȞ~)ne ,C m B@B@x@ADAE€g2@a =\a$_@v@ # @>A  B@B@x@ADAE; AQa = ha"@[y@ W!>yfPEepg}^J KICx  B@B@x@ADAE3iaLR`a =~s`="@Kv@ $ @>u  B@B@x@ADAE0taL aaa =^:="@1t`~ ! @W/eC2 B@Bs @x@ADAEta bqa =`="@0@& ǝÞ 26dAL mv*Uka& n`A适$A B@B@x@ADAEaLra =`=$_@ @ &+ @>V栂 O B@B@x@ADAEkaL a = 3=+$_@@ /LC B@B@x@ADAE\aWa =c`=" @[@`Bg94G+ [ p DjAyx/a& } qZ A B@B.@x@ADA E\aLa =``="@W@ #>AUA[ B@B@x@ADAE@aL a =  = @p@ ! @+KA/'}  B@B@x@ADAÉ a =tԮa"@ˀ@ 'u:tZ]hH_Q2$^F+G@4 B@B+@x@ADAE1aLa = oѹ`=$_@Ȁ@ ! @>A+  B@B@x@ADA EaL) a! =ތ=""@E@&A/C# $ B@ BP@x@ADA%E>aa& =LE`="'@<@ +#玢d8 %M.YĔނ[_:! }1( ) B@B+@x@ADA*E a+ =DBa",@9@ '>A- . B@B@x@ADA/E @Y a0 = {="+1@@ C! @+A/123 B@B@x@ADA4Eqbua5 =*`="6@@ 57N==AU%HI쳼a& ϿF+7 8 B@B:@x@ADA9EgaL A:V `=+$_;@y@ ! @>A<թ = B@B}@x@ADA>EdaL a? = n=$_@@e@&/9CA\B B@B@x@ADACEF a$aD =&`="E@Z@&ApMBes҂ ^r'g|AF$+G B@B@x@ADAHE؀ aI =#G@Qd_J@J@ '>AK L B@B@x@ADAMEՀ !!aN =a߀="O@ր@ !$+A/CP1րNQ B@BP 5@ADAR @Eb"1aS =ȗ `=!IT@0@&s%]yhr֩t|/Hp|g` AU$V B@DBP@x@ADAWEI aL2@aX =Ĕ`=$_Y@ @ `@>AZA[d[ B@B@x@ADA\EjFaL AAa] = 7P="^@G@&/C_` B@B@x@ADAaEaBQab =$`="c@@ #M=NqIWkyd@J,wAdq#`$e B@B%o@x@ADAfE[ R`ag =/`"h@.`@ '$>AiU j B@B@x@ADAkE?L) aaal =="m@o@&$/Cn o B@B$@x@ADApEr0bbqaq =wy;`= r@p@ +Gbk4MIp۞MЛ|x?9AsFA[t B@B@x@ADAuE1+Ax*A[y B@BA@x@ADAzE(GaL@Y a{ =1= |@I)@&/nC} ~ B@B@x@ADAE a =SRa"@@A䟬tZ3k/[ AIIz?S~7`7|A$ B@B@x@ADAESaLa =C]`=$_@ހ@dA݀  B@B@x@ADAE^aL a == @@ * @+/C !k B@B@x@ADAEpT_a$a =[j`="A@R@ AqO#HӃ}"[UyPuA%/Q@4a@ B@B@x@ADAE kaLAa =Xu`= @uO@dAN  B@B@x@ADAE vaL a =="@ @ /LD[ B@Bc'@x@ADAEFŁ Aa =ˁa"@ZÀ@ Br(\CnAm3䗐@ X&a& ߨA€$ B@Bg@x@ADAE}aLa =Ȍ`="@I@$=DK  B@B@x@ADAEzaL a =d="@{@ }/)C0 +A B@B@x@ADAE6aa =<`=*W@/4@&mh}dS=wq|−(dƄ5ta& OA3+ B@BJ@x@ADAE8[A =9$_@1@dA0A[ B@B@x@ADAEi뀈 a =.="@@+#V+/cCA[ B@BW@x@ADAEba =`="+@@ UEnY#>yS淵ZG^荢fB3DK<US͞a& AE$+ B@B@x@ADAE0Ё 2@a =ma$_@@dA)  B@B@x@ADAE͡ @Y AAa =ր="@D΀@ /OC  B@B{%o@x@ADAEbBQa =G`= +@@&AR:_P a/wWCE%~C UQ8& ykA$ B@BA@x@ADAEAaL$R`a =`= @@dA  B@B@x@ADAE=aLaaa =G="@?@ /GC>W B@Bt@x@ADAEpbqa =,a"@`@ PYu?#!t ? ~Հ  B@B@x@ADA Ei1aL a =1= @@ ! @Ƕ/zE B@B@x@ADAEK2aa =R=`=!I@J@gĠqy:~RKFހCa& GoI$A B@BV@x@ADAEZ>aLa =OH`=$_@F@ &+ @K=:WA[d B@B@x@ADAE>IaL a = ="@n@ / C #a B@B@x@ADAEż Wa =tTa"!@غ@ #$Ġ>3EPwϪ]j{4V=k 5#nA"D # B@B@x@ADA$E/uUaLW"% =m_`="&@ȷ@ #>A'( c @( B@B@x@ADA)Er`aLa* ={="++@Cs@ ! @A/BC, - B@B@x@ADA.E-aa-&a/ = ^4l`="0@+@ AﲴV5[|Lr &-12 B@B@x@ADA3E  a4 =B1wa$_5@(@ ! @Q`B?A6' 7 B@B@x@ADA8E ) A!!a9 ==":@@ /"Y;。< B@B@x@ADA=EoxbA"1a> =`="?@@ +#+A?\zr1$߭O2+QYLa& C]@ +A B@B+@x@ADABEVaL2@aC =`="D@s@ #>AEӘ F B@B@x@ADAGESaL AAaH =]="+I@T@҃/^CJYAK B@B@x@ADALEDaBQaM =`="N@X @ +DsM|mO`qhsk]ypa& {O P B@BJ@x@ADAQEǀR`aR =a+$_S@L @ @JAT U B@B}@x@ADAVE aaaW =W΀= X@ŀ@g @/Y/A[mZ B@B@x@ADA[Eb$bqa\ =ֆ`="]@-~@ +mGD@~c+AkNa& rF+^}$+_ B@B@x@ADA`E8aLraa =ƒ`=+$_b@{@ PAc}z d B@B@x@ADAeEh5aL af =A?=$_g@6@ ! @ /^ChAi B@B}@x@ADAjE Aak =a"l@@ ҎbAB:4N> a& dmo$n B@Bx@x@ADAoEYaLap =`= q@@ ! @>rRA[s B@B@x@ADAtE=aL au = ="v@m@ />dw #aAx B@B6h@x@ADAyEaaaz =ph`="{@_@ #+"^0úF,dQpHrɈa& <|D} B@BJ@x@ADA~E.aLa =e`="@\@ #>( A B@Bu@x@ADAEaL) a = ="@B@ `!/HIA  B@Bv@x@ADAEҁ a =Fa!I@Ѐ@ $I Аrl"X$w=h*a& }A + B@B@x@ADAEaLa =A@$_@̀@ ! @JÀ  B@BA@x@ADAEaL@Yh5 a == @@ /C  B@B@x@ADAEnCaa =J`="@A@ #!'# sf3eo8^,dkK>Y%A@$ B@B@x@ADAEA =G$_@r>@ # @>A=  B@B@x@ADAE Aa ="@~ ! @/CY  B@B@x@ADAECL$a ='a"A@W@&^y8E2[' g txC5a&  %Añm B@B@x@ADAEl(aL a = 2`=$_@H@ &+ @> ȸ B@B@x@ADAEi3aLA!!a =fs="@j@ `/ C2$ B@B1@x@ADAE%4aA"1a =+?`="@-#@ +#+AvC.fڑ#2A}  B@B@x@ADAEgڀ) uAAa =3="@ۀ@ !A/r nA B@B@x@ADAEKbBQa =V`=!I@@*ĤHR\FRY,^tKUSh4E/F+n  B@B+@x@ADAEXNWaLR`a =a`=$_@@ }&+ @JARA[c @ B@B@x@ADAES ` B@B@x@ADA EaL@Y) Aa =b=  @@&/ 1 +A B@B@x@ADAEʁ a =a"+@0Ȁ@ #!I)9M,K WFحN֙tZрyda& dC]ǀ  B@B@x@ADAEaLA zA =`=$_@ŀ@dA|Ā  B@B@x@ADAEfaL A! = /="@@ A/+C B@Bu@x@ADAE:a$A =A`=" @9@ +|+ֺeڔA¤w|(uu A!m8 " B@B+@x@ADA#EX+A$ => %@5@dA&Q ' B@B@x@ADA(E< @Y !!A) ==+'u+*@l@ }  =AW #xC+ , B@B@x@ADA-E«b"1A. =s`="/@֩@1A0B$+1 B@B@x@ADA2E-daLA2@A3 =k@ 4@Ʀ@dA5&@Ȥ6 B@B@x@ADA7EaaL NAj`=8=`=G%  A B99=$`=`3{ `9@8:80V# 8H`8;88 7`8<8j q=8m68q>88>?8t,`8ֵq ' d2@ =@U`=\ F`rKA@@@S`6@@`@AÀ@|@{BJcJ J `J@  -FC B@Bo B  @' @x@A ARDEIa$ J 8DE =@@[i=: `!RRUF@@ `@# P@ K@(@5!+JF GT `T\i\H B@BA~`@ \@x@AA\IEXa\ 5E\@a\JX@\`=3K@@ d!\""@BLBBBJM B@JBo@١ `  J@x@AAJNEπEUO=Tހ=``yP8#a` 8Q88 R =! 3,O`v!S@M@@{%BTJJ  a U B@B@x@R =BV @EacMW =@ր= =#MX@#@ #M$`! !@BYBJZ B@B@a@ @x@AAJ[EmÀ> < :H \ =@paJ$W! ]@@@S~@- @ HAR^JJ J `J-GH_ B@BpE$ H@x@A AR`E5Ya =@Y=!R"\R'Hb@u@ 2$ @HcTՠ+ T\i\d B@BC\%@x@AA\eExbqHAJcf =@z=#g@y@~!\""@BhBXB0+BJi B@Bp J@x@f =AJj @EB4aJaJk =@p==J! l@@@S "ARmJqJ n B@Bt d@x@A ,oEPaR aRp =@P=lq@@ "#: @, K )".  C !+L "c  rAsrk s B@B  @x@A-AstEfasZasu = `%$`=$v@#@!s#@BwB"Bx B@B@x@AAJyE݀aJz =JJ$J! {@@J "dAR|JJ$ } B@RB@x@A AR~ETa aR =RQW= '@-V@@Sb"$)`]K  xANFUF B@NB `@x@AANExaNaN =@̀="@.@ V!N B̀ B B@B@x@AAJEJaJ =JE`=J#@PD@ "BJCJ  B@RB@x@A ARE aR =R2R#a@~H' ;KSF!M305-Joyst3ck0`.` B@hBL @x@A"AhE*)1 =hcwh#h'@v@ pB<B B@JBN@x@AAJE&1a Jb =J="@@ *, _4AJ]JA# B@RB@x@A ARE4aR aR =Rc= RA@ ~R B B@BDJK @x@AAcasA! Q =5c  1&]@}3@,WJ2J NV B@NBV@x@A ARE퀈V  aR = = V!R]2W@@6WT\T\ : B@BB%E@x@AA\EGdbq N  a\ =@ =\&Q@w@~QB BQ B@BQ@x@AAJEdeaJQ  aJ =JM#f`=,n"J#@!@!, AJ$J  B@RB@x@A AREۀ aR =Rހ=RL@݀@@S" @` WuKw܀K¦ B@SB!@x@A ASEagbh SaS =@&=S#@@~:!S @ IBB B@BJ@x@AAJERhaJx =J3i`=J#@@!C"8)AJ J  B@RBI@x@A AREɀ b =RЀ=R#@3VI@2ˀ@ " @3Ikʀk0 B@sBJS@x@A-AsE|jb sas =A?A=s#s$@@~!s @ BB B@B@x@AAJEAkaJaJ =J^=J$@@ @{'%AJ5J Z B@RB@x@A ARElaRVaR =@ր=R#@Q@ @ B B B@B 5@x@AAJEsmaJaJ =J1n`=J#J"@M0@! BJ/J  B@RB@x@A AREꀈ ;aR =R= Rb\Rp@@@~; ZB B@B B@x@AA+obw 1: 8a = )$ q`= @@ "BVJJ  B@NB@x@A ARE RA@aR =@=R$V@ـ@' @Q }@'K u&5F 0 1 2 5u%F;e 9Beu %E ) ^a@G[ 1! B@B 4@x@ADAEErbj a ==f@@ 6(!{& !]A] a!! Ame *m B@mB :m@x@A'AmEOsaml =mc t`=m"@ @ u" @ B;B B@JB@x@AAJEƀQ> !.=-`@<Li@k-|axTk,,? L8=5D k u7`=u - A#@@ @ @ @`@(_' @# `@ @   )   !@   / @8Bitd  @B5!! P@8@@ A ` " `g XG@  a>ABAE"}% @HZ`i `n@8!`$`M a@I@;@c1`M }Р"I@s`@ @E$ 1 @5i&A]fwupd-1.2.14/plugins/ebitdo/ebitdo.quirk000066400000000000000000000034241402665037500202110ustar00rootroot00000000000000# bootloader [DeviceInstanceId=USB\VID_0483&PID_5750] Plugin = ebitdo Flags = is-bootloader [DeviceInstanceId=USB\VID_2DC8&PID_5750] Plugin = ebitdo Flags = is-bootloader InstallDuration = 120 [DeviceInstanceId=USB\VID_0483&PID_5760] Plugin = ebitdo Flags = is-bootloader InstallDuration = 120 # FC30 [DeviceInstanceId=USB\VID_1235&PID_AB11] Plugin = ebitdo Flags = none [DeviceInstanceId=USB\VID_2DC8&PID_AB11] Plugin = ebitdo Flags = none InstallDuration = 120 # NES30 [DeviceInstanceId=USB\VID_1235&PID_AB12] Plugin = ebitdo Flags = none [DeviceInstanceId=USB\VID_2DC8&PID_AB12] Plugin = ebitdo Flags = none InstallDuration = 120 # SFC30 [DeviceInstanceId=USB\VID_1235&PID_AB21] Plugin = ebitdo Flags = none [DeviceInstanceId=USB\VID_2DC8&PID_AB21] Plugin = ebitdo Flags = none InstallDuration = 120 # SNES30 [DeviceInstanceId=USB\VID_1235&PID_AB20] Plugin = ebitdo Flags = none [DeviceInstanceId=USB\VID_2DC8&PID_AB20] Plugin = ebitdo Flags = none InstallDuration = 120 # FC30PRO [DeviceInstanceId=USB\VID_1002&PID_9000] Plugin = ebitdo Flags = none [DeviceInstanceId=USB\VID_2DC8&PID_9000] Plugin = ebitdo Flags = none InstallDuration = 120 # NES30PRO [DeviceInstanceId=USB\VID_2002&PID_9000] Plugin = ebitdo Flags = none [DeviceInstanceId=USB\VID_2DC8&PID_9001] Plugin = ebitdo Flags = none InstallDuration = 120 # FC30_ARCADE [DeviceInstanceId=USB\VID_8000&PID_1002] Plugin = ebitdo Flags = none [DeviceInstanceId=USB\VID_2DC8&PID_1002] Plugin = ebitdo Flags = none InstallDuration = 120 # SF30 PRO/SN30 PRO ## Dinput mode (Start + B) [DeviceInstanceId=USB\VID_2DC8&PID_6000] Plugin = ebitdo Flags = none [DeviceInstanceId=USB\VID_2DC8&PID_6001] Plugin = ebitdo Flags = none InstallDuration = 120 # M30 [DeviceInstanceId=USB\VID_2DC8&PID_5006] Plugin = ebitdo Flags = none InstallDuration = 120 fwupd-1.2.14/plugins/ebitdo/fu-ebitdo-common.c000066400000000000000000000052321402665037500211750ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ebitdo-common.h" const gchar * fu_ebitdo_pkt_type_to_string (FuEbitdoPktType cmd) { if (cmd == FU_EBITDO_PKT_TYPE_USER_CMD) return "user-cmd"; if (cmd == FU_EBITDO_PKT_TYPE_USER_DATA) return "user-data"; if (cmd == FU_EBITDO_PKT_TYPE_MID_CMD) return "mid-cmd"; return NULL; } const gchar * fu_ebitdo_pkt_cmd_to_string (FuEbitdoPktCmd cmd) { if (cmd == FU_EBITDO_PKT_CMD_FW_UPDATE_DATA) return "fw-update-data"; if (cmd == FU_EBITDO_PKT_CMD_FW_UPDATE_HEADER) return "fw-update-header"; if (cmd == FU_EBITDO_PKT_CMD_FW_UPDATE_OK) return "fw-update-ok"; if (cmd == FU_EBITDO_PKT_CMD_FW_UPDATE_ERROR) return "fw-update-error"; if (cmd == FU_EBITDO_PKT_CMD_FW_GET_VERSION) return "fw-get-version"; if (cmd == FU_EBITDO_PKT_CMD_FW_SET_VERSION) return "fw-set-version"; if (cmd == FU_EBITDO_PKT_CMD_FW_SET_ENCODE_ID) return "fw-set-encode-id"; if (cmd == FU_EBITDO_PKT_CMD_ACK) return "ack"; if (cmd == FU_EBITDO_PKT_CMD_NAK) return "nak"; if (cmd == FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA) return "update-firmware-data"; if (cmd == FU_EBITDO_PKT_CMD_TRANSFER_ABORT) return "transfer-abort"; if (cmd == FU_EBITDO_PKT_CMD_VERIFICATION_ID) return "verification-id"; if (cmd == FU_EBITDO_PKT_CMD_GET_VERIFICATION_ID) return "get-verification-id"; if (cmd == FU_EBITDO_PKT_CMD_VERIFY_ERROR) return "verify-error"; if (cmd == FU_EBITDO_PKT_CMD_VERIFY_OK) return "verify-ok"; if (cmd == FU_EBITDO_PKT_CMD_TRANSFER_TIMEOUT) return "transfer-timeout"; if (cmd == FU_EBITDO_PKT_CMD_GET_VERSION) return "get-version"; if (cmd == FU_EBITDO_PKT_CMD_GET_VERSION_RESPONSE) return "get-version-response"; return NULL; } void fu_ebitdo_dump_pkt (FuEbitdoPkt *hdr) { g_print ("PktLength: 0x%02x\n", hdr->pkt_len); g_print ("PktType: 0x%02x [%s]\n", hdr->type, fu_ebitdo_pkt_type_to_string (hdr->type)); g_print ("CmdSubtype: 0x%02x [%s]\n", hdr->subtype, fu_ebitdo_pkt_cmd_to_string (hdr->subtype)); g_print ("CmdLen: 0x%04x\n", GUINT16_FROM_LE (hdr->cmd_len)); g_print ("Cmd: 0x%02x [%s]\n", hdr->cmd, fu_ebitdo_pkt_cmd_to_string (hdr->cmd)); g_print ("Payload Len: 0x%04x\n", GUINT16_FROM_LE (hdr->payload_len)); } void fu_ebitdo_dump_firmware_header (FuEbitdoFirmwareHeader *hdr) { g_print ("Version: %.2f\n", (gdouble) GUINT32_FROM_LE (hdr->version) / 100.f); g_print ("Destination Address: %x\n", GUINT32_FROM_LE (hdr->destination_addr)); g_print ("Destination Length: %" G_GUINT32_FORMAT "\n", GUINT32_FROM_LE (hdr->destination_len)); } fwupd-1.2.14/plugins/ebitdo/fu-ebitdo-common.h000066400000000000000000000051331402665037500212020ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS /* little endian */ typedef struct __attribute__((packed)) { guint32 version; guint32 destination_addr; guint32 destination_len; guint32 reserved[4]; } FuEbitdoFirmwareHeader; /* little endian */ typedef struct __attribute__((packed)) { guint8 pkt_len; guint8 type; /* FuEbitdoPktType */ guint8 subtype; /* FuEbitdoPktCmd */ guint16 cmd_len; guint8 cmd; /* FuEbitdoPktCmd */ guint16 payload_len; /* optional */ } FuEbitdoPkt; #define FU_EBITDO_USB_TIMEOUT 5000 /* ms */ #define FU_EBITDO_USB_BOOTLOADER_EP_IN 0x82 #define FU_EBITDO_USB_BOOTLOADER_EP_OUT 0x01 #define FU_EBITDO_USB_RUNTIME_EP_IN 0x81 #define FU_EBITDO_USB_RUNTIME_EP_OUT 0x02 #define FU_EBITDO_USB_EP_SIZE 64 /* bytes */ typedef enum { FU_EBITDO_PKT_TYPE_USER_CMD = 0x00, FU_EBITDO_PKT_TYPE_USER_DATA = 0x01, FU_EBITDO_PKT_TYPE_MID_CMD = 0x02, FU_EBITDO_PKT_TYPE_LAST } FuEbitdoPktType; /* commands */ typedef enum { FU_EBITDO_PKT_CMD_FW_UPDATE_DATA = 0x00, /* update firmware data */ FU_EBITDO_PKT_CMD_FW_UPDATE_HEADER = 0x01, /* update firmware header */ FU_EBITDO_PKT_CMD_FW_UPDATE_OK = 0x02, /* mark update as successful */ FU_EBITDO_PKT_CMD_FW_UPDATE_ERROR = 0x03, /* update firmware error */ FU_EBITDO_PKT_CMD_FW_GET_VERSION = 0x04, /* get cur firmware vision */ FU_EBITDO_PKT_CMD_FW_SET_VERSION = 0x05, /* set firmware version */ FU_EBITDO_PKT_CMD_FW_SET_ENCODE_ID = 0x06, /* set app firmware encode ID */ FU_EBITDO_PKT_CMD_ACK = 0x14, /* acknowledge */ FU_EBITDO_PKT_CMD_NAK = 0x15, /* negative acknowledge */ FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA = 0x16, /* update firmware data */ FU_EBITDO_PKT_CMD_TRANSFER_ABORT = 0x18, /* aborts transfer */ FU_EBITDO_PKT_CMD_VERIFICATION_ID = 0x19, /* verification id (only BT?) */ FU_EBITDO_PKT_CMD_GET_VERIFICATION_ID = 0x1a, /* verification id (only BT) */ FU_EBITDO_PKT_CMD_VERIFY_ERROR = 0x1b, /* verification error */ FU_EBITDO_PKT_CMD_VERIFY_OK = 0x1c, /* verification successful */ FU_EBITDO_PKT_CMD_TRANSFER_TIMEOUT = 0x1d, /* send or receive data timeout */ FU_EBITDO_PKT_CMD_GET_VERSION = 0x21, /* get fw ver, joystick mode */ FU_EBITDO_PKT_CMD_GET_VERSION_RESPONSE = 0x22, /* get fw version response */ FU_EBITDO_PKT_CMD_FW_LAST } FuEbitdoPktCmd; const gchar *fu_ebitdo_pkt_cmd_to_string (FuEbitdoPktCmd cmd); const gchar *fu_ebitdo_pkt_type_to_string (FuEbitdoPktType type); void fu_ebitdo_dump_firmware_header (FuEbitdoFirmwareHeader *hdr); void fu_ebitdo_dump_pkt (FuEbitdoPkt *hdr); fwupd-1.2.14/plugins/ebitdo/fu-ebitdo-device.c000066400000000000000000000420061402665037500211440ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ebitdo-common.h" #include "fu-ebitdo-device.h" struct _FuEbitdoDevice { FuUsbDevice parent_instance; guint32 serial[9]; }; G_DEFINE_TYPE (FuEbitdoDevice, fu_ebitdo_device, FU_TYPE_USB_DEVICE) static gboolean fu_ebitdo_device_send (FuEbitdoDevice *self, FuEbitdoPktType type, FuEbitdoPktCmd subtype, FuEbitdoPktCmd cmd, const guint8 *in, gsize in_len, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); guint8 packet[FU_EBITDO_USB_EP_SIZE] = {0}; gsize actual_length; guint8 ep_out = FU_EBITDO_USB_RUNTIME_EP_OUT; g_autoptr(GError) error_local = NULL; FuEbitdoPkt *hdr = (FuEbitdoPkt *) packet; /* different */ if (fu_device_has_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) ep_out = FU_EBITDO_USB_BOOTLOADER_EP_OUT; /* check size */ if (in_len > 64 - 8) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "input buffer too large"); return FALSE; } /* packet[0] is the total length of the packet */ hdr->type = type; hdr->subtype = subtype; /* do we have a payload */ if (in_len > 0) { hdr->cmd_len = GUINT16_TO_LE (in_len + 3); hdr->cmd = cmd; hdr->payload_len = GUINT16_TO_LE (in_len); memcpy (packet + 0x08, in, in_len); hdr->pkt_len = (guint8) (in_len + 7); } else { hdr->cmd_len = GUINT16_TO_LE (in_len + 1); hdr->cmd = cmd; hdr->pkt_len = 5; } /* debug */ if (g_getenv ("FWUPD_EBITDO_VERBOSE") != NULL) { fu_common_dump_raw (G_LOG_DOMAIN, "->DEVICE", packet, (gsize) hdr->pkt_len + 1); fu_ebitdo_dump_pkt (hdr); } /* get data from device */ if (!g_usb_device_interrupt_transfer (usb_device, ep_out, packet, FU_EBITDO_USB_EP_SIZE, &actual_length, FU_EBITDO_USB_TIMEOUT, NULL, /* cancellable */ &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to send to device on ep 0x%02x: %s", (guint) FU_EBITDO_USB_BOOTLOADER_EP_OUT, error_local->message); return FALSE; } return TRUE; } static gboolean fu_ebitdo_device_receive (FuEbitdoDevice *self, guint8 *out, gsize out_len, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); guint8 packet[FU_EBITDO_USB_EP_SIZE] = {0}; gsize actual_length; guint8 ep_in = FU_EBITDO_USB_RUNTIME_EP_IN; g_autoptr(GError) error_local = NULL; FuEbitdoPkt *hdr = (FuEbitdoPkt *) packet; /* different */ if (fu_device_has_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) ep_in = FU_EBITDO_USB_BOOTLOADER_EP_IN; /* get data from device */ if (!g_usb_device_interrupt_transfer (usb_device, ep_in, packet, FU_EBITDO_USB_EP_SIZE, &actual_length, FU_EBITDO_USB_TIMEOUT, NULL, /* cancellable */ &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to retrieve from device on ep 0x%02x: %s", (guint) FU_EBITDO_USB_BOOTLOADER_EP_IN, error_local->message); return FALSE; } /* debug */ if (g_getenv ("FWUPD_EBITDO_VERBOSE") != NULL) { fu_common_dump_raw (G_LOG_DOMAIN, "<-DEVICE", packet, actual_length); fu_ebitdo_dump_pkt (hdr); } /* get-version (booloader) */ if (hdr->type == FU_EBITDO_PKT_TYPE_USER_CMD && hdr->subtype == FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA && hdr->cmd == FU_EBITDO_PKT_CMD_FW_GET_VERSION) { if (out != NULL) { if (hdr->payload_len != out_len) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "outbuf size wrong, expected %" G_GSIZE_FORMAT " got %u", out_len, hdr->payload_len); return FALSE; } memcpy (out, packet + sizeof(FuEbitdoPkt), hdr->payload_len); } return TRUE; } /* get-version (firmware) -- not a packet, just raw data! */ if (hdr->pkt_len == FU_EBITDO_PKT_CMD_GET_VERSION_RESPONSE) { if (out != NULL) { if (out_len != 4) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "outbuf size wrong, expected 4 got %" G_GSIZE_FORMAT, out_len); return FALSE; } memcpy (out, packet + 1, 4); } return TRUE; } /* verification-id response */ if (hdr->type == FU_EBITDO_PKT_TYPE_USER_CMD && hdr->subtype == FU_EBITDO_PKT_CMD_VERIFICATION_ID) { if (out != NULL) { if (hdr->cmd_len != out_len) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "outbuf size wrong, expected %" G_GSIZE_FORMAT " got %i", out_len, hdr->cmd_len); return FALSE; } memcpy (out, packet + sizeof(FuEbitdoPkt) - 3, hdr->cmd_len); } return TRUE; } /* update-firmware-data */ if (hdr->type == FU_EBITDO_PKT_TYPE_USER_CMD && hdr->subtype == FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA && hdr->payload_len == 0x00) { if (hdr->cmd != FU_EBITDO_PKT_CMD_ACK) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "write failed, got %s", fu_ebitdo_pkt_cmd_to_string (hdr->cmd)); return FALSE; } return TRUE; } /* unhandled */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unexpected device response"); return FALSE; } static void fu_ebitdo_device_set_version (FuEbitdoDevice *self, guint32 version) { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf ("%u.%02u", version / 100, version % 100); fu_device_set_version (FU_DEVICE (self), tmp, FWUPD_VERSION_FORMAT_PAIR); } static gboolean fu_ebitdo_device_validate (FuEbitdoDevice *self, GError **error) { const gchar *ven; const gchar *whitelist[] = { "8Bitdo", "SFC30", NULL }; /* this is a new, always valid, VID */ if (fu_usb_device_get_vid (FU_USB_DEVICE (self)) == 0x2dc8) return TRUE; /* verify the vendor prefix against a whitelist */ ven = fu_device_get_vendor (FU_DEVICE (self)); if (ven == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "could not check vendor descriptor: "); return FALSE; } for (guint i = 0; whitelist[i] != NULL; i++) { if (g_str_has_prefix (ven, whitelist[i])) return TRUE; } g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "vendor '%s' did not match whitelist, " "probably not a 8Bitdo device…", ven); return FALSE; } static gboolean fu_ebitdo_device_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); FuEbitdoDevice *self = FU_EBITDO_DEVICE (device); /* open, then ensure this is actually 8Bitdo hardware */ if (!fu_ebitdo_device_validate (self, error)) return FALSE; if (!g_usb_device_claim_interface (usb_device, 0, /* 0 = idx? */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { return FALSE; } /* success */ return TRUE; } static gboolean fu_ebitdo_device_setup (FuDevice *device, GError **error) { FuEbitdoDevice *self = FU_EBITDO_DEVICE (device); gdouble tmp; guint32 version_tmp = 0; guint32 serial_tmp[9] = {0}; /* in firmware mode */ if (!fu_device_has_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!fu_ebitdo_device_send (self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_GET_VERSION, 0, NULL, 0, /* in */ error)) { return FALSE; } if (!fu_ebitdo_device_receive (self, (guint8 *) &version_tmp, sizeof(version_tmp), error)) { return FALSE; } tmp = (gdouble) GUINT32_FROM_LE (version_tmp); fu_ebitdo_device_set_version (self, tmp); return TRUE; } /* get version */ if (!fu_ebitdo_device_send (self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA, FU_EBITDO_PKT_CMD_FW_GET_VERSION, NULL, 0, /* in */ error)) { return FALSE; } if (!fu_ebitdo_device_receive (self, (guint8 *) &version_tmp, sizeof(version_tmp), error)) { return FALSE; } tmp = (gdouble) GUINT32_FROM_LE (version_tmp); fu_ebitdo_device_set_version (self, tmp); /* get verification ID */ if (!fu_ebitdo_device_send (self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_GET_VERIFICATION_ID, 0x00, /* cmd */ NULL, 0, error)) { return FALSE; } if (!fu_ebitdo_device_receive (self, (guint8 *) &serial_tmp, sizeof(serial_tmp), error)) { return FALSE; } for (guint i = 0; i < 9; i++) self->serial[i] = GUINT32_FROM_LE (serial_tmp[i]); /* success */ return TRUE; } const guint32 * fu_ebitdo_device_get_serial (FuEbitdoDevice *self) { return self->serial; } static gboolean fu_ebitdo_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuEbitdoDevice *self = FU_EBITDO_DEVICE (device); FuEbitdoFirmwareHeader *hdr; const guint8 *payload_data; const guint chunk_sz = 32; guint32 payload_len; guint32 serial_new[3]; g_autoptr(GError) error_local = NULL; const guint32 app_key_index[16] = { 0x186976e5, 0xcac67acd, 0x38f27fee, 0x0a4948f1, 0xb75b7753, 0x1f8ffa5c, 0xbff8cf43, 0xc4936167, 0x92bd03f0, 0x5573c6ed, 0x57d8845b, 0x827197ac, 0xb91901c9, 0x3917edfe, 0xbcd6344f, 0xcf9e23b5 }; /* not in bootloader mode, so print what to do */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER)) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_autoptr(GString) msg = g_string_new ("Not in bootloader mode: "); g_string_append (msg, "Disconnect the controller, "); g_print ("1. \n"); switch (g_usb_device_get_pid (usb_device)) { case 0xab11: /* FC30 */ case 0xab12: /* NES30 */ case 0xab21: /* SFC30 */ case 0xab20: /* SNES30 */ g_string_append (msg, "hold down L+R+START for 3 seconds until " "both LED lights flashing, "); break; case 0x9000: /* FC30PRO */ case 0x9001: /* NES30PRO */ g_string_append (msg, "hold down RETURN+POWER for 3 seconds until " "both LED lights flashing, "); break; case 0x1002: /* FC30-ARCADE */ g_string_append (msg, "hold down L1+R1+HOME for 3 seconds until " "both blue LED and green LED blink, "); break; case 0x6000: /* SF30 pro: Dinput mode */ case 0x6001: /* SN30 pro: Dinput mode */ case 0x028e: /* SF30/SN30 pro: Xinput mode */ case 0x5006: /* M30 */ g_string_append (msg, "press and hold L1+R1+START for 3 seconds " "until the LED on top blinks red, "); break; default: g_string_append (msg, "do what it says in the manual, "); break; } g_string_append (msg, "then re-connect controller"); g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, msg->str); return FALSE; } /* corrupt */ if (g_bytes_get_size (fw) < sizeof (FuEbitdoFirmwareHeader)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware too small for header"); return FALSE; } /* print details about the firmware */ hdr = (FuEbitdoFirmwareHeader *) g_bytes_get_data (fw, NULL); fu_ebitdo_dump_firmware_header (hdr); /* check the file size */ payload_len = (guint32) (g_bytes_get_size (fw) - sizeof (FuEbitdoFirmwareHeader)); if (payload_len != GUINT32_FROM_LE (hdr->destination_len)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file size incorrect, expected 0x%04x got 0x%04x", (guint) GUINT32_FROM_LE (hdr->destination_len), (guint) payload_len); return FALSE; } /* check if this is firmware */ for (guint i = 0; i < 4; i++) { if (hdr->reserved[i] != 0x0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "data invalid, reserved[%u] = 0x%04x", i, hdr->reserved[i]); return FALSE; } } /* set up the firmware header */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); if (!fu_ebitdo_device_send (self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA, FU_EBITDO_PKT_CMD_FW_UPDATE_HEADER, (const guint8 *) hdr, sizeof(FuEbitdoFirmwareHeader), &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to set up firmware header: %s", error_local->message); return FALSE; } if (!fu_ebitdo_device_receive (self, NULL, 0, &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to get ACK for fw update header: %s", error_local->message); return FALSE; } /* flash the firmware in 32 byte blocks */ payload_data = g_bytes_get_data (fw, NULL); payload_data += sizeof(FuEbitdoFirmwareHeader); for (guint32 offset = 0; offset < payload_len; offset += chunk_sz) { if (g_getenv ("FWUPD_EBITDO_VERBOSE") != NULL) { g_debug ("writing %u bytes to 0x%04x of 0x%04x", chunk_sz, offset, payload_len); } fu_device_set_progress_full (device, offset, payload_len); if (!fu_ebitdo_device_send (self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA, FU_EBITDO_PKT_CMD_FW_UPDATE_DATA, payload_data + offset, chunk_sz, &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Failed to write firmware @0x%04x: %s", offset, error_local->message); return FALSE; } if (!fu_ebitdo_device_receive (self, NULL, 0, &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Failed to get ACK for write firmware @0x%04x: %s", offset, error_local->message); return FALSE; } } /* mark as complete */ fu_device_set_progress_full (device, payload_len, payload_len); /* set the "encode id" which is likely a checksum, bluetooth pairing * or maybe just security-through-obscurity -- also note: * SET_ENCODE_ID enforces no read for success?! */ serial_new[0] = self->serial[0] ^ app_key_index[self->serial[0] & 0x0f]; serial_new[1] = self->serial[1] ^ app_key_index[self->serial[1] & 0x0f]; serial_new[2] = self->serial[2] ^ app_key_index[self->serial[2] & 0x0f]; if (!fu_ebitdo_device_send (self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA, FU_EBITDO_PKT_CMD_FW_SET_ENCODE_ID, (guint8 *) serial_new, sizeof(serial_new), &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to set encoding ID: %s", error_local->message); return FALSE; } /* mark flash as successful */ if (!fu_ebitdo_device_send (self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA, FU_EBITDO_PKT_CMD_FW_UPDATE_OK, NULL, 0, &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to mark firmware as successful: %s", error_local->message); return FALSE; } if (!fu_ebitdo_device_receive (self, NULL, 0, &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to get ACK for mark firmware as successful: %s", error_local->message); return FALSE; } /* success! */ return TRUE; } static gboolean fu_ebitdo_device_probe (FuUsbDevice *device, GError **error) { FuEbitdoDevice *self = FU_EBITDO_DEVICE (device); /* allowed, but requires manual bootloader step */ fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_remove_delay (FU_DEVICE (device), FU_DEVICE_REMOVE_DELAY_USER_REPLUG); /* set name and vendor */ fu_device_set_summary (FU_DEVICE (device), "A redesigned classic game controller"); fu_device_set_vendor (FU_DEVICE (device), "8Bitdo"); /* add a hardcoded icon name */ fu_device_add_icon (FU_DEVICE (device), "input-gaming"); /* only the bootloader can do the update */ if (!fu_device_has_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { fu_device_add_counterpart_guid (FU_DEVICE (device), "USB\\VID_0483&PID_5750"); fu_device_add_counterpart_guid (FU_DEVICE (device), "USB\\VID_2DC8&PID_5750"); fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); } /* success */ return TRUE; } static void fu_ebitdo_device_init (FuEbitdoDevice *self) { } static void fu_ebitdo_device_class_init (FuEbitdoDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->write_firmware = fu_ebitdo_device_write_firmware; klass_device->setup = fu_ebitdo_device_setup; klass_usb_device->open = fu_ebitdo_device_open; klass_usb_device->probe = fu_ebitdo_device_probe; } /** * fu_ebitdo_device_new: * * Creates a new #FuEbitdoDevice. * * Returns: (transfer full): a #FuEbitdoDevice, or %NULL if not a game pad * * Since: 0.1.0 **/ FuEbitdoDevice * fu_ebitdo_device_new (FuUsbDevice *device) { FuEbitdoDevice *self; self = g_object_new (FU_TYPE_EBITDO_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return FU_EBITDO_DEVICE (self); } fwupd-1.2.14/plugins/ebitdo/fu-ebitdo-device.h000066400000000000000000000007271402665037500211550ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_EBITDO_DEVICE (fu_ebitdo_device_get_type ()) G_DECLARE_FINAL_TYPE (FuEbitdoDevice, fu_ebitdo_device, FU, EBITDO_DEVICE, FuUsbDevice) FuEbitdoDevice *fu_ebitdo_device_new (FuUsbDevice *device); /* getters */ const guint32 *fu_ebitdo_device_get_serial (FuEbitdoDevice *device); G_END_DECLS fwupd-1.2.14/plugins/ebitdo/fu-plugin-ebitdo.c000066400000000000000000000045411402665037500212050ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ebitdo-device.h" #include "fu-plugin-vfuncs.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.8bitdo"); } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuEbitdoDevice) dev = NULL; /* open the device */ dev = fu_ebitdo_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; /* success */ fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (dev)); FuEbitdoDevice *ebitdo_dev = FU_EBITDO_DEVICE (dev); g_autoptr(FuDeviceLocker) locker = NULL; /* get version */ if (!fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid 8Bitdo device type detected"); return FALSE; } /* write the firmware */ locker = fu_device_locker_new (ebitdo_dev, error); if (locker == NULL) return FALSE; if (!fu_device_write_firmware (FU_DEVICE (ebitdo_dev), blob_fw, flags, error)) return FALSE; /* when doing a soft-reboot the device does not re-enumerate properly * so manually reboot the GUsbDevice */ fu_device_set_status (dev, FWUPD_STATUS_DEVICE_RESTART); if (!g_usb_device_reset (usb_device, error)) { g_prefix_error (error, "failed to force-reset device: "); return FALSE; } /* wait for replug */ fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } gboolean fu_plugin_update_reload (FuPlugin *plugin, FuDevice *dev, GError **error) { FuEbitdoDevice *ebitdo_dev = FU_EBITDO_DEVICE (dev); g_autoptr(FuDeviceLocker) locker = NULL; /* get the new version number */ locker = fu_device_locker_new (ebitdo_dev, error); if (locker == NULL) { g_prefix_error (error, "failed to re-open device: "); return FALSE; } /* success */ return TRUE; } fwupd-1.2.14/plugins/ebitdo/meson.build000066400000000000000000000010731402665037500200260ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginEbitdo"'] install_data(['ebitdo.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_ebitdo', fu_hash, sources : [ 'fu-plugin-ebitdo.c', 'fu-ebitdo-common.c', 'fu-ebitdo-device.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/fastboot/000077500000000000000000000000001402665037500162365ustar00rootroot00000000000000fwupd-1.2.14/plugins/fastboot/README.md000066400000000000000000000022521402665037500175160ustar00rootroot00000000000000Fastboot Support ================ Introduction ------------ This plugin is used to update hardware that uses the fastboot protocol. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in ZIP file format. Inside the zip file must be all the firmware images for each partition and a manifest file. The partition images can be in any format, but the manifest must be either an Android `flashfile.xml` format file, or a QFIL `partition_nand.xml` format file. For both types, all partitions with a defined image found in the zip file will be updated. This plugin supports the following protocol ID: * com.google.fastboot GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_18D1&PID_4EE0&REV_0001` * `USB\VID_18D1&PID_4EE0` * `USB\VID_18D1` Quirk use --------- This plugin uses the following plugin-specific quirk: | Quirk | Description | Minimum fwupd version | |------------------------|----------------------------------|-----------------------| | `FastbootBlockSize` | Block size to use for transfers | 1.2.2 | fwupd-1.2.14/plugins/fastboot/data/000077500000000000000000000000001402665037500171475ustar00rootroot00000000000000fwupd-1.2.14/plugins/fastboot/data/android/000077500000000000000000000000001402665037500205675ustar00rootroot00000000000000fwupd-1.2.14/plugins/fastboot/data/android/flashfile.xml000066400000000000000000000003701402665037500232460ustar00rootroot00000000000000 fwupd-1.2.14/plugins/fastboot/data/lsusb.txt000066400000000000000000000036661402665037500210530ustar00rootroot00000000000000Bus 001 Device 025: ID 18d1:4ee0 Google Inc. Nexus 4 (bootloader) Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x18d1 Google Inc. idProduct 0x4ee0 Nexus 4 (bootloader) bcdDevice 1.00 iManufacturer 1 Google iProduct 2 Android iSerial 3 034412a082919b5c bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0020 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 66 bInterfaceProtocol 3 iInterface 4 fastboot Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/fastboot/data/qfil/000077500000000000000000000000001402665037500201025ustar00rootroot00000000000000fwupd-1.2.14/plugins/fastboot/data/qfil/partition_nand.xml000066400000000000000000000011751402665037500236410ustar00rootroot00000000000000 0xAA7D1B9A 0x1F7D48BC 0x4 0:SBL 0x8 0x2 0 0xFF 0x01 0x00 0xFE sbl1.mbn fwupd-1.2.14/plugins/fastboot/fastboot.quirk000066400000000000000000000001351402665037500211330ustar00rootroot00000000000000# All fastboot devices [DeviceInstanceId=USB\CLASS_FF&SUBCLASS_42&PROT_03] Plugin = fastboot fwupd-1.2.14/plugins/fastboot/fu-fastboot-device.c000066400000000000000000000467501402665037500221040ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-archive.h" #include "fu-chunk.h" #include "fu-fastboot-device.h" #define FASTBOOT_REMOVE_DELAY_RE_ENUMERATE 60000 /* ms */ #define FASTBOOT_TRANSACTION_TIMEOUT 1000 /* ms */ #define FASTBOOT_TRANSACTION_RETRY_MAX 600 #define FASTBOOT_EP_IN 0x81 #define FASTBOOT_EP_OUT 0x01 #define FASTBOOT_CMD_BUFSZ 64 /* bytes */ struct _FuFastbootDevice { FuUsbDevice parent_instance; gboolean secure; guint blocksz; guint8 intf_nr; }; G_DEFINE_TYPE (FuFastbootDevice, fu_fastboot_device, FU_TYPE_USB_DEVICE) static void fu_fastboot_device_to_string (FuDevice *device, GString *str) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device); g_string_append (str, " FuFastbootDevice:\n"); g_string_append_printf (str, " intf:\t0x%02x\n", (guint) self->intf_nr); g_string_append_printf (str, " secure:\t%i\n", self->secure); g_string_append_printf (str, " blocksz:\t%u\n", self->blocksz); } static gboolean fu_fastboot_device_probe (FuDevice *device, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); g_autoptr(GUsbInterface) intf = NULL; /* find the correct fastboot interface */ intf = g_usb_device_get_interface (usb_device, 0xff, 0x42, 0x03, error); if (intf == NULL) return FALSE; self->intf_nr = g_usb_interface_get_number (intf); return TRUE; } static gboolean fu_fastboot_device_open (FuUsbDevice *device, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (device); if (!g_usb_device_claim_interface (usb_device, self->intf_nr, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to claim interface: "); return FALSE; } /* success */ return TRUE; } static void fu_fastboot_buffer_dump (const gchar *title, const guint8 *buf, gsize sz) { if (g_getenv ("FWUPD_FASTBOOT_VERBOSE") == NULL) return; g_print ("%s (%" G_GSIZE_FORMAT "):\n", title, sz); for (gsize i = 0; i < sz; i++) { g_print ("%02x[%c] ", buf[i], g_ascii_isprint (buf[i]) ? buf[i] : '?'); if (i > 0 && (i + 1) % 256 == 0) g_print ("\n"); } g_print ("\n"); } static gboolean fu_fastboot_device_write (FuDevice *device, const guint8 *buf, gsize buflen, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); gboolean ret; gsize actual_len = 0; g_autofree guint8 *buf2 = g_memdup (buf, (guint) buflen); fu_fastboot_buffer_dump ("writing", buf, buflen); ret = g_usb_device_bulk_transfer (usb_device, FASTBOOT_EP_OUT, buf2, buflen, &actual_len, FASTBOOT_TRANSACTION_TIMEOUT, NULL, error); if (!ret) { g_prefix_error (error, "failed to do bulk transfer: "); return FALSE; } if (actual_len != buflen) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } static gboolean fu_fastboot_device_writestr (FuDevice *device, const gchar *str, GError **error) { gsize buflen = strlen (str); if (buflen > FASTBOOT_CMD_BUFSZ - 4) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "fastboot limits writes to %i bytes", FASTBOOT_CMD_BUFSZ - 4); return FALSE; } return fu_fastboot_device_write (device, (const guint8 *) str, buflen, error); } typedef enum { FU_FASTBOOT_DEVICE_READ_FLAG_NONE, FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL, } FuFastbootDeviceReadFlags; static gboolean fu_fastboot_device_read (FuDevice *device, gchar **str, FuFastbootDeviceReadFlags flags, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); guint retries = 1; /* these commands may return INFO or take some time to complete */ if (flags & FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL) retries = FASTBOOT_TRANSACTION_RETRY_MAX; for (guint i = 0; i < retries; i++) { gboolean ret; gsize actual_len = 0; guint8 buf[FASTBOOT_CMD_BUFSZ] = { 0x00 }; g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; ret = g_usb_device_bulk_transfer (usb_device, FASTBOOT_EP_IN, buf, sizeof(buf), &actual_len, FASTBOOT_TRANSACTION_TIMEOUT, NULL, &error_local); if (!ret) { if (g_error_matches (error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT)) { g_debug ("ignoring %s", error_local->message); continue; } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to do bulk transfer: "); return FALSE; } fu_fastboot_buffer_dump ("read", buf, actual_len); if (actual_len < 4) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } /* info */ tmp = g_strndup ((const gchar *) buf + 4, self->blocksz - 4); if (memcmp (buf, "INFO", 4) == 0) { if (g_strcmp0 (tmp, "erasing flash") == 0) fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); else if (g_strcmp0 (tmp, "writing flash") == 0) fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); else g_debug ("INFO returned unknown: %s", tmp); continue; } /* success */ if (memcmp (buf, "OKAY", 4) == 0 || memcmp (buf, "DATA", 4) == 0) { if (str != NULL) *str = g_steal_pointer (&tmp); return TRUE; } /* failure */ if (memcmp (buf, "FAIL", 4) == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to read response: %s", tmp); return FALSE; } /* unknown failure */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to read response"); return FALSE; } /* we timed out a *lot* */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "no response to read"); return FALSE; } static gboolean fu_fastboot_device_getvar (FuDevice *device, const gchar *key, gchar **str, GError **error) { g_autofree gchar *tmp = g_strdup_printf ("getvar:%s", key); if (!fu_fastboot_device_writestr (device, tmp, error)) return FALSE; if (!fu_fastboot_device_read (device, str, FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error)) return FALSE; return TRUE; } static gboolean fu_fastboot_device_cmd (FuDevice *device, const gchar *cmd, FuFastbootDeviceReadFlags flags, GError **error) { if (!fu_fastboot_device_writestr (device, cmd, error)) return FALSE; if (!fu_fastboot_device_read (device, NULL, flags, error)) return FALSE; return TRUE; } static gboolean fu_fastboot_device_flash (FuDevice *device, const gchar *partition, GError **error) { g_autofree gchar *tmp = g_strdup_printf ("flash:%s", partition); return fu_fastboot_device_cmd (device, tmp, FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL, error); } static gboolean fu_fastboot_device_download (FuDevice *device, GBytes *fw, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device); gsize sz = g_bytes_get_size (fw); g_autofree gchar *tmp = g_strdup_printf ("download:%08x", (guint) sz); g_autoptr(GPtrArray) chunks = NULL; /* tell the client the size of data to expect */ if (!fu_fastboot_device_cmd (device, tmp, FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL, error)) return FALSE; /* send the data in chunks */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); chunks = fu_chunk_array_new_from_bytes (fw, 0x00, /* start addr */ 0x00, /* page_sz */ self->blocksz); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); if (!fu_fastboot_device_write (device, chk->data, chk->data_sz, error)) return FALSE; fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len * 2); } if (!fu_fastboot_device_read (device, NULL, FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL, error)) return FALSE; return TRUE; } static gboolean fu_fastboot_device_setup (FuDevice *device, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device); g_autofree gchar *product = NULL; g_autofree gchar *serialno = NULL; g_autofree gchar *version = NULL; g_autofree gchar *secure = NULL; g_autofree gchar *version_bootloader = NULL; /* product */ if (!fu_fastboot_device_getvar (device, "product", &product, error)) return FALSE; if (product != NULL && product[0] != '\0') { g_autofree gchar *tmp = g_strdup_printf ("Fastboot %s", product); fu_device_set_name (device, tmp); } /* fastboot API version */ if (!fu_fastboot_device_getvar (device, "version", &version, error)) return FALSE; if (version != NULL && version[0] != '\0') g_debug ("fastboot version=%s", version); /* bootloader version */ if (!fu_fastboot_device_getvar (device, "version-bootloader", &version_bootloader, error)) return FALSE; if (version_bootloader != NULL && version_bootloader[0] != '\0') fu_device_set_version_bootloader (device, version_bootloader); /* serialno */ if (!fu_fastboot_device_getvar (device, "serialno", &serialno, error)) return FALSE; if (serialno != NULL && serialno[0] != '\0') fu_device_set_serial (device, serialno); /* secure */ if (!fu_fastboot_device_getvar (device, "secure", &secure, error)) return FALSE; if (secure != NULL && secure[0] != '\0') self->secure = TRUE; /* success */ return TRUE; } static gboolean fu_fastboot_device_write_qfil_part (FuDevice *device, FuArchive *archive, XbNode *part, GError **error) { GBytes *data; const gchar *fn; const gchar *partition; /* not all partitions have images */ fn = xb_node_query_text (part, "img_name", NULL); if (fn == NULL) return TRUE; /* find filename */ data = fu_archive_lookup_by_fn (archive, fn, error); if (data == NULL) return FALSE; /* get the partition name */ partition = xb_node_query_text (part, "name", error); if (partition == NULL) return FALSE; if (g_str_has_prefix (partition, "0:")) partition += 2; /* flash the partition */ if (!fu_fastboot_device_download (device, data, error)) return FALSE; return fu_fastboot_device_flash (device, partition, error); } static gboolean fu_fastboot_device_write_motorola_part (FuDevice *device, FuArchive *archive, XbNode *part, GError **error) { const gchar *op = xb_node_get_attr (part, "operation"); /* oem */ if (g_strcmp0 (op, "oem") == 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "OEM commands are not supported"); return FALSE; } /* getvar */ if (g_strcmp0 (op, "getvar") == 0) { const gchar *var = xb_node_get_attr (part, "var"); g_autofree gchar *tmp = NULL; /* check required args */ if (var == NULL) { tmp = xb_node_export (part, XB_NODE_EXPORT_FLAG_NONE, NULL); g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "required var for part: %s", tmp); return FALSE; } /* just has to be non-empty */ if (!fu_fastboot_device_getvar (device, var, &tmp, error)) return FALSE; if (tmp == NULL || tmp[0] == '\0') { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to getvar %s", var); return FALSE; } return TRUE; } /* erase */ if (g_strcmp0 (op, "erase") == 0) { const gchar *partition = xb_node_get_attr (part, "partition"); g_autofree gchar *cmd = g_strdup_printf ("erase:%s", partition); /* check required args */ if (partition == NULL) { g_autofree gchar *tmp = NULL; tmp = xb_node_export (part, XB_NODE_EXPORT_FLAG_NONE, NULL); g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "required partition for part: %s", tmp); return FALSE; } /* erase the partition */ return fu_fastboot_device_cmd (device, cmd, FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error); } /* flash */ if (g_strcmp0 (op, "flash") == 0) { GBytes *data; const gchar *filename = xb_node_get_attr (part, "filename"); const gchar *partition = xb_node_get_attr (part, "partition"); struct { GChecksumType kind; const gchar *str; } csum_kinds[] = { { G_CHECKSUM_MD5, "MD5" }, { G_CHECKSUM_SHA1, "SHA1" }, { 0, NULL } }; /* check required args */ if (partition == NULL || filename == NULL) { g_autofree gchar *tmp = NULL; tmp = xb_node_export (part, XB_NODE_EXPORT_FLAG_NONE, NULL); g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "required partition and filename: %s", tmp); return FALSE; } /* find filename */ data = fu_archive_lookup_by_fn (archive, filename, error); if (data == NULL) return FALSE; /* checksum is optional */ for (guint i = 0; csum_kinds[i].str != NULL; i++) { const gchar *csum; g_autofree gchar *csum_actual = NULL; /* not provided */ csum = xb_node_get_attr (part, csum_kinds[i].str); if (csum == NULL) continue; /* check is valid */ csum_actual = g_compute_checksum_for_bytes (csum_kinds[i].kind, data); if (g_strcmp0 (csum, csum_actual) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s invalid, expected %s, got %s", filename, csum, csum_actual); return FALSE; } } /* flash the partition */ if (!fu_fastboot_device_download (device, data, error)) return FALSE; return fu_fastboot_device_flash (device, partition, error); } /* dumb operation that doesn't expect a response */ if (g_strcmp0 (op, "boot") == 0 || g_strcmp0 (op, "continue") == 0 || g_strcmp0 (op, "reboot") == 0 || g_strcmp0 (op, "reboot-bootloader") == 0 || g_strcmp0 (op, "powerdown") == 0) { return fu_fastboot_device_cmd (device, op, FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error); } /* unknown */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unknown operation %s", op); return FALSE; } static gboolean fu_fastboot_device_write_motorola (FuDevice *device, FuArchive* archive, GError **error) { GBytes *data; g_autoptr(GPtrArray) parts = NULL; g_autoptr(XbBuilder) builder = xb_builder_new (); g_autoptr(XbBuilderSource) source = xb_builder_source_new (); g_autoptr(XbSilo) silo = NULL; /* load the manifest of operations */ data = fu_archive_lookup_by_fn (archive, "flashfile.xml", error); if (data == NULL) return FALSE; if (!xb_builder_source_load_bytes (source, data, XB_BUILDER_SOURCE_FLAG_NONE, error)) return FALSE; xb_builder_import_source (builder, source); silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; /* get all the operation parts */ parts = xb_silo_query (silo, "parts/part", 0, error); if (parts == NULL) return FALSE; for (guint i = 0; i < parts->len; i++) { XbNode *part = g_ptr_array_index (parts, i); if (!fu_fastboot_device_write_motorola_part (device, archive, part, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_fastboot_device_write_qfil (FuDevice *device, FuArchive* archive, GError **error) { GBytes *data; g_autoptr(GPtrArray) parts = NULL; g_autoptr(XbBuilder) builder = xb_builder_new (); g_autoptr(XbBuilderSource) source = xb_builder_source_new (); g_autoptr(XbSilo) silo = NULL; /* load the manifest of operations */ data = fu_archive_lookup_by_fn (archive, "partition_nand.xml", error); if (data == NULL) return FALSE; if (!xb_builder_source_load_bytes (source, data, XB_BUILDER_SOURCE_FLAG_NONE, error)) return FALSE; xb_builder_import_source (builder, source); silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; /* get all the operation parts */ parts = xb_silo_query (silo, "nandboot/partitions/partition", 0, error); if (parts == NULL) return FALSE; for (guint i = 0; i < parts->len; i++) { XbNode *part = g_ptr_array_index (parts, i); if (!fu_fastboot_device_write_qfil_part (device, archive, part, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_fastboot_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuArchive) archive = NULL; /* decompress entire archive ahead of time */ archive = fu_archive_new (fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; /* load the manifest of operations */ if (fu_archive_lookup_by_fn (archive, "partition_nand.xml", NULL) != NULL) return fu_fastboot_device_write_qfil (device, archive, error); if (fu_archive_lookup_by_fn (archive, "flashfile.xml", NULL) != NULL) { return fu_fastboot_device_write_motorola (device, archive, error); } /* not supported */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "manifest not supported"); return FALSE; } static gboolean fu_fastboot_device_close (FuUsbDevice *device, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* we're done here */ if (!g_usb_device_release_interface (usb_device, self->intf_nr, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to release interface: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_fastboot_device_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device); /* load slave address from quirks */ if (g_strcmp0 (key, "FastbootBlockSize") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp >= 0x40 && tmp < 0x100000) { self->blocksz = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid block size"); return FALSE; } /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_fastboot_device_attach (FuDevice *device, GError **error) { fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); return fu_fastboot_device_cmd (device, "reboot", FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error); } static void fu_fastboot_device_init (FuFastbootDevice *self) { /* this is a safe default, even using USBv1 */ self->blocksz = 512; fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_remove_delay (FU_DEVICE (self), FASTBOOT_REMOVE_DELAY_RE_ENUMERATE); } static void fu_fastboot_device_class_init (FuFastbootDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->probe = fu_fastboot_device_probe; klass_device->setup = fu_fastboot_device_setup; klass_device->write_firmware = fu_fastboot_device_write_firmware; klass_device->attach = fu_fastboot_device_attach; klass_device->to_string = fu_fastboot_device_to_string; klass_device->set_quirk_kv = fu_fastboot_device_set_quirk_kv; klass_usb_device->open = fu_fastboot_device_open; klass_usb_device->close = fu_fastboot_device_close; } FuFastbootDevice * fu_fastboot_device_new (FuUsbDevice *device) { FuFastbootDevice *self = g_object_new (FU_TYPE_FASTBOOT_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } fwupd-1.2.14/plugins/fastboot/fu-fastboot-device.h000066400000000000000000000006161402665037500221000ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_FASTBOOT_DEVICE (fu_fastboot_device_get_type ()) G_DECLARE_FINAL_TYPE (FuFastbootDevice, fu_fastboot_device, FU, FASTBOOT_DEVICE, FuUsbDevice) FuFastbootDevice *fu_fastboot_device_new (FuUsbDevice *device); G_END_DECLS fwupd-1.2.14/plugins/fastboot/fu-plugin-fastboot.c000066400000000000000000000030141402665037500221250ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-fastboot-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.google.fastboot"); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* reset */ if (!fu_device_attach (device, error)) return FALSE; /* wait for replug */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuFastbootDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; dev = fu_fastboot_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } fwupd-1.2.14/plugins/fastboot/meson.build000066400000000000000000000010531402665037500203770ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginFastboot"'] install_data(['fastboot.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_fastboot', fu_hash, sources : [ 'fu-plugin-fastboot.c', 'fu-fastboot-device.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/flashrom/000077500000000000000000000000001402665037500162305ustar00rootroot00000000000000fwupd-1.2.14/plugins/flashrom/README.md000066400000000000000000000011141402665037500175040ustar00rootroot00000000000000Flashrom ======== Introduction ------------ This plugin uses `flashrom` to update the system firmware. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format, which is typically the raw input for an EEPROM programmer. This plugin supports the following protocol ID: * org.flashrom GUID Generation --------------- These device uses hardware ID values which are derived from SMBIOS. They should match the values provided by `fwupdtool hwids` or the `ComputerHardwareIds.exe` Windows utility. fwupd-1.2.14/plugins/flashrom/example/000077500000000000000000000000001402665037500176635ustar00rootroot00000000000000fwupd-1.2.14/plugins/flashrom/example/build.sh000077500000000000000000000003231402665037500213170ustar00rootroot00000000000000#/bin/sh appstream-util validate-relax com.Flashrom.Laptop.metainfo.xml tar -cf firmware.tar startup.sh random-tool gcab --create --nopath Flashrom-Laptop-1.2.3.cab firmware.tar com.Flashrom.Laptop.metainfo.xml fwupd-1.2.14/plugins/flashrom/example/com.Flashrom.Laptop.metainfo.xml000066400000000000000000000026311402665037500257760ustar00rootroot00000000000000 com.Flashrom.Laptop.firmware Flashrom Laptop Firmware

System firmware for a Flashrom laptop

The laptop can be updated using flashrom.

a0ce5085-2dea-5086-ae72-45810a186ad0 http://www.bbc.co.uk/ CC0-1.0 Proprietary Flashrom

This release updates a frobnicator to frob faster.

startup.sh firmware.bin org.freedesktop.fwupd fwupd-1.2.14/plugins/flashrom/example/random-tool000077500000000000000000000000471402665037500220450ustar00rootroot00000000000000#/bin/sh echo "hello from the sandbox" fwupd-1.2.14/plugins/flashrom/example/startup.sh000077500000000000000000000003011402665037500217160ustar00rootroot00000000000000#/bin/sh # do something with the old firmware sha1sum /boot/flashrom-librem15v3.bin # run a random tool ./random-tool # this is the deliverable cp /boot/flashrom-librem15v3.bin firmware.bin fwupd-1.2.14/plugins/flashrom/flashrom.quirk000066400000000000000000000002461402665037500211220ustar00rootroot00000000000000# Purism [HwId=a0ce5085-2dea-5086-ae72-45810a186ad0] DeviceId=librem15v3 # Libretrend [HwId=52b68c34-6b31-5ecc-8a5c-de37e666ccd5] DeviceId=LT1000 VersionFormat=quad fwupd-1.2.14/plugins/flashrom/fu-plugin-flashrom.c000066400000000000000000000174171402665037500221250ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2017 Richard Hughes * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have 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. */ #include "config.h" #include #include "fu-plugin-vfuncs.h" #include "libflashrom.h" #define SELFCHECK_TRUE 1 struct FuPluginData { gsize flash_size; struct flashrom_flashctx *flashctx; struct flashrom_layout *layout; struct flashrom_programmer *flashprog; }; void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "org.flashrom"); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); flashrom_layout_release (data->layout); flashrom_programmer_shutdown (data->flashprog); flashrom_flash_release (data->flashctx); } static int fu_plugin_flashrom_debug_cb (enum flashrom_log_level lvl, const char *fmt, va_list args) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" g_autofree gchar *tmp = g_strdup_vprintf (fmt, args); #pragma clang diagnostic pop switch (lvl) { case FLASHROM_MSG_ERROR: case FLASHROM_MSG_WARN: g_warning ("%s", tmp); break; case FLASHROM_MSG_INFO: g_debug ("%s", tmp); break; case FLASHROM_MSG_DEBUG: case FLASHROM_MSG_DEBUG2: if (g_getenv ("FWUPD_FLASHROM_VERBOSE") != NULL) g_debug ("%s", tmp); break; case FLASHROM_MSG_SPEW: break; default: break; } return 0; } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); GPtrArray *hwids = fu_plugin_get_hwids (plugin); g_autoptr(GPtrArray) devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < hwids->len; i++) { const gchar *guid = g_ptr_array_index (hwids, i); const gchar *quirk_str; g_autofree gchar *quirk_key_prefixed = NULL; quirk_key_prefixed = g_strdup_printf ("HwId=%s", guid); quirk_str = fu_plugin_lookup_quirk_by_id (plugin, quirk_key_prefixed, "DeviceId"); if (quirk_str != NULL) { g_autofree gchar *device_id = g_strdup_printf ("flashrom-%s", quirk_str); g_autoptr(FuDevice) dev = fu_device_new (); fu_device_set_id (dev, device_id); fu_device_set_quirks (dev, fu_plugin_get_quirks (plugin)); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_name (dev, fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_NAME)); fu_device_set_vendor (dev, fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER)); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_ENSURE_SEMVER); fu_device_set_version (dev, fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BIOS_VERSION), FWUPD_VERSION_FORMAT_UNKNOWN); fu_device_add_guid (dev, guid); g_ptr_array_add (devices, g_steal_pointer (&dev)); break; } } /* nothing to do, so don't bother initializing flashrom */ if (devices->len == 0) return TRUE; /* actually probe hardware to check for support */ if (flashrom_init (SELFCHECK_TRUE)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flashrom initialization error"); return FALSE; } flashrom_set_log_callback (fu_plugin_flashrom_debug_cb); if (flashrom_programmer_init (&data->flashprog, "internal", NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "programmer initialization failed"); return FALSE; } if (flashrom_flash_probe (&data->flashctx, data->flashprog, NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash probe failed"); return FALSE; } data->flash_size = flashrom_flash_getsize (data->flashctx); if (data->flash_size == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash size zero"); return FALSE; } /* add devices */ for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index (devices, i); fu_plugin_device_add (plugin, dev); fu_plugin_cache_add (plugin, fu_device_get_id (dev), dev); } return TRUE; } gboolean fu_plugin_update_prepare (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *device, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autofree gchar *firmware_orig = NULL; g_autofree gchar *basename = NULL; /* not us */ if (fu_plugin_cache_lookup (plugin, fu_device_get_id (device)) == NULL) return TRUE; /* if the original firmware doesn't exist, grab it now */ basename = g_strdup_printf ("flashrom-%s.bin", fu_device_get_id (device)); firmware_orig = g_build_filename (LOCALSTATEDIR, "lib", "fwupd", "builder", basename, NULL); if (!fu_common_mkdir_parent (firmware_orig, error)) return FALSE; if (!g_file_test (firmware_orig, G_FILE_TEST_EXISTS)) { g_autofree guint8 *newcontents = g_malloc0 (data->flash_size); g_autoptr(GBytes) buf = NULL; fu_device_set_status (device, FWUPD_STATUS_DEVICE_READ); if (flashrom_image_read (data->flashctx, newcontents, data->flash_size)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to back up original firmware"); return FALSE; } buf = g_bytes_new_static (newcontents, data->flash_size); if (!fu_common_set_contents_bytes (firmware_orig, buf, error)) return FALSE; } return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); gsize sz = 0; gint rc; const guint8 *buf = g_bytes_get_data (blob_fw, &sz); if (flashrom_layout_read_from_ifd (&data->layout, data->flashctx, NULL, 0)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read layout from Intel ICH descriptor"); return FALSE; } /* include bios region for safety reasons */ if (flashrom_layout_include_region (data->layout, "bios")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid region name"); return FALSE; } /* write region */ flashrom_layout_set (data->flashctx, data->layout); if (sz != data->flash_size) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid image size 0x%x, expected 0x%x", (guint) sz, (guint) data->flash_size); return FALSE; } fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); rc = flashrom_image_write (data->flashctx, (void *) buf, sz, NULL /* refbuffer */); if (rc != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "image write failed, err=%i", rc); return FALSE; } fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY); if (flashrom_image_verify (data->flashctx, (void *) buf, sz)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "image verify failed"); return FALSE; } /* success */ return TRUE; } fwupd-1.2.14/plugins/flashrom/meson.build000066400000000000000000000011501402665037500203670ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginFlashrom"'] install_data(['flashrom.quirk'], install_dir: join_paths(get_option('datadir'), 'fwupd', 'quirks.d') ) shared_module('fu_plugin_flashrom', fu_hash, sources : [ 'fu-plugin-flashrom.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], dependencies : [ plugin_deps, libflashrom, ], ) fwupd-1.2.14/plugins/meson.build000066400000000000000000000017321402665037500165620ustar00rootroot00000000000000subdir('ata') subdir('dfu') subdir('colorhug') subdir('ebitdo') subdir('fastboot') subdir('steelseries') subdir('dell-dock') subdir('nitrokey') subdir('rts54hid') subdir('rts54hub') subdir('synaptics-prometheus') subdir('test') subdir('udev') subdir('unifying') subdir('upower') subdir('wacom-raw') subdir('wacom-usb') subdir('superio') # depends on dfu subdir('csr') if get_option('plugin_nvme') subdir('nvme') endif if get_option('plugin_modem_manager') subdir('modem-manager') endif if get_option('plugin_altos') subdir('altos') endif if get_option('plugin_amt') subdir('amt') endif if get_option('plugin_thunderbolt') subdir('thunderbolt') subdir('thunderbolt-power') endif if get_option('plugin_redfish') subdir('redfish') endif if get_option('plugin_dell') subdir('dell') subdir('dell-esrt') endif if get_option('plugin_synaptics') subdir('synapticsmst') endif if get_option('plugin_uefi') subdir('uefi') endif if get_option('plugin_flashrom') subdir('flashrom') endif fwupd-1.2.14/plugins/modem-manager/000077500000000000000000000000001402665037500171265ustar00rootroot00000000000000fwupd-1.2.14/plugins/modem-manager/README.md000066400000000000000000000025451402665037500204130ustar00rootroot00000000000000ModemManager ============ Introduction ------------ This plugin adds support for devices managed by ModemManager. GUID Generation --------------- These device use the ModemManager "Firmware Device IDs" as the GUID, e.g. * `USB\VID_413C&PID_81D7&REV_0318&CARRIER_VODAFONE` * `USB\VID_413C&PID_81D7&REV_0318` * `USB\VID_413C&PID_81D7` * `USB\VID_413C` Update method: fastboot ----------------------- If the device supports the 'fastboot' update method, it must also report which AT command should be used to trigger the modem reboot into fastboot mode. Once the device is in fastboot mode, the firmware upgrade process will happen as defined e.g. in the 'flashfile.xml' file. Every file included in the CAB that is not listed in the associated 'flashfile.xml' will be totally ignored during the fastboot upgrade procedure. Update method: qmi-pdc ---------------------- If the device supports the 'qmi-pdc' update method, the contents of the CAB file should include files named as 'mcfg.*.mbn' which will be treated as MCFG configuration files to download into the device using the Persistent Device Configuration QMI service. If a device supports both 'fastboot' and 'qmi-pdc' methods, the fastboot operation will always be run before the QMI operation, so that e.g. the full partition where the MCFG files are stored can be wiped out before installing the new ones. fwupd-1.2.14/plugins/modem-manager/fu-mm-device.c000066400000000000000000000623311402665037500215550ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-io-channel.h" #include "fu-archive.h" #include "fu-mm-device.h" #include "fu-device-private.h" #include "fu-mm-utils.h" #include "fu-qmi-pdc-updater.h" /* Amount of time for the modem to boot in fastboot mode. */ #define FU_MM_DEVICE_REMOVE_DELAY_RE_ENUMERATE 20000 /* ms */ /* Amount of time for the modem to be re-probed and exposed in MM after being * uninhibited. The timeout is long enough to cover the worst case, where the * modem boots without SIM card inserted (and therefore the initialization * may be very slow) and also where carrier config switching is explicitly * required (e.g. if switching from the default (DF) to generic (GC).*/ #define FU_MM_DEVICE_REMOVE_DELAY_REPROBE 120000 /* ms */ struct _FuMmDevice { FuDevice parent_instance; MMManager *manager; /* ModemManager-based devices will have MMObject and inhibition_uid set, * udev-based ones won't (as device is already inhibited) */ MMObject *omodem; gchar *inhibition_uid; /* Properties read from the ModemManager-exposed modem, and to be * propagated to plain udev-exposed modem objects. We assume that * the firmware upgrade operation doesn't change the USB layout, and * therefore the USB interface of the modem device that was an * AT-capable TTY is assumed to be the same one after the upgrade. */ MMModemFirmwareUpdateMethod update_methods; gchar *detach_fastboot_at; gint port_at_ifnum; /* fastboot detach handling */ gchar *port_at; FuIOChannel *io_channel; /* qmi-pdc update logic */ gchar *port_qmi; FuQmiPdcUpdater *qmi_pdc_updater; GArray *qmi_pdc_active_id; guint attach_idle; }; enum { SIGNAL_ATTACH_FINISHED, SIGNAL_LAST }; static guint signals [SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE (FuMmDevice, fu_mm_device, FU_TYPE_DEVICE) static void fu_mm_device_to_string (FuDevice *device, GString *str) { FuMmDevice *self = FU_MM_DEVICE (device); g_string_append (str, " FuMmDevice:\n"); if (self->port_at != NULL) { g_string_append_printf (str, " at-port:\t\t\t%s\n", self->port_at); } if (self->port_qmi != NULL) { g_string_append_printf (str, " qmi-port:\t\t\t%s\n", self->port_qmi); } } const gchar * fu_mm_device_get_inhibition_uid (FuMmDevice *device) { g_return_val_if_fail (FU_IS_MM_DEVICE (device), NULL); return device->inhibition_uid; } MMModemFirmwareUpdateMethod fu_mm_device_get_update_methods (FuMmDevice *device) { g_return_val_if_fail (FU_IS_MM_DEVICE (device), MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE); return device->update_methods; } const gchar * fu_mm_device_get_detach_fastboot_at (FuMmDevice *device) { g_return_val_if_fail (FU_IS_MM_DEVICE (device), NULL); return device->detach_fastboot_at; } gint fu_mm_device_get_port_at_ifnum (FuMmDevice *device) { g_return_val_if_fail (FU_IS_MM_DEVICE (device), -1); return device->port_at_ifnum; } static gboolean fu_mm_device_probe_default (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); MMModemFirmware *modem_fw; MMModem *modem = mm_object_peek_modem (self->omodem); MMModemPortInfo *ports = NULL; const gchar **device_ids; const gchar *version; guint n_ports = 0; g_autoptr(MMFirmwareUpdateSettings) update_settings = NULL; g_autofree gchar *device_sysfs_path = NULL; /* inhibition uid is the modem interface 'Device' property, which may * be the device sysfs path or a different user-provided id */ self->inhibition_uid = mm_modem_dup_device (modem); /* find out what update methods we should use */ modem_fw = mm_object_peek_modem_firmware (self->omodem); update_settings = mm_modem_firmware_get_update_settings (modem_fw); self->update_methods = mm_firmware_update_settings_get_method (update_settings); if (self->update_methods == MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem cannot be put in programming mode"); return FALSE; } /* various fastboot commands */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) { const gchar *tmp; tmp = mm_firmware_update_settings_get_fastboot_at (update_settings); if (tmp == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem does not set fastboot command"); return FALSE; } self->detach_fastboot_at = g_strdup (tmp); } /* get GUIDs */ device_ids = mm_firmware_update_settings_get_device_ids (update_settings); if (device_ids == NULL || device_ids[0] == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem did not specify any device IDs"); return FALSE; } /* get version string, which is fw_ver+config_ver */ version = mm_firmware_update_settings_get_version (update_settings); if (version == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem did not specify a firmware version"); return FALSE; } /* look for the AT and QMI/MBIM ports */ if (!mm_modem_get_ports (modem, &ports, &n_ports)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get port information"); return FALSE; } if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) { for (guint i = 0; i < n_ports; i++) { if (ports[i].type == MM_MODEM_PORT_TYPE_AT) { self->port_at = g_strdup_printf ("/dev/%s", ports[i].name); break; } } } if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) { for (guint i = 0; i < n_ports; i++) { if ((ports[i].type == MM_MODEM_PORT_TYPE_QMI) || (ports[i].type == MM_MODEM_PORT_TYPE_MBIM)) { self->port_qmi = g_strdup_printf ("/dev/%s", ports[i].name); break; } } } mm_modem_port_info_array_free (ports, n_ports); /* an at port is required for fastboot */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) && (self->port_at == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find AT port"); return FALSE; } /* a qmi port is required for qmi-pdc */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) && (self->port_qmi == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find QMI port"); return FALSE; } /* if we have the at port reported, get sysfs path and interface number */ if (self->port_at != NULL) { fu_mm_utils_get_port_info (self->port_at, &device_sysfs_path, &self->port_at_ifnum, NULL); } else if (self->port_qmi != NULL) { fu_mm_utils_get_port_info (self->port_qmi, &device_sysfs_path, NULL, NULL); } else { g_warn_if_reached (); } /* if no device sysfs file, error out */ if (device_sysfs_path == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find device sysfs path"); return FALSE; } /* add properties to fwupd device */ fu_device_set_physical_id (device, device_sysfs_path); if (mm_modem_get_manufacturer (modem) != NULL) fu_device_set_vendor (device, mm_modem_get_manufacturer (modem)); if (mm_modem_get_model (modem) != NULL) fu_device_set_name (device, mm_modem_get_model (modem)); fu_device_set_version (device, version, FWUPD_VERSION_FORMAT_UNKNOWN); for (guint i = 0; device_ids[i] != NULL; i++) fu_device_add_instance_id (device, device_ids[i]); /* convert the instance IDs to GUIDs */ fu_device_convert_instance_ids (device); return TRUE; } static gboolean fu_mm_device_probe_udev (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); /* an at port is required for fastboot */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) && (self->port_at == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find AT port"); return FALSE; } /* a qmi port is required for qmi-pdc */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) && (self->port_qmi == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find QMI port"); return FALSE; } return TRUE; } static gboolean fu_mm_device_probe (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); if (self->omodem) { return fu_mm_device_probe_default (device, error); } else { return fu_mm_device_probe_udev (device, error); } } static gboolean fu_mm_device_at_cmd (FuMmDevice *self, const gchar *cmd, GError **error) { const gchar *buf; gsize bufsz = 0; g_autoptr(GBytes) at_req = NULL; g_autoptr(GBytes) at_res = NULL; g_autofree gchar *cmd_cr = g_strdup_printf ("%s\r\n", cmd); /* command */ at_req = g_bytes_new (cmd_cr, strlen (cmd_cr)); if (g_getenv ("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) fu_common_dump_bytes (G_LOG_DOMAIN, "writing", at_req); if (!fu_io_channel_write_bytes (self->io_channel, at_req, 1500, FU_IO_CHANNEL_FLAG_FLUSH_INPUT, error)) { g_prefix_error (error, "failed to write %s: ", cmd); return FALSE; } /* response */ at_res = fu_io_channel_read_bytes (self->io_channel, -1, 1500, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error); if (at_res == NULL) { g_prefix_error (error, "failed to read response for %s: ", cmd); return FALSE; } if (g_getenv ("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) fu_common_dump_bytes (G_LOG_DOMAIN, "read", at_res); buf = g_bytes_get_data (at_res, &bufsz); if (bufsz < 6) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read valid response for %s", cmd); return FALSE; } if (memcmp (buf, "\r\nOK\r\n", 6) != 0) { g_autofree gchar *tmp = g_strndup (buf + 2, bufsz - 4); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read valid response for %s: %s", cmd, tmp); return FALSE; } return TRUE; } static gboolean fu_mm_device_io_open (FuMmDevice *self, GError **error) { /* open device */ self->io_channel = fu_io_channel_new_file (self->port_at, error); if (self->io_channel == NULL) return FALSE; /* success */ return TRUE; } static gboolean fu_mm_device_io_close (FuMmDevice *self, GError **error) { if (!fu_io_channel_shutdown (self->io_channel, error)) return FALSE; g_clear_object (&self->io_channel); return TRUE; } static gboolean fu_mm_device_detach_fastboot (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; /* boot to fastboot mode */ locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) fu_mm_device_io_open, (FuDeviceLockerFunc) fu_mm_device_io_close, error); if (locker == NULL) return FALSE; if (!fu_mm_device_at_cmd (self, "AT", error)) return FALSE; if (!fu_mm_device_at_cmd (self, self->detach_fastboot_at, error)) { g_prefix_error (error, "rebooting into fastboot not supported: "); return FALSE; } /* success */ fu_device_set_remove_delay (device, FU_MM_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_mm_device_detach (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* This plugin supports currently two methods to download firmware: * fastboot and qmi-pdc. A modem may require one of those, or both, * depending on the update type or the modem type. * * The first time this detach() method is executed is always for a * FuMmDevice that was created from a MM-exposed modem, which is the * moment when we're going to decide the amount of retries we need to * flash all firmware. * * If the FuMmModem is created from a MM-exposed modem and... * a) we only support fastboot, we just trigger the fastboot detach. * b) we only support qmi-pdc, we just exit without any detach. * c) we support both fastboot and qmi-pdc, we will set the * ANOTHER_WRITE_REQUIRED flag in the device and we'll trigger * the fastboot detach. * * If the FuMmModem is created from udev events... * d) it means we're in the extra required write that was flagged * in an earlier detach(), and we need to perform the qmi-pdc * update procedure at this time, so we just exit without any * detach. */ /* FuMmDevice created from MM... */ if (self->omodem != NULL) { /* both fastboot and qmi-pdc supported? another write required */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) && (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC)) { g_debug ("both fastboot and qmi-pdc supported, so the upgrade requires another write"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); } /* fastboot */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) return fu_mm_device_detach_fastboot (device, error); /* otherwise, assume we don't need any detach */ return TRUE; } /* FuMmDevice created from udev... * assume we don't need any detach */ return TRUE; } typedef struct { gchar *filename; GBytes *bytes; GArray *digest; gboolean active; } FuMmFileInfo; static void fu_mm_file_info_free (FuMmFileInfo *file_info) { g_clear_pointer (&file_info->digest, g_array_unref); g_free (file_info->filename); g_bytes_unref (file_info->bytes); g_free (file_info); } typedef struct { FuMmDevice *device; GError *error; GPtrArray *file_infos; gsize total_written; gsize total_bytes; } FuMmArchiveIterateCtx; static gboolean fu_mm_should_be_active (const gchar *version, const gchar *filename) { g_auto(GStrv) split = NULL; g_autofree gchar *carrier_id = NULL; /* The filename of the mcfg file is composed of a "mcfg." prefix, then the * carrier code, followed by the carrier version, and finally a ".mbn" * prefix. Here we try to guess, based on the carrier code, whether the * specific mcfg file should be activated after the firmware upgrade * operation. * * This logic requires that the previous device version includes the carrier * code also embedded in the version string. E.g. "xxxx.VF.xxxx". If we find * this match, we assume this is the active config to use. */ split = g_strsplit (filename, ".", -1); if (g_strv_length (split) < 4) return FALSE; if (g_strcmp0 (split[0], "mcfg") != 0) return FALSE; carrier_id = g_strdup_printf (".%s.", split[1]); return (g_strstr_len (version, -1, carrier_id) != NULL); } static void fu_mm_qmi_pdc_archive_iterate_mcfg (FuArchive *archive, const gchar *filename, GBytes *bytes, gpointer user_data) { FuMmArchiveIterateCtx *ctx = user_data; FuMmFileInfo *file_info; /* filenames should be named as 'mcfg.*.mbn', e.g.: mcfg.A2.018.mbn */ if (!g_str_has_prefix (filename, "mcfg.") || !g_str_has_suffix (filename, ".mbn")) return; file_info = g_new0 (FuMmFileInfo, 1); file_info->filename = g_strdup (filename); file_info->bytes = g_bytes_ref (bytes); file_info->active = fu_mm_should_be_active (fu_device_get_version (FU_DEVICE (ctx->device)), filename); g_ptr_array_add (ctx->file_infos, file_info); ctx->total_bytes += g_bytes_get_size (file_info->bytes); } static gboolean fu_mm_device_qmi_open (FuMmDevice *self, GError **error) { self->qmi_pdc_updater = fu_qmi_pdc_updater_new (self->port_qmi); return fu_qmi_pdc_updater_open (self->qmi_pdc_updater, error); } static gboolean fu_mm_device_qmi_close (FuMmDevice *self, GError **error) { g_autoptr(FuQmiPdcUpdater) updater = NULL; updater = g_steal_pointer (&self->qmi_pdc_updater); return fu_qmi_pdc_updater_close (updater, error); } static gboolean fu_mm_device_qmi_close_no_error (FuMmDevice *self, GError **error) { g_autoptr(FuQmiPdcUpdater) updater = NULL; updater = g_steal_pointer (&self->qmi_pdc_updater); fu_qmi_pdc_updater_close (updater, NULL); return TRUE; } static gboolean fu_mm_device_write_firmware_qmi_pdc (FuDevice *device, GBytes *fw, GArray **active_id, GError **error) { g_autoptr(FuArchive) archive = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GPtrArray) file_infos = g_ptr_array_new_with_free_func ((GDestroyNotify)fu_mm_file_info_free); gint active_i = -1; FuMmArchiveIterateCtx archive_context = { .device = FU_MM_DEVICE (device), .error = NULL, .file_infos = file_infos, .total_written = 0, .total_bytes = 0, }; /* decompress entire archive ahead of time */ archive = fu_archive_new (fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; /* boot to fastboot mode */ locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) fu_mm_device_qmi_open, (FuDeviceLockerFunc) fu_mm_device_qmi_close, error); if (locker == NULL) return FALSE; /* process the list of MCFG files to write */ fu_archive_iterate (archive, fu_mm_qmi_pdc_archive_iterate_mcfg, &archive_context); for (guint i = 0; i < file_infos->len; i++) { FuMmFileInfo *file_info = g_ptr_array_index (file_infos, i); file_info->digest = fu_qmi_pdc_updater_write (archive_context.device->qmi_pdc_updater, file_info->filename, file_info->bytes, &archive_context.error); if (file_info->digest == NULL) { g_prefix_error (&archive_context.error, "Failed to write file '%s':", file_info->filename); break; } /* if we wrongly detect more than one, just assume the latest one; this * is not critical, it may just take a bit more time to perform the * automatic carrier config switching in ModemManager */ if (file_info->active) active_i = i; } /* set expected active configuration */ if (active_i >= 0 && active_id != NULL) { FuMmFileInfo *file_info = g_ptr_array_index (file_infos, active_i); *active_id = g_array_ref (file_info->digest); } if (archive_context.error != NULL) { g_propagate_error (error, archive_context.error); return FALSE; } return TRUE; } static gboolean fu_mm_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(GPtrArray) array = NULL; /* lock device */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* qmi pdc write operation */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) return fu_mm_device_write_firmware_qmi_pdc (device, fw, &self->qmi_pdc_active_id, error); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported update method"); return FALSE; } static gboolean fu_mm_device_attach_qmi_pdc (FuMmDevice *self, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* ignore action if there is no active id specified */ if (self->qmi_pdc_active_id == NULL) return TRUE; /* errors closing may be expected if the device really reboots itself */ locker = fu_device_locker_new_full (self, (FuDeviceLockerFunc) fu_mm_device_qmi_open, (FuDeviceLockerFunc) fu_mm_device_qmi_close_no_error, error); if (locker == NULL) return FALSE; if (!fu_qmi_pdc_updater_activate (self->qmi_pdc_updater, self->qmi_pdc_active_id, error)) return FALSE; return TRUE; } static gboolean fu_mm_device_attach_noop_idle (gpointer user_data) { FuMmDevice *self = FU_MM_DEVICE (user_data); self->attach_idle = 0; g_signal_emit (self, signals [SIGNAL_ATTACH_FINISHED], 0); return G_SOURCE_REMOVE; } static gboolean fu_mm_device_attach_qmi_pdc_idle (gpointer user_data) { FuMmDevice *self = FU_MM_DEVICE (user_data); g_autoptr(GError) error = NULL; if (!fu_mm_device_attach_qmi_pdc (self, &error)) g_warning ("qmi-pdc attach operation failed: %s", error->message); else g_debug ("qmi-pdc attach operation successful"); self->attach_idle = 0; g_signal_emit (self, signals [SIGNAL_ATTACH_FINISHED], 0); return G_SOURCE_REMOVE; } static gboolean fu_mm_device_attach (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; /* lock device */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* we want this attach operation to be triggered asynchronously, because the engine * must learn that it has to wait for replug before we actually trigger the reset. */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) self->attach_idle = g_idle_add ((GSourceFunc) fu_mm_device_attach_qmi_pdc_idle, self); else self->attach_idle = g_idle_add ((GSourceFunc) fu_mm_device_attach_noop_idle, self); /* wait for re-probing after uninhibiting */ fu_device_set_remove_delay (device, FU_MM_DEVICE_REMOVE_DELAY_REPROBE); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_mm_device_init (FuMmDevice *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION); fu_device_set_summary (FU_DEVICE (self), "Mobile broadband device"); fu_device_add_icon (FU_DEVICE (self), "network-modem"); } static void fu_mm_device_finalize (GObject *object) { FuMmDevice *self = FU_MM_DEVICE (object); if (self->attach_idle) g_source_remove (self->attach_idle); if (self->qmi_pdc_active_id) g_array_unref (self->qmi_pdc_active_id); g_object_unref (self->manager); if (self->omodem != NULL) g_object_unref (self->omodem); g_free (self->detach_fastboot_at); g_free (self->port_at); g_free (self->port_qmi); g_free (self->inhibition_uid); G_OBJECT_CLASS (fu_mm_device_parent_class)->finalize (object); } static void fu_mm_device_class_init (FuMmDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); object_class->finalize = fu_mm_device_finalize; klass_device->to_string = fu_mm_device_to_string; klass_device->probe = fu_mm_device_probe; klass_device->detach = fu_mm_device_detach; klass_device->write_firmware = fu_mm_device_write_firmware; klass_device->attach = fu_mm_device_attach; signals [SIGNAL_ATTACH_FINISHED] = g_signal_new ("attach-finished", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } FuMmDevice * fu_mm_device_new (MMManager *manager, MMObject *omodem) { FuMmDevice *self = g_object_new (FU_TYPE_MM_DEVICE, NULL); self->manager = g_object_ref (manager); self->omodem = g_object_ref (omodem); self->port_at_ifnum = -1; return self; } FuPluginMmInhibitedDeviceInfo * fu_plugin_mm_inhibited_device_info_new (FuMmDevice *device) { FuPluginMmInhibitedDeviceInfo *info; info = g_new0 (FuPluginMmInhibitedDeviceInfo, 1); info->physical_id = g_strdup (fu_device_get_physical_id (FU_DEVICE (device))); info->vendor = g_strdup (fu_device_get_vendor (FU_DEVICE (device))); info->name = g_strdup (fu_device_get_name (FU_DEVICE (device))); info->version = g_strdup (fu_device_get_version (FU_DEVICE (device))); info->guids = fu_device_get_guids (FU_DEVICE (device)); info->update_methods = fu_mm_device_get_update_methods (device); info->detach_fastboot_at = g_strdup (fu_mm_device_get_detach_fastboot_at (device)); info->port_at_ifnum = fu_mm_device_get_port_at_ifnum (device); info->inhibited_uid = g_strdup (fu_mm_device_get_inhibition_uid (device)); return info; } void fu_plugin_mm_inhibited_device_info_free (FuPluginMmInhibitedDeviceInfo *info) { g_free (info->inhibited_uid); g_free (info->physical_id); g_free (info->vendor); g_free (info->name); g_free (info->version); if (info->guids) g_ptr_array_unref (info->guids); g_free (info->detach_fastboot_at); g_free (info); } FuMmDevice * fu_mm_device_udev_new (MMManager *manager, FuPluginMmInhibitedDeviceInfo *info) { FuMmDevice *self = g_object_new (FU_TYPE_MM_DEVICE, NULL); g_debug ("creating udev-based mm device at %s", info->physical_id); self->manager = g_object_ref (manager); fu_device_set_physical_id (FU_DEVICE (self), info->physical_id); fu_device_set_vendor (FU_DEVICE (self), info->vendor); fu_device_set_name (FU_DEVICE (self), info->name); fu_device_set_version (FU_DEVICE (self), info->version, FWUPD_VERSION_FORMAT_UNKNOWN); self->update_methods = info->update_methods; self->detach_fastboot_at = g_strdup (info->detach_fastboot_at); self->port_at_ifnum = info->port_at_ifnum; for (guint i = 0; i < info->guids->len; i++) fu_device_add_guid (FU_DEVICE (self), g_ptr_array_index (info->guids, i)); return self; } void fu_mm_device_udev_add_port (FuMmDevice *self, const gchar *subsystem, const gchar *path, gint ifnum) { g_return_if_fail (FU_IS_MM_DEVICE (self)); /* cdc-wdm ports always added unless one already set */ if (g_str_equal (subsystem, "usbmisc") && (self->port_qmi == NULL)) { g_debug ("added QMI port %s (%s)", path, subsystem); self->port_qmi = g_strdup (path); return; } if (g_str_equal (subsystem, "tty") && (self->port_at == NULL) && (ifnum >= 0) && (ifnum == self->port_at_ifnum)) { g_debug ("added AT port %s (%s)", path, subsystem); self->port_at = g_strdup (path); return; } /* otherwise, ignore all other ports */ g_debug ("ignoring port %s (%s)", path, subsystem); } fwupd-1.2.14/plugins/modem-manager/fu-mm-device.h000066400000000000000000000033221402665037500215550ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #ifndef __FU_MM_DEVICE_H #define __FU_MM_DEVICE_H #include #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_MM_DEVICE (fu_mm_device_get_type ()) G_DECLARE_FINAL_TYPE (FuMmDevice, fu_mm_device, FU, MM_DEVICE, FuDevice) FuMmDevice *fu_mm_device_new (MMManager *manager, MMObject *omodem); const gchar *fu_mm_device_get_inhibition_uid (FuMmDevice *device); const gchar *fu_mm_device_get_detach_fastboot_at (FuMmDevice *device); gint fu_mm_device_get_port_at_ifnum (FuMmDevice *device); MMModemFirmwareUpdateMethod fu_mm_device_get_update_methods (FuMmDevice *device); /* support for udev-based devices */ typedef struct FuPluginMmInhibitedDeviceInfo FuPluginMmInhibitedDeviceInfo; struct FuPluginMmInhibitedDeviceInfo { gchar *inhibited_uid; gchar *physical_id; gchar *vendor; gchar *name; gchar *version; GPtrArray *guids; MMModemFirmwareUpdateMethod update_methods; gchar *detach_fastboot_at; gint port_at_ifnum; }; FuPluginMmInhibitedDeviceInfo *fu_plugin_mm_inhibited_device_info_new (FuMmDevice *device); void fu_plugin_mm_inhibited_device_info_free (FuPluginMmInhibitedDeviceInfo *info); G_DEFINE_AUTOPTR_CLEANUP_FUNC (FuPluginMmInhibitedDeviceInfo, fu_plugin_mm_inhibited_device_info_free); FuMmDevice *fu_mm_device_udev_new (MMManager *manager, FuPluginMmInhibitedDeviceInfo *info); void fu_mm_device_udev_add_port (FuMmDevice *device, const gchar *subsystem, const gchar *path, gint ifnum); G_END_DECLS #endif /* __FU_MM_DEVICE_H */ fwupd-1.2.14/plugins/modem-manager/fu-mm-utils.c000066400000000000000000000042251402665037500214540ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-mm-utils.h" gboolean fu_mm_utils_get_udev_port_info (GUdevDevice *device, gchar **out_device_sysfs_path, gint *out_port_ifnum, GError **error) { gint port_ifnum = -1; const gchar *aux; g_autoptr(GUdevDevice) parent = NULL; g_autofree gchar *device_sysfs_path = NULL; /* ID_USB_INTERFACE_NUM is set on the port device itself */ aux = g_udev_device_get_property (device, "ID_USB_INTERFACE_NUM"); if (aux != NULL) port_ifnum = (guint16) g_ascii_strtoull (aux, NULL, 16); /* we need to traverse all parents of the give udev device until we find * the first 'usb_device' reported, which is the GUdevDevice associated with * the full USB device (i.e. all ports of the same device). */ parent = g_udev_device_get_parent (device); while (parent != NULL) { g_autoptr(GUdevDevice) next = NULL; if (g_strcmp0 (g_udev_device_get_devtype (parent), "usb_device") == 0) { device_sysfs_path = g_strdup (g_udev_device_get_sysfs_path (parent)); break; } /* check next parent */ next = g_udev_device_get_parent (parent); g_set_object (&parent, next); } if (parent == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to lookup device info: parent usb_device not found"); return FALSE; } if (out_port_ifnum != NULL) *out_port_ifnum = port_ifnum; if (out_device_sysfs_path != NULL) *out_device_sysfs_path = g_steal_pointer (&device_sysfs_path); return TRUE; } gboolean fu_mm_utils_get_port_info (const gchar *path, gchar **out_device_sysfs_path, gint *out_port_ifnum, GError **error) { g_autoptr(GUdevClient) client = NULL; g_autoptr(GUdevDevice) dev = NULL; client = g_udev_client_new (NULL); dev = g_udev_client_query_by_device_file (client, path); if (dev == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to lookup device by path"); return FALSE; } return fu_mm_utils_get_udev_port_info (dev, out_device_sysfs_path, out_port_ifnum, error); } fwupd-1.2.14/plugins/modem-manager/fu-mm-utils.h000066400000000000000000000014651402665037500214640ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #ifndef __FU_MM_UTILS_H #define __FU_MM_UTILS_H #include "config.h" #include G_BEGIN_DECLS #ifndef HAVE_GUDEV_232 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevClient, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref) #pragma clang diagnostic pop #endif gboolean fu_mm_utils_get_udev_port_info (GUdevDevice *dev, gchar **device_sysfs_path, gint *port_ifnum, GError **error); gboolean fu_mm_utils_get_port_info (const gchar *path, gchar **device_sysfs_path, gint *port_ifnum, GError **error); G_END_DECLS #endif /* __FU_MM_UTILS_H */ fwupd-1.2.14/plugins/modem-manager/fu-plugin-modem-manager.c000066400000000000000000000306061402665037500237140ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-plugin-vfuncs.h" #include "fu-mm-device.h" #include "fu-mm-utils.h" /* amount of time to wait for ports of the same device being exposed by kernel */ #define FU_MM_UDEV_DEVICE_PORTS_TIMEOUT 3 /* s */ typedef struct FuPluginMmInhibitedDeviceInfo FuPluginMmInhibitedDeviceInfo; struct FuPluginData { MMManager *manager; gboolean manager_ready; GUdevClient *udev_client; guint udev_timeout_id; /* when a device is inhibited from MM, we store all relevant details * ourselves to recreate a functional device object even without MM */ FuPluginMmInhibitedDeviceInfo *inhibited; }; static void fu_plugin_mm_udev_device_removed (FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data (plugin); FuMmDevice *dev; if (priv->inhibited == NULL) return; dev = fu_plugin_cache_lookup (plugin, priv->inhibited->physical_id); if (dev == NULL) return; /* once the first port is gone, consider device is gone */ fu_plugin_cache_remove (plugin, priv->inhibited->physical_id); fu_plugin_device_remove (plugin, FU_DEVICE (dev)); /* no need to wait for more ports, cancel that right away */ if (priv->udev_timeout_id != 0) { g_source_remove (priv->udev_timeout_id); priv->udev_timeout_id = 0; } } static void fu_plugin_mm_uninhibit_device (FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data (plugin); g_autoptr(FuPluginMmInhibitedDeviceInfo) info = NULL; /* get the device removed from the plugin cache before uninhibiting */ fu_plugin_mm_udev_device_removed (plugin); info = g_steal_pointer (&priv->inhibited); if ((priv->manager != NULL) && (info != NULL)) { g_debug ("uninhibit modemmanager device with uid %s", info->inhibited_uid); mm_manager_uninhibit_device_sync (priv->manager, info->inhibited_uid, NULL, NULL); } } static gboolean fu_plugin_mm_udev_device_ports_timeout (gpointer user_data) { FuPlugin *plugin = user_data; FuPluginData *priv = fu_plugin_get_data (plugin); FuMmDevice *dev; g_autoptr(GError) error = NULL; g_return_val_if_fail (priv->inhibited != NULL, G_SOURCE_REMOVE); priv->udev_timeout_id = 0; dev = fu_plugin_cache_lookup (plugin, priv->inhibited->physical_id); if (dev != NULL) { if (!fu_device_probe (FU_DEVICE (dev), &error)) { g_warning ("failed to probe MM device: %s", error->message); } else { fu_plugin_device_add (plugin, FU_DEVICE (dev)); } } return G_SOURCE_REMOVE; } static void fu_plugin_mm_udev_device_ports_timeout_reset (FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data (plugin); g_return_if_fail (priv->inhibited != NULL); if (priv->udev_timeout_id != 0) g_source_remove (priv->udev_timeout_id); priv->udev_timeout_id = g_timeout_add_seconds (FU_MM_UDEV_DEVICE_PORTS_TIMEOUT, fu_plugin_mm_udev_device_ports_timeout, plugin); } static void fu_plugin_mm_udev_device_port_added (FuPlugin *plugin, const gchar *subsystem, const gchar *path, gint ifnum) { FuPluginData *priv = fu_plugin_get_data (plugin); FuMmDevice *existing; g_autoptr(FuMmDevice) dev = NULL; g_autoptr(GError) error = NULL; g_return_if_fail (priv->inhibited != NULL); existing = fu_plugin_cache_lookup (plugin, priv->inhibited->physical_id); if (existing != NULL) { /* add port to existing device */ fu_mm_device_udev_add_port (existing, subsystem, path, ifnum); fu_plugin_mm_udev_device_ports_timeout_reset (plugin); return; } /* create device and add to cache */ dev = fu_mm_device_udev_new (priv->manager, priv->inhibited); fu_mm_device_udev_add_port (dev, subsystem, path, ifnum); fu_plugin_cache_add (plugin, priv->inhibited->physical_id, dev); /* wait a bit before probing, in case more ports get added */ fu_plugin_mm_udev_device_ports_timeout_reset (plugin); } static gboolean fu_plugin_mm_udev_uevent_cb (GUdevClient *udev, const gchar *action, GUdevDevice *device, gpointer user_data) { FuPlugin *plugin = FU_PLUGIN (user_data); FuPluginData *priv = fu_plugin_get_data (plugin); const gchar *subsystem = g_udev_device_get_subsystem (device); const gchar *name = g_udev_device_get_name (device); g_autofree gchar *path = NULL; g_autofree gchar *device_sysfs_path = NULL; gint ifnum = -1; if (action == NULL || subsystem == NULL || priv->inhibited == NULL || name == NULL) return TRUE; /* ignore if loading port info fails */ if (!fu_mm_utils_get_udev_port_info (device, &device_sysfs_path, &ifnum, NULL)) return TRUE; /* ignore all events for ports not owned by our device */ if (g_strcmp0 (device_sysfs_path, priv->inhibited->physical_id) != 0) return TRUE; /* ignore non-cdc-wdm usbmisc ports */ if (g_str_equal (subsystem, "usbmisc") && !g_str_has_prefix (name, "cdc-wdm")) return TRUE; path = g_strdup_printf ("/dev/%s", name); if ((g_str_equal (action, "add")) || (g_str_equal (action, "change"))) { g_debug ("added port to inhibited modem: %s (ifnum %d)", path, ifnum); fu_plugin_mm_udev_device_port_added (plugin, subsystem, path, ifnum); } else if (g_str_equal (action, "remove")) { g_debug ("removed port from inhibited modem: %s", path); fu_plugin_mm_udev_device_removed (plugin); } return TRUE; } static gboolean fu_plugin_mm_inhibit_device (FuPlugin *plugin, FuDevice *device, GError **error) { static const gchar *subsystems[] = { "tty", "usbmisc", NULL }; FuPluginData *priv = fu_plugin_get_data (plugin); g_autoptr(FuPluginMmInhibitedDeviceInfo) info = NULL; fu_plugin_mm_uninhibit_device (plugin); info = fu_plugin_mm_inhibited_device_info_new (FU_MM_DEVICE (device)); g_debug ("inhibit modemmanager device with uid %s", info->inhibited_uid); if (!mm_manager_inhibit_device_sync (priv->manager, info->inhibited_uid, NULL, error)) return FALSE; /* setup inhibited device info */ priv->inhibited = g_steal_pointer (&info); /* as soon as inhibition is place, we need to do modem device monitoring based * on the udev client, as MM no longer reports devices */ priv->udev_client = g_udev_client_new (subsystems); g_signal_connect (priv->udev_client, "uevent", G_CALLBACK (fu_plugin_mm_udev_uevent_cb), plugin); return TRUE; } static void fu_plugin_mm_device_add (FuPlugin *plugin, MMObject *modem) { FuPluginData *priv = fu_plugin_get_data (plugin); const gchar *object_path = mm_object_get_path (modem); g_autoptr(FuMmDevice) dev = NULL; g_autoptr(GError) error = NULL; if (fu_plugin_cache_lookup (plugin, object_path) != NULL) { g_warning ("MM device already added, ignoring"); return; } dev = fu_mm_device_new (priv->manager, modem); if (!fu_device_probe (FU_DEVICE (dev), &error)) { g_warning ("failed to probe MM device: %s", error->message); return; } fu_plugin_device_add (plugin, FU_DEVICE (dev)); fu_plugin_cache_add (plugin, object_path, dev); } static void fu_plugin_mm_device_added_cb (MMManager *manager, MMObject *modem, FuPlugin *plugin) { fu_plugin_mm_device_add (plugin, modem); } static void fu_plugin_mm_device_removed_cb (MMManager *manager, MMObject *modem, FuPlugin *plugin) { const gchar *object_path = mm_object_get_path (modem); FuMmDevice *dev = fu_plugin_cache_lookup (plugin, object_path); if (dev == NULL) return; g_debug ("removed modem: %s", mm_object_get_path (modem)); fu_plugin_cache_remove (plugin, object_path); fu_plugin_device_remove (plugin, FU_DEVICE (dev)); } static void fu_plugin_mm_teardown_manager (FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data (plugin); if (priv->manager_ready) { g_debug ("ModemManager no longer available"); g_signal_handlers_disconnect_by_func (priv->manager, G_CALLBACK (fu_plugin_mm_device_added_cb), plugin); g_signal_handlers_disconnect_by_func (priv->manager, G_CALLBACK (fu_plugin_mm_device_removed_cb), plugin); priv->manager_ready = FALSE; } } static void fu_plugin_mm_setup_manager (FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data (plugin); const gchar *version = mm_manager_get_version (priv->manager); GList *list; if (fu_common_vercmp (version, MM_REQUIRED_VERSION) < 0) { g_warning ("ModemManager %s is available, but need at least %s", version, MM_REQUIRED_VERSION); return; } g_debug ("ModemManager %s is available", version); g_signal_connect (priv->manager, "object-added", G_CALLBACK (fu_plugin_mm_device_added_cb), plugin); g_signal_connect (priv->manager, "object-removed", G_CALLBACK (fu_plugin_mm_device_removed_cb), plugin); list = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (priv->manager)); for (GList *l = list; l != NULL; l = g_list_next (l)) { MMObject *modem = MM_OBJECT (l->data); fu_plugin_mm_device_add (plugin, modem); g_object_unref (modem); } g_list_free (list); priv->manager_ready = TRUE; } static void fu_plugin_mm_name_owner_updated (FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data (plugin); g_autofree gchar *name_owner = NULL; name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (priv->manager)); if (name_owner != NULL) fu_plugin_mm_setup_manager (plugin); else fu_plugin_mm_teardown_manager (plugin); } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { FuPluginData *priv = fu_plugin_get_data (plugin); g_signal_connect_swapped (priv->manager, "notify::name-owner", G_CALLBACK (fu_plugin_mm_name_owner_updated), plugin); fu_plugin_mm_name_owner_updated (plugin); return TRUE; } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { FuPluginData *priv = fu_plugin_get_data (plugin); g_autoptr(GDBusConnection) connection = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) return FALSE; priv->manager = mm_manager_new_sync (connection, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START, NULL, error); if (priv->manager == NULL) return FALSE; return TRUE; } void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data (plugin); fu_plugin_mm_uninhibit_device (plugin); if (priv->udev_timeout_id) g_source_remove (priv->udev_timeout_id); if (priv->udev_client) g_object_unref (priv->udev_client); if (priv->manager != NULL) g_object_unref (priv->manager); } gboolean fu_plugin_update_detach (FuPlugin *plugin, FuDevice *device, GError **error) { FuPluginData *priv = fu_plugin_get_data (plugin); g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* inhibit device and track it inside the plugin, not bound to the * lifetime of the FuMmDevice, because that object will only exist for * as long as the ModemManager device exists, and inhibiting will * implicitly remove the device from ModemManager. */ if (priv->inhibited == NULL) { if (!fu_plugin_mm_inhibit_device (plugin, device, error)) return FALSE; } /* reset */ if (!fu_device_detach (device, error)) { fu_plugin_mm_uninhibit_device (plugin); return FALSE; } /* note: wait for replug set by device if it really needs it */ return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } static void fu_plugin_mm_device_attach_finished (gpointer user_data) { FuPlugin *plugin = FU_PLUGIN (user_data); fu_plugin_mm_uninhibit_device (plugin); } gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* schedule device attach asynchronously, which is extremely important * so that engine can setup the device "waiting" logic before the actual * attach procedure happens (which will reset the module if it worked * properly) */ if (!fu_device_attach (device, error)) return FALSE; /* this signal will always be emitted asynchronously */ g_signal_connect_swapped (device, "attach-finished", G_CALLBACK (fu_plugin_mm_device_attach_finished), plugin); return TRUE; } fwupd-1.2.14/plugins/modem-manager/fu-qmi-pdc-updater.c000066400000000000000000000506321402665037500227040ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-qmi-pdc-updater.h" #define FU_QMI_PDC_MAX_OPEN_ATTEMPTS 8 struct _FuQmiPdcUpdater { GObject parent_instance; gchar *qmi_port; QmiDevice *qmi_device; QmiClientPdc *qmi_client; }; G_DEFINE_TYPE (FuQmiPdcUpdater, fu_qmi_pdc_updater, G_TYPE_OBJECT) typedef struct { GMainLoop *mainloop; QmiDevice *qmi_device; QmiClientPdc *qmi_client; GError *error; guint open_attempts; } OpenContext; static void fu_qmi_pdc_updater_qmi_device_open_attempt (OpenContext *ctx); static void fu_qmi_pdc_updater_qmi_device_open_abort_ready (GObject *qmi_device, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *) user_data; g_warn_if_fail (ctx->error != NULL); /* ignore errors when aborting open */ qmi_device_close_finish (QMI_DEVICE (qmi_device), res, NULL); ctx->open_attempts--; if (ctx->open_attempts == 0) { g_clear_object (&ctx->qmi_client); g_clear_object (&ctx->qmi_device); g_main_loop_quit (ctx->mainloop); return; } /* retry */ g_clear_error (&ctx->error); fu_qmi_pdc_updater_qmi_device_open_attempt (ctx); } static void fu_qmi_pdc_updater_open_abort (OpenContext *ctx) { qmi_device_close_async (ctx->qmi_device, 15, NULL, fu_qmi_pdc_updater_qmi_device_open_abort_ready, ctx); } static void fu_qmi_pdc_updater_qmi_device_allocate_client_ready (GObject *qmi_device, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *) user_data; ctx->qmi_client = QMI_CLIENT_PDC (qmi_device_allocate_client_finish (QMI_DEVICE (qmi_device), res, &ctx->error)); if (ctx->qmi_client == NULL) { fu_qmi_pdc_updater_open_abort (ctx); return; } g_main_loop_quit (ctx->mainloop); } static void fu_qmi_pdc_updater_qmi_device_open_ready (GObject *qmi_device, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *) user_data; if (!qmi_device_open_finish (QMI_DEVICE (qmi_device), res, &ctx->error)) { fu_qmi_pdc_updater_open_abort (ctx); return; } qmi_device_allocate_client (ctx->qmi_device, QMI_SERVICE_PDC, QMI_CID_NONE, 5, NULL, fu_qmi_pdc_updater_qmi_device_allocate_client_ready, ctx); } static void fu_qmi_pdc_updater_qmi_device_open_attempt (OpenContext *ctx) { QmiDeviceOpenFlags open_flags = QMI_DEVICE_OPEN_FLAGS_NONE; /* automatically detect QMI and MBIM ports */ open_flags |= QMI_DEVICE_OPEN_FLAGS_AUTO; /* qmi pdc requires indications, so enable them by default */ open_flags |= QMI_DEVICE_OPEN_FLAGS_EXPECT_INDICATIONS; /* all communication through the proxy */ open_flags |= QMI_DEVICE_OPEN_FLAGS_PROXY; g_debug ("trying to open QMI device..."); qmi_device_open (ctx->qmi_device, open_flags, 5, NULL, fu_qmi_pdc_updater_qmi_device_open_ready, ctx); } static void fu_qmi_pdc_updater_qmi_device_new_ready (GObject *source, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *) user_data; ctx->qmi_device = qmi_device_new_finish (res, &ctx->error); if (ctx->qmi_device == NULL) { g_main_loop_quit (ctx->mainloop); return; } fu_qmi_pdc_updater_qmi_device_open_attempt (ctx); } gboolean fu_qmi_pdc_updater_open (FuQmiPdcUpdater *self, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); g_autoptr(GFile) qmi_device_file = g_file_new_for_path (self->qmi_port); OpenContext ctx = { .mainloop = mainloop, .qmi_device = NULL, .qmi_client = NULL, .error = NULL, .open_attempts = FU_QMI_PDC_MAX_OPEN_ATTEMPTS, }; qmi_device_new (qmi_device_file, NULL, fu_qmi_pdc_updater_qmi_device_new_ready, &ctx); g_main_loop_run (mainloop); /* either we have all device, client and config list set, or otherwise error is set */ if ((ctx.qmi_device != NULL) && (ctx.qmi_client != NULL)) { g_warn_if_fail (!ctx.error); self->qmi_device = ctx.qmi_device; self->qmi_client = ctx.qmi_client; /* success */ return TRUE; } g_warn_if_fail (ctx.error != NULL); g_warn_if_fail (ctx.qmi_device == NULL); g_warn_if_fail (ctx.qmi_client == NULL); g_propagate_error (error, ctx.error); return FALSE; } typedef struct { GMainLoop *mainloop; QmiDevice *qmi_device; QmiClientPdc *qmi_client; GError *error; } CloseContext; static void fu_qmi_pdc_updater_qmi_device_close_ready (GObject *qmi_device, GAsyncResult *res, gpointer user_data) { CloseContext *ctx = (CloseContext *) user_data; /* ignore errors when closing if we had one already set when releasing client */ qmi_device_close_finish (QMI_DEVICE (qmi_device), res, (ctx->error == NULL) ? &ctx->error : NULL); g_clear_object (&ctx->qmi_device); g_main_loop_quit (ctx->mainloop); } static void fu_qmi_pdc_updater_qmi_device_release_client_ready (GObject *qmi_device, GAsyncResult *res, gpointer user_data) { CloseContext *ctx = (CloseContext *) user_data; qmi_device_release_client_finish (QMI_DEVICE (qmi_device), res, &ctx->error); g_clear_object (&ctx->qmi_client); qmi_device_close_async (ctx->qmi_device, 15, NULL, fu_qmi_pdc_updater_qmi_device_close_ready, ctx); } gboolean fu_qmi_pdc_updater_close (FuQmiPdcUpdater *self, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); CloseContext ctx = { .mainloop = mainloop, .qmi_device = g_steal_pointer (&self->qmi_device), .qmi_client = g_steal_pointer (&self->qmi_client), }; qmi_device_release_client (ctx.qmi_device, QMI_CLIENT (ctx.qmi_client), QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID, 5, NULL, fu_qmi_pdc_updater_qmi_device_release_client_ready, &ctx); g_main_loop_run (mainloop); /* we should always have both device and client cleared, and optionally error set */ g_warn_if_fail (ctx.qmi_device == NULL); g_warn_if_fail (ctx.qmi_client == NULL); if (ctx.error != NULL) { g_propagate_error (error, ctx.error); return FALSE; } return TRUE; } #define QMI_LOAD_CHUNK_SIZE 0x400 typedef struct { GMainLoop *mainloop; QmiClientPdc *qmi_client; GError *error; gulong indication_id; guint timeout_id; GBytes *blob; GArray *digest; gsize offset; guint token; } WriteContext; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcLoadConfigInput, qmi_message_pdc_load_config_input_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcLoadConfigOutput, qmi_message_pdc_load_config_output_unref) #pragma clang diagnostic pop static void fu_qmi_pdc_updater_load_config (WriteContext *ctx); static gboolean fu_qmi_pdc_updater_load_config_timeout (gpointer user_data) { WriteContext *ctx = user_data; ctx->timeout_id = 0; g_signal_handler_disconnect (ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; g_set_error_literal (&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't load mcfg: timed out"); g_main_loop_quit (ctx->mainloop); return G_SOURCE_REMOVE; } static void fu_qmi_pdc_updater_load_config_indication (QmiClientPdc *client, QmiIndicationPdcLoadConfigOutput *output, WriteContext *ctx) { gboolean frame_reset; guint32 remaining_size; guint16 error_code = 0; g_source_remove (ctx->timeout_id); ctx->timeout_id = 0; g_signal_handler_disconnect (ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; if (!qmi_indication_pdc_load_config_output_get_indication_result (output, &error_code, &ctx->error)) { g_main_loop_quit (ctx->mainloop); return; } if (error_code != 0) { /* when a given mcfg file already exists in the device, an "invalid id" error is returned; * the error naming here is a bit off, as the same protocol error number is used both for * 'invalid id' and 'invalid qos id' */ if (error_code == QMI_PROTOCOL_ERROR_INVALID_QOS_ID) { g_debug ("file already available in device"); g_main_loop_quit (ctx->mainloop); return; } g_set_error (&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't load mcfg: %s", qmi_protocol_error_get_string ((QmiProtocolError) error_code)); g_main_loop_quit (ctx->mainloop); return; } if (qmi_indication_pdc_load_config_output_get_frame_reset (output, &frame_reset, NULL) && frame_reset) { g_set_error (&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't load mcfg: sent data discarded"); g_main_loop_quit (ctx->mainloop); return; } if (!qmi_indication_pdc_load_config_output_get_remaining_size (output, &remaining_size, &ctx->error)) { g_prefix_error (&ctx->error, "couldn't load remaining size: "); g_main_loop_quit (ctx->mainloop); return; } if (remaining_size == 0) { g_debug ("finished loading mcfg"); g_main_loop_quit (ctx->mainloop); return; } g_debug ("loading next chunk (%u bytes remaining)", remaining_size); fu_qmi_pdc_updater_load_config (ctx); } static void fu_qmi_pdc_updater_load_config_ready (GObject *qmi_client, GAsyncResult *res, gpointer user_data) { WriteContext *ctx = (WriteContext *) user_data; g_autoptr(QmiMessagePdcLoadConfigOutput) output = NULL; output = qmi_client_pdc_load_config_finish (QMI_CLIENT_PDC (qmi_client), res, &ctx->error); if (output == NULL) { g_main_loop_quit (ctx->mainloop); return; } if (!qmi_message_pdc_load_config_output_get_result (output, &ctx->error)) { g_main_loop_quit (ctx->mainloop); return; } /* after receiving the response to our request, we now expect an indication * with the actual result of the operation */ g_warn_if_fail (ctx->indication_id == 0); ctx->indication_id = g_signal_connect (ctx->qmi_client, "load-config", G_CALLBACK (fu_qmi_pdc_updater_load_config_indication), ctx); /* don't wait forever */ g_warn_if_fail (ctx->timeout_id == 0); ctx->timeout_id = g_timeout_add_seconds (5, fu_qmi_pdc_updater_load_config_timeout, ctx); } static void fu_qmi_pdc_updater_load_config (WriteContext *ctx) { g_autoptr(QmiMessagePdcLoadConfigInput) input = NULL; g_autoptr(GArray) chunk = NULL; gsize full_size; gsize chunk_size; input = qmi_message_pdc_load_config_input_new (); qmi_message_pdc_load_config_input_set_token (input, ctx->token++, NULL); full_size = g_bytes_get_size (ctx->blob); if ((ctx->offset + QMI_LOAD_CHUNK_SIZE) > full_size) chunk_size = full_size - ctx->offset; else chunk_size = QMI_LOAD_CHUNK_SIZE; chunk = g_array_sized_new (FALSE, FALSE, sizeof (guint8), chunk_size); g_array_set_size (chunk, chunk_size); memcpy (chunk->data, (const guint8 *)g_bytes_get_data (ctx->blob, NULL) + ctx->offset, chunk_size); qmi_message_pdc_load_config_input_set_config_chunk (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, ctx->digest, full_size, chunk, NULL); ctx->offset += chunk_size; qmi_client_pdc_load_config (ctx->qmi_client, input, 10, NULL, fu_qmi_pdc_updater_load_config_ready, ctx); } static GArray * fu_qmi_pdc_updater_get_checksum (GBytes *blob) { gsize file_size; gsize hash_size; GArray *digest; g_autoptr(GChecksum) checksum = NULL; /* get checksum, to be used as unique id */ file_size = g_bytes_get_size (blob); hash_size = g_checksum_type_get_length (G_CHECKSUM_SHA1); checksum = g_checksum_new (G_CHECKSUM_SHA1); g_checksum_update (checksum, g_bytes_get_data (blob, NULL), file_size); /* libqmi expects a GArray of bytes, not a GByteArray */ digest = g_array_sized_new (FALSE, FALSE, sizeof (guint8), hash_size); g_array_set_size (digest, hash_size); g_checksum_get_digest (checksum, (guint8 *)digest->data, &hash_size); return digest; } GArray * fu_qmi_pdc_updater_write (FuQmiPdcUpdater *self, const gchar *filename, GBytes *blob, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); g_autoptr(GArray) digest = fu_qmi_pdc_updater_get_checksum (blob); WriteContext ctx = { .mainloop = mainloop, .qmi_client = self->qmi_client, .error = NULL, .indication_id = 0, .timeout_id = 0, .blob = blob, .digest = digest, .offset = 0, .token = 0, }; fu_qmi_pdc_updater_load_config (&ctx); g_main_loop_run (mainloop); if (ctx.error != NULL) { g_propagate_error (error, ctx.error); return NULL; } return g_steal_pointer (&digest); } typedef struct { GMainLoop *mainloop; QmiClientPdc *qmi_client; GError *error; gulong indication_id; guint timeout_id; GArray *digest; guint token; } ActivateContext; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcActivateConfigInput, qmi_message_pdc_activate_config_input_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcActivateConfigOutput, qmi_message_pdc_activate_config_output_unref) #pragma clang diagnostic pop static gboolean fu_qmi_pdc_updater_activate_config_timeout (gpointer user_data) { ActivateContext *ctx = user_data; ctx->timeout_id = 0; g_signal_handler_disconnect (ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; /* not an error, the device may go away without sending the indication */ g_main_loop_quit (ctx->mainloop); return G_SOURCE_REMOVE; } static void fu_qmi_pdc_updater_activate_config_indication (QmiClientPdc *client, QmiIndicationPdcActivateConfigOutput *output, ActivateContext *ctx) { guint16 error_code = 0; g_source_remove (ctx->timeout_id); ctx->timeout_id = 0; g_signal_handler_disconnect (ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; if (!qmi_indication_pdc_activate_config_output_get_indication_result (output, &error_code, &ctx->error)) { g_main_loop_quit (ctx->mainloop); return; } if (error_code != 0) { g_set_error (&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't activate config: %s", qmi_protocol_error_get_string ((QmiProtocolError) error_code)); g_main_loop_quit (ctx->mainloop); return; } /* assume ok */ g_debug ("successful activate configuration indication: assuming device reset is ongoing"); g_main_loop_quit (ctx->mainloop); } static void fu_qmi_pdc_updater_activate_config_ready (GObject *qmi_client, GAsyncResult *res, gpointer user_data) { ActivateContext *ctx = (ActivateContext *) user_data; g_autoptr(QmiMessagePdcActivateConfigOutput) output = NULL; output = qmi_client_pdc_activate_config_finish (QMI_CLIENT_PDC (qmi_client), res, &ctx->error); if (output == NULL) { /* If we didn't receive a response, this is a good indication that the device * reseted itself, we can consider this a successful operation. * Note: not using g_error_matches() to avoid matching the domain, because the * error may be either QMI_CORE_ERROR_TIMEOUT or MBIM_CORE_ERROR_TIMEOUT (same * numeric value), and we don't want to build-depend on libmbim just for this. */ if (ctx->error->code == QMI_CORE_ERROR_TIMEOUT) { g_debug ("request to activate configuration timed out: assuming device reset is ongoing"); g_clear_error (&ctx->error); } g_main_loop_quit (ctx->mainloop); return; } if (!qmi_message_pdc_activate_config_output_get_result (output, &ctx->error)) { g_main_loop_quit (ctx->mainloop); return; } /* When we activate the config, if the operation is successful, we'll just * see the modem going away completely. So, do not consider an error the timeout * waiting for the Activate Config indication, as that is actually a good * thing. */ g_warn_if_fail (ctx->indication_id == 0); ctx->indication_id = g_signal_connect (ctx->qmi_client, "activate-config", G_CALLBACK (fu_qmi_pdc_updater_activate_config_indication), ctx); /* don't wait forever */ g_warn_if_fail (ctx->timeout_id == 0); ctx->timeout_id = g_timeout_add_seconds (5, fu_qmi_pdc_updater_activate_config_timeout, ctx); } static void fu_qmi_pdc_updater_activate_config (ActivateContext *ctx) { g_autoptr(QmiMessagePdcActivateConfigInput) input = NULL; input = qmi_message_pdc_activate_config_input_new (); qmi_message_pdc_activate_config_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL); qmi_message_pdc_activate_config_input_set_token (input, ctx->token++, NULL); g_debug ("activating selected configuration..."); qmi_client_pdc_activate_config (ctx->qmi_client, input, 5, NULL, fu_qmi_pdc_updater_activate_config_ready, ctx); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcSetSelectedConfigInput, qmi_message_pdc_set_selected_config_input_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcSetSelectedConfigOutput, qmi_message_pdc_set_selected_config_output_unref) #pragma clang diagnostic pop static gboolean fu_qmi_pdc_updater_set_selected_config_timeout (gpointer user_data) { ActivateContext *ctx = user_data; ctx->timeout_id = 0; g_signal_handler_disconnect (ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; g_set_error_literal (&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't set selected config: timed out"); g_main_loop_quit (ctx->mainloop); return G_SOURCE_REMOVE; } static void fu_qmi_pdc_updater_set_selected_config_indication (QmiClientPdc *client, QmiIndicationPdcSetSelectedConfigOutput *output, ActivateContext *ctx) { guint16 error_code = 0; g_source_remove (ctx->timeout_id); ctx->timeout_id = 0; g_signal_handler_disconnect (ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; if (!qmi_indication_pdc_set_selected_config_output_get_indication_result (output, &error_code, &ctx->error)) { g_main_loop_quit (ctx->mainloop); return; } if (error_code != 0) { g_set_error (&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't set selected config: %s", qmi_protocol_error_get_string ((QmiProtocolError) error_code)); g_main_loop_quit (ctx->mainloop); return; } g_debug ("current configuration successfully selected..."); /* now activate config */ fu_qmi_pdc_updater_activate_config (ctx); } static void fu_qmi_pdc_updater_set_selected_config_ready (GObject *qmi_client, GAsyncResult *res, gpointer user_data) { ActivateContext *ctx = (ActivateContext *) user_data; g_autoptr(QmiMessagePdcSetSelectedConfigOutput) output = NULL; output = qmi_client_pdc_set_selected_config_finish (QMI_CLIENT_PDC (qmi_client), res, &ctx->error); if (output == NULL) { g_main_loop_quit (ctx->mainloop); return; } if (!qmi_message_pdc_set_selected_config_output_get_result (output, &ctx->error)) { g_main_loop_quit (ctx->mainloop); return; } /* after receiving the response to our request, we now expect an indication * with the actual result of the operation */ g_warn_if_fail (ctx->indication_id == 0); ctx->indication_id = g_signal_connect (ctx->qmi_client, "set-selected-config", G_CALLBACK (fu_qmi_pdc_updater_set_selected_config_indication), ctx); /* don't wait forever */ g_warn_if_fail (ctx->timeout_id == 0); ctx->timeout_id = g_timeout_add_seconds (5, fu_qmi_pdc_updater_set_selected_config_timeout, ctx); } static void fu_qmi_pdc_updater_set_selected_config (ActivateContext *ctx) { g_autoptr(QmiMessagePdcSetSelectedConfigInput) input = NULL; QmiConfigTypeAndId type_and_id; type_and_id.config_type = QMI_PDC_CONFIGURATION_TYPE_SOFTWARE; type_and_id.id = ctx->digest; input = qmi_message_pdc_set_selected_config_input_new (); qmi_message_pdc_set_selected_config_input_set_type_with_id (input, &type_and_id, NULL); qmi_message_pdc_set_selected_config_input_set_token (input, ctx->token++, NULL); g_debug ("selecting current configuration..."); qmi_client_pdc_set_selected_config (ctx->qmi_client, input, 10, NULL, fu_qmi_pdc_updater_set_selected_config_ready, ctx); } gboolean fu_qmi_pdc_updater_activate (FuQmiPdcUpdater *self, GArray *digest, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); ActivateContext ctx = { .mainloop = mainloop, .qmi_client = self->qmi_client, .error = NULL, .indication_id = 0, .timeout_id = 0, .digest = digest, .token = 0, }; fu_qmi_pdc_updater_set_selected_config (&ctx); g_main_loop_run (mainloop); if (ctx.error != NULL) { g_propagate_error (error, ctx.error); return FALSE; } return TRUE; } static void fu_qmi_pdc_updater_init (FuQmiPdcUpdater *self) { } static void fu_qmi_pdc_updater_finalize (GObject *object) { FuQmiPdcUpdater *self = FU_QMI_PDC_UPDATER (object); g_warn_if_fail (self->qmi_client == NULL); g_warn_if_fail (self->qmi_device == NULL); g_free (self->qmi_port); G_OBJECT_CLASS (fu_qmi_pdc_updater_parent_class)->finalize (object); } static void fu_qmi_pdc_updater_class_init (FuQmiPdcUpdaterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_qmi_pdc_updater_finalize; } FuQmiPdcUpdater * fu_qmi_pdc_updater_new (const gchar *path) { FuQmiPdcUpdater *self = g_object_new (FU_TYPE_QMI_PDC_UPDATER, NULL); self->qmi_port = g_strdup (path); return self; } fwupd-1.2.14/plugins/modem-manager/fu-qmi-pdc-updater.h000066400000000000000000000016211402665037500227030ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #ifndef __FU_QMI_PDC_UPDATER_H #define __FU_QMI_PDC_UPDATER_H #include G_BEGIN_DECLS #define FU_TYPE_QMI_PDC_UPDATER (fu_qmi_pdc_updater_get_type ()) G_DECLARE_FINAL_TYPE (FuQmiPdcUpdater, fu_qmi_pdc_updater, FU, QMI_PDC_UPDATER, GObject) FuQmiPdcUpdater *fu_qmi_pdc_updater_new (const gchar *qmi_port); gboolean fu_qmi_pdc_updater_open (FuQmiPdcUpdater *self, GError **error); GArray *fu_qmi_pdc_updater_write (FuQmiPdcUpdater *self, const gchar *filename, GBytes *blob, GError **error); gboolean fu_qmi_pdc_updater_activate (FuQmiPdcUpdater *self, GArray *digest, GError **error); gboolean fu_qmi_pdc_updater_close (FuQmiPdcUpdater *self, GError **error); G_END_DECLS #endif /* __FU_QMI_PDC_UPDATER_H */ fwupd-1.2.14/plugins/modem-manager/meson.build000066400000000000000000000012121402665037500212640ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginMm"'] install_data(['modem-manager.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_modem_manager', fu_hash, sources : [ 'fu-plugin-modem-manager.c', 'fu-mm-device.c', 'fu-qmi-pdc-updater.c', 'fu-mm-utils.c' ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, c_args : [ cargs, ], link_with : [ libfwupdprivate, ], dependencies : [ plugin_deps, libmm_glib, libqmi_glib, ], ) fwupd-1.2.14/plugins/modem-manager/modem-manager.quirk000066400000000000000000000011001402665037500227040ustar00rootroot00000000000000 # DW5821e [DeviceInstanceId=USB\VID_413C&PID_81D7] Summary = Dell DW5821e LTE modem CounterpartGuid = USB\VID_413C&PID_81D6 # DW5821e in fastboot mode [DeviceInstanceId=USB\VID_413C&PID_81D6] Summary = Dell DW5821e LTE modem (fastboot) CounterpartGuid = USB\VID_413C&PID_81D7 # DW5821e/eSIM [DeviceInstanceId=USB\VID_413C&PID_81E0] Summary = Dell DW5821e/eSIM LTE modem CounterpartGuid = USB\VID_413C&PID_81E1 # DW5821e/eSIM in fastboot mode [DeviceInstanceId=USB\VID_413C&PID_81E1] Summary = Dell DW5821e/eSIM LTE modem (fastboot) CounterpartGuid = USB\VID_413C&PID_81E0 fwupd-1.2.14/plugins/nitrokey/000077500000000000000000000000001402665037500162615ustar00rootroot00000000000000fwupd-1.2.14/plugins/nitrokey/README.md000066400000000000000000000010601402665037500175350ustar00rootroot00000000000000Nitrokey Support ================ Introduction ------------ This plugin is used to get the correct version number on Nitrokey storage devices. These devices have updatable firmware but so far no updates are available from the vendor. The device is switched to a DFU bootloader only when the secret firmware pin is entered into the nitrokey-app tool. This cannot be automated. GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_20A0&PID_4109&REV_0001` * `USB\VID_20A0&PID_4109` * `USB\VID_20A0` fwupd-1.2.14/plugins/nitrokey/fu-nitrokey-common.c000066400000000000000000000015611402665037500221720ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-nitrokey-common.h" static guint32 fu_nitrokey_perform_crc32_mutate (guint32 crc, guint32 data) { crc = crc ^ data; for (guint i = 0; i < 32; i++) { if (crc & 0x80000000) { /* polynomial used in STM32 */ crc = (crc << 1) ^ 0x04C11DB7; } else { crc = (crc << 1); } } return crc; } guint32 fu_nitrokey_perform_crc32 (const guint8 *data, gsize size) { guint32 crc = 0xffffffff; g_autofree guint32 *data_aligned = NULL; data_aligned = g_new0 (guint32, (size / 4) + 1); memcpy (data_aligned, data, size); for (gsize idx = 0; idx * 4 < size; idx++) { guint32 data_aligned_le = GUINT32_FROM_LE (data_aligned[idx]); crc = fu_nitrokey_perform_crc32_mutate (crc, data_aligned_le); } return crc; } fwupd-1.2.14/plugins/nitrokey/fu-nitrokey-common.h000066400000000000000000000032201402665037500221710ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS guint32 fu_nitrokey_perform_crc32 (const guint8 *data, gsize size); #define NITROKEY_TRANSACTION_TIMEOUT 100 /* ms */ #define NITROKEY_NR_RETRIES 5 #define NITROKEY_REQUEST_DATA_LENGTH 59 #define NITROKEY_REPLY_DATA_LENGTH 53 #define NITROKEY_CMD_GET_DEVICE_STATUS (0x20 + 14) typedef struct __attribute__((packed)) { guint8 command; guint8 payload[NITROKEY_REQUEST_DATA_LENGTH]; guint32 crc; } NitrokeyHidRequest; typedef struct __attribute__((packed)) { guint8 device_status; guint8 command_id; guint32 last_command_crc; guint8 last_command_status; guint8 payload[NITROKEY_REPLY_DATA_LENGTH]; guint32 crc; } NitrokeyHidResponse; /* based from libnitrokey/stick20_commands.h from libnitrokey v3.4.1 */ typedef struct __attribute__((packed)) { guint8 _padding[18]; /* stick20_commands.h:132 // 26 - 8 = 18 */ guint8 SendCounter; guint8 SendDataType; guint8 FollowBytesFlag; guint8 SendSize; guint16 MagicNumber_StickConfig; guint8 ReadWriteFlagUncryptedVolume; guint8 ReadWriteFlagCryptedVolume; guint8 VersionMajor; guint8 VersionMinor; guint8 VersionReservedByte; guint8 VersionBuildIteration; guint8 ReadWriteFlagHiddenVolume; guint8 FirmwareLocked; guint8 NewSDCardFound; guint8 SDFillWithRandomChars; guint32 ActiveSD_CardID; guint8 VolumeActiceFlag; guint8 NewSmartCardFound; guint8 UserPwRetryCount; guint8 AdminPwRetryCount; guint32 ActiveSmartCardID; guint8 StickKeysNotInitiated; } NitrokeyGetDeviceStatusPayload; G_END_DECLS fwupd-1.2.14/plugins/nitrokey/fu-nitrokey-device.c000066400000000000000000000154371402665037500221500ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-nitrokey-common.h" #include "fu-nitrokey-device.h" G_DEFINE_TYPE (FuNitrokeyDevice, fu_nitrokey_device, FU_TYPE_USB_DEVICE) static void _dump_to_console (const gchar *title, const guint8 *buf, gsize buf_sz) { if (g_getenv ("FWUPD_NITROKEY_VERBOSE") == NULL) return; g_debug ("%s", title); for (gsize i = 0; i < buf_sz; i++) g_debug ("%" G_GSIZE_FORMAT "=0x%02x", i, buf[i]); } static gboolean nitrokey_execute_cmd (GUsbDevice *usb_device, guint8 command, const guint8 *buf_in, gsize buf_in_sz, guint8 *buf_out, gsize buf_out_sz, GCancellable *cancellable, GError **error) { NitrokeyHidResponse res; gboolean ret; gsize actual_len = 0; guint32 crc_tmp; guint8 buf[64]; g_return_val_if_fail (buf_in_sz <= NITROKEY_REQUEST_DATA_LENGTH, FALSE); g_return_val_if_fail (buf_out_sz <= NITROKEY_REPLY_DATA_LENGTH, FALSE); /* create the request */ memset (buf, 0x00, sizeof(buf)); buf[0] = command; if (buf_in != NULL) memcpy (&buf[1], buf_in, buf_in_sz); crc_tmp = fu_nitrokey_perform_crc32 (buf, sizeof(buf) - 4); fu_common_write_uint32 (&buf[NITROKEY_REQUEST_DATA_LENGTH + 1], crc_tmp, G_LITTLE_ENDIAN); /* send request */ _dump_to_console ("request", buf, sizeof(buf)); ret = g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, 0x09, 0x0300, 0x0002, buf, sizeof(buf), &actual_len, NITROKEY_TRANSACTION_TIMEOUT, NULL, error); if (!ret) { g_prefix_error (error, "failed to do HOST_TO_DEVICE: "); return FALSE; } if (actual_len != sizeof(buf)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } /* get response */ memset (buf, 0x00, sizeof(buf)); ret = g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, 0x01, 0x0300, 0x0002, buf, sizeof(buf), &actual_len, NITROKEY_TRANSACTION_TIMEOUT, NULL, error); if (!ret) { g_prefix_error (error, "failed to do DEVICE_TO_HOST: "); return FALSE; } if (actual_len != sizeof(res)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } _dump_to_console ("response", buf, sizeof(buf)); /* verify this is the answer to the question we asked */ memcpy (&res, buf, sizeof(buf)); if (GUINT32_FROM_LE (res.last_command_crc) != crc_tmp) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got response CRC %x, expected %x", GUINT32_FROM_LE (res.last_command_crc), crc_tmp); return FALSE; } /* verify the response checksum */ crc_tmp = fu_nitrokey_perform_crc32 (buf, sizeof(res) - 4); if (GUINT32_FROM_LE (res.crc) != crc_tmp) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got packet CRC %x, expected %x", GUINT32_FROM_LE (res.crc), crc_tmp); return FALSE; } /* copy out the payload */ if (buf_out != NULL) memcpy (buf_out, &res.payload, buf_out_sz); /* success */ return TRUE; } static gboolean nitrokey_execute_cmd_full (GUsbDevice *usb_device, guint8 command, const guint8 *buf_in, gsize buf_in_sz, guint8 *buf_out, gsize buf_out_sz, GCancellable *cancellable, GError **error) { for (guint i = 0; i < NITROKEY_NR_RETRIES; i++) { g_autoptr(GError) error_local = NULL; gboolean ret; ret = nitrokey_execute_cmd (usb_device, command, buf_in, buf_in_sz, buf_out, buf_out_sz, cancellable, &error_local); if (ret) return TRUE; if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_FAILED)) { if (error != NULL) *error = g_steal_pointer (&error_local); return FALSE; } g_warning ("retrying command: %s", error_local->message); g_usleep (100 * 1000); } /* failed */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to issue command after %i retries", NITROKEY_NR_RETRIES); return FALSE; } static gboolean fu_nitrokey_device_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* claim interface */ if (!g_usb_device_claim_interface (usb_device, 0x02, /* idx */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to do claim nitrokey: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_nitrokey_device_setup (FuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); NitrokeyGetDeviceStatusPayload payload; guint8 buf_reply[NITROKEY_REPLY_DATA_LENGTH]; g_autofree gchar *version = NULL; /* get firmware version */ if (!nitrokey_execute_cmd_full (usb_device, NITROKEY_CMD_GET_DEVICE_STATUS, NULL, 0, buf_reply, sizeof(buf_reply), NULL, error)) { g_prefix_error (error, "failed to do get firmware version: "); return FALSE; } _dump_to_console ("payload", buf_reply, sizeof(buf_reply)); memcpy (&payload, buf_reply, sizeof(payload)); version = g_strdup_printf ("%u.%u", payload.VersionMajor, payload.VersionMinor); fu_device_set_version (FU_DEVICE (device), version, FWUPD_VERSION_FORMAT_PAIR); /* success */ return TRUE; } static gboolean fu_nitrokey_device_close (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); g_autoptr(GError) error_local = NULL; /* reconnect kernel driver */ if (!g_usb_device_release_interface (usb_device, 0x02, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, &error_local)) { g_warning ("failed to release interface: %s", error_local->message); } return TRUE; } static void fu_nitrokey_device_init (FuNitrokeyDevice *device) { fu_device_set_remove_delay (FU_DEVICE (device), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE); } static void fu_nitrokey_device_class_init (FuNitrokeyDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->setup = fu_nitrokey_device_setup; klass_usb_device->open = fu_nitrokey_device_open; klass_usb_device->close = fu_nitrokey_device_close; } FuNitrokeyDevice * fu_nitrokey_device_new (FuUsbDevice *device) { FuNitrokeyDevice *self = g_object_new (FU_TYPE_NITROKEY_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return FU_NITROKEY_DEVICE (self); } fwupd-1.2.14/plugins/nitrokey/fu-nitrokey-device.h000066400000000000000000000007271402665037500221510ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_NITROKEY_DEVICE (fu_nitrokey_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuNitrokeyDevice, fu_nitrokey_device, FU, NITROKEY_DEVICE, FuUsbDevice) struct _FuNitrokeyDeviceClass { FuUsbDeviceClass parent_class; }; FuNitrokeyDevice *fu_nitrokey_device_new (FuUsbDevice *device); G_END_DECLS fwupd-1.2.14/plugins/nitrokey/fu-plugin-nitrokey.c000066400000000000000000000014431402665037500221770ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-nitrokey-device.h" #include "fu-nitrokey-common.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuNitrokeyDevice) dev = NULL; /* open the device */ dev = fu_nitrokey_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; /* success */ fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } fwupd-1.2.14/plugins/nitrokey/fu-self-test.c000066400000000000000000000060311402665037500207430ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-nitrokey-common.h" static void fu_nitrokey_version_test (void) { /* use the Nitrokey Storage v0.53 status response for test, CRC 0xa2762d14 */ NitrokeyGetDeviceStatusPayload payload; NitrokeyHidResponse res; guint32 crc_tmp; /* 65 bytes of response from HIDAPI; first byte is always 0 */ const guint8 buf[] = { /*0x00,*/ 0x00, 0x2e, 0xef, 0xc4, 0x9b, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x2e, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x1c, 0x18, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x45, 0x24, 0xf1, 0x4c, 0x01, 0x00, 0x03, 0x03, 0xc7, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x2d, 0x76, 0xa2 }; /* testing the whole path, as in fu_nitrokey_device_setup()*/ memcpy (&res, buf, sizeof (buf)); memcpy (&payload, &res.payload, sizeof (payload)); /* verify the version number */ g_assert_cmpint (payload.VersionMajor, == , 0); g_assert_cmpint (payload.VersionMinor, == , 53); g_assert_cmpint (buf[34], == , payload.VersionMinor); g_assert_cmpint (payload.VersionBuildIteration, == , 0); /* verify the response checksum */ crc_tmp = fu_nitrokey_perform_crc32 (buf, sizeof (res) - 4); g_assert_cmpint (GUINT32_FROM_LE (res.crc), == , crc_tmp); } static void fu_nitrokey_version_test_static (void) { /* use static response from numbered bytes, to make sure fields occupy * expected bytes */ NitrokeyGetDeviceStatusPayload payload; NitrokeyHidResponse res; const guint8 buf[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, }; memcpy (&res, buf, sizeof (buf)); memcpy (&payload, &res.payload, sizeof (payload)); g_assert_cmpint (payload.VersionMajor, == , 33); /* 0x1a */ g_assert_cmpint (payload.VersionMinor, == , 34); /* 0x1b */ g_assert_cmpint (buf[34], == , 34); } static void fu_nitrokey_func (void) { const guint8 buf[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; g_assert_cmpint (fu_nitrokey_perform_crc32 (buf, 16), ==, 0x081B46CA); g_assert_cmpint (fu_nitrokey_perform_crc32 (buf, 15), ==, 0xED7320AB); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func ("/fwupd/nitrokey", fu_nitrokey_func); g_test_add_func ("/fwupd/nitrokey-version-static", fu_nitrokey_version_test_static); g_test_add_func ("/fwupd/nitrokey-version", fu_nitrokey_version_test); return g_test_run (); } fwupd-1.2.14/plugins/nitrokey/lsusb.txt000066400000000000000000000165571402665037500201700ustar00rootroot00000000000000Bus 001 Device 007: ID 20a0:4109 Clay Logic Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x20a0 Clay Logic idProduct 0x4109 bcdDevice 1.00 iManufacturer 1 Nitrokey iProduct 2 Nitrokey Storage iSerial 3 0000000000000 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 141 bNumInterfaces 3 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 8 Mass Storage bInterfaceSubClass 6 SCSI bInterfaceProtocol 80 Bulk-Only iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 11 Chip/SmartCard bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 ChipCard Interface Descriptor: bLength 54 bDescriptorType 33 bcdCCID 1.10 (Warning: Only accurate for version 1.0) nMaxSlotIndex 0 bVoltageSupport 2 3.0V dwProtocols 2 T=1 dwDefaultClock 3600 dwMaxiumumClock 3600 bNumClockSupported 0 dwDataRate 9677 bps dwMaxDataRate 116129 bps bNumDataRatesSupp. 0 dwMaxIFSD 261 dwSyncProtocols 00000000 dwMechanical 00000000 dwFeatures 000104BA Auto configuration based on ATR Auto voltage selection Auto clock change Auto baud rate change Auto PPS made by CCID Auto IFSD exchange TPDU level exchange dwMaxCCIDMsgLen 271 bClassGetResponse 00 bClassEnvelope 00 wlcdLayout none bPINSupport 0 bMaxCCIDBusySlots 1 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0010 1x 16 bytes bInterval 16 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x04 EP 4 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x85 EP 5 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 1 Keyboard iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 71 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x86 EP 6 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Device Qualifier (for other device speed): bLength 10 bDescriptorType 6 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 bNumConfigurations 1 Device Status: 0x0000 (Bus Powered) Bus 001 Device 055: ID 03eb:2ff1 Atmel Corp. at32uc3a3 DFU bootloader Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x03eb Atmel Corp. idProduct 0x2ff1 at32uc3a3 DFU bootloader bcdDevice 10.00 iManufacturer 1 ATMEL iProduct 2 AT32UC3A DFU iSerial 3 1.0.3 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 27 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 0 bInterfaceClass 254 Application Specific Interface bInterfaceSubClass 1 Device Firmware Update bInterfaceProtocol 2 iInterface 0 Device Firmware Upgrade Interface Descriptor: bLength 9 bDescriptorType 33 bmAttributes 15 Will Detach Manifestation Tolerant Upload Supported Download Supported wDetachTimeout 0 milliseconds wTransferSize 65535 bytes bcdDFUVersion 1.01 Device Status: 0x0001 Self Powered fwupd-1.2.14/plugins/nitrokey/meson.build000066400000000000000000000020321402665037500204200ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginNitrokey"'] install_data(['nitrokey.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_nitrokey', fu_hash, sources : [ 'fu-nitrokey-device.c', 'fu-nitrokey-common.c', 'fu-plugin-nitrokey.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) if get_option('tests') e = executable( 'nitrokey-self-test', fu_hash, sources : [ 'fu-nitrokey-common.c', 'fu-self-test.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ plugin_deps, valgrind, ], link_with : [ libfwupdprivate, ], ) test('nitrokey-self-test', e) endif fwupd-1.2.14/plugins/nitrokey/nitrokey.quirk000066400000000000000000000003321402665037500212000ustar00rootroot00000000000000# Nitrokey Storage [DeviceInstanceId=USB\VID_20A0&PID_4109] Plugin = nitrokey Flags = needs-bootloader,use-runtime-version CounterpartGuid = USB\VID_03EB&PID_2FF1 Summary = A secure memory stick Icon = media-removable fwupd-1.2.14/plugins/nvme/000077500000000000000000000000001402665037500153625ustar00rootroot00000000000000fwupd-1.2.14/plugins/nvme/README.md000066400000000000000000000031611402665037500166420ustar00rootroot00000000000000NVMe ==== Introduction ------------ This plugin adds support for NVMe storage hardware. Devices are enumerated from the Identify Controller data structure and can be updated with appropriate firmware file. Firmware is sent in 4kB chunks and activated on next reboot. The device GUID is read from the vendor specific area and if not found then generated from the trimmed model string. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * org.nvmexpress GUID Generation --------------- These device use the NVMe DeviceInstanceId values, e.g. * `NVME\VEN_1179&DEV_010F&REV_01` * `NVME\VEN_1179&DEV_010F` * `NVME\VEN_1179` The FRU globally unique identifier (FGUID) is also added from the CNS if set. Please refer to this document for more details on how to add support for FGUID: https://nvmexpress.org/wp-content/uploads/NVM_Express_Revision_1.3.pdf Additionally, for NVMe drives with Dell vendor firmware two extra GUIDs are added: * `STORAGE-DELL-${component-id}` and any optional GUID saved in the vendor extension block. Quirk use --------- This plugin uses the following plugin-specific quirks: | Quirk | Description | Minimum fwupd version | |------------------------|---------------------------------------------|-----------------------| | `NvmeBlockSize` | The block size used for NVMe writes | 1.1.3 | | `Flags` | `force-align` if image should be padded | 1.2.4 | fwupd-1.2.14/plugins/nvme/fu-nvme-common.c000066400000000000000000000123521402665037500203740ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-nvme-common.h" const gchar * fu_nvme_status_to_string (guint32 status) { switch (status) { case NVME_SC_SUCCESS: return "Command completed successfully"; case NVME_SC_INVALID_OPCODE: return "Associated command opcode field is not valid"; case NVME_SC_INVALID_FIELD: return "Unsupported value in a defined field"; case NVME_SC_CMDID_CONFLICT: return "Command identifier is already in use"; case NVME_SC_DATA_XFER_ERROR: return "Error while trying to transfer the data or metadata"; case NVME_SC_POWER_LOSS: return "Command aborted due to power loss notification"; case NVME_SC_INTERNAL: return "Internal error"; case NVME_SC_ABORT_REQ: return "Command Abort request"; case NVME_SC_ABORT_QUEUE: return "Delete I/O Submission Queue request"; case NVME_SC_FUSED_FAIL: return "Other command in a fused operation failing"; case NVME_SC_FUSED_MISSING: return "Missing Fused Command"; case NVME_SC_INVALID_NS: return "Namespace or the format of that namespace is invalid"; case NVME_SC_CMD_SEQ_ERROR: return "Protocol violation in a multicommand sequence"; case NVME_SC_SANITIZE_FAILED: return "No recovery actions has been successfully completed"; case NVME_SC_SANITIZE_IN_PROGRESS: return "A sanitize operation is in progress"; case NVME_SC_LBA_RANGE: return "LBA exceeds the size of the namespace"; case NVME_SC_NS_WRITE_PROTECTED: return "Namespace is write protected by the host"; case NVME_SC_CAP_EXCEEDED: return "Capacity of the namespace to be exceeded"; case NVME_SC_NS_NOT_READY: return "Namespace is not ready to be accessed"; case NVME_SC_RESERVATION_CONFLICT: return "Conflict with a reservation on the accessed namespace"; case NVME_SC_CQ_INVALID: return "Completion Queue does not exist"; case NVME_SC_QID_INVALID: return "Invalid queue identifier specified"; case NVME_SC_QUEUE_SIZE: return "Invalid queue size"; case NVME_SC_ABORT_LIMIT: return "Outstanding Abort commands has exceeded the limit"; case NVME_SC_ABORT_MISSING: return "Abort command is missing"; case NVME_SC_ASYNC_LIMIT: return "Outstanding Async commands has been exceeded"; case NVME_SC_FIRMWARE_SLOT: return "Slot is invalid or read only"; case NVME_SC_FIRMWARE_IMAGE: return "Image specified for activation is invalid"; case NVME_SC_INVALID_VECTOR: return "Creation failed due to an invalid interrupt vector"; case NVME_SC_INVALID_LOG_PAGE: return "Log page indicated is invalid"; case NVME_SC_INVALID_FORMAT: return "LBA Format specified is not supported"; case NVME_SC_FW_NEEDS_CONV_RESET: return "commit was successful, but activation requires reset"; case NVME_SC_INVALID_QUEUE: return "Failed to delete the I/O Completion Queue specified"; case NVME_SC_FEATURE_NOT_SAVEABLE: return "Feature Identifier does not support a saveable value"; case NVME_SC_FEATURE_NOT_CHANGEABLE: return "Feature Identifier is not able to be changed"; case NVME_SC_FEATURE_NOT_PER_NS: return "Feature Identifier specified is not namespace specific"; case NVME_SC_FW_NEEDS_SUBSYS_RESET: return "Commit was successful, activation requires NVM Subsystem"; case NVME_SC_FW_NEEDS_RESET: return "Commit was successful, activation requires a reset"; case NVME_SC_FW_NEEDS_MAX_TIME: return "Would exceed the Maximum Time for Firmware Activation"; case NVME_SC_FW_ACIVATE_PROHIBITED: return "Image specified is being prohibited from activation"; case NVME_SC_OVERLAPPING_RANGE: return "Image has overlapping ranges"; case NVME_SC_NS_INSUFFICENT_CAP: return "Requires more free space than is currently available"; case NVME_SC_NS_ID_UNAVAILABLE: return "Number of namespaces supported has been exceeded"; case NVME_SC_NS_ALREADY_ATTACHED: return "Controller is already attached to the namespace"; case NVME_SC_NS_IS_PRIVATE: return "Namespace is private"; case NVME_SC_NS_NOT_ATTACHED: return "Controller is not attached to the namespace"; case NVME_SC_THIN_PROV_NOT_SUPP: return "Thin provisioning is not supported by the controller"; case NVME_SC_CTRL_LIST_INVALID: return "Controller list provided is invalid"; case NVME_SC_BP_WRITE_PROHIBITED: return "Trying to modify a Boot Partition while it is locked"; case NVME_SC_BAD_ATTRIBUTES: return "Bad attributes"; case NVME_SC_WRITE_FAULT: return "Write data could not be committed to the media"; case NVME_SC_READ_ERROR: return "Read data could not be recovered from the media"; case NVME_SC_GUARD_CHECK: return "End-to-end guard check failure"; case NVME_SC_APPTAG_CHECK: return "End-to-end application tag check failure"; case NVME_SC_REFTAG_CHECK: return "End-to-end reference tag check failure"; case NVME_SC_COMPARE_FAILED: return "Miscompare during a Compare command"; case NVME_SC_ACCESS_DENIED: return "Access denied"; case NVME_SC_UNWRITTEN_BLOCK: return "Read from an LBA range containing a unwritten block"; case NVME_SC_ANA_PERSISTENT_LOSS: return "Namespace is in the ANA Persistent Loss state"; case NVME_SC_ANA_INACCESSIBLE: return "Namespace being in the ANA Inaccessible state"; case NVME_SC_ANA_TRANSITION: return "Namespace transitioning between Async Access states"; default: return "Unknown"; } } fwupd-1.2.14/plugins/nvme/fu-nvme-common.h000066400000000000000000000061471402665037500204060ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS enum { /* * Generic Command Status: */ NVME_SC_SUCCESS = 0x0, NVME_SC_INVALID_OPCODE = 0x1, NVME_SC_INVALID_FIELD = 0x2, NVME_SC_CMDID_CONFLICT = 0x3, NVME_SC_DATA_XFER_ERROR = 0x4, NVME_SC_POWER_LOSS = 0x5, NVME_SC_INTERNAL = 0x6, NVME_SC_ABORT_REQ = 0x7, NVME_SC_ABORT_QUEUE = 0x8, NVME_SC_FUSED_FAIL = 0x9, NVME_SC_FUSED_MISSING = 0xa, NVME_SC_INVALID_NS = 0xb, NVME_SC_CMD_SEQ_ERROR = 0xc, NVME_SC_SGL_INVALID_LAST = 0xd, NVME_SC_SGL_INVALID_COUNT = 0xe, NVME_SC_SGL_INVALID_DATA = 0xf, NVME_SC_SGL_INVALID_METADATA = 0x10, NVME_SC_SGL_INVALID_TYPE = 0x11, NVME_SC_SGL_INVALID_OFFSET = 0x16, NVME_SC_SGL_INVALID_SUBTYPE = 0x17, NVME_SC_SANITIZE_FAILED = 0x1C, NVME_SC_SANITIZE_IN_PROGRESS = 0x1D, NVME_SC_NS_WRITE_PROTECTED = 0x20, NVME_SC_LBA_RANGE = 0x80, NVME_SC_CAP_EXCEEDED = 0x81, NVME_SC_NS_NOT_READY = 0x82, NVME_SC_RESERVATION_CONFLICT = 0x83, /* * Command Specific Status: */ NVME_SC_CQ_INVALID = 0x100, NVME_SC_QID_INVALID = 0x101, NVME_SC_QUEUE_SIZE = 0x102, NVME_SC_ABORT_LIMIT = 0x103, NVME_SC_ABORT_MISSING = 0x104, NVME_SC_ASYNC_LIMIT = 0x105, NVME_SC_FIRMWARE_SLOT = 0x106, NVME_SC_FIRMWARE_IMAGE = 0x107, NVME_SC_INVALID_VECTOR = 0x108, NVME_SC_INVALID_LOG_PAGE = 0x109, NVME_SC_INVALID_FORMAT = 0x10a, NVME_SC_FW_NEEDS_CONV_RESET = 0x10b, NVME_SC_INVALID_QUEUE = 0x10c, NVME_SC_FEATURE_NOT_SAVEABLE = 0x10d, NVME_SC_FEATURE_NOT_CHANGEABLE = 0x10e, NVME_SC_FEATURE_NOT_PER_NS = 0x10f, NVME_SC_FW_NEEDS_SUBSYS_RESET = 0x110, NVME_SC_FW_NEEDS_RESET = 0x111, NVME_SC_FW_NEEDS_MAX_TIME = 0x112, NVME_SC_FW_ACIVATE_PROHIBITED = 0x113, NVME_SC_OVERLAPPING_RANGE = 0x114, NVME_SC_NS_INSUFFICENT_CAP = 0x115, NVME_SC_NS_ID_UNAVAILABLE = 0x116, NVME_SC_NS_ALREADY_ATTACHED = 0x118, NVME_SC_NS_IS_PRIVATE = 0x119, NVME_SC_NS_NOT_ATTACHED = 0x11a, NVME_SC_THIN_PROV_NOT_SUPP = 0x11b, NVME_SC_CTRL_LIST_INVALID = 0x11c, NVME_SC_BP_WRITE_PROHIBITED = 0x11e, /* * I/O Command Set Specific - NVM commands: */ NVME_SC_BAD_ATTRIBUTES = 0x180, NVME_SC_INVALID_PI = 0x181, NVME_SC_READ_ONLY = 0x182, NVME_SC_ONCS_NOT_SUPPORTED = 0x183, /* * I/O Command Set Specific - Fabrics commands: */ NVME_SC_CONNECT_FORMAT = 0x180, NVME_SC_CONNECT_CTRL_BUSY = 0x181, NVME_SC_CONNECT_INVALID_PARAM = 0x182, NVME_SC_CONNECT_RESTART_DISC = 0x183, NVME_SC_CONNECT_INVALID_HOST = 0x184, NVME_SC_DISCOVERY_RESTART = 0x190, NVME_SC_AUTH_REQUIRED = 0x191, /* * Media and Data Integrity Errors: */ NVME_SC_WRITE_FAULT = 0x280, NVME_SC_READ_ERROR = 0x281, NVME_SC_GUARD_CHECK = 0x282, NVME_SC_APPTAG_CHECK = 0x283, NVME_SC_REFTAG_CHECK = 0x284, NVME_SC_COMPARE_FAILED = 0x285, NVME_SC_ACCESS_DENIED = 0x286, NVME_SC_UNWRITTEN_BLOCK = 0x287, /* * Path-related Errors: */ NVME_SC_ANA_PERSISTENT_LOSS = 0x301, NVME_SC_ANA_INACCESSIBLE = 0x302, NVME_SC_ANA_TRANSITION = 0x303, NVME_SC_DNR = 0x4000, }; const gchar *fu_nvme_status_to_string (guint32 status); G_END_DECLS fwupd-1.2.14/plugins/nvme/fu-nvme-device.c000066400000000000000000000342461402665037500203510ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include #include "fu-chunk.h" #include "fu-nvme-common.h" #include "fu-nvme-device.h" #define FU_NVME_ID_CTRL_SIZE 0x1000 struct _FuNvmeDevice { FuUdevDevice parent_instance; guint pci_depth; gint fd; guint64 write_block_size; }; G_DEFINE_TYPE (FuNvmeDevice, fu_nvme_device, FU_TYPE_UDEV_DEVICE) #ifndef HAVE_GUDEV_232 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref) #pragma clang diagnostic pop #endif static void fu_nvme_device_to_string (FuDevice *device, GString *str) { FuNvmeDevice *self = FU_NVME_DEVICE (device); g_string_append (str, " FuNvmeDevice:\n"); g_string_append_printf (str, " fd:\t\t\t%i\n", self->fd); g_string_append_printf (str, " pci-depth:\t\t%u\n", self->pci_depth); } /* @addr_start and @addr_end are *inclusive* to match the NMVe specification */ static gchar * fu_nvme_device_get_string_safe (const guint8 *buf, guint16 addr_start, guint16 addr_end) { GString *str; g_return_val_if_fail (buf != NULL, NULL); g_return_val_if_fail (addr_start < addr_end, NULL); str = g_string_new_len (NULL, addr_end + addr_start + 1); for (guint16 i = addr_start; i <= addr_end; i++) { gchar tmp = (gchar) buf[i]; /* skip leading spaces */ if (g_ascii_isspace (tmp) && str->len == 0) continue; if (g_ascii_isprint (tmp)) g_string_append_c (str, tmp); } /* nothing found */ if (str->len == 0) { g_string_free (str, TRUE); return NULL; } return g_strchomp (g_string_free (str, FALSE)); } static gchar * fu_nvme_device_get_guid_safe (const guint8 *buf, guint16 addr_start) { if (!fu_common_guid_is_plausible (buf + addr_start)) return NULL; return fwupd_guid_to_string ((const fwupd_guid_t *) (buf + addr_start), FWUPD_GUID_FLAG_MIXED_ENDIAN); } static gboolean fu_nvme_device_submit_admin_passthru (FuNvmeDevice *self, struct nvme_admin_cmd *cmd, GError **error) { gint rc; guint32 err; /* submit admin command */ rc = ioctl (self->fd, NVME_IOCTL_ADMIN_CMD, cmd); if (rc < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to issue admin command 0x%02x: %s", cmd->opcode, strerror (errno)); return FALSE; } /* check the error code */ err = rc & 0x3ff; switch (err) { case NVME_SC_SUCCESS: /* devices are always added with _NEEDS_REBOOT, so ignore */ case NVME_SC_FW_NEEDS_CONV_RESET: case NVME_SC_FW_NEEDS_SUBSYS_RESET: case NVME_SC_FW_NEEDS_RESET: return TRUE; default: break; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported: %s", fu_nvme_status_to_string (err)); return FALSE; } static gboolean fu_nvme_device_identify_ctrl (FuNvmeDevice *self, guint8 *data, GError **error) { struct nvme_admin_cmd cmd = { .opcode = 0x06, .nsid = 0x00, .addr = 0x0, /* memory address of data */ .data_len = FU_NVME_ID_CTRL_SIZE, .cdw10 = 0x01, .cdw11 = 0x00, }; memcpy (&cmd.addr, &data, sizeof (gpointer)); return fu_nvme_device_submit_admin_passthru (self, &cmd, error); } static gboolean fu_nvme_device_fw_commit (FuNvmeDevice *self, guint8 slot, guint8 action, guint8 bpid, GError **error) { struct nvme_admin_cmd cmd = { .opcode = 0x10, .cdw10 = (bpid << 31) | (action << 3) | slot, }; return fu_nvme_device_submit_admin_passthru (self, &cmd, error); } static gboolean fu_nvme_device_fw_download (FuNvmeDevice *self, guint32 addr, const guint8 *data, guint32 data_sz, GError **error) { struct nvme_admin_cmd cmd = { .opcode = 0x11, .addr = 0x0, /* memory address of data */ .data_len = data_sz, .cdw10 = (data_sz >> 2) - 1, /* convert to DWORDs */ .cdw11 = addr >> 2, /* convert to DWORDs */ }; memcpy (&cmd.addr, &data, sizeof (gpointer)); return fu_nvme_device_submit_admin_passthru (self, &cmd, error); } static void fu_nvme_device_parse_cns_maybe_dell (FuNvmeDevice *self, const guint8 *buf) { g_autofree gchar *component_id = NULL; g_autofree gchar *devid = NULL; g_autofree gchar *guid_efi = NULL; g_autofree gchar *guid = NULL; /* add extra component ID if set */ component_id = fu_nvme_device_get_string_safe (buf, 0xc36, 0xc3d); if (component_id == NULL || !g_str_is_ascii (component_id) || strlen (component_id) < 6) { g_debug ("invalid component ID, skipping"); return; } /* do not add the FuUdevDevice instance IDs as generic firmware * should not be used on these OEM-specific devices */ fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS); /* add instance ID *and* GUID as using no-auto-instance-ids */ devid = g_strdup_printf ("STORAGE-DELL-%s", component_id); fu_device_add_instance_id (FU_DEVICE (self), devid); guid = fwupd_guid_hash_string (devid); fu_device_add_guid (FU_DEVICE (self), guid); /* also add the EFI GUID */ guid_efi = fu_nvme_device_get_guid_safe (buf, 0x0c26); if (guid_efi != NULL) fu_device_add_guid (FU_DEVICE (self), guid_efi); } static gboolean fu_nvme_device_set_version (FuNvmeDevice *self, const gchar *version, GError **error) { FwupdVersionFormat fmt = fu_device_get_version_format (FU_DEVICE (self)); /* unset */ if (fmt == FWUPD_VERSION_FORMAT_UNKNOWN || fmt == FWUPD_VERSION_FORMAT_PLAIN) { fu_device_set_version (FU_DEVICE (self), version, FWUPD_VERSION_FORMAT_PLAIN); return TRUE; } /* AA.BB.CC.DD */ if (fmt == FWUPD_VERSION_FORMAT_QUAD) { guint64 tmp = g_ascii_strtoull (version, NULL, 16); g_autofree gchar *version_new = NULL; if (tmp == 0 || tmp > G_MAXUINT32) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s is not valid 32 bit number", version); return FALSE; } version_new = fu_common_version_from_uint32 (tmp, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version (FU_DEVICE (self), version_new, fmt); return TRUE; } /* invalid, or not supported */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "version format %s not handled", fwupd_version_format_to_string (fmt)); return FALSE; } static gboolean fu_nvme_device_parse_cns (FuNvmeDevice *self, const guint8 *buf, gsize sz, GError **error) { guint8 fawr; guint8 fwug; guint8 nfws; guint8 s1ro; g_autofree gchar *gu = NULL; g_autofree gchar *mn = NULL; g_autofree gchar *sn = NULL; g_autofree gchar *sr = NULL; /* wrong size */ if (sz != FU_NVME_ID_CTRL_SIZE) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to parse blob, expected 0x%04x bytes", (guint) FU_NVME_ID_CTRL_SIZE); return FALSE; } /* get sanitiezed string from CNS -- see the following doc for offsets: * NVM-Express-1_3c-2018.05.24-Ratified.pdf */ sn = fu_nvme_device_get_string_safe (buf, 4, 23); if (sn != NULL) fu_device_set_serial (FU_DEVICE (self), sn); mn = fu_nvme_device_get_string_safe (buf, 24, 63); if (mn != NULL) fu_device_set_name (FU_DEVICE (self), mn); sr = fu_nvme_device_get_string_safe (buf, 64, 71); if (sr != NULL) { if (!fu_nvme_device_set_version (self, sr, error)) return FALSE; } /* firmware update granularity (FWUG) */ fwug = buf[319]; if (fwug != 0x00 && fwug != 0xff) self->write_block_size = ((guint64) fwug) * 0x1000; /* firmware slot information */ fawr = (buf[260] & 0x10) >> 4; nfws = (buf[260] & 0x0e) >> 1; s1ro = buf[260] & 0x01; g_debug ("fawr: %u, nr fw slots: %u, slot1 r/o: %u", fawr, nfws, s1ro); /* FRU globally unique identifier (FGUID) */ gu = fu_nvme_device_get_guid_safe (buf, 127); if (gu != NULL) fu_device_add_guid (FU_DEVICE (self), gu); /* Dell helpfully provide an EFI GUID we can use in the vendor offset, * but don't have a header or any magic we can use -- so check if the * component ID looks plausible and the GUID is "sane" */ fu_nvme_device_parse_cns_maybe_dell (self, buf); /* fall back to the device description */ if (fu_device_get_guids (FU_DEVICE (self))->len == 0) { g_debug ("no vendor GUID, falling back to mn"); fu_device_add_instance_id (FU_DEVICE (self), mn); } return TRUE; } static void fu_nvme_device_dump (const gchar *title, const guint8 *buf, gsize sz) { if (g_getenv ("FWPUD_NVME_VERBOSE") == NULL) return; g_print ("%s (%" G_GSIZE_FORMAT "):", title, sz); for (gsize i = 0; i < sz; i++) { if (i % 64 == 0) g_print ("\naddr 0x%04x: ", (guint) i); g_print ("%02x", buf[i]); } g_print ("\n"); } static gboolean fu_nvme_device_open (FuDevice *device, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE (device); GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device)); /* open device */ self->fd = g_open (g_udev_device_get_device_file (udev_device), O_RDONLY); if (self->fd < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to open %s: %s", g_udev_device_get_device_file (udev_device), strerror (errno)); return FALSE; } /* success */ return TRUE; } static gboolean fu_nvme_device_probe (FuUdevDevice *device, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE (device); /* set the physical ID */ if (!fu_udev_device_set_physical_id (device, "pci", error)) return FALSE; /* look at the PCI depth to work out if in an external enclosure */ self->pci_depth = fu_udev_device_get_slot_depth (device, "pci"); if (self->pci_depth <= 2) fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL); /* all devices need at least a warm reset, but some quirked drives * need a full "cold" shutdown and startup */ if (!fu_device_has_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); return TRUE; } static gboolean fu_nvme_device_setup (FuDevice *device, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE (device); guint8 buf[FU_NVME_ID_CTRL_SIZE] = { 0x0 }; /* get and parse CNS */ if (!fu_nvme_device_identify_ctrl (self, buf, error)) { g_prefix_error (error, "failed to identify %s: ", fu_device_get_physical_id (FU_DEVICE (self))); return FALSE; } fu_nvme_device_dump ("CNS", buf, sizeof (buf)); if (!fu_nvme_device_parse_cns (self, buf, sizeof(buf), error)) return FALSE; /* success */ return TRUE; } static gboolean fu_nvme_device_close (FuDevice *device, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE (device); if (!g_close (self->fd, error)) return FALSE; self->fd = 0; return TRUE; } static gboolean fu_nvme_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE (device); g_autoptr(GBytes) fw2 = NULL; g_autoptr(GPtrArray) chunks = NULL; guint64 block_size = self->write_block_size > 0 ? self->write_block_size : 0x1000; /* some vendors provide firmware files whose sizes are not multiples * of blksz *and* the device won't accept blocks of different sizes */ if (fu_device_has_custom_flag (device, "force-align")) { fw2 = fu_common_bytes_align (fw, block_size, 0xff); } else { fw2 = g_bytes_ref (fw); } /* build packets */ chunks = fu_chunk_array_new_from_bytes (fw2, 0x00, /* start_addr */ 0x00, /* page_sz */ block_size); /* block size */ /* write each block */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); if (!fu_nvme_device_fw_download (self, chk->address, chk->data, chk->data_sz, error)) { g_prefix_error (error, "failed to write chunk %u: ", i); return FALSE; } fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len + 1); } /* commit */ if (!fu_nvme_device_fw_commit (self, 0x00, /* let controller choose */ 0x01, /* download replaces, activated on reboot */ 0x00, /* boot partition identifier */ error)) { g_prefix_error (error, "failed to commit to auto slot: "); return FALSE; } /* success! */ fu_device_set_progress (device, 100); return TRUE; } static gboolean fu_nvme_device_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE (device); if (g_strcmp0 (key, "NvmeBlockSize") == 0) { self->write_block_size = fu_common_strtoull (value); return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_nvme_device_init (FuNvmeDevice *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_summary (FU_DEVICE (self), "NVM Express Solid State Drive"); fu_device_add_icon (FU_DEVICE (self), "drive-harddisk"); } static void fu_nvme_device_finalize (GObject *object) { G_OBJECT_CLASS (fu_nvme_device_parent_class)->finalize (object); } static void fu_nvme_device_class_init (FuNvmeDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUdevDeviceClass *klass_udev_device = FU_UDEV_DEVICE_CLASS (klass); object_class->finalize = fu_nvme_device_finalize; klass_device->to_string = fu_nvme_device_to_string; klass_device->set_quirk_kv = fu_nvme_device_set_quirk_kv; klass_device->open = fu_nvme_device_open; klass_device->setup = fu_nvme_device_setup; klass_device->close = fu_nvme_device_close; klass_device->write_firmware = fu_nvme_device_write_firmware; klass_udev_device->probe = fu_nvme_device_probe; } FuNvmeDevice * fu_nvme_device_new (FuUdevDevice *device) { FuNvmeDevice *self = g_object_new (FU_TYPE_NVME_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } FuNvmeDevice * fu_nvme_device_new_from_blob (const guint8 *buf, gsize sz, GError **error) { g_autoptr(FuNvmeDevice) self = g_object_new (FU_TYPE_NVME_DEVICE, NULL); if (!fu_nvme_device_parse_cns (self, buf, sz, error)) return NULL; return g_steal_pointer (&self); } fwupd-1.2.14/plugins/nvme/fu-nvme-device.h000066400000000000000000000007441402665037500203520ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_NVME_DEVICE (fu_nvme_device_get_type ()) G_DECLARE_FINAL_TYPE (FuNvmeDevice, fu_nvme_device, FU, NVME_DEVICE, FuUdevDevice) FuNvmeDevice *fu_nvme_device_new (FuUdevDevice *device); FuNvmeDevice *fu_nvme_device_new_from_blob (const guint8 *buf, gsize sz, GError **error); G_END_DECLS fwupd-1.2.14/plugins/nvme/fu-plugin-nvme.c000066400000000000000000000023041402665037500203760ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-nvme-device.h" gboolean fu_plugin_udev_device_added (FuPlugin *plugin, FuUdevDevice *device, GError **error) { g_autoptr(FuNvmeDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* interesting device? */ if (g_strcmp0 (fu_udev_device_get_subsystem (device), "nvme") != 0) return TRUE; dev = fu_nvme_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_udev_subsystem (plugin, "nvme"); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "org.nvmexpress"); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } fwupd-1.2.14/plugins/nvme/fu-self-test.c000066400000000000000000000050541402665037500200500ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-device-private.h" #include "fu-nvme-device.h" #include "fu-test.h" static void fu_nvme_cns_func (void) { gboolean ret; gsize sz; g_autofree gchar *data = NULL; g_autofree gchar *path = NULL; g_autoptr(FuNvmeDevice) dev = NULL; g_autoptr(GError) error = NULL; path = fu_test_get_filename (TESTDATADIR, "TOSHIBA_THNSN5512GPU7.bin"); g_assert_nonnull (path); ret = g_file_get_contents (path, &data, &sz, &error); g_assert_no_error (error); g_assert (ret); dev = fu_nvme_device_new_from_blob ((guint8 *)data, sz, &error); g_assert_no_error (error); g_assert_nonnull (dev); fu_device_convert_instance_ids (FU_DEVICE (dev)); g_assert_cmpstr (fu_device_get_name (FU_DEVICE (dev)), ==, "THNSN5512GPU7 TOSHIBA"); g_assert_cmpstr (fu_device_get_version (FU_DEVICE (dev)), ==, "410557LA"); g_assert_cmpstr (fu_device_get_serial (FU_DEVICE (dev)), ==, "37RSDEADBEEF"); g_assert_cmpstr (fu_device_get_guid_default (FU_DEVICE (dev)), ==, "e1409b09-50cf-5aef-8ad8-760b9022f88d"); } static void fu_nvme_cns_all_func (void) { const gchar *fn; g_autofree gchar *path = NULL; g_autoptr(GDir) dir = NULL; /* may or may not exist */ path = fu_test_get_filename (TESTDATADIR, "blobs"); if (path == NULL) return; dir = g_dir_open (path, 0, NULL); while ((fn = g_dir_read_name (dir)) != NULL) { gsize sz; g_autofree gchar *data = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuNvmeDevice) dev = NULL; g_autoptr(GError) error = NULL; filename = g_build_filename (path, fn, NULL); g_print ("parsing %s... ", filename); if (!g_file_get_contents (filename, &data, &sz, &error)) { g_print ("failed to load %s: %s\n", filename, error->message); continue; } dev = fu_nvme_device_new_from_blob ((guint8 *) data, sz, &error); if (dev == NULL) { g_print ("failed to load %s: %s\n", filename, error->message); continue; } g_assert_cmpstr (fu_device_get_name (FU_DEVICE (dev)), !=, NULL); g_assert_cmpstr (fu_device_get_version (FU_DEVICE (dev)), !=, NULL); g_assert_cmpstr (fu_device_get_serial (FU_DEVICE (dev)), !=, NULL); g_print ("done\n"); } } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func ("/fwupd/cns", fu_nvme_cns_func); g_test_add_func ("/fwupd/cns{all}", fu_nvme_cns_all_func); return g_test_run (); } fwupd-1.2.14/plugins/nvme/meson.build000066400000000000000000000023461402665037500175310ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginNvme"'] install_data([ 'nvme.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_nvme', fu_hash, sources : [ 'fu-plugin-nvme.c', 'fu-nvme-common.c', 'fu-nvme-device.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with : [ libfwupdprivate, ], dependencies : [ plugin_deps, ], ) if get_option('tests') testdatadir = join_paths(meson.current_source_dir(), 'tests') cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'nvme-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-nvme-common.c', 'fu-nvme-device.c', ], include_directories : [ include_directories('..'), include_directories('../..'), include_directories('../../libfwupd'), include_directories('../../src'), ], dependencies : [ plugin_deps, ], link_with : [ libfwupdprivate, ], c_args : cargs ) test('nvme-self-test', e) endif fwupd-1.2.14/plugins/nvme/nvme.quirk000066400000000000000000000001151402665037500174010ustar00rootroot00000000000000# Phison [DeviceInstanceId=NVME\VEN_1987] Flags = force-align,needs-shutdown fwupd-1.2.14/plugins/nvme/tests/000077500000000000000000000000001402665037500165245ustar00rootroot00000000000000fwupd-1.2.14/plugins/nvme/tests/.gitignore000066400000000000000000000000061402665037500205100ustar00rootroot00000000000000blobs fwupd-1.2.14/plugins/nvme/tests/TOSHIBA_THNSN5512GPU7.bin000066400000000000000000000100001402665037500222300ustar00rootroot00000000000000yy 37RSDEADBEEFTHNSN5512GPU7 TOSHIBA 410557LA _cfD x'<Pfwupd-1.2.14/plugins/redfish/000077500000000000000000000000001402665037500160415ustar00rootroot00000000000000fwupd-1.2.14/plugins/redfish/README.md000066400000000000000000000041421402665037500173210ustar00rootroot00000000000000Redfish Support =============== Introduction ------------ Redfish is an open industry standard specification and schema that helps enable simple and secure management of modern scalable platform hardware. By specifying a RESTful interface and utilizing JSON and OData, Redfish helps customers integrate solutions within their existing tool chains. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * org.dmtf.redfish GUID Generation --------------- These devices use the provided GUID provided in the `SoftwareId` parameter without modification. Devices without GUIDs are not supported. Setting Service IP Manually --------------------------- The service IP may not be automatically discoverable due to the absence of Type 0x42 entry in SMBIOS. In this case, you have to specify the service IP to RedfishUri in /etc/fwupd/redfish.conf Take HPE Gen10 for example, the service IP can be found with the following command: # ilorest --nologo list --selector=EthernetInterface. -j This command lists all network interfaces, and the Redfish service IP belongs to one of "Manager Network" Interfaces. For example: { "@odata.context": "/redfish/v1/$metadata#EthernetInterface.EthernetInterface", "@odata.id": "/redfish/v1/Managers/1/EthernetInterfaces/1/", "@odata.type": "#EthernetInterface.v1_0_3.EthernetInterface", "Description": "Configuration of this Manager Network Interface", "HostName": "myredfish", "IPv4Addresses": [ { "SubnetMask": "255.255.255.0", "AddressOrigin": "DHCP", "Gateway": "192.168.0.1", "Address": "192.168.0.133" } ], ... In this example, the service IP is "192.168.0.133". Since the conventional HTTP port is 80 and HTTPS port is 443, we can set RedfishUri to either "http://192.168.0.133:80" or "https://192.168.0.133:443" and verify the uri with $ curl http://192.168.0.133:80/redfish/v1/ or $ curl -k https://192.168.0.133:443/redfish/v1/ fwupd-1.2.14/plugins/redfish/fu-plugin-redfish.c000066400000000000000000000070701402665037500215410ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-redfish-client.h" #include "fu-redfish-common.h" struct FuPluginData { FuRedfishClient *client; }; gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); return fu_redfish_client_update (data->client, device, blob_fw, error); } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); GPtrArray *devices; /* get the list of devices */ if (!fu_redfish_client_coldplug (data->client, error)) return FALSE; devices = fu_redfish_client_get_devices (data->client); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); fu_plugin_device_add (plugin, device); } return TRUE; } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autofree gchar *redfish_uri = NULL; g_autofree gchar *ca_check = NULL; g_autoptr(GBytes) smbios_data = NULL; /* optional */ smbios_data = fu_plugin_get_smbios_data (plugin, REDFISH_SMBIOS_TABLE_TYPE); /* read the conf file */ redfish_uri = fu_plugin_get_config_value (plugin, "Uri"); if (redfish_uri != NULL) { g_autofree gchar *username = NULL; g_autofree gchar *password = NULL; const gchar *ip_str = NULL; g_auto(GStrv) split = NULL; guint64 port; if (g_str_has_prefix (redfish_uri, "https://")) { fu_redfish_client_set_https (data->client, TRUE); ip_str = redfish_uri + strlen ("https://"); } else if (g_str_has_prefix (redfish_uri, "http://")) { fu_redfish_client_set_https (data->client, FALSE); ip_str = redfish_uri + strlen ("http://"); } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "in valid scheme"); return FALSE; } split = g_strsplit (ip_str, ":", 2); fu_redfish_client_set_hostname (data->client, split[0]); port = g_ascii_strtoull (split[1], NULL, 10); if (port == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no port specified"); return FALSE; } fu_redfish_client_set_port (data->client, port); username = fu_plugin_get_config_value (plugin, "Username"); password = fu_plugin_get_config_value (plugin, "Password"); if (username != NULL && password != NULL) { fu_redfish_client_set_username (data->client, username); fu_redfish_client_set_password (data->client, password); } } else { if (smbios_data == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no SMBIOS table"); return FALSE; } } ca_check = fu_plugin_get_config_value (plugin, "CACheck"); if (ca_check != NULL && g_ascii_strcasecmp (ca_check, "false") == 0) fu_redfish_client_set_cacheck (data->client, FALSE); else fu_redfish_client_set_cacheck (data->client, TRUE); return fu_redfish_client_setup (data->client, smbios_data, error); } void fu_plugin_init (FuPlugin *plugin) { FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); data->client = fu_redfish_client_new (); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "org.dmtf.redfish"); fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); g_object_unref (data->client); } fwupd-1.2.14/plugins/redfish/fu-redfish-client.c000066400000000000000000000570421402665037500215250ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fwupd-error.h" #include "fwupd-enums.h" #include "fu-device.h" #include "fu-redfish-client.h" #include "fu-redfish-common.h" struct _FuRedfishClient { GObject parent_instance; SoupSession *session; gchar *hostname; guint port; gchar *username; gchar *password; gchar *update_uri_path; gchar *push_uri_path; gboolean auth_created; gboolean use_https; gboolean cacheck; GPtrArray *devices; }; G_DEFINE_TYPE (FuRedfishClient, fu_redfish_client, G_TYPE_OBJECT) static void fu_redfish_client_set_auth (FuRedfishClient *self, SoupURI *uri, SoupMessage *msg) { if ((self->username != NULL && self->password != NULL) && self->auth_created == FALSE) { /* * Some redfish implementations miss WWW-Authenticate * header for a 401 response, and SoupAuthManager couldn't * generate SoupAuth accordingly. Since DSP0266 makes * Basic Authorization a requirement for redfish, it shall be * safe to use Basic Auth for all redfish implementations. */ SoupAuthManager *manager = SOUP_AUTH_MANAGER (soup_session_get_feature (self->session, SOUP_TYPE_AUTH_MANAGER)); g_autoptr(SoupAuth) auth = soup_auth_new (SOUP_TYPE_AUTH_BASIC, msg, "Basic"); soup_auth_authenticate (auth, self->username, self->password); soup_auth_manager_use_auth (manager, uri, auth); self->auth_created = TRUE; } } static GBytes * fu_redfish_client_fetch_data (FuRedfishClient *self, const gchar *uri_path, GError **error) { guint status_code; g_autoptr(SoupMessage) msg = NULL; g_autoptr(SoupURI) uri = NULL; /* create URI */ uri = soup_uri_new (NULL); soup_uri_set_scheme (uri, self->use_https ? "https" : "http"); soup_uri_set_path (uri, uri_path); soup_uri_set_host (uri, self->hostname); soup_uri_set_port (uri, self->port); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); if (msg == NULL) { g_autofree gchar *tmp = soup_uri_to_string (uri, FALSE); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to create message for URI %s", tmp); return NULL; } fu_redfish_client_set_auth (self, uri, msg); status_code = soup_session_send_message (self->session, msg); if (status_code != SOUP_STATUS_OK) { g_autofree gchar *tmp = soup_uri_to_string (uri, FALSE); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to download %s: %s", tmp, soup_status_get_phrase (status_code)); return NULL; } return g_bytes_new (msg->response_body->data, msg->response_body->length); } static gboolean fu_redfish_client_coldplug_member (FuRedfishClient *self, JsonObject *member, GError **error) { g_autoptr(FuDevice) dev = NULL; const gchar *guid = NULL; g_autofree gchar *id = NULL; if (json_object_has_member (member, "SoftwareId")) { guid = json_object_get_string_member (member, "SoftwareId"); } else if (json_object_has_member (member, "Oem")) { JsonObject *oem = json_object_get_object_member (member, "Oem"); if (oem != NULL && json_object_has_member (oem, "Hpe")) { JsonObject *hpe = json_object_get_object_member (oem, "Hpe"); if (hpe != NULL && json_object_has_member (hpe, "DeviceClass")) guid = json_object_get_string_member (hpe, "DeviceClass"); } } /* skip the devices without guid */ if (guid == NULL) return TRUE; dev = fu_device_new (); id = g_strdup_printf ("Redfish-Inventory-%s", json_object_get_string_member (member, "Id")); fu_device_set_id (dev, id); fu_device_add_guid (dev, guid); if (json_object_has_member (member, "Name")) fu_device_set_name (dev, json_object_get_string_member (member, "Name")); fu_device_set_summary (dev, "Redfish device"); if (json_object_has_member (member, "Version")) { fu_device_set_version (dev, json_object_get_string_member (member, "Version"), FWUPD_VERSION_FORMAT_UNKNOWN); } if (json_object_has_member (member, "LowestSupportedVersion")) fu_device_set_version_lowest (dev, json_object_get_string_member (member, "LowestSupportedVersion")); if (json_object_has_member (member, "Description")) fu_device_set_description (dev, json_object_get_string_member (member, "Description")); if (json_object_has_member (member, "Updateable")) { if (json_object_get_boolean_member (member, "Updateable")) fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); } else { /* assume the device is updatable */ fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); } /* success */ g_ptr_array_add (self->devices, g_steal_pointer (&dev)); return TRUE; } static gboolean fu_redfish_client_coldplug_collection (FuRedfishClient *self, JsonObject *collection, GError **error) { JsonArray *members; JsonNode *node_root; JsonObject *member; members = json_object_get_array_member (collection, "Members"); for (guint i = 0; i < json_array_get_length (members); i++) { g_autoptr(JsonParser) parser = json_parser_new (); g_autoptr(GBytes) blob = NULL; JsonObject *member_id; const gchar *member_uri; member_id = json_array_get_object_element (members, i); member_uri = json_object_get_string_member (member_id, "@odata.id"); if (member_uri == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no @odata.id string"); return FALSE; } /* try to connect */ blob = fu_redfish_client_fetch_data (self, member_uri, error); if (blob == NULL) return FALSE; /* get the member object */ if (!json_parser_load_from_data (parser, g_bytes_get_data (blob, NULL), (gssize) g_bytes_get_size (blob), error)) { g_prefix_error (error, "failed to parse node: "); return FALSE; } node_root = json_parser_get_root (parser); if (node_root == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no root node"); return FALSE; } member = json_node_get_object (node_root); if (member == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no member object"); return FALSE; } /* Create the device for the member */ if (!fu_redfish_client_coldplug_member (self, member, error)) return FALSE; } return TRUE; } static gboolean fu_redfish_client_coldplug_inventory (FuRedfishClient *self, JsonObject *inventory, GError **error) { g_autoptr(JsonParser) parser = json_parser_new (); g_autoptr(GBytes) blob = NULL; JsonNode *node_root; JsonObject *collection; const gchar *collection_uri; if (inventory == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no inventory object"); return FALSE; } collection_uri = json_object_get_string_member (inventory, "@odata.id"); if (collection_uri == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no @odata.id string"); return FALSE; } /* try to connect */ blob = fu_redfish_client_fetch_data (self, collection_uri, error); if (blob == NULL) return FALSE; /* get the inventory object */ if (!json_parser_load_from_data (parser, g_bytes_get_data (blob, NULL), (gssize) g_bytes_get_size (blob), error)) { g_prefix_error (error, "failed to parse node: "); return FALSE; } node_root = json_parser_get_root (parser); if (node_root == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no root node"); return FALSE; } collection = json_node_get_object (node_root); if (collection == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no collection object"); return FALSE; } return fu_redfish_client_coldplug_collection (self, collection, error); } gboolean fu_redfish_client_coldplug (FuRedfishClient *self, GError **error) { JsonNode *node_root; JsonObject *obj_root = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(JsonParser) parser = json_parser_new (); /* nothing set */ if (self->update_uri_path == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no update_uri_path"); return FALSE; } /* try to connect */ blob = fu_redfish_client_fetch_data (self, self->update_uri_path, error); if (blob == NULL) return FALSE; /* get the update service */ if (!json_parser_load_from_data (parser, g_bytes_get_data (blob, NULL), (gssize) g_bytes_get_size (blob), error)) { g_prefix_error (error, "failed to parse node: "); return FALSE; } node_root = json_parser_get_root (parser); if (node_root == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no root node"); return FALSE; } obj_root = json_node_get_object (node_root); if (obj_root == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no root object"); return FALSE; } if (!json_object_get_boolean_member (obj_root, "ServiceEnabled")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "service is not enabled"); return FALSE; } if (!json_object_has_member (obj_root, "HttpPushUri")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "HttpPushUri is not available"); return FALSE; } self->push_uri_path = g_strdup (json_object_get_string_member (obj_root, "HttpPushUri")); if (self->push_uri_path == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "HttpPushUri is invalid"); return FALSE; } if (json_object_has_member (obj_root, "FirmwareInventory")) { JsonObject *tmp = json_object_get_object_member (obj_root, "FirmwareInventory"); return fu_redfish_client_coldplug_inventory (self, tmp, error); } if (json_object_has_member (obj_root, "SoftwareInventory")) { JsonObject *tmp = json_object_get_object_member (obj_root, "SoftwareInventory"); return fu_redfish_client_coldplug_inventory (self, tmp, error); } return TRUE; } static gboolean fu_redfish_client_set_uefi_credentials (FuRedfishClient *self, GError **error) { guint32 indications_le; g_autofree gchar *userpass_safe = NULL; g_auto(GStrv) split = NULL; g_autoptr(GBytes) indications = NULL; g_autoptr(GBytes) userpass = NULL; /* get the uint32 specifying if there are EFI variables set */ indications = fu_redfish_common_get_evivar_raw (REDFISH_EFI_INFORMATION_GUID, REDFISH_EFI_INFORMATION_INDICATIONS, error); if (indications == NULL) return FALSE; if (g_bytes_get_size (indications) != 4) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid value for %s, got %" G_GSIZE_FORMAT " bytes", REDFISH_EFI_INFORMATION_INDICATIONS, g_bytes_get_size (indications)); return FALSE; } memcpy (&indications_le, g_bytes_get_data (indications, NULL), 4); if ((indications_le & REDFISH_EFI_INDICATIONS_OS_CREDENTIALS) == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no indications for OS credentials"); return FALSE; } /* read the correct EFI var for runtime */ userpass = fu_redfish_common_get_evivar_raw (REDFISH_EFI_INFORMATION_GUID, REDFISH_EFI_INFORMATION_OS_CREDENTIALS, error); if (userpass == NULL) return FALSE; /* it might not be NUL terminated */ userpass_safe = g_strndup (g_bytes_get_data (userpass, NULL), g_bytes_get_size (userpass)); split = g_strsplit (userpass_safe, ":", -1); if (g_strv_length (split) != 2) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid format for username:password, got '%s'", userpass_safe); return FALSE; } fu_redfish_client_set_username (self, split[0]); fu_redfish_client_set_password (self, split[1]); return TRUE; } static void fu_redfish_client_parse_interface_data (const guint8 *buf, guint8 sz) { switch (buf[0]) { case REDFISH_INTERFACE_TYPE_USB_NEWORK: g_debug ("USB Network Interface"); /* * uint16 idVendor(2-bytes) * uint16 idProduct(2-bytes) * uint8 SerialNumberLen: * uint8 DescriptorType: * uint8* SerialNumber: */ break; case REDFISH_INTERFACE_TYPE_PCI_NEWORK: g_debug ("PCI Network Interface"); /* * uint16 VendorID * uint16 DeviceID * uint16 Subsystem_Vendor_ID * uint16 Subsystem_ID */ break; default: break; } } typedef struct __attribute__((packed)) { guint8 service_uuid[16]; guint8 host_ip_assignment_type; guint8 host_ip_address_format; guint8 host_ip_address[16]; guint8 host_ip_mask[16]; guint8 service_ip_assignment_type; guint8 service_ip_address_format; guint8 service_ip_address[16]; guint8 service_ip_mask[16]; guint16 service_ip_port; guint32 service_ip_vlan_id; guint8 service_hostname_len; /* service_hostname; */ } RedfishProtocolDataOverIp; static gboolean fu_redfish_client_parse_protocol_data (FuRedfishClient *self, const guint8 *buf, guint8 sz, GError **error) { RedfishProtocolDataOverIp *pr; if (sz < sizeof(RedfishProtocolDataOverIp)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "protocol data too small"); return FALSE; } pr = (RedfishProtocolDataOverIp *) buf; /* parse the hostname and port */ if (pr->service_ip_assignment_type == REDFISH_IP_ASSIGNMENT_TYPE_STATIC || pr->service_ip_assignment_type == REDFISH_IP_ASSIGNMENT_TYPE_AUTO_CONFIG) { if (pr->service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V4) { g_autofree gchar *tmp = NULL; tmp = fu_redfish_common_buffer_to_ipv4 (pr->service_ip_address); fu_redfish_client_set_hostname (self, tmp); } else if (pr->service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V6) { g_autofree gchar *tmp = NULL; tmp = fu_redfish_common_buffer_to_ipv6 (pr->service_ip_address); fu_redfish_client_set_hostname (self, tmp); } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "address format is invalid"); return FALSE; } fu_redfish_client_set_port (self, pr->service_ip_port); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "DHCP address formats not supported (%0x2)", pr->service_ip_assignment_type); return FALSE; } return TRUE; } static gboolean fu_redfish_client_set_smbios_interfaces (FuRedfishClient *self, GBytes *smbios_table, GError **error) { const guint8 *buf; gsize sz = 0; /* check size */ buf = g_bytes_get_data (smbios_table, &sz); if (sz < 0x09) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "SMBIOS entry too small: %" G_GSIZE_FORMAT, sz); return FALSE; } /* check interface type */ if (buf[0x04] != REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "only Network Host Interface supported"); return FALSE; } /* check length */ if (buf[0x05] > sz - 0x08) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "interface specific data too large %u > %" G_GSIZE_FORMAT, buf[0x05], sz - 0x08); return FALSE; } /* parse data, for not just for debugging */ if (buf[0x05] > 0) fu_redfish_client_parse_interface_data (&buf[0x06], buf[0x05]); /* parse protocol records */ for (guint8 i = 0x07 + buf[0x05]; i < sz - 1; i++) { guint8 protocol_id = buf[i]; guint8 protocol_sz = buf[i+1]; if (protocol_sz > sz - buf[0x05] + 0x07) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "protocol length too large"); return FALSE; } if (protocol_id == REDFISH_PROTOCOL_REDFISH_OVER_IP) { if (!fu_redfish_client_parse_protocol_data (self, &buf[i+2], protocol_sz, error)) return FALSE; } else { g_debug ("ignoring unsupported protocol ID %02x", protocol_id); } i += protocol_sz - 1; } return TRUE; } gboolean fu_redfish_client_update (FuRedfishClient *self, FuDevice *device, GBytes *blob_fw, GError **error) { FwupdRelease *release; g_autofree gchar *filename = NULL; guint status_code; g_autoptr(SoupMessage) msg = NULL; g_autoptr(SoupURI) uri = NULL; g_autoptr(SoupMultipart) multipart = NULL; g_autoptr(SoupBuffer) buffer = NULL; g_autofree gchar *uri_str = NULL; /* Get the update version */ release = fwupd_device_get_release_default (FWUPD_DEVICE (device)); if (release != NULL) { filename = g_strdup_printf ("%s-%s.bin", fu_device_get_name (device), fwupd_release_get_version (release)); } else { filename = g_strdup_printf ("%s.bin", fu_device_get_name (device)); } /* create URI */ uri = soup_uri_new (NULL); soup_uri_set_scheme (uri, self->use_https ? "https" : "http"); soup_uri_set_path (uri, self->push_uri_path); soup_uri_set_host (uri, self->hostname); soup_uri_set_port (uri, self->port); uri_str = soup_uri_to_string (uri, FALSE); /* Create the multipart request */ multipart = soup_multipart_new (SOUP_FORM_MIME_TYPE_MULTIPART); buffer = soup_buffer_new (SOUP_MEMORY_COPY, g_bytes_get_data (blob_fw, NULL), g_bytes_get_size (blob_fw)); soup_multipart_append_form_file (multipart, filename, filename, "application/octet-stream", buffer); msg = soup_form_request_new_from_multipart (uri_str, multipart); if (msg == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to create message for URI %s", uri_str); return FALSE; } fu_redfish_client_set_auth (self, uri, msg); status_code = soup_session_send_message (self->session, msg); if (status_code != SOUP_STATUS_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to upload %s to %s: %s", filename, uri_str, soup_status_get_phrase (status_code)); return FALSE; } return TRUE; } gboolean fu_redfish_client_setup (FuRedfishClient *self, GBytes *smbios_table, GError **error) { JsonNode *node_root; JsonObject *obj_root = NULL; JsonObject *obj_update_service = NULL; const gchar *data_id; const gchar *version = NULL; g_autofree gchar *user_agent = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(JsonParser) parser = json_parser_new (); /* sanity check */ if (self->port == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no port specified"); return FALSE; } if (self->port > 0xffff) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid port specified: 0x%x", self->port); return FALSE; } /* create the soup session */ user_agent = g_strdup_printf ("%s/%s", PACKAGE_NAME, PACKAGE_VERSION); self->session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent, SOUP_SESSION_TIMEOUT, 60, NULL); if (self->session == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to setup networking"); return FALSE; } if (self->cacheck == FALSE) { g_object_set (G_OBJECT (self->session), SOUP_SESSION_SSL_STRICT, FALSE, NULL); } /* this is optional */ if (smbios_table != NULL) { g_autoptr(GError) error_smbios = NULL; g_autoptr(GError) error_uefi = NULL; if (!fu_redfish_client_set_smbios_interfaces (self, smbios_table, &error_smbios)) { g_debug ("failed to get connection URI automatically: %s", error_smbios->message); } if (!fu_redfish_client_set_uefi_credentials (self, &error_uefi)) { g_debug ("failed to get username and password automatically: %s", error_uefi->message); } } if (self->hostname != NULL) g_debug ("Hostname: %s", self->hostname); if (self->port != 0) g_debug ("Port: %u", self->port); if (self->username != NULL) g_debug ("Username: %s", self->username); if (self->password != NULL) g_debug ("Password: %s", self->password); /* try to connect */ blob = fu_redfish_client_fetch_data (self, "/redfish/v1/", error); if (blob == NULL) return FALSE; /* get the update service */ if (!json_parser_load_from_data (parser, g_bytes_get_data (blob, NULL), (gssize) g_bytes_get_size (blob), error)) { g_prefix_error (error, "failed to parse node: "); return FALSE; } node_root = json_parser_get_root (parser); if (node_root == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no root node"); return FALSE; } obj_root = json_node_get_object (node_root); if (obj_root == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no root object"); return FALSE; } if (json_object_has_member (obj_root, "ServiceVersion")) { version = json_object_get_string_member (obj_root, "ServiceVersion"); } else if (json_object_has_member (obj_root, "RedfishVersion")) { version = json_object_get_string_member (obj_root, "RedfishVersion"); } g_debug ("Version: %s", version); g_debug ("UUID: %s", json_object_get_string_member (obj_root, "UUID")); if (json_object_has_member (obj_root, "UpdateService")) obj_update_service = json_object_get_object_member (obj_root, "UpdateService"); if (obj_update_service == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no UpdateService object"); return FALSE; } data_id = json_object_get_string_member (obj_update_service, "@odata.id"); if (data_id == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no @odata.id string"); return FALSE; } self->update_uri_path = g_strdup (data_id); return TRUE; } GPtrArray * fu_redfish_client_get_devices (FuRedfishClient *self) { return self->devices; } void fu_redfish_client_set_hostname (FuRedfishClient *self, const gchar *hostname) { g_free (self->hostname); self->hostname = g_strdup (hostname); } void fu_redfish_client_set_port (FuRedfishClient *self, guint port) { self->port = port; } void fu_redfish_client_set_https (FuRedfishClient *self, gboolean use_https) { self->use_https = use_https; } void fu_redfish_client_set_cacheck (FuRedfishClient *self, gboolean cacheck) { self->cacheck = cacheck; } void fu_redfish_client_set_username (FuRedfishClient *self, const gchar *username) { g_free (self->username); self->username = g_strdup (username); } void fu_redfish_client_set_password (FuRedfishClient *self, const gchar *password) { g_free (self->password); self->password = g_strdup (password); } static void fu_redfish_client_finalize (GObject *object) { FuRedfishClient *self = FU_REDFISH_CLIENT (object); if (self->session != NULL) g_object_unref (self->session); g_free (self->update_uri_path); g_free (self->push_uri_path); g_free (self->hostname); g_free (self->username); g_free (self->password); g_ptr_array_unref (self->devices); G_OBJECT_CLASS (fu_redfish_client_parent_class)->finalize (object); } static void fu_redfish_client_class_init (FuRedfishClientClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_redfish_client_finalize; } static void fu_redfish_client_init (FuRedfishClient *self) { self->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); } FuRedfishClient * fu_redfish_client_new (void) { FuRedfishClient *self; self = g_object_new (REDFISH_TYPE_CLIENT, NULL); return FU_REDFISH_CLIENT (self); } /* vim: set noexpandtab: */ fwupd-1.2.14/plugins/redfish/fu-redfish-client.h000066400000000000000000000024571402665037500215320ustar00rootroot00000000000000 /* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define REDFISH_TYPE_CLIENT (fu_redfish_client_get_type ()) G_DECLARE_FINAL_TYPE (FuRedfishClient, fu_redfish_client, FU, REDFISH_CLIENT, GObject) FuRedfishClient *fu_redfish_client_new (void); void fu_redfish_client_set_hostname (FuRedfishClient *self, const gchar *hostname); void fu_redfish_client_set_username (FuRedfishClient *self, const gchar *username); void fu_redfish_client_set_password (FuRedfishClient *self, const gchar *password); void fu_redfish_client_set_port (FuRedfishClient *self, guint port); void fu_redfish_client_set_https (FuRedfishClient *self, gboolean use_https); void fu_redfish_client_set_cacheck (FuRedfishClient *self, gboolean cacheck); gboolean fu_redfish_client_update (FuRedfishClient *self, FuDevice *device, GBytes *blob_fw, GError **error); gboolean fu_redfish_client_setup (FuRedfishClient *self, GBytes *smbios_table, GError **error); gboolean fu_redfish_client_coldplug (FuRedfishClient *self, GError **error); GPtrArray *fu_redfish_client_get_devices (FuRedfishClient *self); G_END_DECLS fwupd-1.2.14/plugins/redfish/fu-redfish-common.c000066400000000000000000000024641402665037500215350ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-error.h" #include "fu-redfish-common.h" GBytes * fu_redfish_common_get_evivar_raw (efi_guid_t guid, const gchar *name, GError **error) { gsize sz = 0; guint32 attribs = 0; guint8 *data = NULL; if (efi_get_variable (guid, name, &data, &sz, &attribs) < 0) { g_autofree gchar *guid_str = NULL; efi_guid_to_str (&guid, &guid_str); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to get efivar for %s %s", guid_str, name); return NULL; } return g_bytes_new_take (data, sz); } gchar * fu_redfish_common_buffer_to_ipv4 (const guint8 *buffer) { GString *str = g_string_new (NULL); for (guint i = 0; i < 4; i++) { g_string_append_printf (str, "%u", buffer[i]); if (i != 3) g_string_append (str, "."); } return g_string_free (str, FALSE); } gchar * fu_redfish_common_buffer_to_ipv6 (const guint8 *buffer) { GString *str = g_string_new (NULL); for (guint i = 0; i < 16; i += 4) { g_string_append_printf (str, "%02x%02x%02x%02x", buffer[i+0], buffer[i+1], buffer[i+2], buffer[i+3]); if (i != 12) g_string_append (str, ":"); } return g_string_free (str, FALSE); } /* vim: set noexpandtab: */ fwupd-1.2.14/plugins/redfish/fu-redfish-common.h000066400000000000000000000027411402665037500215400ustar00rootroot00000000000000 /* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include G_BEGIN_DECLS /* SMBIOS */ #define REDFISH_SMBIOS_TABLE_TYPE 0x42 #define REDFISH_PROTOCOL_REDFISH_OVER_IP 0x04 #define REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST 0x40 #define REDFISH_INTERFACE_TYPE_USB_NEWORK 0x02 #define REDFISH_INTERFACE_TYPE_PCI_NEWORK 0x03 #define REDFISH_IP_ASSIGNMENT_TYPE_STATIC 0x00 #define REDFISH_IP_ASSIGNMENT_TYPE_DHCP 0x02 #define REDFISH_IP_ASSIGNMENT_TYPE_AUTO_CONFIG 0x03 #define REDFISH_IP_ASSIGNMENT_TYPE_HOST_SELECT 0x04 #define REDFISH_IP_ADDRESS_FORMAT_UNKNOWN 0x00 #define REDFISH_IP_ADDRESS_FORMAT_V4 0x01 #define REDFISH_IP_ADDRESS_FORMAT_V6 0x02 /* EFI */ #define REDFISH_EFI_INFORMATION_GUID EFI_GUID(0x16faa37e,0x4b6a,0x4891,0x9028,0x24,0x2d,0xe6,0x5a,0x3b,0x70) #define REDFISH_EFI_INFORMATION_INDICATIONS "RedfishIndications" #define REDFISH_EFI_INFORMATION_FW_CREDENTIALS "RedfishFWCredentials" #define REDFISH_EFI_INFORMATION_OS_CREDENTIALS "RedfishOSCredentials" #define REDFISH_EFI_INDICATIONS_FW_CREDENTIALS 0x00000001 #define REDFISH_EFI_INDICATIONS_OS_CREDENTIALS 0x00000002 /* shared */ GBytes *fu_redfish_common_get_evivar_raw (efi_guid_t guid, const gchar *name, GError **error); gchar *fu_redfish_common_buffer_to_ipv4 (const guint8 *buffer); gchar *fu_redfish_common_buffer_to_ipv6 (const guint8 *buffer); G_END_DECLS fwupd-1.2.14/plugins/redfish/fu-self-test.c000066400000000000000000000016341402665037500205270ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-plugin-private.h" #include "fu-test.h" #include "fu-redfish-common.h" static void fu_test_redfish_common_func (void) { const guint8 buf[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; g_autofree gchar *ipv4 = NULL; g_autofree gchar *ipv6 = NULL; ipv4 = fu_redfish_common_buffer_to_ipv4 (buf); g_assert_cmpstr (ipv4, ==, "0.1.2.3"); ipv6 = fu_redfish_common_buffer_to_ipv6 (buf); g_assert_cmpstr (ipv6, ==, "00010203:04050607:08090a0b:0c0d0e0f"); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_test_add_func ("/redfish/common", fu_test_redfish_common_func); return g_test_run (); } fwupd-1.2.14/plugins/redfish/meson.build000066400000000000000000000021461402665037500202060ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginRedfish"'] shared_module('fu_plugin_redfish', fu_hash, sources : [ 'fu-plugin-redfish.c', 'fu-redfish-client.c', 'fu-redfish-common.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, efivar, libjsonglib, ], ) install_data(['redfish.conf'], install_dir: join_paths(sysconfdir, 'fwupd') ) if get_option('tests') e = executable( 'redfish-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-redfish-client.c', 'fu-redfish-common.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ plugin_deps, efivar, libjsonglib, ], link_with : [ libfwupdprivate, ], c_args : cargs ) test('redfish-self-test', e) endif fwupd-1.2.14/plugins/redfish/redfish.conf000066400000000000000000000004561402665037500203410ustar00rootroot00000000000000[redfish] # The URI to the Redfish service in the format ://: # ex: https://192.168.0.133:443 #Uri= # The username and password to the Redfish service #Username= #Password= # Whether to verify the server certificate or not # Expected value: TRUE or FALSE # Default: TRUE #CACheck= fwupd-1.2.14/plugins/rts54hid/000077500000000000000000000000001402665037500160635ustar00rootroot00000000000000fwupd-1.2.14/plugins/rts54hid/README.md000066400000000000000000000026351402665037500173500ustar00rootroot00000000000000Realtek RTS54HID HID Support ========================= Introduction ------------ This plugin allows the user to update any supported hub and attached downstream ICs using a custom HID-based flashing protocol. It does not support any RTS54xx device using the HUB update protocol. Other devices connected to the RTS54HIDxx using I2C will be supported soon. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * com.realtek.rts54 GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0BDA&PID_1100&REV_0001` * `USB\VID_0BDA&PID_1100` * `USB\VID_0BDA` Child I²C devices are created using the device number as a suffix, for instance: * `USB\VID_0BDA&PID_1100&I2C_01` Quirk use --------- This plugin uses the following plugin-specific quirks: | Quirk | Description | Minimum fwupd version | |------------------------|---------------------------------------------|-----------------------| | `Rts54SlaveAddr` | The slave address of a child module. | 1.1.3 | | `Rts54I2cSpeed` | The I2C speed to operate at (0, 1, 2). | 1.1.3 | | `Rts54RegisterAddrLen` | The I2C register address length of commands | 1.1.3 | fwupd-1.2.14/plugins/rts54hid/fu-plugin-rts54hid.c000066400000000000000000000024031402665037500216000ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-rts54hid-device.h" #include "fu-rts54hid-module.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.realtek.rts54"); /* register the custom types */ g_type_ensure (FU_TYPE_RTS54HID_MODULE); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuRts54HidDevice) dev = NULL; /* open the device */ dev = fu_rts54hid_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; /* success */ fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } fwupd-1.2.14/plugins/rts54hid/fu-rts54hid-common.h000066400000000000000000000030431402665037500216000ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2018 Realtek Semiconductor Corporation * Copyright (C) 2018 Dell Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #define FU_RTS54HID_TRANSFER_BLOCK_SIZE 0x80 #define FU_RTS54HID_REPORT_LENGTH 0xc0 /* [vendor-cmd:64] [data-payload:128] */ #define FU_RTS54HID_CMD_BUFFER_OFFSET_DATA 0x40 typedef struct __attribute__ ((packed)) { guint8 slave_addr; guint8 data_sz; guint8 speed; } FuRts54HidI2cParameters; typedef struct __attribute__ ((packed)) { guint8 cmd; guint8 ext; union { guint32 dwregaddr; struct { guint8 cmd_data0; guint8 cmd_data1; guint8 cmd_data2; guint8 cmd_data3; }; }; guint16 bufferlen; union { FuRts54HidI2cParameters parameters_i2c; guint32 parameters; }; } FuRts54HidCmdBuffer; typedef enum { FU_RTS54HID_I2C_SPEED_250K, FU_RTS54HID_I2C_SPEED_400K, FU_RTS54HID_I2C_SPEED_800K, /* */ FU_RTS54HID_I2C_SPEED_LAST, } FuRts54HidI2cSpeed; typedef enum { FU_RTS54HID_CMD_READ_DATA = 0xc0, FU_RTS54HID_CMD_WRITE_DATA = 0x40, /* */ FU_RTS54HID_CMD_LAST, } FuRts54HidCmd; typedef enum { FU_RTS54HID_EXT_MCUMODIFYCLOCK = 0x06, FU_RTS54HID_EXT_READ_STATUS = 0x09, FU_RTS54HID_EXT_I2C_WRITE = 0xc6, FU_RTS54HID_EXT_WRITEFLASH = 0xc8, FU_RTS54HID_EXT_I2C_READ = 0xd6, FU_RTS54HID_EXT_READFLASH = 0xd8, FU_RTS54HID_EXT_VERIFYUPDATE = 0xd9, FU_RTS54HID_EXT_ERASEBANK = 0xe8, FU_RTS54HID_EXT_RESET2FLASH = 0xe9, /* */ FU_RTS54HID_EXT_LAST, } FuRts54HidExt; fwupd-1.2.14/plugins/rts54hid/fu-rts54hid-device.c000066400000000000000000000251451402665037500215510ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-chunk.h" #include "fu-rts54hid-common.h" #include "fu-rts54hid-device.h" struct _FuRts54HidDevice { FuUsbDevice parent_instance; gboolean fw_auth; gboolean dual_bank; }; G_DEFINE_TYPE (FuRts54HidDevice, fu_rts54hid_device, FU_TYPE_USB_DEVICE) #define FU_RTS54HID_DEVICE_TIMEOUT 1000 /* ms */ static void fu_rts54hid_device_to_string (FuDevice *device, GString *str) { FuRts54HidDevice *self = FU_RTS54HID_DEVICE (device); g_string_append (str, " FuRts54HidDevice:\n"); g_string_append_printf (str, " fw-auth: %i\n", self->fw_auth); g_string_append_printf (str, " dual-bank: %i\n", self->dual_bank); } gboolean fu_rts54hid_device_set_report (FuRts54HidDevice *self, guint8 *buf, gsize buf_sz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_len = 0; if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_SET, 0x0200, 0x0000, buf, buf_sz, &actual_len, FU_RTS54HID_DEVICE_TIMEOUT * 2, NULL, error)) { g_prefix_error (error, "failed to SetReport: "); return FALSE; } if (actual_len != buf_sz) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } gboolean fu_rts54hid_device_get_report (FuRts54HidDevice *self, guint8 *buf, gsize buf_sz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_len = 0; if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_GET, 0x0100, 0x0000, buf, buf_sz, &actual_len, /* actual length */ FU_RTS54HID_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to GetReport: "); return FALSE; } if (actual_len != buf_sz) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } static gboolean fu_rts54hid_device_set_clock_mode (FuRts54HidDevice *self, gboolean enable, GError **error) { FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_MCUMODIFYCLOCK, .cmd_data0 = (guint8) enable, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = 0, .parameters = 0, }; guint8 buf[FU_RTS54HID_REPORT_LENGTH] = { 0 }; memcpy (buf, &cmd_buffer, sizeof(cmd_buffer)); if (!fu_rts54hid_device_set_report (self, buf, sizeof(buf), error)) { g_prefix_error (error, "failed to set clock-mode=%i: ", enable); return FALSE; } return TRUE; } static gboolean fu_rts54hid_device_reset_to_flash (FuRts54HidDevice *self, GError **error) { FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_RESET2FLASH, .dwregaddr = 0, .bufferlen = 0, .parameters = 0, }; guint8 buf[FU_RTS54HID_REPORT_LENGTH] = { 0 }; memcpy (buf, &cmd_buffer, sizeof(cmd_buffer)); if (!fu_rts54hid_device_set_report (self, buf, sizeof(buf), error)) { g_prefix_error (error, "failed to soft reset: "); return FALSE; } return TRUE; } static gboolean fu_rts54hid_device_write_flash (FuRts54HidDevice *self, guint32 addr, const guint8 *data, guint16 data_sz, GError **error) { FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_WRITEFLASH, .dwregaddr = GUINT32_TO_LE (addr), .bufferlen = GUINT16_TO_LE (data_sz), .parameters = 0, }; guint8 buf[FU_RTS54HID_REPORT_LENGTH] = { 0 }; g_return_val_if_fail (data_sz <= 128, FALSE); g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (data_sz != 0, FALSE); memcpy (buf, &cmd_buffer, sizeof(cmd_buffer)); memcpy (buf + FU_RTS54HID_CMD_BUFFER_OFFSET_DATA, data, data_sz); if (!fu_rts54hid_device_set_report (self, buf, sizeof(buf), error)) { g_prefix_error (error, "failed to write flash @%08x: ", (guint) addr); return FALSE; } return TRUE; } static gboolean fu_rts54hid_device_verify_update_fw (FuRts54HidDevice *self, GError **error) { const FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_VERIFYUPDATE, .cmd_data0 = 1, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = GUINT16_TO_LE (1), .parameters = 0, }; guint8 buf[FU_RTS54HID_REPORT_LENGTH] = { 0 }; /* set then get */ memcpy (buf, &cmd_buffer, sizeof(cmd_buffer)); if (!fu_rts54hid_device_set_report (self, buf, sizeof(buf), error)) return FALSE; g_usleep (4 * G_USEC_PER_SEC); if (!fu_rts54hid_device_get_report (self, buf, sizeof(buf), error)) return FALSE; /* check device status */ if (buf[0x40] != 0x01) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "firmware flash failed"); return FALSE; } /* success */ return TRUE; } static gboolean fu_rts54hid_device_erase_spare_bank (FuRts54HidDevice *self, GError **error) { FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_ERASEBANK, .cmd_data0 = 0, .cmd_data1 = 1, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = 0, .parameters = 0, }; guint8 buf[FU_RTS54HID_REPORT_LENGTH] = { 0 }; memcpy (buf, &cmd_buffer, sizeof(cmd_buffer)); if (!fu_rts54hid_device_set_report (self, buf, sizeof(buf), error)) { g_prefix_error (error, "failed to erase spare bank: "); return FALSE; } return TRUE; } static gboolean fu_rts54hid_device_ensure_status (FuRts54HidDevice *self, GError **error) { const FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_READ_DATA, .ext = FU_RTS54HID_EXT_READ_STATUS, .cmd_data0 = 0, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = GUINT16_TO_LE (32), .parameters = 0, }; guint8 buf[FU_RTS54HID_REPORT_LENGTH] = { 0 }; g_autofree gchar *version = NULL; /* set then get */ memcpy (buf, &cmd_buffer, sizeof(cmd_buffer)); if (!fu_rts54hid_device_set_report (self, buf, sizeof(buf), error)) return FALSE; if (!fu_rts54hid_device_get_report (self, buf, sizeof(buf), error)) return FALSE; /* check the hardware capabilities */ self->dual_bank = (buf[0x40 + 7] & 0xf0) == 0x80; self->fw_auth = (buf[0x40 + 13] & 0x02) > 0; /* hub version is more accurate than bcdVersion */ version = g_strdup_printf ("%x.%x", buf[0x40 + 10], buf[0x40 + 11]); fu_device_set_version (FU_DEVICE (self), version, FWUPD_VERSION_FORMAT_PAIR); return TRUE; } static gboolean fu_rts54hid_device_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* disconnect, set config, reattach kernel driver */ if (!g_usb_device_set_configuration (usb_device, 0x00, error)) return FALSE; if (!g_usb_device_claim_interface (usb_device, 0x00, /* HID */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to claim interface: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_rts54hid_device_setup (FuDevice *device, GError **error) { FuRts54HidDevice *self = FU_RTS54HID_DEVICE (device); /* check this device is correct */ if (!fu_rts54hid_device_ensure_status (self, error)) return FALSE; /* both conditions must be set */ if (!self->fw_auth) { fu_device_set_update_error (device, "device does not support authentication"); } else if (!self->dual_bank) { fu_device_set_update_error (device, "device does not support dual-bank updating"); } else { fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); } /* success */ return TRUE; } static gboolean fu_rts54hid_device_close (FuUsbDevice *device, GError **error) { FuRts54HidDevice *self = FU_RTS54HID_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* set MCU to normal clock rate */ if (!fu_rts54hid_device_set_clock_mode (self, FALSE, error)) return FALSE; /* we're done here */ if (!g_usb_device_release_interface (usb_device, 0x00, /* HID */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to release interface: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_rts54hid_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuRts54HidDevice *self = FU_RTS54HID_DEVICE (device); g_autoptr(GPtrArray) chunks = NULL; /* set MCU to high clock rate for better ISP performance */ if (!fu_rts54hid_device_set_clock_mode (self, TRUE, error)) return FALSE; /* erase spare flash bank only if it is not empty */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_rts54hid_device_erase_spare_bank (self, error)) return FALSE; /* build packets */ chunks = fu_chunk_array_new_from_bytes (fw, 0x00, /* start addr */ 0x00, /* page_sz */ FU_RTS54HID_TRANSFER_BLOCK_SIZE); /* write each block */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); /* write chunk */ if (!fu_rts54hid_device_write_flash (self, chk->address, chk->data, chk->data_sz, error)) return FALSE; /* update progress */ fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len * 2); } /* get device to authenticate the firmware */ if (!fu_rts54hid_device_verify_update_fw (self, error)) return FALSE; /* send software reset to run available flash code */ if (!fu_rts54hid_device_reset_to_flash (self, error)) return FALSE; /* success! */ return TRUE; } static void fu_rts54hid_device_init (FuRts54HidDevice *self) { } static void fu_rts54hid_device_class_init (FuRts54HidDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->write_firmware = fu_rts54hid_device_write_firmware; klass_device->to_string = fu_rts54hid_device_to_string; klass_device->setup = fu_rts54hid_device_setup; klass_usb_device->open = fu_rts54hid_device_open; klass_usb_device->close = fu_rts54hid_device_close; } FuRts54HidDevice * fu_rts54hid_device_new (FuUsbDevice *device) { FuRts54HidDevice *self = g_object_new (FU_TYPE_RTS54HID_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } fwupd-1.2.14/plugins/rts54hid/fu-rts54hid-device.h000066400000000000000000000012531402665037500215500ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_RTS54HID_DEVICE (fu_rts54hid_device_get_type ()) G_DECLARE_FINAL_TYPE (FuRts54HidDevice, fu_rts54hid_device, FU, RTS54HID_DEVICE, FuUsbDevice) FuRts54HidDevice *fu_rts54hid_device_new (FuUsbDevice *device); gboolean fu_rts54hid_device_set_report (FuRts54HidDevice *self, guint8 *buf, gsize buf_sz, GError **error); gboolean fu_rts54hid_device_get_report (FuRts54HidDevice *self, guint8 *buf, gsize buf_sz, GError **error); G_END_DECLS fwupd-1.2.14/plugins/rts54hid/fu-rts54hid-module.c000066400000000000000000000162531402665037500215770ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-chunk.h" #include "fu-rts54hid-common.h" #include "fu-rts54hid-module.h" #include "fu-rts54hid-device.h" struct _FuRts54HidModule { FuDevice parent_instance; guint8 slave_addr; guint8 i2c_speed; guint8 register_addr_len; }; G_DEFINE_TYPE (FuRts54HidModule, fu_rts54hid_module, FU_TYPE_DEVICE) static void fu_rts54hid_module_to_string (FuDevice *module, GString *str) { FuRts54HidModule *self = FU_RTS54HID_MODULE (module); g_string_append (str, " FuRts54HidModule:\n"); g_string_append_printf (str, " slave-addr: 0x%02x\n", self->slave_addr); g_string_append_printf (str, " i2c-speed: 0x%02x\n", self->i2c_speed); g_string_append_printf (str, " register_addr_len: 0x%02x\n", self->register_addr_len); } static FuRts54HidDevice * fu_rts54hid_module_get_parent (FuRts54HidModule *self, GError **error) { FuDevice *parent = fu_device_get_parent (FU_DEVICE (self)); if (parent == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no parent set"); return NULL; } return FU_RTS54HID_DEVICE (parent); } static gboolean fu_rts54hid_module_i2c_write (FuRts54HidModule *self, const guint8 *data, guint8 data_sz, GError **error) { FuRts54HidDevice *parent; const FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_I2C_WRITE, .dwregaddr = 0, .bufferlen = GUINT16_TO_LE (data_sz), .parameters_i2c = {.slave_addr = self->slave_addr, .data_sz = self->register_addr_len, .speed = self->i2c_speed | 0x80}, }; guint8 buf[FU_RTS54HID_REPORT_LENGTH] = { 0 }; g_return_val_if_fail (data_sz <= 128, FALSE); g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (data_sz != 0, FALSE); /* get parent to issue command */ parent = fu_rts54hid_module_get_parent (self, error); if (parent == NULL) return FALSE; memcpy (buf, &cmd_buffer, sizeof(cmd_buffer)); memcpy (buf + FU_RTS54HID_CMD_BUFFER_OFFSET_DATA, data, data_sz); if (!fu_rts54hid_device_set_report (parent, buf, sizeof(buf), error)) { g_prefix_error (error, "failed to write i2c @%04x: ", self->slave_addr); return FALSE; } return TRUE; } static gboolean fu_rts54hid_module_i2c_read (FuRts54HidModule *self, guint32 cmd, guint8 *data, guint8 data_sz, GError **error) { FuRts54HidDevice *parent; const FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_I2C_READ, .dwregaddr = GUINT32_TO_LE (cmd), .bufferlen = GUINT16_TO_LE (data_sz), .parameters_i2c = {.slave_addr = self->slave_addr, .data_sz = self->register_addr_len, .speed = self->i2c_speed | 0x80}, }; guint8 buf[FU_RTS54HID_REPORT_LENGTH] = { 0 }; g_return_val_if_fail (data_sz <= 192, FALSE); g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (data_sz != 0, FALSE); /* get parent to issue command */ parent = fu_rts54hid_module_get_parent (self, error); if (parent == NULL) return FALSE; /* read from module */ memcpy (buf, &cmd_buffer, sizeof(cmd_buffer)); if (!fu_rts54hid_device_set_report (parent, buf, sizeof(buf), error)) { g_prefix_error (error, "failed to write i2c @%04x: ", self->slave_addr); return FALSE; } if (!fu_rts54hid_device_get_report (parent, buf, sizeof(buf), error)) return FALSE; memcpy (data, buf + FU_RTS54HID_CMD_BUFFER_OFFSET_DATA, data_sz); return TRUE; } static gboolean fu_rts54hid_module_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuRts54HidModule *self = FU_RTS54HID_MODULE (device); /* load slave address from quirks */ if (g_strcmp0 (key, "Rts54SlaveAddr") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp <= 0xff) { self->slave_addr = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid slave address"); return FALSE; } /* load i2c speed from quirks */ if (g_strcmp0 (key, "Rts54I2cSpeed") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < FU_RTS54HID_I2C_SPEED_LAST) { self->i2c_speed = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid I²C speed"); return FALSE; } /* load register address length from quirks */ if (g_strcmp0 (key, "Rts54RegisterAddrLen") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp <= 0xff) { self->register_addr_len = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid register address length"); return FALSE; } /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_rts54hid_module_open (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); if (parent == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no parent device"); return FALSE; } return fu_device_open (parent, error); } static gboolean fu_rts54hid_module_close (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); if (parent == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no parent device"); return FALSE; } return fu_device_close (parent, error); } static gboolean fu_rts54hid_module_write_firmware (FuDevice *module, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuRts54HidModule *self = FU_RTS54HID_MODULE (module); g_autoptr(GPtrArray) chunks = NULL; /* build packets */ chunks = fu_chunk_array_new_from_bytes (fw, 0x00, /* start addr */ 0x00, /* page_sz */ FU_RTS54HID_TRANSFER_BLOCK_SIZE); if (0) { if (!fu_rts54hid_module_i2c_read (self, 0x0000, NULL, 0, error)) return FALSE; if (!fu_rts54hid_module_i2c_write (self, NULL, 0, error)) return FALSE; } /* write each block */ fu_device_set_status (module, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); /* write chunk */ if (!fu_rts54hid_module_i2c_write (self, chk->data, chk->data_sz, error)) return FALSE; /* update progress */ fu_device_set_progress_full (module, (gsize) i, (gsize) chunks->len * 2); } /* success! */ return TRUE; } static void fu_rts54hid_module_init (FuRts54HidModule *self) { } static void fu_rts54hid_module_class_init (FuRts54HidModuleClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->write_firmware = fu_rts54hid_module_write_firmware; klass_device->to_string = fu_rts54hid_module_to_string; klass_device->set_quirk_kv = fu_rts54hid_module_set_quirk_kv; klass_device->open = fu_rts54hid_module_open; klass_device->close = fu_rts54hid_module_close; } FuRts54HidModule * fu_rts54hid_module_new (void) { FuRts54HidModule *self = NULL; self = g_object_new (FU_TYPE_RTS54HID_MODULE, NULL); return self; } fwupd-1.2.14/plugins/rts54hid/fu-rts54hid-module.h000066400000000000000000000005751402665037500216040ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_RTS54HID_MODULE (fu_rts54hid_module_get_type ()) G_DECLARE_FINAL_TYPE (FuRts54HidModule, fu_rts54hid_module, FU, RTS54HID_MODULE, FuDevice) FuRts54HidModule *fu_rts54hid_module_new (void); G_END_DECLS fwupd-1.2.14/plugins/rts54hid/meson.build000066400000000000000000000011161402665037500202240ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginRts54Hid"'] install_data([ 'rts54hid.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_rts54hid', fu_hash, sources : [ 'fu-rts54hid-device.c', 'fu-rts54hid-module.c', 'fu-plugin-rts54hid.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/rts54hid/rts54hid.quirk000066400000000000000000000006351402665037500206120ustar00rootroot00000000000000# RTS5423 [DeviceInstanceId=USB\VID_0BDA&PID_1100] Plugin = rts54hid FirmwareSizeMin = 0x10000 FirmwareSizeMax = 0x40000 Children = FuRts54HidModule|USB\VID_0BDA&PID_1100&I2C_01 # this is a fictitious example... [DeviceInstanceId=USB\VID_0BDA&PID_1100&I2C_01] Plugin = rts54hid Name = HDMI Converter Flags = updatable FirmwareSize = 0x20000 Rts54SlaveAddr = 0x00 Rts54I2cSpeed = 0x00 Rts54RegisterAddrLen = 0x04 fwupd-1.2.14/plugins/rts54hub/000077500000000000000000000000001402665037500160755ustar00rootroot00000000000000fwupd-1.2.14/plugins/rts54hub/README.md000066400000000000000000000013571402665037500173620ustar00rootroot00000000000000Realtek RTS54 HUB Support ========================= Introduction ------------ This plugin allows the user to update any supported hub and attached downstream ICs using a custom HUB-based flashing protocol. It does not support any RTS54xx device using the HID update protocol. Other devices connected to the RTS54xx using I2C will be supported soon. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * com.realtek.rts54 GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0BDA&PID_5423&REV_0001` * `USB\VID_0BDA&PID_5423` * `USB\VID_0BDA` fwupd-1.2.14/plugins/rts54hub/data/000077500000000000000000000000001402665037500170065ustar00rootroot00000000000000fwupd-1.2.14/plugins/rts54hub/data/lsusb.txt000066400000000000000000000111441402665037500207000ustar00rootroot00000000000000Bus 001 Device 038: ID 0bda:5423 Realtek Semiconductor Corp. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.10 bDeviceClass 9 Hub bDeviceSubClass 0 bDeviceProtocol 2 TT per port bMaxPacketSize0 64 idVendor 0x0bda Realtek Semiconductor Corp. idProduct 0x5423 bcdDevice 1.19 iManufacturer 1 Generic iProduct 2 4-Port USB 2.0 Hub iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xe0 Self Powered Remote Wakeup MaxPower 0mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 9 Hub bInterfaceSubClass 0 bInterfaceProtocol 1 Single TT iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0001 1x 1 bytes bInterval 12 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 1 bNumEndpoints 1 bInterfaceClass 9 Hub bInterfaceSubClass 0 bInterfaceProtocol 2 TT per port iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0001 1x 1 bytes bInterval 12 Hub Descriptor: bLength 9 bDescriptorType 41 nNbrPorts 5 wHubCharacteristic 0x00a9 Per-port power switching Per-port overcurrent protection TT think time 16 FS bits Port indicators bPwrOn2PwrGood 0 * 2 milli seconds bHubContrCurrent 100 milli Ampere DeviceRemovable 0x00 PortPwrCtrlMask 0xff Hub Port Status: Port 1: 0000.0100 power Port 2: 0000.0100 power Port 3: 0000.0100 power Port 4: 0000.0100 power Port 5: 0000.0503 highspeed power enable connect Binary Object Store Descriptor: bLength 5 bDescriptorType 15 wTotalLength 73 bNumDeviceCaps 5 USB 2.0 Extension Device Capability: bLength 7 bDescriptorType 16 bDevCapabilityType 2 bmAttributes 0x0000f41e BESL Link Power Management (LPM) Supported BESL value 1024 us Deep BESL value 61440 us SuperSpeed USB Device Capability: bLength 10 bDescriptorType 16 bDevCapabilityType 3 bmAttributes 0x00 wSpeedsSupported 0x000e Device can operate at Full Speed (12Mbps) Device can operate at High Speed (480Mbps) Device can operate at SuperSpeed (5Gbps) bFunctionalitySupport 1 Lowest fully-functional device speed is Full Speed (12Mbps) bU1DevExitLat 10 micro seconds bU2DevExitLat 1023 micro seconds SuperSpeedPlus USB Device Capability: bLength 28 bDescriptorType 16 bDevCapabilityType 10 bmAttributes 0x00000023 Sublink Speed Attribute count 3 Sublink Speed ID count 1 wFunctionalitySupport 0x1100 bmSublinkSpeedAttr[0] 0x00050030 Speed Attribute ID: 0 5Gb/s Symmetric RX SuperSpeed bmSublinkSpeedAttr[1] 0x000500b0 Speed Attribute ID: 0 5Gb/s Symmetric TX SuperSpeed bmSublinkSpeedAttr[2] 0x000a4031 Speed Attribute ID: 1 10Gb/s Symmetric RX SuperSpeedPlus bmSublinkSpeedAttr[3] 0x000a40b1 Speed Attribute ID: 1 10Gb/s Symmetric TX SuperSpeedPlus Container ID Device Capability: bLength 20 bDescriptorType 16 bDevCapabilityType 4 bReserved 0 ContainerID {20b9cde5-7039-e011-a935-0002a5d5c51b} ** UNRECOGNIZED: 03 10 0b Device Status: 0x0001 Self Powered fwupd-1.2.14/plugins/rts54hub/fu-plugin-rts54hub.c000066400000000000000000000022271402665037500216300ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-rts54hub-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.realtek.rts54"); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuRts54HubDevice) dev = NULL; /* open the device */ dev = fu_rts54hub_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; /* success */ fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } fwupd-1.2.14/plugins/rts54hub/fu-rts54hub-device.c000066400000000000000000000311121402665037500215640ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-chunk.h" #include "fu-rts54hub-device.h" struct _FuRts54HubDevice { FuUsbDevice parent_instance; gboolean fw_auth; gboolean dual_bank; gboolean running_on_flash; guint8 vendor_cmd; }; G_DEFINE_TYPE (FuRts54HubDevice, fu_rts54hub_device, FU_TYPE_USB_DEVICE) #define FU_RTS54HUB_DEVICE_TIMEOUT 100 /* ms */ #define FU_RTS54HUB_DEVICE_TIMEOUT_RW 1000 /* ms */ #define FU_RTS54HUB_DEVICE_TIMEOUT_ERASE 5000 /* ms */ #define FU_RTS54HUB_DEVICE_TIMEOUT_AUTH 10000 /* ms */ #define FU_RTS54HUB_DEVICE_BLOCK_SIZE 4096 #define FU_RTS54HUB_DEVICE_STATUS_LEN 25 typedef enum { FU_RTS54HUB_VENDOR_CMD_NONE = 0x00, FU_RTS54HUB_VENDOR_CMD_STATUS = 1 << 0, FU_RTS54HUB_VENDOR_CMD_FLASH = 1 << 1, } FuRts54HubVendorCmd; static void fu_rts54hub_device_to_string (FuDevice *device, GString *str) { FuRts54HubDevice *self = FU_RTS54HUB_DEVICE (device); g_string_append (str, " FuRts54HubDevice:\n"); g_string_append_printf (str, " fw-auth: %i\n", self->fw_auth); g_string_append_printf (str, " dual-bank: %i\n", self->dual_bank); g_string_append_printf (str, " running-on-flash: %i\n", self->running_on_flash); } static gboolean fu_rts54hub_device_highclockmode (FuRts54HubDevice *self, guint16 value, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x06, /* request */ value, /* value */ 0, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to set highclockmode: "); return FALSE; } return TRUE; } static gboolean fu_rts54hub_device_reset_flash (FuRts54HubDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xC0 + 0x29, /* request */ 0x0, /* value */ 0x0, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to reset flash: "); return FALSE; } return TRUE; } static gboolean fu_rts54hub_device_write_flash (FuRts54HubDevice *self, guint32 addr, const guint8 *data, gsize datasz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_len = 0; g_autofree guint8 *datarw = g_memdup (data, datasz); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xC0 + 0x08, /* request */ addr % (1 << 16), /* value */ addr / (1 << 16), /* idx */ datarw, datasz, /* data */ &actual_len, FU_RTS54HUB_DEVICE_TIMEOUT_RW, NULL, error)) { g_prefix_error (error, "failed to write flash: "); return FALSE; } if (actual_len != datasz) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } #if 0 static gboolean fu_rts54hub_device_read_flash (FuRts54HubDevice *self, guint32 addr, guint8 *data, gsize datasz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_len = 0; if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xC0 + 0x18, /* request */ addr % (1 << 16), /* value */ addr / (1 << 16), /* idx */ data, datasz, /* data */ &actual_len, FU_RTS54HUB_DEVICE_TIMEOUT_RW, NULL, error)) { g_prefix_error (error, "failed to read flash: "); return FALSE; } if (actual_len != datasz) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } #endif static gboolean fu_rts54hub_device_flash_authentication (FuRts54HubDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xC0 + 0x19, /* request */ 0x01, /* value */ 0x0, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT_AUTH, NULL, error)) { g_prefix_error (error, "failed to authenticate: "); return FALSE; } return TRUE; } static gboolean fu_rts54hub_device_erase_flash (FuRts54HubDevice *self, guint8 erase_type, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xC0 + 0x28, /* request */ erase_type * 256, /* value */ 0x0, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT_ERASE, NULL, error)) { g_prefix_error (error, "failed to erase flash: "); return FALSE; } return TRUE; } static gboolean fu_rts54hub_device_vendor_cmd (FuRts54HubDevice *self, guint8 value, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); /* don't set something that's already set */ if (self->vendor_cmd == value) { g_debug ("skipping vendor command 0x%02x as already set", value); return TRUE; } if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x02, /* request */ value, /* value */ 0x0bda, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to issue vendor cmd 0x%02x: ", value); return FALSE; } self->vendor_cmd = value; return TRUE; } static gboolean fu_rts54hub_device_ensure_status (FuRts54HubDevice *self, GError **error) { guint8 data[FU_RTS54HUB_DEVICE_STATUS_LEN] = { 0 }; GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_len = 0; if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x09, /* request */ 0x0, /* value */ 0x0, /* idx */ data, sizeof(data), &actual_len, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to get status: "); return FALSE; } if (actual_len != FU_RTS54HUB_DEVICE_STATUS_LEN) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } /* check the hardware capabilities */ self->dual_bank = (data[7] & 0x80) == 0x80; self->fw_auth = (data[13] & 0x02) > 0; self->running_on_flash = (data[15] & 0x02) > 0; return TRUE; } static gboolean fu_rts54hub_device_setup (FuDevice *device, GError **error) { FuRts54HubDevice *self = FU_RTS54HUB_DEVICE (device); /* check this device is correct */ if (!fu_rts54hub_device_vendor_cmd (self, FU_RTS54HUB_VENDOR_CMD_STATUS, error)) { g_prefix_error (error, "failed to vendor enable: "); return FALSE; } if (!fu_rts54hub_device_ensure_status (self, error)) return FALSE; /* all three conditions must be set */ if (!self->running_on_flash) { fu_device_set_update_error (device, "device is abnormally running from ROM"); } else if (!self->fw_auth) { fu_device_set_update_error (device, "device does not support authentication"); } else if (!self->dual_bank) { fu_device_set_update_error (device, "device does not support dual-bank updating"); } else { fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); } /* success */ return TRUE; } static gboolean fu_rts54hub_device_close (FuUsbDevice *device, GError **error) { FuRts54HubDevice *self = FU_RTS54HUB_DEVICE (device); /* disable vendor commands */ if (self->vendor_cmd != FU_RTS54HUB_VENDOR_CMD_NONE) { if (!fu_rts54hub_device_vendor_cmd (self, FU_RTS54HUB_VENDOR_CMD_NONE, error)) { g_prefix_error (error, "failed to disable vendor command: "); return FALSE; } } /* success */ return TRUE; } static gboolean fu_rts54hub_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuRts54HubDevice *self = FU_RTS54HUB_DEVICE (device); g_autoptr(GPtrArray) chunks = NULL; /* enable vendor commands */ if (!fu_rts54hub_device_vendor_cmd (self, FU_RTS54HUB_VENDOR_CMD_STATUS | FU_RTS54HUB_VENDOR_CMD_FLASH, error)) { g_prefix_error (error, "failed to cmd enable: "); return FALSE; } /* erase spare flash bank only if it is not empty */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_rts54hub_device_erase_flash (self, 1, error)) return FALSE; /* set MCU clock to high clock mode */ if (!fu_rts54hub_device_highclockmode (self, 0x0001, error)) { g_prefix_error (error, "failed to enable MCU clock: "); return FALSE; } /* set SPI controller clock to high clock mode */ if (!fu_rts54hub_device_highclockmode (self, 0x0101, error)) { g_prefix_error (error, "failed to enable SPI clock: "); return FALSE; } /* build packets */ chunks = fu_chunk_array_new_from_bytes (fw, 0x00, /* start addr */ 0x00, /* page_sz */ FU_RTS54HUB_DEVICE_BLOCK_SIZE); /* write each block */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); /* write chunk */ if (!fu_rts54hub_device_write_flash (self, chk->address, chk->data, chk->data_sz, error)) return FALSE; /* update progress */ fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len - 1); } /* get device to authenticate the firmware */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY); if (!fu_rts54hub_device_flash_authentication (self, error)) return FALSE; /* send software reset to run available flash code */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!fu_rts54hub_device_reset_flash (self, error)) return FALSE; /* don't reset the vendor command enable, the device will be rebooted */ self->vendor_cmd = FU_RTS54HUB_VENDOR_CMD_NONE; /* success! */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static GBytes * fu_rts54hub_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { gsize sz = 0; const guint8 *data = g_bytes_get_data (fw, &sz); if (sz < 0x7ef3) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware was too small"); return NULL; } if ((data[0x7ef3] & 0xf0) != 0x80) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware needs to be dual bank"); return NULL; } return g_bytes_ref (fw); } static void fu_rts54hub_device_init (FuRts54HubDevice *self) { fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_rts54hub_device_class_init (FuRts54HubDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->write_firmware = fu_rts54hub_device_write_firmware; klass_device->setup = fu_rts54hub_device_setup; klass_device->to_string = fu_rts54hub_device_to_string; klass_device->prepare_firmware = fu_rts54hub_device_prepare_firmware; klass_usb_device->close = fu_rts54hub_device_close; } FuRts54HubDevice * fu_rts54hub_device_new (FuUsbDevice *device) { FuRts54HubDevice *self = g_object_new (FU_TYPE_RTS54HUB_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } fwupd-1.2.14/plugins/rts54hub/fu-rts54hub-device.h000066400000000000000000000006201402665037500215710ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_RTS54HUB_DEVICE (fu_rts54hub_device_get_type ()) G_DECLARE_FINAL_TYPE (FuRts54HubDevice, fu_rts54hub_device, FU, RTS54HUB_DEVICE, FuUsbDevice) FuRts54HubDevice *fu_rts54hub_device_new (FuUsbDevice *device); G_END_DECLS fwupd-1.2.14/plugins/rts54hub/meson.build000066400000000000000000000010621402665037500202360ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginRts54Hub"'] install_data([ 'rts54hub.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_rts54hub', fu_hash, sources : [ 'fu-rts54hub-device.c', 'fu-plugin-rts54hub.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/rts54hub/rts54hub.quirk000066400000000000000000000002131402665037500206260ustar00rootroot00000000000000# RTS5423 Development Board [DeviceInstanceId=USB\VID_0BDA&PID_5423] Plugin = rts54hub FirmwareSizeMin = 0x20000 FirmwareSizeMax = 0x40000 fwupd-1.2.14/plugins/steelseries/000077500000000000000000000000001402665037500167445ustar00rootroot00000000000000fwupd-1.2.14/plugins/steelseries/README.md000066400000000000000000000006431402665037500202260ustar00rootroot00000000000000SteelSeries Support =================== Introduction ------------ This plugin is used to get the correct version number on SteelSeries gaming mice. These mice have updatable firmware but so far no updates are available from the vendor. GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1038&PID_1702&REV_0001` * `USB\VID_1038&PID_1702` * `USB\VID_1038` fwupd-1.2.14/plugins/steelseries/fu-plugin-steelseries.c000066400000000000000000000013511402665037500233430ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-steelseries-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuSteelseriesDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; dev = fu_steelseries_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } fwupd-1.2.14/plugins/steelseries/fu-steelseries-device.c000066400000000000000000000067431402665037500233160ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-steelseries-device.h" #define STEELSERIES_TRANSACTION_TIMEOUT 1000 /* ms */ G_DEFINE_TYPE (FuSteelseriesDevice, fu_steelseries_device, FU_TYPE_USB_DEVICE) static gboolean fu_steelseries_device_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); const guint8 iface_idx = 0x00; /* get firmware version on SteelSeries Rival 100 */ if (!g_usb_device_claim_interface (usb_device, iface_idx, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to claim interface: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_steelseries_device_setup (FuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); gboolean ret; gsize actual_len = 0; guint8 data[32]; g_autofree gchar *version = NULL; memset (data, 0x00, sizeof(data)); data[0] = 0x16; ret = g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, 0x09, 0x0200, 0x0000, data, sizeof(data), &actual_len, STEELSERIES_TRANSACTION_TIMEOUT, NULL, error); if (!ret) { g_prefix_error (error, "failed to do control transfer: "); return FALSE; } if (actual_len != 32) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } ret = g_usb_device_interrupt_transfer (usb_device, 0x81, /* EP1 IN */ data, sizeof(data), &actual_len, STEELSERIES_TRANSACTION_TIMEOUT, NULL, error); if (!ret) { g_prefix_error (error, "failed to do EP1 transfer: "); return FALSE; } if (actual_len != 32) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } version = g_strdup_printf ("%i.%i.%i", data[0], data[1], data[2]); fu_device_set_version (FU_DEVICE (device), version, FWUPD_VERSION_FORMAT_TRIPLET); /* success */ return TRUE; } static gboolean fu_steelseries_device_close (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); const guint8 iface_idx = 0x00; /* we're done here */ if (!g_usb_device_release_interface (usb_device, iface_idx, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to release interface: "); return FALSE; } /* success */ return TRUE; } static void fu_steelseries_device_init (FuSteelseriesDevice *device) { } static void fu_steelseries_device_class_init (FuSteelseriesDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->setup = fu_steelseries_device_setup; klass_usb_device->open = fu_steelseries_device_open; klass_usb_device->close = fu_steelseries_device_close; } FuSteelseriesDevice * fu_steelseries_device_new (FuUsbDevice *device) { FuSteelseriesDevice *self = g_object_new (FU_TYPE_STEELSERIES_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } fwupd-1.2.14/plugins/steelseries/fu-steelseries-device.h000066400000000000000000000007631402665037500233170ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_STEELSERIES_DEVICE (fu_steelseries_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuSteelseriesDevice, fu_steelseries_device, FU, STEELSERIES_DEVICE, FuUsbDevice) struct _FuSteelseriesDeviceClass { FuUsbDeviceClass parent_class; }; FuSteelseriesDevice *fu_steelseries_device_new (FuUsbDevice *device); G_END_DECLS fwupd-1.2.14/plugins/steelseries/meson.build000066400000000000000000000010721402665037500211060ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginSteelSeries"'] install_data(['steelseries.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_steelseries', fu_hash, sources : [ 'fu-plugin-steelseries.c', 'fu-steelseries-device.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/steelseries/steelseries.quirk000066400000000000000000000001771402665037500223550ustar00rootroot00000000000000# Rival 100 [DeviceInstanceId=USB\VID_1038&PID_1702] Plugin = steelseries Summary = An optical gaming mouse Icon = input-mouse fwupd-1.2.14/plugins/superio/000077500000000000000000000000001402665037500161035ustar00rootroot00000000000000fwupd-1.2.14/plugins/superio/README.md000066400000000000000000000014761402665037500173720ustar00rootroot00000000000000SuperIO ======= This plugin enumerates the various ITE85* SuperIO embedded controller ICs found in many laptops. Vendors wanting to expose the SuperIO functionality will need to add a HwId quirk entry to `superio.quirk`. See https://en.wikipedia.org/wiki/Super_I/O for more details about SuperIO and what the EC actually does. Other useful links: * https://raw.githubusercontent.com/system76/ecflash/master/ec.py * https://github.com/system76/firmware-update/tree/master/src * https://github.com/coreboot/coreboot/blob/master/util/superiotool/superiotool.h * https://github.com/flashrom/flashrom/blob/master/it85spi.c * http://wiki.laptop.org/go/Ec_specification GUID Generation --------------- These devices use a custom GUID generated using the SuperIO chipset name: * `SuperIO-$(chipset)`, for example `SuperIO-IT8512` fwupd-1.2.14/plugins/superio/fu-plugin-superio.c000066400000000000000000000116061402665037500216450ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-superio-it85-device.h" #include "fu-superio-it89-device.h" #define FU_QUIRKS_SUPERIO_CHIPSETS "SuperioChipsets" static gboolean fu_plugin_superio_coldplug_chipset (FuPlugin *plugin, const gchar *chipset, GError **error) { g_autoptr(FuSuperioDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autofree gchar *key = g_strdup_printf ("SuperIO=%s", chipset); guint64 id; guint64 port; /* get ID we need for the chipset */ id = fu_plugin_lookup_quirk_by_id_as_uint64 (plugin, key, "Id"); if (id == 0x0000 || id > 0xffff) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SuperIO chip %s has invalid Id", chipset); return FALSE; } /* set address */ port = fu_plugin_lookup_quirk_by_id_as_uint64 (plugin, key, "Port"); if (port == 0x0 || port > 0xffff) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SuperIO chip %s has invalid Port", chipset); return FALSE; } /* create IT89xx or IT89xx */ if (id >> 8 == 0x85) { dev = g_object_new (FU_TYPE_SUPERIO_IT85_DEVICE, "chipset", chipset, "id", id, "port", port, NULL); } else if (id >> 8 == 0x89) { dev = g_object_new (FU_TYPE_SUPERIO_IT89_DEVICE, "chipset", chipset, "id", id, "port", port, NULL); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SuperIO chip %s has unsupported Id", chipset); return FALSE; } /* unlock */ locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } static gboolean fu_plugin_superio_coldplug_chipsets (FuPlugin *plugin, const gchar *str, GError **error) { g_auto(GStrv) chipsets = g_strsplit (str, ",", -1); for (guint i = 0; chipsets[i] != NULL; i++) { if (!fu_plugin_superio_coldplug_chipset (plugin, chipsets[i], error)) return FALSE; } return TRUE; } void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "tw.com.ite.superio"); } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { GPtrArray *hwids = fu_plugin_get_hwids (plugin); for (guint i = 0; i < hwids->len; i++) { const gchar *tmp; const gchar *guid = g_ptr_array_index (hwids, i); g_autofree gchar *key = g_strdup_printf ("HwId=%s", guid); tmp = fu_plugin_lookup_quirk_by_id (plugin, key, FU_QUIRKS_SUPERIO_CHIPSETS); if (tmp == NULL) continue; if (!fu_plugin_superio_coldplug_chipsets (plugin, tmp, error)) return FALSE; } return TRUE; } gboolean fu_plugin_verify_attach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_attach (device, error); } gboolean fu_plugin_verify_detach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_detach (device, error); } gboolean fu_plugin_verify (FuPlugin *plugin, FuDevice *device, FuPluginVerifyFlags flags, GError **error) { g_autoptr(GBytes) fw = NULL; g_autoptr(FuDeviceLocker) locker = NULL; GChecksumType checksum_types[] = { G_CHECKSUM_SHA1, G_CHECKSUM_SHA256, 0 }; /* get data */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; fw = fu_device_read_firmware (device, error); if (fw == NULL) return FALSE; for (guint i = 0; checksum_types[i] != 0; i++) { g_autofree gchar *hash = NULL; hash = g_compute_checksum_for_bytes (checksum_types[i], fw); fu_device_add_checksum (device, hash); } return TRUE; } gboolean fu_plugin_update_detach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_detach (device, error); } gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_attach (device, error); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } fwupd-1.2.14/plugins/superio/fu-superio-common.c000066400000000000000000000040371402665037500216370ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-superio-common.h" gboolean fu_superio_outb (gint fd, guint16 port, guint8 data, GError **error) { if (pwrite (fd, &data, 1, (goffset) port) != 1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write to port %04x: %s", (guint) port, strerror (errno)); return FALSE; } return TRUE; } gboolean fu_superio_inb (gint fd, guint16 port, guint8 *data, GError **error) { if (pread (fd, data, 1, (goffset) port) != 1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to read from port %04x: %s", (guint) port, strerror (errno)); return FALSE; } return TRUE; } const gchar * fu_superio_ldn_to_text (guint8 ldn) { if (ldn == SIO_LDN_FDC) return "Floppy Disk Controller"; if (ldn == SIO_LDN_GPIO) return "General Purpose IO"; if (ldn == SIO_LDN_PARALLEL_PORT) return "Parallel Port"; if (ldn == SIO_LDN_UART1) return "Serial Port 1"; if (ldn == SIO_LDN_UART2) return "Serial Port 2"; if (ldn == SIO_LDN_UART3) return "Serial Port 3"; if (ldn == SIO_LDN_UART4) return "Serial Port 4"; if (ldn == SIO_LDN_SWUC) return "System Wake-Up Control"; if (ldn == SIO_LDN_KBC_MOUSE) return "KBC/Mouse"; if (ldn == SIO_LDN_KBC_KEYBOARD) return "KBC/Keyboard"; if (ldn == SIO_LDN_CIR) return "Consumer IR"; if (ldn == SIO_LDN_SMFI) return "Shared Memory/Flash"; if (ldn == SIO_LDN_RTCT) return "RTC-like Timer"; if (ldn == SIO_LDN_SSSP1) return "Serial Peripheral"; if (ldn == SIO_LDN_PECI) return "Platform Environmental Control"; if (ldn == SIO_LDN_PM1) return "Power Management 1"; if (ldn == SIO_LDN_PM2) return "Power Management 2"; if (ldn == SIO_LDN_PM3) return "Power Management 3"; if (ldn == SIO_LDN_PM4) return "Power Management 4"; if (ldn == SIO_LDN_PM5) return "Power Management 5"; return NULL; } fwupd-1.2.14/plugins/superio/fu-superio-common.h000066400000000000000000000100671402665037500216440ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS /* for all LDNs */ #define SIO_LDNxx_IDX_LDNSEL 0x07 #define SIO_LDNxx_IDX_CHIPID1 0x20 #define SIO_LDNxx_IDX_CHIPID2 0x21 #define SIO_LDNxx_IDX_CHIPVER 0x22 #define SIO_LDNxx_IDX_SIOCTRL 0x23 #define SIO_LDNxx_IDX_SIOIRQ 0x25 #define SIO_LDNxx_IDX_SIOGP 0x26 #define SIO_LDNxx_IDX_SIOPWR 0x2d #define SIO_LDNxx_IDX_D2ADR 0x2e #define SIO_LDNxx_IDX_D2DAT 0x2f #define SIO_LDNxx_IDX_IOBAD0 0x60 /* 16 bit */ #define SIO_LDNxx_IDX_IOBAD1 0x62 /* 16 bit */ /* these registers are only accessible by EC */ #define GCTRL_ECHIPID1 0x2000 #define GCTRL_ECHIPID2 0x2001 #define GCTRL_ECHIPVER 0x2002 /* to create sub-addresses */ #define SIO_DEPTH2_I2EC_ADDRL 0x10 #define SIO_DEPTH2_I2EC_ADDRH 0x11 #define SIO_DEPTH2_I2EC_DATA 0x12 /* * The PMC is a communication channel used between the host and the EC. * Compatible mode uses four registers: * * Name | EC | HOST | ADDR * _____________________|_______________|_______________|______ * PMDIR | RO | WO | 0x62 * PMDOR | WO | RO | 0x62 * PMCMDR | RO | RO | 0x66 * PMSTR | RO | RO | 0x66 */ #define SIO_EC_PMC_PM1STS 0x00 #define SIO_EC_PMC_PM1DO 0x01 #define SIO_EC_PMC_PM1DOSCI 0x02 #define SIO_EC_PMC_PM1DOCMI 0x03 #define SIO_EC_PMC_PM1DI 0x04 #define SIO_EC_PMC_PM1DISCI 0x05 #define SIO_EC_PMC_PM1CTL 0x06 #define SIO_EC_PMC_PM1IC 0x07 #define SIO_EC_PMC_PM1IE 0x08 /* SPI commands */ #define SIO_SPI_CMD_READ 0x03 #define SIO_SPI_CMD_HS_READ 0x0b #define SIO_SPI_CMD_FAST_READ_DUAL_OP 0x3b #define SIO_SPI_CMD_FAST_READ_DUAL_IO 0xbb #define SIO_SPI_CMD_4K_SECTOR_ERASE 0xd7 /* or 0x20 or 0x52 */ #define SIO_SPI_CMD_64K_BLOCK_ERASE 0xd8 #define SIO_SPI_CMD_CHIP_ERASE 0xc7 /* or 0x60 */ #define SIO_SPI_CMD_PAGE_PROGRAM 0x02 #define SIO_SPI_CMD_WRITE_WORD 0xad #define SIO_SPI_CMD_RDSR 0x05 /* read status register */ #define SIO_SPI_CMD_WRSR 0x01 /* write status register */ #define SIO_SPI_CMD_WREN 0x06 /* write enable */ #define SIO_SPI_CMD_WRDI 0x04 /* write disable */ #define SIO_SPI_CMD_RDID 0xab #define SIO_SPI_CMD_JEDEC_ID 0x9f #define SIO_SPI_CMD_DPD 0xb9 /* deep sleep */ #define SIO_SPI_CMD_RDPD 0xab /* wake from deep sleep */ /* EC Status Register (see ec/google/chromeec/ec_commands.h) */ #define SIO_STATUS_EC_OBF (1 << 0) /* o/p buffer full */ #define SIO_STATUS_EC_IBF (1 << 1) /* i/p buffer full */ #define SIO_STATUS_EC_IS_BUSY (1 << 2) #define SIO_STATUS_EC_IS_CMD (1 << 3) #define SIO_STATUS_EC_BURST_ENABLE (1 << 4) #define SIO_STATUS_EC_SCI (1 << 5) /* 1 if more events in queue */ /* EC Command Register (see KB3700-ds-01.pdf) */ #define SIO_CMD_EC_READ 0x80 #define SIO_CMD_EC_WRITE 0x81 #define SIO_CMD_EC_BURST_ENABLE 0x82 #define SIO_CMD_EC_BURST_DISABLE 0x83 #define SIO_CMD_EC_QUERY_EVENT 0x84 #define SIO_CMD_EC_GET_NAME_STR 0x92 #define SIO_CMD_EC_GET_VERSION_STR 0x93 #define SIO_CMD_EC_DISABLE_HOST_WA 0xdc #define SIO_CMD_EC_ENABLE_HOST_WA 0xfc typedef enum { SIO_LDN_FDC = 0x00, /* IT87 */ SIO_LDN_UART1 = 0x01, /* IT87+IT89 */ SIO_LDN_UART2 = 0x02, /* IT87+IT89 */ SIO_LDN_PARALLEL_PORT = 0x03, /* IT87 */ SIO_LDN_SWUC = 0x04, /* IT87+IT89 */ SIO_LDN_KBC_MOUSE = 0x05, /* IT87+IT89 */ SIO_LDN_KBC_KEYBOARD = 0x06, /* IT87+IT89 */ SIO_LDN_GPIO = 0x07, /* IT87 */ SIO_LDN_UART3 = 0x08, /* IT87 */ SIO_LDN_UART4 = 0x09, /* IT87 */ SIO_LDN_CIR = 0x0a, /* IT89 */ SIO_LDN_SMFI = 0x0f, /* IT89 */ SIO_LDN_RTCT = 0x10, /* IT89 */ SIO_LDN_PM1 = 0x11, /* IT89 */ SIO_LDN_PM2 = 0x12, /* IT89 */ SIO_LDN_SSSP1 = 0x13, /* IT89 */ SIO_LDN_PECI = 0x14, /* IT89 */ SIO_LDN_PM3 = 0x17, /* IT89 */ SIO_LDN_PM4 = 0x18, /* IT89 */ SIO_LDN_PM5 = 0x19, /* IT89 */ SIO_LDN_LAST = 0x1a } SioLdn; const gchar *fu_superio_ldn_to_text (guint8 ldn); gboolean fu_superio_outb (gint fd, guint16 port, guint8 data, GError **error); gboolean fu_superio_inb (gint fd, guint16 port, guint8 *data, GError **error); G_END_DECLS fwupd-1.2.14/plugins/superio/fu-superio-device.c000066400000000000000000000335311402665037500216070ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-superio-common.h" #include "fu-superio-device.h" #define FU_PLUGIN_SUPERIO_TIMEOUT 0.25 /* s */ typedef struct { gint fd; gchar *chipset; guint16 port; guint16 pm1_iobad0; guint16 pm1_iobad1; guint16 id; } FuSuperioDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuSuperioDevice, fu_superio_device, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_superio_device_get_instance_private (o)) enum { PROP_0, PROP_CHIPSET, PROP_PORT, PROP_ID, PROP_LAST }; gboolean fu_superio_device_regval (FuSuperioDevice *self, guint8 addr, guint8 *data, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE (self); if (!fu_superio_outb (priv->fd, priv->port, addr, error)) return FALSE; if (!fu_superio_inb (priv->fd, priv->port + 1, data, error)) return FALSE; return TRUE; } gboolean fu_superio_device_regval16 (FuSuperioDevice *self, guint8 addr, guint16 *data, GError **error) { guint8 msb; guint8 lsb; if (!fu_superio_device_regval (self, addr, &msb, error)) return FALSE; if (!fu_superio_device_regval (self, addr + 1, &lsb, error)) return FALSE; *data = ((guint16) msb << 8) | (guint16) lsb; return TRUE; } gboolean fu_superio_device_regwrite (FuSuperioDevice *self, guint8 addr, guint8 data, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE (self); if (!fu_superio_outb (priv->fd, priv->port, addr, error)) return FALSE; if (!fu_superio_outb (priv->fd, priv->port + 1, data, error)) return FALSE; return TRUE; } static gboolean fu_superio_device_set_ldn (FuSuperioDevice *self, guint8 ldn, GError **error) { return fu_superio_device_regwrite (self, SIO_LDNxx_IDX_LDNSEL, ldn, error); } static gboolean fu_superio_device_regdump (FuSuperioDevice *self, guint8 ldn, GError **error) { const gchar *ldnstr = fu_superio_ldn_to_text (ldn); guint8 buf[0xff] = { 0x00 }; guint16 iobad0 = 0x0; guint16 iobad1 = 0x0; g_autoptr(GString) str = g_string_new (NULL); /* set LDN */ if (!fu_superio_device_set_ldn (self, ldn, error)) return FALSE; for (guint i = 0x00; i < 0xff; i++) { if (!fu_superio_device_regval (self, i, &buf[i], error)) return FALSE; } /* get the i/o base addresses */ if (!fu_superio_device_regval16 (self, SIO_LDNxx_IDX_IOBAD0, &iobad0, error)) return FALSE; if (!fu_superio_device_regval16 (self, SIO_LDNxx_IDX_IOBAD1, &iobad1, error)) return FALSE; g_string_append_printf (str, "LDN:0x%02x ", ldn); if (iobad0 != 0x0) g_string_append_printf (str, "IOBAD0:0x%04x ", iobad0); if (iobad1 != 0x0) g_string_append_printf (str, "IOBAD1:0x%04x ", iobad1); if (ldnstr != NULL) g_string_append_printf (str, "(%s)", ldnstr); fu_common_dump_raw (G_LOG_DOMAIN, str->str, buf, sizeof(buf)); return TRUE; } static void fu_superio_device_to_string (FuDevice *device, GString *str) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); FuSuperioDevicePrivate *priv = GET_PRIVATE (self); g_string_append (str, " FuSuperioDevice:\n"); g_string_append_printf (str, " fd:\t\t\t%i\n", priv->fd); g_string_append_printf (str, " chipset:\t\t%s\n", priv->chipset); g_string_append_printf (str, " id:\t\t\t0x%04x\n", (guint) priv->id); g_string_append_printf (str, " port:\t\t0x%04x\n", (guint) priv->port); g_string_append_printf (str, " pm1-iobad0:\t\t0x%04x\n", (guint) priv->pm1_iobad0); g_string_append_printf (str, " pm1-iobad1:\t\t0x%04x\n", (guint) priv->pm1_iobad1); } static guint16 fu_superio_device_check_id (FuSuperioDevice *self, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE (self); guint16 id_tmp; /* check ID, which can be done from any LDN */ if (!fu_superio_device_regval16 (self, SIO_LDNxx_IDX_CHIPID1, &id_tmp, error)) return FALSE; if (priv->id != id_tmp) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SuperIO chip not supported, got %04x, expected %04x", (guint) id_tmp, (guint) priv->id); return FALSE; } return TRUE; } static gboolean fu_superio_device_wait_for (FuSuperioDevice *self, guint8 mask, gboolean set, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GTimer) timer = g_timer_new (); do { guint8 status = 0x00; if (!fu_superio_inb (priv->fd, priv->pm1_iobad1, &status, error)) return FALSE; if (g_timer_elapsed (timer, NULL) > FU_PLUGIN_SUPERIO_TIMEOUT) break; if (set && (status & mask) != 0) return TRUE; if (!set && (status & mask) == 0) return TRUE; } while (TRUE); g_set_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timed out whilst waiting for 0x%02x:%i", mask, set); return FALSE; } gboolean fu_superio_device_ec_read (FuSuperioDevice *self, guint8 *data, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE (self); if (!fu_superio_device_wait_for (self, SIO_STATUS_EC_OBF, TRUE, error)) return FALSE; return fu_superio_inb (priv->fd, priv->pm1_iobad0, data, error); } gboolean fu_superio_device_ec_write0 (FuSuperioDevice *self, guint8 data, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE (self); if (!fu_superio_device_wait_for (self, SIO_STATUS_EC_IBF, FALSE, error)) return FALSE; return fu_superio_outb (priv->fd, priv->pm1_iobad0, data, error); } gboolean fu_superio_device_ec_write1 (FuSuperioDevice *self, guint8 data, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE (self); if (!fu_superio_device_wait_for (self, SIO_STATUS_EC_IBF, FALSE, error)) return FALSE; return fu_superio_outb (priv->fd, priv->pm1_iobad1, data, error); } static gboolean fu_superio_device_ec_flush (FuSuperioDevice *self, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE (self); guint8 status = 0x00; g_autoptr(GTimer) timer = g_timer_new (); do { guint8 unused = 0; if (!fu_superio_inb (priv->fd, priv->pm1_iobad1, &status, error)) return FALSE; if ((status & SIO_STATUS_EC_OBF) == 0) break; if (!fu_superio_inb (priv->fd, priv->pm1_iobad0, &unused, error)) return FALSE; if (g_timer_elapsed (timer, NULL) > FU_PLUGIN_SUPERIO_TIMEOUT) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timed out whilst waiting for flush"); return FALSE; } } while (TRUE); return TRUE; } gboolean fu_superio_device_ec_get_param (FuSuperioDevice *self, guint8 param, guint8 *data, GError **error) { if (!fu_superio_device_ec_write1 (self, SIO_CMD_EC_READ, error)) return FALSE; if (!fu_superio_device_ec_write0 (self, param, error)) return FALSE; return fu_superio_device_ec_read (self, data, error); } #if 0 static gboolean fu_superio_device_ec_set_param (FuSuperioDevice *self, guint8 param, guint8 data, GError **error) { if (!fu_superio_device_ec_write1 (self, SIO_CMD_EC_WRITE, error)) return FALSE; if (!fu_superio_device_ec_write0 (self, param, error)) return FALSE; return fu_superio_device_ec_write0 (self, data, error); } #endif static gboolean fu_superio_device_open (FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); FuSuperioDevicePrivate *priv = GET_PRIVATE (self); /* open device */ priv->fd = g_open (fu_device_get_physical_id (device), O_RDWR); if (priv->fd < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to open %s: %s", fu_device_get_physical_id (device), strerror (errno)); return FALSE; } /* success */ return TRUE; } static gboolean fu_superio_device_probe (FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); FuSuperioDevicePrivate *priv = GET_PRIVATE (self); g_autofree gchar *devid = NULL; g_autofree gchar *name = NULL; /* use the chipset name as the logical ID and for the GUID */ fu_device_set_logical_id (device, priv->chipset); devid = g_strdup_printf ("SuperIO-%s", priv->chipset); fu_device_add_instance_id (device, devid); name = g_strdup_printf ("SuperIO %s", priv->chipset); fu_device_set_name (FU_DEVICE (self), name); return TRUE; } static gboolean fu_superio_device_setup (FuDevice *device, GError **error) { FuSuperioDeviceClass *klass = FU_SUPERIO_DEVICE_GET_CLASS (device); FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); FuSuperioDevicePrivate *priv = GET_PRIVATE (self); guint8 tmp = 0x0; /* check port is valid */ if (!fu_superio_inb (priv->fd, priv->pm1_iobad0, &tmp, error)) return FALSE; if (tmp != 0xff) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "check port!"); return FALSE; } /* check ID is correct */ if (!fu_superio_device_check_id (self, error)) { g_prefix_error (error, "failed to probe id: "); return FALSE; } /* dump LDNs */ if (g_getenv ("FWUPD_SUPERIO_VERBOSE") != NULL) { for (guint j = 0; j < SIO_LDN_LAST; j++) { if (!fu_superio_device_regdump (self, j, error)) return FALSE; } } /* set Power Management I/F Channel 1 LDN */ if (!fu_superio_device_set_ldn (self, SIO_LDN_PM1, error)) return FALSE; /* get the PM1 IOBAD0 address */ if (!fu_superio_device_regval16 (self, SIO_LDNxx_IDX_IOBAD0, &priv->pm1_iobad0, error)) return FALSE; /* get the PM1 IOBAD1 address */ if (!fu_superio_device_regval16 (self, SIO_LDNxx_IDX_IOBAD1, &priv->pm1_iobad1, error)) return FALSE; /* drain */ if (!fu_superio_device_ec_flush (self, error)) { g_prefix_error (error, "failed to flush: "); return FALSE; } /* dump PMC register map */ if (g_getenv ("FWUPD_SUPERIO_VERBOSE") != NULL) { guint8 buf[0xff] = { 0x00 }; for (guint i = 0x00; i < 0xff; i++) { g_autoptr(GError) error_local = NULL; if (!fu_superio_device_ec_get_param (self, i, &buf[i], &error_local)) { g_debug ("param: 0x%02x = %s", i, error_local->message); continue; } } fu_common_dump_raw (G_LOG_DOMAIN, "EC Registers", buf, 0x100); } /* subclassed setup */ if (klass->setup != NULL) return klass->setup (self, error); /* success */ return TRUE; } static GBytes * fu_superio_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { gsize sz = 0; const guint8 *buf = g_bytes_get_data (fw, &sz); const guint8 sig1[] = { 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5 }; const guint8 sig2[] = { 0x85, 0x12, 0x5a, 0x5a, 0xaa }; /* find signature -- maybe ignore byte 0x14 too? */ for (gsize off = 0; off < sz; off += 16) { if (memcmp (&buf[off], sig1, sizeof(sig1)) == 0 && memcmp (&buf[off + 8], sig2, sizeof(sig2)) == 0) { g_debug ("found signature at 0x%04x", (guint) off); return g_bytes_ref (fw); } } g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "did not detect signature in firmware image"); return NULL; } static gboolean fu_superio_device_close (FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); FuSuperioDevicePrivate *priv = GET_PRIVATE (self); if (!g_close (priv->fd, error)) return FALSE; priv->fd = 0; return TRUE; } static void fu_superio_device_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (object); FuSuperioDevicePrivate *priv = GET_PRIVATE (self); switch (prop_id) { case PROP_CHIPSET: g_value_set_string (value, priv->chipset); break; case PROP_PORT: g_value_set_uint (value, priv->port); break; case PROP_ID: g_value_set_uint (value, priv->id); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_superio_device_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (object); FuSuperioDevicePrivate *priv = GET_PRIVATE (self); switch (prop_id) { case PROP_CHIPSET: g_free (priv->chipset); priv->chipset = g_value_dup_string (value); break; case PROP_PORT: priv->port = g_value_get_uint (value); break; case PROP_ID: priv->id = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_superio_device_init (FuSuperioDevice *self) { fu_device_set_physical_id (FU_DEVICE (self), "/dev/port"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_summary (FU_DEVICE (self), "Embedded Controller"); fu_device_add_icon (FU_DEVICE (self), "computer"); } static void fu_superio_device_finalize (GObject *object) { G_OBJECT_CLASS (fu_superio_device_parent_class)->finalize (object); } static void fu_superio_device_class_init (FuSuperioDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); /* properties */ object_class->get_property = fu_superio_device_get_property; object_class->set_property = fu_superio_device_set_property; pspec = g_param_spec_string ("chipset", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_CHIPSET, pspec); pspec = g_param_spec_uint ("port", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_PORT, pspec); pspec = g_param_spec_uint ("id", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_ID, pspec); object_class->finalize = fu_superio_device_finalize; klass_device->to_string = fu_superio_device_to_string; klass_device->open = fu_superio_device_open; klass_device->probe = fu_superio_device_probe; klass_device->setup = fu_superio_device_setup; klass_device->close = fu_superio_device_close; klass_device->prepare_firmware = fu_superio_device_prepare_firmware; } fwupd-1.2.14/plugins/superio/fu-superio-device.h000066400000000000000000000024631402665037500216140ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_SUPERIO_DEVICE (fu_superio_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuSuperioDevice, fu_superio_device, FU, SUPERIO_DEVICE, FuDevice) struct _FuSuperioDeviceClass { FuDeviceClass parent_class; gboolean (*setup) (FuSuperioDevice *self, GError **error); }; gboolean fu_superio_device_ec_read (FuSuperioDevice *self, guint8 *data, GError **error); gboolean fu_superio_device_ec_write0 (FuSuperioDevice *self, guint8 data, GError **error); gboolean fu_superio_device_ec_write1 (FuSuperioDevice *self, guint8 data, GError **error); gboolean fu_superio_device_ec_get_param (FuSuperioDevice *self, guint8 param, guint8 *data, GError **error); gboolean fu_superio_device_regval (FuSuperioDevice *self, guint8 addr, guint8 *data, GError **error); gboolean fu_superio_device_regval16 (FuSuperioDevice *self, guint8 addr, guint16 *data, GError **error); gboolean fu_superio_device_regwrite (FuSuperioDevice *self, guint8 addr, guint8 data, GError **error); G_END_DECLS fwupd-1.2.14/plugins/superio/fu-superio-it85-device.c000066400000000000000000000037701402665037500224000ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-chunk.h" #include "fu-superio-common.h" #include "fu-superio-it85-device.h" struct _FuSuperioIt85Device { FuSuperioDevice parent_instance; }; G_DEFINE_TYPE (FuSuperioIt85Device, fu_superio_it85_device, FU_TYPE_SUPERIO_DEVICE) static gchar * fu_superio_it85_device_get_str (FuSuperioDevice *self, guint8 idx, GError **error) { GString *str = g_string_new (NULL); if (!fu_superio_device_ec_write1 (self, idx, error)) return NULL; for (guint i = 0; i < 0xff; i++) { guint8 c = 0; if (!fu_superio_device_ec_read (self, &c, error)) return NULL; if (c == '$') break; g_string_append_c (str, c); } return g_string_free (str, FALSE); } static gboolean fu_superio_it85_device_setup (FuSuperioDevice *self, GError **error) { guint8 size_tmp = 0; g_autofree gchar *name = NULL; g_autofree gchar *version = NULL; /* get EC size */ if (!fu_superio_device_ec_get_param (self, 0xe5, &size_tmp, error)) { g_prefix_error (error, "failed to get EC size: "); return FALSE; } fu_device_set_firmware_size (FU_DEVICE (self), ((guint32) size_tmp) << 10); /* get EC strings */ name = fu_superio_it85_device_get_str (self, SIO_CMD_EC_GET_NAME_STR, error); if (name == NULL) { g_prefix_error (error, "failed to get EC name: "); return FALSE; } fu_device_set_name (FU_DEVICE (self), name); version = fu_superio_it85_device_get_str (self, SIO_CMD_EC_GET_VERSION_STR, error); if (version == NULL) { g_prefix_error (error, "failed to get EC version: "); return FALSE; } fu_device_set_version (FU_DEVICE (self), version, FWUPD_VERSION_FORMAT_UNKNOWN); return TRUE; } static void fu_superio_it85_device_init (FuSuperioIt85Device *self) { } static void fu_superio_it85_device_class_init (FuSuperioIt85DeviceClass *klass) { FuSuperioDeviceClass *klass_superio_device = FU_SUPERIO_DEVICE_CLASS (klass); klass_superio_device->setup = fu_superio_it85_device_setup; } fwupd-1.2.14/plugins/superio/fu-superio-it85-device.h000066400000000000000000000005611402665037500224000ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-superio-device.h" G_BEGIN_DECLS #define FU_TYPE_SUPERIO_IT85_DEVICE (fu_superio_it85_device_get_type ()) G_DECLARE_FINAL_TYPE (FuSuperioIt85Device, fu_superio_it85_device, FU, SUPERIO_IT85_DEVICE, FuSuperioDevice) G_END_DECLS fwupd-1.2.14/plugins/superio/fu-superio-it89-device.c000066400000000000000000000457211402665037500224060ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-chunk.h" #include "fu-superio-common.h" #include "fu-superio-it89-device.h" struct _FuSuperioIt89Device { FuSuperioDevice parent_instance; }; G_DEFINE_TYPE (FuSuperioIt89Device, fu_superio_it89_device, FU_TYPE_SUPERIO_DEVICE) static gboolean fu_superio_it89_device_read_ec_register (FuSuperioDevice *self, guint16 addr, guint8 *outval, GError **error) { if (!fu_superio_device_regwrite (self, SIO_LDNxx_IDX_D2ADR, SIO_DEPTH2_I2EC_ADDRH, error)) return FALSE; if (!fu_superio_device_regwrite (self, SIO_LDNxx_IDX_D2DAT, addr >> 8, error)) return FALSE; if (!fu_superio_device_regwrite (self, SIO_LDNxx_IDX_D2ADR, SIO_DEPTH2_I2EC_ADDRL, error)) return FALSE; if (!fu_superio_device_regwrite (self, SIO_LDNxx_IDX_D2DAT, addr & 0xff, error)) return FALSE; if (!fu_superio_device_regwrite (self, SIO_LDNxx_IDX_D2ADR, SIO_DEPTH2_I2EC_DATA, error)) return FALSE; return fu_superio_device_regval (self, SIO_LDNxx_IDX_D2DAT, outval, error); } static gboolean fu_superio_it89_device_ec_size (FuSuperioDevice *self, GError **error) { guint8 tmp = 0; /* not sure why we can't just use SIO_LDNxx_IDX_CHIPID1, * but lets do the same as the vendor flash tool... */ if (!fu_superio_it89_device_read_ec_register (self, GCTRL_ECHIPID1, &tmp, error)) return FALSE; if (tmp == 0x85) { g_warning ("possibly IT85xx class device?!"); fu_device_set_firmware_size (FU_DEVICE (self), 0x20000); return TRUE; } g_debug ("ECHIPID1: 0x%02x", (guint) tmp); /* can't we just use SIO_LDNxx_IDX_CHIPVER... */ if (!fu_superio_it89_device_read_ec_register (self, GCTRL_ECHIPVER, &tmp, error)) return FALSE; g_debug ("ECHIPVER: 0x%02x", (guint) tmp); if (tmp >> 4 == 0x00) { fu_device_set_firmware_size (FU_DEVICE (self), 0x20000); return TRUE; } if (tmp >> 4 == 0x04) { fu_device_set_firmware_size (FU_DEVICE (self), 0x30000); return TRUE; } if (tmp >> 4 == 0x08) { fu_device_set_firmware_size (FU_DEVICE (self), 0x40000); return TRUE; } g_warning ("falling back to default size"); fu_device_set_firmware_size (FU_DEVICE (self), 0x20000); return TRUE; } static gboolean fu_superio_it89_device_setup (FuSuperioDevice *self, GError **error) { guint8 version_tmp[2] = { 0x00 }; g_autofree gchar *version = NULL; /* try to recover this */ if (g_getenv ("FWUPD_SUPERIO_RECOVER") != NULL) { fu_device_set_firmware_size (FU_DEVICE (self), 0x20000); return TRUE; } /* get version */ if (!fu_superio_device_ec_get_param (self, 0x00, &version_tmp[0], error)) { g_prefix_error (error, "failed to get version major: "); return FALSE; } if (!fu_superio_device_ec_get_param (self, 0x01, &version_tmp[1], error)) { g_prefix_error (error, "failed to get version minor: "); return FALSE; } version = g_strdup_printf ("%02u.%02u", version_tmp[0], version_tmp[1]); fu_device_set_version (FU_DEVICE (self), version, FWUPD_VERSION_FORMAT_PAIR); /* get size from the EC */ if (!fu_superio_it89_device_ec_size (self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_superio_it89_device_ec_pm1do_sci (FuSuperioDevice *self, guint8 val, GError **error) { if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DOSCI, error)) return FALSE; if (!fu_superio_device_ec_write1 (self, val, error)) return FALSE; return TRUE; } static gboolean fu_superio_it89_device_ec_pm1do_smi (FuSuperioDevice *self, guint8 val, GError **error) { if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DOCMI, error)) return FALSE; if (!fu_superio_device_ec_write1 (self, val, error)) return FALSE; return TRUE; } static gboolean fu_superio_device_ec_read_status (FuSuperioDevice *self, GError **error) { guint8 tmp = 0x00; /* read status register */ if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_RDSR, error)) return FALSE; /* wait for write */ do { if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DI, error)) return FALSE; if (!fu_superio_device_ec_read (self, &tmp, error)) return FALSE; } while ((tmp & SIO_STATUS_EC_OBF) != 0); /* watch SCI events */ return fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error); } static gboolean fu_superio_device_ec_write_disable (FuSuperioDevice *self, GError **error) { guint8 tmp = 0x00; /* read existing status */ if (!fu_superio_device_ec_read_status (self, error)) return FALSE; /* write disable */ if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_WRDI, error)) return FALSE; /* read status register */ if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_RDSR, error)) return FALSE; /* wait for read */ do { if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DI, error)) return FALSE; if (!fu_superio_device_ec_read (self, &tmp, error)) return FALSE; } while ((tmp & SIO_STATUS_EC_IBF) != 0); /* watch SCI events */ return fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error); } static gboolean fu_superio_device_ec_write_enable (FuSuperioDevice *self, GError **error) { guint8 tmp = 0x0; /* read existing status */ if (!fu_superio_device_ec_read_status (self, error)) return FALSE; /* write enable */ if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_WREN, error)) return FALSE; /* read status register */ if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_RDSR, error)) return FALSE; /* wait for !BUSY */ do { if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DI, error)) return FALSE; if (!fu_superio_device_ec_read (self, &tmp, error)) return FALSE; } while ((tmp & 3) != SIO_STATUS_EC_IBF); /* watch SCI events */ return fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error); } static GBytes * fu_superio_it89_device_read_addr (FuSuperioDevice *self, guint32 addr, guint size, GFileProgressCallback progress_cb, GError **error) { g_autofree guint8 *buf = NULL; /* check... */ if (!fu_superio_device_ec_write_disable (self, error)) return NULL; if (!fu_superio_device_ec_read_status (self, error)) return NULL; /* high speed read */ if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) return NULL; if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_HS_READ, error)) return NULL; /* set address, MSB, MID, LSB */ if (!fu_superio_it89_device_ec_pm1do_smi (self, addr >> 16, error)) return NULL; if (!fu_superio_it89_device_ec_pm1do_smi (self, addr >> 8, error)) return NULL; if (!fu_superio_it89_device_ec_pm1do_smi (self, addr & 0xff, error)) return NULL; /* padding for HS? */ if (!fu_superio_it89_device_ec_pm1do_smi (self, 0x0, error)) return NULL; /* read out data */ buf = g_malloc0 (size); for (guint i = 0; i < size; i++) { if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DI, error)) return NULL; if (!fu_superio_device_ec_read (self, &buf[i], error)) return NULL; /* update progress */ if (progress_cb != NULL) progress_cb ((goffset) i, (goffset) size, self); } /* check again... */ if (!fu_superio_device_ec_read_status (self, error)) return NULL; /* success */ return g_bytes_new_take (g_steal_pointer (&buf), size); } static void fu_superio_it89_device_progress_cb (goffset current, goffset total, gpointer user_data) { FuDevice *device = FU_DEVICE (user_data); fu_device_set_progress_full (device, (gsize) current, (gsize) total); } static gboolean fu_superio_it89_device_write_addr (FuSuperioDevice *self, guint addr, GBytes *fw, GError **error) { gsize size = 0; const guint8 *buf = g_bytes_get_data (fw, &size); /* sanity check */ if ((addr & 0xff) != 0x00) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "write addr unaligned, got 0x%04x", (guint) addr); } if (size % 2 != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "write length not supported, got 0x%04x", (guint) size); } /* enable writes */ if (!fu_superio_device_ec_write_enable (self, error)) return FALSE; /* write DWORDs */ if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_WRITE_WORD, error)) return FALSE; /* set address, MSB, MID, LSB */ if (!fu_superio_it89_device_ec_pm1do_smi (self, addr >> 16, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_smi (self, addr >> 8, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_smi (self, addr & 0xff, error)) return FALSE; /* write data two bytes at a time */ for (guint i = 0; i < size; i += 2) { if (i > 0) { if (!fu_superio_device_ec_read_status (self, error)) return FALSE; if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_WRITE_WORD, error)) return FALSE; } if (!fu_superio_it89_device_ec_pm1do_smi (self, buf[i+0], error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_smi (self, buf[i+1], error)) return FALSE; } /* reset back? */ if (!fu_superio_device_ec_write_disable (self, error)) return FALSE; return fu_superio_device_ec_read_status (self, error); } static gboolean fu_superio_it89_device_erase_addr (FuSuperioDevice *self, guint addr, GError **error) { /* enable writes */ if (!fu_superio_device_ec_write_enable (self, error)) return FALSE; /* sector erase */ if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_4K_SECTOR_ERASE, error)) return FALSE; /* set address, MSB, MID, LSB */ if (!fu_superio_it89_device_ec_pm1do_smi (self, addr >> 16, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_smi (self, addr >> 8, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_smi (self, addr & 0xff, error)) return FALSE; /* watch SCI events */ if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error)) return FALSE; return fu_superio_device_ec_read_status (self, error); } /* The 14th byte of the 16 byte signature is always read from the hardware as * 0x00 rather than the specified 0xAA. Fix up the firmware to match the * .ROM file which uses 0x7F as the number of bytes to mirror to e-flash... */ static GBytes * fu_plugin_superio_fix_signature (FuSuperioDevice *self, GBytes *fw, GError **error) { gsize sz = 0; const guint8 *buf = g_bytes_get_data (fw, &sz); g_autofree guint8 *buf2 = NULL; const guint signature_offset = 0x4d; /* IT85, IT89 is 0x8d */ /* not big enough */ if (sz < signature_offset + 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "image too small to fix"); return NULL; } /* not zero */ if (buf[signature_offset] != 0x0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "nonzero signature byte"); return NULL; } /* fix signature to match SMT version */ buf2 = g_memdup (buf, sz); buf2[signature_offset] = 0x7f; return g_bytes_new_take (g_steal_pointer (&buf2), sz); } static GBytes * fu_superio_it89_device_read_firmware (FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); guint64 fwsize = fu_device_get_firmware_size_min (device); g_autoptr(GBytes) blob = NULL; fu_device_set_status (device, FWUPD_STATUS_DEVICE_READ); blob = fu_superio_it89_device_read_addr (self, 0x0, fwsize, fu_superio_it89_device_progress_cb, error); return fu_plugin_superio_fix_signature (self, blob, error); } static gboolean fu_superio_it89_device_attach (FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); /* re-enable HOSTWA -- use 0xfd for LCFC */ if (!fu_superio_device_ec_write1 (self, SIO_CMD_EC_ENABLE_HOST_WA, error)) return FALSE; /* success */ fu_device_remove_flag (self, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_superio_it89_device_detach (FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); guint8 tmp = 0x00; /* turn off HOSTWA bit, keeping HSEMIE and HSEMW high */ if (!fu_superio_device_ec_write1 (self, SIO_CMD_EC_DISABLE_HOST_WA, error)) return FALSE; if (!fu_superio_device_ec_read (self, &tmp, error)) return FALSE; if (tmp != 0x33) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to clear HOSTWA, got 0x%02x, expected 0x33", tmp); return FALSE; } /* success */ fu_device_add_flag (self, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_superio_it89_device_check_eflash (FuSuperioDevice *self, GError **error) { g_autoptr(GBytes) fw = NULL; const guint64 fwsize = fu_device_get_firmware_size_min (FU_DEVICE (self)); const guint sigsz = 16; /* last 16 bytes of eeprom */ fw = fu_superio_it89_device_read_addr (self, fwsize - sigsz, sigsz, NULL, error); if (fw == NULL) { g_prefix_error (error, "failed to read signature bytes"); return FALSE; } /* cannot flash here without keyboard programmer */ if (!fu_common_bytes_is_empty (fw)) { gsize sz = 0; const guint8 *buf = g_bytes_get_data (fw, &sz); g_autoptr(GString) str = g_string_new (NULL); for (guint i = 0; i < sz; i++) g_string_append_printf (str, "0x%02x ", buf[i]); if (str->len > 0) g_string_truncate (str, str->len - 1); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "e-flash has been protected: %s", str->str); return FALSE; } /* success */ return TRUE; } static gboolean fu_superio_it89_device_write_chunk (FuSuperioDevice *self, FuChunk *chk, GError **error) { g_autoptr(GBytes) fw1 = NULL; g_autoptr(GBytes) fw2 = NULL; g_autoptr(GBytes) fw3 = NULL; /* erase page */ if (!fu_superio_it89_device_erase_addr (self, chk->address, error)) { g_prefix_error (error, "failed to erase @0x%04x", (guint) chk->address); return FALSE; } /* check erased */ fw1 = fu_superio_it89_device_read_addr (self, chk->address, chk->data_sz, NULL, error); if (fw1 == NULL) { g_prefix_error (error, "failed to read erased " "bytes @0x%04x", (guint) chk->address); return FALSE; } if (!fu_common_bytes_is_empty (fw1)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "sector was not erased"); return FALSE; } /* skip empty page */ fw2 = g_bytes_new_static (chk->data, chk->data_sz); if (fu_common_bytes_is_empty (fw2)) return TRUE; /* write page */ if (!fu_superio_it89_device_write_addr (self, chk->address, fw2, error)) { g_prefix_error (error, "failed to write @0x%04x", (guint) chk->address); return FALSE; } /* verify page */ fw3 = fu_superio_it89_device_read_addr (self, chk->address, chk->data_sz, NULL, error); if (fw3 == NULL) { g_prefix_error (error, "failed to read written " "bytes @0x%04x", (guint) chk->address); return FALSE; } if (!fu_common_bytes_compare (fw2, fw3, error)) { g_prefix_error (error, "failed to verify @0x%04x", (guint) chk->address); return FALSE; } /* success */ return TRUE; } static gboolean fu_superio_it89_device_get_jedec_id (FuSuperioDevice *self, guint8 *id, GError **error) { /* read status register */ if (!fu_superio_device_ec_read_status (self, error)) return FALSE; if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci (self, SIO_SPI_CMD_JEDEC_ID, error)) return FALSE; /* wait for reads */ for (guint i = 0; i < 4; i++) { if (!fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DI, error)) return FALSE; if (!fu_superio_device_ec_read (self, &id[i], error)) return FALSE; } /* watch SCI events */ return fu_superio_device_ec_write1 (self, SIO_EC_PMC_PM1DISCI, error); } static gboolean fu_superio_it89_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); guint8 id[4] = { 0x0 }; g_autoptr(GBytes) fw_fixed = NULL; g_autoptr(GPtrArray) chunks = NULL; /* check JEDEC ID */ if (!fu_superio_it89_device_get_jedec_id (self, id, error)) { g_prefix_error (error, "failed to get JEDEC ID: "); return FALSE; } if (id[0] != 0xff || id[1] != 0xff || id[2] != 0xfe || id[3] != 0xff) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "JEDEC ID not valid, 0x%02x%02x%02x%02x", id[0], id[1], id[2], id[3]); return FALSE; } /* check eflash is writable */ if (!fu_superio_it89_device_check_eflash (self, error)) return FALSE; /* disable the mirroring of e-flash */ if (g_getenv ("FWUPD_SUPERIO_DISABLE_MIRROR") != NULL) { fw_fixed = fu_plugin_superio_fix_signature (self, fw, error); if (fw_fixed == NULL) return FALSE; } else { fw_fixed = g_bytes_ref (fw); } /* chunks of 1kB, skipping the final chunk */ chunks = fu_chunk_array_new_from_bytes (fw_fixed, 0x00, 0x00, 0x400); fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < chunks->len - 1; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); /* try this many times; the failure-to-flash case leaves you * without a keyboard and future boot may completely fail */ for (guint j = 0;; j++) { g_autoptr(GError) error_chk = NULL; if (fu_superio_it89_device_write_chunk (self, chk, &error_chk)) break; if (j > 5) { g_propagate_error (error, g_steal_pointer (&error_chk)); return FALSE; } g_warning ("failure %u: %s", j, error_chk->message); } /* set progress */ fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len); } /* success */ fu_device_set_progress (device, 100); return TRUE; } static void fu_superio_it89_device_init (FuSuperioIt89Device *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_ONLY_OFFLINE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } static void fu_superio_it89_device_class_init (FuSuperioIt89DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuSuperioDeviceClass *klass_superio_device = FU_SUPERIO_DEVICE_CLASS (klass); klass_device->attach = fu_superio_it89_device_attach; klass_device->detach = fu_superio_it89_device_detach; klass_device->read_firmware = fu_superio_it89_device_read_firmware; klass_device->write_firmware = fu_superio_it89_device_write_firmware; klass_superio_device->setup = fu_superio_it89_device_setup; } fwupd-1.2.14/plugins/superio/fu-superio-it89-device.h000066400000000000000000000005611402665037500224040ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-superio-device.h" G_BEGIN_DECLS #define FU_TYPE_SUPERIO_IT89_DEVICE (fu_superio_it89_device_get_type ()) G_DECLARE_FINAL_TYPE (FuSuperioIt89Device, fu_superio_it89_device, FU, SUPERIO_IT89_DEVICE, FuSuperioDevice) G_END_DECLS fwupd-1.2.14/plugins/superio/meson.build000066400000000000000000000012011402665037500202370ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginSuperio"'] install_data(['superio.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_superio', fu_hash, sources : [ 'fu-plugin-superio.c', 'fu-superio-device.c', 'fu-superio-it85-device.c', 'fu-superio-it89-device.c', 'fu-superio-common.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/superio/superio.quirk000066400000000000000000000012311402665037500206430ustar00rootroot00000000000000# N13xWU [HwId=992f1bc7-f8ee-567a-88dd-30e5158d72ed] SuperioChipsets=IT8587 # W740SU [HwId=f00d8c4e-dce2-51c3-89d6-6cbc5fc5cdbb] SuperioChipsets=IT8587 # Star LabTop Mk3 [HwId=3dc52d2c-9e9b-5ba5-b10d-9ba1eb11dacc] SuperioChipsets=IT8987 InstallDuration=20 # Star Lite Mk2 [HwId=d1a64840-4307-58fb-a62c-de28a07c0151] SuperioChipsets=IT8987 InstallDuration=20 [SuperIO=IT8510] Id=0x8510 Port=0x2e [SuperIO=IT8511] Id=0x8511 Port=0x2e [SuperIO=IT8512] Id=0x8512 Port=0x2e [SuperIO=IT8513] Id=0x8513 Port=0x2e [SuperIO=IT8516] Id=0x8516 Port=0x2e [SuperIO=IT8518] Id=0x8518 Port=0x2e [SuperIO=IT8587] Id=0x8587 Port=0x2e [SuperIO=IT8987] Id=0x8987 Port=0x4e fwupd-1.2.14/plugins/synaptics-prometheus/000077500000000000000000000000001402665037500206235ustar00rootroot00000000000000fwupd-1.2.14/plugins/synaptics-prometheus/README.md000066400000000000000000000011611402665037500221010ustar00rootroot00000000000000Synaptics Prometheus ==================== Introduction ------------ This plugin can flash the firmware on the Synaptics Prometheus fingerprint readers. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. The binary file has a vendor-specific header that is used when flashing the image. This plugin supports the following protocol ID: * com.synaptics.prometheus GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_06CB&PID_00A9&REV_0001` * `USB\VID_06CB&PID_00A9` fwupd-1.2.14/plugins/synaptics-prometheus/data/000077500000000000000000000000001402665037500215345ustar00rootroot00000000000000fwupd-1.2.14/plugins/synaptics-prometheus/data/lsusb.txt000066400000000000000000000044351402665037500234330ustar00rootroot00000000000000Bus 001 Device 043: ID 06cb:00a9 Synaptics, Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 255 Vendor Specific Class bDeviceSubClass 16 bDeviceProtocol 255 bMaxPacketSize0 8 idVendor 0x06cb Synaptics, Inc. idProduct 0x00a9 bcdDevice 0.00 iManufacturer 0 iProduct 0 iSerial 1 942cfe315551 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0027 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 4 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/synaptics-prometheus/data/test.pkg000066400000000000000000000004461402665037500232220ustar00rootroot00000000000000A RHfwupd-1.2.14/plugins/synaptics-prometheus/fu-dump.c000066400000000000000000000024201402665037500223420ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaprom-firmware.h" static gboolean fu_dump_parse (const gchar *filename, GError **error) { gchar *data = NULL; gsize len = 0; g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) array = NULL; if (!g_file_get_contents (filename, &data, &len, error)) return FALSE; blob = g_bytes_new_take (data, len); array = fu_synaprom_firmware_new (blob, error); return array != NULL; } static gboolean fu_dump_generate (const gchar *filename, GError **error) { const gchar *data; gsize len = 0; g_autoptr(GBytes) blob = NULL; blob = fu_synaprom_firmware_generate (); data = g_bytes_get_data (blob, &len); return g_file_set_contents (filename, data, len, error); } int main (int argc, char **argv) { g_autoptr(GError) error = NULL; if (argc == 2) { if (!fu_dump_parse (argv[1], &error)) { g_printerr ("parse failed: %s\n", error->message); return 1; } } else if (argc == 3 && g_strcmp0 (argv[2], "gen") == 0) { if (!fu_dump_generate (argv[1], &error)) { g_printerr ("generate failed: %s\n", error->message); return 1; } } else { g_printerr ("firmware filename required\n"); return 2; } g_print ("OK!\n"); return 0; } fwupd-1.2.14/plugins/synaptics-prometheus/fu-plugin-synaptics-prometheus.c000066400000000000000000000042101402665037500270760ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaprom-device.h" #include "fu-plugin-vfuncs.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.synaptics.prometheus"); } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuSynapromDevice) dev = NULL; /* open the device */ dev = fu_synaprom_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; /* success */ fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } gboolean fu_plugin_update_detach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_detach (device, error); } gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_attach (device, error); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* write the firmware */ locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; if (!fu_device_write_firmware (dev, blob_fw, flags, error)) return FALSE; /* success */ return TRUE; } gboolean fu_plugin_update_reload (FuPlugin *plugin, FuDevice *dev, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* get the new version number */ locker = fu_device_locker_new (dev, error); if (locker == NULL) { g_prefix_error (error, "failed to re-open device: "); return FALSE; } /* success */ return TRUE; } fwupd-1.2.14/plugins/synaptics-prometheus/fu-self-test.c000066400000000000000000000046171402665037500233150ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-plugin-private.h" #include "fu-test.h" #include "fu-synaprom-device.h" #include "fu-synaprom-firmware.h" static void fu_test_synaprom_firmware_func (void) { const guint8 *buf; gsize sz = 0; g_autofree gchar *filename = NULL; g_autoptr(FuSynapromDevice) device = fu_synaprom_device_new (NULL); g_autoptr(GBytes) blob1 = NULL; g_autoptr(GBytes) blob2 = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) firmware = NULL; filename = fu_test_get_filename (TESTDATADIR, "test.pkg"); g_assert_nonnull (filename); fw = fu_common_get_contents_bytes (filename, &error); g_assert_no_error (error); g_assert_nonnull (fw); buf = g_bytes_get_data (fw, &sz); g_assert_cmpint (sz, ==, 294); g_assert_cmpint (buf[0], ==, 0x01); g_assert_cmpint (buf[1], ==, 0x00); firmware = fu_synaprom_firmware_new (fw, &error); g_assert_no_error (error); g_assert_nonnull (firmware); /* does not exist */ blob1 = fu_synaprom_firmware_get_bytes_by_tag (firmware, 0, NULL); g_assert_null (blob1); blob1 = fu_synaprom_firmware_get_bytes_by_tag (firmware, FU_SYNAPROM_FIRMWARE_TAG_CFG_HEADER, NULL); g_assert_null (blob1); /* header needs to exist */ blob1 = fu_synaprom_firmware_get_bytes_by_tag (firmware, FU_SYNAPROM_FIRMWARE_TAG_MFW_HEADER, &error); g_assert_no_error (error); g_assert_nonnull (blob1); buf = g_bytes_get_data (blob1, &sz); g_assert_cmpint (sz, ==, 24); g_assert_cmpint (buf[0], ==, 0x41); g_assert_cmpint (buf[1], ==, 0x00); g_assert_cmpint (buf[2], ==, 0x00); g_assert_cmpint (buf[3], ==, 0x00); g_assert_cmpint (buf[4], ==, 0xff); /* payload needs to exist */ fu_synaprom_device_set_version (device, 10, 1, 1234); blob2 = fu_synaprom_device_prepare_fw (FU_DEVICE (device), fw, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert_nonnull (blob2); buf = g_bytes_get_data (blob2, &sz); g_assert_cmpint (sz, ==, 2); g_assert_cmpint (buf[0], ==, 'R'); g_assert_cmpint (buf[1], ==, 'H'); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_test_add_func ("/synaprom/firmware", fu_test_synaprom_firmware_func); return g_test_run (); } fwupd-1.2.14/plugins/synaptics-prometheus/fu-synaprom-common.c000066400000000000000000000055241402665037500245430ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-synaprom-common.h" enum { FU_SYNAPROM_RESULT_OK = 0, FU_SYNAPROM_RESULT_GEN_OPERATION_CANCELED = 103, FU_SYNAPROM_RESULT_GEN_INVALID = 110, FU_SYNAPROM_RESULT_GEN_BAD_PARAM = 111, FU_SYNAPROM_RESULT_GEN_NULL_POINTER = 112, FU_SYNAPROM_RESULT_GEN_UNEXPECTED_FORMAT = 114, FU_SYNAPROM_RESULT_GEN_TIMEOUT = 117, FU_SYNAPROM_RESULT_GEN_OBJECT_DOESNT_EXIST = 118, FU_SYNAPROM_RESULT_GEN_ERROR = 119, FU_SYNAPROM_RESULT_SENSOR_MALFUNCTIONED = 202, FU_SYNAPROM_RESULT_SYS_OUT_OF_MEMORY = 602, }; GByteArray * fu_synaprom_request_new (guint8 cmd, const gpointer data, gsize len) { GByteArray *blob = g_byte_array_new (); g_byte_array_append (blob, &cmd, 1); if (data != NULL) g_byte_array_append (blob, data, len); return blob; } GByteArray * fu_synaprom_reply_new (gsize cmdlen) { GByteArray *blob = g_byte_array_new (); g_byte_array_set_size (blob, cmdlen); return blob; } gboolean fu_synaprom_error_from_status (guint16 status, GError **error) { if (status == FU_SYNAPROM_RESULT_OK) return TRUE; switch (status) { case FU_SYNAPROM_RESULT_GEN_OPERATION_CANCELED: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "cancelled"); break; case FU_SYNAPROM_RESULT_GEN_BAD_PARAM: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "bad parameter"); break; case FU_SYNAPROM_RESULT_GEN_NULL_POINTER: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "NULL pointer"); break; case FU_SYNAPROM_RESULT_GEN_UNEXPECTED_FORMAT: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unexpected format"); break; case FU_SYNAPROM_RESULT_GEN_TIMEOUT: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timed out"); break; case FU_SYNAPROM_RESULT_GEN_OBJECT_DOESNT_EXIST: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "object does not exist"); break; case FU_SYNAPROM_RESULT_GEN_ERROR: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic error"); break; case FU_SYNAPROM_RESULT_SENSOR_MALFUNCTIONED: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "sensor malfunctioned"); break; case FU_SYNAPROM_RESULT_SYS_OUT_OF_MEMORY: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_AGAIN, "out of heap memory"); break; default: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "error status: 0x%x", status); } return FALSE; } fwupd-1.2.14/plugins/synaptics-prometheus/fu-synaprom-common.h000066400000000000000000000006441402665037500245460ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include GByteArray *fu_synaprom_request_new (guint8 cmd, const gpointer data, gsize len); GByteArray *fu_synaprom_reply_new (gsize cmdlen); gboolean fu_synaprom_error_from_status (guint16 status, GError **error); fwupd-1.2.14/plugins/synaptics-prometheus/fu-synaprom-config.c000066400000000000000000000233641402665037500245220ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaprom-common.h" #include "fu-synaprom-config.h" #include "fu-synaprom-firmware.h" struct _FuSynapromConfig { FuDevice parent_instance; FuSynapromDevice *device; guint32 configid1; /* config ID1 */ guint32 configid2; /* config ID2 */ }; /* Iotas can exceed the size of available RAM in the part. * In order to allow the host to read them the IOTA_FIND command supports * transferring iotas with multiple commands */ typedef struct __attribute__((packed)) { guint16 itype; /* type of iotas to find */ guint16 flags; /* flags, see below */ guint8 maxniotas; /* maximum number of iotas to return, 0 = unlimited */ guint8 firstidx; /* first index of iotas to return */ guint8 dummy[2]; guint32 offset; /* byte offset of data to return */ guint32 nbytes; /* maximum number of bytes to return */ } FuSynapromCmdIotaFind; /* this is followed by a chain of iotas, as follows */ typedef struct __attribute__((packed)) { guint16 status; guint32 fullsize; guint16 nbytes; guint16 itype; } FuSynapromReplyIotaFindHdr; /* this iota contains the configuration id and version */ typedef struct __attribute__((packed)) { guint32 config_id1; /* YYMMDD */ guint32 config_id2; /* HHMMSS */ guint16 version; guint16 unused[3]; } FuSynapromIotaConfigVersion; #define FU_SYNAPROM_CMD_IOTA_FIND_FLAGS_ALLIOTAS 0x0001 /* itype ignored*/ #define FU_SYNAPROM_CMD_IOTA_FIND_FLAGS_READMAX 0x0002 /* nbytes ignored */ #define FU_SYNAPROM_MAX_IOTA_READ_SIZE (64 * 1024) /* max size of iota data returned */ #define FU_SYNAPROM_IOTA_ITYPE_CONFIG_VERSION 0x0009 /* Configuration id and version */ G_DEFINE_TYPE (FuSynapromConfig, fu_synaprom_config, FU_TYPE_DEVICE) enum { PROP_0, PROP_DEVICE, PROP_LAST }; static gboolean fu_synaprom_config_setup (FuDevice *device, GError **error) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (device); FuSynapromCmdIotaFind cmd = { 0x0 }; FuSynapromIotaConfigVersion cfg; FuSynapromReplyIotaFindHdr hdr; g_autofree gchar *version = NULL; g_autoptr(GByteArray) reply = NULL; g_autoptr(GByteArray) request = NULL; /* get IOTA */ cmd.itype = GUINT16_TO_LE((guint16)FU_SYNAPROM_IOTA_ITYPE_CONFIG_VERSION); cmd.flags = GUINT16_TO_LE((guint16)FU_SYNAPROM_CMD_IOTA_FIND_FLAGS_READMAX); request = fu_synaprom_request_new (FU_SYNAPROM_CMD_IOTA_FIND, &cmd, sizeof(cmd)); reply = fu_synaprom_reply_new (sizeof(FuSynapromReplyIotaFindHdr) + FU_SYNAPROM_MAX_IOTA_READ_SIZE); if (!fu_synaprom_device_cmd_send (self->device, request, reply, 5000, error)) return FALSE; if (reply->len < sizeof(hdr) + sizeof(cfg)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG return data invalid size: 0x%04x", reply->len); return FALSE; } memcpy (&hdr, reply->data, sizeof(hdr)); if (GUINT32_FROM_LE(hdr.itype) != FU_SYNAPROM_IOTA_ITYPE_CONFIG_VERSION) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG iota had invalid itype: 0x%04x", GUINT32_FROM_LE(hdr.itype)); return FALSE; } memcpy (&cfg, reply->data + sizeof(hdr), sizeof(cfg)); self->configid1 = GUINT32_FROM_LE(cfg.config_id1); self->configid2 = GUINT32_FROM_LE(cfg.config_id2); g_debug ("id1=%u, id2=%u, ver=%u", self->configid1, self->configid2, GUINT16_FROM_LE(cfg.version)); /* no downgrades are allowed */ version = g_strdup_printf ("%04u", GUINT16_FROM_LE(cfg.version)); fu_device_set_version (FU_DEVICE (self), version, FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_version_lowest (FU_DEVICE (self), version); return TRUE; } static GBytes * fu_synaprom_config_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (device); FuSynapromFirmwareCfgHeader hdr; g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) firmware = NULL; guint32 product; guint32 id1; /* parse the firmware */ fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); firmware = fu_synaprom_firmware_new (fw, error); if (firmware == NULL) return NULL; /* check the update header product and version */ blob = fu_synaprom_firmware_get_bytes_by_tag (firmware, FU_SYNAPROM_FIRMWARE_TAG_CFG_HEADER, error); if (blob == NULL) return NULL; if (g_bytes_get_size (blob) != sizeof(hdr)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "CFG metadata is invalid"); return NULL; } memcpy (&hdr, g_bytes_get_data (blob, NULL), sizeof(hdr)); product = GUINT32_FROM_LE(hdr.product); if (product != FU_SYNAPROM_PRODUCT_PROMETHEUS) { if (flags & FWUPD_INSTALL_FLAG_FORCE) { g_warning ("CFG metadata not compatible, " "got 0x%02x expected 0x%02x", product, (guint) FU_SYNAPROM_PRODUCT_PROMETHEUS); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG metadata not compatible, " "got 0x%02x expected 0x%02x", product, (guint) FU_SYNAPROM_PRODUCT_PROMETHEUS); return NULL; } } id1 = GUINT32_FROM_LE(hdr.id1); if (id1 != self->configid1) { if (flags & FWUPD_INSTALL_FLAG_FORCE) { g_warning ("CFG version not compatible, " "got %u expected %u", id1, self->configid1); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG version not compatible, " "got %u expected %u", id1, self->configid1); return NULL; } } /* get payload */ return fu_synaprom_firmware_get_bytes_by_tag (firmware, FU_SYNAPROM_FIRMWARE_TAG_CFG_PAYLOAD, error); } static gboolean fu_synaprom_config_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (device); /* I assume the CFG/MFW difference is detected in the device...*/ return fu_synaprom_device_write_fw (self->device, fw, error); } static void fu_synaprom_config_init (FuSynapromConfig *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_logical_id (FU_DEVICE (self), "cfg"); fu_device_set_name (FU_DEVICE (self), "Prometheus IOTA Config"); } static void fu_synaprom_config_finalize (GObject *obj) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (obj); g_object_unref (self->device); G_OBJECT_CLASS (fu_synaprom_config_parent_class)->finalize (obj); } static void fu_synaprom_config_constructed (GObject *obj) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (obj); g_autofree gchar *devid = NULL; /* append the firmware kind to the generated GUID */ devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X-cfg", fu_usb_device_get_vid (FU_USB_DEVICE (self->device)), fu_usb_device_get_pid (FU_USB_DEVICE (self->device))); fu_device_add_instance_id (FU_DEVICE (self), devid); G_OBJECT_CLASS (fu_synaprom_config_parent_class)->constructed (obj); } static void fu_synaprom_config_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (obj); switch (prop_id) { case PROP_DEVICE: g_value_set_object (value, self->device); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static void fu_synaprom_config_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (obj); switch (prop_id) { case PROP_DEVICE: g_set_object (&self->device, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static gboolean fu_synaprom_config_open (FuDevice *device, GError **error) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (device); return fu_device_open (FU_DEVICE (self->device), error); } static gboolean fu_synaprom_config_close (FuDevice *device, GError **error) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (device); return fu_device_close (FU_DEVICE (self->device), error); } static gboolean fu_synaprom_config_attach (FuDevice *device, GError **error) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (device); return fu_device_attach (FU_DEVICE (self->device), error); } static gboolean fu_synaprom_config_detach (FuDevice *device, GError **error) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (device); return fu_device_detach (FU_DEVICE (self->device), error); } static void fu_synaprom_config_class_init (FuSynapromConfigClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; object_class->constructed = fu_synaprom_config_constructed; object_class->finalize = fu_synaprom_config_finalize; object_class->get_property = fu_synaprom_config_get_property; object_class->set_property = fu_synaprom_config_set_property; klass_device->write_firmware = fu_synaprom_config_write_firmware; klass_device->prepare_firmware = fu_synaprom_config_prepare_firmware; klass_device->open = fu_synaprom_config_open; klass_device->close = fu_synaprom_config_close; klass_device->setup = fu_synaprom_config_setup; klass_device->attach = fu_synaprom_config_attach; klass_device->detach = fu_synaprom_config_detach; pspec = g_param_spec_object ("device", NULL, NULL, FU_TYPE_SYNAPROM_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_DEVICE, pspec); } FuSynapromConfig * fu_synaprom_config_new (FuSynapromDevice *device) { FuSynapromConfig *self; self = g_object_new (FU_TYPE_SYNAPROM_CONFIG, "device", device, NULL); return FU_SYNAPROM_CONFIG (self); } fwupd-1.2.14/plugins/synaptics-prometheus/fu-synaprom-config.h000066400000000000000000000007251402665037500245230ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-synaprom-device.h" G_BEGIN_DECLS #define FU_TYPE_SYNAPROM_CONFIG (fu_synaprom_config_get_type ()) G_DECLARE_FINAL_TYPE (FuSynapromConfig, fu_synaprom_config, FU, SYNAPROM_CONFIG, FuDevice) FuSynapromConfig *fu_synaprom_config_new (FuSynapromDevice *device); G_END_DECLS fwupd-1.2.14/plugins/synaptics-prometheus/fu-synaprom-device.c000066400000000000000000000341761402665037500245170ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaprom-common.h" #include "fu-synaprom-config.h" #include "fu-synaprom-device.h" #include "fu-synaprom-firmware.h" struct _FuSynapromDevice { FuUsbDevice parent_instance; guint8 vmajor; guint8 vminor; }; /* vendor-specific USB control requets to write DFT word (Hayes) */ #define FU_SYNAPROM_USB_CTRLREQUEST_VENDOR_WRITEDFT 21 /* endpoint addresses for command and fingerprint data */ #define FU_SYNAPROM_USB_REQUEST_EP 0x01 #define FU_SYNAPROM_USB_REPLY_EP 0x81 #define FU_SYNAPROM_USB_FINGERPRINT_EP 0x82 #define FU_SYNAPROM_USB_INTERRUPT_EP 0x83 /* le */ typedef struct __attribute__((packed)) { guint16 status; } FuSynapromReplyGeneric; /* le */ typedef struct __attribute__((packed)) { guint16 status; guint32 buildtime; /* Unix-style build time */ guint32 buildnum; /* build number */ guint8 vmajor; /* major version */ guint8 vminor; /* minor version */ guint8 target; /* target, e.g. VCSFW_TARGET_ROM */ guint8 product; /* product, e.g. VCSFW_PRODUCT_FALCON */ guint8 siliconrev; /* silicon revision */ guint8 formalrel; /* boolean: non-zero -> formal release */ guint8 platform; /* Platform (PCB) revision */ guint8 patch; /* patch level */ guint8 serial_number[6]; /* 48-bit Serial Number */ guint8 security[2]; /* bytes 0 and 1 of OTP */ guint32 patchsig; /* opaque patch signature */ guint8 iface; /* interface type, see below */ guint8 otpsig[3]; /* OTP Patch Signature */ guint16 otpspare1; /* spare space */ guint8 reserved; /* reserved byte */ guint8 device_type; /* device type */ } FuSynapromReplyGetVersion; /* the following bits describe security options in ** FuSynapromReplyGetVersion::security[1] bit-field */ #define FU_SYNAPROM_SECURITY1_PROD_SENSOR (1 << 5) G_DEFINE_TYPE (FuSynapromDevice, fu_synaprom_device, FU_TYPE_USB_DEVICE) static gboolean fu_synaprom_device_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); if (!g_usb_device_claim_interface (usb_device, 0x0, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { return FALSE; } return TRUE; } gboolean fu_synaprom_device_cmd_send (FuSynapromDevice *device, GByteArray *request, GByteArray *reply, guint timeout_ms, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); gboolean ret; gsize actual_len = 0; if (g_getenv ("FWUPD_SYNAPROM_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "REQST", request->data, request->len, 16, FU_DUMP_FLAGS_SHOW_ADDRESSES); } ret = g_usb_device_bulk_transfer (usb_device, FU_SYNAPROM_USB_REQUEST_EP, request->data, request->len, &actual_len, timeout_ms, NULL, error); if (!ret) { g_prefix_error (error, "failed to request: "); return FALSE; } if (actual_len < request->len) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only sent 0x%04x of 0x%04x", (guint) actual_len, request->len); return FALSE; } ret = g_usb_device_bulk_transfer (usb_device, FU_SYNAPROM_USB_REPLY_EP, reply->data, reply->len, NULL, /* allowed to return short read */ timeout_ms, NULL, error); if (!ret) { g_prefix_error (error, "failed to reply: "); return FALSE; } if (g_getenv ("FWUPD_SYNAPROM_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "REPLY", reply->data, actual_len, 16, FU_DUMP_FLAGS_SHOW_ADDRESSES); } /* parse as FuSynapromReplyGeneric */ if (reply->len >= sizeof(FuSynapromReplyGeneric)) { FuSynapromReplyGeneric *hdr = (FuSynapromReplyGeneric *) reply->data; return fu_synaprom_error_from_status (GUINT16_FROM_LE(hdr->status), error); } /* success */ return TRUE; } void fu_synaprom_device_set_version (FuSynapromDevice *self, guint8 vmajor, guint8 vminor, guint32 buildnum) { g_autofree gchar *str = NULL; /* We decide to skip 10.02.xxxxxx firmware, so we force the minor version from 0x02 ** to 0x01 to make the devices with 0x02 minor version firmware allow to be updated ** back to minor version 0x01. */ if (vmajor == 0x0a && vminor == 0x02) { g_debug ("quirking vminor from %02x to 01", vminor); vminor = 0x01; } /* set display version */ str = g_strdup_printf ("%02u.%02u.%u", vmajor, vminor, buildnum); fu_device_set_version (FU_DEVICE (self), str, FWUPD_VERSION_FORMAT_TRIPLET); /* we need this for checking the firmware compatibility later */ self->vmajor = vmajor; self->vminor = vminor; } static void fu_synaprom_device_set_serial_number (FuSynapromDevice *self, guint64 serial_number) { g_autofree gchar *str = NULL; str = g_strdup_printf ("%" G_GUINT64_FORMAT, serial_number); fu_device_set_serial (FU_DEVICE (self), str); } static gboolean fu_synaprom_device_setup (FuDevice *device, GError **error) { FuSynapromDevice *self = FU_SYNAPROM_DEVICE (device); FuSynapromReplyGetVersion pkt; guint32 product; guint64 serial_number = 0; g_autoptr(GByteArray) request = NULL; g_autoptr(GByteArray) reply = NULL; /* get version */ request = fu_synaprom_request_new (FU_SYNAPROM_CMD_GET_VERSION, NULL, 0); reply = fu_synaprom_reply_new (sizeof(FuSynapromReplyGetVersion)); if (!fu_synaprom_device_cmd_send (self, request, reply, 250, error)) { g_prefix_error (error, "failed to get version: "); return FALSE; } memcpy (&pkt, reply->data, sizeof(pkt)); product = GUINT32_FROM_LE(pkt.product); g_debug ("product ID is %u, version=%u.%u, buildnum=%u prod=%i", product, pkt.vmajor, pkt.vminor, GUINT32_FROM_LE(pkt.buildnum), pkt.security[1] & FU_SYNAPROM_SECURITY1_PROD_SENSOR); fu_synaprom_device_set_version (self, pkt.vmajor, pkt.vminor, GUINT32_FROM_LE(pkt.buildnum)); /* get serial number */ memcpy (&serial_number, pkt.serial_number, sizeof(pkt.serial_number)); fu_synaprom_device_set_serial_number (self, serial_number); /* check device type */ if (product == FU_SYNAPROM_PRODUCT_PROMETHEUS) { fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else if (product == FU_SYNAPROM_PRODUCT_PROMETHEUSPBL || product == FU_SYNAPROM_PRODUCT_PROMETHEUSMSBL) { fu_device_add_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "device %u is not supported by this plugin", product); return FALSE; } /* add updatable config child, if this is a production sensor */ if (pkt.security[1] & FU_SYNAPROM_SECURITY1_PROD_SENSOR) { g_autoptr(FuSynapromConfig) cfg = fu_synaprom_config_new (self); if (!fu_device_setup (FU_DEVICE (cfg), error)) { g_prefix_error (error, "failed to get config version: "); return FALSE; } fu_device_add_child (FU_DEVICE (device), FU_DEVICE (cfg)); } /* success */ return TRUE; } static gboolean fu_synaprom_device_cmd_download_chunk (FuSynapromDevice *device, const GByteArray *chunk, GError **error) { g_autoptr(GByteArray) request = NULL; g_autoptr(GByteArray) reply = NULL; request = fu_synaprom_request_new (FU_SYNAPROM_CMD_BOOTLDR_PATCH, chunk->data, chunk->len); reply = fu_synaprom_reply_new (sizeof(FuSynapromReplyGeneric)); return fu_synaprom_device_cmd_send (device, request, reply, 20000, error); } GBytes * fu_synaprom_device_prepare_fw (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapromDevice *self = FU_SYNAPROM_DEVICE (device); FuSynapromFirmwareMfwHeader hdr; guint32 product; g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) firmware = NULL; /* parse the firmware */ fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); firmware = fu_synaprom_firmware_new (fw, error); if (firmware == NULL) return NULL; /* check the update header product and version */ blob = fu_synaprom_firmware_get_bytes_by_tag (firmware, FU_SYNAPROM_FIRMWARE_TAG_MFW_HEADER, error); if (blob == NULL) return NULL; if (g_bytes_get_size (blob) != sizeof(hdr)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "MFW metadata is invalid"); return NULL; } memcpy (&hdr, g_bytes_get_data (blob, NULL), sizeof(hdr)); product = GUINT32_FROM_LE(hdr.product); if (product != FU_SYNAPROM_PRODUCT_PROMETHEUS) { if (flags & FWUPD_INSTALL_FLAG_FORCE) { g_warning ("MFW metadata not compatible, " "got 0x%02x expected 0x%02x", product, (guint) FU_SYNAPROM_PRODUCT_PROMETHEUS); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "MFW metadata not compatible, " "got 0x%02x expected 0x%02x", product, (guint) FU_SYNAPROM_PRODUCT_PROMETHEUS); return NULL; } } if (hdr.vmajor != self->vmajor || hdr.vminor != self->vminor) { if (flags & FWUPD_INSTALL_FLAG_FORCE) { g_warning ("MFW version not compatible, " "got %u.%u expected %u.%u", hdr.vmajor, hdr.vminor, self->vmajor, self->vminor); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "MFW version not compatible, " "got %u.%u expected %u.%u", hdr.vmajor, hdr.vminor, self->vmajor, self->vminor); return NULL; } } /* get payload */ return fu_synaprom_firmware_get_bytes_by_tag (firmware, FU_SYNAPROM_FIRMWARE_TAG_MFW_PAYLOAD, error); } gboolean fu_synaprom_device_write_fw (FuSynapromDevice *self, GBytes *fw, GError **error) { const guint8 *buf; gsize sz = 0; /* write chunks */ fu_device_set_progress (FU_DEVICE (self), 10); fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); buf = g_bytes_get_data (fw, &sz); while (sz != 0) { guint32 chunksz; g_autoptr(GByteArray) chunk = g_byte_array_new (); /* get chunk size */ if (sz < sizeof(guint32)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No enough data for patch len"); return FALSE; } memcpy (&chunksz, buf, sizeof(guint32)); buf += sizeof(guint32); sz -= sizeof(guint32); if (sz < chunksz) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No enough data for patch chunk"); return FALSE; } /* download chunk */ g_byte_array_append (chunk, buf, chunksz); if (!fu_synaprom_device_cmd_download_chunk (self, chunk, error)) return FALSE; /* next chunk */ buf += chunksz; sz -= chunksz; } /* success! */ fu_device_set_progress (FU_DEVICE (self), 100); return TRUE; } static gboolean fu_synaprom_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapromDevice *self = FU_SYNAPROM_DEVICE (device); return fu_synaprom_device_write_fw (self, fw, error); } static gboolean fu_synaprom_device_attach (FuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); gboolean ret; gsize actual_len = 0; guint8 data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; ret = g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_SYNAPROM_USB_CTRLREQUEST_VENDOR_WRITEDFT, 0x0000, 0x0000, data, sizeof(data), &actual_len, 2000, NULL, error); if (!ret) return FALSE; if (actual_len != sizeof(data)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only sent 0x%04x of 0x%04x", (guint) actual_len, (guint) sizeof(data)); return FALSE; } fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!g_usb_device_reset (usb_device, error)) { g_prefix_error (error, "failed to force-reset device: "); return FALSE; } return TRUE; } static gboolean fu_synaprom_device_detach (FuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); gboolean ret; gsize actual_len = 0; guint8 data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }; ret = g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_SYNAPROM_USB_CTRLREQUEST_VENDOR_WRITEDFT, 0x0000, 0x0000, data, sizeof(data), &actual_len, 2000, NULL, error); if (!ret) return FALSE; if (actual_len != sizeof(data)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only sent 0x%04x of 0x%04x", (guint) actual_len, (guint) sizeof(data)); return FALSE; } fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!g_usb_device_reset (usb_device, error)) { g_prefix_error (error, "failed to force-reset device: "); return FALSE; } return TRUE; } static void fu_synaprom_device_init (FuSynapromDevice *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_name (FU_DEVICE (self), "Prometheus"); fu_device_set_summary (FU_DEVICE (self), "Fingerprint reader"); fu_device_set_vendor (FU_DEVICE (self), "Synaptics"); fu_device_add_icon (FU_DEVICE (self), "touchpad-disabled"); } static void fu_synaprom_device_class_init (FuSynapromDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->write_firmware = fu_synaprom_device_write_firmware; klass_device->prepare_firmware = fu_synaprom_device_prepare_fw; klass_device->setup = fu_synaprom_device_setup; klass_device->attach = fu_synaprom_device_attach; klass_device->detach = fu_synaprom_device_detach; klass_usb_device->open = fu_synaprom_device_open; } FuSynapromDevice * fu_synaprom_device_new (FuUsbDevice *device) { FuSynapromDevice *self; self = g_object_new (FU_TYPE_SYNAPROM_DEVICE, NULL); if (device != NULL) fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return FU_SYNAPROM_DEVICE (self); } fwupd-1.2.14/plugins/synaptics-prometheus/fu-synaprom-device.h000066400000000000000000000024511402665037500245130ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_SYNAPROM_DEVICE (fu_synaprom_device_get_type ()) G_DECLARE_FINAL_TYPE (FuSynapromDevice, fu_synaprom_device, FU, SYNAPROM_DEVICE, FuUsbDevice) #define FU_SYNAPROM_PRODUCT_PROMETHEUS 65 /* Prometheus (b1422) */ #define FU_SYNAPROM_PRODUCT_PROMETHEUSPBL 66 #define FU_SYNAPROM_PRODUCT_PROMETHEUSMSBL 67 #define FU_SYNAPROM_CMD_GET_VERSION 0x01 #define FU_SYNAPROM_CMD_BOOTLDR_PATCH 0x7d #define FU_SYNAPROM_CMD_IOTA_FIND 0x8e FuSynapromDevice *fu_synaprom_device_new (FuUsbDevice *device); gboolean fu_synaprom_device_cmd_send (FuSynapromDevice *device, GByteArray *request, GByteArray *reply, guint timeout_ms, GError **error); gboolean fu_synaprom_device_write_fw (FuSynapromDevice *self, GBytes *fw, GError **error); /* for self tests */ void fu_synaprom_device_set_version (FuSynapromDevice *self, guint8 vmajor, guint8 vminor, guint32 buildnum); GBytes *fu_synaprom_device_prepare_fw (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/plugins/synaptics-prometheus/fu-synaprom-firmware.c000066400000000000000000000106011402665037500250570ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaprom-firmware.h" typedef struct __attribute__((packed)) { guint16 tag; guint32 bufsz; } FuSynapromFirmwareHdr; typedef struct { guint16 tag; GBytes *bytes; } FuSynapromFirmwareItem; /* use only first 12 bit of 16 bits as tag value */ #define FU_SYNAPROM_FIRMWARE_TAG_MAX 0xfff0 #define FU_SYNAPROM_FIRMWARE_SIGSIZE 0x0100 static void fu_synaprom_firmware_item_free (FuSynapromFirmwareItem *item) { g_bytes_unref (item->bytes); g_free (item); } static const gchar * fu_synaprom_firmware_tag_to_string (guint16 tag) { if (tag == FU_SYNAPROM_FIRMWARE_TAG_MFW_HEADER) return "mfw-update-header"; if (tag == FU_SYNAPROM_FIRMWARE_TAG_MFW_PAYLOAD) return "mfw-update-payload"; if (tag == FU_SYNAPROM_FIRMWARE_TAG_CFG_HEADER) return "cfg-update-header"; if (tag == FU_SYNAPROM_FIRMWARE_TAG_CFG_PAYLOAD) return "cfg-update-payload"; return NULL; } GPtrArray * fu_synaprom_firmware_new (GBytes *blob, GError **error) { const guint8 *buf; gsize bufsz = 0; gsize offset = 0; g_autoptr(GPtrArray) firmware = NULL; g_return_val_if_fail (blob != NULL, NULL); firmware = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_synaprom_firmware_item_free); buf = g_bytes_get_data (blob, &bufsz); /* 256 byte signature as footer */ if (bufsz < FU_SYNAPROM_FIRMWARE_SIGSIZE + sizeof(FuSynapromFirmwareHdr)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "blob is too small to be firmware"); return NULL; } bufsz -= FU_SYNAPROM_FIRMWARE_SIGSIZE; /* parse each chunk */ while (offset != bufsz) { FuSynapromFirmwareHdr header; guint32 hdrsz; g_autofree FuSynapromFirmwareItem *item = NULL; /* verify item header */ memcpy (&header, buf, sizeof(header)); item = g_new0 (FuSynapromFirmwareItem, 1); item->tag = GUINT16_FROM_LE(header.tag); if (item->tag >= FU_SYNAPROM_FIRMWARE_TAG_MAX) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "tag 0x%04x is too large", item->tag); return NULL; } hdrsz = GUINT32_FROM_LE(header.bufsz); offset += sizeof(header) + hdrsz; if (offset > bufsz) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "data is corrupted 0x%04x > 0x%04x", (guint) offset, (guint) bufsz); return NULL; } /* move pointer to data */ buf += sizeof(header); item->bytes = g_bytes_new (buf, hdrsz); g_debug ("adding 0x%04x (%s) with size 0x%04x", item->tag, fu_synaprom_firmware_tag_to_string (item->tag), hdrsz); g_ptr_array_add (firmware, g_steal_pointer (&item)); /* next item */ buf += hdrsz; } return g_steal_pointer (&firmware); } GBytes * fu_synaprom_firmware_get_bytes_by_tag (GPtrArray *firmware, guint16 tag, GError **error) { for (guint i = 0; i < firmware->len; i++) { FuSynapromFirmwareItem *item = g_ptr_array_index (firmware, i); if (item->tag == tag) return g_bytes_ref (item->bytes); } g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no item with tag 0x%04x", tag); return NULL; } GBytes * fu_synaprom_firmware_generate (void) { GByteArray *blob = g_byte_array_new (); const guint8 data[] = { 'R', 'H' }; FuSynapromFirmwareMfwHeader hdr = { .product = GUINT32_TO_LE(0x41), .id = GUINT32_TO_LE(0xff), .buildtime = GUINT32_TO_LE(0xff), .buildnum = GUINT32_TO_LE(0xff), .vmajor = 10, .vminor = 1, }; guint16 tag1 = GUINT16_TO_LE(FU_SYNAPROM_FIRMWARE_TAG_MFW_HEADER); guint16 tag2 = GUINT16_TO_LE(FU_SYNAPROM_FIRMWARE_TAG_MFW_PAYLOAD); guint32 hdrsz = GUINT32_TO_LE(sizeof(hdr)); guint32 datasz = GUINT32_TO_LE(sizeof(data)); /* add header */ g_byte_array_append (blob, (const guint8 *) &tag1, sizeof(tag1)); g_byte_array_append (blob, (const guint8 *) &hdrsz, sizeof(hdrsz)); g_byte_array_append (blob, (const guint8 *) &hdr, sizeof(hdr)); /* add payload */ g_byte_array_append (blob, (const guint8 *) &tag2, sizeof(tag2)); g_byte_array_append (blob, (const guint8 *) &datasz, sizeof(datasz)); g_byte_array_append (blob, data, sizeof(data)); /* add signature */ for (guint i = 0; i < FU_SYNAPROM_FIRMWARE_SIGSIZE; i++) { guint8 sig = 0xff; g_byte_array_append (blob, &sig, 1); } return g_byte_array_free_to_bytes (blob); } fwupd-1.2.14/plugins/synaptics-prometheus/fu-synaprom-firmware.h000066400000000000000000000023401402665037500250650ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_SYNAPROM_FIRMWARE_TAG_MFW_HEADER 0x0001 #define FU_SYNAPROM_FIRMWARE_TAG_MFW_PAYLOAD 0x0002 #define FU_SYNAPROM_FIRMWARE_TAG_CFG_HEADER 0x0003 #define FU_SYNAPROM_FIRMWARE_TAG_CFG_PAYLOAD 0x0004 /* le */ typedef struct __attribute__((packed)) { guint32 product; guint32 id; /* MFW unique id used for compat verification */ guint32 buildtime; /* unix-style build time */ guint32 buildnum; /* build number */ guint8 vmajor; /* major version */ guint8 vminor; /* minor version */ guint8 unused[6]; } FuSynapromFirmwareMfwHeader; /* le */ typedef struct __attribute__((packed)) { guint32 product; guint32 id1; /* verification ID */ guint32 id2; /* verification ID */ guint16 version; /* config version */ guint8 unused[2]; } FuSynapromFirmwareCfgHeader; GPtrArray *fu_synaprom_firmware_new (GBytes *blob, GError **error); GBytes *fu_synaprom_firmware_get_bytes_by_tag (GPtrArray *firmware, guint16 tag, GError **error); GBytes *fu_synaprom_firmware_generate (void); fwupd-1.2.14/plugins/synaptics-prometheus/meson.build000066400000000000000000000031711402665037500227670ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsPrometheus"'] install_data(['synaptics-prometheus.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_synaptics_prometheus', fu_hash, sources : [ 'fu-plugin-synaptics-prometheus.c', 'fu-synaprom-common.c', 'fu-synaprom-config.c', 'fu-synaprom-device.c', 'fu-synaprom-firmware.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) if get_option('tests') testdatadir = join_paths(meson.current_source_dir(), 'data') cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'synaptics-prometheus-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-synaprom-common.c', 'fu-synaprom-config.c', 'fu-synaprom-device.c', 'fu-synaprom-firmware.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ plugin_deps, ], link_with : [ libfwupdprivate, ], c_args : cargs ) test('synaptics-prometheus-self-test', e) # for fuzzing executable( 'synaptics-prometheus-dump', sources : [ 'fu-dump.c', 'fu-synaprom-firmware.c', ], include_directories : [ include_directories('../..'), ], dependencies : [ gio, ], c_args : cargs ) endif fwupd-1.2.14/plugins/synaptics-prometheus/synaptics-prometheus.quirk000066400000000000000000000002671402665037500261130ustar00rootroot00000000000000[DeviceInstanceId=USB\VID_06CB&PID_00A9] Plugin = synaptics_prometheus InstallDuration = 2 [DeviceInstanceId=USB\VID_06CB&PID_00BD] Plugin = synaptics_prometheus InstallDuration = 2 fwupd-1.2.14/plugins/synapticsmst/000077500000000000000000000000001402665037500171565ustar00rootroot00000000000000fwupd-1.2.14/plugins/synapticsmst/README.md000066400000000000000000000043321402665037500204370ustar00rootroot00000000000000# Synaptics MST This plugin supports querying and flashing Synaptics MST hubs used in Dell systems and docks. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * com.synaptics.mst GUID Generation --------------- These devices use custom GUID values, e.g. * `MST-$(device_kind)-$(chip-ID)-$(board-ID)` Please refer to the plugin source for more details about how the GUID is constructed for specific hardware. ## Requirements ### (Kernel) DP Aux Interface Kernel 4.6 introduced an DRM DP Aux interface for manipulation of the registers needed to access an MST hub. This patch can be backported to earlier kernels: https://github.com/torvalds/linux/commit/e94cb37b34eb8a88fe847438dba55c3f18bf024a ### libsmbios At compilation time and runtime you will need libsmbios_c version 2.3.0 or later * source: https://github.com/dell/libsmbios * rpms: https://apps.fedoraproject.org/packages/libsmbios * debs (Debian): http://tracker.debian.org/pkg/libsmbios * debs (Ubuntu): http://launchpad.net/ubuntu/+source/libsmbios If you don't want or need this functionality you can use the `--disable-dell` option. ## Usage Supported devices will be displayed in `# fwupdmgr get-devices` output. Here is an example output from a Dell WD15 dock: ``` Dell WD15/TB16 wired Dock Synaptics VMM3332 Guid: 653cd006-5433-57db-8632-0413af4d3fcc DeviceID: MST-1-1-0-0 Plugin: synapticsmst Flags: allow-online Version: 3.10.002 Created: 2017-01-13 Modified: 2017-01-13 Trusted: none ``` Payloads can be flashed just like any other plugin from LVFS. ## Supported devices Not all Dell systems or accessories contain MST hubs. Here is a sample list of systems known to support them however: * Dell WD15 dock * Dell TB16 dock * Dell TB18DC * Latitude E5570 * Latitude E5470 * Latitude E5270 * Latitude E7470 * Latitude E7270 * Latitude E7450 * Latitude E7250 * Latitude E5550 * Latitude E5450 * Latitude E5250 * Latitude Rugged 5414 * Latitude Rugged 7214 * Latitude Rugged 7414 fwupd-1.2.14/plugins/synapticsmst/fu-plugin-synapticsmst.c000066400000000000000000000364411402665037500237770ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * Copyright (C) 2017 Peichen Huang * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "synapticsmst-device.h" #include "synapticsmst-common.h" #include "fu-plugin-vfuncs.h" #include "fu-device-metadata.h" #define SYNAPTICS_FLASH_MODE_DELAY 3 #define SYNAPTICS_UPDATE_ENUMERATE_TRIES 3 static gboolean syanpticsmst_check_amdgpu_safe (GError **error) { gsize bufsz = 0; g_autofree gchar *buf = NULL; g_auto(GStrv) lines = NULL; if (!g_file_get_contents ("/proc/modules", &buf, &bufsz, error)) return FALSE; lines = g_strsplit (buf, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { if (g_str_has_prefix (lines[i], "amdgpu ")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "amdgpu has known issues with synapticsmst"); return FALSE; } } return TRUE; } static gboolean synapticsmst_common_check_supported_system (FuPlugin *plugin, GError **error) { if (g_getenv ("FWUPD_SYNAPTICSMST_FW_DIR") != NULL) { g_debug ("Running Synaptics plugin in test mode"); return TRUE; } /* See https://github.com/hughsie/fwupd/issues/1121 for more details */ if (!syanpticsmst_check_amdgpu_safe (error)) return FALSE; if (!g_file_test (SYSFS_DRM_DP_AUX, G_FILE_TEST_IS_DIR)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "MST firmware updating not supported, missing kernel support."); return FALSE; } return TRUE; } /* creates MST-$str-$BOARDID */ static void fu_plugin_synapticsmst_create_simple_guid (FuDevice *fu_device, SynapticsMSTDevice *device, const gchar *str) { guint16 board_id = synapticsmst_device_get_board_id (device); g_autofree gchar *devid = g_strdup_printf ("MST-%s-%u", str, board_id); fu_device_add_instance_id (fu_device, devid); } /* creates MST-$str-$chipid-$BOARDID */ static void fu_plugin_synapticsmst_create_complex_guid (FuDevice *fu_device, SynapticsMSTDevice *device, const gchar *device_kind) { const gchar *chip_id_str = synapticsmst_device_get_chip_id_str (device); g_autofree gchar *chip_id_down = g_ascii_strdown (chip_id_str, -1); g_autofree gchar *tmp = g_strdup_printf ("%s-%s", device_kind, chip_id_down); fu_plugin_synapticsmst_create_simple_guid (fu_device, device, tmp); } static gboolean fu_plugin_synapticsmst_lookup_device (FuPlugin *plugin, FuDevice *fu_device, SynapticsMSTDevice *device, GError **error) { const gchar *board_str; const gchar *guid_template; guint16 board_id = synapticsmst_device_get_board_id (device); const gchar *chip_id_str = synapticsmst_device_get_chip_id_str (device); g_autofree gchar *group = NULL; g_autofree gchar *name = NULL; /* GUIDs used only for test mode */ if (g_getenv ("FWUPD_SYNAPTICSMST_FW_DIR") != NULL) { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf ("test-%s", chip_id_str); fu_plugin_synapticsmst_create_simple_guid (fu_device, device, tmp); return TRUE; } /* set up the device name via quirks */ group = g_strdup_printf ("SynapticsMSTBoardID=%u", board_id); board_str = fu_plugin_lookup_quirk_by_id (plugin, group, FU_QUIRKS_NAME); if (board_str == NULL) board_str = "Unknown Platform"; name = g_strdup_printf ("Synaptics %s inside %s", synapticsmst_device_get_chip_id_str (device), board_str); fu_device_set_name (fu_device, name); /* build the GUIDs for the device */ guid_template = fu_plugin_lookup_quirk_by_id (plugin, group, "DeviceKind"); /* no quirks defined for this board */ if (guid_template == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown board_id %u", board_id); return FALSE; /* this is a host system, use system ID */ } else if (g_strcmp0 (guid_template, "system") == 0) { const gchar *system_type = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_SKU); fu_plugin_synapticsmst_create_simple_guid (fu_device, device, system_type); /* docks or something else */ } else { g_auto(GStrv) templates = NULL; templates = g_strsplit (guid_template, ",", -1); for (guint i = 0; templates[i] != NULL; i++) { fu_plugin_synapticsmst_create_complex_guid (fu_device, device, templates[i]); } } return TRUE; } static gboolean fu_plugin_synaptics_add_device (FuPlugin *plugin, SynapticsMSTDevice *device, GError **error) { g_autoptr(FuDevice) dev = NULL; const gchar *kind_str = NULL; g_autofree gchar *dev_id_str = NULL; g_autofree gchar *layer_str = NULL; g_autofree gchar *rad_str = NULL; const gchar *aux_node; guint8 layer; guint16 rad; aux_node = synapticsmst_device_get_aux_node (device); if (!synapticsmst_device_enumerate_device (device, error)) { g_prefix_error (error, "Error enumerating device at %s: ", aux_node); return FALSE; } /* create the device */ dev = fu_device_new (); /* Store $KIND-$AUXNODE-$LAYER-$RAD as device ID */ layer = synapticsmst_device_get_layer (device); rad = synapticsmst_device_get_rad (device); kind_str = synapticsmst_device_kind_to_string (synapticsmst_device_get_kind (device)); dev_id_str = g_strdup_printf ("MST-%s-%s-%u-%u", kind_str, aux_node, layer, rad); fu_device_set_id (dev, dev_id_str); fu_device_set_physical_id (dev, aux_node); fu_device_set_metadata (dev, "SynapticsMSTKind", kind_str); fu_device_set_metadata (dev, "SynapticsMSTAuxNode", aux_node); layer_str = g_strdup_printf ("%u", layer); fu_device_set_metadata (dev, "SynapticsMSTLayer", layer_str); rad_str = g_strdup_printf ("%u", rad); fu_device_set_metadata (dev, "SynapticsMSTRad", rad_str); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_vendor (dev, "Synaptics"); fu_device_set_summary (dev, "Multi-Stream Transport Device"); fu_device_add_icon (dev, "video-display"); fu_device_set_version (dev, synapticsmst_device_get_version (device), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_quirks (dev, fu_plugin_get_quirks (plugin)); /* create GUIDs and name */ if (!fu_plugin_synapticsmst_lookup_device (plugin, dev, device, error)) return FALSE; if (!fu_device_setup (dev, error)) return FALSE; fu_plugin_device_add (plugin, dev); fu_plugin_cache_add (plugin, dev_id_str, dev); /* inhibit the idle sleep of the daemon */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_INHIBITS_IDLE, "SynapticsMST can cause the screen to flash when probing"); return TRUE; } static gboolean fu_plugin_synaptics_scan_cascade (FuPlugin *plugin, SynapticsMSTDevice *device, GError **error) { g_autoptr(SynapticsMSTDevice) cascade_device = NULL; FuDevice *fu_dev = NULL; const gchar *aux_node; aux_node = synapticsmst_device_get_aux_node (device); if (!synapticsmst_device_open (device, error)) { g_prefix_error (error, "failed to open aux node %s again", aux_node); return FALSE; } for (guint8 j = 0; j < 2; j++) { guint8 layer = synapticsmst_device_get_layer (device) + 1; guint16 rad = synapticsmst_device_get_rad (device) | (j << (2 * (layer - 1))); g_autofree gchar *dev_id_str = NULL; dev_id_str = g_strdup_printf ("MST-REMOTE-%s-%u-%u", aux_node, layer, rad); fu_dev = fu_plugin_cache_lookup (plugin, dev_id_str); /* run the scan */ if (!synapticsmst_device_scan_cascade_device (device, error, j)) return FALSE; /* check if cascaded device was found */ if (!synapticsmst_device_get_cascade (device)) { /* not found, nothing new to see here, move along */ if (fu_dev == NULL) continue; /* not found, but should have existed - remove it */ else { fu_plugin_device_remove (plugin, fu_dev); fu_plugin_cache_remove (plugin, dev_id_str); /* don't scan any deeper on this node */ continue; } /* Found a device, add it */ } else { cascade_device = synapticsmst_device_new (SYNAPTICSMST_DEVICE_KIND_REMOTE, aux_node, layer, rad); /* new device */ if (fu_dev == NULL) { g_debug ("Adding remote device %s", dev_id_str); if (!fu_plugin_synaptics_add_device (plugin, cascade_device, error)) return FALSE; } else g_debug ("Skipping previously added device %s", dev_id_str); /* check recursively for more devices */ if (!fu_plugin_synaptics_scan_cascade (plugin, cascade_device, error)) return FALSE; } } return TRUE; } static void fu_plugin_synapticsmst_remove_cascaded (FuPlugin *plugin, const gchar *aux_node) { FuDevice *fu_dev = NULL; for (guint8 i = 0; i < 8; i++) { for (guint16 j = 0; j < 256; j++) { g_autofree gchar *dev_id_str = NULL; dev_id_str = g_strdup_printf ("MST-REMOTE-%s-%u-%u", aux_node, i, j); fu_dev = fu_plugin_cache_lookup (plugin, dev_id_str); if (fu_dev != NULL) { fu_plugin_device_remove (plugin, fu_dev); fu_plugin_cache_remove (plugin, dev_id_str); continue; } break; } } } static gboolean fu_plugin_synapticsmst_enumerate (FuPlugin *plugin, GError **error) { g_autoptr(GDir) dir = NULL; const gchar *dp_aux_dir; const gchar *aux_node = NULL; dp_aux_dir = g_getenv ("FWUPD_SYNAPTICSMST_FW_DIR"); if (dp_aux_dir == NULL) dp_aux_dir = SYSFS_DRM_DP_AUX; else g_debug ("Using %s to look for MST devices", dp_aux_dir); dir = g_dir_open (dp_aux_dir, 0, NULL); do { g_autofree gchar *dev_id_str = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(SynapticsMSTDevice) device = NULL; FuDevice *fu_dev = NULL; aux_node = g_dir_read_name (dir); if (aux_node == NULL) break; dev_id_str = g_strdup_printf ("MST-DIRECT-%s-0-0", aux_node); fu_dev = fu_plugin_cache_lookup (plugin, dev_id_str); /* If we open successfully a device exists here */ device = synapticsmst_device_new (SYNAPTICSMST_DEVICE_KIND_DIRECT, aux_node, 0, 0); if (!synapticsmst_device_open (device, &error_local)) { /* No device exists here, but was there - remove from DB */ if (fu_dev != NULL) { g_debug ("Removing devices on %s", aux_node); fu_plugin_device_remove (plugin, fu_dev); fu_plugin_cache_remove (plugin, dev_id_str); fu_plugin_synapticsmst_remove_cascaded (plugin, aux_node); } else { /* Nothing to see here - move on*/ g_debug ("No device found on %s: %s", aux_node, error_local->message); g_clear_error (&error_local); } continue; } /* Add direct devices */ if (fu_dev == NULL) { g_debug ("Adding direct device %s", dev_id_str); if (!fu_plugin_synaptics_add_device (plugin, device, &error_local)) g_debug ("failed to add device: %s", error_local->message); } else { g_debug ("Skipping previously added device %s", dev_id_str); } /* recursively look for cascade devices */ if (!fu_plugin_synaptics_scan_cascade (plugin, device, error)) return FALSE; } while(TRUE); return TRUE; } static void fu_synapticsmst_write_progress_cb (goffset current, goffset total, gpointer user_data) { FuDevice *device = FU_DEVICE (user_data); fu_device_set_progress_full (device, current, total); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(SynapticsMSTDevice) device = NULL; SynapticsMSTDeviceKind kind; const gchar *aux_node; guint8 layer; guint8 rad; gboolean reboot; gboolean install_force; /* extract details to build a new device */ kind = synapticsmst_device_kind_from_string (fu_device_get_metadata (dev, "SynapticsMSTKind")); aux_node = fu_device_get_metadata (dev, "SynapticsMSTAuxNode"); layer = g_ascii_strtoull (fu_device_get_metadata (dev, "SynapticsMSTLayer"), NULL, 0); rad = g_ascii_strtoull (fu_device_get_metadata (dev, "SynapticsMSTRad"), NULL, 0); /* sleep to allow device wakeup to complete */ g_debug ("waiting %d seconds for MST hub wakeup", SYNAPTICS_FLASH_MODE_DELAY); fu_device_set_status (dev, FWUPD_STATUS_DEVICE_BUSY); g_usleep (SYNAPTICS_FLASH_MODE_DELAY * 1000000); device = synapticsmst_device_new (kind, aux_node, layer, rad); if (!synapticsmst_device_enumerate_device (device, error)) return FALSE; reboot = !fu_device_has_custom_flag (dev, "skip-restart"); install_force = (flags & FWUPD_INSTALL_FLAG_FORCE) != 0 || fu_device_has_custom_flag (dev, "ignore-board-id"); fu_device_set_status (dev, FWUPD_STATUS_DEVICE_WRITE); if (!synapticsmst_device_write_firmware (device, blob_fw, fu_synapticsmst_write_progress_cb, dev, reboot, install_force, error)) { g_prefix_error (error, "failed to flash firmware: "); return FALSE; } if (!reboot) { g_debug ("Skipping device restart per quirk request"); return TRUE; } /* Re-run device enumeration to find the new device version */ fu_device_set_status (dev, FWUPD_STATUS_DEVICE_RESTART); for (guint i = 1; i <= SYNAPTICS_UPDATE_ENUMERATE_TRIES; i++) { g_autoptr(GError) error_local = NULL; g_usleep (SYNAPTICS_FLASH_MODE_DELAY * 1000000); if (!synapticsmst_device_enumerate_device (device, &error_local)) { g_warning ("Unable to find device after %u seconds: %s", SYNAPTICS_FLASH_MODE_DELAY * i, error_local->message); if (i == SYNAPTICS_UPDATE_ENUMERATE_TRIES) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s", error_local->message); return FALSE; } } } fu_device_set_version (dev, synapticsmst_device_get_version (device), FWUPD_VERSION_FORMAT_TRIPLET); return TRUE; } gboolean fu_plugin_device_removed (FuPlugin *plugin, FuDevice *device, GError **error) { const gchar *aux_node; const gchar *kind_str; const gchar *layer_str; const gchar *rad_str; g_autofree gchar *dev_id_str = NULL; aux_node = fu_device_get_metadata (device, "SynapticsMSTAuxNode"); if (aux_node == NULL) return TRUE; kind_str = fu_device_get_metadata (device, "SynapticsMSTKind"); if (kind_str == NULL) return TRUE; layer_str = fu_device_get_metadata (device, "SynapticsMSTLayer"); if (layer_str == NULL) return TRUE; rad_str = fu_device_get_metadata (device, "SynapticsMSTRad"); if (rad_str == NULL) return TRUE; dev_id_str = g_strdup_printf ("MST-%s-%s-%s-%s", kind_str, aux_node, layer_str, rad_str); if (fu_plugin_cache_lookup (plugin, dev_id_str) != NULL) { g_debug ("Removing %s from cache", dev_id_str); fu_plugin_cache_remove (plugin, dev_id_str); } else { g_debug ("%s constructed but not found in cache", dev_id_str); } return TRUE; } static gboolean fu_plugin_synapticsmst_coldplug (FuPlugin *plugin, GError **error) { g_autoptr(GError) error_local = NULL; /* verify that this is a supported system */ if (!synapticsmst_common_check_supported_system (plugin, error)) return FALSE; /* look for host devices or already plugged in dock devices */ if (!fu_plugin_synapticsmst_enumerate (plugin, &error_local)) g_debug ("error enumerating: %s", error_local->message); return TRUE; } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { return fu_plugin_synapticsmst_coldplug (plugin, error); } gboolean fu_plugin_recoldplug (FuPlugin *plugin, GError **error) { return fu_plugin_synapticsmst_coldplug (plugin, error); } void fu_plugin_init (FuPlugin *plugin) { /* make sure dell is already coldplugged */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "dell"); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.synaptics.mst"); fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); } fwupd-1.2.14/plugins/synapticsmst/fu-self-test.c000066400000000000000000000046021402665037500216420ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-plugin-private.h" static void _plugin_device_added_cb (FuPlugin *plugin, FuDevice *device, gpointer user_data) { GPtrArray **devices = (GPtrArray **) user_data; g_ptr_array_add (*devices, device); } static void fu_plugin_synapticsmst_func (void) { gboolean ret; guint device_count; GPtrArray *devices = NULL; g_autoptr(GError) error = NULL; FuDevice *device = NULL; g_autoptr(FuPlugin) plugin = NULL; const gchar *test_directory; devices = g_ptr_array_new (); plugin = fu_plugin_new (); g_signal_connect (plugin, "device-added", G_CALLBACK (_plugin_device_added_cb), &devices); ret = fu_plugin_open (plugin, PLUGINBUILDDIR "/libfu_plugin_synapticsmst.so", &error); g_assert_no_error (error); g_assert (ret); ret = fu_plugin_runner_startup (plugin, &error); g_assert_no_error (error); g_assert (ret); /* Test with no Synaptics MST devices */ test_directory = SOURCEDIR "/tests/no_devices"; g_assert(g_file_test (test_directory, G_FILE_TEST_IS_DIR)); g_setenv ("FWUPD_SYNAPTICSMST_FW_DIR", test_directory, TRUE); ret = fu_plugin_runner_coldplug (plugin, &error); g_assert_no_error (error); g_assert (ret); /* Emulate adding/removing a Dell TB16 dock */ test_directory = SOURCEDIR "/tests/tb16_dock"; g_assert (g_file_test (test_directory, G_FILE_TEST_IS_DIR)); g_setenv ("FWUPD_SYNAPTICSMST_FW_DIR", test_directory, TRUE); ret = fu_plugin_runner_coldplug (plugin, &error); g_assert_no_error (error); g_assert (ret); device_count = devices->len; g_assert_cmpuint (device_count, ==, 2); for (guint i = 0; i < device_count; i++) { device = g_ptr_array_index (devices, i); g_assert_cmpstr (fu_device_get_version (device), ==, "3.10.002"); g_ptr_array_remove (devices, device); fu_plugin_device_remove (plugin, device); } g_ptr_array_unref (devices); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_assert_cmpint (g_mkdir_with_parents ("/tmp/fwupd-self-test/var/lib/fwupd", 0755), ==, 0); /* tests go here */ g_test_add_func ("/fwupd/plugin{synapticsmst}", fu_plugin_synapticsmst_func); return g_test_run (); } fwupd-1.2.14/plugins/synapticsmst/meson.build000066400000000000000000000026041402665037500213220ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsMST"'] install_data(['synapticsmst.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_synapticsmst', fu_hash, sources : [ 'fu-plugin-synapticsmst.c', 'synapticsmst-common.c', 'synapticsmst-device.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, c_args : [ cargs, ], link_with : [ libfwupdprivate, ], dependencies : [ plugin_deps, ], ) if get_option('tests') cargs += '-DFU_OFFLINE_DESTDIR="/tmp/fwupd-self-test"' cargs += '-DPLUGINBUILDDIR="' + meson.current_build_dir() + '"' cargs += '-DSOURCEDIR="' + meson.current_source_dir() + '"' e = executable( 'synapticsmst-self-test', fu_hash, sources : [ 'fu-self-test.c', 'synapticsmst-common.c', 'synapticsmst-device.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ plugin_deps, sqlite, valgrind, ], link_with : [ libfwupdprivate, ], c_args : [ cargs, ], ) test('synapticsmst-self-test', e, env: ['FWUPD_LOCALSTATEDIR=/tmp/fwupd-self-test/var']) endif fwupd-1.2.14/plugins/synapticsmst/synapticsmst-common.c000066400000000000000000000275701402665037500233640ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Peichen Huang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "synapticsmst-common.h" #define UNIT_SIZE 32 #define MAX_WAIT_TIME 3 /* unit : second */ struct _SynapticsMSTConnection { gint fd; /* not owned by the connection */ guint8 layer; guint8 remain_layer; guint8 rad; }; static gboolean synapticsmst_common_aux_node_read (SynapticsMSTConnection *connection, guint32 offset, guint8 *buf, gint length, GError **error) { if (lseek (connection->fd, offset, SEEK_SET) != offset) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to lseek"); return FALSE; } if (read (connection->fd, buf, length) != length) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to read"); return FALSE; } return TRUE; } static gboolean synapticsmst_common_aux_node_write (SynapticsMSTConnection *connection, guint32 offset, const guint8 *buf, gint length, GError **error) { if (lseek (connection->fd, offset, SEEK_SET) != offset) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to lseek"); return FALSE; } if (write (connection->fd, buf, length) != length) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to write"); return FALSE; } return TRUE; } static gboolean synapticsmst_common_bus_read (SynapticsMSTConnection *connection, guint32 offset, guint8 *buf, guint32 length, GError **error) { return synapticsmst_common_aux_node_read (connection, offset, buf, length, error); } static gboolean synapticsmst_common_bus_write (SynapticsMSTConnection *connection, guint32 offset, const guint8 *buf, guint32 length, GError **error) { return synapticsmst_common_aux_node_write (connection, offset, buf, length, error); } void synapticsmst_common_free (SynapticsMSTConnection *connection) { g_free (connection); } SynapticsMSTConnection * synapticsmst_common_new (gint fd, guint8 layer, guint rad) { SynapticsMSTConnection *connection = g_new0 (SynapticsMSTConnection, 1); connection->fd = fd; connection->layer = layer; connection->remain_layer = layer; connection->rad = rad; return connection; } gboolean synapticsmst_common_read (SynapticsMSTConnection *connection, guint32 offset, guint8 *buf, guint32 length, GError **error) { if (connection->layer && connection->remain_layer) { guint8 node; gboolean result; connection->remain_layer--; node = (connection->rad >> connection->remain_layer * 2) & 0x03; result = synapticsmst_common_rc_get_command (connection, UPDC_READ_FROM_TX_DPCD + node, length, offset, (guint8 *)buf, error); connection->remain_layer++; return result; } return synapticsmst_common_bus_read (connection, offset, buf, length, error); } gboolean synapticsmst_common_write (SynapticsMSTConnection *connection, guint32 offset, const guint8 *buf, guint32 length, GError **error) { if (connection->layer && connection->remain_layer) { guint8 node; gboolean result; connection->remain_layer--; node = (connection->rad >> connection->remain_layer * 2) & 0x03; result = synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_TX_DPCD + node, length, offset, (guint8 *)buf, error); connection->remain_layer++; return result; } return synapticsmst_common_bus_write (connection, offset, buf, length, error); } gboolean synapticsmst_common_rc_set_command (SynapticsMSTConnection *connection, guint32 rc_cmd, guint32 length, guint32 offset, const guint8 *buf, GError **error) { guint32 cur_offset = offset; guint32 cur_length; gint data_left = length; gint cmd; gint readData = 0; long deadline; struct timespec t_spec; do { if (data_left > UNIT_SIZE) { cur_length = UNIT_SIZE; } else { cur_length = data_left; } if (cur_length) { /* write data */ if (!synapticsmst_common_write (connection, REG_RC_DATA, buf, cur_length, error)) { g_prefix_error (error, "failure writing data register: "); return FALSE; } /* write offset */ if (!synapticsmst_common_write (connection, REG_RC_OFFSET, (guint8 *)&cur_offset, 4, error)) { g_prefix_error (error, "failure writing offset register: "); return FALSE; } /* write length */ if (!synapticsmst_common_write (connection, REG_RC_LEN, (guint8 *)&cur_length, 4, error)) { g_prefix_error (error, "failure writing length register: "); return FALSE; } } /* send command */ cmd = 0x80 | rc_cmd; if (!synapticsmst_common_write (connection, REG_RC_CMD, (guint8 *)&cmd, 1, error)) { g_prefix_error (error, "failed to write command: "); return FALSE; } /* wait command complete */ clock_gettime (CLOCK_REALTIME, &t_spec); deadline = t_spec.tv_sec + MAX_WAIT_TIME; do { if (!synapticsmst_common_read (connection, REG_RC_CMD, (guint8 *)&readData, 2, error)) { g_prefix_error (error, "failed to read command: "); return FALSE; } clock_gettime (CLOCK_REALTIME, &t_spec); if (t_spec.tv_sec > deadline) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "timeout exceeded"); return FALSE; } } while (readData & 0x80); if (readData & 0xFF00) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "remote command failed: %d", (readData >> 8) & 0xFF); return FALSE; } buf += cur_length; cur_offset += cur_length; data_left -= cur_length; } while (data_left); return TRUE; } gboolean synapticsmst_common_rc_get_command (SynapticsMSTConnection *connection, guint32 rc_cmd, guint32 length, guint32 offset, guint8 *buf, GError **error) { guint32 cur_offset = offset; guint32 cur_length; gint data_need = length; guint32 cmd; guint32 readData = 0; long deadline; struct timespec t_spec; while (data_need) { if (data_need > UNIT_SIZE) { cur_length = UNIT_SIZE; } else { cur_length = data_need; } if (cur_length) { /* write offset */ if (!synapticsmst_common_write (connection, REG_RC_OFFSET, (guint8 *)&cur_offset, 4, error)) { g_prefix_error (error, "failed to write offset: "); return FALSE; } /* write length */ if (!synapticsmst_common_write (connection, REG_RC_LEN, (guint8 *)&cur_length, 4, error)) { g_prefix_error (error, "failed to write length: "); return FALSE; } } /* send command */ cmd = 0x80 | rc_cmd; if (!synapticsmst_common_write (connection, REG_RC_CMD, (guint8 *)&cmd, 1, error)) { g_prefix_error (error, "failed to write command: "); return FALSE; } /* wait command complete */ clock_gettime (CLOCK_REALTIME, &t_spec); deadline = t_spec.tv_sec + MAX_WAIT_TIME; do { if (!synapticsmst_common_read (connection, REG_RC_CMD, (guint8 *)&readData, 2, error)) { g_prefix_error (error, "failed to read command: "); return FALSE; } clock_gettime (CLOCK_REALTIME, &t_spec); if (t_spec.tv_sec > deadline) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "timeout exceeded"); return FALSE; } } while (readData & 0x80); if (readData & 0xFF00) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "remote command failed: %u", (readData >> 8) & 0xFF); return FALSE; } if (cur_length) { if (!synapticsmst_common_read (connection, REG_RC_DATA, buf, cur_length, error)) { g_prefix_error (error, "failed to read data: "); return FALSE; } } buf += cur_length; cur_offset += cur_length; data_need -= cur_length; } return TRUE; } gboolean synapticsmst_common_rc_special_get_command (SynapticsMSTConnection *connection, guint32 rc_cmd, guint32 cmd_length, guint32 cmd_offset, guint8 *cmd_data, guint32 length, guint8 *buf, GError **error) { guint32 readData = 0; guint32 cmd; long deadline; struct timespec t_spec; if (cmd_length) { /* write cmd data */ if (cmd_data != NULL) { if (!synapticsmst_common_write (connection, REG_RC_DATA, cmd_data, cmd_length, error)) { g_prefix_error (error, "Failed to write command data: "); return FALSE; } } /* write offset */ if (!synapticsmst_common_write (connection, REG_RC_OFFSET, (guint8 *)&cmd_offset, 4, error)) { g_prefix_error (error, "failed to write offset: "); return FALSE; } /* write length */ if (!synapticsmst_common_write (connection, REG_RC_LEN, (guint8 *)&cmd_length, 4, error)) { g_prefix_error (error, "failed to write length: "); return FALSE; } } /* send command */ cmd = 0x80 | rc_cmd; if (!synapticsmst_common_write (connection, REG_RC_CMD, (guint8 *)&cmd, 1, error)) { g_prefix_error (error, "failed to write command: "); return FALSE; } /* wait command complete */ clock_gettime (CLOCK_REALTIME, &t_spec); deadline = t_spec.tv_sec + MAX_WAIT_TIME; do { if (!synapticsmst_common_read (connection, REG_RC_CMD, (guint8 *)&readData, 2, error)) { g_prefix_error (error, "failed to read command: "); return FALSE; } clock_gettime (CLOCK_REALTIME, &t_spec); if (t_spec.tv_sec > deadline) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "timeout exceeded"); return FALSE; } } while (readData & 0x80); if (readData & 0xFF00) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "remote command failed: %u", (readData >> 8) & 0xFF); return FALSE; } if (length) { if (!synapticsmst_common_read (connection, REG_RC_DATA, buf, length, error)) { g_prefix_error (error, "failed to read length: "); } } return TRUE; } gboolean synapticsmst_common_enable_remote_control (SynapticsMSTConnection *connection, GError **error) { const gchar *sc = "PRIUS"; for (gint i = 0; i <= connection->layer; i++) { g_autoptr(SynapticsMSTConnection) connection_tmp = synapticsmst_common_new (connection->fd, i, connection->rad); g_autoptr(GError) error_local = NULL; if (!synapticsmst_common_rc_set_command (connection_tmp, UPDC_ENABLE_RC, 5, 0, (guint8*)sc, &error_local)) { g_debug ("Failed to enable remote control in layer %d: %s, retrying", i, error_local->message); if (!synapticsmst_common_disable_remote_control (connection_tmp, error)) return FALSE; if (!synapticsmst_common_rc_set_command (connection_tmp, UPDC_ENABLE_RC, 5, 0, (guint8*)sc, error)) { g_prefix_error (error, "failed to enable remote control in layer %d: ", i); return FALSE; } } } return TRUE; } gboolean synapticsmst_common_disable_remote_control (SynapticsMSTConnection *connection, GError **error) { for (gint i = connection->layer; i >= 0; i--) { g_autoptr(SynapticsMSTConnection) connection_tmp = synapticsmst_common_new (connection->fd, i, connection->rad); if (!synapticsmst_common_rc_set_command (connection_tmp, UPDC_DISABLE_RC, 0, 0, NULL, error)) { g_prefix_error (error, "failed to disable remote control in layer %d: ", i); return FALSE; } } return TRUE; } fwupd-1.2.14/plugins/synapticsmst/synapticsmst-common.h000066400000000000000000000060001402665037500233520ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Peichen Huang * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #define ADDR_CUSTOMER_ID 0X10E #define ADDR_BOARD_ID 0x10F #define ADDR_MEMORY_CUSTOMER_ID 0x170E #define ADDR_MEMORY_BOARD_ID 0x170F #define REG_RC_CAP 0x4B0 #define REG_RC_STATE 0X4B1 #define REG_RC_CMD 0x4B2 #define REG_RC_RESULT 0x4B3 #define REG_RC_LEN 0x4B8 #define REG_RC_OFFSET 0x4BC #define REG_RC_DATA 0x4C0 #define REG_VENDOR_ID 0x500 #define REG_CHIP_ID 0x507 #define REG_FIRMWARE_VERSION 0x50A typedef enum { UPDC_COMMAND_SUCCESS = 0, UPDC_COMMAND_INVALID, UPDC_COMMAND_UNSUPPORT, UPDC_COMMAND_FAILED, UPDC_COMMAND_DISABLED, } SynapticsMstUpdcRc; typedef enum { UPDC_ENABLE_RC = 0x01, UPDC_DISABLE_RC = 0x02, UPDC_GET_ID = 0x03, UPDC_GET_VERSION = 0x04, UPDC_ENABLE_FLASH_CHIP_ERASE = 0x08, UPDC_CAL_EEPROM_CHECKSUM = 0x11, UPDC_FLASH_ERASE = 0x14, UPDC_CAL_EEPROM_CHECK_CRC8 = 0x16, UPDC_CAL_EEPROM_CHECK_CRC16 = 0x17, UPDC_WRITE_TO_EEPROM = 0X20, UPDC_WRITE_TO_MEMORY = 0x21, UPDC_WRITE_TO_TX_DPCD = 0x22, UPDC_READ_FROM_EEPROM = 0x30, UPDC_READ_FROM_MEMORY = 0x31, UPDC_READ_FROM_TX_DPCD = 0x32, } SynapticsMstUpdcCmd; typedef struct _SynapticsMSTConnection SynapticsMSTConnection; void synapticsmst_common_free (SynapticsMSTConnection *connection); SynapticsMSTConnection *synapticsmst_common_new (gint fd, guint8 layer, guint rad); gboolean synapticsmst_common_read (SynapticsMSTConnection *connection, guint32 offset, guint8 *buf, guint32 length, GError **error); gboolean synapticsmst_common_write (SynapticsMSTConnection *connection, guint32 offset, const guint8 *buf, guint32 length, GError **error); gboolean synapticsmst_common_rc_set_command (SynapticsMSTConnection *connection, guint32 rc_cmd, guint32 length, guint32 offset, const guint8 *buf, GError **error); gboolean synapticsmst_common_rc_get_command (SynapticsMSTConnection *connection, guint32 rc_cmd, guint32 length, guint32 offset, guint8 *buf, GError **error); gboolean synapticsmst_common_rc_special_get_command (SynapticsMSTConnection *connection, guint32 rc_cmd, guint32 cmd_length, guint32 cmd_offset, guint8 *cmd_data, guint32 length, guint8 *buf, GError **error); gboolean synapticsmst_common_enable_remote_control (SynapticsMSTConnection *connection, GError **error); gboolean synapticsmst_common_disable_remote_control (SynapticsMSTConnection *connection, GError **error); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(SynapticsMSTConnection, synapticsmst_common_free) #pragma clang diagnostic pop fwupd-1.2.14/plugins/synapticsmst/synapticsmst-device.c000066400000000000000000001121421402665037500233210ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Peichen Huang * Copyright (C) 2018 Ryan Chang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-device-locker.h" #include "synapticsmst-device.h" #include "synapticsmst-common.h" #include #include #include #define BIT( n ) ( 1 << (n) ) #define FLASH_SECTOR_ERASE_4K 0x1000 #define FLASH_SECTOR_ERASE_32K 0x2000 #define FLASH_SECTOR_ERASE_64K 0x3000 #define EEPROM_TAG_OFFSET 0x1FFF0 #define EEPROM_BANK_OFFSET 0x20000 #define EEPROM_ESM_OFFSET 0x40000 #define ESM_CODE_SIZE 0x40000 #define PAYLOAD_SIZE_512K 0x80000 #define PAYLOAD_SIZE_64K 0x10000 #define MAX_RETRY_COUNTS 10 #define BLOCK_UNIT 64 #define BANKTAG_0 0 #define BANKTAG_1 1 #define CRC_8 8 #define CRC_16 16 #define REG_ESM_DISABLE 0x2000fc #define REG_QUAD_DISABLE 0x200fc0 #define REG_HDCP22_DISABLE 0x200f90 #define FLASH_SETTLE_TIME 5000000 /* us */ typedef struct { SynapticsMSTDeviceKind kind; gchar *version; guint32 board_id; guint16 chip_id; gchar *chip_id_str; GPtrArray *guids; gchar *aux_node; guint8 layer; guint16 rad; gint fd; gboolean has_cascade; gchar *fw_dir; gboolean test_mode; } SynapticsMSTDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE (SynapticsMSTDevice, synapticsmst_device, G_TYPE_OBJECT) #define GET_PRIVATE(o) (synapticsmst_device_get_instance_private (o)) SynapticsMSTDeviceKind synapticsmst_device_kind_from_string (const gchar *kind) { if (g_strcmp0 (kind, "DIRECT") == 0) return SYNAPTICSMST_DEVICE_KIND_DIRECT; if (g_strcmp0 (kind, "REMOTE") == 0) return SYNAPTICSMST_DEVICE_KIND_REMOTE; return SYNAPTICSMST_DEVICE_KIND_UNKNOWN; } const gchar * synapticsmst_device_kind_to_string (SynapticsMSTDeviceKind kind) { if (kind == SYNAPTICSMST_DEVICE_KIND_DIRECT) return "DIRECT"; if (kind == SYNAPTICSMST_DEVICE_KIND_REMOTE) return "REMOTE"; return NULL; } static void synapticsmst_device_finalize (GObject *object) { SynapticsMSTDevice *device = SYNAPTICSMST_DEVICE (object); SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); if (priv->fd > 0) close (priv->fd); g_free (priv->fw_dir); g_free (priv->aux_node); g_free (priv->version); g_free (priv->chip_id_str); G_OBJECT_CLASS (synapticsmst_device_parent_class)->finalize (object); } static void synapticsmst_device_init (SynapticsMSTDevice *device) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); const gchar *tmp; tmp = g_getenv ("FWUPD_SYNAPTICSMST_FW_DIR"); if (tmp == NULL) { priv->test_mode = FALSE; priv->fw_dir = g_strdup ("/dev"); } else { priv->test_mode = TRUE; priv->fw_dir = g_strdup (tmp); } } static void synapticsmst_device_class_init (SynapticsMSTDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = synapticsmst_device_finalize; } SynapticsMSTDeviceKind synapticsmst_device_get_kind (SynapticsMSTDevice *device) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); return priv->kind; } guint16 synapticsmst_device_get_board_id (SynapticsMSTDevice *device) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); return priv->board_id; } static gboolean synapticsmst_device_enable_remote_control (SynapticsMSTDevice *device, GError **error) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); g_autoptr(SynapticsMSTConnection) connection = NULL; /* in test mode we need to open a different file node instead */ if (priv->test_mode) { g_autofree gchar *filename = NULL; close(priv->fd); filename = g_strdup_printf ("%s/remote/%s", priv->fw_dir, priv->aux_node); if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no device exists %s", filename); return FALSE; } priv->fd = open (filename, O_RDWR); if (priv->fd == -1) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "cannot open device %s: %s", filename, g_strerror (errno)); return FALSE; } return TRUE; } connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); if (!synapticsmst_common_enable_remote_control (connection, error)) return FALSE; return TRUE; } static gboolean synapticsmst_device_disable_remote_control (SynapticsMSTDevice *device, GError **error) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); g_autoptr(SynapticsMSTConnection) connection = NULL; /* in test mode we need to open a different file node instead */ if (priv->test_mode) { g_autofree gchar *filename = NULL; close(priv->fd); filename = g_strdup_printf ("%s/%s", priv->fw_dir, priv->aux_node); if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no device exists %s", filename); return FALSE; } priv->fd = open (filename, O_RDWR); if (priv->fd == -1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, "cannot open device %s", filename); return FALSE; } return TRUE; } connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); if (!synapticsmst_common_disable_remote_control (connection, error)) return FALSE; return TRUE; } gboolean synapticsmst_device_scan_cascade_device (SynapticsMSTDevice *device, GError **error, guint8 tx_port) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); guint8 layer = priv->layer + 1; guint16 rad = priv->rad | (tx_port << (2 * (priv->layer))); guint8 byte[4]; g_autoptr(SynapticsMSTConnection) connection = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(FuDeviceLocker) locker = NULL; if (priv->test_mode) return TRUE; /* reset */ priv->has_cascade = FALSE; /* enable remote control and disable on exit */ locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) synapticsmst_device_enable_remote_control, (FuDeviceLockerFunc) synapticsmst_device_disable_remote_control, error); if (locker == NULL) return FALSE; connection = synapticsmst_common_new (priv->fd, layer, rad); if (!synapticsmst_common_read (connection, REG_RC_CAP, byte, 1, &error_local)) { g_debug ("No cascade device found: %s", error_local->message); return TRUE; } if (byte[0] & 0x04) { if (!synapticsmst_common_read (connection, REG_VENDOR_ID, byte, 3, error)) { g_prefix_error (error, "failed to read cascade device on tx_port %d: ", tx_port); return FALSE; } if (byte[0] == 0x90 && byte[1] == 0xCC && byte[2] == 0x24) priv->has_cascade = TRUE; } return TRUE; } static gboolean synapticsmst_device_read_board_id (SynapticsMSTDevice *device, SynapticsMSTConnection *connection, guint8 *byte, GError **error) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); if (priv->test_mode) { g_autofree gchar *filename = NULL; gint fd; filename = g_strdup_printf ("%s/remote/%s_eeprom", priv->fw_dir, priv->aux_node); if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no device exists %s", filename); return FALSE; } fd = open (filename, O_RDONLY); if (fd == -1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, "cannot open device %s", filename); return FALSE; } if (read (fd, byte, 2) != 2) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "error reading EEPROM file %s", filename); close (fd); return FALSE; } close (fd); } else { /* get board ID via MCU address 0x170E instead of flash access due to HDCP2.2 running */ if (!synapticsmst_common_rc_get_command (connection, UPDC_READ_FROM_MEMORY, 2, (gint)ADDR_MEMORY_CUSTOMER_ID, byte, error)) { g_prefix_error (error, "Memory query failed: "); return FALSE; } } return TRUE; } static gboolean synapticsmst_device_get_active_bank_panamera (SynapticsMSTDevice *device, guint8 *bank_out, GError **error) { g_autoptr(SynapticsMSTConnection) connection = NULL; SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); guint32 dwData[16]; /* get used bank */ connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); if (!synapticsmst_common_rc_get_command (connection, UPDC_READ_FROM_MEMORY, ((sizeof(dwData)/sizeof(dwData[0]))*4), (gint) 0x20010c, (guint8*) dwData, error)) { g_prefix_error (error, "get active bank failed: "); return FALSE; } if ((dwData[0] & BIT(7)) || (dwData[0] & BIT(30))) *bank_out = BANKTAG_1; else *bank_out = BANKTAG_0; g_debug ("bank in use:%x", *bank_out); return TRUE; } gboolean synapticsmst_device_enumerate_device (SynapticsMSTDevice *device, GError **error) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); guint8 byte[16]; guint8 bank; g_autoptr(SynapticsMSTConnection) connection = NULL; g_autoptr(FuDeviceLocker) locker = NULL; if (!synapticsmst_device_open (device, error)) { g_prefix_error (error, "Failed to open device in DP Aux Node %s: ", synapticsmst_device_get_aux_node (device)); return FALSE; } /* enable remote control and disable on exit */ locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) synapticsmst_device_enable_remote_control, (FuDeviceLockerFunc) synapticsmst_device_disable_remote_control, error); if (locker == NULL) return FALSE; /* read firmware version */ connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); if (!synapticsmst_common_read (connection, REG_FIRMWARE_VERSION, byte, 3, error)) return FALSE; priv->version = g_strdup_printf ("%1d.%02d.%03d", byte[0], byte[1], byte[2]); /* read board ID */ if (!synapticsmst_device_read_board_id (device, connection, byte, error)) return FALSE; priv->board_id = (byte[0] << 8) | (byte[1]); g_debug ("BoardID %x", priv->board_id); /* read board chip_id */ if (!synapticsmst_common_read (connection, REG_CHIP_ID, byte, 2, error)) { g_prefix_error (error, "failed to read chip id: "); return FALSE; } priv->chip_id = (byte[0] << 8) | (byte[1]); priv->chip_id_str = g_strdup_printf ("VMM%02x%02x", byte[0], byte[1]); /* if running on panamera, check the active bank (for debugging logs) */ if (priv->chip_id > 0x5000 && !synapticsmst_device_get_active_bank_panamera (device, &bank, error)) return FALSE; return TRUE; } const gchar * synapticsmst_device_get_aux_node (SynapticsMSTDevice *device) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); return priv->aux_node; } const gchar * synapticsmst_device_get_version (SynapticsMSTDevice *device) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); return priv->version; } static guint16 synapticsmst_device_get_chip_id (SynapticsMSTDevice *device) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); return priv->chip_id; } const gchar * synapticsmst_device_get_chip_id_str (SynapticsMSTDevice *device) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); return priv->chip_id_str; } guint16 synapticsmst_device_get_rad (SynapticsMSTDevice *device) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); return priv->rad; } guint8 synapticsmst_device_get_layer (SynapticsMSTDevice *device) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); return priv->layer; } gboolean synapticsmst_device_get_cascade (SynapticsMSTDevice *device) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); return priv->has_cascade; } static gboolean synapticsmst_device_get_flash_checksum (SynapticsMSTDevice *device, guint32 length, guint32 offset, guint32 *checksum, GError **error) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); g_autoptr(SynapticsMSTConnection) connection = NULL; connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); if (!synapticsmst_common_rc_special_get_command (connection, UPDC_CAL_EEPROM_CHECKSUM, length, offset, NULL, 4, (guint8 *)checksum, error)) { g_prefix_error (error, "failed to get flash checksum: "); return FALSE; } return TRUE; } static guint16 synapticsmst_device_get_crc (guint16 crc, guint8 type, guint32 length, const guint8 *payload_data) { static const guint16 CRC16_table[] = { 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202 }; static const guint16 CRC8_table[] = { 0x00, 0xd5, 0x7f, 0xaa, 0xfe, 0x2b, 0x81, 0x54, 0x29, 0xfc, 0x56, 0x83, 0xd7, 0x02, 0xa8, 0x7d, 0x52, 0x87, 0x2d, 0xf8, 0xac, 0x79, 0xd3, 0x06, 0x7b, 0xae, 0x04, 0xd1, 0x85, 0x50, 0xfa, 0x2f, 0xa4, 0x71, 0xdb, 0x0e, 0x5a, 0x8f, 0x25, 0xf0, 0x8d, 0x58, 0xf2, 0x27, 0x73, 0xa6, 0x0c, 0xd9, 0xf6, 0x23, 0x89, 0x5c, 0x08, 0xdd, 0x77, 0xa2, 0xdf, 0x0a, 0xa0, 0x75, 0x21, 0xf4, 0x5e, 0x8b, 0x9d, 0x48, 0xe2, 0x37, 0x63, 0xb6, 0x1c, 0xc9, 0xb4, 0x61, 0xcb, 0x1e, 0x4a, 0x9f, 0x35, 0xe0, 0xcf, 0x1a, 0xb0, 0x65, 0x31, 0xe4, 0x4e, 0x9b, 0xe6, 0x33, 0x99, 0x4c, 0x18, 0xcd, 0x67, 0xb2, 0x39, 0xec, 0x46, 0x93, 0xc7, 0x12, 0xb8, 0x6d, 0x10, 0xc5, 0x6f, 0xba, 0xee, 0x3b, 0x91, 0x44, 0x6b, 0xbe, 0x14, 0xc1, 0x95, 0x40, 0xea, 0x3f, 0x42, 0x97, 0x3d, 0xe8, 0xbc, 0x69, 0xc3, 0x16, 0xef, 0x3a, 0x90, 0x45, 0x11, 0xc4, 0x6e, 0xbb, 0xc6, 0x13, 0xb9, 0x6c, 0x38, 0xed, 0x47, 0x92, 0xbd, 0x68, 0xc2, 0x17, 0x43, 0x96, 0x3c, 0xe9, 0x94, 0x41, 0xeb, 0x3e, 0x6a, 0xbf, 0x15, 0xc0, 0x4b, 0x9e, 0x34, 0xe1, 0xb5, 0x60, 0xca, 0x1f, 0x62, 0xb7, 0x1d, 0xc8, 0x9c, 0x49, 0xe3, 0x36, 0x19, 0xcc, 0x66, 0xb3, 0xe7, 0x32, 0x98, 0x4d, 0x30, 0xe5, 0x4f, 0x9a, 0xce, 0x1b, 0xb1, 0x64, 0x72, 0xa7, 0x0d, 0xd8, 0x8c, 0x59, 0xf3, 0x26, 0x5b, 0x8e, 0x24, 0xf1, 0xa5, 0x70, 0xda, 0x0f, 0x20, 0xf5, 0x5f, 0x8a, 0xde, 0x0b, 0xa1, 0x74, 0x09, 0xdc, 0x76, 0xa3, 0xf7, 0x22, 0x88, 0x5d, 0xd6, 0x03, 0xa9, 0x7c, 0x28, 0xfd, 0x57, 0x82, 0xff, 0x2a, 0x80, 0x55, 0x01, 0xd4, 0x7e, 0xab, 0x84, 0x51, 0xfb, 0x2e, 0x7a, 0xaf, 0x05, 0xd0, 0xad, 0x78, 0xd2, 0x07, 0x53, 0x86, 0x2c, 0xf9 }; guint8 val; guint16 remainder = (guint16) crc; const guint8 *message = payload_data; if (type == CRC_8) { for (guint32 byte = 0; byte < length; ++byte) { val = (guint8)(message[byte] ^ remainder); remainder = CRC8_table[val]; } } else { for (guint32 byte = 0; byte < length; ++byte) { val = (guint8)(message[byte] ^ (remainder >> 8)); remainder = CRC16_table[val] ^ (remainder << 8); } } return remainder; } static gboolean synapticsmst_device_set_flash_sector_erase (SynapticsMSTDevice *device, guint16 rc_cmd, guint16 offset, GError **error) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); guint16 us_data; g_autoptr(SynapticsMSTConnection) connection = NULL; connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); /* Need to add Wp control ? */ us_data = rc_cmd + offset; if (!synapticsmst_common_rc_set_command (connection, UPDC_FLASH_ERASE, 2, 0, (guint8 *)&us_data, error)) { g_prefix_error (error, "can't sector erase flash at offset %x", offset); return FALSE; } return TRUE; } static gboolean synapticsmst_device_update_esm (SynapticsMSTDevice *device, const guint8 *payload_data, GFileProgressCallback progress_cb, gpointer progress_data, GError **error) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); guint32 checksum = 0; guint32 esm_sz = ESM_CODE_SIZE; guint32 flash_checksum = 0; guint32 unit_sz = BLOCK_UNIT; guint32 write_loops = 0; g_autoptr(SynapticsMSTConnection) connection = NULL; connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); for (guint32 i = 0; i < esm_sz; i++) checksum += *(payload_data + EEPROM_ESM_OFFSET +i); if (!synapticsmst_device_get_flash_checksum (device, esm_sz, EEPROM_ESM_OFFSET, &flash_checksum, error)) { return FALSE; } /* ESM checksum same */ if (checksum == flash_checksum) { g_debug ("ESM checksum already matches"); return TRUE; } g_debug ("ESM checksum %x doesn't match expected %x", flash_checksum, checksum); /* update ESM firmware */ write_loops = esm_sz / unit_sz; for (guint retries_cnt = 0; ; retries_cnt++) { guint32 write_idx = 0; guint32 write_offset = EEPROM_ESM_OFFSET; const guint8 *esm_code_ptr = &payload_data[EEPROM_ESM_OFFSET]; /* erase ESM firmware; erase failure is fatal */ for (guint32 j = 0; j < 4; j++) { if (!synapticsmst_device_set_flash_sector_erase (device, FLASH_SECTOR_ERASE_64K, j + 4, error)) { g_prefix_error (error, "failed to erase sector %u: ", j); return FALSE; } } g_debug ("Waiting for flash clear to settle"); g_usleep (FLASH_SETTLE_TIME); /* write firmware */ for (guint32 i = 0; i < write_loops; i++) { g_autoptr(GError) error_local = NULL; if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_EEPROM, unit_sz, write_offset, esm_code_ptr + write_idx, &error_local)) { g_warning ("failed to write ESM: %s", error_local->message); break; } write_offset += unit_sz; write_idx += unit_sz; if (progress_cb != NULL) { progress_cb ((goffset) i * 100, (goffset) (write_loops -1) * 100, progress_data); } } /* check ESM checksum */ checksum = 0; flash_checksum = 0; for (guint32 i = 0; i < esm_sz; i++) checksum += *(payload_data + EEPROM_ESM_OFFSET +i); if (!synapticsmst_device_get_flash_checksum (device, esm_sz, EEPROM_ESM_OFFSET, &flash_checksum, error)) return FALSE; /* ESM update done */ if (checksum == flash_checksum) break; g_debug ("attempt %u: ESM checksum %x didn't match %x", retries_cnt, flash_checksum, checksum); /* abort */ if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum did not match after %u tries", retries_cnt); return FALSE; } } g_debug ("ESM successfully written"); return TRUE; } static gboolean synapticsmst_device_update_tesla_leaf_firmware (SynapticsMSTDevice *device, guint32 payload_len, const guint8 *payload_data, GFileProgressCallback progress_cb, gpointer progress_data, GError **error) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); g_autoptr(SynapticsMSTConnection) connection = NULL; guint32 data_to_write = 0; guint32 offset = 0; guint32 write_loops = 0; write_loops = (payload_len / BLOCK_UNIT); data_to_write = payload_len; if (payload_len % BLOCK_UNIT) write_loops++; connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); for (guint32 retries_cnt = 0; ; retries_cnt++) { guint32 checksum = 0; guint32 flash_checksum = 0; if (!synapticsmst_device_set_flash_sector_erase (device, 0xffff, 0, error)) return FALSE; g_debug ("Waiting for flash clear to settle"); g_usleep (FLASH_SETTLE_TIME); for (guint32 i = 0; i < write_loops; i++) { g_autoptr(GError) error_local = NULL; guint8 length = BLOCK_UNIT; if (data_to_write < BLOCK_UNIT) length = data_to_write; if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_EEPROM, length, offset, payload_data + offset, &error_local)) { g_warning ("Failed to write flash offset 0x%04x: %s, retrying", offset, error_local->message); /* repeat once */ if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_EEPROM, length, offset, payload_data + offset, error)) { g_prefix_error (error, "can't write flash offset 0x%04x: ", offset); return FALSE; } } offset += length; data_to_write -= length; if (progress_cb != NULL) { progress_cb ((goffset) i * 100, (goffset) (write_loops -1) * 100, progress_data); } } /* check data just written */ for (guint32 i = 0; i < payload_len; i++) checksum += *(payload_data + i); if (!synapticsmst_device_get_flash_checksum (device, payload_len, 0, &flash_checksum, error)) return FALSE; if (checksum == flash_checksum) break; g_debug ("attempt %u: checksum %x didn't match %x", retries_cnt, flash_checksum, checksum); if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum %x mismatched %x", flash_checksum, checksum); return FALSE; } } return TRUE; } static gboolean synapticsmst_device_update_panamera_firmware (SynapticsMSTDevice *device, guint32 payload_len, const guint8 *payload_data, GFileProgressCallback progress_cb, gpointer progress_data, GError **error) { guint16 crc_tmp = 0; guint32 fw_size; guint32 unit_sz = BLOCK_UNIT; guint32 write_loops = 0; guint8 bank_in_use; guint8 bank_to_update = BANKTAG_1; guint8 readBuf[256]; guint8 tagData[16]; struct tm *pTM; time_t timeptr; g_autoptr(SynapticsMSTConnection) connection = NULL; SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); /* get used bank */ if (!synapticsmst_device_get_active_bank_panamera (device, &bank_in_use, error)) return FALSE; if (bank_in_use == BANKTAG_1) bank_to_update = BANKTAG_0; g_debug ("bank to update:%x", bank_to_update); /* get firmware size */ fw_size = 0x410 + (*(payload_data + 0x400) << 24) + (*(payload_data + 0x401) << 16) + (*(payload_data + 0x402) << 8) + (*(payload_data + 0x403)); /* Current max firmware size is 104K */ if (fw_size < payload_len) fw_size = 104 * 1024; g_debug ("Calculated fw size as %u", fw_size); /* Update firmware */ write_loops = fw_size / unit_sz; if (fw_size % unit_sz) write_loops++; for (guint32 retries_cnt = 0; ; retries_cnt++) { guint32 checksum = 0; guint32 erase_offset; guint32 flash_checksum = 0; guint32 write_idx; guint32 write_offset; /* erase storage */ erase_offset = bank_to_update * 2; if (!synapticsmst_device_set_flash_sector_erase (device, FLASH_SECTOR_ERASE_64K, erase_offset++, error)) return FALSE; if (!synapticsmst_device_set_flash_sector_erase (device, FLASH_SECTOR_ERASE_64K, erase_offset, error)) return FALSE; g_debug ("Waiting for flash clear to settle"); g_usleep (FLASH_SETTLE_TIME); /* write */ write_idx = 0; write_offset = EEPROM_BANK_OFFSET * bank_to_update; connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); for (guint32 i = 0; i < write_loops ; i++ ) { g_autoptr(GError) error_local = NULL; if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_EEPROM, unit_sz, write_offset, payload_data + write_idx, &error_local)) { g_warning ("Write failed: %s, retrying", error_local->message); /* repeat once */ if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_EEPROM, unit_sz, write_offset, payload_data + write_idx, error)) { g_prefix_error (error, "firmware write failed: "); return FALSE; } } write_offset += unit_sz; write_idx += unit_sz; if (progress_cb != NULL) { progress_cb ((goffset) i * 100, (goffset) (write_loops -1) * 100, progress_data); } } /* verify CRC */ checksum = synapticsmst_device_get_crc ( 0, 16, fw_size, payload_data ); for (guint32 i = 0; i < 4; i++) { g_usleep (1000); /* wait crc calculation */ if (!synapticsmst_common_rc_special_get_command (connection, UPDC_CAL_EEPROM_CHECK_CRC16, fw_size, (EEPROM_BANK_OFFSET * bank_to_update), NULL, 4, (guint8 *)(&flash_checksum), error)) { g_prefix_error (error, "Failed to get flash checksum: "); return FALSE; } } if (checksum == flash_checksum) break; if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware update fail"); return FALSE; } g_usleep (2000); } /* set tag valid */ time (&timeptr); pTM = localtime (&timeptr); memset (tagData, 0, sizeof (tagData)); memset (readBuf, 0, sizeof (readBuf)); tagData[1] = pTM->tm_mon + 1; tagData[2] = pTM->tm_mday; tagData[3] = pTM->tm_year + 1900 - 2000; crc_tmp = synapticsmst_device_get_crc (0, 16, fw_size, payload_data); tagData[0] = bank_to_update; tagData[4] = (crc_tmp >> 8) & 0xff; tagData[5] = crc_tmp & 0xff; tagData[15] = (guint8) synapticsmst_device_get_crc (0, 8, 15, tagData); g_debug ("tag date %x %x %x crc %x %x %x %x", tagData[1], tagData[2], tagData[3], tagData[0], tagData[4], tagData[5], tagData[15]); for (guint32 retries_cnt = 0; ; retries_cnt++) { gboolean match = TRUE; if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_EEPROM, 16, (EEPROM_BANK_OFFSET * bank_to_update + EEPROM_TAG_OFFSET), tagData, error)) { g_prefix_error (error, "failed to write tag: "); return FALSE; } g_usleep (200); if (!synapticsmst_common_rc_get_command (connection, UPDC_READ_FROM_EEPROM, 16, (EEPROM_BANK_OFFSET * bank_to_update + EEPROM_TAG_OFFSET), readBuf, error)) { g_prefix_error (error, "failed to read tag: "); return FALSE; } for (guint32 i = 0; i < 16; i++){ if (readBuf[i] != tagData[i]){ match = FALSE; break; } } if (match) break; if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set tag valid fail"); return FALSE; } } /* set tag invalid*/ if (!synapticsmst_common_rc_get_command (connection, UPDC_READ_FROM_EEPROM, 1, (EEPROM_BANK_OFFSET * bank_in_use + EEPROM_TAG_OFFSET + 15), tagData, error)) { g_prefix_error (error, "failed to read tag from flash: "); return FALSE; } for (guint32 retries_cnt = 0; ; retries_cnt++) { /* CRC8 is not 0xff, erase last 4k of bank# */ if (tagData[0] != 0xff) { guint32 erase_offset; /* offset for last 4k of bank# */ erase_offset = (EEPROM_BANK_OFFSET * bank_in_use + EEPROM_BANK_OFFSET - 0x1000) / 0x1000; if (!synapticsmst_device_set_flash_sector_erase (device, FLASH_SECTOR_ERASE_4K, erase_offset, error)) return FALSE; /* CRC8 is 0xff, set it to 0x00 */ } else { tagData[1] = 0x00; if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_EEPROM, 1, (EEPROM_BANK_OFFSET * bank_in_use + EEPROM_TAG_OFFSET + 15), &tagData[1], error)) { g_prefix_error (error, "failed to clear CRC: "); return FALSE; } } if (!synapticsmst_common_rc_get_command (connection, UPDC_READ_FROM_EEPROM, 1, (EEPROM_BANK_OFFSET * bank_in_use + EEPROM_TAG_OFFSET + 15), readBuf, error)) { g_prefix_error (error, "failed to read CRC from flash: "); return FALSE; } if ((readBuf[0] == 0xff && tagData[0] != 0xff) || (readBuf[0] == 0x00 && tagData[0] == 0xff)) { break; } if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set tag invalid fail"); return FALSE; } } return TRUE; } static gboolean synapticsmst_device_check_firmware_content (SynapticsMSTDevice *device, GBytes *fw, SynapticsMSTChipKind chip_type, GError **error) { gsize payload_len, payload_len_max; switch (chip_type) { case SYNAPTICSMST_CHIP_KIND_PANAMERA: payload_len_max = PAYLOAD_SIZE_512K; break; case SYNAPTICSMST_CHIP_KIND_TESLA_LEAF: payload_len_max = PAYLOAD_SIZE_64K; break; default: g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unknown chip type %u", chip_type); return FALSE; } /* check size */ payload_len = g_bytes_get_size (fw); if (payload_len > payload_len_max || payload_len == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid payload size %" G_GSIZE_FORMAT "(max %" G_GSIZE_FORMAT")", payload_len, payload_len_max); return FALSE; } return TRUE; } static gboolean synapticsmst_device_panamera_prepare_write (SynapticsMSTDevice *device, GError **error) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); guint32 dwData[4] = {0}; g_autoptr(SynapticsMSTConnection) connection = NULL; /* Need to detect flash mode and ESM first ? */ /* disable flash Quad mode and ESM/HDCP2.2*/ connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); /* disable ESM first */ dwData[0] = 0x21; if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_MEMORY, 4, (gint)REG_ESM_DISABLE, (guint8*)dwData, error)) { g_prefix_error (error, "ESM disable failed: "); return FALSE; } /* wait for ESM exit */ g_usleep (200); /* disable QUAD mode */ if (!synapticsmst_common_rc_get_command (connection, UPDC_READ_FROM_MEMORY, ((sizeof(dwData)/sizeof(dwData[0]))*4), (gint)REG_QUAD_DISABLE, (guint8*)dwData, error)) { g_prefix_error (error, "quad query failed: "); return FALSE; } dwData[0] = 0x00; if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_MEMORY, 4, (gint)REG_QUAD_DISABLE, (guint8*)dwData, error)) { g_prefix_error (error, "quad disable failed: "); return FALSE; } /* disable HDCP2.2 */ if (!synapticsmst_common_rc_get_command (connection, UPDC_READ_FROM_MEMORY, 4, (gint)REG_HDCP22_DISABLE, (guint8*)dwData, error)) { g_prefix_error (error, "HDCP query failed: "); return FALSE; } dwData[0] = dwData[0] & (~BIT(2)); if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_MEMORY, 4, (gint)REG_HDCP22_DISABLE, (guint8*)dwData, error)) { g_prefix_error (error, "HDCP disable failed: "); return FALSE; } return TRUE; } static gboolean synapticsmst_device_restart (SynapticsMSTDevice *device, GError **error) { g_autoptr(SynapticsMSTConnection) connection = NULL; SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); guint8 dwData[4] = {0xF5, 0, 0 ,0}; g_autoptr(GError) error_local = NULL; /* issue the reboot command, ignore return code (triggers before returning) */ connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad); if (!synapticsmst_common_rc_set_command (connection, UPDC_WRITE_TO_MEMORY, 4, (gint) 0x2000FC, (guint8*) &dwData, &error_local)) g_debug ("failed to restart: %s", error_local->message); return TRUE; } gboolean synapticsmst_device_write_firmware (SynapticsMSTDevice *device, GBytes *fw, GFileProgressCallback progress_cb, gpointer progress_data, gboolean reboot, gboolean install_force, GError **error) { const guint8 *payload_data; gsize payload_len; guint16 tmp; SynapticsMSTChipKind chip_type = SYNAPTICSMST_CHIP_KIND_UNKNOWN; g_autoptr(FuDeviceLocker) locker = NULL; payload_data = g_bytes_get_data (fw, &payload_len); if (synapticsmst_device_get_chip_id (device) > 0x5000) chip_type = SYNAPTICSMST_CHIP_KIND_PANAMERA; else chip_type = SYNAPTICSMST_CHIP_KIND_TESLA_LEAF; if (!synapticsmst_device_check_firmware_content (device, fw, chip_type, error)){ g_prefix_error (error, "Invalid file content: "); return FALSE; } /* check firmware and board ID again */ tmp = (*(payload_data + ADDR_CUSTOMER_ID) << 8) + *(payload_data + ADDR_BOARD_ID); if (tmp != synapticsmst_device_get_board_id (device) && !install_force) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "board ID mismatch"); return FALSE; } /* open device */ if (!synapticsmst_device_open (device, error)) { g_prefix_error (error, "can't open DP Aux node %s", synapticsmst_device_get_aux_node (device)); return FALSE; } /* enable remote control and disable on exit */ if (reboot) { locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) synapticsmst_device_enable_remote_control, (FuDeviceLockerFunc) synapticsmst_device_restart, error); } else { locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) synapticsmst_device_enable_remote_control, (FuDeviceLockerFunc) synapticsmst_device_disable_remote_control, error); } if (locker == NULL) return FALSE; /* update firmware */ if (chip_type == SYNAPTICSMST_CHIP_KIND_PANAMERA) { if (!synapticsmst_device_panamera_prepare_write (device, error)) { g_prefix_error (error, "Failed to prepare for write: "); return FALSE; } if (!synapticsmst_device_update_esm (device, payload_data, progress_cb, progress_data, error)) { g_prefix_error (error, "ESM update failed: "); return FALSE; } if (!synapticsmst_device_update_panamera_firmware (device, payload_len, payload_data, progress_cb, progress_data, error)) { g_prefix_error (error, "Firmware update failed: "); return FALSE; } } else { if (!synapticsmst_device_update_tesla_leaf_firmware (device, payload_len, payload_data, progress_cb, progress_data, error)) { g_prefix_error (error, "Firmware update failed: "); return FALSE; } } return TRUE; } SynapticsMSTDevice * synapticsmst_device_new (SynapticsMSTDeviceKind kind, const gchar *aux_node, guint8 layer, guint16 rad) { SynapticsMSTDevice *device; SynapticsMSTDevicePrivate *priv; device = g_object_new (SYNAPTICSMST_TYPE_DEVICE, NULL); priv = GET_PRIVATE (device); priv->aux_node = g_strdup(aux_node); priv->kind = kind; priv->version = NULL; priv->layer = layer; priv->rad = rad; priv->has_cascade = FALSE; return SYNAPTICSMST_DEVICE (device); } gboolean synapticsmst_device_open (SynapticsMSTDevice *device, GError **error) { SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device); g_autofree gchar *filename = NULL; guint8 byte[4]; g_autoptr(SynapticsMSTConnection) connection = NULL; /* file doesn't exist on this system */ filename = g_strdup_printf ("%s/%s", priv->fw_dir, priv->aux_node); if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no device exists %s", filename); return FALSE; } /* can't open aux node, try use sudo to get the permission */ priv->fd = open (filename, O_RDWR); if (priv->fd == -1) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "cannot open device %s: %s", filename, g_strerror (errno)); return FALSE; } connection = synapticsmst_common_new (priv->fd, 0, 0); if (!synapticsmst_common_read (connection, REG_RC_CAP, byte, 1, error)) { g_prefix_error (error, "failed to read device: "); return FALSE; } if (byte[0] & 0x04) { if (!synapticsmst_common_read (connection, REG_VENDOR_ID, byte, 3, error)) { g_prefix_error (error, "failed to read vendor ID: "); return FALSE; } if (byte[0] == 0x90 && byte[1] == 0xCC && byte[2] == 0x24) return TRUE; } /* not a correct device */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no device"); return FALSE; } fwupd-1.2.14/plugins/synapticsmst/synapticsmst-device.h000066400000000000000000000055111402665037500233270ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Peichen Huang * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define SYNAPTICSMST_TYPE_DEVICE (synapticsmst_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (SynapticsMSTDevice, synapticsmst_device, SYNAPTICSMST, DEVICE, GObject) #define SYSFS_DRM_DP_AUX "/sys/class/drm_dp_aux_dev" struct _SynapticsMSTDeviceClass { GObjectClass parent_class; }; /** * SynapticsMSTDeviceKind: * @SYNAPTICSMST_DEVICE_KIND_UNKNOWN: Type invalid or not known * @SYNAPTICSMST_DEVICE_KIND_DIRECT: Directly addressable * @SYNAPTICSMST_DEVICE_KIND_REMOTE: Requires remote register work * * The device type. **/ typedef enum { SYNAPTICSMST_DEVICE_KIND_UNKNOWN, SYNAPTICSMST_DEVICE_KIND_DIRECT, SYNAPTICSMST_DEVICE_KIND_REMOTE, /*< private >*/ SYNAPTICSMST_DEVICE_KIND_LAST } SynapticsMSTDeviceKind; typedef enum { SYNAPTICSMST_CHIP_KIND_UNKNOWN, SYNAPTICSMST_CHIP_KIND_TESLA_LEAF, SYNAPTICSMST_CHIP_KIND_PANAMERA, /**/ SYNAPTICSMST_CHIP_KIND_LAST } SynapticsMSTChipKind; #define CUSTOMERID_DELL 0x1 SynapticsMSTDevice *synapticsmst_device_new (SynapticsMSTDeviceKind kind, const gchar *aux_node, guint8 layer, guint16 rad); /* helpers */ SynapticsMSTDeviceKind synapticsmst_device_kind_from_string (const gchar *kind); const gchar *synapticsmst_device_kind_to_string (SynapticsMSTDeviceKind kind); GPtrArray *synapticsmst_device_get_guids (SynapticsMSTDevice *device); gboolean synapticsmst_device_scan_cascade_device (SynapticsMSTDevice *device, GError **error, guint8 tx_port); gboolean synapticsmst_device_open (SynapticsMSTDevice *device, GError **error); /* getters */ SynapticsMSTDeviceKind synapticsmst_device_get_kind (SynapticsMSTDevice *device); guint16 synapticsmst_device_get_board_id (SynapticsMSTDevice *device); const gchar *synapticsmst_device_get_version (SynapticsMSTDevice *device); const gchar *synapticsmst_device_get_chip_id_str (SynapticsMSTDevice *device); const gchar *synapticsmst_device_get_aux_node (SynapticsMSTDevice *device); guint16 synapticsmst_device_get_rad (SynapticsMSTDevice *device); guint8 synapticsmst_device_get_layer (SynapticsMSTDevice *device); gboolean synapticsmst_device_get_cascade (SynapticsMSTDevice *device); /* object methods */ gboolean synapticsmst_device_enumerate_device (SynapticsMSTDevice *devices, GError **error); gboolean synapticsmst_device_write_firmware (SynapticsMSTDevice *device, GBytes *fw, GFileProgressCallback progress_cb, gpointer user_data, gboolean reboot, gboolean install_force, GError **error); G_END_DECLS fwupd-1.2.14/plugins/synapticsmst/synapticsmst.quirk000066400000000000000000000022131402665037500227720ustar00rootroot00000000000000# GUID generation for Synaptics MST plugin # # SynapticsMSTBoardID is the 16 bit board ID which contains: # * Customer ID in first byte # * Board ID in the second byte # # DeviceKind = system # * Will map to a GUID containing HwID product SKU # * These GUIDs will look like MST-${PRODUCTSKU}-${BOARDID} # DeviceKind != system # * Will map to a GUID containing each comma delimitted substring # * These GUIDs will look like MST-${DEVICEKIND}-${CHIPID}-${BOARDID} # # By default the Synaptics MST device will restart after update # To override this behavior add the custom flag "skip-restart" # [SynapticsMSTBoardID=272] Name = Dell X6 Platform DeviceKind = system [SynapticsMSTBoardID=273] Name = Dell X7 Platform DeviceKind = system [SynapticsMSTBoardID=274] Name = Dell WD15/TB16/TB18 wired Dock DeviceKind = wd15,tb16,tb18 [SynapticsMSTBoardID=275] Name = Dell WLD15 Wireless Dock DeviceKind = wld15 [SynapticsMSTBoardID=277] Name = Dell Rugged Platform DeviceKind = system [SynapticsMSTBoardID=513] Name = ThinkPad Workstation Dock DeviceKind = panamera [SynapticsMSTBoardID=595] Name = ThinkPad Thunderbolt 3 Workstation Dock DeviceKind = panamera fwupd-1.2.14/plugins/synapticsmst/synapticsmst_evb.quirk000066400000000000000000000011711402665037500236300ustar00rootroot00000000000000# Synaptics MST early validation board support # # This is not installed by default, but can be used to exercise new boards # that don't yet have customer ID or board ID bytes filled out. # # To use it, load the quirk file into the quirks directory for the fwupd installation # Usually this is /usr/share/fwupd/quirks.d # # Note: The flag "ignore-board-id" will be used to ignore the board ID checking in # during flashing. This shouldn't be used in practice for production boards. # [SynapticsMSTBoardID=2] Name = Synaptics EVB development board DeviceKind = panamera_evb [Guid=MST-panamera_evb-vmm5331-2] Flags = ignore-board-id fwupd-1.2.14/plugins/synapticsmst/tests/000077500000000000000000000000001402665037500203205ustar00rootroot00000000000000fwupd-1.2.14/plugins/synapticsmst/tests/no_devices/000077500000000000000000000000001402665037500224365ustar00rootroot00000000000000fwupd-1.2.14/plugins/synapticsmst/tests/no_devices/drm_dp_aux0000066400000000000000000003720001402665037500245650ustar00rootroot00000000000000 A   wU@fwupd-1.2.14/plugins/synapticsmst/tests/no_devices/drm_dp_aux1000066400000000000000000000000001402665037500245520ustar00rootroot00000000000000fwupd-1.2.14/plugins/synapticsmst/tests/no_devices/drm_dp_aux2000066400000000000000000000000001402665037500245530ustar00rootroot00000000000000fwupd-1.2.14/plugins/synapticsmst/tests/tb16_dock/000077500000000000000000000000001402665037500220745ustar00rootroot00000000000000fwupd-1.2.14/plugins/synapticsmst/tests/tb16_dock/drm_dp_aux0000066400000000000000000003720001402665037500242230ustar00rootroot00000000000000 A   wU@fwupd-1.2.14/plugins/synapticsmst/tests/tb16_dock/drm_dp_aux1000066400000000000000000011610001402665037500242210ustar00rootroot00000000000000wO wO  ?TESLA$SYNA30 ,] Non-PnP fwupd-1.2.14/plugins/synapticsmst/tests/tb16_dock/drm_dp_aux2000066400000000000000000011610001402665037500242220ustar00rootroot00000000000000|O |O  ?TESLA$SYNA3  G0fwupd-1.2.14/plugins/synapticsmst/tests/tb16_dock/remote/000077500000000000000000000000001402665037500233675ustar00rootroot00000000000000fwupd-1.2.14/plugins/synapticsmst/tests/tb16_dock/remote/drm_dp_aux0000066400000000000000000007640001402665037500255230ustar00rootroot00000000000000 A   wU@fwupd-1.2.14/plugins/synapticsmst/tests/tb16_dock/remote/drm_dp_aux1000066400000000000000000003720001402665037500255170ustar00rootroot00000000000000* *  ?TESLAIUS$SYNA30 ,] Non-PnP fwupd-1.2.14/plugins/synapticsmst/tests/tb16_dock/remote/drm_dp_aux1_eeprom000066400000000000000000000000021402665037500270540ustar00rootroot00000000000000fwupd-1.2.14/plugins/synapticsmst/tests/tb16_dock/remote/drm_dp_aux2000066400000000000000000007640001402665037500255250ustar00rootroot000000000000002 , 2 ,  ?TESLAPRIUS$SYNA3  ,] Non-PnP fwupd-1.2.14/plugins/synapticsmst/tests/tb16_dock/remote/drm_dp_aux2_eeprom000066400000000000000000000000021402665037500270550ustar00rootroot00000000000000fwupd-1.2.14/plugins/test/000077500000000000000000000000001402665037500153745ustar00rootroot00000000000000fwupd-1.2.14/plugins/test/README.md000066400000000000000000000004241402665037500166530ustar00rootroot00000000000000Test Support ============ Introduction ------------ This plugin is used when running the self tests in the fwupd project. GUID Generation --------------- The devices created by this plugin use hardcoded GUIDs that do not correspond to any kind of DeviceInstanceId values. fwupd-1.2.14/plugins/test/fu-plugin-test.c000066400000000000000000000155761402665037500204410ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" struct FuPluginData { GMutex mutex; }; void fu_plugin_init (FuPlugin *plugin) { if (g_strcmp0 (g_getenv ("FWUPD_PLUGIN_TEST"), "build-hash") == 0) fu_plugin_set_build_hash (plugin, "invalid"); else fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.acme.test"); fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); g_debug ("init"); } void fu_plugin_destroy (FuPlugin *plugin) { //FuPluginData *data = fu_plugin_get_data (plugin); g_debug ("destroy"); } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { g_autoptr(FuDevice) device = NULL; device = fu_device_new (); fu_device_set_id (device, "FakeDevice"); fu_device_add_guid (device, "b585990a-003e-5270-89d5-3705a17f9a43"); fu_device_set_name (device, "Integrated_Webcam(TM)"); fu_device_add_icon (device, "preferences-desktop-keyboard"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_summary (device, "A fake webcam"); fu_device_set_vendor (device, "ACME Corp."); fu_device_set_vendor_id (device, "USB:0x046D"); fu_device_set_version_bootloader (device, "0.1.2"); fu_device_set_version (device, "1.2.2", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version_lowest (device, "1.2.0"); if (g_strcmp0 (g_getenv ("FWUPD_PLUGIN_TEST"), "registration") == 0) { fu_plugin_device_register (plugin, device); if (fu_device_get_metadata (device, "BestDevice") == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Device not set by another plugin"); return FALSE; } } fu_plugin_device_add (plugin, device); if (g_strcmp0 (g_getenv ("FWUPD_PLUGIN_TEST"), "composite") == 0) { g_autoptr(FuDevice) child1 = NULL; g_autoptr(FuDevice) child2 = NULL; child1 = fu_device_new (); fu_device_set_physical_id (child1, "fake"); fu_device_set_logical_id (child1, "child1"); fu_device_add_guid (child1, "7fddead7-12b5-4fb9-9fa0-6d30305df755"); fu_device_set_name (child1, "Module1"); fu_device_set_version (child1, "1", FWUPD_VERSION_FORMAT_PLAIN); fu_device_add_parent_guid (child1, "b585990a-003e-5270-89d5-3705a17f9a43"); fu_device_add_flag (child1, FWUPD_DEVICE_FLAG_UPDATABLE); fu_plugin_device_add (plugin, child1); child2 = fu_device_new (); fu_device_set_physical_id (child2, "fake"); fu_device_set_logical_id (child2, "child2"); fu_device_add_guid (child2, "b8fe6b45-8702-4bcd-8120-ef236caac76f"); fu_device_set_name (child2, "Module2"); fu_device_set_version (child2, "10", FWUPD_VERSION_FORMAT_PLAIN); fu_device_add_parent_guid (child2, "b585990a-003e-5270-89d5-3705a17f9a43"); fu_device_add_flag (child2, FWUPD_DEVICE_FLAG_UPDATABLE); fu_plugin_device_add (plugin, child2); } return TRUE; } void fu_plugin_device_registered (FuPlugin *plugin, FuDevice *device) { fu_device_set_metadata (device, "BestDevice", "/dev/urandom"); } gboolean fu_plugin_verify (FuPlugin *plugin, FuDevice *device, FuPluginVerifyFlags flags, GError **error) { if (g_strcmp0 (fu_device_get_version (device), "1.2.3") == 0) { fu_device_add_checksum (device, "7998cd212721e068b2411135e1f90d0ad436d730"); fu_device_add_checksum (device, "dbae6a0309b3de8e850921631916a60b2956056e109fc82c586e3f9b64e2401a"); return TRUE; } if (g_strcmp0 (fu_device_get_version (device), "1.2.4") == 0) { fu_device_add_checksum (device, "2b8546ba805ad10bf8a2e5ad539d53f303812ba5"); fu_device_add_checksum (device, "b546c241029ce4e16c99eb6bfd77b86e4490aa3826ba71b8a4114e96a2d69bcd"); return TRUE; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no checksum for %s", fu_device_get_version (device)); return FALSE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { const gchar *test = g_getenv ("FWUPD_PLUGIN_TEST"); gboolean requires_activation = g_strcmp0 (test, "requires-activation") == 0; if (g_strcmp0 (test, "fail") == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device was not in supported mode"); return FALSE; } fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); for (guint i = 1; i <= 100; i++) { g_usleep (1000); fu_device_set_progress (device, i); } fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 1; i <= 100; i++) { g_usleep (1000); fu_device_set_progress (device, i); } fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY); for (guint i = 1; i <= 100; i++) { g_usleep (1000); fu_device_set_progress (device, i); } /* composite test, upgrade composite devices */ if (g_strcmp0 (test, "composite") == 0) { if (g_strcmp0 (fu_device_get_logical_id (device), "child1") == 0) { fu_device_set_version (device, "2", FWUPD_VERSION_FORMAT_PLAIN); return TRUE; } else if (g_strcmp0 (fu_device_get_logical_id (device), "child2") == 0) { fu_device_set_version (device, "11", FWUPD_VERSION_FORMAT_PLAIN); return TRUE; } } /* upgrade, or downgrade */ if (requires_activation) { fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); } else { if (flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) { fu_device_set_version (device, "1.2.2", FWUPD_VERSION_FORMAT_TRIPLET); } else { fu_device_set_version (device, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); } } /* do this all over again */ if (g_strcmp0 (test, "another-write-required") == 0) { g_unsetenv ("FWUPD_PLUGIN_TEST"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); } /* for the self tests only */ fu_device_set_metadata_integer (device, "nr-update", fu_device_get_metadata_integer (device, "nr-update") + 1); return TRUE; } gboolean fu_plugin_activate (FuPlugin *plugin, FuDevice *device, GError **error) { fu_device_set_version (device, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); return TRUE; } gboolean fu_plugin_get_results (FuPlugin *plugin, FuDevice *device, GError **error) { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_SUCCESS); fu_device_set_update_error (device, NULL); return TRUE; } gboolean fu_plugin_composite_prepare (FuPlugin *plugin, GPtrArray *devices, GError **error) { if (g_strcmp0 (g_getenv ("FWUPD_PLUGIN_TEST"), "composite") == 0) { for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); fu_device_set_metadata (device, "frimbulator", "1"); } } return TRUE; } gboolean fu_plugin_composite_cleanup (FuPlugin *plugin, GPtrArray *devices, GError **error) { if (g_strcmp0 (g_getenv ("FWUPD_PLUGIN_TEST"), "composite") == 0) { for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); fu_device_set_metadata (device, "frombulator", "1"); } } return TRUE; } fwupd-1.2.14/plugins/test/meson.build000066400000000000000000000010021402665037500175270ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginTest"'] install_dummy = false if get_option('plugin_dummy') install_dummy = true endif shared_module('fu_plugin_test', fu_hash, sources : [ 'fu-plugin-test.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : install_dummy, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/thunderbolt-power/000077500000000000000000000000001402665037500201015ustar00rootroot00000000000000fwupd-1.2.14/plugins/thunderbolt-power/fu-plugin-thunderbolt-power.c000066400000000000000000000305341402665037500256420ustar00rootroot00000000000000/* * Copyright (C) 2017 Dell, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-plugin-vfuncs.h" #include "fu-device-metadata.h" #define BOLT_DBUS_SERVICE "org.freedesktop.bolt" #define BOLT_DBUS_PATH "/org/freedesktop/bolt" #define BOLT_DBUS_INTERFACE "org.freedesktop.bolt1.Power" #ifndef HAVE_GUDEV_232 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref) #pragma clang diagnostic pop #endif /* empirically measured amount of time for the TBT device to come and go */ #define TBT_NEW_DEVICE_TIMEOUT 2 /* s */ struct FuPluginData { GUdevClient *udev; gchar *force_path; gboolean needs_forcepower; gboolean updating; guint timeout_id; gint bolt_fd; }; static gboolean fu_plugin_thunderbolt_power_bolt_supported (FuPlugin *plugin) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GVariant) val = NULL; g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GError) error_local = NULL; gboolean supported = FALSE; connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error_local); if (connection == NULL) { g_warning ("Failed to initialize d-bus connection: %s", error_local->message); return supported; } proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, NULL, BOLT_DBUS_SERVICE, BOLT_DBUS_PATH, BOLT_DBUS_INTERFACE, NULL, &error_local); if (proxy == NULL) { g_warning ("Failed to initialize d-bus proxy: %s", error_local->message); return supported; } val = g_dbus_proxy_get_cached_property (proxy, "Supported"); if (val != NULL) g_variant_get (val, "b", &supported); g_debug ("Bolt force power support: %d", supported); return supported; } static gboolean fu_plugin_thunderbolt_power_bolt_force_power (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GUnixFDList) fds = NULL; g_autoptr(GVariant) val = NULL; GVariant *input; input = g_variant_new ("(ss)", "fwupd", /* who */ ""); /* flags */ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) return FALSE; proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, NULL, BOLT_DBUS_SERVICE, BOLT_DBUS_PATH, BOLT_DBUS_INTERFACE, NULL, error); if (proxy == NULL) return FALSE; val = g_dbus_proxy_call_with_unix_fd_list_sync (proxy, "ForcePower", input, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &fds, NULL, error); if (val == NULL) return FALSE; if (g_unix_fd_list_get_length (fds) != 1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid number of file descriptors returned: %d", g_unix_fd_list_get_length (fds)); return FALSE; } data->bolt_fd = g_unix_fd_list_get (fds, 0, NULL); return TRUE; } static void fu_plugin_thunderbolt_power_get_kernel_path (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); g_autoptr(GList) devices = NULL; const gchar *basepath; const gchar *driver; /* in case driver went away */ if (data->force_path != NULL) { g_free (data->force_path); data->force_path = NULL; } devices = g_udev_client_query_by_subsystem (data->udev, "wmi"); for (GList* l = devices; l != NULL; l = l->next) { g_autofree gchar *built_path = NULL; GUdevDevice *device = l->data; /* only supports intel-wmi-thunderbolt for now */ driver = g_udev_device_get_driver (device); if (g_strcmp0 (driver, "intel-wmi-thunderbolt") != 0) continue; /* check for the attribute to be loaded */ basepath = g_udev_device_get_sysfs_path (device); if (basepath == NULL) continue; built_path = g_build_path ("/", basepath, "force_power", NULL); if (g_file_test (built_path, G_FILE_TEST_IS_REGULAR)) { data->force_path = g_steal_pointer (&built_path); g_debug ("Direct kernel force power support at %s", data->force_path); break; } } g_list_foreach (devices, (GFunc) g_object_unref, NULL); } static gboolean fu_plugin_thunderbolt_power_kernel_supported (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); return data->force_path != NULL; } static gboolean fu_plugin_thunderbolt_power_kernel_force_power (FuPlugin *plugin, gboolean enable, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); gint fd; gint ret; if (!fu_plugin_thunderbolt_power_kernel_supported (plugin)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unable to set power to %d (missing kernel support)", enable); return FALSE; } g_debug ("Setting force power to %d using kernel", enable); fd = g_open (data->force_path, O_WRONLY); if (fd == -1) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to open %s", data->force_path); return FALSE; } ret = write (fd, enable ? "1" : "0", 1); if (ret < 1) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not write to force_power': %s", g_strerror (errno)); g_close (fd, NULL); return FALSE; } return g_close (fd, error); } static gboolean fu_plugin_thunderbolt_power_set (FuPlugin *plugin, gboolean enable, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); /* prefer bolt API if available */ if (fu_plugin_thunderbolt_power_bolt_supported (plugin)) { g_debug ("Setting force power to %d using bolt", enable); if (enable) return fu_plugin_thunderbolt_power_bolt_force_power (plugin, error); return data->bolt_fd >= 0 ? g_close (data->bolt_fd, error) : TRUE; } return fu_plugin_thunderbolt_power_kernel_force_power (plugin, enable, error); } static gboolean fu_plugin_thunderbolt_power_reset_cb (gpointer user_data) { FuPlugin *plugin = FU_PLUGIN (user_data); FuPluginData *data = fu_plugin_get_data (plugin); if (!fu_plugin_thunderbolt_power_set (plugin, FALSE, NULL)) g_warning ("failed to reset thunderbolt power"); data->timeout_id = 0; return FALSE; } static void fu_plugin_thunderbolt_reset_timeout (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); if (!data->needs_forcepower || data->updating) return; g_debug ("Setting timeout to %d seconds", TBT_NEW_DEVICE_TIMEOUT * 10); /* in case this was a re-coldplug */ if (data->timeout_id != 0) g_source_remove (data->timeout_id); /* reset force power to off after enough time to enumerate */ data->timeout_id = g_timeout_add (TBT_NEW_DEVICE_TIMEOUT * 10000, fu_plugin_thunderbolt_power_reset_cb, plugin); } static gboolean udev_uevent_cb (GUdevClient *udev, const gchar *action, GUdevDevice *device, gpointer user_data) { FuPlugin *plugin = FU_PLUGIN(user_data); if (action == NULL) return TRUE; g_debug ("uevent for %s: (%s) %s", g_udev_device_get_name (device), g_udev_device_get_sysfs_path (device), action); /* thunderbolt device was turned on */ if (g_str_equal (g_udev_device_get_subsystem (device), "thunderbolt") && g_str_equal (action, "add")) { fu_plugin_thunderbolt_reset_timeout (plugin); /* intel-wmi-thunderbolt has been loaded/unloaded */ } else if (g_str_equal (action, "change")) { fu_plugin_thunderbolt_power_get_kernel_path (plugin); if (fu_plugin_thunderbolt_power_kernel_supported (plugin)) { fu_plugin_set_enabled (plugin, TRUE); fu_plugin_request_recoldplug (plugin); } else { fu_plugin_set_enabled (plugin, FALSE); } } return TRUE; } /* virtual functions */ void fu_plugin_init (FuPlugin *plugin) { FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); const gchar *subsystems[] = { "thunderbolt", "wmi", NULL }; data->udev = g_udev_client_new (subsystems); g_signal_connect (data->udev, "uevent", G_CALLBACK (udev_uevent_cb), plugin); /* initially set to true, will wait for a device_register to reset */ data->needs_forcepower = TRUE; /* will reset when needed */ data->bolt_fd = -1; /* determines whether to run device_registered */ fu_plugin_thunderbolt_power_get_kernel_path (plugin); /* make sure it's tried to coldplug */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "thunderbolt"); fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); if (data->timeout_id != 0) { g_source_remove (data->timeout_id); data->timeout_id = 0; } g_object_unref (data->udev); g_free (data->force_path); /* in case destroying before force power turned off */ if (data->bolt_fd >= 0) g_close (data->bolt_fd, NULL); } void fu_plugin_device_registered (FuPlugin *plugin, FuDevice *device) { FuPluginData *data = fu_plugin_get_data (plugin); /* We care only about the thunderbolt devices. NB: we don't care * about avoiding to auto-starting boltd here, because if there * is thunderbolt hardware present, boltd is already running */ if (g_strcmp0 (fu_device_get_plugin (device), "thunderbolt") == 0 && (fu_plugin_thunderbolt_power_bolt_supported (plugin) || fu_plugin_thunderbolt_power_kernel_supported (plugin))) { data->needs_forcepower = FALSE; if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_INTERNAL)) { fu_device_set_metadata_boolean (device, FU_DEVICE_METADATA_TBT_CAN_FORCE_POWER, TRUE); } } } gboolean fu_plugin_update_prepare (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *device, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autoptr(GUdevDevice) udevice = NULL; const gchar *devpath; /* only run for thunderbolt plugin */ if (g_strcmp0 (fu_device_get_plugin (device), "thunderbolt") != 0) return TRUE; /* reset any timers that might still be running from coldplug */ if (data->timeout_id != 0) { g_source_remove (data->timeout_id); data->timeout_id = 0; } devpath = fu_device_get_metadata (device, "sysfs-path"); udevice = g_udev_client_query_by_sysfs_path (data->udev, devpath); if (udevice != NULL) { data->needs_forcepower = FALSE; return TRUE; } data->updating = TRUE; if (!fu_plugin_thunderbolt_power_set (plugin, TRUE, error)) return FALSE; data->needs_forcepower = TRUE; /* wait for the device to come back onto the bus */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); for (guint i = 0; i < 5; i++) { g_autoptr(GUdevDevice) udevice_tmp = NULL; g_usleep (TBT_NEW_DEVICE_TIMEOUT * G_USEC_PER_SEC); udevice_tmp = g_udev_client_query_by_sysfs_path (data->udev, devpath); if (udevice_tmp != NULL) return TRUE; } /* device did not wake up */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device did not wake up when required"); return FALSE; } gboolean fu_plugin_update_cleanup (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *device, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); /* only run for thunderbolt plugin */ if (g_strcmp0 (fu_device_get_plugin (device), "thunderbolt") != 0) return TRUE; data->updating = FALSE; if (data->needs_forcepower && !fu_plugin_thunderbolt_power_set (plugin, FALSE, error)) return FALSE; return TRUE; } static gboolean fu_plugin_thunderbolt_power_coldplug (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); /* NB: we don't check for force-power support via bolt here * (although we later prefer that), because boltd uses the * same kernel interface and if that does not exist, we can * avoid pinging bolt, potentially auto-starting it. */ if (!fu_plugin_thunderbolt_power_kernel_supported (plugin)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No support for force power detected"); return FALSE; } /* this means no devices were found at coldplug by thunderbolt plugin */ if (data->needs_forcepower) { if (!fu_plugin_thunderbolt_power_set (plugin, TRUE, error)) return FALSE; fu_plugin_thunderbolt_reset_timeout (plugin); } return TRUE; } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { return fu_plugin_thunderbolt_power_coldplug (plugin, error); } gboolean fu_plugin_recoldplug (FuPlugin *plugin, GError **error) { return fu_plugin_thunderbolt_power_coldplug (plugin, error); } fwupd-1.2.14/plugins/thunderbolt-power/meson.build000066400000000000000000000007461402665037500222520ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginThunderbolt"'] fu_plugin_thunderbolt_power = shared_module('fu_plugin_thunderbolt_power', fu_hash, sources : [ 'fu-plugin-thunderbolt-power.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/thunderbolt/000077500000000000000000000000001402665037500167475ustar00rootroot00000000000000fwupd-1.2.14/plugins/thunderbolt/README.md000066400000000000000000000054641402665037500202370ustar00rootroot00000000000000Thunderbolt™ Support ==================== Introduction ------------ Thunderbolt™ is the brand name of a hardware interface developed by Intel that allows the connection of external peripherals to a computer. Versions 1 and 2 use the same connector as Mini DisplayPort (MDP), whereas version 3 uses USB Type-C. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format, with vendor specific header. This plugin supports the following protocol ID: * com.intel.thunderbolt GUID Generation --------------- These devices use a custom GUID generation scheme. When the device is in "safe mode" the GUID is hardcoded using: * `TBT-safemode` ... and when in runtime mode the GUID is: * `TBT-$(vid)$(pid)-native` when native, and `TBT-$(vid)$(pid)` otherwise. Runtime Power Management ------------------------ Thunderbolt controllers are slightly unusual in that they power down completely when no thunderbolt devices are detected. This poses a problem for fwupd as it can't coldplug devices to see if there are firmware updates available, and also can't ensure the controller stays awake during a firmware upgrade. On Dell hardware the `Thunderbolt::CanForcePower` metadata value is set as the system can force the thunderbolt controller on during coldplug or during the firmware update process. This is typically done calling a SMI or ACPI method which asserts the GPIO for the duration of the request. On non-Dell hardware you will have to insert a Thunderbolt device (e.g. a dock) into the laptop to be able to update the controller itself. Safe Mode --------- Thunderbolt hardware is also slightly unusual in that it goes into "safe mode" whenever it encounters a critical firmware error, for instance if an update failed to be completed. In this safe mode you cannot query the controller vendor or model and therefore the thunderbolt plugin cannot add the correct GUID used to match it to the correct firmware. In this case the metadata value `Thunderbolt::IsSafeMode` is set which would allow a different plugin to add the correct GUID based on some out-of-band device discovery. At the moment this only happens on Dell hardware. GUID generation for LVFS ------------------------ The GUID for the controller, which must appear in the metadata when uploading an NVM to LVFS, can be generated by a tool like `appstream-util` (with `generate-guid` command) or by Python (with `uuid.uuid5(uuid.NAMESPACE_DNS, 'string')`). The format of the string used as input is "TBT-vvvvdddd", where vvvvv is the vendor ID and dddd is the device ID, both in hex, as appear in the controller's DROM and exposed in the relevant sysfs attributes. If the controller is in native enumeration mode, the string "-native" is added at the end so the format is "TBT-vvvvdddd-native". fwupd-1.2.14/plugins/thunderbolt/fu-plugin-thunderbolt.c000066400000000000000000000504671402665037500233650ustar00rootroot00000000000000/* * Copyright (C) 2017 Christian J. Kellner * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include "fu-plugin-vfuncs.h" #include "fu-device-metadata.h" #include "fu-thunderbolt-image.h" #ifndef HAVE_GUDEV_232 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref) #pragma clang diagnostic pop #endif #define TBT_NVM_RETRY_TIMEOUT 200 /* ms */ #define FU_PLUGIN_THUNDERBOLT_UPDATE_TIMEOUT 60000 /* ms */ typedef void (*UEventNotify) (FuPlugin *plugin, GUdevDevice *udevice, const gchar *action, gpointer user_data); struct FuPluginData { GUdevClient *udev; }; static gchar * fu_plugin_thunderbolt_gen_id_from_syspath (const gchar *syspath) { gchar *id; id = g_strdup_printf ("tbt-%s", syspath); g_strdelimit (id, "/:.-", '_'); return id; } static gchar * fu_plugin_thunderbolt_gen_id (GUdevDevice *device) { const gchar *syspath = g_udev_device_get_sysfs_path (device); return fu_plugin_thunderbolt_gen_id_from_syspath (syspath); } static gboolean udev_device_get_sysattr_guint64 (GUdevDevice *device, const gchar *name, guint64 *val_out, GError **error) { const gchar *sysfs; sysfs = g_udev_device_get_sysfs_attr (device, name); if (sysfs == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed get id %s for %s", name, sysfs); return FALSE; } *val_out = g_ascii_strtoull (sysfs, NULL, 16); if (*val_out == 0x0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to parse %s", sysfs); return FALSE; } return TRUE; } static guint16 fu_plugin_thunderbolt_udev_get_id (GUdevDevice *device, const gchar *name, GError **error) { guint64 id = 0; if (!udev_device_get_sysattr_guint64 (device, name, &id, error)) return 0x0; if (id > G_MAXUINT16) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "vendor id overflows"); return 0x0; } return (guint16) id; } static gboolean fu_plugin_thunderbolt_is_host (GUdevDevice *device) { g_autoptr(GUdevDevice) parent = NULL; const gchar *name; /* the (probably safe) assumption this code makes is * that the thunderbolt device which is a direct child * of the domain is the host controller device itself */ parent = g_udev_device_get_parent (device); name = g_udev_device_get_name (parent); if (name == NULL) return FALSE; return g_str_has_prefix (name, "domain"); } static GFile * fu_plugin_thunderbolt_find_nvmem (GUdevDevice *udevice, gboolean active, GError **error) { const gchar *nvmem_dir = active ? "nvm_active" : "nvm_non_active"; const gchar *devpath; const gchar *name; g_autoptr(GDir) d = NULL; devpath = g_udev_device_get_sysfs_path (udevice); if (G_UNLIKELY (devpath == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Could not determine sysfs path for device"); return NULL; } d = g_dir_open (devpath, 0, error); if (d == NULL) return NULL; while ((name = g_dir_read_name (d)) != NULL) { if (g_str_has_prefix (name, nvmem_dir)) { g_autoptr(GFile) parent = g_file_new_for_path (devpath); g_autoptr(GFile) nvm_dir = g_file_get_child (parent, name); return g_file_get_child (nvm_dir, "nvmem"); } } g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Could not find non-volatile memory location"); return NULL; } static gchar * fu_plugin_thunderbolt_parse_version (const gchar *version_raw) { g_auto(GStrv) split = NULL; if (version_raw == NULL) return NULL; split = g_strsplit (version_raw, ".", -1); if (g_strv_length (split) != 2) return NULL; return g_strdup_printf ("%02x.%02x", (guint) g_ascii_strtoull (split[0], NULL, 16), (guint) g_ascii_strtoull (split[1], NULL, 16)); } static gchar * fu_plugin_thunderbolt_udev_get_version (GUdevDevice *udevice) { const gchar *version = NULL; for (guint i = 0; i < 50; i++) { version = g_udev_device_get_sysfs_attr (udevice, "nvm_version"); if (version != NULL) break; g_debug ("Attempt %u: Failed to read NVM version", i); if (errno != EAGAIN) break; g_usleep (TBT_NVM_RETRY_TIMEOUT * 1000); } return fu_plugin_thunderbolt_parse_version (version); } static gboolean fu_plugin_thunderbolt_is_native (GUdevDevice *udevice, gboolean *is_native, GError **error) { gsize nr_chunks; g_autoptr(GFile) nvmem = NULL; g_autoptr(GBytes) controller_fw = NULL; g_autoptr(GInputStream) istr = NULL; nvmem = fu_plugin_thunderbolt_find_nvmem (udevice, TRUE, error); if (nvmem == NULL) return FALSE; /* read just enough bytes to read the status byte */ nr_chunks = (FU_TBT_OFFSET_NATIVE + FU_TBT_CHUNK_SZ - 1) / FU_TBT_CHUNK_SZ; istr = G_INPUT_STREAM (g_file_read (nvmem, NULL, error)); if (istr == NULL) return FALSE; controller_fw = g_input_stream_read_bytes (istr, nr_chunks * FU_TBT_CHUNK_SZ, NULL, error); if (controller_fw == NULL) return FALSE; return fu_thunderbolt_image_controller_is_native (controller_fw, is_native, error); } static gboolean fu_plugin_thunderbolt_can_update (GUdevDevice *udevice) { g_autoptr(GError) nvmem_error = NULL; g_autoptr(GFile) non_active_nvmem = NULL; non_active_nvmem = fu_plugin_thunderbolt_find_nvmem (udevice, FALSE, &nvmem_error); if (non_active_nvmem == NULL) { g_debug ("%s", nvmem_error->message); return FALSE; } return TRUE; } static void fu_plugin_thunderbolt_add (FuPlugin *plugin, GUdevDevice *device) { FuDevice *dev_tmp; const gchar *name; const gchar *uuid; const gchar *vendor; const gchar *devpath; const gchar *devtype; gboolean is_host; gboolean is_safemode = FALSE; gboolean is_native = FALSE; guint16 did; guint16 vid; g_autofree gchar *id = NULL; g_autofree gchar *version = NULL; g_autofree gchar *vendor_id = NULL; g_autofree gchar *device_id = NULL; g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_vid = NULL; g_autoptr(GError) error_did = NULL; g_autoptr(GError) error_setup = NULL; uuid = g_udev_device_get_sysfs_attr (device, "unique_id"); if (uuid == NULL) { /* most likely the domain itself, ignore */ return; } devpath = g_udev_device_get_sysfs_path (device); devtype = g_udev_device_get_devtype (device); if (g_strcmp0 (devtype, "thunderbolt_device") != 0) { g_debug ("ignoring %s device at %s", devtype, devpath); return; } g_debug ("adding udev device: %s at %s", uuid, devpath); id = fu_plugin_thunderbolt_gen_id (device); dev_tmp = fu_plugin_cache_lookup (plugin, id); if (dev_tmp != NULL) { /* devices that are force-powered are re-added */ g_debug ("ignoring duplicate %s", id); return; } vid = fu_plugin_thunderbolt_udev_get_id (device, "vendor", &error_vid); if (vid == 0x0) g_warning ("failed to get Vendor ID: %s", error_vid->message); did = fu_plugin_thunderbolt_udev_get_id (device, "device", &error_did); if (did == 0x0) g_warning ("failed to get Device ID: %s", error_did->message); dev = fu_device_new (); is_host = fu_plugin_thunderbolt_is_host (device); version = fu_plugin_thunderbolt_udev_get_version (device); /* test for safe mode */ if (is_host && version == NULL) { g_autoptr(GError) error_local = NULL; g_autofree gchar *test_safe = NULL; g_autofree gchar *safe_path = NULL; /* glib can't return a properly mapped -ENODATA but the * kernel only returns -ENODATA or -EAGAIN */ safe_path = g_build_path ("/", devpath, "nvm_version", NULL); if (!g_file_get_contents (safe_path, &test_safe, NULL, &error_local) && !g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { g_warning ("%s is in safe mode -- VID/DID will " "need to be set by another plugin", devpath); version = g_strdup ("00.00"); is_safemode = TRUE; device_id = g_strdup ("TBT-safemode"); fu_device_set_metadata_boolean (dev, FU_DEVICE_METADATA_TBT_IS_SAFE_MODE, TRUE); } fu_plugin_add_report_metadata (plugin, "ThunderboltSafeMode", is_safemode ? "True" : "False"); } if (!is_safemode) { if (fu_plugin_thunderbolt_can_update (device)) { if (is_host) { g_autoptr(GError) native_error = NULL; if (!fu_plugin_thunderbolt_is_native (device, &is_native, &native_error)) { g_warning ("failed to get native mode status: %s", native_error->message); return; } fu_plugin_add_report_metadata (plugin, "ThunderboltNative", is_native ? "True" : "False"); } vendor_id = g_strdup_printf ("TBT:0x%04X", (guint) vid); device_id = g_strdup_printf ("TBT-%04x%04x%s", (guint) vid, (guint) did, is_native ? "-native" : ""); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); } else { device_id = g_strdup ("TBT-fixed"); fu_device_set_update_error (dev, "Missing non-active nvmem"); } } else { fu_device_set_update_error (dev, "Device is in safe mode"); } fu_device_set_physical_id (dev, uuid); fu_device_set_metadata (dev, "sysfs-path", devpath); name = g_udev_device_get_sysfs_attr (device, "device_name"); if (name == NULL && is_host) name = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_NAME); if (name != NULL) { if (is_host) { g_autofree gchar *pretty_name = NULL; pretty_name = g_strdup_printf ("%s Thunderbolt Controller", name); fu_device_set_name (dev, pretty_name); } else { fu_device_set_name (dev, name); } } if (is_host) fu_device_set_summary (dev, "Unmatched performance for high-speed I/O"); fu_device_add_icon (dev, "thunderbolt"); fu_device_set_quirks (dev, fu_plugin_get_quirks (plugin)); vendor = g_udev_device_get_sysfs_attr (device, "vendor_name"); if (vendor != NULL) fu_device_set_vendor (dev, vendor); if (vendor_id != NULL) fu_device_set_vendor_id (dev, vendor_id); if (device_id != NULL) fu_device_add_instance_id (dev, device_id); if (version != NULL) fu_device_set_version (dev, version, FWUPD_VERSION_FORMAT_PAIR); if (is_host) fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_REQUIRE_AC); /* we never open the device, so convert the instance IDs */ if (!fu_device_setup (dev, &error_setup)) { g_warning ("failed to setup: %s", error_setup->message); return; } fu_plugin_cache_add (plugin, id, dev); fu_plugin_device_add (plugin, dev); /* inhibit the idle sleep of the daemon */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_INHIBITS_IDLE, "thunderbolt requires device wakeup"); } static void fu_plugin_thunderbolt_remove (FuPlugin *plugin, GUdevDevice *device) { FuDevice *dev; g_autofree gchar *id = NULL; id = fu_plugin_thunderbolt_gen_id (device); dev = fu_plugin_cache_lookup (plugin, id); if (dev == NULL) return; /* on supported systems other plugins may use a GPIO to force * power on supported devices even when in low power mode -- * this will happen in coldplug_prepare and prepare_for_update */ if (fu_plugin_thunderbolt_is_host (device) && !fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) && fu_device_get_metadata_boolean (dev, FU_DEVICE_METADATA_TBT_CAN_FORCE_POWER)) { g_debug ("ignoring remove event as force powered"); return; } fu_plugin_device_remove (plugin, dev); fu_plugin_cache_remove (plugin, id); } static void fu_plugin_thunderbolt_change (FuPlugin *plugin, GUdevDevice *device) { FuDevice *dev; g_autofree gchar *version = NULL; g_autofree gchar *id = NULL; id = fu_plugin_thunderbolt_gen_id (device); dev = fu_plugin_cache_lookup (plugin, id); if (dev == NULL) { g_warning ("got change event for unknown device, adding instead"); fu_plugin_thunderbolt_add (plugin, device); return; } version = fu_plugin_thunderbolt_udev_get_version (device); fu_device_set_version (dev, version, FWUPD_VERSION_FORMAT_PAIR); } static gboolean udev_uevent_cb (GUdevClient *udev, const gchar *action, GUdevDevice *device, gpointer user_data) { FuPlugin *plugin = (FuPlugin *) user_data; if (action == NULL) return TRUE; g_debug ("uevent for %s: %s", g_udev_device_get_sysfs_path (device), action); if (g_str_equal (action, "add")) { fu_plugin_thunderbolt_add (plugin, device); } else if (g_str_equal (action, "remove")) { fu_plugin_thunderbolt_remove (plugin, device); } else if (g_str_equal (action, "change")) { fu_plugin_thunderbolt_change (plugin, device); } return TRUE; } static FuPluginValidation fu_plugin_thunderbolt_validate_firmware (GUdevDevice *udevice, GBytes *blob_fw, GError **error) { g_autoptr(GFile) nvmem = NULL; g_autoptr(GBytes) controller_fw = NULL; gchar *content; gsize length; nvmem = fu_plugin_thunderbolt_find_nvmem (udevice, TRUE, error); if (nvmem == NULL) return VALIDATION_FAILED; if (!g_file_load_contents (nvmem, NULL, &content, &length, NULL, error)) return VALIDATION_FAILED; controller_fw = g_bytes_new_take (content, length); return fu_thunderbolt_image_validate (controller_fw, blob_fw, error); } static gboolean fu_plugin_thunderbolt_trigger_update (GUdevDevice *udevice, GError **error) { const gchar *devpath; ssize_t n; int fd; int r; g_autofree gchar *auth_path = NULL; devpath = g_udev_device_get_sysfs_path (udevice); auth_path = g_build_filename (devpath, "nvm_authenticate", NULL); fd = open (auth_path, O_WRONLY | O_CLOEXEC); if (fd < 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not open 'nvm_authenticate': %s", g_strerror (errno)); return FALSE; } do { n = write (fd, "1", 1); if (n < 1 && errno != EINTR) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not write to 'nvm_authenticate': %s", g_strerror (errno)); (void) close (fd); return FALSE; } } while (n < 1); r = close (fd); if (r < 0 && errno != EINTR) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not close 'nvm_authenticate': %s", g_strerror (errno)); return FALSE; } return TRUE; } static gboolean fu_plugin_thunderbolt_write_firmware (FuDevice *device, GUdevDevice *udevice, GBytes *blob_fw, GError **error) { gsize fw_size; gsize nwritten; gssize n; g_autoptr(GFile) nvmem = NULL; g_autoptr(GOutputStream) os = NULL; nvmem = fu_plugin_thunderbolt_find_nvmem (udevice, FALSE, error); if (nvmem == NULL) return FALSE; os = (GOutputStream *) g_file_append_to (nvmem, G_FILE_CREATE_NONE, NULL, error); if (os == NULL) return FALSE; nwritten = 0; fw_size = g_bytes_get_size (blob_fw); fu_device_set_progress_full (device, nwritten, fw_size); do { g_autoptr(GBytes) fw_data = NULL; fw_data = g_bytes_new_from_bytes (blob_fw, nwritten, fw_size - nwritten); n = g_output_stream_write_bytes (os, fw_data, NULL, error); if (n < 0) return FALSE; nwritten += n; fu_device_set_progress_full (device, nwritten, fw_size); } while (nwritten < fw_size); if (nwritten != fw_size) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "Could not write all data to nvmem"); return FALSE; } return g_output_stream_close (os, NULL, error); } /* virtual functions */ void fu_plugin_init (FuPlugin *plugin) { FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); const gchar *subsystems[] = { "thunderbolt", NULL }; fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.intel.thunderbolt"); data->udev = g_udev_client_new (subsystems); g_signal_connect (data->udev, "uevent", G_CALLBACK (udev_uevent_cb), plugin); /* dell-dock plugin uses a slower bus for flashing */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_BETTER_THAN, "dell_dock"); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); g_object_unref (data->udev); } static gboolean fu_plugin_thunderbolt_coldplug (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); GList *devices; devices = g_udev_client_query_by_subsystem (data->udev, "thunderbolt"); for (GList *l = devices; l != NULL; l = l->next) { GUdevDevice *device = l->data; fu_plugin_thunderbolt_add (plugin, device); } g_list_foreach (devices, (GFunc) g_object_unref, NULL); g_list_free (devices); return TRUE; } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { return fu_plugin_thunderbolt_coldplug (plugin, error); } gboolean fu_plugin_recoldplug (FuPlugin *plugin, GError **error) { return fu_plugin_thunderbolt_coldplug (plugin, error); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); const gchar *devpath; g_autoptr(GUdevDevice) udevice = NULL; g_autoptr(GError) error_local = NULL; gboolean install_force = (flags & FWUPD_INSTALL_FLAG_FORCE) != 0; gboolean device_ignore_validation = fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_IGNORE_VALIDATION); FuPluginValidation validation; devpath = fu_device_get_metadata (dev, "sysfs-path"); g_return_val_if_fail (devpath, FALSE); udevice = g_udev_client_query_by_sysfs_path (data->udev, devpath); if (udevice == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "could not find thunderbolt device at %s", devpath); return FALSE; } validation = fu_plugin_thunderbolt_validate_firmware (udevice, blob_fw, &error_local); if (validation != VALIDATION_PASSED) { g_autofree gchar* msg = NULL; switch (validation) { case VALIDATION_FAILED: msg = g_strdup_printf ("could not validate firmware: %s", error_local->message); break; case UNKNOWN_DEVICE: msg = g_strdup ("firmware validation seems to be passed but the device is unknown"); break; default: break; } if (!install_force && !device_ignore_validation) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "%s. " "See https://github.com/hughsie/fwupd/wiki/Thunderbolt:-Validation-failed-or-unknown-device for more information.", msg); return FALSE; } g_warning ("%s", msg); } fu_device_set_status (dev, FWUPD_STATUS_DEVICE_WRITE); if (!fu_plugin_thunderbolt_write_firmware (dev, udevice, blob_fw, error)) { g_prefix_error (error, "could not write firmware to thunderbolt device at %s: ", devpath); return FALSE; } if (!fu_plugin_thunderbolt_trigger_update (udevice, error)) { g_prefix_error (error, "could not start thunderbolt device upgrade: "); return FALSE; } fu_device_set_status (dev, FWUPD_STATUS_DEVICE_RESTART); fu_device_set_remove_delay (dev, FU_PLUGIN_THUNDERBOLT_UPDATE_TIMEOUT); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *dev, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); const gchar *devpath; const gchar *attribute; guint64 status; g_autoptr(GUdevDevice) udevice = NULL; devpath = fu_device_get_metadata (dev, "sysfs-path"); udevice = g_udev_client_query_by_sysfs_path (data->udev, devpath); if (udevice == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "could not find thunderbolt device at %s", devpath); return FALSE; } /* now check if the update actually worked */ attribute = g_udev_device_get_sysfs_attr (udevice, "nvm_authenticate"); if (attribute == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to find nvm_authenticate attribute for %s", fu_device_get_name (dev)); return FALSE; } status = g_ascii_strtoull (attribute, NULL, 16); if (status == G_MAXUINT64 && errno == ERANGE) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "failed to read 'nvm_authenticate: %s", g_strerror (errno)); return FALSE; } /* anything else then 0x0 means we got an error */ if (status != 0x0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "update failed (status %" G_GINT64_MODIFIER "x)", status); return FALSE; } return TRUE; } fwupd-1.2.14/plugins/thunderbolt/fu-self-test.c000066400000000000000000000770051402665037500214420ustar00rootroot00000000000000/* * Copyright (C) 2017 Christian J. Kellner * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fu-plugin-private.h" #include "fu-thunderbolt-image.h" #include "fu-test.h" static gchar * udev_mock_add_domain (UMockdevTestbed *bed, int id) { gchar *path; g_autofree gchar *name = NULL; name = g_strdup_printf ("domain%d", id); path = umockdev_testbed_add_device (bed, "thunderbolt", name, NULL, "security", "secure", NULL, "DEVTYPE", "thunderbolt_domain", NULL); g_assert_nonnull (path); return path; } static gchar * udev_mock_add_nvmem (UMockdevTestbed *bed, gboolean active, const char *parent, int id) { g_autofree gchar *name = NULL; gchar *path; name = g_strdup_printf ("%s%d", active ? "nvm_active" : "nvm_non_active", id); path = umockdev_testbed_add_device (bed, "nvmem", name, parent, "nvmem", "", NULL, NULL); g_assert_nonnull (path); return path; } typedef struct MockDevice MockDevice; struct MockDevice { const char *name; /* sysfs: device_name */ const char *id; /* sysfs: device */ const char *nvm_version; const char *nvm_parsed_version; int delay_ms; int domain_id; struct MockDevice *children; /* optionally filled out */ const char *uuid; }; typedef struct MockTree MockTree; struct MockTree { const MockDevice *device; MockTree *parent; GPtrArray *children; gchar *sysfs_parent; int sysfs_id; int sysfs_nvm_id; gchar *uuid; UMockdevTestbed *bed; gchar *path; gchar *nvm_non_active; gchar *nvm_active; guint nvm_authenticate; gchar *nvm_version; FuDevice *fu_device; }; static MockTree * mock_tree_new (MockTree *parent, MockDevice *device, int *id) { MockTree *node = g_slice_new0 (MockTree); int current_id = (*id)++; node->device = device; node->sysfs_id = current_id; node->sysfs_nvm_id = current_id; node->parent = parent; if (device->uuid) node->uuid = g_strdup (device->uuid); else node->uuid = g_uuid_string_random (); node->nvm_version = g_strdup (device->nvm_version); return node; } static void mock_tree_free (MockTree *tree) { for (guint i = 0; i < tree->children->len; i++) { MockTree *child = g_ptr_array_index (tree->children, i); mock_tree_free (child); } g_ptr_array_free (tree->children, TRUE); if (tree->fu_device) g_object_unref (tree->fu_device); g_free (tree->uuid); if (tree->bed != NULL) { if (tree->nvm_active) { umockdev_testbed_uevent (tree->bed, tree->nvm_active, "remove"); umockdev_testbed_remove_device (tree->bed, tree->nvm_active); } if (tree->nvm_non_active) { umockdev_testbed_uevent (tree->bed, tree->nvm_non_active, "remove"); umockdev_testbed_remove_device (tree->bed, tree->nvm_non_active); } if (tree->path) { umockdev_testbed_uevent (tree->bed, tree->path, "remove"); umockdev_testbed_remove_device (tree->bed, tree->path); } g_object_unref (tree->bed); } g_free (tree->nvm_version); g_free (tree->nvm_active); g_free (tree->nvm_non_active); g_free (tree->path); g_free (tree->sysfs_parent); g_slice_free (MockTree, tree); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC (MockTree, mock_tree_free); #pragma clang diagnostic pop static GPtrArray * mock_tree_init_children (MockTree *node, int *id) { GPtrArray *children = g_ptr_array_new (); MockDevice *iter; for (iter = node->device->children; iter && iter->name; iter++) { MockTree *child = mock_tree_new (node, iter, id); g_ptr_array_add (children, child); child->children = mock_tree_init_children (child, id); } return children; } static MockTree * mock_tree_init (MockDevice *device) { MockTree *tree; int devices = 0; tree = mock_tree_new (NULL, device, &devices); tree->children = mock_tree_init_children (tree, &devices); return tree; } static void mock_tree_dump (const MockTree *node, int level) { if (node->path) { g_debug ("%*s * %s [%s] at %s", level, " ", node->device->name, node->uuid, node->path); g_debug ("%*s non-active nvmem at %s", level, " ", node->nvm_non_active); g_debug ("%*s active nvmem at %s", level, " ", node->nvm_active); } else { g_debug ("%*s * %s [%s] %d", level, " ", node->device->name, node->uuid, node->sysfs_id); } for (guint i = 0; i < node->children->len; i++) { const MockTree *child = g_ptr_array_index (node->children, i); mock_tree_dump (child, level + 2); } } static void mock_tree_firmware_verify (const MockTree *node, GBytes *data) { g_autoptr(GFile) nvm_device = NULL; g_autoptr(GFile) nvm = NULL; g_autoptr(GInputStream) is = NULL; g_autoptr(GChecksum) chk = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *sum_data = NULL; const gchar *sum_disk = NULL; gsize s; sum_data = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, data); chk = g_checksum_new (G_CHECKSUM_SHA1); g_assert_nonnull (node); g_assert_nonnull (node->nvm_non_active); nvm_device = g_file_new_for_path (node->nvm_non_active); nvm = g_file_get_child (nvm_device, "nvmem"); is = (GInputStream *) g_file_read (nvm, NULL, &error); g_assert_no_error (error); g_assert_nonnull (is); do { g_autoptr(GBytes) b = NULL; const guchar *d; b = g_input_stream_read_bytes (is, 4096, NULL, &error); g_assert_no_error (error); g_assert_nonnull (is); d = g_bytes_get_data (b, &s); if (s > 0) g_checksum_update (chk, d, (gssize) s); } while (s > 0); sum_disk = g_checksum_get_string (chk); g_assert_cmpstr (sum_data, ==, sum_disk); } typedef gboolean (* MockTreePredicate) (const MockTree *node, gpointer data); static const MockTree * mock_tree_contains (const MockTree *node, MockTreePredicate predicate, gpointer data) { if (predicate (node, data)) return node; for (guint i = 0; i < node->children->len; i++) { const MockTree *child; const MockTree *match; child = g_ptr_array_index (node->children, i); match = mock_tree_contains (child, predicate, data); if (match != NULL) return match; } return NULL; } static gboolean mock_tree_all (const MockTree *node, MockTreePredicate predicate, gpointer data) { if (!predicate (node, data)) return FALSE; for (guint i = 0; i < node->children->len; i++) { const MockTree *child; child = g_ptr_array_index (node->children, i); if (!mock_tree_all (child, predicate, data)) return FALSE; } return TRUE; } static gboolean mock_tree_compare_uuid (const MockTree *node, gpointer data) { const gchar *uuid = (const gchar *) data; return g_str_equal (node->uuid, uuid); } static const MockTree * mock_tree_find_uuid (const MockTree *root, const char *uuid) { return mock_tree_contains (root, mock_tree_compare_uuid, (gpointer) uuid); } static gboolean mock_tree_node_have_fu_device (const MockTree *node, gpointer data) { return node->fu_device != NULL; } static void write_controller_fw (const gchar *nvm) { g_autoptr(GFile) nvm_device = NULL; g_autoptr(GFile) nvmem = NULL; g_autofree gchar *fw_path = NULL; g_autoptr(GFile) fw_file = NULL; g_autoptr(GInputStream) is = NULL; g_autoptr(GOutputStream) os = NULL; g_autoptr(GError) error = NULL; gssize n; fw_path = fu_test_get_filename (TESTDATADIR, "thunderbolt/minimal-fw-controller.bin"); g_assert_nonnull (fw_path); fw_file = g_file_new_for_path (fw_path); g_assert_nonnull (fw_file); nvm_device = g_file_new_for_path (nvm); g_assert_nonnull (nvm_device); nvmem = g_file_get_child (nvm_device, "nvmem"); g_assert_nonnull (nvmem); os = (GOutputStream *) g_file_append_to (nvmem, G_FILE_CREATE_NONE, NULL, &error); g_assert_no_error (error); g_assert_nonnull (os); is = (GInputStream *) g_file_read (fw_file, NULL, &error); g_assert_no_error (error); g_assert_nonnull (is); n = g_output_stream_splice (os, is, G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (n, >, 0); } static gboolean mock_tree_attach_device (gpointer user_data) { MockTree *tree = (MockTree *) user_data; const MockDevice *dev = tree->device; g_autofree gchar *idstr = NULL; g_autofree gchar *authenticate = NULL; g_assert_nonnull (tree); g_assert_nonnull (tree->sysfs_parent); g_assert_nonnull (dev); idstr = g_strdup_printf ("%d-%d", dev->domain_id, tree->sysfs_id); authenticate = g_strdup_printf ("0x%x", tree->nvm_authenticate); tree->path = umockdev_testbed_add_device (tree->bed, "thunderbolt", idstr, tree->sysfs_parent, "device_name", dev->name, "device", dev->id, "vendor", "042", "vendor_name", "GNOME.org", "authorized", "0", "nvm_authenticate", authenticate, "nvm_version", tree->nvm_version, "unique_id", tree->uuid, NULL, "DEVTYPE", "thunderbolt_device", NULL); tree->nvm_non_active = udev_mock_add_nvmem (tree->bed, FALSE, tree->path, tree->sysfs_id); tree->nvm_active = udev_mock_add_nvmem (tree->bed, TRUE, tree->path, tree->sysfs_id); g_assert_nonnull (tree->path); g_assert_nonnull (tree->nvm_non_active); g_assert_nonnull (tree->nvm_active); write_controller_fw (tree->nvm_active); for (guint i = 0; i < tree->children->len; i++) { MockTree *child; child = g_ptr_array_index (tree->children, i); child->bed = g_object_ref (tree->bed); child->sysfs_parent = g_strdup (tree->path); g_timeout_add (child->device->delay_ms, mock_tree_attach_device, child); } return FALSE; } typedef struct SyncContext { MockTree *tree; GMainLoop *loop; } SyncContext; static gboolean on_sync_timeout (gpointer user_data) { SyncContext *ctx = (SyncContext *) user_data; g_main_loop_quit (ctx->loop); return FALSE; } static void sync_device_added (FuPlugin *plugin, FuDevice *device, gpointer user_data) { SyncContext *ctx = (SyncContext *) user_data; MockTree *tree = ctx->tree; const gchar *uuid = fu_device_get_physical_id (device); MockTree *target; target = (MockTree *) mock_tree_find_uuid (tree, uuid); if (target == NULL) { g_critical ("Got device that could not be matched: %s", uuid); return; } if (target->fu_device != NULL) g_object_unref (target->fu_device); target->fu_device = g_object_ref (device); } static void sync_device_removed (FuPlugin *plugin, FuDevice *device, gpointer user_data) { SyncContext *ctx = (SyncContext *) user_data; MockTree *tree = ctx->tree; const gchar *uuid = fu_device_get_physical_id (device); MockTree *target; target = (MockTree *) mock_tree_find_uuid (tree, uuid); if (target == NULL) { g_warning ("Got device that could not be matched: %s", uuid); return; } else if (target->fu_device == NULL) { g_warning ("Got remove event for out-of-tree device %s", uuid); return; } g_object_unref (target->fu_device); target->fu_device = NULL; } static void mock_tree_sync (MockTree *root, FuPlugin *plugin, int timeout_ms) { g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); gulong id_add; gulong id_del; SyncContext ctx = { .tree = root, .loop = mainloop, }; id_add = g_signal_connect (plugin, "device-added", G_CALLBACK (sync_device_added), &ctx); id_del = g_signal_connect (plugin, "device-removed", G_CALLBACK (sync_device_removed), &ctx); if (timeout_ms > 0) g_timeout_add (timeout_ms, on_sync_timeout, &ctx); g_main_loop_run (mainloop); g_signal_handler_disconnect (plugin, id_add); g_signal_handler_disconnect (plugin, id_del); } typedef struct AttachContext { /* in */ MockTree *tree; GMainLoop *loop; /* out */ gboolean complete; } AttachContext; static void mock_tree_plugin_device_added (FuPlugin *plugin, FuDevice *device, gpointer user_data) { AttachContext *ctx = (AttachContext *) user_data; MockTree *tree = ctx->tree; const gchar *uuid = fu_device_get_physical_id (device); MockTree *target; target = (MockTree *) mock_tree_find_uuid (tree, uuid); if (target == NULL) { g_warning ("Got device that could not be matched: %s", uuid); return; } g_set_object (&target->fu_device, device); if (mock_tree_all (tree, mock_tree_node_have_fu_device, NULL)) { ctx->complete = TRUE; g_main_loop_quit (ctx->loop); } } static gboolean mock_tree_settle (MockTree *root, FuPlugin *plugin) { g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); gulong id; AttachContext ctx = { .tree = root, .loop = mainloop, }; id = g_signal_connect (plugin, "device-added", G_CALLBACK (mock_tree_plugin_device_added), &ctx); g_main_loop_run (mainloop); g_signal_handler_disconnect (plugin, id); return ctx.complete; } static gboolean mock_tree_attach (MockTree *root, UMockdevTestbed *bed, FuPlugin *plugin) { root->bed = g_object_ref (bed); root->sysfs_parent = udev_mock_add_domain (bed, root->device->domain_id); g_assert_nonnull (root->sysfs_parent); g_timeout_add (root->device->delay_ms, mock_tree_attach_device, root); return mock_tree_settle (root, plugin); } /* the unused parameter makes the function signature compatible * with 'MockTreePredicate' */ static gboolean mock_tree_node_is_detached (const MockTree *node, gpointer unused) { gboolean ret = node->path == NULL; /* consistency checks: if ret, make sure we are * fully detached */ if (ret) { g_assert_null (node->nvm_active); g_assert_null (node->nvm_non_active); g_assert_null (node->bed); } else { g_assert_nonnull (node->nvm_active); g_assert_nonnull (node->nvm_non_active); g_assert_nonnull (node->bed); } return ret; } static void mock_tree_detach (MockTree *node) { UMockdevTestbed *bed; if (mock_tree_node_is_detached (node, NULL)) return; for (guint i = 0; i < node->children->len; i++) { MockTree *child = g_ptr_array_index (node->children, i); mock_tree_detach (child); g_free (child->sysfs_parent); child->sysfs_parent = NULL; } bed = node->bed; umockdev_testbed_uevent (bed, node->nvm_active, "remove"); umockdev_testbed_remove_device (bed, node->nvm_active); umockdev_testbed_uevent (bed, node->nvm_non_active, "remove"); umockdev_testbed_remove_device (bed, node->nvm_non_active); umockdev_testbed_uevent (bed, node->path, "remove"); umockdev_testbed_remove_device (bed, node->path); g_free (node->path); g_free (node->nvm_non_active); g_free (node->nvm_active); node->path = NULL; node->nvm_non_active = NULL; node->nvm_active = NULL; g_object_unref (bed); node->bed = NULL; } typedef enum UpdateResult { UPDATE_SUCCESS = 0, /* nvm_authenticate will report error condition */ UPDATE_FAIL_DEVICE_INTERNAL = 1, /* device to be updated will NOT re-appear */ UPDATE_FAIL_DEVICE_NOSHOW = 2 } UpdateResult; typedef struct UpdateContext { GFileMonitor *monitor; UpdateResult result; guint timeout; GBytes *data; UMockdevTestbed *bed; FuPlugin *plugin; MockTree *node; gchar *version; } UpdateContext; static void update_context_free (UpdateContext *ctx) { if (ctx == NULL) return; g_object_unref (ctx->bed); g_object_unref (ctx->plugin); g_object_unref (ctx->monitor); g_bytes_unref (ctx->data); g_free (ctx->version); g_free (ctx); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC (UpdateContext, update_context_free); #pragma clang diagnostic pop static gboolean reattach_tree (gpointer user_data) { UpdateContext *ctx = (UpdateContext *) user_data; MockTree *node = ctx->node; g_debug ("Mock update done, reattaching tree..."); node->bed = g_object_ref (ctx->bed); g_timeout_add (node->device->delay_ms, mock_tree_attach_device, node); return FALSE; } static void udev_file_changed_cb (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { UpdateContext *ctx = (UpdateContext *) user_data; gboolean ok; gsize len; g_autofree gchar *data = NULL; g_autoptr(GError) error = NULL; g_debug ("Got update trigger"); ok = g_file_monitor_cancel (monitor); g_assert_true (ok); ok = g_file_load_contents (file, NULL, &data, &len, NULL, &error); g_assert_no_error (error); g_assert_true (ok); if (!g_str_has_prefix (data, "1")) return; /* verify the firmware is correct */ mock_tree_firmware_verify (ctx->node, ctx->data); g_debug ("Removing tree below and including: %s", ctx->node->path); mock_tree_detach (ctx->node); ctx->node->nvm_authenticate = (guint) ctx->result; /* update the version only on "success" simulations */ if (ctx->result == UPDATE_SUCCESS) { g_free (ctx->node->nvm_version); ctx->node->nvm_version = g_strdup (ctx->version); } g_debug ("Simulating update to '%s' with result: 0x%x", ctx->version, ctx->node->nvm_authenticate); if (ctx->result == UPDATE_FAIL_DEVICE_NOSHOW) { g_debug ("Simulating no-show fail:" " device tree will not reappear"); return; } g_debug ("Device tree reattachment in %3.2f seconds", ctx->timeout / 1000.0); g_timeout_add (ctx->timeout, reattach_tree, ctx); } static UpdateContext * mock_tree_prepare_for_update (MockTree *node, FuPlugin *plugin, const char *version, GBytes *fw_data, guint timeout_ms) { UpdateContext *ctx; g_autoptr(GFile) dir = NULL; g_autoptr(GFile) f = NULL; g_autoptr(GError) error = NULL; GFileMonitor *monitor; ctx = g_new0 (UpdateContext, 1); dir = g_file_new_for_path (node->path); f = g_file_get_child (dir, "nvm_authenticate"); monitor = g_file_monitor_file (f, G_FILE_MONITOR_NONE, NULL, &error); g_assert_no_error (error); g_assert_nonnull (monitor); ctx->node = node; ctx->plugin = g_object_ref (plugin); ctx->bed = g_object_ref (node->bed); ctx->timeout = timeout_ms; ctx->monitor = monitor; ctx->version = g_strdup (version); ctx->data = g_bytes_ref (fw_data); g_signal_connect (monitor, "changed", G_CALLBACK (udev_file_changed_cb), ctx); return ctx; } static MockDevice root_one = { .name = "Laptop", .id = "0x23", .nvm_version = "20.2", .nvm_parsed_version = "20.02", .children = (MockDevice[]) { { .name = "Thunderbolt Cable", .id = "0x24", .nvm_version = "20.0", .nvm_parsed_version = "20.00", .children = (MockDevice[]) { { .name = "Thunderbolt Dock", .id = "0x25", .nvm_version = "10.0", .nvm_parsed_version = "10.00", }, { NULL, } }, }, { .name = "Thunderbolt Cable", .id = "0x24", .nvm_version = "23.0", .nvm_parsed_version = "23.00", .children = (MockDevice[]) { { .name = "Thunderbolt SSD", .id = "0x26", .nvm_version = "5.0", .nvm_parsed_version = "05.00", }, { NULL, } }, }, { NULL, }, }, }; typedef struct TestParam { gboolean initialize_tree; gboolean attach_and_coldplug; const char *firmware_file; } TestParam; typedef enum TestFlags { TEST_INITIALIZE_TREE = 1 << 0, TEST_ATTACH_AND_COLDPLUG = 1 << 1, TEST_PREPARE_FIRMWARE = 1 << 2, TEST_PREPARE_ALL = TEST_INITIALIZE_TREE | TEST_ATTACH_AND_COLDPLUG | TEST_PREPARE_FIRMWARE } TestFlags; #define TEST_INIT_FULL (GUINT_TO_POINTER (TEST_PREPARE_ALL)) #define TEST_INIT_NONE (GUINT_TO_POINTER (0)) typedef struct ThunderboltTest { UMockdevTestbed *bed; FuPlugin *plugin; /* if TestParam::initialize_tree */ MockTree *tree; /* if TestParam::firmware_file is nonnull */ GMappedFile *fw_file; GBytes *fw_data; } ThunderboltTest; static void test_set_up (ThunderboltTest *tt, gconstpointer params) { TestFlags flags = GPOINTER_TO_UINT(params); gboolean ret; g_autofree gchar *sysfs = NULL; g_autoptr(GError) error = NULL; tt->bed = umockdev_testbed_new (); g_assert_nonnull (tt->bed); sysfs = umockdev_testbed_get_sys_dir (tt->bed); g_debug ("mock sysfs at %s", sysfs); tt->plugin = fu_plugin_new (); g_assert_nonnull (tt->plugin); ret = fu_plugin_open (tt->plugin, PLUGINBUILDDIR "/libfu_plugin_thunderbolt.so", &error); g_assert_no_error (error); g_assert_true (ret); ret = fu_plugin_runner_startup (tt->plugin, &error); g_assert_no_error (error); g_assert_true (ret); if (flags & TEST_INITIALIZE_TREE) { tt->tree = mock_tree_init (&root_one); g_assert_nonnull (tt->tree); } if (!umockdev_in_mock_environment ()) { g_warning ("Need to run with umockdev-wrapper"); return; } if (flags & TEST_ATTACH_AND_COLDPLUG) { g_assert_true (flags & TEST_INITIALIZE_TREE); ret = fu_plugin_runner_coldplug (tt->plugin, &error); g_assert_no_error (error); g_assert_true (ret); ret = mock_tree_attach (tt->tree, tt->bed, tt->plugin); g_assert_true (ret); } if (flags & TEST_PREPARE_FIRMWARE) { g_autofree gchar *fw_path = NULL; fw_path = fu_test_get_filename (TESTDATADIR, "thunderbolt/minimal-fw.bin"); g_assert_nonnull (fw_path); tt->fw_file = g_mapped_file_new (fw_path, FALSE, &error); g_assert_no_error (error); g_assert_nonnull (tt->fw_file); tt->fw_data = g_mapped_file_get_bytes (tt->fw_file); g_assert_nonnull (tt->fw_data); } } static void test_tear_down (ThunderboltTest *tt, gconstpointer user_data) { g_object_unref (tt->plugin); g_object_unref (tt->bed); if (tt->tree) mock_tree_free (tt->tree); if (tt->fw_data) g_bytes_unref (tt->fw_data); if (tt->fw_file) g_mapped_file_unref (tt->fw_file); } static gboolean test_tree_uuids (const MockTree *node, gpointer data) { const MockTree *root = (MockTree *) data; const gchar *uuid = node->uuid; const MockTree *found; g_assert_nonnull (uuid); g_debug ("Looking for %s", uuid); found = mock_tree_find_uuid (root, uuid); g_assert_nonnull (node); g_assert_nonnull (found); g_assert_cmpstr (node->uuid, ==, found->uuid); /* return false so we traverse the whole tree */ return FALSE; } static void test_tree (ThunderboltTest *tt, gconstpointer user_data) { const MockTree *found; gboolean ret; g_autoptr(MockTree) tree = NULL; g_autoptr(GError) error = NULL; tree = mock_tree_init (&root_one); g_assert_nonnull (tree); mock_tree_dump (tree, 0); (void) mock_tree_contains (tree, test_tree_uuids, tree); found = mock_tree_find_uuid (tree, "nonexistentuuid"); g_assert_null (found); ret = fu_plugin_runner_coldplug (tt->plugin, &error); g_assert_no_error (error); g_assert_true (ret); ret = mock_tree_attach (tree, tt->bed, tt->plugin); g_assert_true (ret); mock_tree_detach (tree); ret = mock_tree_all (tree, mock_tree_node_is_detached, NULL); g_assert_true (ret); } static void test_image_validation (ThunderboltTest *tt, gconstpointer user_data) { FuPluginValidation val; g_autofree gchar *ctl_path = NULL; g_autofree gchar *fwi_path = NULL; g_autofree gchar *bad_path = NULL; g_autoptr(GMappedFile) fwi_file = NULL; g_autoptr(GMappedFile) ctl_file = NULL; g_autoptr(GMappedFile) bad_file = NULL; g_autoptr(GBytes) fwi_data = NULL; g_autoptr(GBytes) ctl_data = NULL; g_autoptr(GBytes) bad_data = NULL; g_autoptr(GError) error = NULL; /* image as if read from the controller (i.e. no headers) */ ctl_path = fu_test_get_filename (TESTDATADIR, "thunderbolt/minimal-fw-controller.bin"); g_assert_nonnull (ctl_path); ctl_file = g_mapped_file_new (ctl_path, FALSE, &error); g_assert_no_error (error); g_assert_nonnull (ctl_file); ctl_data = g_mapped_file_get_bytes (ctl_file); g_assert_nonnull (ctl_data); /* valid firmware update image */ fwi_path = fu_test_get_filename (TESTDATADIR, "thunderbolt/minimal-fw.bin"); g_assert_nonnull (fwi_path); fwi_file = g_mapped_file_new (fwi_path, FALSE, &error); g_assert_no_error (error); g_assert_nonnull (fwi_file); fwi_data = g_mapped_file_get_bytes (fwi_file); g_assert_nonnull (fwi_data); /* a wrong/bad firmware update image */ bad_path = fu_test_get_filename (TESTDATADIR, "colorhug/firmware.bin"); g_assert_nonnull (bad_path); bad_file = g_mapped_file_new (bad_path, FALSE, &error); g_assert_no_error (error); g_assert_nonnull (bad_file); bad_data = g_mapped_file_get_bytes (bad_file); g_assert_nonnull (bad_data); /* now for some testing ... this should work */ val = fu_thunderbolt_image_validate (ctl_data, fwi_data, &error); g_assert_no_error (error); g_assert_cmpint (val, ==, VALIDATION_PASSED); /* these all should fail */ /* valid controller, bad update data */ val = fu_thunderbolt_image_validate (ctl_data, ctl_data, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_READ); g_assert_cmpint (val, ==, VALIDATION_FAILED); g_debug ("expected image validation error [ctl, ctl]: %s", error->message); g_clear_error (&error); val = fu_thunderbolt_image_validate (ctl_data, bad_data, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_READ); g_assert_cmpint (val, ==, VALIDATION_FAILED); g_debug ("expected image validation error [ctl, bad]: %s", error->message); g_clear_error (&error); /* bad controller data, valid update data */ val = fu_thunderbolt_image_validate (fwi_data, fwi_data, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_cmpint (val, ==, VALIDATION_FAILED); g_debug ("expected image validation error [fwi, fwi]: %s", error->message); g_clear_error (&error); val = fu_thunderbolt_image_validate (bad_data, fwi_data, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_cmpint (val, ==, VALIDATION_FAILED); g_debug ("expected image validation error [bad, fwi]: %s", error->message); g_clear_error (&error); /* both bad */ val = fu_thunderbolt_image_validate (bad_data, bad_data, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_READ); g_assert_cmpint (val, ==, VALIDATION_FAILED); g_debug ("expected image validation error [bad, bad]: %s", error->message); g_clear_error (&error); } static void test_change_uevent (ThunderboltTest *tt, gconstpointer user_data) { FuPlugin *plugin = tt->plugin; MockTree *tree = tt->tree; gboolean ret; const gchar *version_after; /* test sanity check */ g_assert_nonnull (tree); /* simulate change of version via a change even, i.e. * without add, remove. */ umockdev_testbed_set_attribute (tt->bed, tree->path, "nvm_version", "42.23"); umockdev_testbed_uevent (tt->bed, tree->path, "change"); /* we just "wait" for 500ms, should be enough */ mock_tree_sync (tree, plugin, 500); /* the tree should not have changed */ ret = mock_tree_all (tree, mock_tree_node_have_fu_device, NULL); g_assert_true (ret); /* we should have the version change in the FuDevice */ version_after = fu_device_get_version (tree->fu_device); g_assert_cmpstr (version_after, ==, "42.23"); } static void test_update_working (ThunderboltTest *tt, gconstpointer user_data) { FuPlugin *plugin = tt->plugin; MockTree *tree = tt->tree; GBytes *fw_data = tt->fw_data; gboolean ret; const gchar *version_after; g_autoptr(GError) error = NULL; g_autoptr(UpdateContext) up_ctx = NULL; /* test sanity check */ g_assert_nonnull (tree); g_assert_nonnull (fw_data); /* simulate an update, where the device goes away and comes back * after the time in the last parameter (given in ms) */ up_ctx = mock_tree_prepare_for_update (tree, plugin, "42.23", fw_data, 1000); ret = fu_plugin_runner_update (plugin, tree->fu_device, fw_data, 0, &error); g_assert_no_error (error); g_assert_true (ret); /* we wait until the plugin has picked up all the * subtree changes */ ret = mock_tree_settle (tree, plugin); g_assert_true (ret); ret = fu_plugin_runner_update_attach (plugin, tree->fu_device, &error); g_assert_no_error (error); g_assert_true (ret); version_after = fu_device_get_version (tree->fu_device); g_debug ("version after update: %s", version_after); g_assert_cmpstr (version_after, ==, "42.23"); /* make sure all pending events have happened */ ret = mock_tree_settle (tree, plugin); g_assert_true (ret); /* now we check if the every tree node has a corresponding FuDevice, * this implicitly checks that we are handling uevents correctly * after the event, and that we are in sync with the udev tree */ ret = mock_tree_all (tree, mock_tree_node_have_fu_device, NULL); g_assert_true (ret); } static void test_update_fail (ThunderboltTest *tt, gconstpointer user_data) { FuPlugin *plugin = tt->plugin; MockTree *tree = tt->tree; GBytes *fw_data = tt->fw_data; gboolean ret; const gchar *version_after; g_autoptr(GError) error = NULL; g_autoptr(UpdateContext) up_ctx = NULL; /* test sanity check */ g_assert_nonnull (tree); g_assert_nonnull (fw_data); /* simulate an update, as in test_update_working, * but simulate an error indicated by the device */ up_ctx = mock_tree_prepare_for_update (tree, plugin, "42.23", fw_data, 1000); up_ctx->result = UPDATE_FAIL_DEVICE_INTERNAL; ret = fu_plugin_runner_update (plugin, tree->fu_device, fw_data, 0, &error); g_assert_no_error (error); g_assert_true (ret); /* we wait until the plugin has picked up all the * subtree changes, and make sure we still receive * udev updates correctly and are in sync */ ret = mock_tree_settle (tree, plugin); g_assert_true (ret); ret = fu_plugin_runner_update_attach (plugin, tree->fu_device, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL); g_assert_false (ret); /* make sure all pending events have happened */ ret = mock_tree_settle (tree, plugin); g_assert_true (ret); /* version should *not* have changed (but we get parsed version) */ version_after = fu_device_get_version (tree->fu_device); g_debug ("version after update: %s", version_after); g_assert_cmpstr (version_after, ==, tree->device->nvm_parsed_version); ret = mock_tree_all (tree, mock_tree_node_have_fu_device, NULL); g_assert_true (ret); } static void test_update_fail_nowshow (ThunderboltTest *tt, gconstpointer user_data) { FuPlugin *plugin = tt->plugin; MockTree *tree = tt->tree; GBytes *fw_data = tt->fw_data; gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(UpdateContext) up_ctx = NULL; /* test sanity check */ g_assert_nonnull (tree); g_assert_nonnull (fw_data); /* simulate an update, as in test_update_working, * but simulate an error indicated by the device */ up_ctx = mock_tree_prepare_for_update (tree, plugin, "42.23", fw_data, 1000); up_ctx->result = UPDATE_FAIL_DEVICE_NOSHOW; ret = fu_plugin_runner_update (plugin, tree->fu_device, fw_data, 0, &error); g_assert_no_error (error); g_assert_true (ret); mock_tree_sync (tree, plugin, 500); ret = mock_tree_all (tree, mock_tree_node_have_fu_device, NULL); g_assert_false (ret); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_test_add ("/thunderbolt/basic", ThunderboltTest, NULL, test_set_up, test_tree, test_tear_down); g_test_add ("/thunderbolt/image-validation", ThunderboltTest, TEST_INIT_NONE, test_set_up, test_image_validation, test_tear_down); g_test_add ("/thunderbolt/change-uevent", ThunderboltTest, GUINT_TO_POINTER (TEST_INITIALIZE_TREE | TEST_ATTACH_AND_COLDPLUG), test_set_up, test_change_uevent, test_tear_down); g_test_add ("/thunderbolt/update{working}", ThunderboltTest, TEST_INIT_FULL, test_set_up, test_update_working, test_tear_down); g_test_add ("/thunderbolt/update{failing}", ThunderboltTest, TEST_INIT_FULL, test_set_up, test_update_fail, test_tear_down); g_test_add ("/thunderbolt/update{failing-noshow}", ThunderboltTest, TEST_INIT_FULL, test_set_up, test_update_fail_nowshow, test_tear_down); return g_test_run (); } fwupd-1.2.14/plugins/thunderbolt/fu-thunderbolt-image.c000066400000000000000000000612341402665037500231430ustar00rootroot00000000000000/* * Copyright (C) 2017 Intel Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-thunderbolt-image.h" #include #include enum FuThunderboltSection { DIGITAL_SECTION, DROM_SECTION, ARC_PARAMS_SECTION, DRAM_UCODE_SECTION, SECTION_COUNT }; typedef struct { enum FuThunderboltSection section; /* default is DIGITAL_SECTION */ guint32 offset; guint32 len; guint8 mask; /* 0 means "no mask" */ const gchar *description; } FuThunderboltFwLocation; typedef struct { const guint8 *data; gsize len; guint32 *sections; } FuThunderboltFwObject; typedef struct { guint16 id; guint gen; guint ports; } FuThunderboltHwInfo; enum { DROM_ENTRY_MC = 0x6, }; static const FuThunderboltHwInfo * get_hw_info (guint16 id) { static const FuThunderboltHwInfo hw_info_arr[] = { { 0x156D, 2, 2 }, /* FR 4C */ { 0x156B, 2, 1 }, /* FR 2C */ { 0x157E, 2, 1 }, /* WR */ { 0x1578, 3, 2 }, /* AR 4C */ { 0x1576, 3, 1 }, /* AR 2C */ { 0x15C0, 3, 1 }, /* AR LP */ { 0x15D3, 3, 2 }, /* AR-C 4C */ { 0x15DA, 3, 1 }, /* AR-C 2C */ { 0x15E7, 3, 1 }, /* TR 2C */ { 0x15EA, 3, 2 }, /* TR 4C */ { 0x15EF, 3, 2 }, /* TR 4C device */ { 0 } }; for (gint i = 0; hw_info_arr[i].id != 0; i++) if (hw_info_arr[i].id == id) return hw_info_arr + i; return NULL; } static inline gboolean valid_farb_pointer (guint32 pointer) { return pointer != 0 && pointer != 0xFFFFFF; } static inline gboolean valid_pd_pointer (guint32 pointer) { return pointer != 0 && pointer != 0xFFFFFFFF; } /* returns NULL on error */ static GByteArray * read_location (const FuThunderboltFwLocation *location, const FuThunderboltFwObject *fw, GError **error) { guint32 location_start = fw->sections[location->section] + location->offset; g_autoptr(GByteArray) read = g_byte_array_new (); if (location_start > fw->len || location_start + location->len > fw->len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "Given location is outside of the given FW (%s)", location->description ? location->description : "N/A"); return NULL; } read = g_byte_array_append (read, fw->data + location_start, location->len); if (location->mask) read->data[0] &= location->mask; return g_steal_pointer (&read); } static gboolean read_farb_pointer_impl (const FuThunderboltFwLocation *location, const FuThunderboltFwObject *fw, guint32 *value, GError **error) { g_autoptr(GByteArray) farb = read_location (location, fw, error); if (farb == NULL) return FALSE; *value = 0; memcpy (value, farb->data, farb->len); *value = GUINT32_FROM_LE (*value); return TRUE; } /* returns invalid FARB pointer on error */ static guint32 read_farb_pointer (const FuThunderboltFwObject *fw, GError **error) { const FuThunderboltFwLocation farb0 = { .offset = 0, .len = 3, .description = "farb0" }; const FuThunderboltFwLocation farb1 = { .offset = 0x1000, .len = 3, .description = "farb1" }; guint32 value; if (!read_farb_pointer_impl (&farb0, fw, &value, error)) return 0; if (valid_farb_pointer (value)) return value; if (!read_farb_pointer_impl (&farb1, fw, &value, error)) return 0; if (!valid_farb_pointer (value)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Invalid FW image file format"); return 0; } return value; } static gboolean compare (const FuThunderboltFwLocation *location, const FuThunderboltFwObject *controller_fw, const FuThunderboltFwObject *image_fw, gboolean *result, GError **error) { g_autoptr(GByteArray) controller_data = NULL; g_autoptr(GByteArray) image_data = NULL; controller_data = read_location (location, controller_fw, error); if (controller_data == NULL) return FALSE; image_data = read_location (location, image_fw, error); if (image_data == NULL) return FALSE; *result = memcmp (controller_data->data, image_data->data, location->len) == 0; return TRUE; } static gboolean read_bool (const FuThunderboltFwLocation *location, const FuThunderboltFwObject *fw, gboolean *val, GError **error) { g_autoptr(GByteArray) read = read_location (location, fw, error); if (read == NULL) return FALSE; for (gsize i = 0; i < read->len; i++) if (read->data[i] != 0) { *val = TRUE; return TRUE; } *val = FALSE; return TRUE; } static gboolean read_uint16 (const FuThunderboltFwLocation *location, const FuThunderboltFwObject *fw, guint16 *value, GError **error) { g_autoptr(GByteArray) read = read_location (location, fw, error); g_assert_cmpuint (location->len, ==, sizeof (guint16)); if (read == NULL) return FALSE; *value = 0; memcpy (value, read->data, read->len); *value = GUINT16_FROM_LE (*value); return TRUE; } static gboolean read_uint32 (const FuThunderboltFwLocation *location, const FuThunderboltFwObject *fw, guint32 *value, GError **error) { g_autoptr(GByteArray) read = read_location (location, fw, error); g_assert_cmpuint (location->len, ==, sizeof (guint32)); if (read == NULL) return FALSE; *value = 0; memcpy (value, read->data, read->len); *value = GUINT32_FROM_LE (*value); return TRUE; } /* * Size of ucode sections is uint16 value saved at the start of the section, * it's in DWORDS (4-bytes) units and it doesn't include itself. We need the * offset to the next section, so we translate it to bytes and add 2 for the * size field itself. * * offset parameter must be relative to digital section */ static gboolean read_ucode_section_len (guint32 offset, const FuThunderboltFwObject *fw, guint16 *value, GError **error) { const FuThunderboltFwLocation section_size = { .offset = offset, .len = 2, .description = "size field" }; if (!read_uint16 (§ion_size, fw, value, error)) return FALSE; *value *= sizeof (guint32); *value += section_size.len; return TRUE; } /* * reads generic entries from DROM based on type field and fills * location to point to the entry data if found. Returns TRUE if there * was no error even if the entry was not found (location->offset is != 0 * when entry was found). */ static gboolean read_drom_entry_location (const FuThunderboltFwObject *fw, guint8 type, FuThunderboltFwLocation *location, GError **error) { const FuThunderboltFwLocation drom_len_loc = { .offset = 0x0E, .len = 2, .section = DROM_SECTION, .description = "DROM length" }; FuThunderboltFwLocation drom_entry_loc = { .len = 2, .section = DROM_SECTION, .description = "DROM generic entry" }; guint16 drom_size; if (!read_uint16 (&drom_len_loc, fw, &drom_size, error)) return FALSE; drom_size &= 0x0FFF; /* drom_size is size of DROM block except for identification * section and crc32 so add them here */ drom_size += 9 + 4; /* DROM entries start right after the identification section */ drom_entry_loc.offset = 9 + 4 + 9; do { g_autoptr(GByteArray) entry = NULL; guint8 entry_type; guint8 entry_length; entry = read_location (&drom_entry_loc, fw, error); if (entry == NULL) return FALSE; entry_length = entry->data[0]; entry_type = entry->data[1] & 0x3F; /* generic entry (port bit is not set) */ if ((entry->data[1] & (1 << 7)) == 0 && entry_type == type) { location->len = entry_length - 2; location->offset = drom_entry_loc.offset + 2; return TRUE; } drom_entry_loc.offset += entry_length; } while (drom_entry_loc.offset < drom_size); return TRUE; } /* * Takes a FwObject and fills its section array up * Assumes sections[DIGITAL_SECTION].offset is already set */ static gboolean read_sections (const FuThunderboltFwObject *fw, gboolean is_host, guint gen, GError **error) { const FuThunderboltFwLocation arc_params_offset = { .offset = 0x75, .len = 4, .description = "arc params offset" }; const FuThunderboltFwLocation drom_offset = { .offset = 0x10E, .len = 4, .description = "DROM offset" }; guint32 offset; if (gen >= 3 || gen == 0) { if (!read_uint32 (&drom_offset, fw, &offset, error)) return FALSE; fw->sections[DROM_SECTION] = offset + fw->sections[DIGITAL_SECTION]; if (!read_uint32 (&arc_params_offset, fw, &offset, error)) return FALSE; fw->sections[ARC_PARAMS_SECTION] = offset + fw->sections[DIGITAL_SECTION]; } if (is_host && gen > 2) { /* * Algorithm: * To find the DRAM section, we have to jump from section to * section in a chain of sections. * available_sections location tells what sections exist at all * (with a flag per section). * ee_ucode_start_addr location tells the offset of the first * section in the list relatively to the digital section start. * After having the offset of the first section, we have a loop * over the section list. If the section exists, we read its * length (2 bytes at section start) and add it to current * offset to find the start of the next section. Otherwise, we * already have the next section offset... */ const unsigned DRAM_FLAG = 1 << 6; const FuThunderboltFwLocation available_sections_loc = { .offset = 0x2, .len = 1, .description = "sections" }; const FuThunderboltFwLocation ee_ucode_start_addr_loc = { .offset = 0x3, .len = 2, .description = "ucode start" }; guint16 ucode_offset; g_autoptr(GByteArray) available_sections = read_location (&available_sections_loc, fw, error); if (available_sections == NULL) return FALSE; if (!read_uint16 (&ee_ucode_start_addr_loc, fw, &ucode_offset, error)) return FALSE; offset = ucode_offset; if ((available_sections->data[0] & DRAM_FLAG) == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Can't find needed FW sections in the FW image file"); return FALSE; } for (unsigned u = 1; u < DRAM_FLAG; u <<= 1) if (u & available_sections->data[0]) { if (!read_ucode_section_len (offset, fw, &ucode_offset, error)) return FALSE; offset += ucode_offset; } fw->sections[DRAM_UCODE_SECTION] = offset + fw->sections[DIGITAL_SECTION]; } return TRUE; } static inline gboolean missing_needed_drom (const FuThunderboltFwObject *fw, gboolean is_host, guint gen) { if (fw->sections[DROM_SECTION] != 0) return FALSE; if (is_host && gen < 3) return FALSE; return TRUE; } /* * Controllers that can have 1 or 2 ports have additional locations to check in * the 2 ports case. To make this as generic as possible, both sets are stored * in the same array with an empty entry separating them. The 1 port case should * stop comparing at the separator and the 2 ports case should continue * iterating the array to compare the rest. */ static const FuThunderboltFwLocation * get_host_locations (guint16 id) { static const FuThunderboltFwLocation FR[] = { { .offset = 0x10, .len = 4, .description = "PCIe Settings" }, { .offset = 0x143, .len = 1, .description = "CIO-Port0_TX" }, { .offset = 0x153, .len = 1, .description = "CIO-Port0_RX" }, { .offset = 0x147, .len = 1, .description = "CIO-Port1_TX" }, { .offset = 0x157, .len = 1, .description = "CIO-Port1_RX" }, { .offset = 0x211, .len = 1, .description = "Snk0_0(DP-in)" }, { .offset = 0x215, .len = 1, .description = "Snk0_1(DP-in)" }, { .offset = 0x219, .len = 1, .description = "Snk0_2(DP-in)" }, { .offset = 0x21D, .len = 1, .description = "Snk0_3(DP-in)" }, { .offset = 0X2175, .len = 1, .description = "PA(DP-out)" }, { .offset = 0X2179, .len = 1, .description = "PB(DP-out)" }, { .offset = 0X217D, .len = 1, .description = "Src0(DP-out)", .mask = 0xAA }, { 0 }, { .offset = 0x14B, .len = 1, .description = "CIO-Port2_TX" }, { .offset = 0x15B, .len = 1, .description = "CIO-Port2_RX" }, { .offset = 0x14F, .len = 1, .description = "CIO-Port3_TX" }, { .offset = 0x15F, .len = 1, .description = "CIO-Port3_RX" }, { .offset = 0X11C3, .len = 1, .description = "Snk1_0(DP-in)" }, { .offset = 0X11C7, .len = 1, .description = "Snk1_1(DP-in)" }, { .offset = 0X11CB, .len = 1, .description = "Snk1_2(DP-in)" }, { .offset = 0X11CF, .len = 1, .description = "Snk1_3(DP-in)" }, { 0 } }; static const FuThunderboltFwLocation WR[] = { { .offset = 0x10, .len = 4, .description = "PCIe Settings" }, { .offset = 0x14F, .len = 1, .description = "CIO-Port0_TX" }, { .offset = 0x157, .len = 1, .description = "CIO-Port0_RX" }, { .offset = 0x153, .len = 1, .description = "CIO-Port1_TX" }, { .offset = 0x15B, .len = 1, .description = "CIO-Port1_RX" }, { .offset = 0x1F1, .len = 1, .description = "Snk0_0(DP-in)" }, { .offset = 0x1F5, .len = 1, .description = "Snk0_1(DP-in)" }, { .offset = 0x1F9, .len = 1, .description = "Snk0_2(DP-in)" }, { .offset = 0x1FD, .len = 1, .description = "Snk0_3(DP-in)" }, { .offset = 0X11A5, .len = 1, .description = "PA(DP-out)" }, { 0 } }; static const FuThunderboltFwLocation AR[] = { { .offset = 0x10, .len = 4, .description = "PCIe Settings" }, { .offset = 0x12, .len = 1, .description = "PA", .mask = 0xCC, .section = DRAM_UCODE_SECTION }, { .offset = 0x121, .len = 1, .description = "Snk0" }, { .offset = 0x129, .len = 1, .description = "Snk1" }, { .offset = 0x136, .len = 1, .description = "Src0", .mask = 0xF0 }, { .offset = 0xB6, .len = 1, .description = "PA/PB (USB2)", .mask = 0xC0 }, { .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 }, { .offset = 0x7B, .len = 1, .description = "Native", .mask = 0x20 }, { 0 }, { .offset = 0x13, .len = 1, .description = "PB", .mask = 0xCC, .section = DRAM_UCODE_SECTION }, { 0 } }; static const FuThunderboltFwLocation AR_LP[] = { { .offset = 0x10, .len = 4, .description = "PCIe Settings" }, { .offset = 0x12, .len = 1, .description = "PA", .mask = 0xCC, .section = DRAM_UCODE_SECTION }, { .offset = 0x13, .len = 1, .description = "PB", .mask = 0x44, .section = DRAM_UCODE_SECTION }, { .offset = 0x121, .len = 1, .description = "Snk0" }, { .offset = 0xB6, .len = 1, .description = "PA/PB (USB2)", .mask = 0xC0 }, { .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 }, { .offset = 0x7B, .len = 1, .description = "Native", .mask = 0x20 }, { 0 } }; static const FuThunderboltFwLocation TR[] = { { .offset = 0x10, .len = 4, .description = "PCIe Settings" }, { .offset = 0x12, .len = 1, .description = "PA", .mask = 0xCC, .section = DRAM_UCODE_SECTION }, { .offset = 0x121, .len = 1, .description = "Snk0" }, { .offset = 0x129, .len = 1, .description = "Snk1" }, { .offset = 0x136, .len = 1, .description = "Src0", .mask = 0xF0 }, { .offset = 0xB6, .len = 1, .description = "PA/PB (USB2)", .mask = 0xC0 }, { .offset = 0x5E, .len = 1, .description = "Aux", .mask = 0x0F }, { .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 }, { .offset = 0x7B, .len = 1, .description = "Native", .mask = 0x20 }, { 0 }, { .offset = 0x13, .len = 1, .description = "PB", .mask = 0xCC, .section = DRAM_UCODE_SECTION }, { .offset = 0x5E, .len = 1, .description = "Aux (PB)", .mask = 0x10 }, { 0 } }; switch (id) { case 0x156D: case 0x156B: return FR; case 0x157E: return WR; case 0x1578: case 0x1576: case 0x15D3: case 0x15DA: return AR; case 0x15C0: return AR_LP; case 0x15E7: case 0x15EA: return TR; default: return NULL; } } /* * Finds optional multi controller (MC) entry from controller DROM. * Returns TRUE if the controller did not have MC entry or the * controller and image MC entries match. In any other case FALSE is * returned and error is set accordingly. */ static gboolean compare_device_mc (const FuThunderboltFwObject *controller, const FuThunderboltFwObject *image, GError **error) { FuThunderboltFwLocation image_mc_loc = { .section = DROM_SECTION, .description = "Multi Controller" }; FuThunderboltFwLocation controller_mc_loc = image_mc_loc; g_autoptr(GByteArray) controller_mc = NULL; g_autoptr(GByteArray) image_mc = NULL; if (!read_drom_entry_location (controller, DROM_ENTRY_MC, &controller_mc_loc, error)) return FALSE; /* it is fine if the controller does not have MC entry */ if (controller_mc_loc.offset == 0) return TRUE; if (!read_drom_entry_location (image, DROM_ENTRY_MC, &image_mc_loc, error)) return FALSE; if (image_mc_loc.offset == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware does not have multi controller entry"); return FALSE; } if (controller_mc_loc.len != image_mc_loc.len) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware multi controller entry length mismatch"); return FALSE; } controller_mc = read_location (&controller_mc_loc, controller, error); if (controller_mc == NULL) return FALSE; image_mc = read_location (&image_mc_loc, image, error); if (image_mc == NULL) return FALSE; if (memcmp (controller_mc->data, image_mc->data, controller_mc->len) != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware multi controller entry mismatch"); return FALSE; } return TRUE; } static const FuThunderboltFwLocation * get_device_locations (guint16 id, const FuThunderboltFwObject *controller, const FuThunderboltFwObject *image, GError **error) { static const FuThunderboltFwLocation AR[] = { { .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 }, { .offset = 0x124, .len = 1, .section = ARC_PARAMS_SECTION, .description = "X of N" }, { 0 } }; static const FuThunderboltFwLocation TR[] = { { .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 }, { 0 } }; switch (id) { case 0x1578: case 0x1576: case 0x15D3: case 0x15DA: case 0x15C0: return AR; case 0x15E7: case 0x15EA: case 0x15EF: /* if the controller has multi controller entry need to * compare it against the image first. */ if (!compare_device_mc (controller, image, error)) return NULL; return TR; default: return NULL; } } /* * Compares the given locations, assuming locations is an array. * Returns FALSE and sets error upon failure. * locations points to the end of the array (the empty entry) upon * successful return. */ static gboolean compare_locations (const FuThunderboltFwLocation **locations, const FuThunderboltFwObject *controller, const FuThunderboltFwObject *image, GError **error) { gboolean result; do { if (!compare (*locations, controller, image, &result, error)) return FALSE; if (!result) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "FW image image not compatible to this controller (%s)", (*locations)->description); return FALSE; } } while ((++(*locations))->offset != 0); return TRUE; } static gboolean compare_pd_existence (guint16 id, const FuThunderboltFwObject *controller, const FuThunderboltFwObject *image, GError **error) { const FuThunderboltFwLocation pd_pointer_loc = { .offset = 0x10C, .len = 4, .section = ARC_PARAMS_SECTION, .description = "PD pointer" }; gboolean controller_has_pd; gboolean image_has_pd; guint32 pd_pointer; if (controller->sections[ARC_PARAMS_SECTION] == 0) return TRUE; if (!read_uint32 (&pd_pointer_loc, controller, &pd_pointer, error)) return FALSE; controller_has_pd = valid_pd_pointer (pd_pointer); if (!read_uint32 (&pd_pointer_loc, image, &pd_pointer, error)) return FALSE; image_has_pd = valid_pd_pointer (pd_pointer); if (controller_has_pd != image_has_pd) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "PD section mismatch"); return FALSE; } return TRUE; } FuPluginValidation fu_thunderbolt_image_validate (GBytes *controller_fw, GBytes *blob_fw, GError **error) { gboolean is_host; guint16 device_id; gboolean compare_result; const FuThunderboltHwInfo *hw_info; const FuThunderboltHwInfo unknown = { 0 }; const FuThunderboltFwLocation *locations; gsize fw_size; const guint8 *fw_data = g_bytes_get_data (controller_fw, &fw_size); gsize blob_size; const guint8 *blob_data = g_bytes_get_data (blob_fw, &blob_size); guint32 controller_sections[SECTION_COUNT] = { [DIGITAL_SECTION] = 0 }; guint32 image_sections [SECTION_COUNT] = { 0 }; const FuThunderboltFwObject controller = { fw_data, fw_size, controller_sections }; const FuThunderboltFwObject image = { blob_data, blob_size, image_sections }; const FuThunderboltFwLocation is_host_loc = { .offset = 0x10, .len = 1, .mask = 1 << 1, .description = "host flag" }; const FuThunderboltFwLocation device_id_loc = { .offset = 0x5, .len = 2, .description = "devID" }; image_sections[DIGITAL_SECTION] = read_farb_pointer (&image, error); if (image_sections[DIGITAL_SECTION] == 0) return VALIDATION_FAILED; if (!read_bool (&is_host_loc, &controller, &is_host, error)) return VALIDATION_FAILED; if (!read_uint16 (&device_id_loc, &controller, &device_id, error)) return VALIDATION_FAILED; hw_info = get_hw_info (device_id); if (hw_info == NULL) { if (is_host) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown controller"); return VALIDATION_FAILED; } hw_info = &unknown; } if (!compare (&is_host_loc, &controller, &image, &compare_result, error)) return VALIDATION_FAILED; if (!compare_result) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "The FW image file is for a %s controller", is_host ? "device" : "host"); return VALIDATION_FAILED; } if (!compare (&device_id_loc, &controller, &image, &compare_result, error)) return VALIDATION_FAILED; if (!compare_result) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "The FW image file is for a different HW type"); return VALIDATION_FAILED; } if (!read_sections (&controller, is_host, hw_info->gen, error)) return VALIDATION_FAILED; if (missing_needed_drom (&controller, is_host, hw_info->gen)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "Can't find needed FW sections in the controller"); return VALIDATION_FAILED; } if (!read_sections (&image, is_host, hw_info->gen, error)) return VALIDATION_FAILED; if (missing_needed_drom (&image, is_host, hw_info->gen)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Can't find needed FW sections in the FW image file"); return VALIDATION_FAILED; } if (controller.sections[DROM_SECTION] != 0) { const FuThunderboltFwLocation drom_locations[] = { { .offset = 0x10, .len = 2, .section = DROM_SECTION, .description = "vendor ID" }, { .offset = 0x12, .len = 2, .section = DROM_SECTION, .description = "model ID" }, { 0 } }; locations = drom_locations; if (!compare_locations (&locations, &controller, &image, error)) return VALIDATION_FAILED; } if (!compare_pd_existence (hw_info->id, &controller, &image, error)) return VALIDATION_FAILED; /* * 0 is for the unknown device case, for being future-compatible with * new devices; so we can't know which locations to check besides the * vendor and model IDs that were validated already, but those should be * good enough validation. */ if (hw_info->id == 0) return UNKNOWN_DEVICE; if (is_host) { locations = get_host_locations (hw_info->id); if (locations == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "FW locations to check not found for this controller"); return VALIDATION_FAILED; } } else { locations = get_device_locations (hw_info->id, &controller, &image, error); if (locations == NULL) { /* error is set already by the above */ return VALIDATION_FAILED; } } if (!compare_locations (&locations, &controller, &image, error)) return VALIDATION_FAILED; if (is_host && hw_info->ports == 2) { locations++; if (!compare_locations (&locations, &controller, &image, error)) return VALIDATION_FAILED; } return VALIDATION_PASSED; } gboolean fu_thunderbolt_image_controller_is_native (GBytes *controller_fw, gboolean *is_native, GError **error) { guint32 controller_sections[SECTION_COUNT] = { [DIGITAL_SECTION] = 0 }; gsize fw_size; const guint8 *fw_data = g_bytes_get_data (controller_fw, &fw_size); const FuThunderboltFwObject controller = { fw_data, fw_size, controller_sections }; const FuThunderboltFwLocation location = { .offset = FU_TBT_OFFSET_NATIVE, .len = 1, .description = "Native", .mask = 0x20 }; return read_bool (&location, &controller, is_native, error); } fwupd-1.2.14/plugins/thunderbolt/fu-thunderbolt-image.h000066400000000000000000000011141402665037500231370ustar00rootroot00000000000000/* * Copyright (C) 2017 Intel Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { VALIDATION_PASSED, VALIDATION_FAILED, UNKNOWN_DEVICE, } FuPluginValidation; /* byte offsets in firmware image */ #define FU_TBT_OFFSET_NATIVE 0x7B #define FU_TBT_CHUNK_SZ 0x40 FuPluginValidation fu_thunderbolt_image_validate (GBytes *controller_fw, GBytes *blob_fw, GError **error); gboolean fu_thunderbolt_image_controller_is_native (GBytes *controller_fw, gboolean *is_native, GError **error); fwupd-1.2.14/plugins/thunderbolt/fu-thunderbolt-tool.c000066400000000000000000000041561402665037500230360ustar00rootroot00000000000000/* * Copyright (C) 2017 Intel Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-thunderbolt-image.h" #include "fu-plugin-vfuncs.h" #include "fu-test.h" static gsize read_farb_pointer (gchar *image) { gsize ret = 0; memcpy (&ret, image, 3); ret = GSIZE_FROM_LE (ret); if (ret != 0 && ret != 0xFFFFFF) return ret; ret = 0; memcpy (&ret, image + 0x1000, 3); ret = GSIZE_FROM_LE (ret); g_assert (ret != 0 && ret != 0xFFFFFF); return ret; } int main (int argc, char **argv) { g_autoptr(GError) error = NULL; g_autoptr(GFile) fw_file = NULL; gboolean ok; gsize len; gchar *data = NULL; g_autoptr(GBytes) image = NULL; g_autoptr(GBytes) controller = NULL; FuPluginValidation validation; if (argc < 2 || argc > 3) { g_print ("Usage: %s []\n", argv[0]); g_print ("Runs image validation on 'filename', comparing it to itself\n" "after removing the headers or to 'controller' if given\n"); return 1; } fw_file = g_file_new_for_path (argv[1]); g_assert_nonnull (fw_file); ok = g_file_load_contents (fw_file, NULL, &data, &len, NULL, &error); g_assert_no_error (error); g_assert_true (ok); image = g_bytes_new_take (data, len); if (argc == 2) { gssize header_size = read_farb_pointer (data); g_assert_cmpuint (header_size, !=, 0); g_assert_cmpuint (header_size, <, len); controller = g_bytes_new_from_bytes (image, header_size, len - header_size); } else { g_autoptr(GFile) controller_file = NULL; gsize controller_len; gchar *controller_data = NULL; controller_file = g_file_new_for_path (argv[2]); g_assert_nonnull (controller_file); ok = g_file_load_contents (controller_file, NULL, &controller_data, &controller_len, NULL, &error); g_assert_no_error (error); g_assert_true (ok); controller = g_bytes_new_take (controller_data, controller_len); } validation = fu_thunderbolt_image_validate (controller, image, &error); g_assert_no_error (error); g_assert_cmpint (validation, ==, VALIDATION_PASSED); g_print ("test passed\n"); return 0; } fwupd-1.2.14/plugins/thunderbolt/meson.build000066400000000000000000000040621402665037500211130ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginThunderbolt"'] fu_plugin_thunderbolt = shared_module('fu_plugin_thunderbolt', fu_hash, sources : [ 'fu-plugin-thunderbolt.c', 'fu-thunderbolt-image.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) testdatadir_src = join_paths(meson.source_root(), 'data', 'tests') testdatadir_dst = join_paths(meson.build_root(), 'data', 'tests') cargs += '-DTESTDATADIR="' + testdatadir_src + ':' + testdatadir_dst + '"' executable('tbtfwucli', fu_hash, sources : [ 'fu-thunderbolt-tool.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], c_args : cargs, link_with : [ fu_plugin_thunderbolt, libfwupdprivate, ], dependencies : [ plugin_deps, ], ) # we use functions from 2.52 in the tests if get_option('tests') and umockdev.found() and gio.version().version_compare('>= 2.52') cargs += '-DFU_OFFLINE_DESTDIR="/tmp/fwupd-self-test"' cargs += '-DPLUGINBUILDDIR="' + meson.current_build_dir() + '"' e = executable( 'thunderbolt-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-plugin-thunderbolt.c', 'fu-thunderbolt-image.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ plugin_deps, umockdev, ], link_with : [ libfwupdprivate, ], c_args : cargs ) test_env = environment() if get_option('b_sanitize') == 'address' test_env.prepend('LD_PRELOAD', 'libasan.so.5', 'libumockdev-preload.so.0', separator : ' ') else test_env.prepend('LD_PRELOAD', 'libumockdev-preload.so.0') endif test('thunderbolt-self-test', e, env: test_env, timeout : 120) endif fwupd-1.2.14/plugins/udev/000077500000000000000000000000001402665037500153605ustar00rootroot00000000000000fwupd-1.2.14/plugins/udev/README.md000066400000000000000000000011121402665037500166320ustar00rootroot00000000000000UDev Support ============ Introduction ------------ This plugin reads the version numbers of PCI devices. It cannot deploy firmware onto devices but is used to list devices with known firmware updates that may require booting into another operating system to apply. This plugin is also able to read and parse the firmware of some PCI devices which allows some host state verification to be done. GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `PCI\VEN_%04X&DEV_%04X` Additionally, GUIDs found in OptionROMs may also be added. fwupd-1.2.14/plugins/udev/fu-plugin-udev.c000066400000000000000000000057111402665037500203770ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin.h" #include "fu-rom.h" #include "fu-plugin-vfuncs.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_udev_subsystem (plugin, "pci"); } gboolean fu_plugin_verify (FuPlugin *plugin, FuDevice *device, FuPluginVerifyFlags flags, GError **error) { GPtrArray *checksums; const gchar *rom_fn; g_autoptr(GFile) file = NULL; g_autoptr(FuRom) rom = NULL; /* open the file */ rom_fn = fu_device_get_metadata (device, "RomFilename"); if (rom_fn == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Unable to read firmware from device"); return FALSE; } file = g_file_new_for_path (rom_fn); rom = fu_rom_new (); if (!fu_rom_load_file (rom, file, FU_ROM_LOAD_FLAG_BLANK_PPID, NULL, error)) return FALSE; /* update version */ if (g_strcmp0 (fu_device_get_version (device), fu_rom_get_version (rom)) != 0) { g_debug ("changing version of %s from %s to %s", fu_device_get_id (device), fu_device_get_version (device), fu_rom_get_version (rom)); fu_device_set_version (device, fu_rom_get_version (rom), FWUPD_VERSION_FORMAT_UNKNOWN); } /* Also add the GUID from the firmware as the firmware may be more * generic, which also allows us to match the GUID when doing 'verify' * on a device with a different PID to the firmware */ fu_device_add_guid (device, fu_rom_get_guid (rom)); /* update checksums */ checksums = fu_rom_get_checksums (rom); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index (checksums, i); fu_device_add_checksum (device, checksum); } return TRUE; } gboolean fu_plugin_udev_device_added (FuPlugin *plugin, FuUdevDevice *device, GError **error) { GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device)); const gchar *guid = NULL; g_autofree gchar *rom_fn = NULL; /* interesting device? */ if (g_strcmp0 (fu_udev_device_get_subsystem (device), "pci") != 0) return TRUE; guid = g_udev_device_get_property (udev_device, "FWUPD_GUID"); if (guid == NULL) return TRUE; /* set the physical ID */ if (!fu_udev_device_set_physical_id (device, "pci", error)) return FALSE; /* did we get enough data */ fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon (FU_DEVICE (device), "audio-card"); /* get the FW version from the rom when unlocked */ rom_fn = g_build_filename (fu_udev_device_get_sysfs_path (device), "rom", NULL); if (g_file_test (rom_fn, G_FILE_TEST_EXISTS)) fu_device_set_metadata (FU_DEVICE (device), "RomFilename", rom_fn); /* we never open the device, so convert the instance IDs */ if (!fu_device_setup (FU_DEVICE (device), error)) return FALSE; /* insert to hash */ fu_plugin_device_add (plugin, FU_DEVICE (device)); return TRUE; } fwupd-1.2.14/plugins/udev/fu-rom-tool.c000066400000000000000000000141071402665037500177070ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fwupd-common-private.h" #include "fu-rom.h" #include "fu-common.h" static gboolean fu_fuzzer_rom_parse (const gchar *fn, GError **error) { GPtrArray *checksums; g_autoptr(FuRom) rom = NULL; g_autoptr(GFile) file = NULL; g_debug ("loading %s", fn); file = g_file_new_for_path (fn); rom = fu_rom_new (); if (!fu_rom_load_file (rom, file, FU_ROM_LOAD_FLAG_NONE, NULL, error)) return FALSE; g_print ("filename:%s\n", fn); g_print ("kind:%s\n", fu_rom_kind_to_string (fu_rom_get_kind (rom))); g_print ("version:%s\n", fu_rom_get_version (rom)); checksums = fu_rom_get_checksums (rom); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index (checksums, i); g_autofree gchar *checksum_display = NULL; checksum_display = fwupd_checksum_format_for_display (checksum); g_print ("checksum:%s\n", checksum_display); } g_print ("guid:%s\n", fu_rom_get_guid (rom)); g_print ("vendor:%u\n", fu_rom_get_vendor (rom)); g_print ("model:%u\n\n", fu_rom_get_model (rom)); return TRUE; } static gboolean fu_fuzzer_write_files (GHashTable *hash, GError **error) { GString *str; g_autoptr(GList) keys = g_hash_table_get_keys (hash); for (GList *l = keys; l != NULL; l = l->next) { g_autofree gchar *filename = NULL; const gchar *fn = l->data; filename = g_build_filename ("fuzzing", fn, NULL); str = g_hash_table_lookup (hash, fn); g_debug ("writing %s", fn); if (!g_file_set_contents (filename, str->str, str->len, error)) { g_prefix_error (error, "could not write file %s: ", filename); return FALSE; } } return TRUE; } static void _g_string_unref (GString *str) { g_string_free (str, TRUE); } static gboolean fu_fuzzer_rom_create (GError **error) { GString *str; guint8 *buffer; g_autofree guint8 *blob_header = NULL; g_autofree guint8 *blob_ifr = NULL; g_autoptr(GHashTable) hash = NULL; hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) _g_string_unref); /* 24 byte header */ blob_header = g_malloc0 (0x200); buffer = blob_header; memcpy (buffer, "\x55\xaa", 2); buffer[0x02] = 1; /* rom_len / 512 */ buffer[0x03] = 0x20; /* entry_point lo to blob just after header */ buffer[0x04] = 'K'; /* entry_point hi (NVIDIA) */ buffer[0x05] = '7'; /* entry_point higher (NVIDIA) */ memcpy (&buffer[0x6], "xxxxxxxxxxxxxxxxxx", 18); /* reserved */ buffer[0x18] = 0x20; /* cpi_ptr lo */ buffer[0x19] = 0x00; /* cpi_ptr hi */ memcpy (&blob_header[0x6], "hdr-no-data ", 18); g_hash_table_insert (hash, (gpointer) "header-no-data.rom", g_string_new_len ((gchar *) blob_header, 512)); /* data for header */ buffer = &blob_header[0x20]; memcpy (&buffer[0x00], "PCIR", 4); /* magic */ memcpy (&buffer[0x04], "\0\0", 2); /* vendor */ memcpy (&buffer[0x06], "\0\0", 2); /* device id */ memcpy (&buffer[0x08], "\0\0", 2); /* device_list_ptr */ buffer[0x0a] = 0x1c; /* data_len lo */ buffer[0x0b] = 0x00; /* data_len hi */ buffer[0x0c] = 0x0; /* data_rev */ memcpy (&buffer[0x0d], "\0\0\0", 3); /* class_code */ buffer[0x10] = 0x01; /* image_len lo / 512 */ buffer[0x11] = 0; /* image_len hi / 512 */ buffer[0x12] = 0; /* revision_level lo */ buffer[0x13] = 0; /* revision_level hi */ buffer[0x14] = 0x00; /* code_type, Intel x86 */ buffer[0x15] = 0x80; /* last_image */ buffer[0x16] = 0x0; /* max_runtime_len lo / 512 */ buffer[0x17] = 0x0; /* max_runtime_len hi / 512 */ buffer[0x18] = 0x00; /* config_header_ptr lo */ buffer[0x19] = 0x00; /* config_header_ptr hi */ buffer[0x1a] = 0x00; /* dmtf_clp_ptr lo (used for Intel FW) */ buffer[0x1b] = 0x00; /* dmtf_clp_ptr hi (used for Intel FW) */ blob_header[0x200-1] = 0x5c; /* checksum */ /* blob */ memcpy (&buffer[0x1c], "Version 1.0", 12); memcpy (&blob_header[0x6], "hdr-data-payload ", 18); g_hash_table_insert (hash, (gpointer) "header-data-payload.rom", g_string_new_len ((gchar *) blob_header, 512)); /* optional IFR header on some NVIDIA blobs */ blob_ifr = g_malloc0 (0x80); buffer = blob_ifr; memcpy (buffer, "NVGI", 4); fu_common_write_uint16 (&buffer[0x15], 0x80, G_BIG_ENDIAN); g_hash_table_insert (hash, (gpointer) "naked-ifr.rom", g_string_new_len ((const gchar *) blob_ifr, 0x80)); str = g_string_new_len ((gchar *) blob_ifr, 0x80); memcpy (&blob_header[0x6], (gpointer) "ifr-hdr-data-payld", 18); g_string_append_len (str, (gchar *) blob_header, 0x200); g_hash_table_insert (hash, (gpointer) "ifr-header-data-payload.rom", str); /* dump to files */ return fu_fuzzer_write_files (hash, error); } int main (int argc, char *argv[]) { gboolean verbose = FALSE; g_autoptr(GError) error_parse = NULL; g_autoptr(GOptionContext) context = NULL; const GOptionEntry options[] = { { "verbose", '\0', 0, G_OPTION_ARG_NONE, &verbose, "Run with debugging output enabled", NULL }, { NULL} }; context = g_option_context_new (NULL); g_option_context_add_main_entries (context, options, NULL); if (!g_option_context_parse (context, &argc, &argv, &error_parse)) { g_print ("failed to parse command line arguments: %s\n", error_parse->message); return EXIT_FAILURE; } if (argc < 3) { g_print ("Not enough arguments, expected 'rom' 'foo.rom'\n"); return EXIT_FAILURE; } if (verbose) g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); if (g_strcmp0 (argv[1], "rom") == 0) { gboolean all_successful = TRUE; for (guint i = 2; i < (guint) argc; i++) { g_autoptr(GError) error = NULL; if (!fu_fuzzer_rom_parse (argv[i], &error)) { g_print ("Failed to parse %s: %s\n", argv[i], error->message); all_successful = FALSE; } } return all_successful ? EXIT_SUCCESS : EXIT_FAILURE; } if (g_strcmp0 (argv[1], "create") == 0) { g_autoptr(GError) error = NULL; if (!fu_fuzzer_rom_create (&error)) { g_print ("Failed to create files: %s\n", error->message); return EXIT_FAILURE; } return EXIT_SUCCESS; } g_print ("Type not known: expected 'rom'\n"); return EXIT_FAILURE; } fwupd-1.2.14/plugins/udev/fu-rom.c000066400000000000000000000537461402665037500167500ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-rom.h" static void fu_rom_finalize (GObject *object); /* data from http://resources.infosecinstitute.com/pci-expansion-rom/ */ typedef struct { guint8 *rom_data; guint32 rom_len; guint32 rom_offset; guint32 entry_point; guint8 reserved[18]; guint16 cpi_ptr; guint16 vendor_id; guint16 device_id; guint16 device_list_ptr; guint16 data_len; guint8 data_rev; guint32 class_code; guint32 image_len; guint16 revision_level; guint8 code_type; guint8 last_image; guint32 max_runtime_len; guint16 config_header_ptr; guint16 dmtf_clp_ptr; } FuRomPciHeader; struct _FuRom { GObject parent_instance; GPtrArray *checksums; GInputStream *stream; FuRomKind kind; gchar *version; gchar *guid; guint16 vendor_id; guint16 device_id; GPtrArray *hdrs; /* of FuRomPciHeader */ }; G_DEFINE_TYPE (FuRom, fu_rom, G_TYPE_OBJECT) static void fu_rom_pci_header_free (FuRomPciHeader *hdr) { g_free (hdr->rom_data); g_free (hdr); } const gchar * fu_rom_kind_to_string (FuRomKind kind) { if (kind == FU_ROM_KIND_UNKNOWN) return "unknown"; if (kind == FU_ROM_KIND_ATI) return "ati"; if (kind == FU_ROM_KIND_NVIDIA) return "nvidia"; if (kind == FU_ROM_KIND_INTEL) return "intel"; if (kind == FU_ROM_KIND_PCI) return "pci"; return NULL; } static guint8 * fu_rom_pci_strstr (FuRomPciHeader *hdr, const gchar *needle) { gsize needle_len; guint8 *haystack; gsize haystack_len; if (needle == NULL || needle[0] == '\0') return NULL; if (hdr->rom_data == NULL) return NULL; if (hdr->data_len > hdr->rom_len) return NULL; haystack = &hdr->rom_data[hdr->data_len]; haystack_len = hdr->rom_len - hdr->data_len; needle_len = strlen (needle); if (needle_len > haystack_len) return NULL; for (guint i = 0; i < haystack_len - needle_len; i++) { if (memcmp (haystack + i, needle, needle_len) == 0) return &haystack[i]; } return NULL; } static guint fu_rom_blank_serial_numbers (guint8 *buffer, guint buffer_sz) { guint i; for (i = 0; i < buffer_sz; i++) { if (buffer[i] == 0xff || buffer[i] == '\0' || buffer[i] == '\n' || buffer[i] == '\r') break; buffer[i] = '\0'; } return i; } static gchar * fu_rom_get_hex_dump (guint8 *buffer, guint32 sz) { GString *str = g_string_new (""); for (guint32 i = 0; i < sz; i++) g_string_append_printf (str, "%02x ", buffer[i]); g_string_append (str, " "); for (guint32 i = 0; i < sz; i++) { gchar tmp = '?'; if (g_ascii_isprint (buffer[i])) tmp = (gchar) buffer[i]; g_string_append_printf (str, "%c", tmp); } return g_string_free (str, FALSE); } typedef struct { guint8 segment_kind; guint8 *data; guint16 data_len; guint16 next_offset; } FooRomPciCertificateHdr; static void fu_rom_pci_print_certificate_data (guint8 *buffer, gssize sz) { guint16 off = 0; g_autofree gchar *hdr_str = NULL; /* 27 byte header, unknown purpose */ hdr_str = fu_rom_get_hex_dump (buffer+off, 27); g_debug (" ISBN header: %s", hdr_str); buffer += 27; while (TRUE) { /* 29 byte header to the segment, then data: * 0x01 = type. 0x1 = certificate, 0x2 = hashes? * 0x13,0x14 = offset to next segment */ FooRomPciCertificateHdr h; g_autofree gchar *segment_str = NULL; segment_str = fu_rom_get_hex_dump (buffer+off, 29); g_debug (" ISBN segment @%02x: %s", off, segment_str); h.segment_kind = buffer[off+1]; h.next_offset = (guint16) (((guint16) buffer[off+14] << 8) + buffer[off+13]); h.data = &buffer[off+29]; /* calculate last block length automatically */ if (h.next_offset == 0) h.data_len = (guint16) (sz - off - 29 - 27); else h.data_len = (guint16) (h.next_offset - off - 29); /* print the certificate */ if (h.segment_kind == 0x01) { g_autofree gchar *tmp = NULL; tmp = fu_rom_get_hex_dump (h.data, h.data_len); g_debug ("%s(%i)", tmp, h.data_len); } else if (h.segment_kind == 0x02) { g_autofree gchar *tmp = NULL; tmp = fu_rom_get_hex_dump (h.data, h.data_len < 32 ? h.data_len : 32); g_debug ("%s(%i)", tmp, h.data_len); } else { g_warning ("unknown segment kind %i", h.segment_kind); } /* last block */ if (h.next_offset == 0x0000) break; off = h.next_offset; } } static const gchar * fu_rom_pci_code_type_to_string (guint8 code_type) { if (code_type == 0) return "Intel86"; if (code_type == 1) return "OpenFirmware"; if (code_type == 2) return "PA-RISC"; if (code_type == 3) return "EFI"; return "reserved"; } static guint8 fu_rom_pci_header_get_checksum (FuRomPciHeader *hdr) { guint8 chksum_check = 0x00; for (guint i = 0; i < hdr->rom_len; i++) chksum_check += hdr->rom_data[i]; return chksum_check; } static void fu_rom_pci_print_header (FuRomPciHeader *hdr) { guint8 chksum_check; guint8 *buffer; g_autofree gchar *data_str = NULL; g_autofree gchar *reserved_str = NULL; g_debug ("PCI Header"); g_debug (" RomOffset: 0x%04x", hdr->rom_offset); g_debug (" RomSize: 0x%04x", hdr->rom_len); g_debug (" EntryPnt: 0x%06x", hdr->entry_point); reserved_str = fu_rom_get_hex_dump (hdr->reserved, 18); g_debug (" Reserved: %s", reserved_str); g_debug (" CpiPtr: 0x%04x", hdr->cpi_ptr); /* sanity check */ if (hdr->cpi_ptr > hdr->rom_len) { g_debug (" PCI DATA: Invalid as cpi_ptr > rom_len"); return; } if (hdr->data_len > hdr->rom_len) { g_debug (" PCI DATA: Invalid as data_len > rom_len"); return; } /* print the data */ buffer = &hdr->rom_data[hdr->cpi_ptr]; g_debug (" PCI Data"); g_debug (" VendorID: 0x%04x", hdr->vendor_id); g_debug (" DeviceID: 0x%04x", hdr->device_id); g_debug (" DevList: 0x%04x", hdr->device_list_ptr); g_debug (" DataLen: 0x%04x", hdr->data_len); g_debug (" DataRev: 0x%04x", hdr->data_rev); if (hdr->image_len < 0x0f) { data_str = fu_rom_get_hex_dump (&buffer[hdr->data_len], hdr->image_len); g_debug (" ImageLen: 0x%04x [%s]", hdr->image_len, data_str); } else if (hdr->image_len >= 0x0f) { data_str = fu_rom_get_hex_dump (&buffer[hdr->data_len], 0x0f); g_debug (" ImageLen: 0x%04x [%s...]", hdr->image_len, data_str); } else { g_debug (" ImageLen: 0x%04x", hdr->image_len); } g_debug (" RevLevel: 0x%04x", hdr->revision_level); g_debug (" CodeType: 0x%02x [%s]", hdr->code_type, fu_rom_pci_code_type_to_string (hdr->code_type)); g_debug (" LastImg: 0x%02x [%s]", hdr->last_image, hdr->last_image == 0x80 ? "yes" : "no"); g_debug (" MaxRunLen: 0x%04x", hdr->max_runtime_len); g_debug (" ConfigHdr: 0x%04x", hdr->config_header_ptr); g_debug (" ClpPtr: 0x%04x", hdr->dmtf_clp_ptr); /* dump the ISBN */ if (hdr->code_type == 0x70 && memcmp (&buffer[hdr->data_len], "ISBN", 4) == 0) { fu_rom_pci_print_certificate_data (&buffer[hdr->data_len], hdr->image_len); } /* verify the checksum byte */ if (hdr->image_len <= hdr->rom_len && hdr->image_len > 0) { buffer = hdr->rom_data; chksum_check = fu_rom_pci_header_get_checksum (hdr); if (chksum_check == 0x00) { g_debug (" ChkSum: 0x%02x [valid]", buffer[hdr->image_len-1]); } else { g_debug (" ChkSum: 0x%02x [failed, got 0x%02x]", buffer[hdr->image_len-1], chksum_check); } } else { g_debug (" ChkSum: 0x?? [unknown]"); } } gboolean fu_rom_extract_all (FuRom *self, const gchar *path, GError **error) { FuRomPciHeader *hdr; for (guint i = 0; i < self->hdrs->len; i++) { g_autofree gchar *fn = NULL; hdr = g_ptr_array_index (self->hdrs, i); fn = g_strdup_printf ("%s/%02u.bin", path, i); g_debug ("dumping ROM #%u at 0x%04x [0x%02x] to %s", i, hdr->rom_offset, hdr->rom_len, fn); if (hdr->rom_len == 0) continue; if (!g_file_set_contents (fn, (const gchar *) hdr->rom_data, (gssize) hdr->rom_len, error)) return FALSE; } return TRUE; } static void fu_rom_find_and_blank_serial_numbers (FuRom *self) { FuRomPciHeader *hdr; guint8 *tmp; /* bail if not likely */ if (self->kind == FU_ROM_KIND_PCI || self->kind == FU_ROM_KIND_INTEL) { g_debug ("no serial numbers likely"); return; } for (guint i = 0; i < self->hdrs->len; i++) { hdr = g_ptr_array_index (self->hdrs, i); g_debug ("looking for PPID at 0x%04x", hdr->rom_offset); tmp = fu_rom_pci_strstr (hdr, "PPID"); if (tmp != NULL) { guint len; guint8 chk; len = fu_rom_blank_serial_numbers (tmp, hdr->rom_len - hdr->data_len); g_debug ("cleared %u chars @ 0x%04lx", len, (gulong) (tmp - &hdr->rom_data[hdr->data_len])); /* we have to fix the checksum */ chk = fu_rom_pci_header_get_checksum (hdr); hdr->rom_data[hdr->rom_len - 1] -= chk; fu_rom_pci_print_header (hdr); } } } static gboolean fu_rom_pci_parse_data (FuRomPciHeader *hdr) { guint8 *buffer; /* check valid */ if (hdr->cpi_ptr == 0x0000) { g_debug ("No PCI DATA @ 0x%04x", hdr->rom_offset); return FALSE; } if (hdr->rom_len > 0 && hdr->cpi_ptr > hdr->rom_len) { g_debug ("Invalid PCI DATA @ 0x%04x", hdr->rom_offset); return FALSE; } /* gahh, CPI is out of the first chunk */ if (hdr->cpi_ptr > hdr->rom_len) { g_debug ("No available PCI DATA @ 0x%04x : 0x%04x > 0x%04x", hdr->rom_offset, hdr->cpi_ptr, hdr->rom_len); return FALSE; } /* check signature */ buffer = &hdr->rom_data[hdr->cpi_ptr]; if (memcmp (buffer, "PCIR", 4) != 0) { if (memcmp (buffer, "RGIS", 4) == 0 || memcmp (buffer, "NPDS", 4) == 0 || memcmp (buffer, "NPDE", 4) == 0) { g_debug ("-- using NVIDIA DATA quirk"); } else { g_debug ("Not PCI DATA: %02x%02x%02x%02x [%c%c%c%c]", buffer[0], buffer[1], buffer[2], buffer[3], buffer[0], buffer[1], buffer[2], buffer[3]); return FALSE; } } /* parse */ hdr->vendor_id = ((guint16) buffer[0x05] << 8) + buffer[0x04]; hdr->device_id = ((guint16) buffer[0x07] << 8) + buffer[0x06]; hdr->device_list_ptr = ((guint16) buffer[0x09] << 8) + buffer[0x08]; hdr->data_len = ((guint16) buffer[0x0b] << 8) + buffer[0x0a]; hdr->data_rev = buffer[0x0c]; hdr->class_code = ((guint16) buffer[0x0f] << 16) + ((guint16) buffer[0x0e] << 8) + buffer[0x0d]; hdr->image_len = (((guint16) buffer[0x11] << 8) + buffer[0x10]) * 512; hdr->revision_level = ((guint16) buffer[0x13] << 8) + buffer[0x12]; hdr->code_type = buffer[0x14]; hdr->last_image = buffer[0x15]; hdr->max_runtime_len = (((guint16) buffer[0x17] << 8) + buffer[0x16]) * 512; hdr->config_header_ptr = ((guint16) buffer[0x19] << 8) + buffer[0x18]; hdr->dmtf_clp_ptr = ((guint16) buffer[0x1b] << 8) + buffer[0x1a]; return TRUE; } static FuRomPciHeader * fu_rom_pci_get_header (guint8 *buffer, guint32 sz) { FuRomPciHeader *hdr; /* check signature */ if (memcmp (buffer, "\x55\xaa", 2) != 0) { if (memcmp (buffer, "\x56\x4e", 2) == 0) { g_debug ("-- using NVIDIA ROM quirk"); } else { g_autofree gchar *sig_str = NULL; sig_str = fu_rom_get_hex_dump (buffer, MIN (16, sz)); g_debug ("Not PCI ROM %s", sig_str); return NULL; } } /* decode structure */ hdr = g_new0 (FuRomPciHeader, 1); hdr->rom_len = buffer[0x02] * 512; /* fix up misreporting */ if (hdr->rom_len == 0) { g_debug ("fixing up last image size"); hdr->rom_len = sz; } /* copy this locally to the header */ hdr->rom_data = g_memdup (buffer, hdr->rom_len); /* parse out CPI */ hdr->entry_point = ((guint32) buffer[0x05] << 16) + ((guint16) buffer[0x04] << 8) + buffer[0x03]; memcpy (&hdr->reserved, &buffer[6], 18); hdr->cpi_ptr = ((guint16) buffer[0x19] << 8) + buffer[0x18]; /* parse the header data */ g_debug ("looking for PCI DATA @ 0x%04x", hdr->cpi_ptr); fu_rom_pci_parse_data (hdr); return hdr; } static gchar * fu_rom_find_version_pci (FuRomPciHeader *hdr) { gchar *str; /* ARC storage */ if (memcmp (hdr->reserved, "\0\0ARC", 5) == 0) { str = (gchar *) fu_rom_pci_strstr (hdr, "BIOS: "); if (str != NULL) return g_strdup (str + 6); } return NULL; } static gchar * fu_rom_find_version_nvidia (FuRomPciHeader *hdr) { gchar *str; /* static location for some firmware */ if (memcmp (hdr->rom_data + 0x013d, "Version ", 8) == 0) return g_strdup ((gchar *) &hdr->rom_data[0x013d + 8]); /* usual search string */ str = (gchar *) fu_rom_pci_strstr (hdr, "Version "); if (str != NULL) return g_strdup (str + 8); /* broken */ str = (gchar *) fu_rom_pci_strstr (hdr, "Vension:"); if (str != NULL) return g_strdup (str + 8); str = (gchar *) fu_rom_pci_strstr (hdr, "Version"); if (str != NULL) return g_strdup (str + 7); /* fallback to VBIOS */ if (memcmp (hdr->rom_data + 0xfa, "VBIOS Ver", 9) == 0) return g_strdup ((gchar *) &hdr->rom_data[0xfa + 9]); return NULL; } static gchar * fu_rom_find_version_intel (FuRomPciHeader *hdr) { gchar *str; /* 2175_RYan PC 14.34 06/06/2013 21:27:53 */ str = (gchar *) fu_rom_pci_strstr (hdr, "Build Number:"); if (str != NULL) { g_auto(GStrv) split = NULL; split = g_strsplit (str + 14, " ", -1); for (guint i = 0; split[i] != NULL; i++) { if (g_strstr_len (split[i], -1, ".") == NULL) continue; return g_strdup (split[i]); } } /* fallback to VBIOS */ str = (gchar *) fu_rom_pci_strstr (hdr, "VBIOS "); if (str != NULL) return g_strdup (str + 6); return NULL; } static gchar * fu_rom_find_version_ati (FuRomPciHeader *hdr) { gchar *str; str = (gchar *) fu_rom_pci_strstr (hdr, " VER0"); if (str != NULL) return g_strdup (str + 4); /* broken */ str = (gchar *) fu_rom_pci_strstr (hdr, " VR"); if (str != NULL) return g_strdup (str + 4); return NULL; } static gchar * fu_rom_find_version (FuRomKind kind, FuRomPciHeader *hdr) { if (kind == FU_ROM_KIND_PCI) return fu_rom_find_version_pci (hdr); if (kind == FU_ROM_KIND_NVIDIA) return fu_rom_find_version_nvidia (hdr); if (kind == FU_ROM_KIND_INTEL) return fu_rom_find_version_intel (hdr); if (kind == FU_ROM_KIND_ATI) return fu_rom_find_version_ati (hdr); return NULL; } gboolean fu_rom_load_data (FuRom *self, guint8 *buffer, gsize buffer_sz, FuRomLoadFlags flags, GCancellable *cancellable, GError **error) { FuRomPciHeader *hdr = NULL; guint32 sz = buffer_sz; guint32 jump = 0; guint32 hdr_sz = 0; g_autoptr(GChecksum) checksum_sha1 = g_checksum_new (G_CHECKSUM_SHA1); g_autoptr(GChecksum) checksum_sha256 = g_checksum_new (G_CHECKSUM_SHA256); g_return_val_if_fail (FU_IS_ROM (self), FALSE); /* detect optional IFR header and skip to option ROM */ if (memcmp (buffer, "NVGI", 4) == 0) { guint16 ifr_sz_raw; memcpy (&ifr_sz_raw, &buffer[0x15], 2); hdr_sz = GUINT16_FROM_BE (ifr_sz_raw); g_debug ("detected IFR header, skipping %x bytes", hdr_sz); } /* read all the ROM headers */ while (sz > hdr_sz + jump) { guint32 jump_sz; g_debug ("looking for PCI ROM @ 0x%04x", hdr_sz + jump); hdr = fu_rom_pci_get_header (&buffer[hdr_sz + jump], sz - hdr_sz - jump); if (hdr == NULL) { gboolean found_data = FALSE; /* check it's not just NUL padding */ for (guint i = jump + hdr_sz; i < buffer_sz; i++) { if (buffer[i] != 0x00) { found_data = TRUE; break; } } if (found_data) { g_debug ("found junk data, adding fake"); hdr = g_new0 (FuRomPciHeader, 1); hdr->vendor_id = 0x0000; hdr->device_id = 0x0000; hdr->code_type = 0x00; hdr->last_image = 0x80; hdr->rom_offset = hdr_sz + jump; hdr->rom_len = sz - hdr->rom_offset; hdr->rom_data = g_memdup (&buffer[hdr->rom_offset], hdr->rom_len); hdr->image_len = hdr->rom_len; g_ptr_array_add (self->hdrs, hdr); } else { g_debug ("ignoring 0x%04x bytes of padding", (guint) (buffer_sz - (jump + hdr_sz))); } break; } /* save this so we can fix checksums */ hdr->rom_offset = hdr_sz + jump; /* we can't break on hdr->last_image as * NVIDIA uses packed but not merged extended headers */ g_ptr_array_add (self->hdrs, hdr); /* NVIDIA don't always set a ROM size for extensions */ jump_sz = hdr->rom_len; if (jump_sz == 0) jump_sz = hdr->image_len; if (jump_sz == 0x0) break; jump += jump_sz; } /* we found nothing */ if (self->hdrs->len == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to detect firmware header [%02x%02x]", buffer[0], buffer[1]); return FALSE; } /* print all headers */ for (guint i = 0; i < self->hdrs->len; i++) { hdr = g_ptr_array_index (self->hdrs, i); fu_rom_pci_print_header (hdr); } /* find first ROM header */ hdr = g_ptr_array_index (self->hdrs, 0); self->vendor_id = hdr->vendor_id; self->device_id = hdr->device_id; self->kind = FU_ROM_KIND_PCI; /* detect intel header */ if (memcmp (hdr->reserved, "00000000000", 11) == 0) hdr_sz = (guint32) (((guint16) buffer[0x1b] << 8) + buffer[0x1a]); if (hdr_sz > sz) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware corrupt (overflow)"); return FALSE; } if (hdr->entry_point == 0x374beb) { self->kind = FU_ROM_KIND_NVIDIA; } else if (memcmp (buffer + hdr_sz, "$VBT", 4) == 0) { self->kind = FU_ROM_KIND_INTEL; } else if (memcmp(buffer + 0x30, " 761295520", 10) == 0) { self->kind = FU_ROM_KIND_ATI; } /* nothing */ if (self->kind == FU_ROM_KIND_UNKNOWN) { g_autofree gchar *str = NULL; str = fu_rom_get_hex_dump (buffer + hdr_sz, 0x32); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to detect firmware kind from [%s]", str); return FALSE; } /* find version string */ self->version = fu_rom_find_version (self->kind, hdr); if (self->version != NULL) { g_strstrip (self->version); g_strdelimit (self->version, "\r\n ", '\0'); } /* update checksum */ if (flags & FU_ROM_LOAD_FLAG_BLANK_PPID) fu_rom_find_and_blank_serial_numbers (self); for (guint i = 0; i < self->hdrs->len; i++) { hdr = g_ptr_array_index (self->hdrs, i); g_checksum_update (checksum_sha1, hdr->rom_data, hdr->rom_len); g_checksum_update (checksum_sha256, hdr->rom_data, hdr->rom_len); } /* done updating checksums */ g_ptr_array_add (self->checksums, g_strdup (g_checksum_get_string (checksum_sha1))); g_ptr_array_add (self->checksums, g_strdup (g_checksum_get_string (checksum_sha256))); /* update guid */ self->guid = g_strdup_printf ("PCI\\VEN_%04X&DEV_%04X", self->vendor_id, self->device_id); /* not known */ if (self->version == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Firmware version extractor not known"); return FALSE; } return TRUE; } gboolean fu_rom_load_file (FuRom *self, GFile *file, FuRomLoadFlags flags, GCancellable *cancellable, GError **error) { const gssize buffer_sz = 0x400000; gssize sz; guint number_reads = 0; g_autoptr(GError) error_local = NULL; g_autofree gchar *fn = NULL; g_autofree guint8 *buffer = NULL; g_autoptr(GFileOutputStream) output_stream = NULL; g_return_val_if_fail (FU_IS_ROM (self), FALSE); /* open file */ self->stream = G_INPUT_STREAM (g_file_read (file, cancellable, &error_local)); if (self->stream == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, error_local->message); return FALSE; } /* we have to enable the read for devices */ fn = g_file_get_path (file); if (g_str_has_prefix (fn, "/sys")) { output_stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, cancellable, error); if (output_stream == NULL) return FALSE; if (g_output_stream_write (G_OUTPUT_STREAM (output_stream), "1", 1, cancellable, error) < 0) return FALSE; } /* read out the header */ buffer = g_malloc ((gsize) buffer_sz); sz = g_input_stream_read (self->stream, buffer, buffer_sz, cancellable, error); if (sz < 0) return FALSE; if (sz < 512) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Firmware too small: %" G_GSSIZE_FORMAT " bytes", sz); return FALSE; } /* ensure we got enough data to fill the buffer */ while (sz < buffer_sz) { gssize sz_chunk; sz_chunk = g_input_stream_read (self->stream, buffer + sz, buffer_sz - sz, cancellable, error); if (sz_chunk == 0) break; g_debug ("ROM returned 0x%04x bytes, adding 0x%04x...", (guint) sz, (guint) sz_chunk); if (sz_chunk < 0) return FALSE; sz += sz_chunk; /* check the firmware isn't serving us small chunks */ if (number_reads++ > 16) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware not fulfilling requests"); return FALSE; } } g_debug ("ROM buffer filled %" G_GSSIZE_FORMAT "kb/%" G_GSSIZE_FORMAT "kb", sz / 0x400, buffer_sz / 0x400); return fu_rom_load_data (self, buffer, sz, flags, cancellable, error); } FuRomKind fu_rom_get_kind (FuRom *self) { g_return_val_if_fail (FU_IS_ROM (self), FU_ROM_KIND_UNKNOWN); return self->kind; } const gchar * fu_rom_get_version (FuRom *self) { g_return_val_if_fail (FU_IS_ROM (self), NULL); return self->version; } const gchar * fu_rom_get_guid (FuRom *self) { g_return_val_if_fail (FU_IS_ROM (self), NULL); return self->guid; } guint16 fu_rom_get_vendor (FuRom *self) { g_return_val_if_fail (FU_IS_ROM (self), 0x0000); return self->vendor_id; } guint16 fu_rom_get_model (FuRom *self) { g_return_val_if_fail (FU_IS_ROM (self), 0x0000); return self->device_id; } GPtrArray * fu_rom_get_checksums (FuRom *self) { return self->checksums; } static void fu_rom_class_init (FuRomClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_rom_finalize; } static void fu_rom_init (FuRom *self) { self->checksums = g_ptr_array_new_with_free_func (g_free); self->hdrs = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_rom_pci_header_free); } static void fu_rom_finalize (GObject *object) { FuRom *self = FU_ROM (object); g_free (self->version); g_free (self->guid); g_ptr_array_unref (self->checksums); g_ptr_array_unref (self->hdrs); if (self->stream != NULL) g_object_unref (self->stream); G_OBJECT_CLASS (fu_rom_parent_class)->finalize (object); } FuRom * fu_rom_new (void) { FuRom *self; self = g_object_new (FU_TYPE_ROM, NULL); return FU_ROM (self); } fwupd-1.2.14/plugins/udev/fu-rom.h000066400000000000000000000025011402665037500167340ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FU_TYPE_ROM (fu_rom_get_type ()) G_DECLARE_FINAL_TYPE (FuRom, fu_rom, FU, ROM, GObject) typedef enum { FU_ROM_KIND_UNKNOWN, FU_ROM_KIND_ATI, FU_ROM_KIND_NVIDIA, FU_ROM_KIND_INTEL, FU_ROM_KIND_PCI, FU_ROM_KIND_LAST } FuRomKind; typedef enum { FU_ROM_LOAD_FLAG_NONE, FU_ROM_LOAD_FLAG_BLANK_PPID = 1, FU_ROM_LOAD_FLAG_LAST } FuRomLoadFlags; FuRom *fu_rom_new (void); gboolean fu_rom_load_file (FuRom *self, GFile *file, FuRomLoadFlags flags, GCancellable *cancellable, GError **error); gboolean fu_rom_load_data (FuRom *self, guint8 *buffer, gsize buffer_sz, FuRomLoadFlags flags, GCancellable *cancellable, GError **error); gboolean fu_rom_extract_all (FuRom *self, const gchar *path, GError **error); FuRomKind fu_rom_get_kind (FuRom *self); const gchar *fu_rom_get_version (FuRom *self); GPtrArray *fu_rom_get_checksums (FuRom *self); const gchar *fu_rom_get_guid (FuRom *self); guint16 fu_rom_get_vendor (FuRom *self); guint16 fu_rom_get_model (FuRom *self); const gchar *fu_rom_kind_to_string (FuRomKind kind); G_END_DECLS fwupd-1.2.14/plugins/udev/fu-self-test.c000066400000000000000000000075731402665037500200560ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-keyring.h" #include "fu-history.h" #include "fu-plugin-private.h" #include "fu-rom.h" #include "fu-test.h" static void fu_rom_func (void) { struct { FuRomKind kind; const gchar *fn; const gchar *ver; const gchar *csum; guint16 vendor; guint16 model; } data[] = { { FU_ROM_KIND_ATI, "Asus.9800PRO.256.unknown.031114.rom", "008.015.041.001", "3137385685298bbf7db2c8304f60d89005c731ed", 0x1002, 0x4e48 }, { FU_ROM_KIND_ATI, /* atombios */ "Asus.R9290X.4096.131014.rom", "015.039.000.006.003515", "d8e32fa09a00ab9dcc96a990266f3fe5a99eacc5", 0x1002, 0x67b0 }, { FU_ROM_KIND_ATI, /* atombios, with serial */ "Asus.HD7970.3072.121018.rom", "015.023.000.002.000000", "ba8b6ce38f2499c8463fc9d983b8e0162b1121e4", 0x1002, 0x6798 }, { FU_ROM_KIND_NVIDIA, "Asus.GTX480.1536.100406_1.rom", "70.00.1A.00.02", "3fcab24e60934850246fcfc4f42eceb32540a0ad", 0x10de, 0x06c0 }, { FU_ROM_KIND_NVIDIA, /* nvgi */ "Asus.GTX980.4096.140905.rom", "84.04.1F.00.02", "98f58321145bd347156455356bc04c5b04a292f5", 0x10de, 0x13c0 }, { FU_ROM_KIND_NVIDIA, /* nvgi, with serial */ "Asus.TitanBlack.6144.140212.rom", "80.80.4E.00.01", "3c80f35d4e3c440ffb427957d9271384113d7721", 0x10de, 0x100c }, { FU_ROM_KIND_UNKNOWN, NULL, NULL, NULL, 0x0000, 0x0000 } }; for (guint i = 0; data[i].fn != NULL; i++) { gboolean ret; g_autoptr(GError) error = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuRom) rom = NULL; g_autoptr(GFile) file = NULL; rom = fu_rom_new (); g_assert (rom != NULL); /* load file */ filename = fu_test_get_filename (TESTDATADIR, data[i].fn); if (filename == NULL) continue; g_print ("\nparsing %s...", filename); file = g_file_new_for_path (filename); ret = fu_rom_load_file (rom, file, FU_ROM_LOAD_FLAG_BLANK_PPID, NULL, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpstr (fu_rom_get_version (rom), ==, data[i].ver); g_assert_cmpstr (g_ptr_array_index (fu_rom_get_checksums (rom), 0), ==, data[i].csum); g_assert_cmpint (fu_rom_get_kind (rom), ==, data[i].kind); g_assert_cmpint (fu_rom_get_vendor (rom), ==, data[i].vendor); g_assert_cmpint (fu_rom_get_model (rom), ==, data[i].model); } } static void fu_rom_all_func (void) { GDir *dir; g_autofree gchar *path = NULL; /* may or may not exist */ path = fu_test_get_filename (TESTDATADIR, "roms"); if (path == NULL) return; g_print ("\n"); dir = g_dir_open (path, 0, NULL); do { const gchar *fn; gboolean ret; g_autoptr(GError) error = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuRom) rom = NULL; g_autoptr(GFile) file = NULL; fn = g_dir_read_name (dir); if (fn == NULL) break; filename = g_build_filename (path, fn, NULL); g_print ("\nparsing %s...", filename); file = g_file_new_for_path (filename); rom = fu_rom_new (); ret = fu_rom_load_file (rom, file, FU_ROM_LOAD_FLAG_BLANK_PPID, NULL, &error); if (!ret) { g_print ("%s %s : %s\n", fu_rom_kind_to_string (fu_rom_get_kind (rom)), filename, error->message); continue; } g_assert_cmpstr (fu_rom_get_version (rom), !=, NULL); g_assert_cmpstr (fu_rom_get_version (rom), !=, "\0"); g_assert_cmpint (fu_rom_get_checksums(rom)->len, !=, 0); g_assert_cmpint (fu_rom_get_kind (rom), !=, FU_ROM_KIND_UNKNOWN); } while (TRUE); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func ("/fwupd/rom", fu_rom_func); g_test_add_func ("/fwupd/rom{all}", fu_rom_all_func); return g_test_run (); } fwupd-1.2.14/plugins/udev/fuzzing.md000066400000000000000000000001661402665037500174010ustar00rootroot00000000000000CC=afl-gcc ./configure --disable-shared AFL_HARDEN=1 make afl-fuzz -m 300 -i fuzzing -o findings ./fu-rom-tool rom @@ fwupd-1.2.14/plugins/udev/fuzzing/000077500000000000000000000000001402665037500170545ustar00rootroot00000000000000fwupd-1.2.14/plugins/udev/fuzzing/header-data-payload.rom000066400000000000000000000010001402665037500233500ustar00rootroot00000000000000U K7hdr-data-payload PCIRVersion 1.0\fwupd-1.2.14/plugins/udev/fuzzing/header-no-data.rom000066400000000000000000000010001402665037500223330ustar00rootroot00000000000000U K7hdr-no-data fwupd-1.2.14/plugins/udev/fuzzing/ifr-header-data-payload.rom000066400000000000000000000012001402665037500241300ustar00rootroot00000000000000NVGIU K7ifr-hdr-data-payld PCIRVersion 1.0\fwupd-1.2.14/plugins/udev/fuzzing/naked-ifr.rom000066400000000000000000000002001402665037500214230ustar00rootroot00000000000000NVGIfwupd-1.2.14/plugins/udev/meson.build000066400000000000000000000027201402665037500175230ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginUdev"'] shared_module('fu_plugin_udev', fu_hash, sources : [ 'fu-plugin-udev.c', 'fu-rom.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) executable( 'fu-rom-tool', fu_hash, sources : [ 'fu-rom-tool.c', 'fu-rom.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ plugin_deps, libjsonglib, ], link_with : [ libfwupdprivate, ], c_args : cargs, ) if get_option('tests') cargs += '-DFU_OFFLINE_DESTDIR="/tmp/fwupd-self-test"' cargs += '-DPLUGINBUILDDIR="' + meson.current_build_dir() + '"' testdatadir = join_paths(meson.current_source_dir(), 'tests') cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'udev-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-rom.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ plugin_deps, ], link_with : [ libfwupdprivate, ], c_args : cargs ) test('udev-self-test', e) endif fwupd-1.2.14/plugins/udev/tests/000077500000000000000000000000001402665037500165225ustar00rootroot00000000000000fwupd-1.2.14/plugins/udev/tests/get-nonfree.sh000077500000000000000000000007251402665037500212760ustar00rootroot00000000000000rm *.rom wget http://www.techpowerup.com/vgabios/375/Asus.9800PRO.256.unknown.031114.rom wget http://www.techpowerup.com/vgabios/133037/Asus.HD7970.3072.121018.rom wget http://www.techpowerup.com/vgabios/148214/Asus.R9290X.4096.131014.rom wget http://www.techpowerup.com/vgabios/74257/Asus.GTX480.1536.100406_1.rom wget http://www.techpowerup.com/vgabios/162406/Asus.GTX980.4096.140905.rom wget http://www.techpowerup.com/vgabios/157835/Asus.TitanBlack.6144.140212.rom fwupd-1.2.14/plugins/uefi/000077500000000000000000000000001402665037500153455ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/README.md000066400000000000000000000043211402665037500166240ustar00rootroot00000000000000UEFI Support ============ Introduction ------------ The Unified Extensible Firmware Interface (UEFI) is a specification that defines the software interface between an OS and platform firmware. With the UpdateCapsule boot service it can be used to update system firmware. If you don't want or need this functionality you can use the `-Dplugin_uefi=false` option. UEFI SBAT Support ----------------- If compiling with `-Dsupported_build=true` the packager must also specify the SBAT metadata required for the secure boot revocation support. See the specification for more information: https://github.com/rhboot/shim/blob/sbat/SBAT.md Typically, this will be set as part of the package build script, e.g. -Defi_sbat_distro_id="fedora" \ -Defi_sbat_distro_summary="The Fedora Project" \ -Defi_sbat_distro_pkgname="%{name}" \ -Defi_sbat_distro_version="%{version}" \ -Defi_sbat_distro_url="https://src.fedoraproject.org/rpms/%{name}" \ Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in EFI capsule file format. See https://www.uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf for details. This plugin supports the following protocol ID: * org.uefi.capsule GUID Generation --------------- These devices use the UEFI GUID as provided in the ESRT. Additionally, for the system device the `main-system-firmware` GUID is also added. For compatibility with Windows 10, the plugin also adds GUIDs of the form `UEFI\RES_{$(esrt)}`. UEFI Unlock Support ------------------- On some Dell systems it is possible to turn on and off UEFI capsule support from within the BIOS. This functionality can also be adjusted from within the OS by fwupd. This requires compiling with libsmbios support. When fwupd has been compiled with this support you will be able to enable UEFI support on the device by using the `unlock` command. Custom EFI System Partition --------------------------- Since version 1.1.0 fwupd will autodetect the ESP when it is mounted on `/boot/efi`, `/boot`, or `/efi`. A custom EFI system partition location can be used by modifying *OverrideESPMountPoint* in `/etc/fwupd/uefi.conf`. Setting an invalid directory will disable the fwupd plugin. fwupd-1.2.14/plugins/uefi/efi/000077500000000000000000000000001402665037500161105ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/efi/fwup-cleanups.h000066400000000000000000000010011402665037500210420ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #define _DEFINE_CLEANUP_FUNCTION0(Type, name, func) \ static inline VOID name(VOID *v) \ { \ if (*(Type*)v) \ func (*(Type*)v); \ } _DEFINE_CLEANUP_FUNCTION0(VOID *, _FreePool_p, FreePool) #define _cleanup_free __attribute__ ((cleanup(_FreePool_p))) static inline VOID * _steal_pointer(VOID *pp) { VOID **ptr = (VOID **) pp; VOID *ref = *ptr; *ptr = NULL; return ref; } fwupd-1.2.14/plugins/uefi/efi/fwup-common.c000066400000000000000000000050751402665037500205320ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "fwup-debug.h" #include "fwup-common.h" VOID fwup_msleep(unsigned long msecs) { BS->Stall(msecs); } /* * Allocate some raw pages that aren't part of the pool allocator. */ VOID * fwup_malloc_raw(UINTN size) { UINTN pages = size / 4096 + ((size % 4096) ? 1 : 0); /* page size is always 4096 */ EFI_STATUS rc; EFI_PHYSICAL_ADDRESS pageaddr = 0; EFI_ALLOCATE_TYPE type = AllocateAnyPages; if (sizeof(VOID *) == 4) { pageaddr = 0xffffffffULL - 8192; type = AllocateMaxAddress; } rc = uefi_call_wrapper(BS->AllocatePages, 4, type, EfiLoaderData, pages, &pageaddr); if (EFI_ERROR(rc)) { fwup_warning(L"Could not allocate %d", size); return NULL; } if (sizeof(VOID *) == 4 && pageaddr > 0xffffffffULL) { uefi_call_wrapper(BS->FreePages, 2, pageaddr, pages); fwup_warning(L"Got bad allocation at 0x%016x", (UINT64)pageaddr); return NULL; } return (VOID *)(UINTN)pageaddr; } /* * Free our raw page allocations. */ static EFI_STATUS fwup_free_raw(VOID *addr, UINTN size) { UINTN pages = size / 4096 + ((size % 4096) ? 1 : 0); return uefi_call_wrapper(BS->FreePages, 2, (EFI_PHYSICAL_ADDRESS)(UINTN)addr, pages); } VOID * fwup_malloc (UINTN size) { VOID *addr = AllocatePool(size); if (addr == NULL) fwup_warning(L"Could not allocate %d", size); return addr; } VOID * fwup_malloc0 (UINTN size) { VOID *addr = AllocateZeroPool(size); if (addr == NULL) fwup_warning(L"Could not allocate %d", size); return addr; } EFI_STATUS fwup_time(EFI_TIME *ts) { EFI_TIME_CAPABILITIES timecaps = { 0, }; return uefi_call_wrapper(RT->GetTime, 2, ts, &timecaps); } EFI_STATUS fwup_read_file(EFI_FILE_HANDLE fh, UINT8 **buf_out, UINTN *buf_size_out) { const UINTN bs = 512; UINTN i = 0; UINTN n_blocks = 4096; UINT8 *buf = NULL; while (1) { VOID *newb = NULL; UINTN news = n_blocks * bs * 2; newb = fwup_malloc_raw(news); if (newb == NULL) return EFI_OUT_OF_RESOURCES; if (buf != NULL) { CopyMem(newb, buf, bs * n_blocks); fwup_free_raw(buf, bs * n_blocks); } buf = newb; n_blocks *= 2; for (; i < n_blocks; i++) { EFI_STATUS rc; UINTN sz = bs; rc = uefi_call_wrapper(fh->Read, 3, fh, &sz, &buf[i * bs]); if (EFI_ERROR(rc)) { fwup_free_raw(buf, bs * n_blocks); fwup_warning(L"Could not read file: %r", rc); return rc; } if (sz != bs) { *buf_size_out = bs * i + sz; *buf_out = buf; return EFI_SUCCESS; } } } return EFI_SUCCESS; } fwupd-1.2.14/plugins/uefi/efi/fwup-common.h000066400000000000000000000012551402665037500205330ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Peter Jones * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fwup-efi.h" VOID fwup_msleep (unsigned long msecs); EFI_STATUS fwup_time (EFI_TIME *ts); EFI_STATUS fwup_read_file (EFI_FILE_HANDLE fh, UINT8 **buf_out, UINTN *buf_size_out); VOID *fwup_malloc_raw (UINTN size); VOID *fwup_malloc (UINTN size); VOID *fwup_malloc0 (UINTN size); #define fwup_new(struct_type, n) ((struct_type*)fwup_malloc((n)*sizeof(struct_type))) #define fwup_new0(struct_type, n) ((struct_type*)fwup_malloc0((n)*sizeof(struct_type))) fwupd-1.2.14/plugins/uefi/efi/fwup-debug.c000066400000000000000000000031251402665037500203220ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "fwup-cleanups.h" #include "fwup-debug.h" #include "fwup-efi.h" static BOOLEAN debugging = FALSE; BOOLEAN fwup_debug_get_enabled(VOID) { return debugging; } VOID fwup_debug_set_enabled(BOOLEAN val) { debugging = val; } static VOID fwupd_debug_efivar_append(CHAR16 *out1) { CHAR16 *name = L"FWUPDATE_DEBUG_LOG"; UINT32 attrs = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS; static BOOLEAN once = TRUE; if (once) { once = FALSE; fwup_delete_variable(name, &fwupdate_guid); } else { attrs |= EFI_VARIABLE_APPEND_WRITE; } fwup_set_variable(name, &fwupdate_guid, out1, StrSize(out1) - sizeof(CHAR16), attrs); } VOID fwup_log(FwupLogLevel level, const char *func, const char *file, const int line, CHAR16 *fmt, ...) { va_list args; _cleanup_free CHAR16 *tmp = NULL; va_start(args, fmt); tmp = VPoolPrint(fmt, args); va_end(args); if (tmp == NULL) { Print(L"fwupdate: Allocation for debug log failed!\n"); return; } if (debugging) { _cleanup_free CHAR16 *out1 = NULL; out1 = PoolPrint(L"%a:%d:%a(): %s", file, line, func, tmp); if (out1 == NULL) { Print(L"fwupdate: Allocation for debug log failed!\n"); return; } Print(L"%s\n", out1); fwupd_debug_efivar_append(out1); } else { switch (level) { case FWUP_DEBUG_LEVEL_DEBUG: break; case FWUP_DEBUG_LEVEL_WARNING: Print(L"WARNING: %s\n", tmp); break; default: Print(L"%s\n", tmp); break; } } } fwupd-1.2.14/plugins/uefi/efi/fwup-debug.h000066400000000000000000000015561402665037500203350ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Peter Jones * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once typedef enum { FWUP_DEBUG_LEVEL_DEBUG, FWUP_DEBUG_LEVEL_INFO, FWUP_DEBUG_LEVEL_WARNING, FWUP_DEBUG_LEVEL_LAST } FwupLogLevel; VOID fwup_log (FwupLogLevel level, const char *func, const char *file, const int line, CHAR16 *fmt, ...); BOOLEAN fwup_debug_get_enabled (VOID); VOID fwup_debug_set_enabled (BOOLEAN val); #define fwup_debug(fmt, args...) fwup_log(FWUP_DEBUG_LEVEL_DEBUG, __func__, __FILE__, __LINE__, fmt, ## args ) #define fwup_info(fmt, args...) fwup_log(FWUP_DEBUG_LEVEL_INFO, __func__, __FILE__, __LINE__, fmt, ## args ) #define fwup_warning(fmt, args...) fwup_log(FWUP_DEBUG_LEVEL_WARNING, __func__, __FILE__, __LINE__, fmt, ## args ) fwupd-1.2.14/plugins/uefi/efi/fwup-efi.c000066400000000000000000000035131402665037500200000ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "fwup-cleanups.h" #include "fwup-common.h" #include "fwup-debug.h" #include "fwup-efi.h" EFI_STATUS fwup_delete_variable(CHAR16 *name, EFI_GUID *guid) { EFI_STATUS rc; UINT32 attrs = 0; /* get the attrs so we can delete it */ rc = uefi_call_wrapper(RT->GetVariable, 5, name, guid, &attrs, NULL, NULL); if (EFI_ERROR(rc)) { if (rc == EFI_NOT_FOUND) { fwup_debug(L"Not deleting variable '%s' as not found", name); return EFI_SUCCESS; } fwup_debug(L"Could not get variable '%s' for delete: %r", name, rc); return rc; } return uefi_call_wrapper(RT->SetVariable, 5, name, guid, attrs, 0, NULL); } EFI_STATUS fwup_set_variable(CHAR16 *name, EFI_GUID *guid, VOID *data, UINTN size, UINT32 attrs) { return uefi_call_wrapper(RT->SetVariable, 5, name, guid, attrs, size, data); } EFI_STATUS fwup_get_variable(CHAR16 *name, EFI_GUID *guid, VOID **buf_out, UINTN *buf_size_out, UINT32 *attrs_out) { EFI_STATUS rc; UINTN size = 0; UINT32 attrs; _cleanup_free VOID *buf = NULL; rc = uefi_call_wrapper(RT->GetVariable, 5, name, guid, &attrs, &size, NULL); if (EFI_ERROR(rc)) { if (rc == EFI_BUFFER_TOO_SMALL) { buf = fwup_malloc(size); if (buf == NULL) return EFI_OUT_OF_RESOURCES; } else if (rc != EFI_NOT_FOUND) { fwup_debug(L"Could not get variable '%s': %r", name, rc); return rc; } } else { fwup_debug(L"GetVariable(%s) succeeded with size=0", name); return EFI_INVALID_PARAMETER; } rc = uefi_call_wrapper(RT->GetVariable, 5, name, guid, &attrs, &size, buf); if (EFI_ERROR(rc)) { fwup_warning(L"Could not get variable '%s': %r", name, rc); return rc; } *buf_out = _steal_pointer(&buf); *buf_size_out = size; *attrs_out = attrs; return EFI_SUCCESS; } fwupd-1.2.14/plugins/uefi/efi/fwup-efi.h000066400000000000000000000033121402665037500200020ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #define FWUPDATE_ATTEMPT_UPDATE 0x00000001 #define FWUPDATE_ATTEMPTED 0x00000002 #define UPDATE_INFO_VERSION 7 static __attribute__((__unused__)) EFI_GUID empty_guid = {0x0,0x0,0x0,{0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0}}; static __attribute__((__unused__))EFI_GUID fwupdate_guid = {0x0abba7dc,0xe516,0x4167,{0xbb,0xf5,0x4d,0x9d,0x1c,0x73,0x94,0x16}}; static __attribute__((__unused__))EFI_GUID ux_capsule_guid = {0x3b8c8162,0x188c,0x46a4,{0xae,0xc9,0xbe,0x43,0xf1,0xd6,0x56,0x97}}; static __attribute__((__unused__))EFI_GUID global_variable_guid = EFI_GLOBAL_VARIABLE; typedef struct { UINT8 version; UINT8 checksum; UINT8 image_type; UINT8 reserved; UINT32 mode; UINT32 x_offset; UINT32 y_offset; } __attribute__((__packed__)) UX_CAPSULE_HEADER; typedef struct { UINT32 update_info_version; /* stuff we need to apply an update */ EFI_GUID guid; UINT32 capsule_flags; UINT64 hw_inst; EFI_TIME time_attempted; /* our metadata */ UINT32 status; /* variadic device path */ union { EFI_DEVICE_PATH dp; UINT8 dp_buf[0]; }; } __attribute__((__packed__)) FWUP_UPDATE_INFO; typedef struct { UINT32 attributes; UINT16 file_path_list_length; CHAR16 *description; } __attribute__((__packed__)) EFI_LOAD_OPTION; EFI_STATUS fwup_delete_variable (CHAR16 *name, EFI_GUID *guid); EFI_STATUS fwup_set_variable (CHAR16 *name, EFI_GUID *guid, VOID *data, UINTN size, UINT32 attrs); EFI_STATUS fwup_get_variable (CHAR16 *name, EFI_GUID *guid, VOID **buf_out, UINTN *buf_size_out, UINT32 *attrs_out); fwupd-1.2.14/plugins/uefi/efi/fwupdate.c000066400000000000000000000465711402665037500201100ustar00rootroot00000000000000/* * Copyright (C) 2014-2018 Red Hat, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "fwup-cleanups.h" #include "fwup-common.h" #include "fwup-efi.h" #include "fwup-debug.h" #define UNUSED __attribute__((__unused__)) #define GNVN_BUF_SIZE 1024 #define FWUP_NUM_CAPSULE_UPDATES_MAX 128 typedef struct { CHAR16 *name; UINT32 attrs; UINTN size; FWUP_UPDATE_INFO *info; } FWUP_UPDATE_TABLE; static VOID fwup_update_table_free(FWUP_UPDATE_TABLE *update) { FreePool(update->info); FreePool(update->name); FreePool(update); } _DEFINE_CLEANUP_FUNCTION0(FWUP_UPDATE_TABLE *, _fwup_update_table_free_p, fwup_update_table_free) #define _cleanup_update_table __attribute__ ((cleanup(_fwup_update_table_free_p))) #define SECONDS 1000000 static INTN fwup_dp_size(EFI_DEVICE_PATH *dp, INTN limit) { INTN ret = 0; while (1) { if (limit < 4) break; INTN nodelen = DevicePathNodeLength(dp); if (nodelen > limit) break; limit -= nodelen; ret += nodelen; if (IsDevicePathEnd(dp)) return ret; dp = NextDevicePathNode(dp); } return -1; } static EFI_STATUS fwup_populate_update_info(CHAR16 *name, FWUP_UPDATE_TABLE *info_out) { EFI_STATUS rc; FWUP_UPDATE_INFO *info = NULL; UINTN info_size = 0; UINT32 attrs = 0; VOID *info_ptr = NULL; rc = fwup_get_variable(name, &fwupdate_guid, &info_ptr, &info_size, &attrs); if (EFI_ERROR(rc)) return rc; info = (FWUP_UPDATE_INFO *)info_ptr; if (info_size < sizeof(*info)) { fwup_warning(L"Update '%s' is is too small", name); return EFI_INVALID_PARAMETER; } if (info_size - sizeof(EFI_DEVICE_PATH) <= sizeof(*info)) { fwup_warning(L"Update '%s' is malformed, " L"and cannot hold a file path", name); return EFI_INVALID_PARAMETER; } EFI_DEVICE_PATH *hdr = (EFI_DEVICE_PATH *)&info->dp; INTN is = EFI_FIELD_OFFSET(FWUP_UPDATE_INFO, dp); if (is > (INTN)info_size) { fwup_warning(L"Update '%s' has an invalid file path, " L"device path offset is %d, but total size is %d", name, is, info_size); return EFI_INVALID_PARAMETER; } is = info_size - is; INTN sz = fwup_dp_size(hdr, info_size); if (sz < 0 || is > (INTN)info_size || is != sz) { fwup_warning(L"Update '%s' has an invalid file path, " L"update info size: %d dp size: %d size for dp: %d", name, info_size, sz, is); return EFI_INVALID_PARAMETER; } info_out->info = info; info_out->size = info_size; info_out->attrs = attrs; info_out->name = StrDuplicate(name); if (info_out->name == NULL) { fwup_warning(L"Could not allocate %d", StrSize(name)); return EFI_OUT_OF_RESOURCES; } return EFI_SUCCESS; } static EFI_STATUS fwup_populate_update_table(FWUP_UPDATE_TABLE **updates, UINTN *n_updates_out) { EFI_GUID vendor_guid = empty_guid; EFI_STATUS rc; UINTN n_updates = 0; _cleanup_free CHAR16 *variable_name = NULL; /* How much do we trust "size of the VariableName buffer" to mean * sizeof(vn) and not sizeof(vn)/sizeof(vn[0]) ? */ variable_name = fwup_malloc0(GNVN_BUF_SIZE * 2); if (variable_name == NULL) return EFI_OUT_OF_RESOURCES; while (1) { UINTN variable_name_size = GNVN_BUF_SIZE; rc = uefi_call_wrapper(RT->GetNextVariableName, 3, &variable_name_size, variable_name, &vendor_guid); if (rc == EFI_NOT_FOUND) break; /* ignore any huge names */ if (rc == EFI_BUFFER_TOO_SMALL) continue; if (EFI_ERROR(rc)) { fwup_warning(L"Could not get variable name: %r", rc); return rc; } /* not one of our state variables */ if (CompareGuid(&vendor_guid, &fwupdate_guid)) continue; /* ignore debugging settings */ if (StrCmp(variable_name, L"FWUPDATE_VERBOSE") == 0 || StrCmp(variable_name, L"FWUPDATE_DEBUG_LOG") == 0) continue; if (n_updates > FWUP_NUM_CAPSULE_UPDATES_MAX) { fwup_warning(L"Ignoring update %s", variable_name); continue; } fwup_info(L"Found update %s", variable_name); _cleanup_update_table FWUP_UPDATE_TABLE *update = fwup_malloc0(sizeof(FWUP_UPDATE_TABLE)); if (update == NULL) return EFI_OUT_OF_RESOURCES; rc = fwup_populate_update_info(variable_name, update); if (EFI_ERROR(rc)) { fwup_delete_variable(variable_name, &fwupdate_guid); fwup_warning(L"Could not populate update info for '%s'", variable_name); return rc; } if (update->info->status & FWUPDATE_ATTEMPT_UPDATE) { fwup_time(&update->info->time_attempted); update->info->status = FWUPDATE_ATTEMPTED; updates[n_updates++] = _steal_pointer(&update); } } *n_updates_out = n_updates; return EFI_SUCCESS; } static EFI_STATUS fwup_search_file(EFI_DEVICE_PATH **file_dp, EFI_FILE_HANDLE *fh) { EFI_DEVICE_PATH *dp, *parent_dp; EFI_GUID sfsp = SIMPLE_FILE_SYSTEM_PROTOCOL; EFI_GUID dpp = DEVICE_PATH_PROTOCOL; UINTN n_handles, count; EFI_STATUS rc; _cleanup_free EFI_FILE_HANDLE *devices = NULL; rc = uefi_call_wrapper(BS->LocateHandleBuffer, 5, ByProtocol, &sfsp, NULL, &n_handles, (EFI_HANDLE **)&devices); if (EFI_ERROR(rc)) { fwup_warning(L"Could not find handles"); return rc; } dp = *file_dp; fwup_debug(L"Searching Device Path: %s...", DevicePathToStr(dp)); parent_dp = DuplicateDevicePath(dp); if (parent_dp == NULL) return EFI_INVALID_PARAMETER; dp = parent_dp; count = 0; while (1) { if (IsDevicePathEnd(dp)) return EFI_INVALID_PARAMETER; if (DevicePathType(dp) == MEDIA_DEVICE_PATH && DevicePathSubType(dp) == MEDIA_FILEPATH_DP) break; dp = NextDevicePathNode(dp); ++count; } SetDevicePathEndNode(dp); fwup_debug(L"Device Path prepared: %s", DevicePathToStr(parent_dp)); for (UINTN i = 0; i < n_handles; i++) { EFI_DEVICE_PATH *path; rc = uefi_call_wrapper(BS->HandleProtocol, 3, devices[i], &dpp, (VOID **)&path); if (EFI_ERROR(rc)) continue; fwup_debug(L"Device supporting SFSP: %s", DevicePathToStr(path)); while (!IsDevicePathEnd(path)) { fwup_debug(L"Comparing: %s and %s", DevicePathToStr(parent_dp), DevicePathToStr(path)); if (LibMatchDevicePaths(path, parent_dp) == TRUE) { *fh = devices[i]; for (UINTN j = 0; j < count; j++) *file_dp = NextDevicePathNode(*file_dp); fwup_debug(L"Match up! Returning %s", DevicePathToStr(*file_dp)); return EFI_SUCCESS; } path = NextDevicePathNode(path); } } fwup_warning(L"Failed to find '%s' DevicePath", DevicePathToStr(*file_dp)); return EFI_UNSUPPORTED; } static EFI_STATUS fwup_open_file(EFI_DEVICE_PATH *dp, EFI_FILE_HANDLE *fh) { CONST UINTN devpath_max_size = 1024; /* arbitrary limit */ EFI_DEVICE_PATH *file_dp = dp; EFI_GUID sfsp = SIMPLE_FILE_SYSTEM_PROTOCOL; EFI_FILE_HANDLE device; EFI_FILE_IO_INTERFACE *drive; EFI_FILE_HANDLE root; EFI_STATUS rc; rc = uefi_call_wrapper(BS->LocateDevicePath, 3, &sfsp, &file_dp, (EFI_HANDLE *)&device); if (EFI_ERROR(rc)) { rc = fwup_search_file(&file_dp, &device); if (EFI_ERROR(rc)) { fwup_warning(L"Could not locate device handle: %r", rc); return rc; } } if (DevicePathType(file_dp) != MEDIA_DEVICE_PATH || DevicePathSubType(file_dp) != MEDIA_FILEPATH_DP) { fwup_warning(L"Could not find appropriate device"); return EFI_UNSUPPORTED; } UINT16 sz16; UINTN sz; CopyMem(&sz16, &file_dp->Length[0], sizeof(sz16)); sz = sz16; sz -= 4; if (sz <= 6 || sz % 2 != 0 || sz > devpath_max_size * sizeof(CHAR16)) { fwup_warning(L"Invalid file device path of size %d", sz); return EFI_INVALID_PARAMETER; } _cleanup_free CHAR16 *filename = fwup_malloc0(sz + sizeof(CHAR16)); CopyMem(filename, (UINT8 *)file_dp + 4, sz); rc = uefi_call_wrapper(BS->HandleProtocol, 3, device, &sfsp, (VOID **)&drive); if (EFI_ERROR(rc)) { fwup_warning(L"Could not open device interface: %r", rc); return rc; } fwup_debug(L"Found device"); rc = uefi_call_wrapper(drive->OpenVolume, 2, drive, &root); if (EFI_ERROR(rc)) { fwup_warning(L"Could not open volume: %r", rc); return rc; } fwup_debug(L"Found volume"); rc = uefi_call_wrapper(root->Open, 5, root, fh, filename, EFI_FILE_MODE_READ, 0); if (EFI_ERROR(rc)) { fwup_warning(L"Could not open file '%s': %r", filename, rc); return rc; } fwup_debug(L"Found file"); return EFI_SUCCESS; } static EFI_STATUS fwup_delete_boot_order(CHAR16 *name, EFI_GUID guid) { UINT16 boot_num; EFI_STATUS rc; UINTN info_size = 0; UINT32 attrs = 0; _cleanup_free VOID *info_ptr = NULL; _cleanup_free UINT16 *new_info_ptr = NULL; UINT8 num_found = FALSE; UINTN new_list_num = 0; /* get boot hex number */ boot_num = xtoi((CHAR16 *)((UINT8 *)name + sizeof(L"Boot"))); rc = fwup_get_variable(L"BootOrder", &guid, &info_ptr, &info_size, &attrs); if (EFI_ERROR(rc)) return rc; new_info_ptr = fwup_malloc(info_size); if (new_info_ptr == NULL) return EFI_OUT_OF_RESOURCES; for (UINTN i = 0; i < (info_size / sizeof(UINT16)) ; i++) { if (((UINT16 *)info_ptr)[i] != boot_num) { new_info_ptr[i] = ((UINT16 *)info_ptr)[i]; new_list_num++; } else { num_found = TRUE; } } /* if not in the BootOrder list, do not update BootOrder */ if (!num_found) return EFI_SUCCESS; rc = uefi_call_wrapper(RT->SetVariable, 5, L"BootOrder", &guid, attrs, new_list_num * sizeof(UINT16), new_info_ptr); if (EFI_ERROR(rc)) { fwup_warning(L"Could not update variable status for '%s': %r", name, rc); return rc; } return rc; } /* TODO: move to gnu-efi: https://github.com/vathpela/gnu-efi/issues/7 */ static BOOLEAN _StrHasPrefix(IN CONST CHAR16 *s1, IN CONST CHAR16 *s2) { while (*s2) { if (*s1 == L'\0' || *s1 != *s2) return FALSE; s1 += 1; s2 += 1; } return TRUE; } static EFI_STATUS fwup_delete_boot_entry(VOID) { EFI_STATUS rc; UINTN variable_name_size = 0; _cleanup_free CHAR16 *variable_name = NULL; EFI_GUID vendor_guid = empty_guid; variable_name = fwup_malloc0(GNVN_BUF_SIZE * 2); if (variable_name == NULL) return EFI_OUT_OF_RESOURCES; while (1) { variable_name_size = GNVN_BUF_SIZE; rc = uefi_call_wrapper(RT->GetNextVariableName, 3, &variable_name_size, variable_name, &vendor_guid); if (rc == EFI_NOT_FOUND) break; /* ignore any huge names */ if (rc == EFI_BUFFER_TOO_SMALL) continue; if (EFI_ERROR(rc)) { fwup_warning(L"Could not get variable name: %r", rc); return rc; } /* check if the variable name is Boot#### */ if (CompareGuid(&vendor_guid, &global_variable_guid) != 0) continue; if (StrCmp(variable_name, L"Boot") != 0) continue; UINTN info_size = 0; _cleanup_free VOID *info_ptr = NULL; /* get the data */ rc = fwup_get_variable(variable_name, &vendor_guid, &info_ptr, &info_size, NULL); if (EFI_ERROR(rc)) return rc; if (info_size < sizeof(EFI_LOAD_OPTION)) continue; /* * check if the boot path created by fwupdate, * check with EFI_LOAD_OPTION description */ EFI_LOAD_OPTION *load_op = (EFI_LOAD_OPTION *) info_ptr; if (_StrHasPrefix(load_op->description, L"Linux Firmware Updater") || _StrHasPrefix(load_op->description, L"Linux-Firmware-Updater")) { /* delete the boot path from BootOrder list */ rc = fwup_delete_boot_order(variable_name, vendor_guid); if (EFI_ERROR(rc)) { fwup_warning(L"Failed to delete boot entry from BootOrder"); return rc; } rc = fwup_delete_variable(variable_name, &vendor_guid); if (EFI_ERROR(rc)) { fwup_warning(L"Failed to delete boot entry"); return rc; } break; } } return EFI_SUCCESS; } static EFI_STATUS fwup_get_gop_mode(UINT32 *mode, EFI_HANDLE loaded_image) { EFI_HANDLE *handles, gop_handle; UINTN num_handles; EFI_STATUS status; EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; EFI_GRAPHICS_OUTPUT_PROTOCOL *gop; VOID *iface; status = LibLocateHandle(ByProtocol, &gop_guid, NULL, &num_handles, &handles); if (EFI_ERROR(status)) return status; if (handles == NULL || num_handles == 0) return EFI_UNSUPPORTED; for (UINTN i = 0; i < num_handles; i++) { gop_handle = handles[i]; status = uefi_call_wrapper(BS->OpenProtocol, 6, gop_handle, &gop_guid, &iface, loaded_image, 0, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) continue; gop = (EFI_GRAPHICS_OUTPUT_PROTOCOL *)iface; *mode = gop->Mode->Mode; return EFI_SUCCESS; } return EFI_UNSUPPORTED; } static inline void fwup_update_ux_capsule_checksum(UX_CAPSULE_HEADER *payload_hdr) { UINT8 *buf = (UINT8 *)payload_hdr; UINT8 sum = 0; payload_hdr->checksum = 0; for (UINTN i = 0; i < sizeof(*payload_hdr); i++) sum = (UINT8) (sum + buf[i]); payload_hdr->checksum = sum; } static EFI_STATUS fwup_check_gop_for_ux_capsule(EFI_HANDLE loaded_image, EFI_CAPSULE_HEADER *capsule) { UX_CAPSULE_HEADER *payload_hdr; EFI_STATUS rc; payload_hdr = (UX_CAPSULE_HEADER *) (((UINT8 *) capsule) + capsule->HeaderSize); rc = fwup_get_gop_mode(&payload_hdr->mode, loaded_image); if (EFI_ERROR(rc)) return EFI_UNSUPPORTED; fwup_update_ux_capsule_checksum(payload_hdr); return EFI_SUCCESS; } static EFI_STATUS fwup_add_update_capsule(FWUP_UPDATE_TABLE *update, EFI_CAPSULE_HEADER **capsule_out, EFI_CAPSULE_BLOCK_DESCRIPTOR *cbd_out, EFI_HANDLE loaded_image) { EFI_STATUS rc; EFI_FILE_HANDLE fh = NULL; UINT8 *fbuf = NULL; UINTN fsize = 0; EFI_CAPSULE_HEADER *capsule; UINTN cbd_len; EFI_PHYSICAL_ADDRESS cbd_data; EFI_CAPSULE_HEADER *cap_out; rc = fwup_open_file((EFI_DEVICE_PATH *)update->info->dp_buf, &fh); if (EFI_ERROR(rc)) return rc; rc = fwup_read_file(fh, &fbuf, &fsize); if (EFI_ERROR(rc)) return rc; uefi_call_wrapper(fh->Close, 1, fh); if (fsize < sizeof(EFI_CAPSULE_HEADER)) { fwup_warning(L"Invalid capsule size %d", fsize); return EFI_INVALID_PARAMETER; } fwup_debug(L"Read file; %d bytes", fsize); fwup_debug(L"updates guid: %g", &update->info->guid); fwup_debug(L"File guid: %g", fbuf); cbd_len = fsize; cbd_data = (EFI_PHYSICAL_ADDRESS)(UINTN)fbuf; capsule = cap_out = (EFI_CAPSULE_HEADER *)fbuf; if (cap_out->Flags == 0 && CompareGuid(&update->info->guid, &ux_capsule_guid) != 0) { #if defined(__aarch64__) cap_out->Flags |= update->info->capsule_flags; #else cap_out->Flags |= update->info->capsule_flags | CAPSULE_FLAGS_PERSIST_ACROSS_RESET | CAPSULE_FLAGS_INITIATE_RESET; #endif } if (CompareGuid(&update->info->guid, &ux_capsule_guid) == 0) { fwup_debug(L"Checking GOP for ux capsule"); rc = fwup_check_gop_for_ux_capsule(loaded_image, capsule); if (EFI_ERROR(rc)) return EFI_UNSUPPORTED; } cbd_out->Length = cbd_len; cbd_out->Union.DataBlock = cbd_data; *capsule_out = cap_out; return EFI_SUCCESS; } static EFI_STATUS fwup_apply_capsules(EFI_CAPSULE_HEADER **capsules, EFI_CAPSULE_BLOCK_DESCRIPTOR *cbd, UINTN num_updates, EFI_RESET_TYPE *reset) { UINT64 max_capsule_size; EFI_STATUS rc; /* still try to apply capsule on failure */ rc = fwup_delete_boot_entry(); if (EFI_ERROR(rc)) fwup_warning(L"Could not delete boot entry: %r", rc); rc = uefi_call_wrapper(RT->QueryCapsuleCapabilities, 4, capsules, num_updates, &max_capsule_size, reset); if (EFI_ERROR(rc)) { fwup_warning(L"Could not query capsule capabilities: %r", rc); return rc; } fwup_debug(L"QueryCapsuleCapabilities: %r max: %ld reset:%d", rc, max_capsule_size, *reset); fwup_debug(L"Capsules: %d", num_updates); fwup_msleep(1 * SECONDS); rc = uefi_call_wrapper(RT->UpdateCapsule, 3, capsules, num_updates, (EFI_PHYSICAL_ADDRESS)(UINTN)cbd); if (EFI_ERROR(rc)) { fwup_warning(L"Could not apply capsule update: %r", rc); return rc; } return EFI_SUCCESS; } static EFI_STATUS fwup_set_update_statuses(FWUP_UPDATE_TABLE **updates) { EFI_STATUS rc; for (UINTN i = 0; i < FWUP_NUM_CAPSULE_UPDATES_MAX; i++) { if (updates[i] == NULL || updates[i]->name == NULL) break; rc = fwup_set_variable(updates[i]->name, &fwupdate_guid, updates[i]->info, updates[i]->size, updates[i]->attrs); if (EFI_ERROR(rc)) { fwup_warning(L"Could not update variable status for '%s': %r", updates[i]->name, rc); return rc; } } return EFI_SUCCESS; } EFI_GUID SHIM_LOCK_GUID = {0x605dab50,0xe046,0x4300,{0xab,0xb6,0x3d,0xd8,0x10,0xdd,0x8b,0x23}}; static VOID __attribute__((__optimize__("0"))) fwup_debug_hook(VOID) { EFI_GUID guid = SHIM_LOCK_GUID; UINTN data = 0; UINTN data_size = 1; EFI_STATUS efi_status; UINT32 attrs; register volatile int x = 0; extern char _text UNUSED, _data UNUSED; /* shim has done whatever is needed to get a debugger attached */ efi_status = uefi_call_wrapper(RT->GetVariable, 5, L"SHIM_DEBUG", &guid, &attrs, &data_size, &data); if (EFI_ERROR(efi_status) || data != 1) { efi_status = uefi_call_wrapper(RT->GetVariable, 5, L"FWUPDATE_VERBOSE", &fwupdate_guid, &attrs, &data_size, &data); if (EFI_ERROR(efi_status) || data != 1) return; fwup_debug_set_enabled(TRUE); return; } fwup_debug_set_enabled(TRUE); if (x) return; x = 1; fwup_info(L"add-symbol-file "DEBUGDIR L"fwupdate.efi.debug %p -s .data %p", &_text, &_data); } EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) { EFI_STATUS rc; UINTN i, n_updates = 0; EFI_RESET_TYPE reset_type = EfiResetWarm; _cleanup_free FWUP_UPDATE_TABLE **updates = NULL; InitializeLib(image, systab); /* if SHIM_DEBUG is set, fwup_info info for our attached debugger */ fwup_debug_hook(); /* step 1: find and validate update state variables */ /* XXX TODO: * 1) survey the reset types first, and separate into groups * according to them * 2) if there's more than one, mirror BootCurrent back into BootNext * so we can do multiple runs * 3) only select the ones from one type for the first go */ updates = fwup_new0(FWUP_UPDATE_TABLE *, FWUP_NUM_CAPSULE_UPDATES_MAX); if (updates == NULL) return EFI_OUT_OF_RESOURCES; rc = fwup_populate_update_table(updates, &n_updates); if (EFI_ERROR(rc)) { fwup_warning(L"Could not find updates: %r", rc); return rc; } if (n_updates == 0) { fwup_warning(L"No updates to process. Called in error?"); return EFI_INVALID_PARAMETER; } /* step 2: Build our data structure and add the capsules to it */ _cleanup_free EFI_CAPSULE_HEADER **capsules = NULL; capsules = fwup_new0(EFI_CAPSULE_HEADER *, n_updates + 1); EFI_CAPSULE_BLOCK_DESCRIPTOR *cbd_data; UINTN j = 0; cbd_data = fwup_malloc_raw(sizeof(EFI_CAPSULE_BLOCK_DESCRIPTOR)*(n_updates+1)); if (cbd_data == NULL) return EFI_OUT_OF_RESOURCES; for (i = 0; i < n_updates; i++) { fwup_info(L"Adding new capsule"); rc = fwup_add_update_capsule(updates[i], &capsules[j], &cbd_data[j], image); if (EFI_ERROR(rc)) { /* ignore a failing UX capsule */ if (rc == EFI_UNSUPPORTED && CompareGuid(&updates[i]->info->guid, &ux_capsule_guid) == 0) { fwup_debug(L"GOP unsuitable: %r", rc); continue; } fwup_warning(L"Could not build update list: %r", rc); return rc; } j++; } n_updates = j; fwup_debug(L"n_updates: %d", n_updates); cbd_data[i].Length = 0; cbd_data[i].Union.ContinuationPointer = 0; /* step 3: update the state variables */ rc = fwup_set_update_statuses(updates); if (EFI_ERROR(rc)) { fwup_warning(L"Could not set update status: %r", rc); return rc; } /* step 4: apply the capsules */ rc = fwup_apply_capsules(capsules, cbd_data, n_updates, &reset_type); if (EFI_ERROR(rc)) { fwup_warning(L"Could not apply capsules: %r", rc); return rc; } /* step 5: if #4 didn't reboot us, do it manually */ fwup_info(L"Reset System"); fwup_msleep(5 * SECONDS); if (fwup_debug_get_enabled()) fwup_msleep(30 * SECONDS); uefi_call_wrapper(RT->ResetSystem, 4, reset_type, EFI_SUCCESS, 0, NULL); return EFI_SUCCESS; } fwupd-1.2.14/plugins/uefi/efi/generate_binary.py000077500000000000000000000035171402665037500216310ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2021 Javier Martinez Canillas # Copyright (C) 2021 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # pylint: disable=missing-docstring, invalid-name import subprocess import sys import argparse def _run_objcopy(args): argv = [ args.objcopy, "-j", ".text", "-j", ".sbat", "-j", ".sdata", "-j", ".data", "-j", ".dynamic", "-j", ".dynsym", "-j", ".rel*", args.infile, args.outfile, ] # aarch64 and arm32 don't have an EFI capable objcopy # Use 'binary' instead, and add required symbols manually if args.arch in ["aarch64", "arm"]: argv.extend(["-O", "binary"]) else: argv.extend(["--target", "efi-app-{}".format(args.arch)]) try: subprocess.run(argv, check=True) except FileNotFoundError as e: print(str(e)) sys.exit(1) def _run_genpeimg(args): # this is okay if it does not exist argv = [ "genpeimg", "-d", "+d", "+n", "-d", "+s", args.outfile, ] try: subprocess.run(argv, check=True) except FileNotFoundError as _: pass if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "--objcopy", default="objcopy", help="Binary file to use for objcopy", ) parser.add_argument( "--arch", default="x86_64", help="EFI architecture", ) parser.add_argument( "infile", help="Input file", ) parser.add_argument( "outfile", help="Output file", ) _args = parser.parse_args() _run_objcopy(_args) _run_genpeimg(_args) sys.exit(0) fwupd-1.2.14/plugins/uefi/efi/generate_sbat.py000077500000000000000000000074351402665037500213010ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2021 Javier Martinez Canillas # Copyright (C) 2021 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # pylint: disable=missing-docstring, invalid-name import subprocess import sys import argparse import tempfile def _generate_sbat(args): """ append SBAT metadata """ FWUPD_SUMMARY = "Firmware update daemon" FWUPD_URL = "https://github.com/fwupd/fwupd" subprocess.run( [args.cc, "-x", "c", "-c", "-o", args.outfile, "/dev/null"], check=True ) # not specified if not args.sbat_distro_id: return with tempfile.NamedTemporaryFile() as sfd: # spec sfd.write( "{0},{1},{2},{0},{1},{3}\n".format( "sbat", args.sbat_version, "UEFI shim", "https://github.com/rhboot/shim/blob/main/SBAT.md", ).encode() ) # fwupd sfd.write( "{0},{1},{2},{0},{3},{4}\n".format( args.project_name, args.sbat_generation, "Firmware update daemon", args.project_version, FWUPD_URL, ).encode() ) # distro specifics, falling back to the project defaults sfd.write( "{0}-{1},{2},{3},{4},{5},{6}\n".format( args.project_name, args.sbat_distro_id, args.sbat_distro_generation or args.sbat_generation, args.sbat_distro_summary or FWUPD_SUMMARY, args.sbat_distro_pkgname, args.sbat_distro_version or args.project_version, args.sbat_distro_url or FWUPD_URL, ).encode() ) # all written sfd.seek(0) # add a section to the object; use `objdump -s -j .sbat` to verify argv = [ args.objcopy, "--add-section", ".sbat={}".format(sfd.name), "--set-section-flags", ".sbat=contents,alloc,load,readonly,data", args.outfile, ] subprocess.run(argv, check=True) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "--cc", default="gcc", help="Compiler to use for generating sbat object", ) parser.add_argument( "--objcopy", default="objcopy", help="Binary file to use for objcopy", ) parser.add_argument( "--project-name", help="SBAT project name", ) parser.add_argument( "--project-version", help="SBAT project version", ) parser.add_argument( "--sbat-version", default=1, type=int, help="SBAT version", ) parser.add_argument( "--sbat-generation", default=1, type=int, help="SBAT generation", ) parser.add_argument( "--sbat-distro-id", default=None, help="SBAT distribution ID" ) parser.add_argument( "--sbat-distro-generation", default=None, type=int, help="SBAT distribution generation", ) parser.add_argument( "--sbat-distro-summary", default=None, help="SBAT distribution summary", ) parser.add_argument( "--sbat-distro-pkgname", default=None, help="SBAT distribution package name", ) parser.add_argument( "--sbat-distro-version", default=None, help="SBAT distribution version", ) parser.add_argument( "--sbat-distro-url", default=None, help="SBAT distribution URL", ) parser.add_argument( "outfile", help="Output file", ) _args = parser.parse_args() _generate_sbat(_args) sys.exit(0) fwupd-1.2.14/plugins/uefi/efi/lds/000077500000000000000000000000001402665037500166725ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/efi/lds/elf_aarch64_efi.lds000066400000000000000000000024731402665037500223050ustar00rootroot00000000000000OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") OUTPUT_ARCH(aarch64) ENTRY(_start) SECTIONS { .text 0x0 : { _text = .; *(.text.head) *(.text) *(.text.*) *(.gnu.linkonce.t.*) *(.srodata) *(.rodata*) . = ALIGN(16); _etext = .; } . = ALIGN(4096); .dynamic : { *(.dynamic) } . = ALIGN(4096); .note.gnu.build-id : { *(.note.gnu.build-id) } . = ALIGN(4096); .data.ident : { *(.data.ident) } . = ALIGN(4096); .data : { _data = .; *(.sdata) *(.data) *(.data1) *(.data.*) *(.got.plt) *(.got) /* the EFI loader doesn't seem to like a .bss section, so we stick it all into .data: */ . = ALIGN(16); _bss = .; *(.sbss) *(.scommon) *(.dynbss) *(.bss) *(COMMON) . = ALIGN(16); _bss_end = .; } . = ALIGN(4096); .sbat : { _sbat = .; *(.sbat) *(.sbat.*) _esbat = .; } . = ALIGN(4096); .rela : { *(.rela.dyn) *(.rela.plt) *(.rela.got) *(.rela.data) *(.rela.data*) } _edata = .; _data_size = . - _data; . = ALIGN(4096); .dynsym : { *(.dynsym) } . = ALIGN(4096); .dynstr : { *(.dynstr) } . = ALIGN(4096); /DISCARD/ : { *(.rel.reloc) *(.eh_frame) *(.note.GNU-stack) } .comment 0 : { *(.comment) } } fwupd-1.2.14/plugins/uefi/efi/lds/elf_arm_efi.lds000066400000000000000000000023551402665037500216330ustar00rootroot00000000000000OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { .text 0x0 : { _text = .; *(.text.head) *(.text) *(.text.*) *(.gnu.linkonce.t.*) *(.srodata) *(.rodata*) . = ALIGN(16); } _etext = .; _text_size = . - _text; .dynamic : { *(.dynamic) } .data : { _data = .; *(.sdata) *(.data) *(.data1) *(.data.*) *(.got.plt) *(.got) /* the EFI loader doesn't seem to like a .bss section, so we stick it all into .data: */ . = ALIGN(16); _bss = .; *(.sbss) *(.scommon) *(.dynbss) *(.bss) *(.bss.*) *(COMMON) . = ALIGN(16); _bss_end = .; } . = ALIGN(4096); .sbat : { _sbat = .; *(.sbat) *(.sbat.*) _esbat = .; } . = ALIGN(4096); .rel.dyn : { *(.rel.dyn) } .rel.plt : { *(.rel.plt) } .rel.got : { *(.rel.got) } .rel.data : { *(.rel.data) *(.rel.data*) } _edata = .; _data_size = . - _etext; . = ALIGN(4096); .dynsym : { *(.dynsym) } . = ALIGN(4096); .dynstr : { *(.dynstr) } . = ALIGN(4096); .note.gnu.build-id : { *(.note.gnu.build-id) } /DISCARD/ : { *(.rel.reloc) *(.eh_frame) *(.note.GNU-stack) } .comment 0 : { *(.comment) } } fwupd-1.2.14/plugins/uefi/efi/lds/elf_ia32_efi.lds000066400000000000000000000025341402665037500216110ustar00rootroot00000000000000OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") OUTPUT_ARCH(i386) ENTRY(_start) SECTIONS { . = 0; ImageBase = .; .hash : { *(.hash) } /* this MUST come first! */ . = ALIGN(4096); .text : { _text = .; *(.text) *(.text.*) *(.gnu.linkonce.t.*) _etext = .; } .reloc : { *(.reloc) } . = ALIGN(4096); .note.gnu.build-id : { *(.note.gnu.build-id) } . = ALIGN(4096); .data.ident : { *(.data.ident) } . = ALIGN(4096); .data : { _data = .; *(.rodata*) *(.data) *(.data1) *(.data.*) *(.sdata) *(.got.plt) *(.got) /* the EFI loader doesn't seem to like a .bss section, so we stick it all into .data: */ *(.sbss) *(.scommon) *(.dynbss) *(.bss) *(COMMON) } . = ALIGN(4096); .sbat : { _sbat = .; *(.sbat) *(.sbat.*) _esbat = .; } . = ALIGN(4096); .dynamic : { *(.dynamic) } . = ALIGN(4096); .rel : { *(.rel.data) *(.rel.data.*) *(.rel.got) *(.rel.stab) *(.data.rel.ro.local) *(.data.rel.local) *(.data.rel.ro) *(.data.rel*) } _edata = .; _data_size = . - _data; . = ALIGN(4096); .dynsym : { *(.dynsym) } . = ALIGN(4096); .dynstr : { *(.dynstr) } . = ALIGN(4096); /DISCARD/ : { *(.rel.reloc) *(.eh_frame) *(.note.GNU-stack) } .comment 0 : { *(.comment) } } fwupd-1.2.14/plugins/uefi/efi/lds/elf_x86_64_efi.lds000066400000000000000000000026621402665037500220130ustar00rootroot00000000000000/* Same as elf_x86_64_fbsd_efi.lds, except for OUTPUT_FORMAT below - KEEP IN SYNC */ OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64") OUTPUT_ARCH(i386:x86-64) ENTRY(_start) SECTIONS { . = 0; ImageBase = .; .hash : { *(.hash) } /* this MUST come first! */ . = ALIGN(4096); .eh_frame : { *(.eh_frame) } . = ALIGN(4096); .text : { _text = .; *(.text) _etext = .; } . = ALIGN(4096); .reloc : { *(.reloc) } . = ALIGN(4096); .note.gnu.build-id : { *(.note.gnu.build-id) } . = ALIGN(4096); .data.ident : { *(.data.ident) } . = ALIGN(4096); .data : { _data = .; *(.rodata*) *(.got.plt) *(.got) *(.data*) *(.sdata) /* the EFI loader doesn't seem to like a .bss section, so we stick it all into .data: */ *(.sbss) *(.scommon) *(.dynbss) *(.bss) *(COMMON) *(.rel.local) } . = ALIGN(4096); .sbat : { _sbat = .; *(.sbat) *(.sbat.*) _esbat = .; } . = ALIGN(4096); .dynamic : { *(.dynamic) } . = ALIGN(4096); .rela : { *(.rela.data*) *(.rela.got*) *(.rela.stab*) } _edata = .; _data_size = . - _data; . = ALIGN(4096); .dynsym : { *(.dynsym) } . = ALIGN(4096); .dynstr : { *(.dynstr) } . = ALIGN(4096); .ignored.reloc : { *(.rela.reloc) *(.eh_frame) *(.note.GNU-stack) } .comment 0 : { *(.comment) } .note.gnu.build-id : { *(.note.gnu.build-id) } } fwupd-1.2.14/plugins/uefi/efi/meson.build000066400000000000000000000212331402665037500202530ustar00rootroot00000000000000efi_cc = get_option('efi-cc') efi_ld = get_option('efi-ld') efi_objcopy = get_option('efi-objcopy') efi_ldsdir = get_option('efi-ldsdir') efi_incdir = get_option('efi-includedir') gnu_efi_path_arch = '' foreach name : [gnu_efi_arch, EFI_MACHINE_TYPE_NAME] if (gnu_efi_path_arch == '' and name != '' and cc.has_header('@0@/@1@/efibind.h'.format(efi_incdir, name))) gnu_efi_path_arch = name endif endforeach if gnu_efi_path_arch != '' and EFI_MACHINE_TYPE_NAME == '' error('gnu-efi is available, but EFI_MACHINE_TYPE_NAME is unknown') endif efi_libdir = get_option('efi-libdir') if efi_libdir == '' cmd = 'cd /usr/lib/$(@0@ -print-multi-os-directory) && pwd'.format(efi_cc) ret = run_command('sh', '-c', cmd) if ret.returncode() == 0 efi_libdir = ret.stdout().strip() endif endif have_gnu_efi = gnu_efi_path_arch != '' and efi_libdir != '' if not have_gnu_efi error('gnu-efi support requested, but headers were not found') endif arch_lds = 'efi.lds' arch_crt = 'crt0.o' if efi_ldsdir == '' efi_ldsdir = join_paths(efi_libdir, 'gnuefi', gnu_efi_path_arch) cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds)) if cmd.returncode() != 0 arch_lds = 'elf_@0@_efi.lds'.format(gnu_efi_path_arch) arch_crt = 'crt0-efi-@0@.o'.format(gnu_efi_path_arch) efi_ldsdir = join_paths(efi_libdir, 'gnuefi') cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds)) endif if cmd.returncode() != 0 efi_ldsdir = efi_libdir cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds)) if cmd.returncode() != 0 error('Cannot find @0@'.format(arch_lds)) endif endif else cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds)) if cmd.returncode() != 0 arch_lds = 'elf_@0@_efi.lds'.format(gnu_efi_path_arch) arch_crt = 'crt0-efi-@0@.o'.format(gnu_efi_path_arch) cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds)) endif if cmd.returncode() != 0 error('Cannot find @0@'.format(arch_lds)) endif endif # is the system linker script new enough to know about SBAT? # i.e. gnu-efi with https://github.com/vathpela/gnu-efi/pull/14 has been installed efi_crtdir = efi_ldsdir if get_option('efi_sbat_distro_id') != '' cmd = run_command('grep', '-q', 'sbat', join_paths(efi_ldsdir, arch_lds)) if cmd.returncode() != 0 warning('Cannot find SBAT section in @0@, using local copy'.format(join_paths(efi_ldsdir, arch_lds))) efi_ldsdir = join_paths(meson.current_source_dir(), 'lds') endif endif message('efi-libdir: "@0@"'.format(efi_libdir)) message('efi-ldsdir: "@0@"'.format(efi_ldsdir)) message('efi-crtdir: "@0@"'.format(efi_crtdir)) message('efi-includedir: "@0@"'.format(efi_incdir)) debugdir = join_paths (libdir, 'debug') compile_args = ['-Og', '-g3', '--param=ssp-buffer-size=4', '-fexceptions', '-Wall', '-Wextra', '-Wvla', '-std=gnu11', '-fpic', '-fshort-wchar', '-ffreestanding', '-fno-strict-aliasing', '-fno-stack-protector', '-fno-stack-check', '-fno-merge-constants', '-Wsign-compare', '-Wno-missing-field-initializers', '-Wno-address-of-packed-member', '-grecord-gcc-switches', '-DDEBUGDIR="@0@"'.format(debugdir), '-isystem', efi_incdir, '-isystem', join_paths(efi_incdir, gnu_efi_path_arch)] if get_option('werror') compile_args += '-Werror' endif if efi_arch == 'x86_64' compile_args += ['-mno-red-zone', '-mno-sse', '-mno-mmx', '-DEFI_FUNCTION_WRAPPER', '-DGNU_EFI_USE_MS_ABI'] elif efi_arch == 'ia32' compile_args += ['-mno-sse', '-mno-mmx', '-mno-red-zone', '-m32'] # no special cases for aarch64 or arm endif efi_ldflags = ['-T', join_paths(efi_ldsdir, arch_lds), '-shared', '-Bsymbolic', '-nostdlib', '-znocombreloc', '-L', efi_crtdir, '-L', efi_libdir, join_paths(efi_crtdir, arch_crt)] if efi_arch == 'aarch64' or efi_arch == 'arm' # Aarch64 and ARM32 don't have an EFI capable objcopy. Use 'binary' # instead, and add required symbols manually. efi_ldflags += ['--defsym=EFI_SUBSYSTEM=0xa'] efi_format = ['-O', 'binary'] else efi_format = ['--target=efi-app-@0@'.format(gnu_efi_arch)] endif libgcc_file_name = run_command(efi_cc, '-print-libgcc-file-name').stdout().strip() efi_name = 'fwupd@0@.efi'.format(EFI_MACHINE_TYPE_NAME) o_file1 = custom_target('fwupdate.o', input : 'fwupdate.c', output : 'fwupdate.o', command : [efi_cc, '-c', '@INPUT@', '-o', '@OUTPUT@'] + compile_args) o_file2 = custom_target('fwup-debug.o', input : 'fwup-debug.c', output : 'fwup-debug.o', command : [efi_cc, '-c', '@INPUT@', '-o', '@OUTPUT@'] + compile_args) o_file3 = custom_target('fwup-efi.o', input : 'fwup-efi.c', output : 'fwup-efi.o', command : [efi_cc, '-c', '@INPUT@', '-o', '@OUTPUT@'] + compile_args) o_file4 = custom_target('fwup-common.o', input : 'fwup-common.c', output : 'fwup-common.o', command : [efi_cc, '-c', '@INPUT@', '-o', '@OUTPUT@'] + compile_args) o_file5 = custom_target('fwup-sbat.o', output : 'fwup-sbat.o', command : [ join_paths(meson.current_source_dir(), 'generate_sbat.py'), '@OUTPUT@', '--cc', efi_cc, '--objcopy', efi_objcopy, '--project-name', meson.project_name(), '--project-version', meson.project_version(), '--sbat-version', '1', '--sbat-generation', '@0@'.format(get_option('efi_sbat_fwupd_generation')), '--sbat-distro-id', get_option('efi_sbat_distro_id'), '--sbat-distro-generation', '0', '--sbat-distro-summary', get_option('efi_sbat_distro_summary'), '--sbat-distro-pkgname', get_option('efi_sbat_distro_pkgname'), '--sbat-distro-version', get_option('efi_sbat_distro_version'), '--sbat-distro-url', get_option('efi_sbat_distro_url'), ]) so = custom_target('fwup.so', input : [o_file1, o_file2, o_file3, o_file4, o_file5], output : 'fwup.so', command : [efi_ld, '-o', '@OUTPUT@'] + efi_ldflags + ['@INPUT@'] + ['-lefi', '-lgnuefi', libgcc_file_name]) # sanity check the packager set this to *SOMETHING* if get_option('efi_sbat_distro_id') == '' warning('-Defi_sbat_distro_id is unset, see plugins/uefi-capsule/README.md') endif app = custom_target(efi_name, input : so, output : efi_name, command : [ join_paths(meson.current_source_dir(), 'generate_binary.py'), '@INPUT@', '@OUTPUT@', '--arch', gnu_efi_arch, '--objcopy', efi_objcopy, ], install : true, install_dir : efi_app_location) dbg = custom_target('efi_debug', input : so, output : efi_name + '.debug', command : [efi_objcopy, '-j', '.text', '-j', '.sdata', '-j', '.data', '-j', '.dynamic', '-j', '.dynsym', '-j', '.rel*', '-j', '.rela*', '-j', '.reloc', '-j', '.eh_frame', '-j', '.debug*', '-j', '.note.gnu.build-id'] + efi_format + ['@INPUT@', '@OUTPUT@'], install : false, install_dir : debugdir) fwupd-1.2.14/plugins/uefi/fu-plugin-uefi.c000066400000000000000000000706331402665037500203560ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-plugin-vfuncs.h" #include "fu-uefi-bgrt.h" #include "fu-uefi-common.h" #include "fu-uefi-device.h" #include "fu-uefi-vars.h" #ifndef HAVE_GIO_2_55_0 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUnixMountEntry, g_unix_mount_free) #pragma clang diagnostic pop #endif struct FuPluginData { gchar *esp_path; gboolean require_shim_for_sb; FuUefiBgrt *bgrt; }; void fu_plugin_init (FuPlugin *plugin) { FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); data->bgrt = fu_uefi_bgrt_new (); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "upower"); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "org.uefi.capsule"); fu_plugin_add_compile_version (plugin, "com.redhat.efivar", EFIVAR_LIBRARY_VERSION); fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); g_free (data->esp_path); g_object_unref (data->bgrt); } gboolean fu_plugin_clear_results (FuPlugin *plugin, FuDevice *device, GError **error) { FuUefiDevice *device_uefi = FU_UEFI_DEVICE (device); return fu_uefi_device_clear_status (device_uefi, error); } gboolean fu_plugin_get_results (FuPlugin *plugin, FuDevice *device, GError **error) { FuUefiDevice *device_uefi = FU_UEFI_DEVICE (device); FuUefiDeviceStatus status = fu_uefi_device_get_status (device_uefi); const gchar *tmp; g_autofree gchar *err_msg = NULL; g_autofree gchar *version_str = NULL; /* trivial case */ if (status == FU_UEFI_DEVICE_STATUS_SUCCESS) { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_SUCCESS); return TRUE; } /* something went wrong */ if (status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_AC || status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT) { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED_TRANSIENT); } else { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED); } version_str = g_strdup_printf ("%u", fu_uefi_device_get_version_error (device_uefi)); tmp = fu_uefi_device_status_to_string (status); if (tmp == NULL) { err_msg = g_strdup_printf ("failed to update to %s", version_str); } else { err_msg = g_strdup_printf ("failed to update to %s: %s", version_str, tmp); } fu_device_set_update_error (device, err_msg); return TRUE; } static GBytes * fu_plugin_uefi_get_splash_data (guint width, guint height, GError **error) { const gchar * const *langs = g_get_language_names (); const gchar *localedir = LOCALEDIR; const gsize chunk_size = 1024 * 1024; gsize buf_idx = 0; gsize buf_sz = chunk_size; gssize len; g_autofree gchar *basename = NULL; g_autofree guint8 *buf = NULL; g_autoptr(GBytes) compressed_data = NULL; g_autoptr(GConverter) conv = NULL; g_autoptr(GInputStream) stream_compressed = NULL; g_autoptr(GInputStream) stream_raw = NULL; /* ensure this is sane */ if (!g_str_has_prefix (localedir, "/")) localedir = "/usr/share/locale"; /* find the closest locale match, falling back to `en` and `C` */ basename = g_strdup_printf ("fwupd-%u-%u.bmp.gz", width, height); for (guint i = 0; langs[i] != NULL; i++) { g_autofree gchar *fn = NULL; if (g_str_has_suffix (langs[i], ".UTF-8")) continue; fn = g_build_filename (localedir, langs[i], "LC_IMAGES", basename, NULL); if (g_file_test (fn, G_FILE_TEST_EXISTS)) { compressed_data = fu_common_get_contents_bytes (fn, error); if (compressed_data == NULL) return NULL; break; } g_debug ("no %s found", fn); } /* we found nothing */ if (compressed_data == NULL) { g_autofree gchar *tmp = g_strjoinv (",", (gchar **) langs); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get splash file for %s in %s", tmp, localedir); return NULL; } /* decompress data */ stream_compressed = g_memory_input_stream_new_from_bytes (compressed_data); conv = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP)); stream_raw = g_converter_input_stream_new (stream_compressed, conv); buf = g_malloc0 (buf_sz); while ((len = g_input_stream_read (stream_raw, buf + buf_idx, buf_sz - buf_idx, NULL, error)) > 0) { buf_idx += len; if (buf_sz - buf_idx < chunk_size) { buf_sz += chunk_size; buf = g_realloc (buf, buf_sz); } } if (len < 0) { g_prefix_error (error, "failed to decompress file: "); return NULL; } g_debug ("decompressed image to %" G_GSIZE_FORMAT "kb", buf_idx / 1024); return g_bytes_new_take (g_steal_pointer (&buf), buf_idx); } static guint8 fu_plugin_uefi_calc_checksum (const guint8 *buf, gsize sz) { guint8 csum = 0; for (gsize i = 0; i < sz; i++) csum += buf[i]; return csum; } static gboolean fu_plugin_uefi_write_splash_data (FuPlugin *plugin, FuDevice *device, GBytes *blob, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); guint32 screen_x, screen_y; gsize buf_size = g_bytes_get_size (blob); gssize size; guint32 height, width; guint8 csum = 0; efi_ux_capsule_header_t header = { 0 }; efi_capsule_header_t capsule_header = { .flags = EFI_CAPSULE_HEADER_FLAGS_PERSIST_ACROSS_RESET, .guid = efi_guid_ux_capsule, .header_size = sizeof(efi_capsule_header_t), .capsule_image_size = 0 }; g_autofree gchar *fn = NULL; g_autofree gchar *directory = NULL; g_autofree gchar *basename = NULL; g_autoptr(GFile) ofile = NULL; g_autoptr(GOutputStream) ostream = NULL; /* get screen dimensions */ if (!fu_uefi_get_framebuffer_size (&screen_x, &screen_y, error)) return FALSE; if (!fu_uefi_get_bitmap_size ((const guint8 *) g_bytes_get_data (blob, NULL), buf_size, &width, &height, error)) { g_prefix_error (error, "splash invalid: "); return FALSE; } /* save to a predicatable filename */ directory = fu_uefi_get_esp_path_for_os (data->esp_path); basename = g_strdup_printf ("fwupd-%s.cap", FU_UEFI_VARS_GUID_UX_CAPSULE); fn = g_build_filename (directory, "fw", basename, NULL); if (!fu_common_mkdir_parent (fn, error)) return FALSE; ofile = g_file_new_for_path (fn); ostream = G_OUTPUT_STREAM (g_file_replace (ofile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)); if (ostream == NULL) return FALSE; capsule_header.capsule_image_size = g_bytes_get_size (blob) + sizeof(efi_capsule_header_t) + sizeof(efi_ux_capsule_header_t); header.version = 1; header.image_type = 0; header.reserved = 0; header.x_offset = (screen_x / 2) - (width / 2); header.y_offset = fu_uefi_bgrt_get_yoffset (data->bgrt) + fu_uefi_bgrt_get_height (data->bgrt); /* header, payload and image has to add to zero */ csum += fu_plugin_uefi_calc_checksum ((guint8 *) &capsule_header, sizeof(capsule_header)); csum += fu_plugin_uefi_calc_checksum ((guint8 *) &header, sizeof(header)); csum += fu_plugin_uefi_calc_checksum (g_bytes_get_data (blob, NULL), g_bytes_get_size (blob)); header.checksum = 0x100 - csum; /* write capsule file */ size = g_output_stream_write (ostream, &capsule_header, capsule_header.header_size, NULL, error); if (size < 0) return FALSE; size = g_output_stream_write (ostream, &header, sizeof(header), NULL, error); if (size < 0) return FALSE; size = g_output_stream_write_bytes (ostream, blob, NULL, error); if (size < 0) return FALSE; /* write display capsule location as UPDATE_INFO */ if (!fu_uefi_device_write_update_info (FU_UEFI_DEVICE (device), fn, "fwupd-ux-capsule", &efi_guid_ux_capsule, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_plugin_uefi_update_splash (FuPlugin *plugin, FuDevice *device, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); guint best_idx = G_MAXUINT; guint32 lowest_border_pixels = G_MAXUINT; guint32 screen_height = 768; guint32 screen_width = 1024; g_autoptr(GBytes) image_bmp = NULL; struct { guint32 width; guint32 height; } sizes[] = { { 640, 480 }, /* matching the sizes in po/make-images */ { 800, 600 }, { 1024, 768 }, { 1920, 1080 }, { 3840, 2160 }, { 5120, 2880 }, { 5688, 3200 }, { 7680, 4320 }, { 0, 0 } }; /* no UX capsule support, so deleting var if it exists */ if (fu_device_has_custom_flag (device, "no-ux-capsule")) { g_debug ("not providing UX capsule"); return fu_uefi_vars_delete (FU_UEFI_VARS_GUID_FWUPDATE, "fwupd-ux-capsule", error); } /* get the boot graphics resource table data */ if (!fu_uefi_bgrt_get_supported (data->bgrt)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "BGRT is not supported"); return FALSE; } if (!fu_uefi_get_framebuffer_size (&screen_width, &screen_height, error)) return FALSE; g_debug ("framebuffer size %" G_GUINT32_FORMAT " x%" G_GUINT32_FORMAT, screen_width, screen_height); /* find the 'best sized' pre-generated image */ for (guint i = 0; sizes[i].width != 0; i++) { guint32 border_pixels; /* disregard any images that are bigger than the screen */ if (sizes[i].width > screen_width) continue; if (sizes[i].height > screen_height) continue; /* is this the best fit for the display */ border_pixels = (screen_width * screen_height) - (sizes[i].width * sizes[i].height); if (border_pixels < lowest_border_pixels) { lowest_border_pixels = border_pixels; best_idx = i; } } if (best_idx == G_MAXUINT) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find a suitable image to use"); return FALSE; } /* get the raw data */ image_bmp = fu_plugin_uefi_get_splash_data (sizes[best_idx].width, sizes[best_idx].height, error); if (image_bmp == NULL) return FALSE; /* perform the upload */ return fu_plugin_uefi_write_splash_data (plugin, device, image_bmp, error); } static gboolean fu_plugin_uefi_esp_mounted (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autofree gchar *contents = NULL; g_auto(GStrv) lines = NULL; gsize length; if (!g_file_get_contents ("/proc/mounts", &contents, &length, error)) return FALSE; lines = g_strsplit (contents, "\n", 0); for (guint i = 0; lines[i] != NULL; i++) { if (lines[i] != NULL && g_strrstr (lines[i], data->esp_path)) return TRUE; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EFI System partition %s is not mounted", data->esp_path); return FALSE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { const gchar *str; guint32 flashes_left; g_autofree gchar *efibootmgr_path = NULL; g_autofree gchar *boot_variables = NULL; g_autoptr(GError) error_splash = NULL; /* test the flash counter */ flashes_left = fu_device_get_flashes_left (device); if (flashes_left > 0) { g_debug ("%s has %" G_GUINT32_FORMAT " flashes left", fu_device_get_name (device), flashes_left); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && flashes_left <= 2) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s only has %" G_GUINT32_FORMAT " flashes left -- " "see https://github.com/hughsie/fwupd/wiki/Dell-TPM:-flashes-left for more information.", fu_device_get_name (device), flashes_left); return FALSE; } } /* TRANSLATORS: this is shown when updating the firmware after the reboot */ str = _("Installing firmware update…"); g_assert (str != NULL); /* make sure that the ESP is mounted */ if (g_getenv ("FWUPD_UEFI_ESP_PATH") == NULL) { if (!fu_plugin_uefi_esp_mounted (plugin, error)) return FALSE; } /* perform the update */ g_debug ("Performing UEFI capsule update"); fu_device_set_status (device, FWUPD_STATUS_SCHEDULING); if (!fu_plugin_uefi_update_splash (plugin, device, &error_splash)) { g_debug ("failed to upload UEFI UX capsule text: %s", error_splash->message); } if (!fu_device_write_firmware (device, blob_fw, flags, error)) return FALSE; /* record if we had an invalid header during update */ str = fu_uefi_missing_capsule_header (device) ? "True" : "False"; fu_plugin_add_report_metadata (plugin, "MissingCapsuleHeader", str); /* record boot information to system log for future debugging */ efibootmgr_path = fu_common_find_program_in_path ("efibootmgr", NULL); if (efibootmgr_path != NULL) { g_autofree gchar *cmd = g_strdup_printf ("%s -v", efibootmgr_path); if (!g_spawn_command_line_sync (cmd, &boot_variables, NULL, NULL, error)) return FALSE; g_message ("Boot Information:\n%s", boot_variables); } return TRUE; } static void fu_plugin_uefi_register_proxy_device (FuPlugin *plugin, FuDevice *device) { FuPluginData *data = fu_plugin_get_data (plugin); g_autoptr(FuUefiDevice) dev = fu_uefi_device_new_from_dev (device); if (data->esp_path != NULL) fu_device_set_metadata (FU_DEVICE (dev), "EspPath", data->esp_path); fu_plugin_device_add (plugin, FU_DEVICE (dev)); } void fu_plugin_device_registered (FuPlugin *plugin, FuDevice *device) { if (fu_device_get_metadata (device, "UefiDeviceKind") != NULL) { if (fu_device_get_guid_default (device) == NULL) { g_autofree gchar *dbg = fu_device_to_string (device); g_warning ("cannot create proxy device as no GUID: %s", dbg); return; } fu_plugin_uefi_register_proxy_device (plugin, device); } } static FwupdVersionFormat fu_plugin_uefi_get_version_format_for_type (FuPlugin *plugin, FuUefiDeviceKind device_kind) { const gchar *content; const gchar *quirk; g_autofree gchar *group = NULL; /* we have no information for devices */ if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) return FWUPD_VERSION_FORMAT_TRIPLET; content = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER); if (content == NULL) return FWUPD_VERSION_FORMAT_TRIPLET; /* any quirks match */ group = g_strdup_printf ("SmbiosManufacturer=%s", content); quirk = fu_plugin_lookup_quirk_by_id (plugin, group, FU_QUIRKS_UEFI_VERSION_FORMAT); if (quirk == NULL) return FWUPD_VERSION_FORMAT_TRIPLET; return fwupd_version_format_from_string (quirk); } static const gchar * fu_plugin_uefi_uefi_type_to_string (FuUefiDeviceKind device_kind) { if (device_kind == FU_UEFI_DEVICE_KIND_UNKNOWN) return "Unknown Firmware"; if (device_kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) return "System Firmware"; if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) return "Device Firmware"; if (device_kind == FU_UEFI_DEVICE_KIND_UEFI_DRIVER) return "UEFI Driver"; if (device_kind == FU_UEFI_DEVICE_KIND_FMP) return "Firmware Management Protocol"; return NULL; } static gchar * fu_plugin_uefi_get_name_for_type (FuPlugin *plugin, FuUefiDeviceKind device_kind) { GString *display_name; /* set Display Name prefix for capsules that are not PCI cards */ display_name = g_string_new (fu_plugin_uefi_uefi_type_to_string (device_kind)); if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) { g_string_prepend (display_name, "UEFI "); } else { const gchar *tmp; tmp = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_NAME); if (tmp != NULL && tmp[0] != '\0') { g_string_prepend (display_name, " "); g_string_prepend (display_name, tmp); } } return g_string_free (display_name, FALSE); } static gboolean fu_plugin_uefi_coldplug_device (FuPlugin *plugin, FuUefiDevice *dev, GError **error) { FuUefiDeviceKind device_kind; FwupdVersionFormat version_format; /* set default version format */ device_kind = fu_uefi_device_get_kind (dev); version_format = fu_plugin_uefi_get_version_format_for_type (plugin, device_kind); fu_device_set_version_format (FU_DEVICE (dev), version_format); /* probe to get add GUIDs (and hence any quirk fixups) */ if (!fu_device_probe (FU_DEVICE (dev), error)) return FALSE; /* if not already set by quirks */ if (fu_device_get_custom_flags (FU_DEVICE (dev)) == NULL) { /* for all Lenovo hardware */ if (fu_plugin_check_hwid (plugin, "6de5d951-d755-576b-bd09-c5cf66b27234")) { fu_device_set_custom_flags (FU_DEVICE (dev), "use-legacy-bootmgr-desc"); fu_plugin_add_report_metadata (plugin, "BootMgrDesc", "legacy"); } } /* set fallback name if nothing else is set */ if (fu_device_get_name (FU_DEVICE (dev)) == 0) { g_autofree gchar *name = NULL; name = fu_plugin_uefi_get_name_for_type (plugin, fu_uefi_device_get_kind (dev)); if (name != NULL) fu_device_set_name (FU_DEVICE (dev), name); } /* set fallback vendor if nothing else is set */ if (fu_device_get_vendor (FU_DEVICE (dev)) == NULL && fu_uefi_device_get_kind (dev) == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) { const gchar *vendor = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER); if (vendor != NULL) fu_device_set_vendor (FU_DEVICE (dev), vendor); } /* success */ return TRUE; } static void fu_plugin_uefi_test_secure_boot (FuPlugin *plugin) { const gchar *result_str = "Disabled"; if (fu_uefi_secure_boot_enabled ()) result_str = "Enabled"; g_debug ("SecureBoot is: %s", result_str); fu_plugin_add_report_metadata (plugin, "SecureBoot", result_str); } static gboolean fu_plugin_uefi_delete_old_capsules (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autofree gchar *pattern = NULL; g_autoptr(GPtrArray) files = NULL; /* delete any files matching the glob in the ESP */ files = fu_common_get_files_recursive (data->esp_path, error); if (files == NULL) return FALSE; pattern = g_build_filename (data->esp_path, "EFI/*/fw/fwupd-*.cap", NULL); for (guint i = 0; i < files->len; i++) { const gchar *fn = g_ptr_array_index (files, i); if (fnmatch (pattern, fn, 0) == 0) { g_autoptr(GFile) file = g_file_new_for_path (fn); g_debug ("deleting %s", fn); if (!g_file_delete (file, NULL, error)) return FALSE; } } return TRUE; } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { const guint8 *data; gsize sz; g_autoptr(GBytes) bios_information = fu_plugin_get_smbios_data (plugin, 0); if (bios_information == NULL) { const gchar *tmp = g_getenv ("FWUPD_DELL_FAKE_SMBIOS"); if (tmp != NULL) return TRUE; g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "SMBIOS not supported"); return FALSE; } data = g_bytes_get_data (bios_information, &sz); if (sz < 0x13) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "offset bigger than size %" G_GSIZE_FORMAT, sz); return FALSE; } if (data[1] < 0x13) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "SMBIOS 2.3 not supported"); return FALSE; } if (!(data[0x13] & (1 << 3))) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "System does not support UEFI mode"); return FALSE; } /* test for invalid ESP in coldplug, and set the update-error rather * than showing no output if the plugin had self-disabled here */ return TRUE; } static gboolean fu_plugin_uefi_ensure_esp_path (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); guint64 sz_reqd = FU_UEFI_COMMON_REQUIRED_ESP_FREE_SPACE; g_autofree gchar *require_esp_free_space = NULL; g_autofree gchar *require_shim_for_sb = NULL; /* parse free space */ require_esp_free_space = fu_plugin_get_config_value (plugin, "RequireESPFreeSpace"); if (require_esp_free_space != NULL) sz_reqd = fu_common_strtoull (require_esp_free_space); /* load from file */ data->esp_path = fu_plugin_get_config_value (plugin, "OverrideESPMountPoint"); if (data->esp_path != NULL) { g_autoptr(GError) error_local = NULL; if (!fu_uefi_check_esp_path (data->esp_path, &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "invalid OverrideESPMountPoint=%s specified in config: %s", data->esp_path, error_local->message); return FALSE; } return fu_uefi_check_esp_free_space (data->esp_path, sz_reqd, error); } require_shim_for_sb = fu_plugin_get_config_value (plugin, "RequireShimForSecureBoot"); if (require_shim_for_sb == NULL || g_ascii_strcasecmp (require_shim_for_sb, "true") == 0) data->require_shim_for_sb = TRUE; /* try to guess from heuristics */ data->esp_path = fu_uefi_guess_esp_path (); if (data->esp_path == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "Unable to determine EFI system partition location, " "See https://github.com/hughsie/fwupd/wiki/Determining-EFI-system-partition-location"); return FALSE; } /* check free space */ if (!fu_uefi_check_esp_free_space (data->esp_path, sz_reqd, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_plugin_uefi_ensure_efivarfs_rw (GError **error) { g_autofree gchar *sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); g_autofree gchar *sysfsefivardir = g_build_filename (sysfsfwdir, "efi", "efivars", NULL); g_autoptr(GUnixMountEntry) mount = g_unix_mount_at (sysfsefivardir, NULL); if (mount == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "%s was not mounted", sysfsefivardir); return FALSE; } if (g_unix_mount_is_readonly (mount)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s is read only", sysfsefivardir); return FALSE; } return TRUE; } gboolean fu_plugin_unlock (FuPlugin *plugin, FuDevice *device, GError **error) { FuUefiDevice *device_uefi = FU_UEFI_DEVICE (device); FuDevice *device_alt = NULL; FwupdDeviceFlags device_flags_alt = 0; guint flashes_left = 0; guint flashes_left_alt = 0; if (fu_uefi_device_get_kind (device_uefi) != FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unable to unlock %s", fu_device_get_name (device)); return FALSE; } /* for unlocking TPM1.2 <-> TPM2.0 switching */ g_debug ("Unlocking upgrades for: %s (%s)", fu_device_get_name (device), fu_device_get_id (device)); device_alt = fu_device_get_alternate (device); if (device_alt == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No alternate device for %s", fu_device_get_name (device)); return FALSE; } g_debug ("Preventing upgrades for: %s (%s)", fu_device_get_name (device_alt), fu_device_get_id (device_alt)); flashes_left = fu_device_get_flashes_left (device); flashes_left_alt = fu_device_get_flashes_left (device_alt); if (flashes_left == 0) { /* flashes left == 0 on both means no flashes left */ if (flashes_left_alt == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ERROR: %s has no flashes left.", fu_device_get_name (device)); /* flashes left == 0 on just unlocking device is ownership */ } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ERROR: %s is currently OWNED. " "Ownership must be removed to switch modes.", fu_device_get_name (device_alt)); } return FALSE; } /* clone the info from real device but prevent it from being flashed */ device_flags_alt = fu_device_get_flags (device_alt); fu_device_set_flags (device, device_flags_alt); fu_device_set_flags (device_alt, device_flags_alt & ~FWUPD_DEVICE_FLAG_UPDATABLE); /* make sure that this unlocked device can be updated */ fu_device_set_version (device, "0.0.0.0", FWUPD_VERSION_FORMAT_QUAD); return TRUE; } static gboolean fu_plugin_uefi_create_dummy (FuPlugin *plugin, GError **error) { const gchar *key; g_autoptr(FuDevice) dev = fu_device_new (); key = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER); if (key != NULL) fu_device_set_vendor (dev, key); key = fu_plugin_uefi_get_name_for_type (plugin, FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE); fu_device_set_name (dev, key); key = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BIOS_VERSION); if (key != NULL) fu_device_set_version (dev, key, FWUPD_VERSION_FORMAT_PLAIN); key = "Firmware can not be updated in legacy mode, switch to UEFI mode."; fu_device_set_update_error (dev, key); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_icon (dev, "computer"); fu_device_set_plugin (dev, fu_plugin_get_name (plugin)); fu_device_set_id (dev, "UEFI-dummy"); fu_device_add_instance_id (dev, "main-system-firmware"); if (!fu_device_setup (dev, error)) return FALSE; fu_plugin_device_add (plugin, dev); return TRUE; } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); const gchar *str; g_autofree gchar *bootloader = NULL; g_autofree gchar *esrt_path = NULL; g_autofree gchar *sysfsfwdir = NULL; g_autoptr(GError) error_bootloader = NULL; g_autoptr(GError) error_efivarfs = NULL; g_autoptr(GError) error_esp = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) entries = NULL; /* are the EFI dirs set up so we can update each device */ if (!fu_uefi_vars_supported (&error_local)) { g_warning ("%s", error_local->message); return fu_plugin_uefi_create_dummy (plugin, error); } /* get the directory of ESRT entries */ sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); esrt_path = g_build_filename (sysfsfwdir, "efi", "esrt", NULL); entries = fu_uefi_get_esrt_entry_paths (esrt_path, error); if (entries == NULL) return FALSE; /* make sure that efivarfs is rw */ if (!fu_plugin_uefi_ensure_efivarfs_rw (&error_efivarfs)) g_warning ("%s", error_efivarfs->message); /* if secure boot is enabled ensure we have a signed fwupd.efi */ bootloader = fu_uefi_get_built_app_path (&error_bootloader); if (bootloader == NULL) { if (fu_uefi_secure_boot_enabled ()) g_prefix_error (&error_bootloader, "missing signed bootloader for secure boot: "); g_warning ("%s", error_bootloader->message); } /* ensure the ESP is detected */ if (!fu_plugin_uefi_ensure_esp_path (plugin, &error_esp)) g_warning ("%s", error_esp->message); /* add each device */ for (guint i = 0; i < entries->len; i++) { const gchar *path = g_ptr_array_index (entries, i); g_autoptr(GError) error_parse = NULL; g_autoptr(FuUefiDevice) dev = fu_uefi_device_new_from_entry (path, &error_parse); if (dev == NULL) { g_warning ("failed to add %s: %s", path, error_parse->message); continue; } fu_device_set_quirks (FU_DEVICE (dev), fu_plugin_get_quirks (plugin)); if (!fu_plugin_uefi_coldplug_device (plugin, dev, error)) return FALSE; if (error_esp != NULL) { fu_device_set_update_error (FU_DEVICE (dev), error_esp->message); } else if (error_bootloader != NULL) { fu_device_set_update_error (FU_DEVICE (dev), error_bootloader->message); } else if (error_efivarfs != NULL) { fu_device_set_update_error (FU_DEVICE (dev), error_efivarfs->message); } else { fu_device_set_metadata (FU_DEVICE (dev), "EspPath", data->esp_path); fu_device_set_metadata_boolean (FU_DEVICE (dev), "RequireShimForSecureBoot", data->require_shim_for_sb); fu_device_add_flag (FU_DEVICE (dev), FWUPD_DEVICE_FLAG_UPDATABLE); } fu_plugin_device_add (plugin, FU_DEVICE (dev)); } /* no devices are updatable */ if (error_esp != NULL || error_bootloader != NULL) return TRUE; /* delete any existing .cap files to avoid the small ESP partition * from running out of space when we've done lots of firmware updates * -- also if the distro has changed the ESP may be different anyway */ if (fu_uefi_vars_exists (FU_UEFI_VARS_GUID_EFI_GLOBAL, "BootNext")) { g_debug ("detected BootNext, not cleaning up"); } else { if (!fu_plugin_uefi_delete_old_capsules (plugin, error)) return FALSE; if (!fu_uefi_vars_delete_with_glob (FU_UEFI_VARS_GUID_FWUPDATE, "fwupd-*", error)) return FALSE; } /* save in report metadata */ g_debug ("ESP mountpoint set as %s", data->esp_path); fu_plugin_add_report_metadata (plugin, "ESPMountPoint", data->esp_path); /* for debugging problems later */ fu_plugin_uefi_test_secure_boot (plugin); if (!fu_uefi_bgrt_setup (data->bgrt, &error_local)) g_debug ("BGRT setup failed: %s", error_local->message); str = fu_uefi_bgrt_get_supported (data->bgrt) ? "Enabled" : "Disabled"; g_debug ("UX Capsule support : %s", str); fu_plugin_add_report_metadata (plugin, "UEFIUXCapsule", str); return TRUE; } fwupd-1.2.14/plugins/uefi/fu-self-test.c000066400000000000000000000272471402665037500200430ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-test.h" #include "fu-ucs2.h" #include "fu-uefi-bgrt.h" #include "fu-uefi-common.h" #include "fu-uefi-device.h" #include "fu-uefi-pcrs.h" #include "fu-uefi-vars.h" static void fu_uefi_pcrs_1_2_func (void) { gboolean ret; g_autoptr(FuUefiPcrs) pcrs = fu_uefi_pcrs_new (); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) pcr0s = NULL; g_autoptr(GPtrArray) pcrXs = NULL; ret = fu_uefi_pcrs_setup (pcrs, &error); g_assert_no_error (error); g_assert_true (ret); pcr0s = fu_uefi_pcrs_get_checksums (pcrs, 0); g_assert_nonnull (pcr0s); g_assert_cmpint (pcr0s->len, ==, 1); pcrXs = fu_uefi_pcrs_get_checksums (pcrs, 999); g_assert_nonnull (pcrXs); g_assert_cmpint (pcrXs->len, ==, 0); } static void fu_uefi_pcrs_2_0_func (void) { gboolean ret; g_autoptr(FuUefiPcrs) pcrs = fu_uefi_pcrs_new (); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) pcr0s = NULL; g_autoptr(GPtrArray) pcr1s = NULL; g_autoptr(GPtrArray) pcrXs = NULL; g_setenv ("FWUPD_UEFI_TPM2_YAML_DATA", "sha1 :\n" " 0 : cbd9e4112727bc75761001abcb2dddd87a66caf5\n" "sha256 :\n" /* old output format of tpm2_listpcrs and tpm2_pcrlist */ " 0 : 122de8b579cce17b0703ca9f9716d6f99125af9569e7303f51ea7f85d317f01e\n" /* new output format of tpm2_pcrread */ " 1 : 0x0D89E7CA2C487EED36DB5684826B4ABF0904D3BDCD74E61999573570CF3F9C75\n", TRUE); ret = fu_uefi_pcrs_setup (pcrs, &error); g_assert_no_error (error); g_assert_true (ret); pcr0s = fu_uefi_pcrs_get_checksums (pcrs, 0); g_assert_nonnull (pcr0s); g_assert_cmpint (pcr0s->len, ==, 2); pcr1s = fu_uefi_pcrs_get_checksums (pcrs, 1); g_assert_nonnull (pcr1s); g_assert_cmpint (pcr1s->len, ==, 1); pcrXs = fu_uefi_pcrs_get_checksums (pcrs, 999); g_assert_nonnull (pcrXs); g_assert_cmpint (pcrXs->len, ==, 0); } static void fu_uefi_pcrs_2_0_failure_func (void) { gboolean ret; g_autoptr(FuUefiPcrs) pcrs = fu_uefi_pcrs_new (); g_autoptr(GError) error = NULL; g_setenv ("FWUPD_UEFI_TPM2_YAML_DATA", "Something is not working properly!\n" "999:hello\n" "0:dave\n" "\n", TRUE); ret = fu_uefi_pcrs_setup (pcrs, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_false (ret); } static void fu_uefi_ucs2_func (void) { g_autofree guint16 *str1 = NULL; g_autofree gchar *str2 = NULL; str1 = fu_uft8_to_ucs2 ("hw!", -1); g_assert_cmpint (fu_ucs2_strlen (str1, -1), ==, 3); str2 = fu_ucs2_to_uft8 (str1, -1); g_assert_cmpstr ("hw!", ==, str2); } static void fu_uefi_bgrt_func (void) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(FuUefiBgrt) bgrt = fu_uefi_bgrt_new (); ret = fu_uefi_bgrt_setup (bgrt, &error); g_assert_no_error (error); g_assert_true (ret); g_assert_true (fu_uefi_bgrt_get_supported (bgrt)); g_assert_cmpint (fu_uefi_bgrt_get_xoffset (bgrt), ==, 123); g_assert_cmpint (fu_uefi_bgrt_get_yoffset (bgrt), ==, 456); g_assert_cmpint (fu_uefi_bgrt_get_width (bgrt), ==, 54); g_assert_cmpint (fu_uefi_bgrt_get_height (bgrt), ==, 24); } static void fu_uefi_framebuffer_func (void) { gboolean ret; guint32 height = 0; guint32 width = 0; g_autoptr(GError) error = NULL; ret = fu_uefi_get_framebuffer_size (&width, &height, &error); g_assert_no_error (error); g_assert_true (ret); g_assert_cmpint (width, ==, 456); g_assert_cmpint (height, ==, 789); } static void fu_uefi_bitmap_func (void) { gboolean ret; gsize sz = 0; guint32 height = 0; guint32 width = 0; g_autofree gchar *fn = NULL; g_autofree gchar *buf = NULL; g_autoptr(GError) error = NULL; fn = fu_test_get_filename (TESTDATADIR, "test.bmp"); g_assert (fn != NULL); ret = g_file_get_contents (fn, &buf, &sz, &error); g_assert_no_error (error); g_assert_true (ret); g_assert_nonnull (buf); ret = fu_uefi_get_bitmap_size ((guint8 *)buf, sz, &width, &height, &error); g_assert_no_error (error); g_assert_true (ret); g_assert_cmpint (width, ==, 54); g_assert_cmpint (height, ==, 24); } static void fu_uefi_device_func (void) { g_autofree gchar *fn = NULL; g_autoptr(FuUefiDevice) dev = NULL; g_autoptr(GError) error = NULL; fn = fu_test_get_filename (TESTDATADIR, "efi/esrt/entries/entry0"); g_assert (fn != NULL); dev = fu_uefi_device_new_from_entry (fn, &error); g_assert_nonnull (dev); g_assert_no_error (error); g_assert_cmpint (fu_uefi_device_get_kind (dev), ==, FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE); g_assert_cmpstr (fu_uefi_device_get_guid (dev), ==, "ddc0ee61-e7f0-4e7d-acc5-c070a398838e"); g_assert_cmpint (fu_uefi_device_get_hardware_instance (dev), ==, 0x0); g_assert_cmpint (fu_uefi_device_get_version (dev), ==, 65586); g_assert_cmpint (fu_uefi_device_get_version_lowest (dev), ==, 65582); g_assert_cmpint (fu_uefi_device_get_version_error (dev), ==, 18472960); g_assert_cmpint (fu_uefi_device_get_capsule_flags (dev), ==, 0xfe); g_assert_cmpint (fu_uefi_device_get_status (dev), ==, FU_UEFI_DEVICE_STATUS_ERROR_UNSUCCESSFUL); /* check enums all converted */ for (guint i = 0; i < FU_UEFI_DEVICE_STATUS_LAST; i++) g_assert_nonnull (fu_uefi_device_status_to_string (i)); } static void fu_uefi_vars_func (void) { gboolean ret; gsize sz = 0; guint32 attr = 0; g_autofree guint8 *data = NULL; g_autoptr(GError) error = NULL; /* check supported */ ret = fu_uefi_vars_supported (&error); g_assert_no_error (error); g_assert_true (ret); /* check existing keys */ g_assert_false (fu_uefi_vars_exists (FU_UEFI_VARS_GUID_EFI_GLOBAL, "NotGoingToExist")); g_assert_true (fu_uefi_vars_exists (FU_UEFI_VARS_GUID_EFI_GLOBAL, "SecureBoot")); /* write and read a key */ ret = fu_uefi_vars_set_data (FU_UEFI_VARS_GUID_EFI_GLOBAL, "Test", (guint8 *) "1", 1, FU_UEFI_VARS_ATTR_NON_VOLATILE | FU_UEFI_VARS_ATTR_RUNTIME_ACCESS, &error); g_assert_no_error (error); g_assert_true (ret); ret = fu_uefi_vars_get_data (FU_UEFI_VARS_GUID_EFI_GLOBAL, "Test", &data, &sz, &attr, &error); g_assert_no_error (error); g_assert_true (ret); g_assert_cmpint (sz, ==, 1); g_assert_cmpint (attr, ==, FU_UEFI_VARS_ATTR_NON_VOLATILE | FU_UEFI_VARS_ATTR_RUNTIME_ACCESS); g_assert_cmpint (data[0], ==, '1'); /* delete single key */ ret = fu_uefi_vars_delete (FU_UEFI_VARS_GUID_EFI_GLOBAL, "Test", &error); g_assert_no_error (error); g_assert_true (ret); g_assert_false (fu_uefi_vars_exists (FU_UEFI_VARS_GUID_EFI_GLOBAL, "Test")); /* delete multiple keys */ ret = fu_uefi_vars_set_data (FU_UEFI_VARS_GUID_EFI_GLOBAL, "Test1", (guint8 *)"1", 1, 0, &error); g_assert_no_error (error); g_assert_true (ret); ret = fu_uefi_vars_set_data (FU_UEFI_VARS_GUID_EFI_GLOBAL, "Test2", (guint8 *)"1", 1, 0, &error); g_assert_no_error (error); g_assert_true (ret); ret = fu_uefi_vars_delete_with_glob (FU_UEFI_VARS_GUID_EFI_GLOBAL, "Test*", &error); g_assert_no_error (error); g_assert_true (ret); g_assert_false (fu_uefi_vars_exists (FU_UEFI_VARS_GUID_EFI_GLOBAL, "Test1")); g_assert_false (fu_uefi_vars_exists (FU_UEFI_VARS_GUID_EFI_GLOBAL, "Test2")); /* read a key that doesn't exist */ ret = fu_uefi_vars_get_data (FU_UEFI_VARS_GUID_EFI_GLOBAL, "NotGoingToExist", NULL, NULL, NULL, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false (ret); } static void fu_uefi_plugin_func (void) { FuUefiDevice *dev; g_autofree gchar *esrt_path = NULL; g_autofree gchar *sysfsfwdir = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) entries = NULL; /* add each device */ sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); esrt_path = g_build_filename (sysfsfwdir, "efi", "esrt", NULL); entries = fu_uefi_get_esrt_entry_paths (esrt_path, &error); g_assert_no_error (error); g_assert_nonnull (entries); devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < entries->len; i++) { const gchar *path = g_ptr_array_index (entries, i); g_autoptr(GError) error_local = NULL; g_autoptr(FuUefiDevice) dev_tmp = fu_uefi_device_new_from_entry (path, &error_local); if (dev_tmp == NULL) { g_debug ("failed to add %s: %s", path, error_local->message); continue; } g_ptr_array_add (devices, g_object_ref (dev_tmp)); } g_assert_cmpint (devices->len, ==, 2); /* system firmware */ dev = g_ptr_array_index (devices, 0); g_assert_cmpint (fu_uefi_device_get_kind (dev), ==, FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE); g_assert_cmpstr (fu_uefi_device_get_guid (dev), ==, "ddc0ee61-e7f0-4e7d-acc5-c070a398838e"); g_assert_cmpint (fu_uefi_device_get_version (dev), ==, 65586); g_assert_cmpint (fu_uefi_device_get_version_lowest (dev), ==, 65582); g_assert_cmpint (fu_uefi_device_get_version_error (dev), ==, 18472960); g_assert_cmpint (fu_uefi_device_get_capsule_flags (dev), ==, 0xfe); g_assert_cmpint (fu_uefi_device_get_status (dev), ==, FU_UEFI_DEVICE_STATUS_ERROR_UNSUCCESSFUL); /* system firmware */ dev = g_ptr_array_index (devices, 1); g_assert_cmpint (fu_uefi_device_get_kind (dev), ==, FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE); g_assert_cmpstr (fu_uefi_device_get_guid (dev), ==, "671d19d0-d43c-4852-98d9-1ce16f9967e4"); g_assert_cmpint (fu_uefi_device_get_version (dev), ==, 3090287969); g_assert_cmpint (fu_uefi_device_get_version_lowest (dev), ==, 1); g_assert_cmpint (fu_uefi_device_get_version_error (dev), ==, 0); g_assert_cmpint (fu_uefi_device_get_capsule_flags (dev), ==, 32784); g_assert_cmpint (fu_uefi_device_get_status (dev), ==, FU_UEFI_DEVICE_STATUS_SUCCESS); } static void fu_uefi_update_info_func (void) { g_autofree gchar *fn = NULL; g_autoptr(FuUefiDevice) dev = NULL; g_autoptr(FuUefiUpdateInfo) info = NULL; g_autoptr(GError) error = NULL; fn = fu_test_get_filename (TESTDATADIR, "efi/esrt/entries/entry0"); g_assert (fn != NULL); dev = fu_uefi_device_new_from_entry (fn, &error); g_assert_no_error (error); g_assert_nonnull (dev); g_assert_cmpint (fu_uefi_device_get_kind (dev), ==, FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE); g_assert_cmpstr (fu_uefi_device_get_guid (dev), ==, "ddc0ee61-e7f0-4e7d-acc5-c070a398838e"); info = fu_uefi_device_load_update_info (dev, &error); g_assert_no_error (error); g_assert_nonnull (info); g_assert_cmpint (fu_uefi_update_info_get_version (info), ==, 0x7); g_assert_cmpstr (fu_uefi_update_info_get_guid (info), ==, "697bd920-12cf-4da9-8385-996909bc6559"); g_assert_cmpint (fu_uefi_update_info_get_capsule_flags (info), ==, 0x50000); g_assert_cmpint (fu_uefi_update_info_get_hw_inst (info), ==, 0x0); g_assert_cmpint (fu_uefi_update_info_get_status (info), ==, FU_UEFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE); g_assert_cmpstr (fu_uefi_update_info_get_capsule_fn (info), ==, "/EFI/fedora/fw/fwupd-697bd920-12cf-4da9-8385-996909bc6559.cap"); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); g_setenv ("FWUPD_SYSFSFWDIR", TESTDATADIR, TRUE); g_setenv ("FWUPD_SYSFSDRIVERDIR", TESTDATADIR, TRUE); g_setenv ("FWUPD_SYSFSTPMDIR", TESTDATADIR, TRUE); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func ("/uefi/pcrs1.2", fu_uefi_pcrs_1_2_func); g_test_add_func ("/uefi/pcrs2.0", fu_uefi_pcrs_2_0_func); g_test_add_func ("/uefi/pcrs2.0{failure}", fu_uefi_pcrs_2_0_failure_func); g_test_add_func ("/uefi/ucs2", fu_uefi_ucs2_func); g_test_add_func ("/uefi/variable", fu_uefi_vars_func); g_test_add_func ("/uefi/bgrt", fu_uefi_bgrt_func); g_test_add_func ("/uefi/framebuffer", fu_uefi_framebuffer_func); g_test_add_func ("/uefi/bitmap", fu_uefi_bitmap_func); g_test_add_func ("/uefi/device", fu_uefi_device_func); g_test_add_func ("/uefi/update-info", fu_uefi_update_info_func); g_test_add_func ("/uefi/plugin", fu_uefi_plugin_func); return g_test_run (); } fwupd-1.2.14/plugins/uefi/fu-ucs2.c000066400000000000000000000033221402665037500167750ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ucs2.h" #define ev_bits(val, mask, shift) (((val) & ((mask) << (shift))) >> (shift)) gchar * fu_ucs2_to_uft8 (const guint16 *str, gssize max) { gssize i, j; gchar *ret; if (max < 0) max = fu_ucs2_strlen (str, max); ret = g_malloc0 (max * 3 + 1); /* would be s/3/6 if this were UCS-4 */ for (i = 0, j = 0; i < max && str[i]; i++, j++) { if (str[i] <= 0x7f) { ret[j] = str[i]; } else if (str[i] > 0x7f && str[i] <= 0x7ff) { ret[j++] = 0xc0 | ev_bits(str[i], 0x1f, 6); ret[j] = 0x80 | ev_bits(str[i], 0x3f, 0); } else if (str[i] > 0x7ff /* && str[i] < 0x10000 */ ) { ret[j++] = 0xe0 | ev_bits(str[i], 0xf, 12); ret[j++] = 0x80 | ev_bits(str[i], 0x3f, 6); ret[j] = 0x80 | ev_bits(str[i], 0x3f, 0); } } return ret; } guint16 * fu_uft8_to_ucs2 (const gchar *str, gssize max) { gssize i, j; guint16 *ret = g_new0 (guint16, g_utf8_strlen (str, max) + 1); for (i = 0, j = 0; i < (max >= 0 ? max : i + 1) && str[i] != '\0'; j++) { guint32 val = 0; if ((str[i] & 0xe0) == 0xe0 && !(str[i] & 0x10)) { val = ((str[i+0] & 0x0f) << 10) |((str[i+1] & 0x3f) << 6) |((str[i+2] & 0x3f) << 0); i += 3; } else if ((str[i] & 0xc0) == 0xc0 && !(str[i] & 0x20)) { val = ((str[i+0] & 0x1f) << 6) |((str[i+1] & 0x3f) << 0); i += 2; } else { val = str[i] & 0x7f; i += 1; } ret[j] = val; } ret[j] = L'\0'; return ret; } gsize fu_ucs2_strlen (const guint16 *str, gssize limit) { gssize i; for (i = 0; i < (limit >= 0 ? limit : i + 1) && str[i] != L'\0'; i++); return i; } fwupd-1.2.14/plugins/uefi/fu-ucs2.h000066400000000000000000000006211402665037500170010ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gsize fu_ucs2_strlen (const guint16 *str, gssize limit); guint16 *fu_uft8_to_ucs2 (const gchar *str, gssize max); gchar *fu_ucs2_to_uft8 (const guint16 *str, gssize max); fwupd-1.2.14/plugins/uefi/fu-uefi-bgrt.c000066400000000000000000000056451402665037500200170ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-uefi-bgrt.h" #include "fu-uefi-common.h" struct _FuUefiBgrt { GObject parent_instance; guint32 xoffset; guint32 yoffset; guint32 width; guint32 height; }; G_DEFINE_TYPE (FuUefiBgrt, fu_uefi_bgrt, G_TYPE_OBJECT) gboolean fu_uefi_bgrt_setup (FuUefiBgrt *self, GError **error) { gsize sz = 0; guint64 type; guint64 version; g_autofree gchar *bgrtdir = NULL; g_autofree gchar *data = NULL; g_autofree gchar *imagefn = NULL; g_autofree gchar *sysfsfwdir = NULL; g_return_val_if_fail (FU_IS_UEFI_BGRT (self), FALSE); sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); bgrtdir = g_build_filename (sysfsfwdir, "acpi", "bgrt", NULL); if (!g_file_test (bgrtdir, G_FILE_TEST_EXISTS)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "BGRT is not supported"); return FALSE; } type = fu_uefi_read_file_as_uint64 (bgrtdir, "type"); if (type != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "BGRT type was %" G_GUINT64_FORMAT, type); return FALSE; } version = fu_uefi_read_file_as_uint64 (bgrtdir, "version"); if (version != 1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "BGRT version was %" G_GUINT64_FORMAT, version); return FALSE; } /* load image */ self->xoffset = fu_uefi_read_file_as_uint64 (bgrtdir, "xoffset"); self->yoffset = fu_uefi_read_file_as_uint64 (bgrtdir, "yoffset"); imagefn = g_build_filename (bgrtdir, "image", NULL); if (!g_file_get_contents (imagefn, &data, &sz, error)) { g_prefix_error (error, "failed to load BGRT image: "); return FALSE; } if (!fu_uefi_get_bitmap_size ((guint8 *) data, sz, &self->width, &self->height, error)) { g_prefix_error (error, "BGRT image invalid: "); return FALSE; } /* success */ return TRUE; } gboolean fu_uefi_bgrt_get_supported (FuUefiBgrt *self) { g_return_val_if_fail (FU_IS_UEFI_BGRT (self), FALSE); if (self->width == 0 || self->height == 0) return FALSE; return TRUE; } guint32 fu_uefi_bgrt_get_xoffset (FuUefiBgrt *self) { g_return_val_if_fail (FU_IS_UEFI_BGRT (self), 0); return self->xoffset; } guint32 fu_uefi_bgrt_get_yoffset (FuUefiBgrt *self) { g_return_val_if_fail (FU_IS_UEFI_BGRT (self), 0); return self->yoffset; } guint32 fu_uefi_bgrt_get_width (FuUefiBgrt *self) { g_return_val_if_fail (FU_IS_UEFI_BGRT (self), 0); return self->width; } guint32 fu_uefi_bgrt_get_height (FuUefiBgrt *self) { g_return_val_if_fail (FU_IS_UEFI_BGRT (self), 0); return self->height; } static void fu_uefi_bgrt_class_init (FuUefiBgrtClass *klass) { } static void fu_uefi_bgrt_init (FuUefiBgrt *self) { } FuUefiBgrt * fu_uefi_bgrt_new (void) { FuUefiBgrt *self; self = g_object_new (FU_TYPE_UEFI_BGRT, NULL); return FU_UEFI_BGRT (self); } fwupd-1.2.14/plugins/uefi/fu-uefi-bgrt.h000066400000000000000000000012271402665037500200140ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once G_BEGIN_DECLS #define FU_TYPE_UEFI_BGRT (fu_uefi_bgrt_get_type ()) G_DECLARE_FINAL_TYPE (FuUefiBgrt, fu_uefi_bgrt, FU, UEFI_BGRT, GObject) FuUefiBgrt *fu_uefi_bgrt_new (void); gboolean fu_uefi_bgrt_setup (FuUefiBgrt *self, GError **error); gboolean fu_uefi_bgrt_get_supported (FuUefiBgrt *self); guint32 fu_uefi_bgrt_get_xoffset (FuUefiBgrt *self); guint32 fu_uefi_bgrt_get_yoffset (FuUefiBgrt *self); guint32 fu_uefi_bgrt_get_width (FuUefiBgrt *self); guint32 fu_uefi_bgrt_get_height (FuUefiBgrt *self); G_END_DECLS fwupd-1.2.14/plugins/uefi/fu-uefi-bootmgr.c000066400000000000000000000264421402665037500205300ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fwupd-error.h" #include "fu-ucs2.h" #include "fu-uefi-bootmgr.h" #include "fu-uefi-common.h" /* XXX PJFIX: this should be in efiboot-loadopt.h in efivar */ #define LOAD_OPTION_ACTIVE 0x00000001 static gboolean fu_uefi_bootmgr_add_to_boot_order (guint16 boot_entry, GError **error) { gsize boot_order_size = 0; gint rc; guint i = 0; guint32 attr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS; g_autofree guint16 *boot_order = NULL; g_autofree guint16 *new_boot_order = NULL; /* get size of the BootOrder */ rc = efi_get_variable_size (efi_guid_global, "BootOrder", &boot_order_size); if (rc == ENOENT) { boot_order_size = 0; efi_error_clear (); } else if (rc < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_get_variable_size() failed"); return rc; } /* get the current boot order */ if (boot_order_size != 0) { rc = efi_get_variable (efi_guid_global, "BootOrder", (guint8 **)&boot_order, &boot_order_size, &attr); if (rc < 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_get_variable(BootOrder) failed"); return FALSE; } /* already set next */ for (i = 0; i < boot_order_size / sizeof (guint16); i++) { guint16 val = boot_order[i]; if (val == boot_entry) return TRUE; } } /* add the new boot index to the end of the list */ new_boot_order = g_malloc0 (boot_order_size + sizeof (guint16)); if (boot_order_size != 0) memcpy (new_boot_order, boot_order, boot_order_size); i = boot_order_size / sizeof (guint16); new_boot_order[i] = boot_entry; boot_order_size += sizeof (guint16); rc = efi_set_variable(efi_guid_global, "BootOrder", (guint8 *)new_boot_order, boot_order_size, attr, 0644); if (rc < 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_set_variable(BootOrder) failed"); return FALSE; } return TRUE; } static gboolean fu_uefi_setup_bootnext_with_dp (const guint8 *dp_buf, guint8 *opt, gssize opt_size, GError **error) { efi_guid_t *guid = NULL; efi_load_option *loadopt = NULL; gchar *name = NULL; gint rc; gint set_entries[0x10000 / sizeof(gint)] = {0,}; gsize var_data_size = 0; guint16 real_boot16; guint32 attr; guint32 boot_next = 0x10000; g_autofree guint8 *var_data = NULL; while ((rc = efi_get_next_variable_name (&guid, &name)) > 0) { const gchar *desc; gint div, mod; gint scanned = 0; guint16 entry = 0; g_autofree guint8 *var_data_tmp = NULL; if (efi_guid_cmp (guid, &efi_guid_global) != 0) continue; rc = sscanf (name, "Boot%hX%n", &entry, &scanned); if (rc < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to parse Boot entry %s", name); return FALSE; } if (rc != 1) continue; if (scanned != 8) continue; div = entry / (sizeof(set_entries[0]) * 8); mod = entry % (sizeof(set_entries[0]) * 8); set_entries[div] |= 1 << mod; rc = efi_get_variable (*guid, name, &var_data_tmp, &var_data_size, &attr); if (rc < 0) { g_debug ("efi_get_variable(%s) failed", name); continue; } loadopt = (efi_load_option *)var_data_tmp; if (!efi_loadopt_is_valid(loadopt, var_data_size)) { g_debug ("load option was invalid"); continue; } desc = (const gchar *) efi_loadopt_desc (loadopt, var_data_size); if (g_strcmp0 (desc, "Linux Firmware Updater") != 0 && g_strcmp0 (desc, "Linux-Firmware-Updater") != 0) { g_debug ("description does not match"); continue; } var_data = g_steal_pointer (&var_data_tmp); boot_next = entry; efi_error_clear (); break; } if (rc < 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to find boot variable"); return FALSE; } /* already exists */ if (var_data != NULL) { /* is different than before */ if (var_data_size != (gsize) opt_size || memcmp (var_data, opt, opt_size) != 0) { efi_loadopt_attr_set (loadopt, LOAD_OPTION_ACTIVE); rc = efi_set_variable (*guid, name, opt, opt_size, attr, 0644); if (rc < 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "could not set boot variable active"); return FALSE; } } /* create a new one */ } else { g_autofree gchar *boot_next_name = NULL; for (guint32 value = 0; value < 0x10000; value++) { gint div = value / (sizeof(set_entries[0]) * 8); gint mod = value % (sizeof(set_entries[0]) * 8); if (set_entries[div] & (1 << mod)) continue; boot_next = value; break; } if (boot_next >= 0x10000) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "no free boot variables (tried %x)", boot_next); return FALSE; } boot_next_name = g_strdup_printf ("Boot%04X", (guint) (boot_next & 0xffff)); rc = efi_set_variable (efi_guid_global, boot_next_name, opt, opt_size, EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, 0644); if (rc < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "could not set boot variable %s: %d", boot_next_name, rc); return FALSE; } } /* TODO: conditionalize this on the UEFI version? */ if(!fu_uefi_bootmgr_add_to_boot_order (boot_next, error)) return FALSE; /* set the boot next */ real_boot16 = boot_next; rc = efi_set_variable (efi_guid_global, "BootNext", (guint8 *)&real_boot16, 2, EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, 0644); if (rc < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "could not set BootNext(%" G_GUINT16_FORMAT ")", real_boot16); return FALSE; } return TRUE; } static gboolean fu_uefi_cmp_asset (const gchar *source, const gchar *target) { gsize len = 0; g_autofree gchar *source_checksum = NULL; g_autofree gchar *source_data = NULL; g_autofree gchar *target_checksum = NULL; g_autofree gchar *target_data = NULL; /* nothing in target yet */ if (!g_file_test (target, G_FILE_TEST_EXISTS)) return FALSE; /* test if the file needs to be updated */ if (!g_file_get_contents (source, &source_data, &len, NULL)) return FALSE; source_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256, (guchar *) source_data, len); if (!g_file_get_contents (target, &target_data, &len, NULL)) return FALSE; target_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256, (guchar *) target_data, len); return g_strcmp0 (target_checksum, source_checksum) == 0; } static gboolean fu_uefi_copy_asset (const gchar *source, const gchar *target, GError **error) { g_autoptr(GFile) source_file = g_file_new_for_path (source); g_autoptr(GFile) target_file = g_file_new_for_path (target); if (!g_file_copy (source_file, target_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, error)) { g_prefix_error (error, "Failed to copy %s to %s: ", source, target); return FALSE; } return TRUE; } gboolean fu_uefi_bootmgr_bootnext (const gchar *esp_path, const gchar *description, FuUefiBootmgrFlags flags, GError **error) { const gchar *filepath; gboolean use_fwup_path = FALSE; gsize loader_sz = 0; gssize opt_size = 0; gssize sz, dp_size = 0; guint32 attributes = LOAD_OPTION_ACTIVE; g_autofree guint16 *loader_str = NULL; g_autofree gchar *label = NULL; g_autofree gchar *shim_app = NULL; g_autofree gchar *shim_cpy = NULL; g_autofree guint8 *dp_buf = NULL; g_autofree guint8 *opt = NULL; g_autofree gchar *source_app = NULL; g_autofree gchar *target_app = NULL; /* skip for self tests */ if (g_getenv ("FWUPD_UEFI_ESP_PATH") != NULL) return TRUE; /* if secure boot was turned on this might need to be installed separately */ source_app = fu_uefi_get_built_app_path (error); if (source_app == NULL) return FALSE; /* test to make sure shim is there if we need it */ shim_app = fu_uefi_get_esp_app_path (esp_path, "shim", error); if (shim_app == NULL) return FALSE; if (g_file_test (shim_app, G_FILE_TEST_EXISTS)) { /* use a custom copy of shim for firmware updates */ if (flags & FU_UEFI_BOOTMGR_FLAG_USE_SHIM_UNIQUE) { shim_cpy = fu_uefi_get_esp_app_path (esp_path, "shimfwupd", error); if (shim_cpy == NULL) return FALSE; if (!fu_uefi_cmp_asset (shim_app, shim_cpy)) { if (!fu_uefi_copy_asset (shim_app, shim_cpy, error)) return FALSE; } filepath = shim_cpy; } else { filepath = shim_app; } } else { if (fu_uefi_secure_boot_enabled () && (flags & FU_UEFI_BOOTMGR_FLAG_USE_SHIM_FOR_SB) > 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_BROKEN_SYSTEM, "Secure boot is enabled, but shim isn't installed to the EFI system partition"); return FALSE; } use_fwup_path = TRUE; } /* test if correct asset in place */ target_app = fu_uefi_get_esp_app_path (esp_path, "fwupd", error); if (target_app == NULL) return FALSE; if (!fu_uefi_cmp_asset (source_app, target_app)) { if (!fu_uefi_copy_asset (source_app, target_app, error)) return FALSE; } /* no shim, so use this directly */ if (use_fwup_path) filepath = target_app; /* generate device path for target */ sz = efi_generate_file_device_path (dp_buf, dp_size, filepath, EFIBOOT_OPTIONS_IGNORE_FS_ERROR| EFIBOOT_ABBREV_HD); if (sz < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_generate_file_device_path(%s) failed", filepath); return FALSE; } /* add the fwupdx64.efi ESP path as the shim loadopt data */ dp_size = sz; dp_buf = g_malloc0 (dp_size); if (!use_fwup_path) { g_autofree gchar *fwup_fs_basename = g_path_get_basename (target_app); g_autofree gchar *fwup_esp_path = g_strdup_printf ("\\%s", fwup_fs_basename); loader_str = fu_uft8_to_ucs2 (fwup_esp_path, -1); loader_sz = fu_ucs2_strlen (loader_str, -1) * 2; if (loader_sz) loader_sz += 2; } sz = efi_generate_file_device_path (dp_buf, dp_size, filepath, EFIBOOT_OPTIONS_IGNORE_FS_ERROR| EFIBOOT_ABBREV_HD); if (sz != dp_size) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_generate_file_device_path(%s) failed", filepath); return FALSE; } label = g_strdup (description); sz = efi_loadopt_create (opt, opt_size, attributes, (efidp)dp_buf, dp_size, (guint8 *)label, (guint8 *)loader_str, loader_sz); if (sz < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_loadopt_create(%s) failed", label); return FALSE; } opt = g_malloc0 (sz); opt_size = sz; sz = efi_loadopt_create (opt, opt_size, attributes, (efidp)dp_buf, dp_size, (guint8 *)label, (guint8 *)loader_str, loader_sz); if (sz != opt_size) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "loadopt size was unreasonable."); return FALSE; } if (!fu_uefi_setup_bootnext_with_dp (dp_buf, opt, opt_size, error)) return FALSE; efi_error_clear(); return TRUE; } fwupd-1.2.14/plugins/uefi/fu-uefi-bootmgr.h000066400000000000000000000011241402665037500205230ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include G_BEGIN_DECLS typedef enum { FU_UEFI_BOOTMGR_FLAG_NONE = 0, FU_UEFI_BOOTMGR_FLAG_USE_SHIM_FOR_SB = 1 << 0, FU_UEFI_BOOTMGR_FLAG_USE_SHIM_UNIQUE = 1 << 1, FU_UEFI_BOOTMGR_FLAG_LAST } FuUefiBootmgrFlags; gboolean fu_uefi_bootmgr_bootnext (const gchar *esp_path, const gchar *description, FuUefiBootmgrFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/plugins/uefi/fu-uefi-common.c000066400000000000000000000244661402665037500203530ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-common.h" #include "fu-uefi-common.h" #include "fu-uefi-vars.h" #include "fwupd-common.h" #include "fwupd-error.h" #ifndef HAVE_GIO_2_55_0 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUnixMountEntry, g_unix_mount_free) #pragma clang diagnostic pop #endif static const gchar * fu_uefi_bootmgr_get_suffix (GError **error) { guint64 firmware_bits; struct { guint64 bits; const gchar *arch; } suffixes[] = { #if defined(__x86_64__) { 64, "x64" }, #elif defined(__aarch64__) { 64, "aa64" }, #endif #if defined(__x86_64__) || defined(__i386__) || defined(__i686__) { 32, "ia32" }, #endif { 0, NULL } }; g_autofree gchar *sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); g_autofree gchar *sysfsefidir = g_build_filename (sysfsfwdir, "efi", NULL); firmware_bits = fu_uefi_read_file_as_uint64 (sysfsefidir, "fw_platform_size"); if (firmware_bits == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "%s/fw_platform_size cannot be found", sysfsefidir); return NULL; } for (guint i = 0; suffixes[i].arch != NULL; i++) { if (firmware_bits != suffixes[i].bits) continue; return suffixes[i].arch; } /* this should exist */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "%s/fw_platform_size has unknown value %" G_GUINT64_FORMAT, sysfsefidir, firmware_bits); return NULL; } gchar * fu_uefi_get_esp_app_path (const gchar *esp_path, const gchar *cmd, GError **error) { const gchar *suffix = fu_uefi_bootmgr_get_suffix (error); g_autofree gchar *base = NULL; if (suffix == NULL) return NULL; base = fu_uefi_get_esp_path_for_os (esp_path); return g_strdup_printf ("%s/%s%s.efi", base, cmd, suffix); } gchar * fu_uefi_get_built_app_path (GError **error) { const gchar *extension = ""; const gchar *suffix; g_autofree gchar *source_path = NULL; g_autofree gchar *prefix = NULL; if (fu_uefi_secure_boot_enabled ()) extension = ".signed"; suffix = fu_uefi_bootmgr_get_suffix (error); if (suffix == NULL) return NULL; prefix = fu_common_get_path (FU_PATH_KIND_EFIAPPDIR); source_path = g_strdup_printf ("%s/fwupd%s.efi%s", prefix, suffix, extension); if (!g_file_test (source_path, G_FILE_TEST_EXISTS)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "%s cannot be found", source_path); return NULL; } return g_steal_pointer (&source_path); } gboolean fu_uefi_get_framebuffer_size (guint32 *width, guint32 *height, GError **error) { guint32 height_tmp; guint32 width_tmp; g_autofree gchar *sysfsdriverdir = NULL; g_autofree gchar *fbdir = NULL; sysfsdriverdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_DRIVERS); fbdir = g_build_filename (sysfsdriverdir, "efi-framebuffer", "efi-framebuffer.0", NULL); if (!g_file_test (fbdir, G_FILE_TEST_EXISTS)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "EFI framebuffer not found"); return FALSE; } height_tmp = fu_uefi_read_file_as_uint64 (fbdir, "height"); width_tmp = fu_uefi_read_file_as_uint64 (fbdir, "width"); if (width_tmp == 0 || height_tmp == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "EFI framebuffer has invalid size " "%"G_GUINT32_FORMAT"x%"G_GUINT32_FORMAT, width_tmp, height_tmp); return FALSE; } if (width != NULL) *width = width_tmp; if (height != NULL) *height = height_tmp; return TRUE; } gboolean fu_uefi_get_bitmap_size (const guint8 *buf, gsize bufsz, guint32 *width, guint32 *height, GError **error) { guint32 ui32; g_return_val_if_fail (buf != NULL, FALSE); /* check header */ if (bufsz < 26) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "blob was too small %" G_GSIZE_FORMAT, bufsz); return FALSE; } if (memcmp (buf, "BM", 2) != 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid BMP header signature"); return FALSE; } /* starting address */ ui32 = fu_common_read_uint32 (buf + 10, G_LITTLE_ENDIAN); if (ui32 < 26) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "BMP header invalid @ %"G_GUINT32_FORMAT"x", ui32); return FALSE; } /* BITMAPINFOHEADER header */ ui32 = fu_common_read_uint32 (buf + 14, G_LITTLE_ENDIAN); if (ui32 < 26 - 14) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "BITMAPINFOHEADER invalid @ %"G_GUINT32_FORMAT"x", ui32); return FALSE; } /* dimensions */ if (width != NULL) *width = fu_common_read_uint32 (buf + 18, G_LITTLE_ENDIAN); if (height != NULL) *height = fu_common_read_uint32 (buf + 22, G_LITTLE_ENDIAN); return TRUE; } gboolean fu_uefi_secure_boot_enabled (void) { gsize data_size = 0; g_autofree guint8 *data = NULL; if (!fu_uefi_vars_get_data (FU_UEFI_VARS_GUID_EFI_GLOBAL, "SecureBoot", &data, &data_size, NULL, NULL)) return FALSE; if (data_size >= 1 && data[0] & 1) return TRUE; return FALSE; } static gint fu_uefi_strcmp_sort_cb (gconstpointer a, gconstpointer b) { const gchar *stra = *((const gchar **) a); const gchar *strb = *((const gchar **) b); return g_strcmp0 (stra, strb); } GPtrArray * fu_uefi_get_esrt_entry_paths (const gchar *esrt_path, GError **error) { GPtrArray *entries = g_ptr_array_new_with_free_func (g_free); const gchar *fn; g_autofree gchar *esrt_entries = NULL; g_autoptr(GDir) dir = NULL; /* search ESRT */ esrt_entries = g_build_filename (esrt_path, "entries", NULL); dir = g_dir_open (esrt_entries, 0, error); if (dir == NULL) return NULL; while ((fn = g_dir_read_name (dir)) != NULL) g_ptr_array_add (entries, g_build_filename (esrt_entries, fn, NULL)); /* sort by name */ g_ptr_array_sort (entries, fu_uefi_strcmp_sort_cb); return entries; } gchar * fu_uefi_get_esp_path_for_os (const gchar *esp_path) { const gchar *os_release_id = NULL; #ifndef EFI_OS_DIR g_autoptr(GError) error_local = NULL; g_autoptr(GHashTable) os_release = fwupd_get_os_release (&error_local); if (os_release != NULL) { os_release_id = g_hash_table_lookup (os_release, "ID"); } else { g_debug ("failed to get ID: %s", error_local->message); } if (os_release_id == NULL) os_release_id = "unknown"; #else os_release_id = EFI_OS_DIR; #endif return g_build_filename (esp_path, "EFI", os_release_id, NULL); } guint64 fu_uefi_read_file_as_uint64 (const gchar *path, const gchar *attr_name) { g_autofree gchar *data = NULL; g_autofree gchar *fn = g_build_filename (path, attr_name, NULL); if (!g_file_get_contents (fn, &data, NULL, NULL)) return 0x0; return fu_common_strtoull (data); } gboolean fu_uefi_check_esp_free_space (const gchar *path, guint64 required, GError **error) { guint64 fs_free; g_autoptr(GFile) file = NULL; g_autoptr(GFileInfo) info = NULL; file = g_file_new_for_path (path); info = g_file_query_filesystem_info (file, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, error); if (info == NULL) return FALSE; fs_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); if (fs_free < required) { g_autofree gchar *str_free = g_format_size (fs_free); g_autofree gchar *str_reqd = g_format_size (required); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s does not have sufficient space, required %s, got %s", path, str_reqd, str_free); return FALSE; } return TRUE; } gboolean fu_uefi_check_esp_path (const gchar *path, GError **error) { const gchar *fs_types[] = { "vfat", "ntfs", "exfat", "autofs", NULL }; g_autoptr(GUnixMountEntry) mount = g_unix_mount_at (path, NULL); if (mount == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "%s was not mounted", path); return FALSE; } /* /boot is a special case because systemd sandboxing marks * it read-only, but we need to write to /boot/EFI */ if (g_strcmp0 (path, "/boot") == 0) { if (!g_file_test ("/boot/EFI", G_FILE_TEST_IS_DIR)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s/EFI does not exist", path); return FALSE; } /* /efi is a special case because systemd sandboxing marks * it read-only, but we need to write to /efi/EFI */ } else if (g_strcmp0 (path, "/efi") == 0) { if (!g_file_test ("/efi/EFI", G_FILE_TEST_IS_DIR)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s/EFI does not exist", path); return FALSE; } } else if (g_unix_mount_is_readonly (mount)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s is read only", path); return FALSE; } if (!g_strv_contains (fs_types, g_unix_mount_get_fs_type (mount))) { g_autofree gchar *supported = g_strjoinv ("|", (gchar **) fs_types); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s has an invalid type, expected %s", path, supported); return FALSE; } return TRUE; } gchar * fu_uefi_guess_esp_path (void) { const gchar *paths[] = {"/boot/efi", "/boot", "/efi", NULL}; const gchar *path_tmp; /* for the test suite use local directory for ESP */ path_tmp = g_getenv ("FWUPD_UEFI_ESP_PATH"); if (path_tmp != NULL) return g_strdup (path_tmp); for (guint i = 0; paths[i] != NULL; i++) { g_autoptr(GError) error = NULL; if (!fu_uefi_check_esp_path (paths[i], &error)) { g_debug ("ignoring ESP path: %s", error->message); continue; } return g_strdup (paths[i]); } return NULL; } gboolean fu_uefi_prefix_efi_errors (GError **error) { g_autoptr(GString) str = g_string_new (NULL); for (gint i = 0; ; i++) { gchar *filename = NULL; gchar *function = NULL; gchar *message = NULL; gint line = 0; gint err = 0; if (efi_error_get (i, &filename, &function, &line, &message, &err) <= 0) break; g_string_append_printf (str, "{error #%d} %s:%d %s(): %s: %s\t", i, filename, line, function, message, strerror (err)); } if (str->len > 1) g_string_truncate (str, str->len - 1); g_prefix_error (error, "%s: ", str->str); return FALSE; } fwupd-1.2.14/plugins/uefi/fu-uefi-common.h000066400000000000000000000045201402665037500203450ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include G_BEGIN_DECLS #define EFI_CAPSULE_HEADER_FLAGS_PERSIST_ACROSS_RESET 0x00010000 #define EFI_CAPSULE_HEADER_FLAGS_POPULATE_SYSTEM_TABLE 0x00020000 #define EFI_CAPSULE_HEADER_FLAGS_INITIATE_RESET 0x00040000 typedef struct __attribute__((__packed__)) { guint16 year; guint8 month; guint8 day; guint8 hour; guint8 minute; guint8 second; guint8 pad1; guint32 nanosecond; guint16 timezone; guint8 daylight; guint8 pad2; } efi_time_t; typedef struct __attribute__((__packed__)) { efi_guid_t guid; guint32 header_size; guint32 flags; guint32 capsule_image_size; } efi_capsule_header_t; typedef struct __attribute__((__packed__)) { guint8 version; guint8 checksum; guint8 image_type; guint8 reserved; guint32 mode; guint32 x_offset; guint32 y_offset; } efi_ux_capsule_header_t; typedef struct __attribute__((__packed__)) { guint32 update_info_version; efi_guid_t guid; guint32 capsule_flags; guint64 hw_inst; efi_time_t time_attempted; guint32 status; } efi_update_info_t; /* the biggest size SPI part currently seen */ #define FU_UEFI_COMMON_REQUIRED_ESP_FREE_SPACE (32 * 1024 * 1024) gchar *fu_uefi_get_esp_app_path (const gchar *esp_path, const gchar *cmd, GError **error); gchar *fu_uefi_get_built_app_path (GError **error); gboolean fu_uefi_get_bitmap_size (const guint8 *buf, gsize bufsz, guint32 *width, guint32 *height, GError **error); gboolean fu_uefi_get_framebuffer_size (guint32 *width, guint32 *height, GError **error); gboolean fu_uefi_secure_boot_enabled (void); gchar *fu_uefi_guess_esp_path (void); gboolean fu_uefi_check_esp_path (const gchar *path, GError **error); gboolean fu_uefi_check_esp_free_space (const gchar *path, guint64 required, GError **error); gchar *fu_uefi_get_esp_path_for_os (const gchar *esp_path); GPtrArray *fu_uefi_get_esrt_entry_paths (const gchar *esrt_path, GError **error); guint64 fu_uefi_read_file_as_uint64 (const gchar *path, const gchar *attr_name); gboolean fu_uefi_prefix_efi_errors (GError **error); G_END_DECLS fwupd-1.2.14/plugins/uefi/fu-uefi-device.c000066400000000000000000000477441402665037500203260ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-device-metadata.h" #include "fu-uefi-common.h" #include "fu-uefi-device.h" #include "fu-uefi-devpath.h" #include "fu-uefi-bootmgr.h" #include "fu-uefi-pcrs.h" #include "fu-uefi-vars.h" struct _FuUefiDevice { FuDevice parent_instance; gchar *fw_class; FuUefiDeviceKind kind; guint32 capsule_flags; guint32 fw_version; guint32 fw_version_lowest; FuUefiDeviceStatus last_attempt_status; guint32 last_attempt_version; guint64 fmp_hardware_instance; gboolean missing_header; gboolean requires_header; }; G_DEFINE_TYPE (FuUefiDevice, fu_uefi_device, FU_TYPE_DEVICE) const gchar * fu_uefi_device_kind_to_string (FuUefiDeviceKind kind) { if (kind == FU_UEFI_DEVICE_KIND_UNKNOWN) return "unknown"; if (kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) return "system-firmware"; if (kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) return "device-firmware"; if (kind == FU_UEFI_DEVICE_KIND_UEFI_DRIVER) return "uefi-driver"; if (kind == FU_UEFI_DEVICE_KIND_FMP) return "fmp"; if (kind == FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE) return "dell-tpm-firmware"; return NULL; } static FuUefiDeviceKind fu_uefi_device_kind_from_string (const gchar *kind) { if (g_strcmp0 (kind, "system-firmware") == 0) return FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE; if (g_strcmp0 (kind, "device-firmware") == 0) return FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE; if (g_strcmp0 (kind, "uefi-driver") == 0) return FU_UEFI_DEVICE_KIND_UEFI_DRIVER; if (g_strcmp0 (kind, "fmp") == 0) return FU_UEFI_DEVICE_KIND_FMP; if (g_strcmp0 (kind, "dell-tpm-firmware") == 0) return FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE; return FU_UEFI_DEVICE_KIND_UNKNOWN; } const gchar * fu_uefi_device_status_to_string (FuUefiDeviceStatus status) { if (status == FU_UEFI_DEVICE_STATUS_SUCCESS) return "success"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_UNSUCCESSFUL) return "unsuccessful"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_INSUFFICIENT_RESOURCES) return "insufficient resources"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_INCORRECT_VERSION) return "incorrect version"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_INVALID_FORMAT) return "invalid firmware format"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_AUTH_ERROR) return "authentication signing error"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_AC) return "AC power required"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT) return "battery level is too low"; return NULL; } static void fu_uefi_device_to_string (FuDevice *device, GString *str) { FuUefiDevice *self = FU_UEFI_DEVICE (device); g_string_append (str, " FuUefiDevice:\n"); g_string_append_printf (str, " kind:\t\t\t%s\n", fu_uefi_device_kind_to_string (self->kind)); g_string_append_printf (str, " fw_class:\t\t\t%s\n", self->fw_class); g_string_append_printf (str, " capsule_flags:\t\t%" G_GUINT32_FORMAT "\n", self->capsule_flags); g_string_append_printf (str, " fw_version:\t\t\t%" G_GUINT32_FORMAT "\n", self->fw_version); g_string_append_printf (str, " fw_version_lowest:\t\t%" G_GUINT32_FORMAT "\n", self->fw_version_lowest); g_string_append_printf (str, " last_attempt_status:\t%s\n", fu_uefi_device_status_to_string (self->last_attempt_status)); g_string_append_printf (str, " last_attempt_version:\t%" G_GUINT32_FORMAT "\n", self->last_attempt_version); g_string_append_printf (str, " esp path:\t%s\n", fu_device_get_metadata (device, "EspPath")); } FuUefiDeviceKind fu_uefi_device_get_kind (FuUefiDevice *self) { g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0); return self->kind; } guint32 fu_uefi_device_get_version (FuUefiDevice *self) { g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0x0); return self->fw_version; } guint32 fu_uefi_device_get_version_lowest (FuUefiDevice *self) { g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0x0); return self->fw_version_lowest; } guint32 fu_uefi_device_get_version_error (FuUefiDevice *self) { g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0x0); return self->last_attempt_version; } guint64 fu_uefi_device_get_hardware_instance (FuUefiDevice *self) { g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0x0); return self->fmp_hardware_instance; } FuUefiDeviceStatus fu_uefi_device_get_status (FuUefiDevice *self) { g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0); return self->last_attempt_status; } guint32 fu_uefi_device_get_capsule_flags (FuUefiDevice *self) { g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), 0x0); return self->capsule_flags; } const gchar * fu_uefi_device_get_guid (FuUefiDevice *self) { g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), NULL); return self->fw_class; } static gchar * fu_uefi_device_build_varname (FuUefiDevice *self) { return g_strdup_printf ("fwupd-%s-%"G_GUINT64_FORMAT, self->fw_class, self->fmp_hardware_instance); } FuUefiUpdateInfo * fu_uefi_device_load_update_info (FuUefiDevice *self, GError **error) { gsize datasz = 0; g_autofree gchar *varname = fu_uefi_device_build_varname (self); g_autofree guint8 *data = NULL; g_autoptr(FuUefiUpdateInfo) info = fu_uefi_update_info_new (); g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* get the existing status */ if (!fu_uefi_vars_get_data (FU_UEFI_VARS_GUID_FWUPDATE, varname, &data, &datasz, NULL, error)) return NULL; if (!fu_uefi_update_info_parse (info, data, datasz, error)) return NULL; return g_steal_pointer (&info); } gboolean fu_uefi_device_clear_status (FuUefiDevice *self, GError **error) { efi_update_info_t info; gsize datasz = 0; g_autofree gchar *varname = fu_uefi_device_build_varname (self); g_autofree guint8 *data = NULL; g_return_val_if_fail (FU_IS_UEFI_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* get the existing status */ if (!fu_uefi_vars_get_data (FU_UEFI_VARS_GUID_FWUPDATE, varname, &data, &datasz, NULL, error)) return FALSE; if (datasz < sizeof(efi_update_info_t)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "EFI variable is corrupt"); return FALSE; } /* just copy the efi_update_info_t, ignore devpath then save it back */ memcpy (&info, data, sizeof(info)); info.status = FU_UEFI_DEVICE_STATUS_SUCCESS; memcpy (data, &info, sizeof(info)); return fu_uefi_vars_set_data (FU_UEFI_VARS_GUID_FWUPDATE, varname, data, datasz, FU_UEFI_VARS_ATTR_NON_VOLATILE | FU_UEFI_VARS_ATTR_BOOTSERVICE_ACCESS | FU_UEFI_VARS_ATTR_RUNTIME_ACCESS, error); } static guint8 * fu_uefi_device_build_dp_buf (const gchar *path, gsize *bufsz, GError **error) { gssize req; gssize sz; g_autofree guint8 *dp_buf = NULL; g_autoptr(GPtrArray) dps = NULL; /* get the size of the path first */ req = efi_generate_file_device_path (NULL, 0, path, EFIBOOT_OPTIONS_IGNORE_FS_ERROR | EFIBOOT_ABBREV_HD); if (req < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to efi_generate_file_device_path(%s)", path); return NULL; } /* if we just have an end device path, it's not going to work */ if (req <= 4) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get valid device_path for (%s)", path); return NULL; } /* actually get the path this time */ dp_buf = g_malloc0 (req); sz = efi_generate_file_device_path (dp_buf, req, path, EFIBOOT_OPTIONS_IGNORE_FS_ERROR | EFIBOOT_ABBREV_HD); if (sz < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to efi_generate_file_device_path(%s)", path); return NULL; } /* parse what we got back from efivar */ dps = fu_uefi_devpath_parse (dp_buf, (gsize) sz, FU_UEFI_DEVPATH_PARSE_FLAG_NONE, error); if (dps == NULL) { fu_common_dump_raw (G_LOG_DOMAIN, "dp_buf", dp_buf, (gsize) sz); return NULL; } /* success */ if (bufsz != NULL) *bufsz = sz; return g_steal_pointer (&dp_buf); } static GBytes * fu_uefi_device_fixup_firmware (FuDevice *device, GBytes *fw, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE (device); gsize fw_length; efi_guid_t esrt_guid; efi_guid_t payload_guid; const gchar *data = g_bytes_get_data (fw, &fw_length); self->missing_header = FALSE; /* convert to EFI GUIDs */ if (efi_str_to_guid (fu_uefi_device_get_guid (self), &esrt_guid) < 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid ESRT GUID"); return NULL; } if (fw_length < sizeof(efi_guid_t)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Invalid payload"); return NULL; } memcpy (&payload_guid, data, sizeof(efi_guid_t)); /* ESRT header matches payload */ if (efi_guid_cmp (&esrt_guid, &payload_guid) == 0) { g_debug ("ESRT matches payload GUID"); return g_bytes_new_from_bytes (fw, 0, fw_length); /* Type that doesn't require a header */ } else if (!self->requires_header) { return g_bytes_new_from_bytes (fw, 0, fw_length); /* Missing, add a header */ } else { guint header_size = getpagesize(); guint8 *new_data = g_malloc (fw_length + header_size); guint8 *capsule = new_data + header_size; efi_capsule_header_t *header = (efi_capsule_header_t *) new_data; g_warning ("missing or invalid embedded capsule header"); self->missing_header = TRUE; header->flags = self->capsule_flags; header->header_size = header_size; header->capsule_image_size = fw_length + header_size; memcpy (&header->guid, &esrt_guid, sizeof (efi_guid_t)); memcpy (capsule, data, fw_length); return g_bytes_new_take (new_data, fw_length + header_size); } } gboolean fu_uefi_missing_capsule_header (FuDevice *device) { FuUefiDevice *self = FU_UEFI_DEVICE (device); return self->missing_header; } gboolean fu_uefi_device_write_update_info (FuUefiDevice *self, const gchar *filename, const gchar *varname, const efi_guid_t *guid, GError **error) { gsize datasz = 0; gsize dp_bufsz = 0; g_autofree guint8 *data = NULL; g_autofree guint8 *dp_buf = NULL; efi_update_info_t info = { .update_info_version = 0x7, .guid = { 0x0 }, .capsule_flags = self->capsule_flags, .hw_inst = self->fmp_hardware_instance, .time_attempted = { 0x0 }, .status = FU_UEFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE, }; /* set the body as the device path */ if (g_getenv ("FWUPD_UEFI_ESP_PATH") != NULL) { g_debug ("not building device path, in tests...."); return TRUE; } /* convert to EFI device path */ dp_buf = fu_uefi_device_build_dp_buf (filename, &dp_bufsz, error); if (dp_buf == NULL) { fu_uefi_prefix_efi_errors (error); return FALSE; } /* save this header and body to the hardware */ memcpy (&info.guid, guid, sizeof(efi_guid_t)); datasz = sizeof(info) + dp_bufsz; data = g_malloc0 (datasz); memcpy (data, &info, sizeof(info)); memcpy (data + sizeof(info), dp_buf, dp_bufsz); if (!fu_uefi_vars_set_data (FU_UEFI_VARS_GUID_FWUPDATE, varname, data, datasz, FU_UEFI_VARS_ATTR_NON_VOLATILE | FU_UEFI_VARS_ATTR_BOOTSERVICE_ACCESS | FU_UEFI_VARS_ATTR_RUNTIME_ACCESS, error)) { fu_uefi_prefix_efi_errors (error); return FALSE; } return TRUE; } static gboolean fu_uefi_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags install_flags, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE (device); FuUefiBootmgrFlags flags = FU_UEFI_BOOTMGR_FLAG_NONE; const gchar *bootmgr_desc = "Linux Firmware Updater"; const gchar *esp_path = fu_device_get_metadata (device, "EspPath"); efi_guid_t guid; g_autoptr(GBytes) fixed_fw = NULL; g_autofree gchar *basename = NULL; g_autofree gchar *directory = NULL; g_autofree gchar *fn = NULL; g_autofree gchar *varname = fu_uefi_device_build_varname (self); /* ensure we have the existing state */ if (self->fw_class == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot update device info with no GUID"); return FALSE; } /* save the blob to the ESP */ directory = fu_uefi_get_esp_path_for_os (esp_path); basename = g_strdup_printf ("fwupd-%s.cap", self->fw_class); fn = g_build_filename (directory, "fw", basename, NULL); if (!fu_common_mkdir_parent (fn, error)) return FALSE; fixed_fw = fu_uefi_device_fixup_firmware (device, fw, error); if (fixed_fw == NULL) return FALSE; if (!fu_common_set_contents_bytes (fn, fixed_fw, error)) return FALSE; /* set the blob header shared with fwupd.efi */ if (efi_str_to_guid (self->fw_class, &guid) < 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get convert GUID"); return FALSE; } if (!fu_uefi_device_write_update_info (self, fn, varname, &guid, error)) return FALSE; /* update the firmware before the bootloader runs */ if (fu_device_get_metadata_boolean (device, "RequireShimForSecureBoot")) flags |= FU_UEFI_BOOTMGR_FLAG_USE_SHIM_FOR_SB; if (fu_device_has_custom_flag (device, "use-shim-unique")) flags |= FU_UEFI_BOOTMGR_FLAG_USE_SHIM_UNIQUE; /* some legacy devices use the old name to deduplicate boot entries */ if (fu_device_has_custom_flag (device, "use-legacy-bootmgr-desc")) bootmgr_desc = "Linux-Firmware-Updater"; if (!fu_uefi_bootmgr_bootnext (esp_path, bootmgr_desc, flags, error)) return FALSE; /* success! */ return TRUE; } static gboolean fu_uefi_device_add_system_checksum (FuDevice *device, GError **error) { g_autoptr(FuUefiPcrs) pcrs = fu_uefi_pcrs_new (); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) pcr0s = NULL; /* get all the PCRs */ if (!fu_uefi_pcrs_setup (pcrs, &error_local)) { if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { g_debug ("%s", error_local->message); return TRUE; } g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } /* get all the PCR0s */ pcr0s = fu_uefi_pcrs_get_checksums (pcrs, 0); if (pcr0s->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no PCR0s detected"); return FALSE; } for (guint i = 0; i < pcr0s->len; i++) { const gchar *checksum = g_ptr_array_index (pcr0s, i); fu_device_add_checksum (device, checksum); } /* success */ return TRUE; } static gboolean fu_uefi_device_probe (FuDevice *device, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE (device); FwupdVersionFormat version_format; g_autofree gchar *devid = NULL; g_autofree gchar *guid_strup = NULL; g_autofree gchar *version_lowest = NULL; g_autofree gchar *version = NULL; /* broken sysfs? */ if (self->fw_class == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read fw_class"); return FALSE; } /* add GUID first, as quirks may set the version format */ fu_device_add_guid (device, self->fw_class); /* set versions */ version_format = fu_device_get_version_format (device); version = fu_common_version_from_uint32 (self->fw_version, version_format); fu_device_set_version (device, version, version_format); if (self->fw_version_lowest != 0) { version_lowest = fu_common_version_from_uint32 (self->fw_version_lowest, version_format); fu_device_set_version_lowest (device, version_lowest); } /* set flags */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_REQUIRE_AC); /* add icons */ if (self->kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) { /* nothing better in the icon naming spec */ fu_device_add_icon (device, "audio-card"); } else { /* this is probably system firmware */ fu_device_add_icon (device, "computer"); fu_device_add_instance_id (device, "main-system-firmware"); } /* set the PCR0 as the device checksum */ if (self->kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) { g_autoptr(GError) error_local = NULL; if (!fu_uefi_device_add_system_checksum (device, &error_local)) g_warning ("Failed to get PCR0s: %s", error_local->message); } /* whether to create a missing header */ if (self->kind == FU_UEFI_DEVICE_KIND_FMP || self->kind == FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE) self->requires_header = FALSE; else self->requires_header = TRUE; /* Windows seems to be case insensitive, but for convenience we'll * match the upper case values typically specified in the .inf file */ guid_strup = g_ascii_strup (self->fw_class, -1); devid = g_strdup_printf ("UEFI\\RES_{%s}", guid_strup); fu_device_add_instance_id (device, devid); return TRUE; } static void fu_uefi_device_init (FuUefiDevice *self) { } static void fu_uefi_device_finalize (GObject *object) { FuUefiDevice *self = FU_UEFI_DEVICE (object); g_free (self->fw_class); G_OBJECT_CLASS (fu_uefi_device_parent_class)->finalize (object); } static void fu_uefi_device_class_init (FuUefiDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); object_class->finalize = fu_uefi_device_finalize; klass_device->to_string = fu_uefi_device_to_string; klass_device->probe = fu_uefi_device_probe; klass_device->write_firmware = fu_uefi_device_write_firmware; } FuUefiDevice * fu_uefi_device_new_from_entry (const gchar *entry_path, GError **error) { g_autoptr(FuUefiDevice) self = NULL; g_autofree gchar *fw_class_fn = NULL; g_autofree gchar *id = NULL; g_return_val_if_fail (entry_path != NULL, NULL); /* create object */ self = g_object_new (FU_TYPE_UEFI_DEVICE, NULL); /* read values from sysfs */ fw_class_fn = g_build_filename (entry_path, "fw_class", NULL); if (g_file_get_contents (fw_class_fn, &self->fw_class, NULL, NULL)) g_strdelimit (self->fw_class, "\n", '\0'); self->capsule_flags = fu_uefi_read_file_as_uint64 (entry_path, "capsule_flags"); self->kind = fu_uefi_read_file_as_uint64 (entry_path, "fw_type"); self->fw_version = fu_uefi_read_file_as_uint64 (entry_path, "fw_version"); self->last_attempt_status = fu_uefi_read_file_as_uint64 (entry_path, "last_attempt_status"); self->last_attempt_version = fu_uefi_read_file_as_uint64 (entry_path, "last_attempt_version"); self->fw_version_lowest = fu_uefi_read_file_as_uint64 (entry_path, "lowest_supported_fw_version"); /* the hardware instance is not in the ESRT table and we should really * write the EFI stub to query with FMP -- but we still have not ever * seen a PCIe device with FMP support... */ self->fmp_hardware_instance = 0x0; /* set ID */ id = g_strdup_printf ("UEFI-%s-dev%" G_GUINT64_FORMAT, self->fw_class, self->fmp_hardware_instance); fu_device_set_id (FU_DEVICE (self), id); /* this is invalid */ if (!fwupd_guid_is_valid (self->fw_class)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ESRT GUID '%s' was not valid", self->fw_class); return NULL; } return g_steal_pointer (&self); } FuUefiDevice * fu_uefi_device_new_from_dev (FuDevice *dev) { const gchar *tmp; FuUefiDevice *self; g_return_val_if_fail (fu_device_get_guid_default (dev) != NULL, NULL); /* create virtual object not backed by an ESRT entry */ self = g_object_new (FU_TYPE_UEFI_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), dev); self->fw_class = g_strdup (fu_device_get_guid_default (dev)); tmp = fu_device_get_metadata (dev, FU_DEVICE_METADATA_UEFI_DEVICE_KIND); self->kind = fu_uefi_device_kind_from_string (tmp); self->capsule_flags = fu_device_get_metadata_integer (dev, FU_DEVICE_METADATA_UEFI_CAPSULE_FLAGS); self->fw_version = fu_device_get_metadata_integer (dev, FU_DEVICE_METADATA_UEFI_FW_VERSION); g_assert (self->fw_class != NULL); return self; } FuUefiDevice * fu_uefi_device_new_from_guid (const gchar *guid) { FuUefiDevice *self; self = g_object_new (FU_TYPE_UEFI_DEVICE, NULL); self->fw_class = g_strdup (guid); return self; } fwupd-1.2.14/plugins/uefi/fu-uefi-device.h000066400000000000000000000046701402665037500203220ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-uefi-device.h" #include "fu-uefi-update-info.h" G_BEGIN_DECLS #define FU_TYPE_UEFI_DEVICE (fu_uefi_device_get_type ()) G_DECLARE_FINAL_TYPE (FuUefiDevice, fu_uefi_device, FU, UEFI_DEVICE, FuDevice) typedef enum { FU_UEFI_DEVICE_KIND_UNKNOWN, FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE, FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE, FU_UEFI_DEVICE_KIND_UEFI_DRIVER, FU_UEFI_DEVICE_KIND_FMP, FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE, FU_UEFI_DEVICE_KIND_LAST } FuUefiDeviceKind; typedef enum { FU_UEFI_DEVICE_STATUS_SUCCESS = 0x00, FU_UEFI_DEVICE_STATUS_ERROR_UNSUCCESSFUL = 0x01, FU_UEFI_DEVICE_STATUS_ERROR_INSUFFICIENT_RESOURCES = 0x02, FU_UEFI_DEVICE_STATUS_ERROR_INCORRECT_VERSION = 0x03, FU_UEFI_DEVICE_STATUS_ERROR_INVALID_FORMAT = 0x04, FU_UEFI_DEVICE_STATUS_ERROR_AUTH_ERROR = 0x05, FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_AC = 0x06, FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT = 0x07, FU_UEFI_DEVICE_STATUS_LAST } FuUefiDeviceStatus; FuUefiDevice *fu_uefi_device_new_from_guid (const gchar *guid); FuUefiDevice *fu_uefi_device_new_from_entry (const gchar *entry_path, GError **error); FuUefiDevice *fu_uefi_device_new_from_dev (FuDevice *dev); gboolean fu_uefi_device_clear_status (FuUefiDevice *self, GError **error); FuUefiDeviceKind fu_uefi_device_get_kind (FuUefiDevice *self); const gchar *fu_uefi_device_get_guid (FuUefiDevice *self); guint32 fu_uefi_device_get_version (FuUefiDevice *self); guint32 fu_uefi_device_get_version_lowest (FuUefiDevice *self); guint32 fu_uefi_device_get_version_error (FuUefiDevice *self); guint32 fu_uefi_device_get_capsule_flags (FuUefiDevice *self); guint64 fu_uefi_device_get_hardware_instance (FuUefiDevice *self); FuUefiDeviceStatus fu_uefi_device_get_status (FuUefiDevice *self); const gchar *fu_uefi_device_kind_to_string (FuUefiDeviceKind kind); const gchar *fu_uefi_device_status_to_string (FuUefiDeviceStatus status); FuUefiUpdateInfo *fu_uefi_device_load_update_info (FuUefiDevice *self, GError **error); gboolean fu_uefi_missing_capsule_header (FuDevice *device); gboolean fu_uefi_device_write_update_info (FuUefiDevice *self, const gchar *filename, const gchar *varname, const efi_guid_t *guid, GError **error); G_END_DECLS fwupd-1.2.14/plugins/uefi/fu-uefi-devpath.c000066400000000000000000000063161402665037500205100ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "fu-uefi-devpath.h" #include "fwupd-error.h" typedef struct { guint8 type; guint8 subtype; GBytes *data; } FuUefiDevPath; static void fu_uefi_efi_dp_free (FuUefiDevPath *dp) { if (dp->data != NULL) g_bytes_unref (dp->data); g_free (dp); } GBytes * fu_uefi_devpath_find_data (GPtrArray *dps, guint8 type, guint8 subtype, GError **error) { for (guint i = 0; i < dps->len; i++) { FuUefiDevPath *dp = g_ptr_array_index (dps, i); if (dp->type == type && dp->subtype == subtype) return dp->data; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no DP with type 0x%02x and subtype 0x%02x", type, subtype); return NULL; } GPtrArray * fu_uefi_devpath_parse (const guint8 *buf, gsize sz, FuUefiDevpathParseFlags flags, GError **error) { guint16 offset = 0; g_autoptr(GPtrArray) dps = NULL; /* sanity check */ if (sz < sizeof(efidp_header)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "const_efidp is corrupt"); return NULL; } dps = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_uefi_efi_dp_free); while (1) { FuUefiDevPath *dp; const efidp_header *hdr = (efidp_header *) (buf + offset); guint16 hdr_length = GUINT16_FROM_LE(hdr->length); /* check if last entry */ g_debug ("DP type:0x%02x subtype:0x%02x size:0x%04x", hdr->type, hdr->subtype, hdr->length); if (hdr->type == EFIDP_END_TYPE && hdr->subtype == EFIDP_END_ENTIRE) break; /* work around a bug in efi_va_generate_file_device_path_from_esp */ if (offset + sizeof(efidp_header) + hdr->length > sz) { hdr_length = 0; fu_common_dump_full (G_LOG_DOMAIN, "efidp", buf + offset, sz - offset, 32, FU_DUMP_FLAGS_SHOW_ADDRESSES); for (guint16 i = offset + 4; i <= sz - 4; i++) { if (memcmp (buf + i, "\x7f\xff\x04\x00", 4) == 0) { hdr_length = i - offset; g_debug ("found END_ENTIRE at 0x%04x", (guint) (i - offset)); break; } } if (hdr_length == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DP length invalid and no END_ENTIRE " "found, possibly data truncation?"); return NULL; } if ((flags & FU_UEFI_DEVPATH_PARSE_FLAG_REPAIR) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DP length invalid, reported 0x%04x, maybe 0x%04x", hdr->length, hdr_length); return NULL; } g_debug ("DP length invalid! Truncating from 0x%04x to 0x%04x", hdr->length, hdr_length); } /* add new DP */ dp = g_new0 (FuUefiDevPath, 1); dp->type = hdr->type; dp->subtype = hdr->subtype; if (hdr_length > 0) dp->data = g_bytes_new (buf + offset + 4, hdr_length); g_ptr_array_add (dps, dp); /* advance to next DP */ offset += hdr_length; if (offset + sizeof(efidp_header) > sz) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DP length invalid after fixing"); return NULL; } } return g_steal_pointer (&dps); } fwupd-1.2.14/plugins/uefi/fu-uefi-devpath.h000066400000000000000000000011221402665037500205030ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS typedef enum { FU_UEFI_DEVPATH_PARSE_FLAG_NONE = 0, FU_UEFI_DEVPATH_PARSE_FLAG_REPAIR = 1 << 0, FU_UEFI_DEVPATH_PARSE_FLAG_LAST } FuUefiDevpathParseFlags; GPtrArray *fu_uefi_devpath_parse (const guint8 *buf, gsize sz, FuUefiDevpathParseFlags flags, GError **error); GBytes *fu_uefi_devpath_find_data (GPtrArray *dps, guint8 type, guint8 subtype, GError **error); G_END_DECLS fwupd-1.2.14/plugins/uefi/fu-uefi-pcrs.c000066400000000000000000000130011402665037500200110ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-uefi-pcrs.h" typedef struct { guint idx; gchar *checksum; } FuUefiPcrItem; struct _FuUefiPcrs { GObject parent_instance; GPtrArray *items; /* of FuUefiPcrItem */ }; G_DEFINE_TYPE (FuUefiPcrs, fu_uefi_pcrs, G_TYPE_OBJECT) static gboolean _g_string_isxdigit (GString *str) { for (gsize i = 0; i < str->len; i++) { if (!g_ascii_isxdigit (str->str[i])) return FALSE; } return TRUE; } static void fu_uefi_pcrs_parse_line (const gchar *line, gpointer user_data) { FuUefiPcrs *self = FU_UEFI_PCRS (user_data); FuUefiPcrItem *item; guint64 idx; g_autofree gchar *idxstr = NULL; g_auto(GStrv) split = NULL; g_autoptr(GString) str = NULL; /* split into index:hash */ if (line == NULL || line[0] == '\0') return; split = g_strsplit (line, ":", -1); if (g_strv_length (split) != 2) { g_debug ("unexpected format, skipping: %s", line); return; } /* get index */ idxstr = fu_common_strstrip (split[0]); idx = fu_common_strtoull (idxstr); if (idx > 64) { g_debug ("unexpected index, skipping: %s", idxstr); return; } /* parse hash */ str = g_string_new (split[1]); fu_common_string_replace (str, " ", ""); fu_common_string_replace (str, "0x", ""); if ((str->len != 40 && str->len != 64) || !_g_string_isxdigit (str)) { g_debug ("not SHA-1 or SHA-256, skipping: %s", split[1]); return; } g_string_ascii_down (str); item = g_new0 (FuUefiPcrItem, 1); item->idx = idx; item->checksum = g_string_free (g_steal_pointer (&str), FALSE); g_ptr_array_add (self->items, item); g_debug ("added PCR-%02u=%s", item->idx, item->checksum); } static gboolean fu_uefi_pcrs_setup_dummy (FuUefiPcrs *self, const gchar *test_yaml, GError **error) { g_auto(GStrv) lines = g_strsplit (test_yaml, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) fu_uefi_pcrs_parse_line (lines[i], self); return TRUE; } static gboolean fu_uefi_pcrs_setup_tpm12 (FuUefiPcrs *self, const gchar *fn_pcrs, GError **error) { g_auto(GStrv) lines = NULL; g_autofree gchar *buf_pcrs = NULL; /* get entire contents */ if (!g_file_get_contents (fn_pcrs, &buf_pcrs, NULL, error)) return FALSE; /* find PCR lines */ lines = g_strsplit (buf_pcrs, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { if (g_str_has_prefix (lines[i], "PCR-")) fu_uefi_pcrs_parse_line (lines[i] + 4, self); } return TRUE; } static gboolean fu_uefi_pcrs_setup_tpm20 (FuUefiPcrs *self, const gchar *argv0, GError **error) { const gchar *argv[] = { argv0, NULL }; return fu_common_spawn_sync (argv, fu_uefi_pcrs_parse_line, self, 1500, NULL, error); } gboolean fu_uefi_pcrs_setup (FuUefiPcrs *self, GError **error) { g_autofree gchar *devpath = NULL; g_autofree gchar *sysfstpmdir = NULL; g_autofree gchar *fn_pcrs = NULL; const gchar *test_yaml = g_getenv ("FWUPD_UEFI_TPM2_YAML_DATA"); g_return_val_if_fail (FU_IS_UEFI_PCRS (self), FALSE); /* check the TPM device exists at all */ sysfstpmdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_TPM); devpath = g_build_filename (sysfstpmdir, "tpm0", NULL); if (!g_file_test (devpath, G_FILE_TEST_EXISTS)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no TPM device found"); return FALSE; } fn_pcrs = g_build_filename (devpath, "pcrs", NULL); /* fake device */ if (test_yaml != NULL) { if (!fu_uefi_pcrs_setup_dummy (self, test_yaml, error)) return FALSE; /* look for TPM 1.2 */ } else if (g_file_test (fn_pcrs, G_FILE_TEST_EXISTS)) { if (!fu_uefi_pcrs_setup_tpm12 (self, fn_pcrs, error)) return FALSE; /* assume TPM 2.0 */ } else { g_autofree gchar *argv0 = NULL; /* tpm2-tools 2 tool name */ argv0 = fu_common_find_program_in_path ("tpm2_listpcrs", NULL); if (argv0 == NULL) /* tpm2-tools 3 tool name */ argv0 = fu_common_find_program_in_path ("tpm2_pcrlist", error); if (argv0 == NULL) /* tpm2-tools 4 tool name */ argv0 = fu_common_find_program_in_path ("tpm2_pcrread", error); if (argv0 == NULL) return FALSE; if (!fu_uefi_pcrs_setup_tpm20 (self, argv0, error)) return FALSE; } /* check we got anything */ if (self->items->len == 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no TPMxx measurements found"); return FALSE; } /* success */ return TRUE; } GPtrArray * fu_uefi_pcrs_get_checksums (FuUefiPcrs *self, guint idx) { g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func (g_free); g_return_val_if_fail (FU_IS_UEFI_PCRS (self), NULL); for (guint i = 0; i < self->items->len; i++) { FuUefiPcrItem *item = g_ptr_array_index (self->items, i); if (item->idx == idx) g_ptr_array_add (array, g_strdup (item->checksum)); } return g_steal_pointer (&array); } static void fu_uefi_pcrs_item_free (FuUefiPcrItem *item) { g_free (item->checksum); g_free (item); } static void fu_uefi_pcrs_finalize (GObject *object) { FuUefiPcrs *self = FU_UEFI_PCRS (object); g_ptr_array_unref (self->items); G_OBJECT_CLASS (fu_uefi_pcrs_parent_class)->finalize (object); } static void fu_uefi_pcrs_class_init (FuUefiPcrsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_uefi_pcrs_finalize; } static void fu_uefi_pcrs_init (FuUefiPcrs *self) { self->items = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_uefi_pcrs_item_free); } FuUefiPcrs * fu_uefi_pcrs_new (void) { FuUefiPcrs *self; self = g_object_new (FU_TYPE_UEFI_PCRS, NULL); return FU_UEFI_PCRS (self); } fwupd-1.2.14/plugins/uefi/fu-uefi-pcrs.h000066400000000000000000000007221402665037500200240ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once G_BEGIN_DECLS #define FU_TYPE_UEFI_PCRS (fu_uefi_pcrs_get_type ()) G_DECLARE_FINAL_TYPE (FuUefiPcrs, fu_uefi_pcrs, FU, UEFI_PCRS, GObject) FuUefiPcrs *fu_uefi_pcrs_new (void); gboolean fu_uefi_pcrs_setup (FuUefiPcrs *self, GError **error); GPtrArray *fu_uefi_pcrs_get_checksums (FuUefiPcrs *self, guint idx); G_END_DECLS fwupd-1.2.14/plugins/uefi/fu-uefi-tool.c000066400000000000000000000256731402665037500200410ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include "fu-ucs2.h" #include "fu-uefi-common.h" #include "fu-uefi-device.h" #include "fu-uefi-update-info.h" #include "fu-uefi-vars.h" /* custom return code */ #define EXIT_NOTHING_TO_DO 2 typedef struct { GCancellable *cancellable; GMainLoop *loop; GOptionContext *context; } FuUtilPrivate; static void fu_util_ignore_cb (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { } static void fu_util_private_free (FuUtilPrivate *priv) { if (priv->context != NULL) g_option_context_free (priv->context); g_free (priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free) #pragma clang diagnostic pop int main (int argc, char *argv[]) { gboolean action_enable = FALSE; gboolean action_info = FALSE; gboolean action_list = FALSE; gboolean action_log = FALSE; gboolean action_set_debug = FALSE; gboolean action_supported = FALSE; gboolean action_unset_debug = FALSE; gboolean action_version = FALSE; gboolean ret; gboolean verbose = FALSE; g_autofree gchar *apply = FALSE; g_autofree gchar *esp_path = NULL; g_autoptr(FuUtilPrivate) priv = g_new0 (FuUtilPrivate, 1); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; const GOptionEntry options[] = { { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: command line option */ _("Show extra debugging information"), NULL }, { "version", '\0', 0, G_OPTION_ARG_NONE, &action_version, /* TRANSLATORS: command line option */ _("Display version"), NULL }, { "log", 'L', 0, G_OPTION_ARG_NONE, &action_log, /* TRANSLATORS: command line option */ _("Show the debug log from the last attempted update"), NULL }, { "list", 'l', 0, G_OPTION_ARG_NONE, &action_list, /* TRANSLATORS: command line option */ _("List supported firmware updates"), NULL }, { "supported", 's', 0, G_OPTION_ARG_NONE, &action_supported, /* TRANSLATORS: command line option */ _("Query for firmware update support"), NULL }, { "info", 'i', 0, G_OPTION_ARG_NONE, &action_info, /* TRANSLATORS: command line option */ _("Show the information of firmware update status"), NULL }, { "enable", 'e', 0, G_OPTION_ARG_NONE, &action_enable, /* TRANSLATORS: command line option */ _("Enable firmware update support on supported systems"), NULL }, { "esp-path", 'p', 0, G_OPTION_ARG_STRING, &esp_path, /* TRANSLATORS: command line option */ _("Override the default ESP path"), "PATH" }, { "set-debug", 'd', 0, G_OPTION_ARG_NONE, &action_set_debug, /* TRANSLATORS: command line option */ _("Set the debugging flag during update"), NULL }, { "unset-debug", 'D', 0, G_OPTION_ARG_NONE, &action_unset_debug, /* TRANSLATORS: command line option */ _("Unset the debugging flag during update"), NULL }, { "apply", 'a', 0, G_OPTION_ARG_STRING, &apply, /* TRANSLATORS: command line option */ _("Apply firmware updates"), "GUID" }, { NULL} }; setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); /* ensure root user */ if (getuid () != 0 || geteuid () != 0) /* TRANSLATORS: we're poking around as a power user */ g_printerr ("%s\n", _("This program may only work correctly as root")); /* get a action_list of the commands */ priv->context = g_option_context_new (NULL); g_option_context_set_description (priv->context, "This tool allows an administrator to debug UpdateCapsule operation."); /* TRANSLATORS: program name */ g_set_application_name (_("UEFI Firmware Utility")); g_option_context_add_main_entries (priv->context, options, NULL); ret = g_option_context_parse (priv->context, &argc, &argv, &error); if (!ret) { /* TRANSLATORS: the user didn't read the man page */ g_print ("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* set verbose? */ if (verbose) { g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); } else { g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, fu_util_ignore_cb, NULL); } /* nothing specified */ if (!action_enable && !action_info && !action_list && !action_log && !action_set_debug && !action_supported && !action_unset_debug && !action_version && apply == NULL) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help (priv->context, TRUE, NULL); g_printerr ("%s\n\n%s", _("No action specified!"), tmp); return EXIT_FAILURE; } /* action_version first */ if (action_version) g_print ("fwupd version: %s\n", PACKAGE_VERSION); /* override the default ESP path */ if (esp_path != NULL) { if (!fu_uefi_check_esp_path (esp_path, &error)) { /* TRANSLATORS: ESP is EFI System Partition */ g_print ("%s: %s\n", _("ESP specified was not valid"), error->message); return EXIT_FAILURE; } } else { esp_path = fu_uefi_guess_esp_path (); if (esp_path == NULL) { g_printerr ("Unable to determine EFI system partition " "location, override using --esp-path\n"); return EXIT_FAILURE; } } /* check free space */ if (!fu_uefi_check_esp_free_space (esp_path, FU_UEFI_COMMON_REQUIRED_ESP_FREE_SPACE, &error)) { g_printerr ("Unable to use EFI system partition: %s\n", error->message); return EXIT_FAILURE; } g_debug ("ESP mountpoint set as %s", esp_path); /* show the debug action_log from the last attempted update */ if (action_log) { gsize sz = 0; g_autofree guint8 *buf = NULL; g_autofree guint16 *buf_ucs2 = NULL; g_autofree gchar *str = NULL; g_autoptr(GError) error_local = NULL; if (!fu_uefi_vars_get_data (FU_UEFI_VARS_GUID_FWUPDATE, "FWUPDATE_DEBUG_LOG", &buf, &sz, NULL, &error_local)) { g_printerr ("failed: %s\n", error_local->message); return EXIT_FAILURE; } buf_ucs2 = g_new0 (guint16, (sz / 2) + 1); memcpy (buf_ucs2, buf, sz); str = fu_ucs2_to_uft8 (buf_ucs2, sz / 2); g_print ("%s", str); } if (action_list || action_supported || action_info) { g_autoptr(GPtrArray) entries = NULL; g_autofree gchar *esrt_path = NULL; g_autofree gchar *sysfsfwdir = NULL; g_autoptr(GError) error_local = NULL; /* get the directory of ESRT entries */ sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); esrt_path = g_build_filename (sysfsfwdir, "efi", "esrt", NULL); entries = fu_uefi_get_esrt_entry_paths (esrt_path, &error_local); if (entries == NULL) { g_printerr ("failed: %s\n", error_local->message); return EXIT_FAILURE; } /* add each device */ devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < entries->len; i++) { const gchar *path = g_ptr_array_index (entries, i); g_autoptr(GError) error_parse = NULL; g_autoptr(FuUefiDevice) dev = fu_uefi_device_new_from_entry (path, &error_parse); if (dev == NULL) { g_warning ("failed to parse %s: %s", path, error_parse->message); continue; } fu_device_set_metadata (FU_DEVICE (dev), "EspPath", esp_path); g_ptr_array_add (devices, g_object_ref (dev)); } } /* action_list action_supported firmware updates */ if (action_list) { for (guint i = 0; i < devices->len; i++) { FuUefiDevice *dev = g_ptr_array_index (devices, i); g_print ("%s type, {%s} version %" G_GUINT32_FORMAT " can be updated " "to any version above %" G_GUINT32_FORMAT "\n", fu_uefi_device_kind_to_string (fu_uefi_device_get_kind (dev)), fu_uefi_device_get_guid (dev), fu_uefi_device_get_version (dev), fu_uefi_device_get_version_lowest (dev) - 1); } } /* query for firmware update support */ if (action_supported) { if (devices->len > 0) { g_print ("%s\n", _("Firmware updates are supported on this machine.")); } else { g_print ("%s\n", _("Firmware updates are not supported on this machine.")); } } /* show the information of firmware update status */ if (action_info) { for (guint i = 0; i < devices->len; i++) { FuUefiDevice *dev = g_ptr_array_index (devices, i); g_autoptr(FuUefiUpdateInfo) info = NULL; g_autoptr(GError) error_local = NULL; /* load any existing update info */ info = fu_uefi_device_load_update_info (dev, &error_local); if (info == NULL) { g_printerr ("failed: %s\n", error_local->message); continue; } g_print ("Information for the update status entry %u:\n", i); g_print (" Information Version: %" G_GUINT32_FORMAT "\n", fu_uefi_update_info_get_version (info)); g_print (" Firmware GUID: {%s}\n", fu_uefi_update_info_get_guid (info)); g_print (" Capsule Flags: 0x%08" G_GUINT32_FORMAT "x\n", fu_uefi_update_info_get_capsule_flags (info)); g_print (" Hardware Instance: %" G_GUINT64_FORMAT "\n", fu_uefi_update_info_get_hw_inst (info)); g_print (" Update Status: %s\n", fu_uefi_update_info_status_to_string (fu_uefi_update_info_get_status (info))); g_print (" Capsule File Path: %s\n\n", fu_uefi_update_info_get_capsule_fn (info)); } } /* action_enable firmware update support on action_supported systems */ if (action_enable) { g_printerr ("Unsupported, use `fwupdmgr unlock`\n"); return EXIT_FAILURE; } /* set the debugging flag during update */ if (action_set_debug) { const guint8 data = 1; g_autoptr(GError) error_local = NULL; if (!fu_uefi_vars_set_data (FU_UEFI_VARS_GUID_FWUPDATE, "FWUPDATE_VERBOSE", &data, sizeof(data), EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, &error_local)) { g_printerr ("failed: %s\n", error_local->message); return EXIT_FAILURE; } g_print ("%s\n", _("Enabled fwupdate debugging")); } /* unset the debugging flag during update */ if (action_unset_debug) { g_autoptr(GError) error_local = NULL; if (!fu_uefi_vars_delete (FU_UEFI_VARS_GUID_FWUPDATE, "FWUPDATE_VERBOSE", &error_local)) { g_printerr ("failed: %s\n", error_local->message); return EXIT_FAILURE; } g_print ("%s\n", _("Disabled fwupdate debugging")); } /* apply firmware updates */ if (apply != NULL) { g_autoptr(FuUefiDevice) dev = fu_uefi_device_new_from_guid (apply); g_autoptr(GError) error_local = NULL; g_autoptr(GBytes) fw = NULL; if (argv[1] == NULL) { g_printerr ("capsule filename required\n"); return EXIT_FAILURE; } fw = fu_common_get_contents_bytes (argv[1], &error_local); if (fw == NULL) { g_printerr ("failed: %s\n", error_local->message); return EXIT_FAILURE; } fu_device_set_metadata (FU_DEVICE (dev), "EspPath", esp_path); if (!fu_device_write_firmware (FU_DEVICE (dev), fw, FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_printerr ("failed: %s\n", error_local->message); return EXIT_FAILURE; } } /* success */ return EXIT_SUCCESS; } fwupd-1.2.14/plugins/uefi/fu-uefi-update-info.c000066400000000000000000000107721402665037500212710ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-uefi-devpath.h" #include "fu-uefi-update-info.h" #include "fu-uefi-common.h" #include "fu-ucs2.h" #include "fwupd-error.h" struct _FuUefiUpdateInfo { GObject parent_instance; guint32 version; gchar *guid; gchar *capsule_fn; guint32 capsule_flags; guint64 hw_inst; FuUefiUpdateInfoStatus status; }; G_DEFINE_TYPE (FuUefiUpdateInfo, fu_uefi_update_info, G_TYPE_OBJECT) const gchar * fu_uefi_update_info_status_to_string (FuUefiUpdateInfoStatus status) { if (status == FU_UEFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE) return "attempt-update"; if (status == FU_UEFI_UPDATE_INFO_STATUS_ATTEMPTED) return "attempted"; return "unknown"; } static gchar * fu_uefi_update_info_parse_dp (const guint8 *buf, gsize sz, GError **error) { GBytes *dp_data; const gchar *data; gsize ucs2sz = 0; g_autofree gchar *relpath = NULL; g_autofree guint16 *ucs2file = NULL; g_autoptr(GPtrArray) dps = NULL; g_return_val_if_fail (buf != NULL, NULL); g_return_val_if_fail (sz != 0, NULL); /* get all headers */ dps = fu_uefi_devpath_parse (buf, sz, FU_UEFI_DEVPATH_PARSE_FLAG_REPAIR, error); if (dps == NULL) return NULL; dp_data = fu_uefi_devpath_find_data (dps, EFIDP_MEDIA_TYPE, EFIDP_MEDIA_FILE, error); if (dp_data == NULL) return NULL; /* convert to UTF-8 */ data = g_bytes_get_data (dp_data, &ucs2sz); ucs2file = g_new0 (guint16, (ucs2sz / 2) + 1); memcpy (ucs2file, data, ucs2sz); relpath = fu_ucs2_to_uft8 (ucs2file, ucs2sz / sizeof (guint16)); if (relpath == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot convert to UTF-8"); return NULL; } g_strdelimit (relpath, "\\", '/'); return g_steal_pointer (&relpath); } gboolean fu_uefi_update_info_parse (FuUefiUpdateInfo *self, const guint8 *buf, gsize sz, GError **error) { efi_update_info_t info; efi_guid_t guid_tmp; g_return_val_if_fail (FU_IS_UEFI_UPDATE_INFO (self), FALSE); if (sz < sizeof(efi_update_info_t)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "EFI variable is corrupt"); return FALSE; } memcpy (&info, buf, sizeof(info)); self->version = info.update_info_version; self->capsule_flags = info.capsule_flags; self->hw_inst = info.hw_inst; self->status = info.status; memcpy (&guid_tmp, &info.guid, sizeof(efi_guid_t)); if (efi_guid_to_str (&guid_tmp, &self->guid) < 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to convert GUID"); return FALSE; } if (sz > sizeof(efi_update_info_t)) { self->capsule_fn = fu_uefi_update_info_parse_dp (buf + sizeof(efi_update_info_t), sz - sizeof(efi_update_info_t), error); if (self->capsule_fn == NULL) return FALSE; } return TRUE; } const gchar * fu_uefi_update_info_get_guid (FuUefiUpdateInfo *self) { g_return_val_if_fail (FU_IS_UEFI_UPDATE_INFO (self), NULL); return self->guid; } const gchar * fu_uefi_update_info_get_capsule_fn (FuUefiUpdateInfo *self) { g_return_val_if_fail (FU_IS_UEFI_UPDATE_INFO (self), NULL); return self->capsule_fn; } guint32 fu_uefi_update_info_get_version (FuUefiUpdateInfo *self) { g_return_val_if_fail (FU_IS_UEFI_UPDATE_INFO (self), 0); return self->version; } guint32 fu_uefi_update_info_get_capsule_flags (FuUefiUpdateInfo *self) { g_return_val_if_fail (FU_IS_UEFI_UPDATE_INFO (self), 0); return self->capsule_flags; } guint64 fu_uefi_update_info_get_hw_inst (FuUefiUpdateInfo *self) { g_return_val_if_fail (FU_IS_UEFI_UPDATE_INFO (self), 0); return self->hw_inst; } FuUefiUpdateInfoStatus fu_uefi_update_info_get_status (FuUefiUpdateInfo *self) { g_return_val_if_fail (FU_IS_UEFI_UPDATE_INFO (self), 0); return self->status; } static void fu_uefi_update_info_finalize (GObject *object) { FuUefiUpdateInfo *self = FU_UEFI_UPDATE_INFO (object); g_free (self->guid); g_free (self->capsule_fn); G_OBJECT_CLASS (fu_uefi_update_info_parent_class)->finalize (object); } static void fu_uefi_update_info_class_init (FuUefiUpdateInfoClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_uefi_update_info_finalize; } static void fu_uefi_update_info_init (FuUefiUpdateInfo *self) { } FuUefiUpdateInfo * fu_uefi_update_info_new (void) { FuUefiUpdateInfo *self; self = g_object_new (FU_TYPE_UEFI_UPDATE_INFO, NULL); return FU_UEFI_UPDATE_INFO (self); } fwupd-1.2.14/plugins/uefi/fu-uefi-update-info.h000066400000000000000000000022211402665037500212640ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once G_BEGIN_DECLS #define FU_TYPE_UEFI_UPDATE_INFO (fu_uefi_update_info_get_type ()) G_DECLARE_FINAL_TYPE (FuUefiUpdateInfo, fu_uefi_update_info, FU, UEFI_UPDATE_INFO, GObject) typedef enum { FU_UEFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE = 0x00000001, FU_UEFI_UPDATE_INFO_STATUS_ATTEMPTED = 0x00000002, } FuUefiUpdateInfoStatus; const gchar *fu_uefi_update_info_status_to_string (FuUefiUpdateInfoStatus status); FuUefiUpdateInfo *fu_uefi_update_info_new (void); gboolean fu_uefi_update_info_parse (FuUefiUpdateInfo *self, const guint8 *buf, gsize sz, GError **error); guint32 fu_uefi_update_info_get_version (FuUefiUpdateInfo *self); const gchar *fu_uefi_update_info_get_guid (FuUefiUpdateInfo *self); const gchar *fu_uefi_update_info_get_capsule_fn (FuUefiUpdateInfo *self); guint32 fu_uefi_update_info_get_capsule_flags (FuUefiUpdateInfo *self); guint64 fu_uefi_update_info_get_hw_inst (FuUefiUpdateInfo *self); FuUefiUpdateInfoStatus fu_uefi_update_info_get_status (FuUefiUpdateInfo *self); G_END_DECLS fwupd-1.2.14/plugins/uefi/fu-uefi-vars.c000066400000000000000000000166611402665037500200340ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include #include "fu-common.h" #include "fu-uefi-vars.h" #include "fwupd-error.h" static gchar * fu_uefi_vars_get_path (void) { g_autofree gchar *sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); return g_build_filename (sysfsfwdir, "efi", "efivars", NULL); } static gchar * fu_uefi_vars_get_filename (const gchar *guid, const gchar *name) { g_autofree gchar *efivardir = fu_uefi_vars_get_path (); return g_strdup_printf ("%s/%s-%s", efivardir, name, guid); } gboolean fu_uefi_vars_supported (GError **error) { g_autofree gchar *efivardir = fu_uefi_vars_get_path (); if (!g_file_test (efivardir, G_FILE_TEST_IS_DIR)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "kernel efivars support missing: %s", efivardir); return FALSE; } return TRUE; } static gboolean fu_uefi_vars_set_immutable_fd (int fd, gboolean value, gboolean *value_old, GError **error) { guint flags; gboolean is_immutable; int rc; /* get existing status */ rc = ioctl (fd, FS_IOC_GETFLAGS, &flags); if (rc < 0) { /* check for tmpfs */ if (errno == ENOTTY || errno == ENOSYS) { is_immutable = FALSE; } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get flags: %s", strerror (errno)); return FALSE; } } else { is_immutable = (flags & FS_IMMUTABLE_FL) > 0; } /* save the old value */ if (value_old != NULL) *value_old = is_immutable; /* is this already correct */ if (value) { if (is_immutable) return TRUE; flags |= FS_IMMUTABLE_FL; } else { if (!is_immutable) return TRUE; flags &= ~FS_IMMUTABLE_FL; } /* set the new status */ rc = ioctl (fd, FS_IOC_SETFLAGS, &flags); if (rc < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to set flags: %s", strerror (errno)); return FALSE; } return TRUE; } static gboolean fu_uefi_vars_set_immutable (const gchar *fn, gboolean value, gboolean *value_old, GError **error) { gint fd; g_autoptr(GInputStream) istr = NULL; /* open file readonly */ fd = open (fn, O_RDONLY); if (fd < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "failed to open: %s", strerror (errno)); return FALSE; } istr = g_unix_input_stream_new (fd, TRUE); return fu_uefi_vars_set_immutable_fd (fd, value, value_old, error); } gboolean fu_uefi_vars_delete (const gchar *guid, const gchar *name, GError **error) { g_autofree gchar *fn = fu_uefi_vars_get_filename (guid, name); g_autoptr(GFile) file = g_file_new_for_path (fn); if (!g_file_query_exists (file, NULL)) return TRUE; if (!fu_uefi_vars_set_immutable (fn, FALSE, NULL, error)) { g_prefix_error (error, "failed to set %s as mutable: ", fn); return FALSE; } return g_file_delete (file, NULL, error); } gboolean fu_uefi_vars_delete_with_glob (const gchar *guid, const gchar *name_glob, GError **error) { const gchar *fn; g_autofree gchar *nameguid_glob = NULL; g_autofree gchar *efivardir = fu_uefi_vars_get_path (); g_autoptr(GDir) dir = g_dir_open (efivardir, 0, error); if (dir == NULL) return FALSE; nameguid_glob = g_strdup_printf ("%s-%s", name_glob, guid); while ((fn = g_dir_read_name (dir)) != NULL) { if (fnmatch (nameguid_glob, fn, 0) == 0) { g_autofree gchar *keyfn = g_build_filename (efivardir, fn, NULL); g_autoptr(GFile) file = g_file_new_for_path (keyfn); if (!fu_uefi_vars_set_immutable (keyfn, FALSE, NULL, error)) { g_prefix_error (error, "failed to set %s as mutable: ", keyfn); return FALSE; } if (!g_file_delete (file, NULL, error)) return FALSE; } } return TRUE; } gboolean fu_uefi_vars_exists (const gchar *guid, const gchar *name) { g_autofree gchar *fn = fu_uefi_vars_get_filename (guid, name); return g_file_test (fn, G_FILE_TEST_EXISTS); } gboolean fu_uefi_vars_get_data (const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error) { gssize attr_sz; gssize data_sz_tmp; guint32 attr_tmp; guint64 sz; g_autofree gchar *fn = fu_uefi_vars_get_filename (guid, name); g_autoptr(GFile) file = g_file_new_for_path (fn); g_autoptr(GFileInfo) info = NULL; g_autoptr(GInputStream) istr = NULL; /* open file as stream */ istr = G_INPUT_STREAM (g_file_read (file, NULL, error)); if (istr == NULL) return FALSE; info = g_file_input_stream_query_info (G_FILE_INPUT_STREAM (istr), G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, error); if (info == NULL) { g_prefix_error (error, "failed to get stream info: "); return FALSE; } /* get total stream size */ sz = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE); if (sz < 4) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "efivars file too small: %" G_GUINT64_FORMAT, sz); return FALSE; } /* read out the attributes */ attr_sz = g_input_stream_read (istr, &attr_tmp, sizeof(attr_tmp), NULL, error); if (attr_sz == -1) { g_prefix_error (error, "failed to read attr: "); return FALSE; } if (attr != NULL) *attr = attr_tmp; /* read out the data */ data_sz_tmp = sz - sizeof(attr_tmp); if (data_sz != NULL) *data_sz = data_sz_tmp; if (data != NULL) { g_autofree guint8 *data_tmp = g_malloc0 (data_sz_tmp); if (!g_input_stream_read_all (istr, data_tmp, data_sz_tmp, NULL, NULL, error)) { g_prefix_error (error, "failed to read data: "); return FALSE; } *data = g_steal_pointer (&data_tmp); } return TRUE; } gboolean fu_uefi_vars_set_data (const gchar *guid, const gchar *name, const guint8 *data, gsize data_sz, guint32 attr, GError **error) { int fd; gboolean was_immutable; g_autofree gchar *fn = fu_uefi_vars_get_filename (guid, name); g_autofree guint8 *buf = g_malloc0 (sizeof(guint32) + data_sz); g_autoptr(GFile) file = g_file_new_for_path (fn); g_autoptr(GOutputStream) ostr = NULL; /* create empty file so we can clear the immutable bit before writing */ if (!g_file_query_exists (file, NULL)) { g_autoptr(GFileOutputStream) ostr_tmp = NULL; ostr_tmp = g_file_create (file, G_FILE_CREATE_NONE, NULL, error); if (ostr_tmp == NULL) return FALSE; if (!g_output_stream_close (G_OUTPUT_STREAM (ostr_tmp), NULL, error)) return FALSE; } if (!fu_uefi_vars_set_immutable (fn, FALSE, &was_immutable, error)) { g_prefix_error (error, "failed to set %s as mutable: ", fn); return FALSE; } /* open file for writing */ fd = open (fn, O_WRONLY); if (fd < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to open %s: %s", fn, strerror (errno)); return FALSE; } ostr = g_unix_output_stream_new (fd, TRUE); memcpy (buf, &attr, sizeof(attr)); memcpy (buf + sizeof(attr), data, data_sz); if (g_output_stream_write (ostr, buf, sizeof(attr) + data_sz, NULL, error) < 0) return FALSE; /* set as immutable again */ if (was_immutable && !fu_uefi_vars_set_immutable (fn, TRUE, NULL, error)) { g_prefix_error (error, "failed to set %s as immutable: ", fn); return FALSE; } /* success */ return TRUE; } fwupd-1.2.14/plugins/uefi/fu-uefi-vars.h000066400000000000000000000030231402665037500200250ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FU_UEFI_VARS_GUID_EFI_GLOBAL "8be4df61-93ca-11d2-aa0d-00e098032b8c" #define FU_UEFI_VARS_GUID_FWUPDATE "0abba7dc-e516-4167-bbf5-4d9d1c739416" #define FU_UEFI_VARS_GUID_UX_CAPSULE "3b8c8162-188c-46a4-aec9-be43f1d65697" #define FU_UEFI_VARS_ATTR_NON_VOLATILE (1 << 0) #define FU_UEFI_VARS_ATTR_BOOTSERVICE_ACCESS (1 << 1) #define FU_UEFI_VARS_ATTR_RUNTIME_ACCESS (1 << 2) #define FU_UEFI_VARS_ATTR_HARDWARE_ERROR_RECORD (1 << 3) #define FU_UEFI_VARS_ATTR_AUTHENTICATED_WRITE_ACCESS (1 << 4) #define FU_UEFI_VARS_ATTR_TIME_BASED_AUTHENTICATED_WRITE_ACCESS (5 << 0) #define FU_UEFI_VARS_ATTR_APPEND_WRITE (1 << 6) gboolean fu_uefi_vars_supported (GError **error); gboolean fu_uefi_vars_exists (const gchar *guid, const gchar *name); gboolean fu_uefi_vars_get_data (const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error); gboolean fu_uefi_vars_set_data (const gchar *guid, const gchar *name, const guint8 *data, gsize sz, guint32 attr, GError **error); gboolean fu_uefi_vars_delete (const gchar *guid, const gchar *name, GError **error); gboolean fu_uefi_vars_delete_with_glob (const gchar *guid, const gchar *name_glob, GError **error); G_END_DECLS fwupd-1.2.14/plugins/uefi/meson.build000066400000000000000000000045771402665037500175240ustar00rootroot00000000000000subdir('efi') cargs = ['-DG_LOG_DOMAIN="FuPluginUefi"'] efi_os_dir = get_option('efi_os_dir') if efi_os_dir != '' cargs += '-DEFI_OS_DIR="' + efi_os_dir + '"' endif install_data(['uefi.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_uefi', fu_hash, sources : [ 'fu-plugin-uefi.c', 'fu-uefi-bgrt.c', 'fu-ucs2.c', 'fu-uefi-bootmgr.c', 'fu-uefi-common.c', 'fu-uefi-device.c', 'fu-uefi-devpath.c', 'fu-uefi-pcrs.c', 'fu-uefi-update-info.c', 'fu-uefi-vars.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, efivar, efiboot, ], ) executable( 'fwupdate', resources_src, fu_hash, sources : [ 'fu-uefi-tool.c', 'fu-uefi-bgrt.c', 'fu-ucs2.c', 'fu-uefi-bootmgr.c', 'fu-uefi-common.c', 'fu-uefi-device.c', 'fu-uefi-devpath.c', 'fu-uefi-pcrs.c', 'fu-uefi-update-info.c', 'fu-uefi-vars.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ libxmlb, giounix, gusb, gudev, efivar, efiboot, ], link_with : [ libfwupdprivate, ], install : true, install_dir : join_paths(libexecdir, 'fwupd'), c_args : cargs, ) install_data(['uefi.conf'], install_dir: join_paths(sysconfdir, 'fwupd') ) if get_option('tests') testdatadir = join_paths(meson.current_source_dir(), 'tests') cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'uefi-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-uefi-bgrt.c', 'fu-uefi-bootmgr.c', 'fu-uefi-common.c', 'fu-uefi-device.c', 'fu-uefi-devpath.c', 'fu-uefi-pcrs.c', 'fu-uefi-update-info.c', 'fu-uefi-vars.c', 'fu-ucs2.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], dependencies : [ plugin_deps, efivar, efiboot, ], link_with : [ libfwupdprivate, ], c_args : cargs ) test('uefi-self-test', e) endif fwupd-1.2.14/plugins/uefi/tests/000077500000000000000000000000001402665037500165075ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/.gitignore000066400000000000000000000001421402665037500204740ustar00rootroot00000000000000EFI efi/efivars/fwupd-c34cb672-a81e-5d32-9d89-cbcabe8ec37b-0-0abba7dc-e516-4167-bbf5-4d9d1c739416 fwupd-1.2.14/plugins/uefi/tests/acpi/000077500000000000000000000000001402665037500174235ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/acpi/bgrt/000077500000000000000000000000001402665037500203615ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/acpi/bgrt/image000077700000000000000000000000001402665037500234662../../test.bmpustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/acpi/bgrt/status000066400000000000000000000000021402665037500216170ustar00rootroot000000000000001 fwupd-1.2.14/plugins/uefi/tests/acpi/bgrt/type000066400000000000000000000000021402665037500212550ustar00rootroot000000000000000 fwupd-1.2.14/plugins/uefi/tests/acpi/bgrt/version000066400000000000000000000000021402665037500217610ustar00rootroot000000000000001 fwupd-1.2.14/plugins/uefi/tests/acpi/bgrt/xoffset000066400000000000000000000000041402665037500217540ustar00rootroot00000000000000123 fwupd-1.2.14/plugins/uefi/tests/acpi/bgrt/yoffset000066400000000000000000000000041402665037500217550ustar00rootroot00000000000000456 fwupd-1.2.14/plugins/uefi/tests/efi-framebuffer/000077500000000000000000000000001402665037500215345ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi-framebuffer/efi-framebuffer.0/000077500000000000000000000000001402665037500247175ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi-framebuffer/efi-framebuffer.0/height000066400000000000000000000000041402665037500261040ustar00rootroot00000000000000789 fwupd-1.2.14/plugins/uefi/tests/efi-framebuffer/efi-framebuffer.0/width000066400000000000000000000000041402665037500257530ustar00rootroot00000000000000456 fwupd-1.2.14/plugins/uefi/tests/efi/000077500000000000000000000000001402665037500172525ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/efivars/000077500000000000000000000000001402665037500207115ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/efivars/BootNext-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000000001402665037500272570ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000000011402665037500275700ustar00rootroot000000000000001fwupd-ddc0ee61-e7f0-4e7d-acc5-c070a398838e-0-0abba7dc-e516-4167-bbf5-4d9d1c739416000066400000000000000000000003461402665037500340460ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/efivars {iMi eY*@ZZ~I ʍ5Mm\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.capfwupd-1.2.14/plugins/uefi/tests/efi/esrt/000077500000000000000000000000001402665037500202275ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/000077500000000000000000000000001402665037500217005ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry0/000077500000000000000000000000001402665037500231215ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry0/capsule_flags000066400000000000000000000000051402665037500256470ustar00rootroot000000000000000xfe fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry0/fw_class000066400000000000000000000000451402665037500246440ustar00rootroot00000000000000ddc0ee61-e7f0-4e7d-acc5-c070a398838e fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry0/fw_type000066400000000000000000000000021402665037500245110ustar00rootroot000000000000001 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry0/fw_version000066400000000000000000000000061402665037500252210ustar00rootroot0000000000000065586 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry0/last_attempt_status000066400000000000000000000000021402665037500271400ustar00rootroot000000000000001 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry0/last_attempt_version000066400000000000000000000000111402665037500273020ustar00rootroot0000000000000018472960 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry0/lowest_supported_fw_version000066400000000000000000000000061402665037500307230ustar00rootroot0000000000000065582 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry1/000077500000000000000000000000001402665037500231225ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry1/capsule_flags000066400000000000000000000000071402665037500256520ustar00rootroot000000000000000x8010 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry1/fw_class000066400000000000000000000000451402665037500246450ustar00rootroot00000000000000671d19d0-d43c-4852-98d9-1ce16f9967e4 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry1/fw_type000066400000000000000000000000021402665037500245120ustar00rootroot000000000000002 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry1/fw_version000066400000000000000000000000131402665037500252200ustar00rootroot000000000000003090287969 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry1/last_attempt_status000066400000000000000000000000021402665037500271410ustar00rootroot000000000000000 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry1/last_attempt_version000066400000000000000000000000021402665037500273030ustar00rootroot000000000000000 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry1/lowest_supported_fw_version000066400000000000000000000000021402665037500307200ustar00rootroot000000000000001 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry2/000077500000000000000000000000001402665037500231235ustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry2/capsule_flags000077700000000000000000000000001402665037500320412../entry1/capsule_flagsustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry2/fw_class000066400000000000000000000000451402665037500246460ustar00rootroot0000000000000000000000-0000-0000-0000-000000000000 fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry2/fw_type000077700000000000000000000000001402665037500275532../entry1/fw_typeustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry2/fw_version000077700000000000000000000000001402665037500307632../entry1/fw_versionustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry2/last_attempt_status000077700000000000000000000000001402665037500346312../entry1/last_attempt_statusustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry2/last_attempt_version000077700000000000000000000000001402665037500351352../entry1/last_attempt_versionustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/esrt/entries/entry2/lowest_supported_fw_version000077700000000000000000000000001402665037500401672../entry1/lowest_supported_fw_versionustar00rootroot00000000000000fwupd-1.2.14/plugins/uefi/tests/efi/fw_platform_size000066400000000000000000000000031402665037500225400ustar00rootroot0000000000000064 fwupd-1.2.14/plugins/uefi/tests/test.bmp000066400000000000000000000046721402665037500201770ustar00rootroot00000000000000BM zl6@  BGRs  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ĞwSIGIFCGEHVtȸmODCGGFFEFVnûȱ|E  #;pØg:  "I|ǹo* )BGEBCDE<-TƋM3ACEEDFH9 0uɾv, >wZ0 Bʇ5 5fh1 #ȓ0BͿ5 ?ş. Eų77M 1þ™+q̶R>ǿƹ%^nűő1Ǘ#žíiL IîD/|H {ʶoiƒ)>Ő+i! xʤ4 |qḻU|4İb5#ǵX:ʃkͶSȗ;Y4ǘ(eτh˳Rk.ƛ4 ĀiȰUDZUoà 5ɷd0ʊlɲX Ț<\ĝ 8ά[i)l=s7O36O2$%j/۶HSɦI~ԧ\ki}tso׻b9]쮾g7o]'6o7 _}M~tv1i態/!C6}v||qx|9|yܜ]]mNwnsջͫ?^\Wf1˜)n䟓?n>_뛋ݻ団v/nvnް/.CWhWjw^_\,o?]^ޒocmy mMr˵XyC3˫ݼ9~ w ՇO>]^Fqk^&)إfe?ݏGIj1]l)OPLI5e)b|R`K ԟ7 fŵ[yˣyKjʒ)|e5)h3Y|/yX[|%ڵ[̜eXS[*5g NVBܙ@LmإwrCYt"vpmN\q9lreYЧzWp9?_]bBL`PDd",ʦٜ|_)856 Ո!(%X'RѕPC y5ɄxۧuPNAyoF ~F}`4"E%JoJVOOF閣#%(>hՠA%d"T,ysqoLX3Gu D. 2ycna"܏%\k6[]z|iJ#[1)cA?u][j{#][d,i6bN?UזF&*9B(S/?3Te-R-\,ǔϷ@b~7ϋw JUU$? wh k$~[q 7}|٨A"h'>^v;SĂ=Y_> TbA2f184PHɲb$#fWѻw{c QG<,戮ÎpAI:uiZAviS8YҚ]"s,^M7Fmd/Bv_EP7${ !WNtsqǿW7W.w8娝:uLdaTdC6:V6}g &uK,mUJσ'(x|* JJrkMG0].rBTl<N,vK5ViPRJtMxpx*YZnҀ얚 =v^߃* HR0`ڂr54v^߃* 5v uB!PedMPUnطz=Ҁ`rAUrTik YrSi"t:JOwujSF%%C;A:uZn* !iaTi@ɾ)'jk\@R`|{Pg]CX:]؆:ϼUxg$T[l\Ҡd?Թ+ug^Tx5ԉ#r * <4Hkg^Txvu+զ\THJ'y}OPicP'X-4qcxB6Ҡ$ۼ'4L)uS:>|j#sCZ8y}OTi,'6w“ڲruTi )a^UgCr!lTǽ*y}OTi@$g8QAI =0* u"\ȚYGAc#QD$x(ΡNvW4()'4L׹k Yg} 4Q}[=ư)4Hry6!7Vi)їÓTD~t ur%ky64V)7I* L;p(W'%%aj$/Ax((rK* hSCܼ'4V&(W';>qҀ6!qHxJڄ\{J8}CkPQaP2~$vt XTPA`u˼.'HL3‘b-" yˏ]:p:" yNj_²:~n67*2t7f%uO67x/dS ʳQ5K.oqښ;bNMytO}oǐf2C:oÁrt%ÍkZ<}?3/y/wOkϏpɧdJZn`+WAu/kgu^.@k4gOyL?sRTrc|N2֨LЮoPSZGh5wFX" GʬԀډ $#6F}Ϡ`Z.y 5E 8VڔH^g$3׈}Or+M X pfc_ÉRmJd$2s?&(DhcmRO8}cW[W vx"(͋,grsJ'pb* :LtS,4<֖ľT LO߸Tupb* x Y?dB,3(ol\ * $#i{- [yxzM:ńgܿsFS< c6S _T)8pꓞ VJ9o 2rDzGٗڅ'嵚8Ww+I9VXl\NFbWyQOz2W9]<)wU.6T' `FN|QH% -<;?9 G`@w$UY{k\$>ڙv+Y! o<ŃbP<;:U0F}'7NJMRWx;xȓ᝟ѻp-QM)D fD"(iM;?~(ۃ,Nh+: ynyFTFP< 0=7yO`m":Ǽ])(xZr]{E ]Փ~D^+q|ky=DB `}z^^o{yՖ٣+<"$cV&S@|&{j4.x~^Qf>f׎aAOWԆ8M7''WUShD@U/ɫ6|yBw<:e$y]t@m}0L!\%>rrth$(o[h4z=G2R(L~`k~ӽy! T<)<ӠXĆ9؁)W &`2R KK??=xXrxĆGqsJ0Y|uB5H<壯I$8GV/Cx8z%'~)3-q$к>/WΝ Y6<u: B8Gh-1\4ԢQ~.Ix$Gˏ>A@o7rxp~To1r[kl/V%;tl? >ʒgx 7>A[ÎVCPsj=GJ|+@f`% d+y{\\?-NRE}7 i|5 s.4w2uYO3~g'禊/Ԟgw4*cgx$xLZs/S|0|)?f-*^$_Jj9RG;Gˏ>A@} ꃫ!(xyp.Y՛>x/`Dq#F(·*ԣ>g[9>CȏhG&Noiݛ"gsjċU\>*gA)s?k!hxϣ`DRM>|kQT@^=z?=_޷}ų!{[_Uo9>A.!hx#okG򃏹v_)xׯxw+u+G?T  L >OC'ʏVO3`^=ZC|Wޞ)'xfVDA|_f!<,E~ p[h??>"?W,--jxyiJ)-? xaGnFg51Z8P}?| {HyZHgL5 4>AXc- _,N{  0n޾Ghd!?G@3YU'Cu}eGӔ3<<9xᑀGz$xmHѼ,w.3p^+[SGydx_ﮅQG## Vo=ex+^:Ɨ!)xtpw=17GApݵ<,vه ~ 8 l%ĜԶ'5Q"oGT|N ޞ[? zCjsH=s?Cn o#ԨO{=۸8hK;e-E}7~A4<$ݎ!G:D[Ń~/rl+gZ|N G79BBF껙rD~ϩpV q g7}h7u<?k.9"?<q~}ܻa<8?|N ~ƵL'+A6S}Sv*gx~Aܨ gȭ»-R?v~nZS?ɭaqWC:Ghfm1P~$8?OSŝo&Cu)gs~4<܌0on WW~8Oe>~wUp9Bs69] A|C]gȭ`"VoqU<7>'  L>"^W19"Vo=x>hhsU3w0~=,bmFf!D>Dr>)'x x3k!hxϹ!GM>|E0!Yz|q-_qw:sgƣe'7_>]l#1W W׋F6-;^\[>~wQo/g,d}w5 9ICbg(gx]Nwwsg~>ϢLWC_~Ww]z?)x93\2<ϋ(ݵ<Z2Q؟4 HaOU@:L7dI|vLm*sg>/LKVCSKg(͟.Q7@hǨP4D|noW̆Io E{AGde|}GD-~^؟8Ga[kFԎz=sOVQpQБ80] A#2C~4>O {GK]Aj"oq͍W/yuW j!hx4>OԒ1tu{ x#>~/T$^D [8ic5%#Sg?\~g ƼU␂9k! Q84?_<.^wpax@kxq'Yu?D ^8<ϰ|?g(g?\~0x㊗9Ѳ}S\pE}p2!hxI#  KJ{̓]_I="Jw}f}S?%g? B4<$n~~%g;GyV]O?U%g~ Vo{] ApA<8?D c|@*[0Toa$?.Q?+g+3!L>QPD L?|^bhKlG!?10lY?\~UoG?xtj֋3`_wZ/W,'bL<Ȁ?G!hxϩ%ٟphq"6s X^qOLBw sj $w]N\C<$$vB{am NJy! A4qz5 ;+ Bd gokzï?d?v.;,8D7%k1hޝH!q?DɒNMsIh"Y|RzRB`h:Cv>roU<$(I#|d-eoz%QV\iC< j8HPuzZOkVa%}"i%8dNVc {$Ɋ+H(K=TaZ kKV/NoH@akeIBy¬| =qH$K2Nm)NCRc//H>qC)$YzqƠAz*^-dI'$ć5eɶԷH=!Hw8/YA7CG$˾$ *vm+4H@r9oBnvpqƠ@_LW; BR CR<$޶YH^;{p EδJVWr,U獑,1{{p@8ǵ$p*ʎ 88?Ʒ!YA6s:#YҎ;ejB$zIZGޟ#Czאx4H@%@ؗ+YҼKxZ\]W`8ԚY}@ ǡJaw2⩐$+Xu8,{iC@itq~5 F֧ c``dI$%n:K‰"sUq8dIw7Z $qH$K{&짹?zɜfR`:Wd ]Msppq^As'3D^ 8jBȳLƪ5쏷z>Ô_zUU\!tU 6h񜌃Vc q@w/Yɇ/Y$wߑTq@=4z/As2a/4HHCy C 9NsIpom~pٸJnye>9)Λq k5 ;a"ɇ/$eشݙofI@y"x- лv$K7hOr ;oL"]Fg']qArn &t2i~ L w2A@ ,iXTv/"&}on$yS 9'88LWc s'PqDй}@2zR16vJ˴Y oB%wHb s'X%8Ift֦=zYUZRaQP$w Aylq5 {9tvXQ#PTm'in|u=7$SH2N㐷y.YANg!ɒ%Yz' @2ְ?dGeW^YKl(K!KyIx8d3ǵ Q8ڭnt$tbtBFw^ŠAzN=+HW^D yzҿtM{7@Nz,7/g:<Ա ;Ɗ[,z MW? -;5 UH6~_V^D~x:xU%n; > :YH<ɇ/)n^,}Yp{1%nz=k\$wjzqh~Ocj $wj(Οx$QfoMUkWg߉b<(3]b ǷviMu!Uqޢu2,>";KbP E]6<9o¢&u!h1U/PrS񴼿|u`|;CK)ZNơA24 轈F;d !SWiHDk72E/s/u%sP;D:`:i4H@%9i%U]!O'.^r,|+#5OW\+$6mk%w8%k1h} qH$KKPK(KZ%x&U>;$:/UVo [}QVGO| Cq4H 9t'Y^jXDZC2z|4U>iѸ?MuA̿pk7f35䛺⪆u$MEmu$Ar%-ѫi]<+aƠAFD!,!k}kCS2}Wy\E#+lѓ4}CT׺6ӝ1h@R8!H@, :_`k3(a5x67#>x =wՕIj> $8Yd5 ;w2 UXXdл ;4<6q|cjD7X~ˠw*[Xd;C *k1h($%"ȠwZ7VHƍS ? ]:I 'U/)wsHZ;C1H Fkmzw%fzXhŸm--^@pUYzq( 4H@tB:@5IIdtŕP7ʿۿ>_C[l[U@z' \VNPZ%xih5 лk//H@O_.:\.ZDtJ~y3;jީ9-;y$*ŠAzюL\aQ@wz5{&S+cowj(7;ôj !!v!z Kj Yn>nH}uQՓ A8wj(WmڶC 0]_AED )K4f/V񾗒#v=mgowЯ!);tq~5 ;w2UdIA#eI*5 o!S+KY_Ya}]d !C ;!,q(Kfݴs;ʹ'ڸG!U{ HSyFnCy+1hޓ̡8m{HF!r3O6ި`H =7}lrfo@<Ӷ61({9t`\{,=j Rw-=6YzhOd5H@p I;C4H@t#A*;|d.Wk,{Aтe4K@pU vYzqr5 ;HPNnfpYz/%ezak=ᨭ`@g {i)$Yzq.ίƠAz/2@"738,=K lmU\vk2M4oͷi4z78Yz/Iw:dC4z7JsgswnW~V@y kYb8P1{:KbP aHdI[Hh [鼑)Z?4z7_ε,qXd- F:dIFPveZC~(BjrۭI$Ar%YƁӐŠA̡#x, MoЪFTzyg`۬GAi4z7T}dIq68_Aѻvɒ,in,e,5{~־)y3km+awy I@4zqj $w+s$A$ K@6 -;5dcڻUX}>xށޝb9$-Kaf5zwĊ ×0/)Mkx;T#^Q&0_=L ;w\B8YUq5 лÐD+xē_bf=K&M2SI~\ށ]9$zqhö61h޹abH i}04(lyK^ȫnӡ]]Wyw64H@>@ fHZ/wwR:}i_}X,$w,IHt;C=ѻ ݗ#$W  N_HF+j *W;KgZijii`%l7!}Oރy$Ơ@A5Zq9x{ا 3{hiW-OX~/4W%ˆV==HrKtz/Lb s'$ $ ʒ6Ҽ!K}fVFt*G[%S.;t%x5 ;w2d z <=$KJhoYzq0n~ZA^,dݻv߬|ӗq.yՂ,1 >Ǥ!4i(K"أ#y`kV4H@%r #Yb@ 2J+Ώ^wkwp!]/y'kb?ʎѻݶY#y1(Fv3/& |@2UaM@mڍ_{tWcwV>$cqGH"-R2|=٭i ̷Otȫg߁, Tt!| 8aKVc inK7w8YPUax`4} ċaxZ4zܘ. ;C7zqj $ aH£#|@2|kwp<4H IUr>fpcqhy~CI$"L\ $ }({hozS,ǿU Iwj0Wzo>{H6 AeGz%v6o'2VM$wj0Wy .|8|d- ;7 \ޑ@ zoUkk^dlj \b}w@ܘ= K@< 49 9W Yz/e5[)Q7/THЛ@ܘquG$:ǡ̶CƠAzwee]Zg]>B2%UkxП=;m*C΍Wyw;}!j $wo!_e\:7ܪրr\zAq!YnWWõCM/ `Ae5Y9,IRPIsvV'}HRoR{z*\*$fpZ`<ŠAzf) 4H/B2ntZ`~ܮ<'t?ZgCidd NWc h,Y%w qv7:ļؤ{k0!KDtH@q)u<v0j $hz dIAީ/ĹW=OnZ t9PN;kZgcpqhz-?1hcF{ErY/×8? h Iڟ'arS=S ksIKt8vHZg6KF<%dTº?`qI_o 琴#\dlq~5 {<C$tf=Ukܿ< ;7f\]}u2aKVc rKd =SJlӾW+įh{arMe$wn̸:6d as~5 {$F K@9 -;ޫPktc;͋>a\,7f\UA޳_d- {.mD K@G6Uk[~=9)x"~1铧 pS1I͒:j ! pCb%KCB&6Wc ikơ|hnuBa{N tDi$@F qHF2e>Kb I$ 9H% $[{ y%94)e8~ +ΑKge%MڬƠAQKN#K;%!O> \Cث!\xV YAuliͰ#: ݫp>ح~U!+4zwd1IKtq̪1h4zwaA ;B3psKg=m92Wut88;{ k5 ;tء|Î%w%$nx%Zm12 ;U%8d ݦ$Z $w5ګK ;B3p/8?ڦ}oZ[%!Q =K@dq IDޝ Z $wjpCzIÎ.\ 5U!uLU%8:HZ稥 f%!\VuWR+x$wjpWC8,@ X%X!BkpsVQBUON K@<aV{_ANmHdd !l%Qc>ۋBZQZ]3*>1hީ 8vxv%wj<eOȶݐ^dAzM<Yzqh _. =:% N>MOTo)ǣ6{<*,kvkHZ;DkCbP k"X!Bkp/$5c?9.`yYcU'ѷZvkvwYou2<|g5 ;w2 ]"^:G6>OO\x\N[uV lmu2.YADg,MTyoݹcv >Ss%:ݿfkZHBwW!K@<e~ZA^;  N6aL޷z'Nh@]jIBwW K@4~ ;Hd l2h`³M;mG=}T.kM $8s{ Z $=w2 iGq%k'w gvF?39Z$wSHѻtj $wdD!"z Z"|lzV׋D 5}d a4H@N.w%wGY[ngƵ8HZ [N!WcP kw2dI,i un y:KDku3u1,kCZA8$%" f;g3ĂDү'~Z}7O% Yzq(Z $w/s$I$!K@tD-?0N:(խ;\|hi$d ǡO\k1hރ̡CHdd .ˈ4$5|W|OR6:]dd !lzj $w$b,#fd .ˈfr_g3/ q/(se%]ZB Z $ptIrYFley,ޟlf?gfh_]lVSZZ Y ne8kVc GCGD.ˈ:OMUGϗ<ZC3A9uh%~CN, @,8i4H@I!H8K҆,SBDOspG/Isf#]NSq_P/ptBjN09  IH%wjB$zWY'P/3In_?:TZ邅pU N}- {9t,SByVi5O홅ް=:ހhcuMky`!\]Bkd-,s'#YnpԄ4pe³O嗀-FGZ邅pU NM!N!ob BjNFZ &89ZÞ칵Bޫm W8Z $"sHXXΓp[%xƸgyUoX7 ;[E"W8yz_A{d"HZHi2K^Uk聼z q.pP4 w8LiƠARIDu' *5~o%rWިlj^U%8A{k G'HZHsCmIuw@R'W;š0⭩8l$HRN!:su|Fy{Wc S $Y$#K@$\0YzU>ojt̥gwr:/+} µNơUWcP k]2dI;ZH$[exPa;$w:/+} µNa4H@QБ,)%%"@BY*u5CϽ2AAi׺@'}IAy+1hީ`Β!K@$\QoW8l|q\kӎp t?^U,8LƠAz8Ԧ7' K@$\Vσg_Uk<\W"\d{Wc S@*g#Yb ipf خ?Vp %V},8,@ :%V"K@$\gx ;>խ"\f AiҴj $&s$%Y=ptpxxYˬ4H UQ8@v%} µ!M>$2@%K5u*Bj s&) Xt8!?=T"\"]0 $8LƠA=R($%Y=p HF;TUk^zyC 蝴tU Y]aںy5 ;iiE{Yz'=I])R6-4 EEU%8d a4H@BPɢv @kIf`%"8Yp4}4HZkHZ@kCv5EHCmY׺Hu-; yr8tpt @*gN!sZ $wHC,v @k]$^ZѶFto_\׺HJ0Z'0mر POpkrB=Dy;%BF4[׺H @,8Jp璵4H@?0 dIFIγ5-Q - ]gw5+ W$wM՟}u2yb S@59#K@$\ݻh /i.o$nՍϰ9Yeu_׺HJ0Z'0 \b9tɒv @k]$:Jݦџw%}F)Azk!A\dZ% r $(s$r @nuk@2U4[+L\*Q`knj9$wO`!ð{4H@?0 gIِ%w67q)+|LsTfGz/=pd*pqfKc 'C )H%wK+{7[+Hj"H||xY^8 ;Yǧ+w8>4H@Yl%Yz'cL"c1)Fp!.eS/<ԭ"\"Y_C8YÎHZ 狑,iu2Y 6[$zqηyg|V ;YJpA\xϒ4H@?,1%C?µ.p]K&$;-V/oi~~YN}u2n>Kb CG %Yz'LiӻռPKI˾獏;6 .=Crv}u21h4zO9@r>qY,ic TDep_M6i?|LΦ oe HB7O'.Naְc9 H"0,qȒHx`HľޕnԫDr#% 2&nG%U14K>:6HZ̡CH 4HH.%j8@F A\df1h8@!^HXKxxK=ABX$ znh@Rj8o:4yr $ޓ8,Q )ABM%׉kM[Ph0~FGϦ6?V\pK~+ 8̶i/ǠAzc$K% K@ԄP&V!]k-L'Yz'|զ]0ۦ f:ob eDɒXOě$Ybրms*t07u4?*pK~pq(1h} xJ%C?µdkMָLsEI{-0+Lc! |YOHt;C1&/ǠAz'<9_RH2ihqTA qу_2)=bo ;ϗD84H@[%@%@Yb'W\j _$}by8= \$,kC&m|ZAuX%8K4׺)KdTzIabO:93}׏6m%:_.e \d+1h"Y<#\R,A%xn^Vbl(ǴtIy$w:YP);]ƠAz劫Hd =mHW\5X]m}}OIXBMCpKtWAydr $$s5$v(KAeIzYZexf}%AzW! K@<4H@? IH% MRtexI uNJB͒:ٞBZf$K~k]ʔ%ԧ!k?Ը!e$%:_.*{zq =zM{ %C?.ʒIÎ=[3jnh_i׺D'Ž{;ìr $!%Yz/%yzuo;߾"{r#Q׺D'Y<"K@<m1hiHXdIQIĴ?Cz-?Rg>l~4ztŴsH%wYw4HMHdC@lklDtKǽ uN3$'=%pqu^Auy9tɒ桟ZFsgi6k;{jy?@= ĵ,k'4Hgr$g5.yɒ桟ZM$yT^?W5?@y G4zq4$k1h@R8E,LR2~e`=Fa7La(3^Uhܞ}9O}jسsHeƤoU_#eڗ=s דܖA$k I*rhs*^R2ZgcC@2ϒehl=9EF`Xk`}IEfyld5 /`Pf8dtJ-vՍyZrpU},m⛧* Wr-3'#Ъvˣ`E13;[zw\11MR⦊VQw\1\'`dF#pYG40s ʌ2G>WՀ7} '},qŚ}FAf${0Z`n[7rQx/qUo䘺D֞>&lɿǸbcUϴ 8\aw5 pM|cCzm>1ZrUxJL8|r^(6jW81~]\q x[ BY Anqɼ97+P;MDU~R/v&.5JADNhWU Bc- 0s4(PG-* гfY=Y:>1W0+t\ydW>q2y~Q_x7n^ A|e WH\vfU~>4zG>D=?1V 0A4'ٵ$GG\Y_=l~9A!|N!{8?mr:կpԾݣўۭ҇z;}p$szTj!Z %4*:]VE4c\_*lr@ҫvA"p\G')U<˾exuk0Ekxi|;b|arrMÃ8ίϝۻ%"jxȏ`0mIGⵋhG6#*ȋc gT7zRsAAX A#5<(➄hDkZa4<^Α>.1@',9>-w#7 Mt:]ۭE~yljs:?C~ϩBxp{ҳzY\TUG; + ÀywWCP0sw#]8?eqـϩxꃡ{tܣ#٥p#,;+L5g>Ck]>A4Usxx7k!hxcs~xlnh4g޻gUrs:.#?<4aYuC7s\2v0 Lb>/2usR}'*حr'u|Y~ϋJY~$Wk!hxϋL#o >'z~vx"^_Ț(ڵK|`m^|-<s!hx4>70GF~4>7>V/ .|5G;ۡj>5>7nϳk|΃|3#@>7񱬟M>|E0[k֗[: }'9*#x w">Gs ?2 4xJY[%()xwUoo{?OGsC-cx6p-ȏ<(?`0hYx ֶN҂\p7}ᑀG ? x7] A#!!ȏg1^ {^|5g$]ţ񹡓6GAo_ A|N-~6qݐsRl=/n>\xeW?Cs: G>A0G3|NshQ_0Co*\_]>.áywWCS :w?{9ct?x8\16KgxIxs>A4Ax>2u"?>nQ?xWL|N'E}סyWCS >'xQ4|{><i?VCP Sbls#9C.j_5}e z?us: /w:!>2uќsd*V[ҽs: :aZ18b |N:h#"[/c->O"GGB4c%ƣɷ?+Jߦ>'ys4|Nl}w5 yu: t>81+?³W~Ivs0}jG`?n>'?1x *B-_ۺt/2G9ᢿE!LwWCӨtD9D7<xYÐ=s ǿ:??GyiJ|s7_/Y A|^b+<  Szf}[U>Aq5n2u^wv [2!:e[ϒPq*g {rj|.0}jx9?iH#OW8/ܨ2dj2cϭiJY~dGA_jϭsΏh|nɊ>z{,q=_##?xGs8;_ᑀLxΏHwGa얨½±jKGsJU ?=t}w5 A7GsKn1O?R?byo&1XOLU<KRiU/~G >Aȵ^js'b gSq~SxUŃegY|N:x{d`r`?n >'?ꉣ%b?{6R}3/ޟg9ᢾ4qc- utd9tMWxG7,$3@\ To tq,x?B/Y Ap@7,VOӝ~\Д3<8i޾F껞<gx*kxI]? A_w7k!hx }9uTŃZ[S\|>:ٜRy|΃0߾<(QM>|EJ\ŃۃzmvL`{/>g%  L>/CȏdMÃ\Ҭ_x(eGt*Ssi:+y^ΖTRp LWCP?-C}GA4{8쵿;BYطK~lZ'\綦TGn8A _ A#ܦK<8?2#=?2w\Ńӟ; kPT~Z?c%^!Mܑ ?;:MgUZg>QpJ 9 i?<笃z'6ʏк +r'vx+ON'Sמ? +\ox ?Gl[~gZs8U g%:a޾Q<,E~⑇rm=mQ/s)gxؖAﮆ8SK<8?lF4c=Wԓ߯'kO'قy+`[~Nԣ%!hxσL8?C4 rEqzz`$hy1m? +Wx8w4%̿wzc)st<]wCh82۟z{4}?.1Z-U<[mz=q1Wx`퓣*,l^#܆_ppΑ.:'0헱GsO-y[8? 'h?uiG2[_'>> (#Ve tNa4< sΏ(e,U'@* !?s/<p2a?BP?vQ~D>RjQDRNHO.8bL,Pn~88[o_ A|NqF#sy+N6ҽWxAQ9Na4]3,ꉝ<Naڟz5y/SI?zBkor{\ŃvΓ~/9dWC:`_ A|Nq񧎬v1޽U`;4[vpʍ'%pΑRyҟ82OZH}7:#"G ԃnU |N'E}7p<Wk!hxϩ w|)?t~xxdoދP.۞?x?B|< |墾82OZ6O?6?8(?~.@ wt+ Єϝt\w#:'Zl]Ljgʏ oU/G}Gi/`pgAꛂ<闫.:'Z Ewd? +[.J <| v#@? B#[ A#! GvGD4#xoU<zu\%uTƺ^&˟pN—nF'{Vo_ Aãy+P}7__,ȏP~IVŃG,.IK< t}5 <Ľp~4=+6O}Nzq|tRi&Zګz`L*O)N=C`z@R8)dځzV V}_*wUQ@ɕ'޴qkA֭ovI,cz3 ȝedzqz4&kA醀2R~O<Lub2ZcFP1&]b a TL@4nĄ<{>9pɬ&0 08PzOV0m\+YΓ$@ߑa|^9h/SǬ_7KbQ5QTx<[\ B= #e65w4LY{"iexx:O`,rbup`2-E|7tׇ0pU=O=b8>A `}F7њZ O:'0튲Ihe$o'ys=<~C?TSvk2\}`<mIIxi TL6 c'xUz?!F:gULT'x&WP1;G0alzIȺ[ uyǽb$'x0ǯbw2`ybzl]Qu*K~ Eo }'y:Zj&ގb¶ٺOSiҶ=_ 5+Ox {YGX%3LzOd<&kẠ#p&hmQI%V зULN1yqv'_ Bdy'-۶yGgH 8>LzOdo<\ BOMp'xҲm *=yTLT'x2'kA㩩ԅ3gOZ ?@}ApU G:tt1*a{ L'0]_ Bt1<: =`w=p"(o|%Nt( o~aNyw:sZ*&$&l矃4LHvf]c:ĽSrɪ_b<'u<=sZ*&xj*Ą𨋤eq>遪Bp̒qa'=\ԅsR`]TVP1gGɬç.dmYcJg0$_50ǓIT`mfxZ BesUIwnW稤z0Y]$=%ϳpqp5 4L`WIÄxEݬdz]fw*$!RSN'Wyi.ډ;8ߵ ,IϯhϬz<ƒg_F7_>%Pڧb8>Q}l{ b'\/IDMvV_l0!-}Jx4o騾 S';j*&xj*#uyƤ 8LJkgtnR9LMD (x*ڋp=pqXd-p<5ؑp8OLp  {G1w{/\,rv<n{5p&1&LѾgӊ+ZK an訾`OLzi_ b0&' 8w0Gb]$a4#kbNi 8ۋOঽ=VP1LJA `8O\p<5&<Qc*&x:.zyqpӮAָO ~V(6?/.? 7>w_bҮ6օWP01KQL<牏=`By'9U2la{U9.Q}wQ/zq 8>mØpiPcӘ Ͼdqֵ_|gy\OGE]'xio TLIZ} ' 8ɨJ8}>o7>w)*yb0v TLYс< ' 8JI7WtˁOՅ]Ez[UL`{yoV\ B(&$<ScOӘL""sflf>oQmo |R.U< <ϝCOV0]*1 I2= 5&Q?WP>R821t쯡^[1KWy,O>w2~+Ạ$Γ{<ՅG{^UzHQZOӝb_9%?W_T&=O<yr5yۆ1<=OgjLr.s<ǿ'y6w'eo\ B$A `W$xdOE!<^#ז%o^`Ry'IqXd-w<OւP1i`:@!1;|=q|~!]`,O4;. {=SHj**`y+35*tmsEWPam׳O=`Uؑ ~WVP>ITLPLҸ (i&1'zI^E T_F{neu.J/?HC|8{b5 P~ 瞷Ȱ%ng:y-%k{LkkLLgaA E[Bx H ,GX-o_KzP[XƽgAYsCaAO"sZ*,|8#EcrbX`o`IeQ&h{%ewBX:ܡ_b`$( @<ӵ(TX@tɖгOjw% VUg/=Up: 'r,[zd(J==@^BO}~Z/سOwlQ3w'e;֗YgLGZ*, #]fc:#[&*UM?jW \xAo/C1]_B4KXz»lyS?롶.+QpM[Ǔ.FBgHيd   }˻#Rp;{=3 y4XP, ,MװH,$Hm',V}/gR!sʿx;2L aq(fZPai_Ll=[ ƹݪR^uJőE=ZIg ( f(_bd9 P>-| f2wXo, ;Ⱦ*,|ׄsXzy(f}Pa;PG`1-g (n",1߯ówYk-&‚ d(,e& uTid .#00++s+1;H'M_]&o߈= oBYM6<VA΃ (\PaXG`W>O1"rvy+_w+^[W|J-lP,G'#0NzKPX[DOË-=jP>\S/Py_BdBI-| mu<fZ aE/#/4׳OCg—Pa4dʧ ̽-*xvJ} gVګ Wd x(b f߰EB!Qlyda#H~|RI'?5X@d*P]r*,(Hx\O``W!ٹݚR=[}Ϩ/q\?yJXrg ("ζ/GO>lnpoIRA%q8c X _DP~jBY =A<r*,|3CdzD~z%V sCsý9Jn&fK)@a| ( Pل°tOx,Rw/?}ݒWp1#շ+Vp .[00ӓm _BfzL PDɖn+;Yj5c*<^9_Chn',|n+ ܷ`'C*ȣQ`ɖ,GBNe[DxQKw ( r硘YB_" -$ɖnS= }b޷~Dyj̽]+2#SaA,=A<9L1(TX>[[~\f [$nG۷G;_hqҎ |%xyoz( tj׆bLd9 XG1 $ӳʖ47U8pDZT-z뵡ȳ+(Xl۟X9K)Vw=ʖIoZܚ;E5X)!^a`^Y(TX` mBH,V=ʖVASN o+~yjdGV-DJ1|[ QJկO gKǡ(+(TXB<Fal[ϖ`yġ lS_`-웪TdKS=7M%z (Un=[@4+Ew2"bz1[]eȈ|H}l*>cGր.%XʧH e- waKKO} ;")\ 4)k@d*v րi(ty5 P>yġ¾ k@א'x!稚ًD:MlO'=RjP>%pk{i(Ҭr*,|/,b`mP>5d7o8qjn,*['Xzy(f[×Pam׳O} yoR4>2+qN i/jP>% u=[@<~r*,|HC}+v lS_`5VAtH* |1z}bAt>]-.6Ce- P~ {/*K.ZP>}Kn<^9A\NdU Me{"Qas[U.ZP>E G+kQ# U%X%X '2x[%_Qkh;8P>% =A<Q# U%гO} y5ܶ{Mw(L|zE (?!Xzy($ (<Pي]=[@א$~2D(;LQcU,Ov 骰oa`(be9 '*^q%{D-eTUOj? Hk4oL-u|n+2|CQ&O/G'Ӈ4rJXr(%?H*2SNIsGa߁ P>E(TX@1K= @-elRP#VE:P>X>Z,SyWPa{zXXt'X'2x(r*jʧTXRӕM=AEԉ]j*,&r'nHgK|CNLObv\i ,Ply(r,8 Kl ;YU U*7z )__`v~% K|c,һ||CNeoR!7SWO2YJ/X` U;͖ҳQ>E[VPa %6H e ~e| (~7Wֲ{~ )lI8n~[BQ#_8[\N 9 \9(۪T|,>j8_U_-i=(|Uw[ϖF2~V4^BoEyg (ʤ(r_gOܷZM-O| ;x>ʷDE@N&!X$[zwB;>3==Z5ʇSĝ;q P>)װPubT (u43-"[Jgk\lw[=(g_tX@b\o|VnNEʧ{c_^γIW|HO-zEP(TX@W:XbAi;2-<fxA;5=ƾ,|jWd[,"(̚/GO]X{QRV|qUH25L^ݒ!My4X(`A'P> E12j, rl Q`)="RsKlox~{ / (UǾ}" OCQ+ȫQQ$]4 KoHL3$.6&P[eή6sJaʧ,ÆQs{5I- }3i?ظ=>_^GK- O-P4+-/עPa#` [,(5|x+xۖ{Kbl%׍'fL"#ʧrY- _D|YBO}UEJp KeTRG|鏇]fP>X KPZ,QF2V  찴hJ렝)/bG)6OZ AYۨ4ʷbQ. O$7ʗ.FK8’`  `lmHJKፁ K,l 8aV\BQF2V ],r|k `/컪T4r?r#<,-X\Dr|sPaI% qp"'Bْab{0!N|*HOy zSIXJկaRq(|y- FEFal"7ʷ%O/En.<۱ iK4.-X¾}"Q Ŵj,oeB|bịExet%RadA 'g' (\,X(=kvOmXe5 P u [XbQ@f]*V bJKbŗ're{H;Q'aYBO"-l1[]*}U*Vx!X_7WO^WŢ\}o(|'4,kQ򩯠 Xb= `ąbwj~tX@bQ*ȾX}b (TX@^&X YOb&)7"ED׏lqp߳b1K= *O[VP`qp߳A&!X$[bge *Ó(Txlw]{Z,Uaߣ}ٲ (#` =(؎I=cv P~(U(=g (?,(TX@Q&!X$[|P~lqոjWW껭 SPaG?硘_BeBE\,ʏ-tkj*,a-g (?SAŷ!bhMq:V/{6*ҳCf[,VPa嗍)UFɖԳ_)|7\&w@q I-p߳WԳCe- P~ {/|$[R÷ rU*a#D9k{6K@{!Xpߓȳ-Qh}ϖ2KXJ(ŔiXD[^̶N؁;2}m Ay(xVPai9nܾ-94R|_>bUa %Utpߓ( (TX`,Q`m&RAN/j+/-峆XQ3r-gK0Ӟ/Q4P&"=;ɐ[]BϾd yll74XPzKOF1j*,sR{D%`ݷvQWI㺥Nz C[9,HyP3[|YB{sW qk2;,Efp߲ (s". {O;,| U)\"t}==G*SD^ EAX%؞-|o i UhF|7cC\~m=)LY9RUkxPLF={5YX80@Wډϊ:/u{Η!Xpߓ (TX@W`JLZ,Bo ;kɐ& D|s[#azt=|կ.f{2Ӆ(TX@WX'1i{Z,*5j|u=ϡʧ sU{2(TX@W`ZÃXb=G-6-'vQ[<{^(Z,a-pߓ_BO}fȊ'HE-psbag;CU* Sg3WÕ X~-pߓ5!%E!{\[FeX.l"zՃ ϥx!*,|rn0WDCaWPaS*ȢqH=[@ԝf=_bU*ps{΅WA{*DCZ,psEXdKo7zRx&59)*,|j0WX}NwF?4G\qKRzD-% vv3eK|O-[,AVA&ui[֢Pai﩯" =7{ynl'CN,UX`C@ ,kQ$xBIJ`c}SwkQ,O,5X{rn'=j*,=5 RD-pԝfoiyf3zyjWX}Ob j,pW`PYZ,Bo 2U@$(É*,=X0,Z~o E_E([&+XXb=O-.N"JrIH-w)}S؏{2ӷFʧr:Ei{Z,:,/)Ց =2)IN Ê}S b=4-kQL#HE p%MS(ε^;UN]JOa1=[@<Ӆ(TX@N&!X$[zwlAa%V,[KdtkΗ'=O,PLW`!h%[z[\^=`yObRaS|{2eT (ߋ;-b{wOXƷiʋ|n?Ğ}ϓ"o*"'C1F" Iw'}χcƱ*6m( 'z <)װ-| 7ݱ (#`q-;{>`~T* ,`b6Ho4'=*,|RY(=\P> Fʏ"[dKN{Ynk4\ÇJ,p۫~Pj,p16/һ#eNT<^Cw{M4$ w|چ`A}MWPassPa?zɖb=jChEJt Q{ϫ+iVT}/s,-e(ZoX֢PaI% "7Rܳ%xeTR&(IR 3oo{e9l)8)VPaiLCH-5Y+T k4m:ğή{ȝp# Ee- #,r#Eč{Kj'Cą}g|tu#^0r-}sҖ}Z*,=8,- 7Rxk0뙝lȍ ٚ2G4W liC᧯]BoeB%ɍiʧ2jMJ/\ZG^ gl۰Z*,|',-g (w6Xy ,\Pɶ^hYDU'= l1=[@4_֢Pa ,r#E2=[@>n< L4I[g +U;= og)5 2}pC$7R$HZTljfzӻDhSvRa{_lAC.F2"7R$%xێN;Dz ^ 4[lP>Ŵj*, ,-g (daBtQWkJ?ᯛ<%~r=[@<~T (:˩h.!-|ì{JvǍ<y6s[`]2Ig (b (?ʄ:-b|P>a%!,ĥ$Yye/qz-p d$= j,p Q&X.!.}/ f=_rU*zoz8RIOv  {2ׁFʧQX$[{l2W"z_"&P>iBP>tkj*,$,гOZ>`[rU*hu1iIWzy([WPagPG(_{Il)f寘:1ή{kXbP>Eϖ(TX@$`~-?Ş-|ìEJzLc`^ -_S'CQkQh}/*IԵ|ì~J)zL5ꔘTX@ r'C1 (Ȅ:hkp G VsU*$-{=OIg ("L_ KɄ:dKIˏfRoUYm ۓxckI}"LF’KEԵ|E-dȋo}ji_`҇//|e D XJJaRa(´kj*, ud%&Z~Z>"iN[R>f=SsO/alH^$-?\V=PA^B{Ȅ:-姮}/QAl+U8"Gs]ASaq l ty5 FQX8[rIˏ/*^׉ẉ {w HZ~ }ObT (0TA΢}/[y^J qzj$?E HZ~ }Ob (Ʉ:-Yܵ|ElkxJ|LI6'ٶڟOGrN(<"i5 أHt-{4[,' (pUA]ˇEA^BOv gsIO"*ā7ޜS>"i᪂= 21E2"ҵ|ElkxJ1ӷK>I˿kp㡈ӭQsEܵ|EӬEi7_2i}/*ȹkp㡈n>[֢`^$;UhkpߋQ-T% Kf'-?^V= 7-kQL#HZ~5[U*ceʣmol_[IX-]ˇ ta5 Fi۠+^8$e3n poIp\'Cn/Iclcot9t?p(?S>tva pߓ^Q$S?`V`=|2zn>hBGBtyUHTERK$(8 m`5 X2tŋ)rK÷8YUB^آFtz"1"^L|5XྗzPY?(4Xྗ?ꃌ<]}fU"]ߛؠ߭Љ_zJ&‚^<(ߊ:1-kQ]{/(ll!Zz[hMV yX!E*Ejvט.{Z,aAE E3s71fZ ,{ RŢ"}/QE&wvɭVmkE{k (Z,X>㖂=4 ( PaHEAE^*0*Wku5  jUX@b lP>(TX@W\ËX۳O-e|J60rɃ (Z,`ʧHӮQ UX׳O-.E%^w,}WaSE-|4 ( P~z"VoUe>?y(>4Oᾗ"] Z,x(Ҵ=j,pKQ&!X$[bྗX'C|wtK^teO]b'CZ*,(j$&--{Z,Nۊ!'r-wk~ʧt߳C  Z*,|+HCEZ,Jʧ<{)l/+|Mt}/}/QE/g (?:1-Q i(g (Z,v^ ?zK{dXM Kש"]UK硘6s_BeBi(g (Z,r 6$ݫRl_Pq+yz] X++=8+FT⋴XzD-UG]Uw6;}< XI {)_f Dd(R-ԉ (EPaİG`2zѽ)wj9 a4H|EʧS/Ҩ2#[@b),{2ӮQ ہ}d =|2Z|OxJ"žyqo&ESe؍c-p>SCfȫQQ$ۡlaq[B f-v2$RL-oV3쨢\կOa}"pߓܲ 25e ,GX([.Q+gx_5>g rU>=i+(TX@(RUIl)=| K^ ?R![o^l}VMj(U}/G;G}ObZoYBG1-0,,nRfM<բT3X@1UzߎSCg(TX@17ܷx.Up J?LAO};" G!쓃~X@ߗ2Y|Tuqx|yǽhL`'#Pg44`SDXFQ!^A_柝nd|@ T}VflUT8ܜ2Tgh O܂{;Jl [NU@r 8{<+ŠVm)uǧ֋O^ٚ }rzR ´j * 6}^B>~TUc  k\S`'3d-F\=)h ="s͵yu RH =C L %1]~(yn@Fgf;e`>6E2p~$y2 ?+d5  @J=ɏm r_;*ߦ>n5Qss HrX ej * u(#vF*'q@u*){e9c?$I* un˸8$O!ͪU1ԝU#(0+2ԟULpcHI$Y:QAR熌 ~x2ӷۮƠRwc2>5^q*Yel͸J0* ubdH?zc<i)d5~bxSi@@XiEÜ4OOYk1h ʐ~VvxbZwgUݍ\b)2r?S ;B G3<e;zj * `ϳ5^ hqY-cT}pϿMq8RG Hrqt}5:s;33`jղUK8ǐñ~7L! hU@@dQ.'9H=0y|q5:9s TgH= "Z“̾1mVK'-gH= õT@@QF eHR!w<`Io]n]!ruR$eZ "w{޶J2X|a(IZw A~;"+xX8S)&1yo] BĞ0&0REJ" Jn& aj*&,&`zݳ;`2Z}VQajOV˔v3mo_no =Lzylsj*&,&' >Si׌>6sT:o͌J(ŐM{auWX꿲#=O<ծb/vyb{;`2>w[ڛдq%{iSejDTR#>wj O`s'kAhn_~q#p&'qrzUuAR]ihda^= s< 5ʙgEǝôj*&̣8i=ܾdօUO+=7U bIlt'4Z*&aL8O\ϓL(OPtxUAy#+E}%v(TQ󴣱Iq.b&r9{qLF9q7Uߑ%`Bh=DGUF;ŤIq0x5Ld<)GϿ@e7~hy{~>.CqblGb&y"vۂW0_;Bё_Γ{< HٝΓ XXD_ĆW\o#s DL8i TL609'xI>`2Z~u{+yz#Ca'~ 8ޙ*pa{i:& TLNс<' 8Qvqp򪒃 ˆkbE>Q.\ 8Lzi̴ij*&x'&' 8Qv/Y.Y q4, 4LT O`lk5p$Γ&"<17 ΦZ޿Fq6>Vl&4Z&eс=ɬͫIreztY;w_ S*FjkW!O8 WP1GGn< 84l;[~Cľqv顼qs' 8>Lzyʸ 8$ALJ͞'xR]<: OzWa_bv@B#Pd5?-уIgym=8aV 8><: I=8x£WϽU!Z8{?룶yOzz8yLւP1Ǔ= Ozj| x-,mBY)Zt=&Oz ۮp47ǻLUzHM,~+I?0 $6Lμ:z4qu TL0IØpt=>`Byb'- 8[_`򢑜zq!=\ut=>${;/IxC:3P<ǓpZ8{'[2rߣ'oxw}:*&*bsq<`ʰ#j*&Db O|`Lr$ҵ&UzxwD?&qaz48«Ah n{{ GclfUzCxeA WWPOy@2y lØpdiPc mQvÍS/a|wȼ |<0=O<yGb5p|Ĉ#IyƄ*=.Tf7EE;iCY+<8r*&M#y9OJp<5&VVj*zj@^Oh.}A_[8>I'>3.b2`7/O ~DxL xI\R38>*ybqd9p|yt\t0fp<5&x3W[q[2,sBp]o}+JN1yqpV+Ạ#p'xjLvϣ{hg;|@h5cHZ?5fp|'̽baybz17dH̝p-as)&=O4nc9p|8L8Olp<5&x7W[A8>;׻_g>TXuW_%SsI8y>Y Bạ̈1*=쯏xr+޳R 8ɉEp<ϓ TLmƄ4Ԙ[]x7u4t%Ix*yy8^ac9L1r5y ɤ۪|pΕLJN1yq0Y BŤq54‹  q<0w܏؛,z5P|=o'qefݶP1)s~op" LXnX=oUչ+m*ybr=ma{9`R8Kn H[xk4 }n->QT1io騾 ;8,b7IRDny}ULtT]ؠkA㩩ԅ]<=Oe2=wUIr=_gyc-6p<wua{yf{P1SS&Qdy'-O^D*ԣ[틢#Kgi|g2]iQ}wQv 8a TLNL8ORpw6iLDzؓc$;H{ir*& ȍr' 8l`2Jpt=T<: Iy%1WL7G,[4HOG/1)=O<Wa.b1L Iy'-;L d9$9b@3BṀ~}]x>wZ=z |d<a;KM~.\:x&eTA\cJx/ 0Q}u"z |df/\Bds1&'az"]ͷXLzQMtT'x0/ 8><:\hz"KR=c{PP~|cu|,gdMp7b'Y:/<8p/b2`yb{ {-MjZ'u;(DB/1Q}Q)Hq a;[dr&'yzR,}xB[]RT*HROG1ANơՅxd-p<5 byLb>G7/?Hq+O4wtT_ԅyqXAb8m2\Dyz`By2_lm^^B[w?=|uaz4qXd-Ld\th8蝝Ggϝ#=8Yo TL;ҡ0a='2b*=|5x쫻Y>dmΑϒI w=>w<ac9 9ҡH]س;g&}J3ֶΨt UL;Y^;۟t=>w<k`Ixgexx9#ƒ=J;%>|툪]"nO{T1Ǔ/z}sfVP1[GG0wx`2ߵoHx :&xE]w=>w2A~S~c 8t0RΑ~b􀣱6}R&sLJds'0ý |yt}sHaz&XGqMs_c<ϝìr*&x_1\ Nay9pw.4ףM{3kF&}p=wI3N!c 8>QLX]ϝ#=ֻlmo.?XHnA轱Ftϝ#=>\Յ;Y 4LsHCuaCs#*=)mMXl9F,ZtiIux8ϸ 8>\ԅCs.lR4Z*&xҡH]8;Gz|d_x{'"R_:;hI¡qẠ#pt=>we0 ݖ2)T\m5LsH5x8` |\ytCsH kWpNT'C7 )|)/&x1A!NׅWP1Ǔ= Is7ggGUAޯ-DCpnJ_O ?zsI3!ϸIxO:t CsIOnr@ZRO;O/¡qXd-Ld'??qUzؓ>k]p+w}0IxOz%&]ϝt]x5QLX]ϝ'=>oo-'1ni~Γ/¡qhuQ 4LsI#uz|z<|^ԅCs'0]_ BŤq2`z|z<|t=>w2.<|q5p >wxQ]ϝt]x5pytΓ{7s޵48KyElE;Oz||]ϝôj&^LX]ϝ'=> *=Xi3wX.; !Γj]ϝCմVP1Ǔ= IsI&گCտ Ul{֐/ۉT_wΓ]C^ BO:t ;Oz|nkWȭ2 -EmckxyU]q&y 8><: Is#_~<"=R3=RdnKmUk;Oz%&]C^ B(&LJΓ4&ֆsJ w }ZIϝ'=>^Յg8$;Z &>wt8Tf=>Bi;`2^мZs{W1Ǔ.# ;`2'kẠטD# ;Oz|v@}IIZMOzrKJG+ GX;Γ.w2q 8tAL8OgsՏ?]zOo/ƫʬsHOuzyf\BŤq| :ԅ#< $M޵GvTV1I$Wy'e|ḅ#p'ՅG__pд0t>Z _b{{V6j*&񣘰}ϓL(OfkJ Z] @z| G>w2u 4LsHN#uz|sH&_:BmOw|+E?4dzv?s'0T[ BŤq|2`z|sH/4NjDt7Ƌ=ySkPsHOz4qӵ TLVL8Obp< 8tQ 8!O b'z cy'=.<*=7nhu"H׿bΥy'=>]x{O㐧WP1;GϟDcy'=]ϣ5gH|׷sD @z| Cqu 4Lṣyz|sH/.qNp<ט Os'0}j*&xҡ;&'w7E# @z|A rL߮պҲ)_|[\23oW >w|/spw|푺;j*&$&']] = _nhP/ *&xE]8u=>w2=ܫAIÄxぉܟ*=t,C]bIIux8L b2^O el5*=H,ݔX(Ms>w|QL]ϝCݟaPw뵤G+4f /x`iyHq?XH-?ߌrES;Ijd]璕Ō\yt"OXO.pdo%4顾\ѳ÷cY_Nfϓs<|ט OsPz]xzOnCLCObyz<|qlaRT*tkKnlyyVsI3&sP^CL:GҡL]8T]<(Obm%4!Vq3R|Cr]|"_8;\ bI&Okc94veޛ#9Nu~~?dO>Ĥs|4v)Cl1q0 bIHMy;y˘cb|d94nNmi:"JK?5~]4k1Aǡ,Ej*(3a&y754>Վ|ﲎǺ1.ZSgL>w2Yz^CL:Gj*(3u8OBx:&d|Ф(<߿M0[]i͓2A 1SS$&'Qo)OV=Z_cɯ>h_Χz>MNac7!&xj*(vBM$j?a2;wžuõɅžSkh7ywI$)ϝCX}? b 8<:'$iI\&=ӡy|5;Na{7!&xj*Ą$0-յK>byqX1dhIYDz-w͜q9 0.F;Nơ1OMsΓy%CQwkz HLscjGL'q:bk7&QL(Oqhx&}$kO|hiw7!&$5&Ix:&1sHg&t+$SD׶{A 1SS$&'F(OV=b*YYI25ʻ0|bL 1ў8Pm{ 㩩ԅiOh?a2w.6!=OQ|_Է(t"HC :q6yI֞8(& {A 1gGg0<'Ly`E)-!c?G>w؎ɇ<ў8,{{1>w<:oz$sKjGL'q뮽 KƄ0)'Lkn\T>[)ގ}%wkaܥ6C 2kO|d6j{A 1G'0q'Ns|:(OV{cߺ3ãB@/ΛG$`)O>w2A 1IDL8OT]"=ެ .=qzx2ge%c'0Q=>w2qoe7!&qU]"=ޠ9sv2u+'N1)K&5#&=O |d*&A 0)K80<_sH&֍N_'&#qXS[ 1m'L8,ϸ<: z|]"=ެօS"ZhXr@m8^w%:&OIx'{A 1< I<ǓLfǧ&=g$i@k rpM^'Qýps&y'=otVOs 8ޥ&}D㰁^CLNL8O 8x`2OŒ6r_ktym}ӓ7.y; ;\ b |yt3Dz]|NMzKe)z5Sa5O4fGb7!& &y'=l_}WҺfhmƣ<쓚J%Gv 8>&}DO㰃^CLAL8O 8x푚pb>.$O' |d}$vaD: _%H/:˰ޭX)cH[2=}!&d!O ; ̣ט Dz] &=ԛleUO!&f0<8,wbOyy'=OIz8ƻ? |RM<8_nCLCpa=pODz0\6zBTm?.o.hyjonCLYL8O 8x1顝&鹙?tG. ; F.G'0a=@/K[HLVrǾe}:WCӲVi6x%E@/qX b 8tIL8O%j>C +oJdK@S&!&3.\ 8ay7!&!&y9>oZ84!f^=q1~]&=\x>y9^ơ1$ ъ$OƓOpuܤ(<Qrvۉ -j;L:gc&0ŞbG' LxCxtLHv>ܹoxm8O<8Lӧ&&6y܅;{^#LsԅIx j>7A"8h>}O);TEHek&0ў8 tT`PUIODŽl`R& s٫!%VK.\`y9=AA 1SS.$hIvI4 =3e{?Ȏɇ 1;$OhO|xl k/!&xg;]]$jI&sH|şϔ1X^S]l'מ8,wbw2`y5Oe;x>Ozt~#5T)n0]c!O>w2˵ F.SS&$ycBZ60;p]L+6}~beG!&x |d=wb;UN'hXR0ǣI1\8,ׅwb]1)'xjLpV?K ǫϧOOwS6>w9&y~¤ Os'㰁^#Lsc f0<)Y&'yVʾ< 5/5`⻾zt`<ϝC^瓽 㓙%Jӈ:*Ԛ[ciËy 8=sY߆NNwΚ hb\|.^k(A`dSw[5])}F}*]y;*&w7q}>ELCQV;v2b$[f | *M$^'9yzmA p} {Mk- { \ ލb >˄:dlS7˓'ΓΫH=رwK;F.>aA™U}k7,@í*>>|W1l_Ea|/MN761< Bͼ~b͎><} t#,);u hnCX@ti鮡x%iD>h~4-~ ;+7nEVw{.5IfKB~_l/ClνX4,Ni$K.HA Կ{(R Vl)<(t/(`l- ,ՓK%x>~]?>>'18.*5d ,d(ۻw bBȇ=5 _ů6ݗK>Nϯ:)<(-Jg(/!,^̑ҬBsznԉ+NvI#,)#<eC(ɐk(N`l[ʖk,t_ |bR}LƷ/tL+ f ("؍b (< z - 7+ʴ< kp㹟4mOK5N=0X>q7!,|%p3%dˣӦپp`/n27S#׵я+l# ry7,p+tEEW~W+}mcn-^NkX4[@<e['YX$[!x%l ph|5㉹Mue8޸8.@j-| \Aލb (, Lw EEM`Wny|z6..+YOC]Zv2N"٢>+V TL}ܙ5V(3 ^CX@U tJl\ʲbqzuׯ1[&yUA6f (LvO2Nb'h!6ߺRiHtBM_}>2#X@O_ 4[@<˅(LSHP>5-Tn":ʠ2z+|*/k -| nCX@dZ0 l1-|k[S*/{ Wdc-˓E1~y%˄:d5Q[&w|ݚR1~nx=' .d<ʧ le r;nCX@E&Xd`l˓X|i+ʧ=lf ("S^CX@ԑѥI3V{ U*a֏r'-sX=ejKN88a7lS>4A<iUۍbP0Tٰ BOЈr?2-_p z$>dw~J,)raєCVkbQ a)%sS$kBْ~~>Zr /EzwF ={^< G4>yfGQiXdQB 6ޚRQovst^:߿g{6Q[q%53,ȖׇbMz7!,dbB4^%`)'Xf[oMף9y 9%tXh j 7A%P,w:,u̖ 4[Bdx4l:֔ y g]i|>ʧ]ofK\e~nCX@ԑ0 dKl WC̵=׫Iݰ (6 -|+VzQ aSG*(5[@$`_Mhހ[ K]d1Łi/ lq|N(F8P u ɖthD.|5){ y[\-y5O (_Ò-OC wʧ0WAN-)hD[\}51~+_Ղ]Yb9 uYׂfZoO;ea?iy(:,{Q aSG,,-YO2x\ujJx!/V<3! (6Շ rlPunًb (?Ȅ:Klɚ-|j)t,$dX27A~%Y [7@ gP>_] F1dBEhIa))(I6z+|AAd | dP, F1Ń#a"RFa!,"J9mc,Rᷯ<+#X(,UjCBP,wF1%L3b$[re})=[X@t5,ڐ@<vQ aPg`-6hDSAjJE --[C 뭀0pUAڐ@4Xϖ(#a(dd K7Ne)ߐBZ~PD:N u[D˷N|2kՕ o/>TYTlW̯TpPKjlQ-?t硈gxF1PgE|Z~(.NbT7|,S!-򣱧(FNIXD˷N {˽ A>й?,}޼GXRJ?Âly(b &e/!,db ,$vJTD)<j8zS!, ilUˏ­v)XPg`-ߪ;ӪŽ)_R?Da^-Buq^哖?fK| >E1o<,-GP>iWd˳V[dd<^EP>ioUˏ| nCX@$`ǩ -ߪA姸ܛR~C6J7](~c3_!, OZ~ [(b(F$P u (|2ܛRs}rEx׃@ǫ}Z~P,"P uj OZ~Z=F~:4{}۴.ia (|J?4'P>E OF1O,,-'P>i"&(m]95il]tJ,Iˏd(bP~ uj OZ~ʋ{S*t~8W!,|eR,'MF1dBEE'-?[O9P_7 '-3(by7,O$,[3( h|N\‘[LDg/eۇ '-?^VUϠ|tNE1Ov TϠ|˽)1?x+^'3(xՃTϠ|P~ u'ZS-?Iϫ"w=3ԟ`oq+)b ( X4[@4iP~ u 3(+ȳMş _;қl哖B-N ʧHge(IE|Z~哖L/_&V lh=llP>i骂T/|VF1LSH_@gˣ;q} '&&Et;V@_âZ~P,F1_dBE|Z~哖bqR*/otu.}%~碌wP>i骂T/| |z^CX@$`"٢Z~哖bѝX)vg?̟4CGg7\1S%-?]UjC\܍bK|Kv ;jS%-ObTâ)_be7!,Ȅ:djʖU{GS*ܣmRn|ʷ5G|KZ5,ay(jr7,o{a.DwkDtK1=n4@w2xsF=؄ϰ)_c(88lQ`)`ɴ2mb&_'M@y8֩Z۴0S7 CC9=EZ_E1S4UAv0,AS%< l)5e@]UpZk i1 ,|`l/C\ߍb (:fal -|sYΖVMeO>{@C]=LJ?uʧQ aPg`ɒ-YO2x =hlgtf ("lًb (?Ȅ:eɖ'ɎG?c@а}ґ= -{Q`z?f (bP>u$P>eJũ$!oq+Wg2{щ_o`gӄҏP>rnCX@ԑ HP> $?'(Uܾ"/E',p߳t} {4$P,{F1{6˄:d)`lY~v'̏Ký{ˇ=>E1_4,VzB28`uv}v'~]KlBl-|޲P>u$ -VO2xYgW*p!,a"؀e/!,! ,NitwlT'KϦT۷o|Ν#X ` O514[:P rAލbK,2N{'4[ `ɀeE :?Ogm,p߫߄ҏق="dve/,psԑ*K=g2,TPou3psdZ>x(YU'wLSH$˳)-ԥt سH=g ILۃԺ۝GmV&T%nl1OZ~:Ы=܃P> eE}.)q5ٔ۳-QZ{ի{o-5o1ps嗫 W-{2˅(F}9P`lQ-{^K8i^n&魡?s?-ps_âZ>d(ϝ܍b ([Ƚ]^|F*51({V*z瓚FI=CS> Eh]Tj܍b (: *ȟuŰd,)^䓣a%^?{(| ~7Oa凃+X4A<[lٍb (?ƎI^`ɚ-@ٲ;C^tR-cίȶq}υ06$}Ob5|7!,|HE%keKZ^ 7P'maH=kpshBǕ6$}Ob\d7,ps@"n)-jDY`T v~d !2nH#J?Rpߓq?e7!,:]fK Ò4"i{ⶣWS*#Yڎȩ}{,ea 'CWȻQ acBCx$)@[aHX^'rUAf (Ob{Q aQL/hS<2{d۫)cs&?6O媂P~ .ٶP~>z dlgڵ+ȳ2ث gXdj,9{ Y\b-pߣPXE(F}ePg`1-&iDfY5y5L~4"WYRleEϗ(tLq-NS?xڙƓViDemӦӡV5V|yS`l <h =m7!,= ^-K= `ݗjJŷKٿq{v#,ASS El5'({KWULl?U&oO1}4KD|Kn (Ƚ3};{MڣS>M >0pߓXm ߎb <7G"GNw$Ԁ,Tpy|WY>KPF;;_]J?Âl EZ0زhDtJ]?1ջ6ɭ#Kw"p-pߓmkO$P~kiڝ=Ep4R۰1ʦwAPM <(g^CX@(_X}X&+5П'wc+8a凣 aєPΗ(ʽWHEV#ʧ^؟l߫,Jf}_%#P> Nz?E1"8}bOiGS*[p}5fy ,Z@+p YʏORu9XȼnTYj'g]|r[_!,~1[<(¬P~,d%-tܗ!# ك~:&|ְTikxhi(깓Q a'y x8EO wOVk@T|Ϸ/.¾O %O}Obu#v#XW}BE9XyN?'VK9l6O>$= ,pߓXߎb (?iXd spԝX&ItqJp5_5GsngX4[@<Q ab%d ,*vV4: )#Kt!,bg`|޲P~q/|ESC׳e/!,q-r"E_LS* TO&ŗz|xM}/] \,,d(zkpXb&["}/ڴ:iR)>U_zF?nbq  =U3(8z KXFX([bM4"ruiW=q([*Gy(:,{Q a=O,E-XIuw~H;D5~lK,ןal/CQVw’K(&NFq^%ʖ3M0%8܏ `MJ?4[=s)?Xyr4[:j7q}wxrfN ![l= ڱjE.{,yv_~ (~pD胹y}Qw)}"{<vՊg;!,|j1SQ\,"\,,u'ŎPvt\W;_Cd bJ?fKlPP u ɖʧر_I_pe˯W}"ʧkXf (^CX@^&X"&P>X8:Y33DaEެ?7GYjXߎb ( falI-|O_LS*u젰sFdUs#Xಂ),d(VxdBEN0p eo'N=~$Jb-u hXCX@`*ICuXʧ{K)jp $^ێlS*c(,u7%Ofhh= [aًb !LTˇ^,NzO>=AOƜux,q d x(ry7!,Ȅ:djpߋ9[湥|R逳AM ;EŜ#@z "i֤Z>x(]AލbKH$,'IT9+ۮT &lu[fÇ JwG7ubIwWǁ&CKQ aI%AWK'@XUˤ~ y+X^ڕs7>9T9ޤՈ:,[~S*x2}WxEBLs (aHyP,wF1BPRf (j6faɴW+4&v\|ۃ JJ?â硈딿P?N|Ζ(ْ4[@) ~w@諚,|Pq4[@<nG1G1KlI-|k0s RA9" XS(nCcs KS}Ob5|7,pߋ{EI%(W{mS*|tdR?綂><,d(=_v'ӇIXXFOW,+1ה Y??녞67^ }/".+ێ'Ca P~w3o;JڝHg{7'.8Y<&_3,|ERݥޢpߓX.F1ߚ#fXEONX|tM?٬d էnK+")p*F1Eɖْ;{l58EvPyt y3;XIwW䬞p؁e/,pߋ |X%hDrOZրD0)%x(|/5P~ ubˇdv'}/@X<FT}pQu "nOD˞!,|RUkxVPeϗ(9̬aXFOW,O/bhFKq!,|jW-p [_E1O}~" =Jpܒ"PF ۸HJL>d(6`ًb (Era? H e[27o~=v?.}P8<}/ҹ,~*d8P!TQ `qpߋ(,A``lY<ԜP$qzྗ\ UAqpߓ谘RnCX:'jS$[pƆ^)_ 9,8{mjOa %4[:P,wKEiN:$TH(>c9GMsy4W- )?ѹfK@kQ a锟QOl-u'UO/'掙ªߊ|xmp}/ѹfK|:XTZyW }הם)n_7[Z2 R{2݊gD(F}/Q[qƆ^jR!iC«kJՄI K{ΕWf8P,F1O"~caI`lYu w݉PɨU3)E->S?D-f (b(IS,-9j",}S*OF@;z4ddHڏe0D-װDP>ŲnCX@W:Xʧx,NTvHj:p2TAHu?v +{Z,Ua?GP> EX|ٍb ( falI-|jy'_FV-M&1%߅ҏ}LObQ`^rJLZ,rQ>]%.[r,Znp][ as,Hyqv"_;fX8%Nvߟh tw734I4O-_3Z,x(²nCX@1HEΚ-|:aЗZcOe$[ Df-#IR{=>ʧ,2nCX@ KX$[f (s+VKZ6u@ 9v (?_Ċf (Ku]f[vʏOȉhD+ȳ[|?iepOzޖ*ᾗHW'R8PҺ%n;}/щq\ΐ[G8l0({^`.~D =@ދb (?Ʉ:K) H{y!7ݸ 쫹xBMT{pRP>EXv戩 r)lӉq5yY_fZ֣ムྗર_f ("܍b (aEN(FO'RĴ(q@>l^&ӃpUA.=hVe7,ptD)^98eSΰLNb)JlSzv]Qږc QL ᪂\{<ezNٺsྗ!VCS*n*XJiL4[:Pe+(LSHDP>5$@}gi5&uR&6vF k֯?rKlPlًb (Β%JDP>5^tZ~hJY#ߣ]^w de: ^Xv ߍb (Ʉ:dKl;])LD4{0l rkn#Xྗx:$[p"_%KhJ7%as,HyPt+}Q a1 q/'RHྗ! l^hJE}3>Lz;*%v?' $L'R>Nppߓ딿P>*r"Eɚ-|k,G3Pg!EGVW5rzP>H1f (b}7!,|2}sd9dP>5-7ӏ9k샿EOP>H/ EC\ߍb (L HQf (R TT$r~p_w}/Ӊ=^#Xྗc[)_Yo(_N(%kDtrLObTH(wє{{ ( $c5d(K8)/!,|:W#T-]uX:"XS*mװ&(yƔm3,57GtP$[;{ )]9n8|q ( .*5P> ,{Q a_5,-NS~! zKlJŹwkc<ǔBmװd(Jl7!, JIhw*e!)6޾5s<}Π];XrJ?â)_y(jnCX `Q̰h*yc?6.`~?vo&1jE[,WZby7,p+W7XB-y=ΐva,״C=,HyP,{F1Q̼spI9a2?5;s\,b+PvH?lq[zPE wOa)|7! {(H3%}t4"ĦN//x2FZEV?{* v{2&/! S#:>&yNw@^-ǦJSa}gC8|F;KC*k~ܭ,;)e[uS;a5|P@ߖV{CV<B Ҳ'0aP3H2lFOWyqɋum[`5,F9x_p+djpzxx ,;0Nih*+h4j$ɫM߱im ]$ӓ[ uWA.W=e/Щ$M(r!t8(*<O-}׻7lA3:fChv!tOө#4{R^H^< V;(3%d2"/B> B=q! uliԩ#ʹyR QRjYé6-cs-Qr`HxWAg0z=7u{/(8x u/Jvirk5Ѷz>! u^HxKpAXd7! rULx:T{3yIY,+-Jy-(VC7zt B^_e0u@Yc vWowtwd8u-gLA/Lχ_CJlZ'@4C=BY_e0$W;ѡ)HYq6ql6m۟,cS`HXO9y01$uHK#OIY:&8vRMMO8O伷bqړ!& v:"_tʛCӤ8ek ٩WcynbҋSln#tI#&ȓqX! bIGg0<ց'uDow{Ǿ|7NNBۋb'< x ya y ٷtwTd0n_~f 8>va<8 L9xΓy'q$w+C4²3l2?olS}ǧm~ʓyqX.1d$pp|p|<8,w1_d$q$p|ybz]x+7%>^7iE7O>V4Ou'{A 1ǗbO;x^C1\ Yb9ސ@S[f2$r=4ד=[ t7 f< 8sh<[1Zxvb駴PKN?IxѹNssA 1DDo)OV7-Mz?d WD] kwV#O28a~7&Owpx[ysD<:ӗ&=>ưgZY3>ڇoٱ~`\xec &İ*}ǻ'"=@7 5]/I=8>} 8u#& qX 1 te`/x0ݯXzj]M SBnm 8&y~8Zup<ކ 3U3fz~U {h "J-4i> 8ާɇ&άs;-I0/x/z{n#L 8>n{=O&z|ClF뭆[=)HxkL!6#&7,s^CL!y$}1t Sk+IQi﹎Wi{1$(h&0q@[^CLў14wq `2?wp^_u.5x-\16#&'xs]{A 1A`y=O=X˦Oc':DNO:jQ ÿ;p|2M{^CLy #Q#aW?i&=إbǟW9ՐG '&ǧ1'8>ް~pS~#aap<]a2_']4>޼p|> $ p|abφ>$m!\1Z/ʷW??#v{pp|MID xcW s f0akyϔ'V?{,S[7 55$zK洺L1_ L 8>ް b 8YLxL4O_0Gޚ PNA`v曹2qh 8&CDWp|/$I'aWoMzJPz=5j)01R y/7򤦵9t L`L `:5M{׷]5mOLxK1sr~7!&3&Nf\B0.aW[NuOq{s[~$d菘dyz]8IG `9 >\B0 î{k]RP%U79[msv+LzqXָ>^aRY4O:[Cyr>Wo]3m?d菘htqHL4&m5O 0#v7!&xj3uaLJh8yrkp:>p=B}>w6M'^쁳p<5zؙpk?p|M:8ǡuv b 8>b"O|dLL3b II~g>Ϙ2 qX b |*#b§i8sgYąE>j҃v9\`O1ANag7!&zsgYĭ5A+l2?޾Sic4O<1O x..VPoRB g5';0jwYDssA 1g߹*Ob`LL3z~S[_ЋvuZ~r OV:8>ް'{A 1"O?qkcb oY-ÅjO=;Wuas'|pn#LsgɗNsgK}jIrv;= M |'qG:|dj r7!&O|Muk8`#R[FjË߲Rpvۇo;-l6CsIxG,Cv;eo twN{$s稿@1óv?n^ʳ7$dOy=qd/!& >^by=s#<)ݚoQ |~;G=h|xܱZ bIxgd{$HQ_=I?Y:U_dJk5a;G=V=qp}A0ϝ3yZ&sWNҸ_/djjI돍0Wj|xt/nCL:;+Q#G>wz$]w ۚi;C$4+:Qi p b 8<: Αw./g^jlCWԛ8p ?u#ᵿ>w| `2'&= *G 'l{΅.y~^>w2u 㹿a. s>{P[ Yq WUo==sK1\8U> b 8fz=Hx/ϝ# ɺbEy+}9!&xn kykA 1G; Hxy' WW;?ĨsM >w.voDϝC^d/&sfj}$<|$|HY?{L\Kl\-ӫ/k;ǭ>>w2\o[{wbOs&##sG—e5xp"?_>WtCkCLpWG>w2e}ݵp<'Lby stF8Mz1")?Gҕ -71Ǔ yqu 3`gmx`ϝƄzIfyBg9Qނ&Oܛ$|Y'xLb'7S|ֆW9jLgޛ@7Wz-9t |Y\b=qg1>w<: Ճ>w`ku$n/0^^{Ј->w>wEמ8x b 8iL8OpNEϝ#-;p߻&%۟5߸<xU֞8e ṭdΓy9ޓ잠{Hyl1|bҳ>$䵏htqX1$Gg0<)' P}rܽIrۦ}66V5K~\ϝ.1ў8.<{nCL:{j*äp͓k:ܛp 7߯{Ä߽? 0 .;~ Dyj*SBy|dV7\C8ׅ#|<1/1 )q뮽 tL܅1&Q阐{I-L(SLKy1p<1|wCsònCL6Mcyb4Oe8w= ˆ2t,˵>7\: 1Ǔg yqu2;w1X'{y'-;?+@R۽>w< Ep0'xewbw2`yb5Oe b |yt,,e^}?y4fOdVpz?Ǐ폶dI{Ldp'xLb2`y5O~G* p .,Yĸ>e5n Co;Oz`kirnCLbz|'x]^ n["kn($ybr~;Oz#|xB yLa;O: ;Oz<0DzOMbiv6q?mӶڥŘxtFyE]8@q~+A 1'G'0a=>@G񷓕b jXb'1Γ 8a.p|JӘpDpsC &LJy'=>p?ۻm7S25(+0O0Ǔ.!jyVvb2`y4OUG̭|J5A;ʛrRa} |<@qX b ||yt;Oz|u^G59 ۂkx>wyEws'd/!&xҡ'1<sI>&=@6j;Ɠ^@zd͓dOMzGߖW5:ihw:R7$`)OIxeo $`" &'E$ʓ[7 EZޭsHĤh{'A 1HÄP4O:cYMzd&ӎIJ)̖&ǭ^ |.z<|d;n#LsHSua Ugt-ߦ[no8>wpQNac7!&フyz|T=>wj䅹֨$)oY}.!&xz|T=>w<q mƄDx“]n}ֶSvIz<|xnCL`z|T=>wk&=`;@ѷ~q4'Ͱ&xE]8;췲pytsH&y"ÏRHz ӛsH=Qx8n#LṣU] =>ZI"jP%'DoVsHuz<|d»A 1Ǔ= ǧ*= \ IheuKKpsHuz<|d}$vb2N`z|T=>wx`2l-⤘--0Ǔ/jQx8,p1dDxSXx0,[ CL_bz<|djk7!&hg1a=>@z<0մMzK:SܳN.6>wxQN!a@:t G ǧ^Oރ&= 8V(45!&xEpT=>w21dz<|)-z6A[zKht /#L_cyqX b 8>%h}">w|"=3Û^;&xu=qȫDA 1SSA htL=a2{ܫ[_#FwN<>wE]8jO|d b 8 L]b@$p|>,ǿ`ٕD{ͷ箝>w O5H)ϝCYA1>w 0I')h<ҲS 9݂aн6_Bw| `2u::l=. ?Zt2.Rk@ :|8|n#Ls?. Xc5`/jQ?y5g`z1z<Ej ;}1r^tM >wRe>n"_tTnbրtQOIqX b 8yJG`LƓOȫɫIw n* ~Hz Cs9~/!&x%Li)JPcl:8ފް{A 1Ƿ L t`Rrv֫[_==b5e _㪫/i2u$Z}>w2A0]Z}3.yoդ['OS6k'$p>w2>ܻA 1{m/H#;F^F5={kzɾ;H=ט s'g>{7!&x H3I=#ZI_o-U讖rd!{x%]tQO#;eo 7 H$푀]zfKn5W-Ž }Ƈ;.RD'푀ϝF~/!&x H3u=I{$sG"I7]zJ{nʙP8|"H o; $hg1H=X{?HR!QmQ)ُ~atQOHqȫ 7 H$H$E&D_y*chޟGy՟aD]tQNA ٵvCLI LG"p\a5ta?#_bkL4O>{LbO2`y5OY0^ IKoo[uHzէyqȫ=A 1g{c = R<ǓgAqkp4Nn{[Þ1SqW6 EY'x$8Uo F.fG'=  |"yE/``O7_W3YMb%!&xE> N-1b 8<: {$$E,(/6*\|߯L{|]$Y=_ 8/ o1 spdpEo/Ȧi+ ʬ[tz 1)$6Sd͓2aObRIn{Qdς;F'?h-fdv`U}aĭWw!N!.A0]"w2=A 1q$r3P]>P,zIrV5q@~ dL:'n C$8po1Ĥs|"]̿7!ss}#Nī i$sl`PҲYa>H6},es|\Eǡϸp?^] >wt >Hbneϗ\?D@ri;m|GLq(=A 1{AL IxI8a2i&=CEӺwϴ< 1sk7zxzfvCLtnQoj}4IPw)by1'nu YZX=z_37"ܥ%Ox |d;nCLwnĄgP,pӤkIaypGΓyϔ'qQ۫v9ӷ5r3>w 08l1>w)<:'$fPd_} [y!i?p|<ϝêvCLb8Ox:& 'pzkT:؇[֫zMN\L ůKw4ٲA 1̣3p .',VlߺqrYx4+q~f7|LV_4O:8Pm{=~7!&ILAyR͓R,z{pDzs{nób\_nfb9>gA C$Y.ֻvb9>^2Rس 玮qdOJuUo?ޟ!smjGL4;s ]մvbڋ]Ysm}̢J !x)t&vL޿3x$8un#LsWYLس .;]vI >w2~+A 1ǻA`yς xK4) I|8m>a=qXb 8޻>^ {y'^wnm#]_:[L>`yqXFb 8&r8p&|"+f|};RK&!5ykO|dV1b |ryt(Oz~G6遈/nCS2{(CmChO|d{wb2N`9Ot?|r'Lf¶ؗד}9]~̽O15#&'x b 8>fD= s0~+5DjVZE˔l3wEh 8>&y~D= s'PbO,YPԳ>w9gY6AY=/1ǧ8:8ǡ= vbOщ= z箂GoW=lߺa A f1&4&Y;sa,&YPԳ>w9&.㞩/>sluz,b Oس\;][Mz{?OZ'h .c0=1_ 1'|C >wuRڊkgi&&ϧQAs'3X7%].]4w|x̲nCL%%&wdp|I'Lfk[_q ~>b}j`u*0Irt&;1Ĥs|9 tۋ0P>w| t@>Jmxdկ1鹞s'V31$P>wϘ| Kx[IQP2Þ Bw!N31Ĥs|Z=P_sWW'Lf]kc((=3|GL4O:8,wbI f0<)'PZ}rMzsE2LLJ&JX6ٍbJ' nb1\CLqQ>]Zx~.(啌3k}H{y[甽(aUl\&@1_9:6xfi]N >?ty z)2Ndk7,+"`XC) %2 5KشBqNMo`S5,pXލb ؞ e@d KD(LlD#t0|OV ,I);~isQ aSׇ Sb2bFX$Kw)ǣ0=e))XSvʧ3]Cqf (:jWkbD{ ܻ c[P>ŲnCX@1 dlSӁ;ai~XbMW/{|$J0M ?2x(rn#X~WäII༇ B}e~{t9aew%&~< 2-vF1Rlq-8!P3%cQ%C2+w'_ʏfE,]Q aS9w~O(VX\&s| (E%)ʏ"GJoًb (?Fb&[|bX¡teH6A+э' (Ew BSOCa(ΜYCf (z[butrY`$A2d(kQ aSCP$[bЈ:,ԉXf+Ⱦ~â)硈Q aSg,,Q`Q>]ekGq< 7<'7`/kHׇbynCX` gfK,-(OThYRU;ߔ[4XWyxIgSMP> [>s7!,|n*#٢ OeuR" )r?D |K~X a"*ji((L3XN|2?۾Y$'s֘xzY~mI`|R'`ADP>[F1%[s,-ڝAMXfOTж{59mX@Ȼ^%y(uv戩¾q-ڝAdO{N{T¾ - O-~Д /k'bo]( <(bc7!,|+hrUH9*gN-)‹ch!yd5 ZCg^#X2(# ba;!!:䇦T݃|^ /]( 7rPlw)ߐ gW)ߐC0˓(b=U2S~ox)/wKEE+r,-vy, {H[< aI E ,rF/Q aoȸdZXN,,lE :5;#p~d?X:Ӆϰhtzba)E&)X$["|JwݤK 'yr#WϿnVSAǸ_ cboZiA{ܥ?=Rt__ݯӟowo.^fD?{^~Я?wi?Wm6`2!Gjn +l>Ws]*C?)/J-\9J;w'I?[xO>~?oW-_ԫ^Q׉~K_,u2%&HYJYɯ(us+9eiρI%XcWn0|R~QmEcTMd*V*1/p9M~S.kj2<$7a wZt5ѯ*fenKo2O7iChϮ]Rf3kts{-2E{ ~_wifg\Z>=g[wJ9cf][>.Y%ɩ{ݛ}<;xOԉM|o}Vч;2W}7;څEgQM"uhE`wN 2$GS-1Zvbeslx;u\k~B^pW];HvQ.oumJLG$Gd7qFx|I؞6W5*δ97om:S<5q#l6ߔ6B0qŞs V8E=?-o hh& Bg{3IlM} ;S\(r9Aװ|kߺwǬ13oz"WٷyG5bF1o{̜c6_37>Y]rrs\'h)^;3fafkt >ɦ=gI,1sjb .t z$25| ?ߏ`e?ҬOYr?UW Q_[&Еt&+?lX@=KFt=t.T֖C*NшX]ȆhˀM9 hpN1ٟ0Gt%'fC}T3k>Xs.݃/4qhLm|1h-Ԇ1mC] L\7v7n"AlxPw?mP!=76[%kϯ`.X{1v}qӰD sAއ| ʵ+;N N2(.(zP\ ) 5?R!ݍ;Wt}je5ça֧ׯA EҸ@.F9DVfXv;Q2]Tµa! 5FӯN~D7zFݬ^FEƾ_4.*6T ko> )e:5yƖz>huAµ6Op'_iJͲ5c?jI-֫<Nk~pS&?[5NTL4wpes]0E%\d(Na ⸥k\W%9ܟLUVu}S*A4j[097te]cfN'+a, WTպUwfX4!z-7}1ozSM$D0ozHxB`UE1OQx:r$rҏŪ&WN9ebvHb~49ap_L/oxX0?.1;,51S7[pfbu,S1Ucy,/Pt:ċM K}N Q{,s^rfB#l5 id{4 HIvo&:_$em{}m[~"wspu"?@l9Ȣi]ő*vN`QC'cӅ 8X4 # Hd-%-a6it7iFFs8qʚyM)H{Ry/f{Ao{_?ßo72J!Wjzr1s9#,8&*s .zjԐ2WȎr9%:XHc,]3W1V]7}残=0: \r]ZzޣE[GЀmp>uaR==Zt%wC1Anq[R Z5kV]t ok {2[)Ŷ }w޼;.5 _?-w=,!r_ϼ~Nn}EH:O]}M/* \Õ_>%sOW;W%fL]qfҀɆXa )#x'N2{?0eW_)a8VB:O"^_BE뀼޾׬ȥ /};|rnj*$xG *{-GL de11o k vAbc>eyߎ?k y[>tlVboʯ~zEeƸk l[Qm>(Em ~3Nmvha{G}e 2V뽺]nKBuڠ`oS4}r[5I > wJxҜxnp.+Ċ+EoH5Øu~aV ӯ?Quezfw{׿l2t! fwupd-1.2.14/plugins/unifying/data/dump.tdc000066400000000000000000025021401402665037500206230ustar00rootroot00000000000000TPDC?7IX !!   ,\% X7IX| Of l @`.   `  9!-e -`-  > @`8%` Q8  ld3@@@=O2`@v`v@k-X&`'jk #7  b8Z= k`=!uA"@7` m uu `= uuр7s E1 u "a =j Wuu7#4aB@u>ruA@-k W `;= k 3@urC`;=h uB@u!>`;=,u Wu 3@u#C`;=sK+ @c@@~?i`;= k 3@uul u`;=, u 3@u-C`;=9.u 3@u#v +i'E. A ;F`=1A5@G = d @3K @ @7?@@`6@@`@B! % C ,@7J>J J `J E-F +@B{F @' @x@A FP`CE ER@aR @[=H = ` RJ! @R@ @l `6@@ awEK )AQ" + II Q`IQQ# >@B^ QQ@x@A AQ$`Ej6@Q@=% +@@4Lr@BH/{!' G@@ " @arA(I I `IiqB) B@a Bl@@x@A A* #+@㷀@ " @$5@B, GB ! Bi- +@a B@U  @x@AB^`En7@ EAkc/ @p@4:/8@J A0 .@-@,A1JJ  `A2 "@@By@`8@B @x@A AR3`'E ER@aR4 @=R#5@?@ " @eA NB6II QA7 B@B_ b a[Q@x@A AQ,``E9@ Q,@=-`@4+@`y@"A ($: @ ={%$!;@x@ @ (N A<I٣I $4 IwA= B@`BA@x@A A.R$%A?@@ " @ɥA@B$A!`a BA"A@x@AB^B @`E ]:@A@! {=S;`=J#J! D <@@,AEJJ A*`@B@x@A AR` `EԀ 0!H :؀=`SI@B׀@p@S@"!+@#րI R`SA!`a BaA@x@A AQLDE<@N QeAM +@@4*E@`@AN A;=h{@=!$ O )@ݐ@o@~@ API@J#J#^X .@ @,AYJVJ  `Z +@@B@x@A AR[`CE4   aR\ &ƀ="A]@qŀ@k@S"!A1B^IĀI A_ B@QB@x@A AQ``E}?@QeAa +@@4Jb ;A;=O{- Ac@ʀ@ @ }AdI+IXAe "@`B@x@A EAY$%Ag G@@ Y" @$5@Bh Bg BAi B@a BA@x@ABj`CEB9@@Nؠ$on ;ck +\d`=! l@܀@,AmJ*J  dCAn "@@BA@x@A ARo`CEe@NRA@<@\BA~ K@x@AA\h `E0Fa\*BCa\@n=\( {@@@V!\ @"@B B;BBJv aB{@UJ5"@'@x@@AJ|N @"GaJ@E DDE=m=`=$v~>[`v` 8$8Xn8_~a 9! =&r`v! <@$@'%B J`J M  +@B_B@x@A B`CE/߀b +aR @=R#M@n@ #M @ /@D3!@B Ϡ B B@BJ @x@AAJ @`Esb_Q J@-aJ P`=HH(! @@@3 @ HARJJ J +@a B|B&d @x@A AR`CEPaaR 9R*T=NH@EQ@H@HT \/ B@\BC%@x@AA\`E a\ſa\ =\Z =#@ @~!\" B)BS B@aBx@J@x@AAJ`CEǀ|aJ 9@pla! #@ʃ@J #AJ6J  B@B~ dR@x@A AR .@`E!>a aR ;RD=R#dR2@^?@5" @1 @DK "".1 /C !" ҿAj 0  B@a$B  @x@A$Aj @`Ejaj ;=j@`E<,a JaJ ;@p`==%U@@!U#dAJ_J UBP@x@A AREIaR UaR =R=#U!z@@@S" @+:` K  xAN @3  B@B "9@x@AAN @`E^aNaN ;'`=N#N#@@!N @ BB B@aB@x@AAJe `EՀBaJ :JFaJ"qX+! J# )@@ "AJ J  B@a B B@x@A AR`CELa aR 9 R=#R=i <@'N@G)" @/ @! KUSB6mBo tL a<e9J8Ah (`M`@ B@BLHZ@x@A"Ah @`Erahah ;ŀ=h$@5@u'!h @ BĀ B B@aB@x@AAJ`CEaJQaJ 9J=`="@B<@!D _4AJ;J  +@RBy@x@A AR`CEDaRR= RcA@-@  B B@BG @x@A`Abp.=R{``>O k-kS`k@: =0 0` ka - -z:a!T A  `=؟* 5 .@uր@!. ;sJՠJ . +@Bz.@x@A B*`CEaR 8aR @ܔ=&.{@@6sT\T\ F B@BB)@x@AA\ @`EGL\a\ ;\N= ')@wM@ )B ) B@aBz@٣Bs@x@AAJ`CEaJ)aJ 9@p(ƀ=(3+0@Ā@!# AJàJ  +@B@x@A AR`CE~aR aR 9Rہ=RL@@@S" @f smAS @8wKc `@`  K u &&V Ac4 X [l|[ c `[P5@? cB  @x@2 =Ac6 @`EF7`ac7 = == >8 = @ @  b"AJ9 ? Bu BB&r:@? aBh@x8 9AAJ; @`ETaJ(e/r5FX< = =+ J! = = @ǀ@X %#<fX> G J`J P R? , a BX@x@A AR@``E{aR`.66aRA  =K=T.LB = @@ bL 8BC _ bb j`D@? a$B| q@x@A$AjEE<j77+F=%b\d!I2@ " = A{!H = @8=@1~4`@!9g yA.TԌhv/G?qI _ bb% iXWJ@? aB !c@x@A$A,K E ED,+L = @uC@ " @$g@BM ? BBB ! BN@? a B@q@x@ABO@/ `E89d P = =b! Q = @@ "w$asR G J{J sS@? a Bk¢@x@A ARTEna s::aRU = 8v=R#V = @q@s0;%jBW _ bb jsX@? jBy s@x@A$AjYE*j;;ajZ = -=j#$[ = @@ \ ? B`, B] ? JB !K@x@AAJ^  E倈b N@,<Qf_=$F$=&` = {M*a& `@3Ba @H@ ! @bAb _ bLb b `Lc@? abB !@x@A$AdE7+a,RSbe = jÀ=j%pj%f = @@,g G J6J 4th@? aRB@x@A ARiEEz,aR`.TTaRj  =R%k = @U}@`;k%l _ b|bjm@? jB@x@A$AjnE5-aj jUUajo = [9="p = @8@ $(%"Dq ? BBJr@? JB@x@AAJsERVket=ӤFu = WY=a% F"v = @T@: ) @BaAw _ bSb b륱 aB@x@A$Ay E>a, lmbz =$π=j { = @̀@,QJ̠J \}@? a B@x@A AR~E?aR FnnaR = =" = @@`;dB _ bhb @? jB@x@A$AjEzA@jooaj = D=%p(+c = @W@  ? BC,5 JB@x@AAJcs Epe@ =| = =ePa% % = @I`@ ! @bbAAOb_b b @? a$B@x@A$AEQa,>b = ڀ=^(f = @ـ@b=M'%IB>" G JؠJ J , aRB @x@A AR@CERaR aR = H=! = @@`;EiB _ bb j@? jBz !@x@A$AjE)MSjaj = P=j = @@ $( @"AJ ? BaO Bc@? JBC!K@x@AAJETaJe=1| = pc`{%  = @k@ $F @BۣA _ b]b bc@? aB@x@A$AED&da,R = z=" = @@h5' G JGJ J aRB@x@A AR@ `EQeaR aR = ==R(o = @U@`;B _ bb j@? a$Bc@x@A$AjEXfjaj = X\=(%p(+c = @[@ r ? B J@? B@C@x@AAJ@/ E_gaJe=F |{`|v`{% "@w@ ! @b:}bb b B@`B !@x@A$AE1wa,b = =j(f = @y@ m' @c G JJ @? aRB@x@A ARExaR aR = =" = @@ `;oD _ bdb j@? jB@x@A$AjEdyjaj = h= = @sg@$ ? BfB J@? JBc@x@AAJE zaJe=2| = `{%  = @U@ $F @BB _ bb b @? aB@x@A$AE=a,yb = =j%pj% = @,@, G JJ F@? aRB@x@A AREaR aR = T=R(o = @@ `;lB _ b#b j@? jB@x@A$AjE6paj2 jaj = s=j = @@~ `@"AJ ? Bvr J@? JB@c@x@AAJ@/ E+aJe==>| = {`{% F" = @@c"{A _ bfb ! @? abB@x@A$AEPIa,b = v `=" = @@ m'%c G JCJ @? aRB@x@A ARE^@XA FaR = Ȁ=! = @fÀ@`;ĭB _ b b ĩ@? jB@x@A$AjE{b jaj = U= = @~@~ ? BB [@? JB@o/l@x@AAJ@/ Ek7aJ@==Fc = {i`{%  = @@ $F @c"ZgB _ bb @? abB@x@A$AjETa, V = )`=j^% = @@, G JJ @? aRB@x@A ARE ̀ F  aR = Ӏ=R(o = @π@`;$y!`&'}Πb@6#$E`Bx oy@x@A$AjEb j  aj = ɠ=%-(6@w@+B׉BB$  B@JB@x@AAJECaJ !e=UF = `{%p" @e@ ! @biA båb bO B@aBy@x@A$A7 E`a,"#b  >`= `= @A@,JJ F B@B@x@A ARE F$$aR =RM߀=R"@ڀ@`|bb  B@jB@x@A$AjEBb j%%aj =jҖ=%-(%@.@ $( @K BB B@JB@x@AAJENaJ&;e=JaF =ζ`{% "@@ ' @B brb b륱 B@aB@x@A$A!E]la,y<=b" =j,`=^"#@*@,$J\JA#% B@aRBh@x@A AR&Ej"j>>aR' =R= (@j@`|eN)bb@6j* B@jB@x@A$Aj+Ebj??aj, =j}=j%p(%n-@ء@ .B9Bc/ B@JB@x@AAJ0ExZaJ@Ue1=F2 =`{% %3@½@ B! @bA4b!b ! b 5 B@aB W.X,@x@A$A6E xa,VWb7 =j)8`="8@6@,9J5J : B@aRB@x@A AR;E XXaR< =R=R"=@1@`|D>bb@6? B@jB$@x@A$Aj@Eb jYYajA =j(=( B@@~ $( @(DCBB$BD B@JB@$@x@AAJEE'faJZoeF=F,G =@(`{% H@qɀ@"!IbȀb ! cJ B@`B@x@A$AKEa,pqbL =jC`=^"M@NB@, NJAJ O B@aRB@x@A ARPE FrraRQ =R}$R%R@~ o+o`|ySbHb@6jcT B@jB@x@A$AjUEO, jssajV =jӹ=(%-(+cW@.@ rXB$ yY B@JB@@x@AAJZEqbwte[=WF\ =@, % "]@ Հ@ ! @;m^bԀb bco `B@x@A$A` @Eja,ba = ?`=zO`=$j%b@M@ m2 @,cJHJ d B@B@x@A AReEwa FaRf =R=R"g@ @`|hbb@6#i B@jB@x@A$AjjE jajk =jzŀ=j%-%l@Ā@$mB:B Jn B@JB@@x@AAJoE}bBep=Fq =@!`{% "r@@c" sb.b t B@`Bc@x@A$AuE"a,ybv =jA[#`="cw@Y@ mxJJ kM aRB`B/@x@A ARe `E&$a FY+{=&|@.@ N}bb@8'~ B@a$B@x@A$AjÈ2 jb =@퀃5р=j @Ѐ@ ) @(EBB B@B@x@AAJE3%bce=F =94`{% @}@ ! @c"z6bbj bc B@aBc@x@A$AEǦ5a,* b =jf6`="@Ze@,JdJA#F B@aRB@x@A ARE7a FaR =R%=R(o@ @`|bQb@6j B@jB{ o@x@A$AjE[٠@Y jaj =j܀=j%p(+c@>@ $( @ťBۀ B B@JB @x@AAJE8be=cF = ÀG`{% "@,@ ) @B[!bbj B@`B@x@A$AEvHa,cb =jrI`="@p@,JeJ R B@aRB@x@A ARE)Ja FaR =R41=R"@,@ `|$Dbbj B@jB@x@A$AjE @YY i 8aj =@=%p(%@@ BFBJ B@Bc@x@AAJEKbe=F =[`{%p"@@ ! @brAb:b bc B@aB@x@A$AE%b =jE~\aj^%@|@,JJA# B@aRBPB@x@A ARE25]a FaR =R<= @?8@"`|yBb7b  B@jB@x@A$AjEjaj =jI=j @@~r `@"AJBB B@JB@x@AAJE@^b @==F, =In`{% @@  c"Abb ! 륱 B@aB@x@A$AjEɀ Z^`.   A joaj"@c@,JχJ > B@aRB@x@A ARE@pa@Y R!8 8aR =@H=R(o@C@$`|ezBbUb@6j B@B@x@A$AjEh@YAXaj =@=( @F@ BJ$ , B@B@١B2a@x@AAJEqbc%e=pʄF =@`{% @8@ ! @c")bb b, B@`B@x@A$AEՀ&'b =jj(f@@,JJ  B@aRB@x@A ARELa F((aR =RDT="@O@&`|DEbb@6j B@jB@x@A$AjEj))aj =j =(%-(1@ @ r$( @"@BBOB J B@JB@@x@AAJEÀc*?e=քF =@+a% (@&@  c"qAbKb 륱 B@`B !R@x@A$AE2@Ab =jYj @@ !j @J$J  B@aRB}@@x@A AR@/ `E?Xa BBaR =R_=!@O[@(`||BbZb@6' B@a$B@x@A$AjEjCCaj =jR=j%pj%@@$B J B@JB@x@AAJEMπDYe O`ΤF =V7a% %@2@c";Ab1b  B@a$Bc@x@A$AE쀈Z[b =jaj(f @g@, JӪJA#F B@aRB|#B@x@A AR Eca\\aR =Rk= @f@*`|Bb^b@6j B@jB@x@A$AjEuaj ]]aj =j"=j @R@~ЏcB! B B@JB@x@AAJEڀ^se=Fc =Ca% @H>@ ) @BwBb=` ! b  B@aB@x@ =A @Etub =jø`"@"@, JJ F! B@a B@x@A AR"EoavvaR# =RHw=R(o$@r@,`|B%bb@6j& B@jBL6@x@A$Aj'E$+ajF wwaj( =j.=j )@@~*Bp- B$ + B@JB@x@AAJ,E怈xe-=+a. =N`="/@I@  bztB0bSb ! b1 B@aB@x@A$A2E>a,,b3 =jiĀ=j+c4@€@,5J5JA#F6 B@aRB@x@A AR7EL{aR@Y FaR8 =R=FR%9@\~@v @oK0.`|B:b}b j; B@jB~  @x@A$Aj<E6aj jaj= =jV:=%-1>@9@ nc?BB$ @ B@JBc@x@AAJAEYeB=ڤFC =_Za% "D@U@ `@B[AEbb F B@aBc@x@A$AGEa,cbH =j$Ѐ=j$j%I@΀@,JJ͠JA#(K B@aRB{@x@A ARLEaRcaRM =R=FR"N@@c0`|%qBObsb P B@jBc@x@A$AjQEBaj ajR =jE=j%-%S@Z@~rTBD BU B@JB@x@AAJVEeW=|FX =fa% "Y@Ta@ ! @bmZb`b b [ B@aB@x@A$A\Ea,b] =jۀ="^@/ڀ@,_J٠JA#` B@aRB@x@A ARaEaRaRb =R\=R"c@@2`|db)b@6je B@jB@x@A$AjfE0Naj ajg =jQ=( h@@ $( @(EiB|Pj B@JB@,@x@AAJkE aJcel=<|m =@q$% n@l@c"zAob`b cp B@`B,@x@A$AqEK'a,br =q=j"s@@,tJ>J cu B@aRB@x@A ARvEXaR FaRw =R="x@X@4`|dNybĠb@6#z B@jB@x@A$>!EY aj@Y jaj| =jo]=%p(+c}@\@ r~B+B J B@JBc@x@AAJEf aJc@==F =}`{% (@x@"yAbb B@aBc@x@A$AjE2a, b =j(=^%@@ 2%JJA#`  B@aRB@x@A AREaRcaR =R=!@ @6`|ErBbwb@6a`j B@jB@x@A$AjEejaj =ji=j @jh@ BgB B@JB@x@AAJE!aJc`==3|@ =,`{% @\@ $F @B!bb ! b B@aB@x@A$AE>-a,c A =j="@G@,JJA#F B@aRB@x@A ARE.aR AR =RL=R(o@@8`|ybb@6j B@jBF@x@A$AjE=q/jAj =jt=( @)@$cBs J$ #w` 6iy@ JB@x@AAJE,0aJ5n)`==E?| =ɔ?`{% @ @c":mbmb y B@aB@x@A$A EWJ@a,,*+B jq A`=j+c@@,J>J@F B@a B@x@A AREe@Y ,48' =R ɀ="@mĀ@:`|bàb@6 B@jB@x@A$AjE|Bb2 j- &!j =j\=%-(1@@~cBB J B@JB@@x@AAJEr8C!#c.!@==F =@tR`{% (@@" bb ! B@`B  !@x@A$AjEVSa,DEB =j8T`=^%@@ m,%JJ  B@aRBz,@x@A ARE͠@YA FFFAR =RԀ=!@ Ѐ@<`|w#bπbĩ B@jB @x@A$AjEUb jGB% = #= @~@~B㊀B c B@B@٢o!Ky@x@AAJE!DVaJH]`==F =@e`{% @d@ ! @b{Bbʦb  B@`B !@x@A$AEafa,^_B =j!g`=^%@D @,JJ  B@aRB@x@A ARE F``AR =Rd=R(o@ۀ@c>`|ĶBb3b@6# B@jB]L@x@A$AjEIhb jaaAj =j֗=j%-(&@1@ n$( @n"@BBn$B B@JB@x@AAJEOiaJbw`==QbF]L =x`{%p"@@ ) @BZ|Abyb b B@aB@x@A$AEdmya,cxyB =j-z`= @+@,JWJA#F B@aRB@x@A AREr FzzAR =R=R"@~@@`|$Bbbf B@jB@x@A$AjE{b j{{Aj =j|=%-(%@آ@ B8BJ B@JB@x@AAJE[|aJ|`==nFË`{% "@ʾ@ ! @bDAb(b b  B@aB`x@9 A @Eya,B =!=,9`=j^(f@7@,J6J  B@a B@x@A AR E FAR =R=R" @%@B`|OB bb  B@jB@x@A$AjEb jAj =j7=j @@~n$( @"AJBB B@JB@x@AAJE.gaJ; `==Fy =3Ϟ`{% @xʞ`@c"Abɀb ! c B@aB@x@A$AE„`,B =jD`="@EC@,JBJA#c B@aRBF@x@A >E FAR =R|$R% @~ $F'EoD`|eLB! LGb@6j" B@jB|  @x@A$Aj#EV, jAj$ =Һ=(%p(+c%@1@~&B B$ _ JB@!K@x@$ AJ( @Erbw`=)=^FF* =@{ڱ`{% "+@'ր@ " @A,bՀb bc- B@`B@x@A$A.Eqa,B/ =jP`=j$j%0@N@ 52 @y1JXJA#2 B@aRB@x@A AR3E~a FAR4 =R="5@~ @F`|DB6b b@6j7 B@jB@x@A$Aj8E jAj9 =jƀ=%-%:@ŀ@ r$( @"@B;BEB J< B@JB @x@AAJ=E~bB`=>= F* ? =@`{% "@@@ $F @BGAAb9b b륱B B@`B !y@x@A$ACE a,BD =jD\`=j%pj"E@Z@ m!j @FJJ  aRB@x@A ARH @`E-a FARI =R=R"J@5@H`|JBKbb@6jL B@a$B@x@A$AjME jAjN =j@Ҁ=j%p%O@р@$PBЀB JQ B@JB@x@AAJRE;bc`=S=FT = À@`{% "U@@c";AVbb W B@`Bc@x@A$AXEϧa,BY = `h`=%Z@if@,[JeJ F\ B@Bc@x@A AR]EaAR^ =R&=&_@!@J`|B`b`b a B@jB@x@A$AjbEcڀjAjc = ݀=j d@M@ ceB܀ Bf B@B@x@AAJgEbB@=h=kF/i =`{% j@3@ ) @c"ABkbb l B@aB@x@A$AjmE}a,yBn =js`="o@ r@,pJxqJA#Fq B@aRB@x@A ARrE*a aRs =R;2=R(ot@-@L`|Bubb@6jcv B@jB@@x@A$AjwE jajx =j=( y@@~zBVB J{ B@JB@x@AAJ|Ebc-e}=FW~ = `{% @@ ': @bzBBbBb ! b c B@aB@x@A$AE,./b =jXj+c@}@,J'JA#F B@aRB@x@A ARE:6.@T F00aREZR==R% ~@29@@V" @N`;IBb8b@6j B@jB@x@@Aj `E j11aj )E= @@ !j @"AJBB By B@aB@5y@x@AAJEGb2Ge=ȤF =@M`{% @@c"[Abb !  B@`B@x@A$AEʀHIb =j j$j"@n@,JڈJA# @ B@aRBW@x@A AREAa FJJaR =RI=R%@D@D$F @P`|%yHeb@6jcM jBz ,@x@A$Aj @`Eo@ jKKaj =a%p6@M@~cB B$ , B@aBW@x@AAJE Lae=w˄FQ = $a% "@;@bcE" @mbb ! b, B@aBy@x@A$AEրbcb =j–%j$j%@!@,JJ  B@aRBF@x@A AREM&addRh =R?U=R"@P@R`|b b  B@jB@x@A$AjE 'aj eeaj =  ="@@ $(n"EB^ J$B B@B@x@AAJEĀf{@==&ׄF =!?,7a% F"@'@G " bNb B@aBc@x@A$AjE9|}b =jl8j^"@̠@,J8JA# B@aRB@x@A AREFY9a~~aR =R`=R"@N\@T`|dNb[b@6j B@jB@x@A$AjE:jaj =ja=j%-(+c@@ BB B@JB@x@AAJETЀe=դFy =]8Ja% "@3@LF! @c"OAb2b ! b B@aB@@x@A$AE퀈,J =jKaj%@_@,J˫J c B@aRB/@x@A AREdLa aR =Rl=R"@g@V`|Ebeb@6 B@jBc@x@A$AjE| Mjaj =j$=( @a#@ $( @"DB"B$Bc B@JB@x@AAJE܀e=Ja* = ÀD]`=% @K?@  c"!b>b !  B@`B@x@A$AEE =jɹ^j"@*@ 5!j @ JJ F B@aRB@x@A AREp_a aR =RGx="@s@X`|ybb@6ja| B@jB@x@A$AjE+,`jaj =j/=(%-(+c@@ rBg. J$ J B@JB@8y@x@AAJE瀈e=3F =@Opa% (@J@ ! @c":mb[b b B@`B !B@x@A$A@/ EFqa,b =jpŀ=j%pj%@À@ mJ B@`B@x@A$Aj?ER(a,cb@ =j|=j#j"A@@,BJIJ FC B@aRB@x@A ARDE`aR FaRE =R=R"F@h@``|%\BGbԡb@6jH B@jB@x@A$AjIEZjajJ =jo^="K@]@ LB*BM B@JBc@x@AAJNEmaJ1eO=FP =~`{% F"Q@y@c"BRbb ! S B@aB@x@A$ATE4a,23bU =j'=j V@@,WJJ FX B@aRB* @x@A ARYEaR 44aRZ =R=R"[@ @b`|B\bwb[] B@jB; @x@A$Aj^Efaj j55aj_ =jj=+"(+c`@yi@ aBhBJ$ Fb B@JB@:y@x@AAJcE"aJ6Ked=Fe =@!`{% "f@g@c"WAgbńb h B@`Bc@x@A$AiE?a,LMbj =j=j k@G@ m, @,lJJA#m B@aRB@x@A ARnEaR FNNaRo =RS="p@@d`|dBqb"b r B@jB@x@A$AjsEDrjOOajt =ju=%-(%u@(@ vBt,w B@JBc@x@AAJaz E-aJPeem =L@|^pЕ`{% %{!@@ ! @bTA|btb b } B@a$B@x@A$A| E_Ka,,fgb ) `=^1@ @,JRJ  B@a B$X@x@A AREl  hhaR =R ʀ=&@lŀ@f`|E_BbĠb@6f B@jBcB@@x@A$AjE}b jiiaj =j{=j @ր@~$( @"AJB7Bc B@JB@x@AAJEz9aJyje=KF5n =`{% @Ȝ@M) @BەAb'b ! bc B@aB@x@A$AEWa,0Ab =jI`="@@,JJ >c B@aRBc@x@A ARE)EaRA@aR =@L= @5H@A $F @ K u% +EBbGbj B@BF@x@A$AjZ `Ejdf3 j2?u {#fT@@ yA.TԌhv/G?eAKb f B@bB@!$  E@x@A$A, /&,,@,@$B B! s B@a B ! @x@ABE6d AzJZ|bw#J(( ~@z@ & @J)J s B@RBx#B@x@A ARED3a saR =:=R-@<6@.$ aRs%s  ( Cb5b js B@jB~@s@x@A$Aj@/ `E jes=Ңw!~sVD%s = ){{ s@@, ~" @ s#Abfb $f bf> B@bBhW!@x@A$A, E E,$(%s@X@ " @$g@BBo! Bs B@a BY@x@ABEQd sd =J{j`=J%2J! @h@,sJHJA#s B@RB@x@A ARE^!a aR =R)=R%s@_$@ Qd0`zxb#b s B@jBQs@x@A$AjE܀jaj =jm="|@߀@ B)B B@JB@x@AAJEl/@T f=F{. My & =\{&F"@@3! @,2 2Tbb b B@aBm"@x@A$>ESaKb =j`=j @e@,JJ  B@aRBb@x@A ARE aR =RҀ=e6 R"@΀@C@"!+% Enter ICPuaMHobt̀b j B@jBT@x@A$AjEb jaj =j=)6()@k@$BψB . B@JB@!K.@x@AAJEBaJ Je=TFw =@H{% "@HC@ p \Ab  p B@`BP @x@A$AEcb =jaj$j+c@@ m!j @JJ! B@aRB@x@A AREta aR =RD|="@w@ ; u"a:Bbb@.# B@jBy@B@x@A$Aj@/ `E(0jdf=0 =6{#$f@e1@ Z @ZZfVAb f B@bBW!f@x@A$KE E,I8,&,'@7@P( @0$g@BB B  B@a B@! @x@ABEsd =@p۫b ! @>@ sJJ s B@BK@x@A AREb a saR =Rxj=&#@e@ @CuT"8 !=Bb5b js B@jB s@x@A$AjEC aj jaj =j!=j#j$@ @~Bo B B@JB@@x@AAJE Jf =KFz% =@߀{&}" @ۀ@ $F @7A bfڀb b% B@`BZ @x@A$AEQ bb =jU `="@S@,JPJ! B@aRB@x@A ARE^ a:faR =R=%p@r@ Sa pBbb  B@jB@x@A$AjE fdf=% =̀{#@ɀ@ f) @f,AZfAbȀb & b f B@aB@x@A$K% E,Ѐ, @oπ@ !, @Ht'_ BB! BB! B@a B@x@AB"Eld sd # =JC`="s$@A@,4v%J_JA#,s& B@RB@x@A AR'Ey aR( =R+$R(1)@~%[a B*bb@1js+ B@jB@x@A$Aj,E, jes-=s. ={F$s/@<@ asA0b $f 1 B@aBs@x@A$Es2 E,$,$,%03@@ ]rs4BༀB,  5 B@a Bs@x@AB6Eqcsd 7 = 1`=J&8@0@,9Jm/J s: B@B @x@A AR;E aR< =R4=%s=@@ sc9a C>bb@1s? B@jB{@x@A$Aj@Eb jesA=#rB =  {$F!C@Q@ s$F @as0Db f bftE B@aB'@x@A$EsF E,3,$,%sG@@$sHB,  sI B@a B@x@ABJE_bsd K =J`= %sL@4@,sMJJA#sN B@RB@x@A AROE aRP = Lހ=#Q@ـ@ [k*?  wCRbb jS B@B@x@A$AjTE6b jesU=>`V =.{$F!W@q@ [ asAXb f fsY B@aB@x@A$EsZ E,^,$,%s[@@& @$g@B\BB+Bs] B@a B@x@AB^EMbd _ =J `="s`@C @,saJ J sb B@RB? @x@A ARcE aRd =R}̀=R#e@ǀ@ sm+)7fbBb jsg B@jB@x@A$AjhEQb jaji =jՃ="j@2@~kB Jl B@JB@x@AAJmE;aJ Jfn=XNF}o =A{&"p@=@ % @2 A[qbsv@x@A$AsE^bt =j~aj&u@ᵀ@,vJMJ w B@aRB@x@A ARxElna aRy =Rv=R"z@|q@ Ի"P"aGHo{bpb½| B@jBz 4@x@A$Aj}E)aj jaj~ =jf-="@,@ $(w"AJB"BJ B B@JB@x@AAJEy Je=F =e{% @@ p 0Abb B@aB@x@A$AEbb =j8a`=^"@_@,JJA#c B@aRB@x@A ARE  a aR =R=R%@@ "E" aBbb½ B@jB@x@A$AjE jaj =j ׀=(%p(/l@jր@ rBՀB J B@JB@& @x@AAJE!b Je=Fyc =@ {% "@W@ @c P6b ! b B@`B@x@A$AEJ"acb =j #`=j c@( @ m' @ JJ  `JR- B@aRB@x@A ARE aR =R_ɀ="@Ā@ ):Kb%agDb'b j B@jB@x@A$AjE6}$baj =jʀ=j(%@$@B B$ c B@JB!y@x@AAJE8%aJ e==KF~ =>{F% @ :@ p$F @ . bl9b! b B@aB@x@A$AECb = `k&aj%pj+c@β@,J:J B@B@x@A AREPk'a aR =Rr=FR(o@Qn@ "e")c:Dbmb½ B@jB @x@A$AjE&(jaj =jc*=%@)@ nBB J$ g B@JB@x@AAJE^ e=ߤFJ =J{% F"@@c Ab !  B@aB@x@A$AE)bb =j ^*`=j^%@o\@,J[JA#c B@aRB@x@A ARE+a aR =R=R"@@ k`1 )afbbb½ B@jB@x@A$AjEyЀ jaj =jԀ=j%-(%@`Ӏ@$BҀBJ$  B@JB@x@AAJE,e=F = À{% "@=@ c jAbb !  B@`Bc@x@A$AEG-acb =j.`=%@ @,JyJA# B@aRBc@x@A ARE FaR =RTƀ="@@ "u/a^Bbb j B@jB@x@A$AjEz/b jaj =j}=%-(%@@~ncBf| B$  B@JB@x@AAJE50Je="HF. =;{% "@6@c hAb=b  B@aB 8p,@x@A$AE(cb =jM1a^%@@,JJ B@aRB@x@A ARE5h2a FaR =Ro=R"@Ek@ 33@ `cBbjbĩ B@jB.@x@A$AjE#3jaj =j<'=j%- @3A@&@ B%B B@JB@x@AAJ  EC e=ĤF =AT{?{% "@@ c qAb !  B@`B@x@A$AEɚ4b Z b = `Z5`="@TY@, JXJ Ry B@B@x@A AR E6aR@saR =@= @@ $F @K " @ibSbj B@B@x@A$AjE^̀ jaj =jЀ=j @A@~!j @(DBπ $Bc B@JB@x@AAJE7e=fF =Վ{F% @@ @! @ޘbbAy(! bc B@aB@x@A$AEkD8ab = `9`=j$j"@@,JfJ R B@B@x@A AR Ey FaR! =R.À=Rc"@@c3@P#bb½$ B@jBP @x@A$Aj%Ev:b jaj& =jz="'@y@ $(n"E(BCBJ$B) B@JB@J!K@x@AAJ*E2;J@=+=EF9, =@~8{% F"-@3@c A.b"b / B@`B@x@A$Aj0E -qb1 = `=j 2@ @ m, @3JyJ 4 B@B|LWJ@x@A AR5E<aR6 =RC="7@@y"Ø"asN8b b 9 B@jBJ@x@A$Aj:Ee=jaj; = h=%-(6<@@ =B^g > B@Bc@x@AAJ?E >Je@=F*A =!?&{% %B@!@ W @c חACb=b b D B@aB@x@A$AEE(܀bF =jK?aj%pj(fG@@,HJJ nI B@aRB@x@A ARJE5S@a aRK =RZ="L@AV@ #(%paBMbUbO.N B@jB @x@A$AjOEAjajP =j4= Q@@$RBB JS B@JB(#@x@AAJTECʀ eU=ĤFu V =3Ѐ{% F%W@ˀ@ ) @+c BXb ! b Y B@aB@x@A$AZEɅBbb[ =jEC`=j \@`D@,]JCJA#^ B@aRB@x@A AR_E aR` =oDaR"a@~ )!(o 38"%e Bbb;b@&jc B@jB/@@x@A$AjdE], jaje =j޻=j f@<@!j @(AJgB B$Bh B@JB@@x@AAJiEsEbwJej=eFk =@y{F% l@!u@ @}Ambtb! bn B@`B@x@A$AoEk/F/@Tcbp = 0`=j$j"q@@,rJ^J cs B@B@x@A ARtExGaR FaRu =R$=R(ov@@ CKW 8"3IwbbA;#x B@jB| !@x@A$AjyEaHjajz =je=j%-1{@d@ |BGB$ } B@JB@x@AAJ~EIJe=0 =j#{% "@@ c 5Ab"b !  B@aB "@x@A$AE ـb =jEJa @@,JJ F B@aRB@x@A AREPKa aR =RW=R"@.S@ S"ЃЂ@8BbRb@&j B@jB@x@A$AjE Ljaj =@݀5=j%-`3A@@ B B$  B@B@x@AAJE' e=F =̀{% "@dȀ@ * ! @c eAb c B@aBQ@x@A$AEMbb =jBN`="@EA@,J@JA#F B@aRB@x@A ARE aR =RhOaR @~ cos䓣Bb4b@(j B@jB@x@A$AjEB, jaj =j=j%-(+c@@~$( @nK@BBz B B@JB@),@x@AAJEpPbw Je=JF, =@v{%p%@r@ c ]Abeqb! c B@`Bc@x@A$AEP,Qa Z b =jy="@@ m!j$ }@ ! @ ARJGJ(R B@aRB@x@A ARE]RaRaR =R=R"@Y@ !$F!Ro sŃł@ a~bťb  B@jB @x@A$AjE^Sjaj =jxb=(%p(%@a@ !j @ťB4B$Bc B@JBc@x@AAJEkTaJA8@==Fc =@Og {% "@@ @ bb^! b륱 B@`B@x@A$AjEՀcA =jUa^"@t@,JJ {R B@aRB @x@A ARELVa  AR = T=R"@P@ ,e"Qpt b{Obj B@Bc@x@A$AjEWaj jaj =j =(%-(%@i @~B B J$  B@JB@x@AAJE Je=քFB =ɀ{% "@Bŀ@ c _`b !  B@aB@x@A$AEXbb =j?Y`=j)j%@>@,J=JA# @R B@aRBvLWB @x@A ARE aR =R>=R"@@ p tanDb b½ B@jBxc@x@A$AjE'Zb j  aj =j="@@ BsJ$ B@JB@x@AAJEm[J  e=/F =s{% F"@n@ ! @c dBbJb b B@aB .Xy@x@A$AE5)\  b =j-=j @-,@,J+J n B@aRBB@x@A ARE F  aR =Rs="@@ csth`ߊa`]bB4b(b j5 B@jB@x@A$Aj6E jaj7 =j(="8@@ 9BB$y: B@JB@٢o?2a3}@'@x@AAJ;E'hb Je<=F= =@@{% F">@a@ ! @J?b e! b @ B@`B@x@A$AAEXiabB =jj`=^%C@I@ b2$^DJJA#cE B@aRB@x@A ARFEπaRG =Re׀=R"H@Ҁ@  F @>oXEIb3bA;jJ B@jBsZ@x@A$AjKEBkb f  ajL =jŽ=(%-(1M@"@ r$( @"@BNBBiFO B@JB@@x@AAJPEFlJ!!eQ=JYFyR =@L{% "S@H@ W$F @6 ˪ATbeGb bcU B@`By@x@A$AVEOmac"#bW =jv€=^"X@@ m!jYJBJA#Z B@aRB@x@A AR[E]ynaR F$$aR\ =R=R"]@a|@  12 EajB^b{bf_ B@jBc@x@A$Aj`E4oaj2 j%%aja =jp8=(%-(%b@7@~cB,B J) d B@JB@x@AAJeEj J&&ef=F|3|=\ Ag =_{% "h@@@at @6Aibb! bj B@aB 'Z,@x@g =Ak @Epb ''bl =j=%pj%m@@,nJaJ@o B@a By@x@A ARpExgqR((aRq =R,o=R"r@j@cn_fMBsbibA;jt B@jBy@x@A$AjuE"rj))ajv =j{&=j%-%w@%@$xB7B$ y B@JB]L@x@AAJzEހ **e{=| =u{F% "}@߀@ ) @  ~b!b B@aB,@x@A$AE sbc+,b =j0Zt`=j^%@X@,JWJ F B@aRB@x@A AREua --aR =R=R"@@ck_Z1a8LDbb½ B@jB o@x@A$AjE j..aj =j,Ѐ=j(%@π@$B΀B J B@JBc@x@AAJE'v//e=F ={"@`@G Ab ! Zc B@aBc@x@A$AECwa01b =x`=j @(@,JJ B@aRB@x@A ARE F22aR =R[€=R"@@ c#NvBaBb'b@$ B@jB@x@A$AjEBvyb j33aj =jy=j @,@~Bx B$ c B@JB@x@AAJE1zJ44e=JDF =7{% c@3@c lbd2b  B@aB* @x@A$AEO퀈c56b =j|{aW @ޫ@,JJJA#Fc B@aRBc@x@A ARE]d|ay F77aR =R l=R%@mg@ 3ƀԀU)aUEbfb@$j B@jB@x@A$AjE}aj j88aj =jl#=%-(+c@"@~B+B B@JB@,,@x@AAJEj J99e=Fw =@V{% "@܀@ ': @+c QAbb ! b B@`B @x@A$AE~bc:;b =j"W`=^1@U@ m2 @,JTJ(> B@aRB@x@A ARE a <>e=F{ ={% @I@ $F @6 eb ^! bc B@aB@x@A$AE@ac?@b =j`="@%~,JJA# B@aRB@x@A ARE FAAaR =RI=R(o@@ SQ 䓣a<|ybb½ B@jBnb@x@A$AjE'sb jBBaj =jv=(%-(+c@u@~B[B J$  B@JB@x@AAJE.aJ JCCe=2A|c =4{% "@/@, |ybIb!  B@aB (,@x@A$AE4DEb =j^aj(f@@,J+J B@aRB@x@A AREBaa FFaR =Rh=R"@Jd@ cc = `ـ=j^%?@D؀@,@JנJ nA B@B@x@A ARBEaRXXaRC =R\=FR"D@@ N`͈$Pa#,Eb+b@$jF B@jBy@x@A$AjGEALaj YYajH =jO=j I@@~a$( @"DJBN B$BK B@JB@`{&@x@AAJLEJZZeM=I|xcN =@ {% O@ @ , 2Pbdb cQ B@`B$X@x@A$AREOÀ[\bS =ja"T@恀@ m,$UJRJ(>V B@aRB@x@A ARWE\:a F]]aRX =RA=R%Y@`=@  ##E#1sa DfZb=R%@@ `ү"Bbb½ B@jB@x@A$AjE&Iaj jhhaj =jL="@@ BfKJ$&5, B@JB@x@AAJEJiie=.| = {% F"@@ ! @6 ]BbIb by B@aBy@x@A$AE4jkb =jbaj @~@,; J/J  B@aRB@x@A AREA7a FllaR =R>="@M:@ "!kZ兺afgBb9b B@jB@x@A$AjE jmmaj =jH=%-(C@@ r!j @(@BBB J B@JB@@x@AAJEOb Jnne=ФF =@G{% %@@  c •Ab ! 륱 B@`B@@x@A$AEiacopb =j*`=j$j"@d(@ m!j @J'JA# B@aRB @x@A ARE qqaR =R="@@ ,`_aBbOb j B@jB @x@A$AjEjbfrraj =j=j%p%@S@~B B$ c B@JB@@x@AAJEWaJ sse=qjFQ =@]{F% %@,Y@ ! @ =$AbXb! bc B@`B@x@A$AEwttb = `=%pj%@w@,JJ  B@B @x@A ARE uuaR =Rր=R"@р@dWhb^b j B@jB5n@x@A$AjEvvaj =j=%-%@o@ $( @"@BBԌB$B B@JB@@x@AAJE FJwwe=tյ =@K{% "@HG@ ) @ hb  B@`B W'Z@x@A$AEaxyb =j=^"@ @ m!j @cJyJ  B@aRB@x@A ARExaR zzaR =RH=!@{@c` x|KdadDfbb½ B@jB@x@A$AjE&4j{{aj =j7=j%-j%@ @ Bn6 B B@JB@x@AAJE ||e=.|, ={%p%@@, LkbIb c B@aBc@x@A$AE3b Z }~b =jmk`="@i@,J:J F B@aRB@x@A AREA"a(aR =R *=R"@]%@#u`aDb$b j B@jB oB@x@A$AjE AXaj =@T=( @@ BB B@B@x@AAJENb Je=ФF; =?{% @@ * ) @c !cBb ^! b B@aB@x@A$AETab =j`=j+c@`@,JJA#{R B@aRB@x@A AREˀ aR RӀ=R%@΀@ c3ү"K[@T BbOb jjB`x |A$Aj@ aib jaj = >`=튀="@I@rBB$&5 B@B@x@AAJ EBJe =qUFc =H{%  @*D@ c iB bCb  B@aB@x@A$AEw b = `aj @o@,JJ  B@By@x@A ARE RaR =R="@@ cCp ӔP aBbnb  B@jB,@x@A$AjEub jaj =j y=%-(1@jx@ BwBµ B@JB@x@AAJE 1Je=F* =7{% (!@G2@, "b  # B@aB,@x@A$A$E1 b% =j<=j &@@,'J J R( B@aRBc@x@A AR)EaR* =Rͯ=R"+@$@ S~.KlaeD,bb j- B@jBc@x@A$Aj.Ecjaj/ =j#g=j 0@~f@ 1BeB2 B@JB@x@AAJ3E&Je4=.y5 =%{% 6@\ @ ! @4 B7b ^! b 8 B@aBc@x@A$A9Eڀyb: =jޚa1;@?@,<JJA#= B@aRB@x@A AR>EQa aR? =RkY=%@@T@ c+l+lwBAb2b½B B@jBy@x@A$AjCEA jajD =j=j E@(@ $( @"AJFB B$BG B@JB@x@AAJHE eI=IۄF{$XJ =΀{% K@ʀ@  jALbcɀb M B@aBQ@x@A$ANENbO =j눀="P@J@,QJJA#R B@aRB@x@A ARSE?RaRT =RG=R%U@B@ $F%osq 1laφBVbUb jW B@jBc@x@A$AjXE\ fajY =j=(%-(1Z@I@~ƹ!j @ť[B J\ B@JB@x@AAJ]Ee^=F_ =׼{% "`@@t ktAab~b 륱b B@aB ?n@x@A$AcEiraybd =j2`=j$j"e@0@,fJ\J g B@aRB@x@A ARhEw FaRi =R.=R"j@@ p"xa!dkbbĩl B@jB@x@A$AjmEb jajn =j="o@㧀@ pBABJq B@JB@x@AAJrE`Jes=sFBt =xf{% F"u@a@ , 'Lvb b w B@aB@x@A$AxE aby ='܀=^%z@ڀ@ ,% {J٠J | B@aRB`@x@A AR}EaRaR~ =RԚ=R"@@o!Rc~uDk*[bb  B@jB @x@A$AjENaj aj =jR=(+"(!@yQ@ rBPB B@JB@x@AAJE& aJ Je=F@ =.{% "@b @ {Ab  B@aB S"c@x@A$AEŀb =jԅaj$j%@7@,cJJ y B@aRBB@x@A ARE B@aRB{ ]L@x@A ARE[Ѡ DE aR =Rـ=R"@sԀ@ 5n2K%@77%; UBbӀb' B@jB@x@A$Aj  `Eb jaj =jn=(%-(%@ˏ@ rB*BJ B@aB@x` =AJ @EiHJe=FF =]N{% "@I@ 5n! @c5A bb b B@abB ?n,@x@A$A Eab =j#Ā=^% @€@,JJ ( B@aRB @x@A AREzaR FaR =R=R"@~@ 1d3`2% bm}bA; B@jBz ,@x@A$AjE6aj2 jaj = 9=( @M@~G$( @"DB8 J B@B@x@AAJE Je=|y ={% @F@c b ! c B@aB]L@x@A$A Ebb! =jm`= "@l@,#JkJ F$ B@aRB@x@A AR%E$a aR& =Rf,=R%'@'@ N$F @o EDD(bb ) B@jB@l@x@A$Aj*E%jaj+ =j=%p(+c,@@ !j @ť-Ba B. B@JB@x@AAJ/Ee0=-FB1 ={% "2@蜀@  3bHb 륱4 B@aB "c@x@A$A5E3Wab6 =jJ`=j$j"7@@,B8JJ R9 B@aRBy@x@A AR:E@΀ aR; =RՀ="<@Hр@ c# u%`mn!=bРb@"na|> B@jB{ @x@A$Aj?Eljb jaj@ =jC=%-%A@@$BBBC B@JBB@x@AAJDENEaJ JeE=WFBF =:K{% %G@F@, t Hb  I B@aB@x@A$AJEacbK =j=j L@S@,cMJJA#FN B@aRB@x@A AROEwaR aRP =R="Q@z@ o!c3))a*DRbbb@$jS B@jB@x@A$AjTEh3jajU =j6=j V@3@cWB5 B/l X B@JB@&nb@x@AAJYE eZ=pa[ =@{F% \@,@ ! @B]bb! bXF^ B@`B@x@A$A_Evaj b` =j=$j+ca@~@,bJꬠJ c B@aRB @x@A ARdEeRaRe =Rm=R(of@i@,Cuu&׮Bgbhb jh B@jB/@x@A$AjiE!jajj =j %=%-+ck@g$@ $( @"@BlB#B$Bm B@JB@@x@AAJnE eo=|,p =@{% "q@Dހ@ 'L , s\Arb s B@`B@x@A$AtEbbu =jX`=^"v@ W@ m!j @Ϣ 6! !%w VJ x B@aRB@x@A ARyEa aRz =L=R"{@@ SQ * @JW|bb½} B@jB@x@A$Aj~E%ˀ jaj =@݀΀=(%-(%@̀@~B]B B@B@@x@AAJEe=-F =@{ @懀@ ! @c AbHb ! bc B@`B@x@A$AE2Bab =je`=^%@@ mJ1JA# B@aRB{&B9 @x@A ARE@ DE  A@aR =@=R%@H@ c uȿMBbbĩ B@By@x@A$AjEtb@Y jaj =jSx=( @w@ $( @"AJBB J$Bc B@JBy@x@AAJEM0aJ Je=ΤF, =26{% :"@1@ ,  Ab ! 륱 B@aB@x@A$AEb =jaj+"j"@o@,J۩J R B@aRB@x@A AREba aR =Rj=R"@e@ s6 u &ΔBbVbj B@jBB@x@A$AjEhaj jaj =j!="@I@ B J$ B@JB@x@AAJE Je=pF =߀{% F"@*ۀ@ c jfBbڀb B@aB@x@A$AEvbb =jU`=^%@S@ 5cJYJA# B@aRB@x@A ARE a aR =R.=R"@@ 1 0 16%QYBbbf B@jBnb@x@A$AjE jaj =jˀ=(%-(1@ʀ@ rBBB J B@JB@y@x@AAJEb Je=Fxc =@{% "@̄@ m$F @6 nAb-b! b B@`B@x@A$AE?"Gcb =j:=j c@@ m!j @JJA#c B@aRB @x@A ARE%aR aR =RԽ="@5@ , 8E  8a/Bbb@$j B@jBB@x@A$AjEqajF jaj =jF- =ـ{ .@Ԁ@ , w A/bUb 0 B@aBy@x@A$Aj1E@b  E2 =j瓀="3@H@,4JJF5 B@aRBB@x@A AR6EJaR RaR7 =RR=R%8@M@5nu6)aB9bJbj: B@jBB@x@A$Aj;EMjaj< =j =(%-=@9@ r>B J? B@JB@x@AAJ@E FeA=ܤFnbB =ǀ{% "C@À@ 5n`@  BDbp€b E B@aB@x@A$AFEZ}b, bG =j=`=^+cH@;@,IJQJA#J B@aRB@x@A ARKEh@Y   aRL =R=R"M@t@ |y 8BNbbO B@jB@x@A$AjPEb2 j  ajQ = s=( R@ϲ@~SB/B JT B@B@x@AAJUEukaJ  eV=F W =jq{% X@l@Yr @( eBYbb b Z B@aB@x@A$A[E&a b\ =j&=%]@@,^JJA#_ B@aRB@x@A AR`E aR FaRa =R=R%b@@   K^n!cbb cd B@jBB@x@A$AjeEYjajf =j]= g@q\@ hB[B,ki B@JB@x@AAJjEJek='|l ={% m@R@ c Enb  o B@aB@x@A$ApEЀt ^bq =jKՀ=jj%r@Ӏ@,sJJA#t B@aRB,@x@A ARuE$aRv =R̓=R%w@,@ 11 %aVBxbb jy B@jB| 5n@x@A$AjzEGjaj{ =j7K=j |@J@ }BIB~ B@JB@x@AAJE2Je=:у* = {% @k@ ! @c hb  B@aBy@x@A$AEb =jXÀ=%@@,J%JRc B@aRB@x@A ARE?z aR =R=R%@S}@ #^ -Eb|b  B@jB@x@A$AjE5!jaj =jV9=( @8@ $( @w"AJBB B@JB@x@AAJEM e=U =={%p@@ y c  Ab ^! y B@aBc@x@A$AEӬ"bb =jl#`=^"@Zk@,JjJA# B@aRB@x@A ARE#$a aR =R+=R%@&@ 3^A+^A!BbUb½ B@jB@x@A$AjEh߀ jaj =j=(%p(C@D@~Bဃ J$ B B@JB@x@AAJE%e=oF =۠{% "@%@ ! @c !bb b, B@aBc@x@A$AEuV&a b =j'`=j @@,J`JA# B@aRB@x@A ARE͠@Y F!!aR = +-Հ=R"@Ѐ@ C B%B+j$sDbπb  B@B@x@A$AjE (bj""aj =j="@@ $(n(AJBMB B@JB@x@AAJED)J##e=WFx =J{% @E@, ?EAb,b B@aB 4 L6@x@A$AE*aj^W$'b =jD7,`=^"@5@,JJ ( f=Rh B@aRB @x@A ARE2RA@((aR =@=%@>@ Sm+ T Bbbj B@B@x@A$AjE-b j))aj =j0=j%p(+c@@~BB B@JBL6@x@AAJE?e.J**e=F|@. 8A +k{% "@{f@Dt @c |Ab ! b^c B@ Bc@x@A$AE /aB+,b =j="@U߀@,/JޠJ! B@ B@x@A AREӗ0aR F--aR =Rk=R"@˚@ c1 "yBb7b   B@jB@x@A$AjEZS1j..aj =jV=( @D@ rBU J$  B@JBc@x@AAJE2aJ //e=b!|y"`@% ={% @@@a hv/G?Bb}b B@aB* @x@A$AEgʀ01b =j3a^+c@@,cJZJA# B@aRB,@x@A AREuA4a 22aR =RI=R%@}D@ s!"; a^BbCb@$j B@jB@x@A$AjE 33aj =jt5aj%-`3A@~ B0B B@JBt@x@AAJE  J44e=˄FQ =f{" @@ !%c Abb B@aB@@x@A$AE t6b56b =j:47`=%@2@,JJ F B@aRB@x@A ARE@Y 77aR =R=R% @&@ %1 @B bb@%½ B@jB| @x@A$Aj E8b j88aj =j!=( @|@$BݨBJ B@JBc@x@AAJE$b9J99e=t6`= =h{%p:"@ac@ 5n c b   y B@aB@x@A$AE:ajt ::b = `^"=j @ @,J+JR B@By@x@A ARE1٠@Y R;;aR =R="@1܀@ +\+\ 64, b۠b j! B@jB5n@x@A$Aj"E;b j<?b. =jˀ=j /@\ʀ@,0JɠJA#F1 B@aRB; @x@A AR2Eӂ>aR F@@aR3 =Ry=R(o4@ۅ@ \]% aDf5bGb@%j6 B@jB@x@A$Aj7EZ>?aj2 jAAaj8 =jA=j%p( = {% "?@@ c CA@b|b 륱A B@`B@x@A$ABEg@bCDbC =juA`=jj"D@s@ m!j @FEJ^JA#F B@aRBc@x@A ARGEu,BaFEEaRH =R+4=F!I@/@ ,";]4]Q),Jb.boK B@jBL6@x@A$AjLE瀈 fFFajM =j{=j%pj%N@@~OB;BP B@JB@@x@AAJQECb JGGeR=F; S =@n{% %T@@ ,Ubb! V B@`B@x@A$AWE _Da,HIbX =j<E`="Y@@,,ZJJ R[ B@aRBc@x@A AR\Eր JJaR] =R݀= ^@ـ@ c)6ˀV`D_bؠb #` B@jBy@x@A$AjaEFb jKKajb =@瀃%=H@ c@@~dBB$&5,e B@B@x@AAJfE$MGaJ JLLeg=Fh =S{% i@[N@ ) @ Bjb ! bk B@aBy@x@A$AlEHacMNbm = `Ȁ="n@%ǀ@ !j$coJƠJ p B@B@x@A ARqEIaR OOaRr =R[=R(os@@  , HBtb(b@(u B@jB@x@A$AjvE>;JjPPajw =j>=j%-(+cx@,@yB= B$ z B@JB,@x@AAJ{E QQe|=F |} =}{"~@@ !%NK Ab-b! b B@aBc@x@A$AELKbcRSb =jrL`=%p1@p@,JOJA#F B@aRB @x@A AREY)Ma TTaR = 1=R%@m,@ @``!Bb+b½ B@BB@x@A$AjE jUUA! =jT=(%-%@@ n$( @"@BBB J$B B@JBB@x@AAJEgNVVe=貄Fu =S{% "@@ ) @+c 7Abb b륱 B@aB@x@A$AE[OaWXb =j&P`=^"@@,JJA# B@aRB@x@A ARE FYYaR =Rڀ=R"@ր@ c@``"xBboՠb@1j B@jB@x@A$AjEQb jZZE =j=(%-(%@`@$BB B@JB@x@AAJEJRJ[[e=\Fy = ÀO{% "@EK@ ': @+c iAb  b B@`BB@x@A$AESa\]b =jŀ= c@Ā@,JàJA#Fc B@aRB@x@A ARE|TaR@Y F^^aR =RY=R"@@@``#&XjccE =j)=j @*@ r) @"AJB( B B@JB@x@AAJE dde=ͤF ={% @@ ! @c AAbab b B@aBc@x@A$AELYbefb =!!~]Z`="@[@,JJJ B@aRB@x@A AREY[a ggaR =R=%@i@ #@``%L2Bbb@1 B@jBy@x@A$AjE jhhE =jhӀ=j%p(1@Ҁ@ $( @"@BB$B B@JB@x@AAJEg\b Jiie=F5n =S{% "@@ AbbW! 륱 B@aB@x@A$AEF]ajkb =j ^`="@l@,JJA#Fc B@aRB@x@A ARE llaR =Rŀ= @@ 3@``07Bbsb@1j B@jB@x@A$AjEy_b jmmE =j}=%p(%@b|@~B{B t B@JB,@x@AAJE5`Jnne=GF =:{% %@C6@ >Ab  B@aB@x@A$AEopbjaa^(f@@,JJ F B@aRB`x@9 AR @`Egba FqqaR =Go=R"@j@ C6%3 fBbb@( B@a$B@x@A$Aj E##cjrraj =j&=(%-(% @@  Bg% B$ B@JBc@x@AAJE sse=+Fc ={ @߀@ c +AbFb  B@aB@x@A$AE1dbtub =jhZe`=^%@X@,J3JA#F B@aRB|JeB@x@A ARE>fa vvaR =R=R%@F@ S3D3~%AaBbboc B@jB@x@A$AjÈywwaj =j]Ѐ=(%- @π@ !BB J" B@JB@x@AAJ#EKgxxe$=ͤF% =<{% "&@@F vB'b ! ( B@aB @@x@A$A)EChyyb* =jpH=j c+@F@,,J>J n- B@aRBQ@x@A AR.EY@Y zzaR/ =Ria"0@i@bc21!ů@VB1bb½2 B@jB /@x@A$Aj3E຀2 j{{aj4 =jh= 5@ǽ@~6B(B J$ 7 B@JB@٢o!K$X@x@AAJ8Efvjbw J||e9=nDQ: =@[|{% ;@w@N, c bB<bbe! c= B@`B !F@x@A$A>E1ka* }~b? =j= @@x@ m!j @AJJ B B@aRB@x@A ARCElaRaRD =R̰=!E@@ $F @1# %s@ @yRBFbkb G B@jB @x@A$AjHEdmaj fajI =jh=%-jCJ@mg@ !j @.`@BKBfBBL B@JBc@x@AAJME naJeN=2|O = &{% +cP@B!@ @xAQb b륱R B@aB="@x@A$ASEۀ bT = ``/=^"U@ހ@,VJݠJ W B@B@x@A ARXEob RaRY =R=%pZ@@ an6[bb \ B@jB@x@A$Aj]ERpjaj^ =jV=j%-j%_@uU@n`BTB) ca B@JB@x@AAJbE#qJec=+܃|d = {% ce@^@ ) @ fb ! bcg B@aB @x@A$AhEɀbi =jщra"j@0@,kJJ l B@aRB@x@A ARmE@sa %T63n =RVH= o@C@ \$F @+c  #&),oDpb#b@%,q B@jBB@x@A$AjrE>@Y jajs =j=j%-(%t@#@~!j @(@BuB B$Bv B@JB@x@AAJwEŷtb Jex=FʄF$Xy ={% %z@@ ! @2A{bab! b륱| B@aBB@x@A$A}EKsua,:$^~ =j3v`="@1@,JJJ Rc B@aRB@x@A AREY@Y aR =R=R"@Y@!~" 258;>ADGJ@WBbbb B@jB5n@x@A$AjEߥwb jaj =jp=j%-(%@ͨ@~!j @ťB,BB B@JB@x@AAJEfaxJe=F =Rg{"@b@ c ]Abb  B@aB,@x@A$AEyacb =j݀=$K@|ۀ@,JڠJA#(> B@aRB* @x@A AREzaR FaR =R=R%@ @ c   a Bbvb@$j B@jBc@x@A$AjEO{jaj =jR=(%-%@W@ nBQ J$  B@JB* @x@AAJE |Je=|* ={*"@D @ ! @c tAb ^! b B@aB@x@A$AEƀb =j}a^K@@,JJA#F B@aRB$X@x@A ARE=~a aR =R FaR =@=!@F@a u¹`pBbb@$ B@B@x@A$AjEĢb jaj =jH=%-j%@@) @(@BBBB)B B@JB@x@AAJEK^Je=̤Fz* =/d{% %@_@-I! @9  Ab  b륱 B@aB@x@A$AEacb =jـ=j^"@Y؀@,FJנJA#F B@aRB@x@A AREߐaR FaR =R="@@ ¸uҒ" u 0BbcbA;j B@jB]L@x@A$AjEfLjaj =jO=%-(%@I@ r$( @"@BBN J B@JB@x@AAJEJe=r|v = {% %@* @ ) @+c Abb b륱 B@aB@x@A$AEs ^b =jȀ=^"@xƀ@,JŀJ R B@aRB@x@A ARE~aR =R="@@ y8"¯uuam4Bbnb j B@jBy@x@A$AjE:jaj =j >=j%p(%@e=@ B<B B@JB@x@AAJE} e=ă| ={% "@B@2B! @+c Ab c B@aB*@x@A$AEbb =jq`="@%p@,JoJA# B@aRB{@x@A ARP`E(a aR =RZ0= @+@ @C BC2 b½ @a$B B@x` =Aj @`E" jaj = ?`== @@~$( @wK x%Bw怃(B B@B,@x@AAJ Ee =.F ={%p .`@3e @ 䠀@4f c BAbEb ! c B@`B "B@x@A$AE0[ab =)_`=$ .`@3kc @ `@,J+J@p B@B@x@A ARE=Ҡ@Y FaR =ـ=R(o@FՀ@ !$F!0 5 @BbԀbf B@jB @x@A$AjEčb jaj =jL=(%p .`@3A @ @~!j @ťBB$B B@Bc@x@AAJEKIJe =̤F* ! =G?O{% .`@3B" @ J@ ! @[A#b ! b륱$ B@`B@x@A$A%Ea b& =)x =^"'@@,(JFJ (Rc) B@aRB@x@A AR*EX@Y RaR+ =R!Ȁ=R%,@pÀ@! #D@-b€bj. B@jB@x@A$Aj/E{b jaj0 =jg=(%- .`@3C1 @ ~@<$( @"@B2B'BB$B3 B@B@oB&$X@x@AAJ4Ef7Je5=nw *A6 = )b={% .`@3B7 @ 8@c  A8bb 륱9 B@`B !!c@x@A$A:Ecb; =)aj$ .`@3e< @ `@ m, @,=J밠J@p4` > B@B$X@x@A AR?Eia FaR@ =q="A@m@ 3Tү0uBBbrlbjC B@jB@x@A$AjDE%jajE =j )=%- .`@3AF @ d(@$GB'B JH B@Bc@x@AAJIE eJ=F,K =G{% .`@3EL @ E@ ': @c AMb ! b N B@`Bc@x@A$AOEbbP =)\`=%p .`@3eQ @ `[@,RJZJ@peRS B@B@x@A ARTEa aRU =]=R+cV@@ cC% SDPaWbb jX B@jB@x@A$AjYE"π jajZ =jҀ= [@@$( @ūc\BrрB$Bc] B@JB@x@AAJ^Eb* e_=*F* ` ={% .`@3ba @ ㋀@ c IpEbbIb cc B@`B@x@A$AdE0Fabe =)Y`=j^"f@@,,gJ'J h B@aRB@x@A ARiE= FaRj =RĀ=R%k@I@ o!oS0cppr!dlbbA;/fm B@jBQ@x@A$AjnExb jajo =jT|=j%- .`@3Cp @ {@ rqBBJr B@B@x@AAJsEK4aJ Jet=̤F{u =G3:{% .`@3Bv @ 5@ @-oAwb b x B@`BB@x@A$AyEcbz =)a%{@\@,c|JȭJA#Fc} B@aRB@x@A AR~Efa aR =Rn=%p .`@3b @ i@c +eS,7bKbf B@Bc@x@A$AjEf"ajfaj =)%=j @A@ B$J B@JB C5 @'@x@AAJE݀ e=mF ={% .`@3b @ @'߀@ 5\ @ @ bހb b B@`B !@x@A$AEsbb =)Y`="@X@ m!j$JrWJ B@aRB@x@A AREa aR =R.=! .`@3b @ @ s01 PP0_"Hobb f`[ B@B ,@x@A$AjÈjaj =){π=j @΀@~ `@(AJB;B B B@JB@!K@x@AAJEe=F, =@~{% .`@3b @ @Ȉ@ , Ab*b !  B@`B @x@A$AECab =)?`="@@ mJ J c B@aRB@x@A ARE"@Y aR =R= @@B-'QZ` q0@`Bbbĩ B@jB; @x@A$AjEub jaj =j-y=j @x@~BwB$  B@JB@x@AAJE01aJCe=F = 7{% .` e @ j2@ $F @c "BbС ! bXF B@`B@x@A$AEt b =)P="@@,JJA#(> B@aRB@x@A ARE=b RaR =R=R6@M@ E*arBbb j B@jB$X@x@A$AjEcjaj =j B@aRB@x@A AREa(aR =RE`="@~ $F!oE T{S KDapDb b  B@jB@x@A$AjE, faj ==%- .`@3A @ 김@ r!j @"@BBKB B@B@x@AAJErbw Je=F =Gzx{% .`@3E @ s@ ! @MAb*b b륱 B@`B@x@A$AE.acb =)A=j$ .`@3e @ `@,JJ@p B@Bz$X@x@A ARE"aR aR =¬=R+c@@ y;SrSqSq䐂aBbb½ROjBB@x@A$AjE`aj2 jaj =j1d=j%p `@3A@c@~$( @ťBbB Jc B@JB@y@x@AAJE/aJ Je=Fw =@$"{% " @m@ B c ]A b ! 륱 B@`B @x@A$A E׀b =j˗a @-@ m!j @,JJ  B@aRB@x@A ARENa aR =RlV=R"@Q@!Ro3t?Sq5n,!db,b½ B@jBB@x@ =Aj @`EJ aj jaj = ?`= =j%p(C@-@~B B$  B@Bc@x@AAJE Je=R؄F ˀ{% "@ǀ@ ! @'Lbmƀb b B@aB@x@A$A!EXbb" =A`=$j(f#@?@,B$JOJA#% B@aRB@x@A AR&Ee aR' =R6aRR"(@y~ t"aD)bbĩ* B@jB@x@A$Aj+E쳁, jaj, =j|=j%-%-@׶@~$( @"@B.B8B/ B@JB@x@AAJ0Esobw> Je1=F2 ={u{% "3@p@ ) @ [A4bb! b륱5 B@aB - @x@A$A6E*acb7 =j+="8@@,c9JJ : B@aRBv@x@A AR;EaR aR< =R= =@@ c,Ki,aB>bkb½? B@jBB@x@A$Aj@E]ajF jajA =ja=%p(%B@i`@ CB_BJ$ cD B@JB5n@x@AAJEEaJ JeF=+|G ={% %H@N@ ! @K )cAIb  bXFJ B@aB@x@A$AKEԀbL =j8ـ=^(fM@׀@,NJJ cO B@aRB|@x@A ARPE"aRQ =Rٗ=R"R@6@y% .-P"a]LSbb jT B@jB@@x@A$AjUEKjajV =j5O=j%-(%W@N@ n$( @"@BXBMB JY B@JBc@x@AAJZE/Je[=7Ճ@\ = {"]@k@ 2]L^b _ B@aBc@x@A$A`E€ba =jXǀ=j b@ŀ@,cJ&J d B@aRB@x@A AReE=~b aRf =R=FR%g@E@ $F @o{aDfhbb ji B@jB @x@A$AjjE9ajajk =jS==%pl@<@!j @ mBB Jn B@JB@x@AAJoEJJep=VÃw!^|5nq = )N{*"r@@c ?aBsb ct B@aB "@x@A$AuEѰb 5j^Jobv =jp`=j$j(fw@\o@,xJnJ >yy B@aRB@x@A ARzE'aRA@ A{ =@/=R"|@*@ #+bp+e" ZsB}bNb@&4j~ B@By @x@A$AjEe@Y jaj =j=j%p+c@P@~B倃 BuƁJB@x@AAJ @E@==qFw, =ؤ{% "@&@ `@+c Abb B@abB@x@A$AjErZab =j`=G@1² @@,cJYJ  B@aRB@x@A AREѠ@Y F E =R5ـ="@Ԁ@ $F @%3 "7|@BbӀb B@jB@x@A$AjEb jaj =j=j @揀@ !j @(AJBKBJ B@JB C&$X@x@AAJEHaJ Je=[F* =N{F% @I@, 7Ab)b c B@aB,@x@A$AEa b =j2Ā=j$j"@€@ m' @JJ (R B@aRB@x@A ARE!{aR   aR =Rς=F!R%@*~@ C 5Bb}b f B@jBc@x@A$AjE6j  G =j0:=j%pj+c@9@rB8B B@JBc@x@AAJE/   e=Fy ={% "@i@ $F @+c MAb ! b B@aB@x@A$AEb b =jm`="@Hl@,JkJA# B@aRB* @x@A ARE$a aR =Ri,=R"@'@  SP _b n"~FިBb7bA;j B@jB@x@A$AjEJ@Y jaj =j=( @2@~B  B$ , B@JB@x@AAJEћb Je=RF{ ={% @ @ ! @+c zZBbmb! b, B@aB@x@A$AEWWacb =j`=^+c@@,JVJA#Rc B@aRB@x@A AREeΠ@Y aR =Rր=R%@iр@ c89:QZp!u8@ybРb@%j B@jB@x@A$AjEb jaj =j|=j%-(+c@،@ $( @"@BB8B$B B@JB@x@AAJErEaJye=Fc =bK{"@F@ c Nmbbj! B@aB@x@A$AEacb =j ="@@,J쾠J F B@aRB@x@A ARExaR FaR ==R%@{@ sLu9TG!%䅻 Dfb~zb@&j B@jBz@x@A$AjE3jaj =j 7=(%-@j6@$B5B J$ ic B@JB@@x@AAJE e=| =@{% "@O@ ': @c fBb  B@`B@x@A$AEbb =!bj`=^%@!i@ m'%JhJ  B@aRB @x@A ARE!a aR =R?)=R"@$@ qTB9r abb½ B@jB @x@A$AjE/ݠ@Y jaj =j=(%-(+c@@$%{߀ J B@JB@U@x@AAJE  eI7F {@={%p"@@ , h!bYb .` `BV@x@A$AE! B@aRB| @x@A AR"Eta F((aR# =R|="$@w@ 䅯9 B%bcb@&'& B@jB{@x@A$Aj'Er0j))aj( =j3=%p(+c)@T@ r*B2 J+ B@JB@x@AAJ,E **e-=zF. ={%/@5@ ) @ hv/G?3A0bb b .`1aB@x@A$A2Ebc+,b3 =jg`=j 4@ f@,5JveJA7F6 B@aRB* @x@A AR7Ea --aR8 =R<&=R"9@!@ -09",B:bbo; B@jBJ@x@A$Aj<Eڀ2 j..aj= =j|݀=j >@܀@ ?B@B@ B@JB @x@AAJAE//eB=F*C =@{% :"D@ז@ ! @+c $FBEb6b ! b .`F`B@x@A$AGE!Qa01bH =jQ`=j I@@,JJ JA7K B@aRB@x@A ARLE/ȀF22aRM = π=FR"N@7ˀ@ cÿ,38c16ObʀbP B@Bc@x@A$AjQEb 33ajR =j==j S@@~$( @ .`CűT B$ByU B@JB@U@x@AAJVE 99el=_Fm ={% (n@@]L Aobzb! .`a paB@x@A$AqEdbc:;br =jd`="s@b@ '$ ! !% ARtJ_J u B@aRB@x@A ARvEra <>e=F = 5k{"@@ cA1Abb ! B@aB@x@A$AENa?@b =j/`=%p.@ @,J JA#Fc B@aRB@x@A ARE FAAaR =R̀=R%@ Ȁ@L6P|1Np!ɐaBbǀb j B@jBL6@x@A$AjEb jBBaj = "=(%-%@}@$B₀B$ .`B@@x@AAJE!<JCCe=Fn, =@B{% "@^=@; ?Ab  .`ac`B$X@x@A$AEDEb =jշa^%@6@ mcJJA7 B@aRB$X@x@A AREna FFFaR =R^v=R"@q@ o)'E (/<TBbb½ B@jB@x@A$AjE<*jGGaj =j-=(%-(%@#@$B, J B@JB@@x@AAJE HHe=HFy =@{% "@@ $F @ SAb_b ! b .``B@x@A$AEI`cIJb =!!na`=j$j%@_@ m!j @J=`=,? =@{% %@@Q@ ! @` Q.W AAb ! bB B@`B,@x@A$ACEV`hibD =j`="E@%@,FJJ G B@aRB@x@A ARHÈjjaRI =R<Հ= J@Ѐ@ c1 9u:)"taBKbb½L B@jB} ]L@x@A$AjME.b fkkajN =j=j%-(%O@@~$( @4@BPBr B$BQ B@JB@x@AAJREDaJ> JlleS=6WFT =J{% %U@E@ y) @` E !\AVbQb! b륱W B@aB@x@A$AXE;`cmnbY =jm="Z@ξ@ !j$c[J:JA#\ B@aRB@x@A AR]EIwaR ooaR^ =R~=R"_@Mz@ p` 8*  `byb½a B@jB@x@A$AjbE2ajF jppajc =jX6=(%-(%d@5@ eBBJ$ cf B@JB@x@AAJgEV Jqqeh=ۤFbi =B{% "j@@ y! @b& E+c  kb  bcl B@aB@x@A$AmEݩ`t rrbnj=^(fo@鬀@,pJUJA#q B@aRBy@x@A ARrEdeRssaRs =m=R"t@|h@ :"!_maDubgb jv B@jBy@x@A$AjwE jttajx =js$=j%-(%y@#@ n$( @"@BzB/B{ B@JB@,@x@AAJ|Eq܀ uue}=yy~ =@]{"@݀@ Ab b B@`B@x@A$AE!bvwb =j3X"`=j+""@V@,JUJA# B@aRB@x@A ARE#a xxaR =R=FR%@@ 19t@VBbib½ B@jB@x@A$AjEʀ jyyaj =@݀΀=%p%@v̀@$B̀BJ B@Bc@x@AAJE$zze=F@ ={*"@O@ ': @` c }Ab  bc B@aB 'Z @x@A$AEA%`{:#A& =j&`=j^%@0@,J  B@aRB@x@A ARE@Y: F}}aR =Rc="@@ +d -LtBb'b½ B@jB@x@A$AjE.t'b~~aj =jw= @ @$Brv JFJB@@x@AAJ @E/(Je=6B|ژ =5{% @0@ 5\ @` E6 >{BbTb b B@abB@x@A$AE;b =jc)a  @Ʃ@,J2J (F B@aRB@x@A AREIb*a aR =R j=R(o@ae@ * 7{|laeBbdb j B@jB oy@x@A$AjE+aj j&(o =jS!=j @ @~r) @(AJBB B@JBc@x@AAJEV Je=פFB =N߀{% @ڀ@t Ab ! c B@aB* @x@A$AEݔ,bcb =j U-`= @pS@,JRJ(R B@aRB@@x@A ARE .a $$F =R=R%@@ @` Ho"? n@|ybVbj B@jBژ@x@A$AjEqǀ jaj =jʀ=j%p(1@W@~Bɀ B$ y B@JB@x@AAJE/b Je=yF =숀{% "@1@  c |ybb! y B@aBc@x@A$AE~>0b =j.C="@A@,J@J c B@aRB,@x@A ARE aR =R1aR"@~ ?D%bDb}b½ B@jBx@x@A$AjE, j&' =j=(%-(%@p@ rBзBJ$  B@JB@x@AAJEq2bw Je=?w!^| = ) w{% "@Mr@ ! @` >Ab  b B@aBc@x@A$AE,3`b =j=^.@,@,cJJA#4` c B@aRBnb@x@A ARE4aR aR =RU=R"@@ D w 5 U&J27Bbbj B@jB3k@x@A$AjE-_5jaj =jb=j%-(%@ @$( @"@BBja B B@JB,@x@AAJE6Je`5-||@ = {"@@ AbPb ! B@aB,@x@A$A  E;ր ^b = >`=ڀ=j/"@?ـ@,JؠJ  B@B@x` =AR @`E‘7aR =Rb=FR%@”@ o @` o  ) 7 _ + C!,V-Bb.b@$g! B@a$BL6@x@A$Aj EHM8` aj =jP=j%p% @#@ n BOJ$ c B@JBc@x@AAJE9Je=פF ={F% "@ @ 8Abk b c B@aB#6(,@x@A$AEVĀ&.W =j:aj$j%@킀@,JYJ Fc B@aRB@x@A AREc;;a FaR =R C=FR"@g>@  #"% @<pba2zb=bf B@jBB@x@A$AjE jaj =j=%-%!@!@$"B J# B@JB@x@AAJ$EqRaR0 =!  1= 1@z,@ ) @` c3<eP @D2b+b4 3 B@jB o@x@A$Aj4E aj5 = =j 6@@ !j @"AJ7BMB8 B@B@x@AAJ9E?be:=n|; =|{% <@ơ@ ! @` DA=b,b ! bc> B@aB*" @'@x@A$A?E\@` b@ \`="A@_@@Sf'BJ^J JC B@A*Bc@x@A ARDEARaRE =@M=R(oF@@Ce#` ژGb b jH B@B @x@A$AjIE Ӏ ajJ =jր=(%p(+cK@Հ@ $$( @ťLB\B$BM B@JB@C!K@x@AAJNEBb JeO=FP =@{% "Q@㏀@ y$F @` E+c "ARbCb b륱S B@`B !@x@A$ATE-JC`ybU =jV D`=^"V@@ m!jWJ$J X B@aRB@x@A ARYE;@YA aRZ =RȀ=R"[@?Ā@ Sep8@ @B\bÀb½] B@jB@x@A$Aj^E|Eb jaj_ =jR=j%-(%`@@ aBBb B@JBy@x@AAJcEH8FJed=ɤFe =4>{"f@9@ B!%N` E4 Agb ! b h B@aBc@x@A$AiEbj =jGa j%p%k@^@,lJʱJA#(Rm B@aRB@x@A ARnEjHa FaRo =Rr=FR%p@m@! @` c+_EQ@[&BqbHbjr B@jB oc@x@A$AjsEc&I` jajt =j)= u@J@$vB(J$ w B@JB@x@AAJxE Jey=kFz ={% F"{@'@ W) @b& B+c B|bb by} B@aB "c@x@A$A~EqJ`b =j]K`=j$jK@[@,JcJ  B@aRBz9>Bh @x@A ARE~La !8 =R;="@@ s6 pBbbf B@jB @x@ =Aj @`E jaj =jӀ=%-+c@Ҁ@$BMB J B@aB@!K@x@AAJEMbye= Fvy =@{% %@͌@ b+b B@`B0 @x@A$AEGNa bjK=^%@J@ m'%5nJ~INFc B@aRB @x@A AREO RaR =G =!@@ y zu{luadDb b4j B@jBy@x@A$AjE 2 jaj =j=j @@ / @"AJBd B B@JBc@x@AAJEyPe=F/ ={F% @z@ W`@` c ;AbBb c B@aB< Z@x@A$AE-5Q`b =jP=j^"@@,JJ! B@aRB@x@A ARE;RaRFaR =Rݳ=FR(o@?@ |{|"\4alZBbb B@jB5n@x@A$AjEgSaj aj =jIk=j%p(+c@j@~BB B@JB@x@AAJEH#TJe=ɤF}c =4){% "@$@W @` E+c ȨAb ! b B@aB@x@A$AEހt b =jy="@@,JGJ  B@aRB@x@A AREUUb RaR =R=R"@^@ cld?p_ n a#Bbʜb$ B@jB o@x@A$AjEUVjaj =jdY=( @X@ B B, F B@JB@x@AAJEcWaJe=k߃, = À[{% @@ c yBbb  B@`Bc@x@A$AÈb =jXa^+c@x@,J䊠J  B@aRB@x@A ARECYa aR =RK=R%@F@$X ?p Wh0aBbGbf B@jB@x@A$AjE~ jaj =jZa(%-(+c@a@$BBJ)  B@JB@x@AAJE Je=̈́Fy ={% "@B@ * ': @a c Ab  b B@aB@x@A$AEv[`b =j6\`=j%@5@,J~4JA# B@aRB@x@A ARE aR =RJ="@@ c h03t t]zBbb j B@jB@x@A$AjE]b j] =j=j%-(%@@$( @"@BBt B B@JB@y@x@AAJEd^aJce='wF{ =@j{F% %@e@  AbFbj! 륱 B@`B !@x@A$AE- _acb =jB=j @ހ@,JJ aK aRB@x@A AR@> @:`aR FaR 6R=FR"@J@ _P1n`63t@: @Rajaj 6jIV=%p(%@U@ n BB J$ c B@@Bc@x@AAJ @> @HbJe @6 @ɤF 64{% "@@ ! @` c Ab ! bc B@@6ǠB@x@A$A@> @6ɀb 6jca j^(f@Y@,JŇJ@j B@@By@x@A AR@> @5\@da aR 6RH=R"@C@ yN`5тx\|zBbPb@j B@@By o@x@A$Aj@> @c@ jaj 6j=j%-(%@J@~$( @"@BB B$B B@@B@x@AAJ @> @ee!@6 @kʄFwc" 6ҽ{% "#@$@ c A$bb 륱% B@@B* @x@A$A&@> @psfb' 6jx=j (@xv@,)JuJ@ܣ>* B@@BQ@x@A AR+@> @.gRaR, 6R6=R"-@1@ }arQ@ho.bgb@%j/ B@@B,@x@A$Aj0@> @6 aj1 6j="2@k@ 3BB4 B@@B@x@AAJ5@> @he6@6 @ t|; 7 6{% 8@@@ , inW9b ^! : B@@Bc@x@A$A;@> @aib< 6 >  :f=j = 9@d@,>JJ F? B@@B@x@A AR@@> @jRaRA 6$=R%B@ @ |tk@)SHoCbb jD B@@B|c@x@A$AjE@> @ ajF 6j!܀=j*(+cG@|ۀ@0HBB$ I B@@B@x@AAJJ@> @keK@6 @'bL 6{:% "M@U@ m c ANb ! XFO B@@Bc@x@A$AP@> @OlaybQ 6jm`=j R@1@,SJ J@T B@@B@x@A ARU@> @ FaRV 6RY΀=FR"W@ɀ@ !$F @a   k@xXb'b jY B@@B@x@A$AjZ@> @82n`.aj[ 6jʅ=%-(%\@&@$]B J$ ^ B@@Bc@x@AAJ_@> @=oJe`@6 @BPFa 6C{% "b@>@ " @` wAcbab bd B@@B@x@A$Ae@> @Hbf 6jypa j$j3g@ڷ@,hJFJ@i B@@B@x@A ARj@> @Upqa aRk 6Rw="l@Ys@, #q l@Bmbrbon B@@By@x@A$Ajo@> @82+rjajp 6jT/=%-%q@.@$rBB Js B@@B@8@x@AAJt@> @b eu@6 @Fv 6@  S{% %w 9@@, j Axb ! y B@@B W!,@x@A$Az@> @sbcb{ 6)ct`=j |@pa@ m, @}J`J@c~ B@@B@x@A AR@> @6ua aR 6R!=R"@@ 3 nEhqB"Q}Bbsb B@@B@x@A$Aj@> @} jaj 6jـ= @b؀@ ) @(AJB׀B J$Bc B@@B@$@x@AAJ@> @ve@6 @F|c 6@  {%  9@?@ O! @` c Ab ! bc B@@6ǠB@x@A$A@> @Lw`cb 6 >   x`=%pj" 9@ @ mJ J  `R-_ B@@B@x@A AR@> @5\ FaR 6Nˀ=R%@ƀ@ cC`76Ô6@@xbb jc B@@B$X@x@A$Aj@> @yb jaj 6j=j%-+c@@~$( @r"@BBWB B B@@B@x@AAJ@> @:zJe@6 @'MF 6@{% "@;@ c uxbBb ! 륱 B@@By@x@A$A@> @,cb 6j_{a"@@,J+J@g B@@Bc@x@A AR@> @5\m|a FaR 6Rt= @Np@ $F @` oS9P~0~ۏ @Dfbobĩ B@@B !@x@A$Aj@> @(}` jaj 6j=,=j%p(%@+@ !j @ťB*B$B B@@B@x@AAJ@> @G Je@6 @ȤF$X 6D{F% %@@ @! @b& (Ab ! b륱 B@@B@x@A$A@> @Ο~`t b 6jd=j$@j @3kc@Ƣ@,J2J@n B@@B$X@x@A AR@> @6[RaR 6A  c=R" 9@M^@ !d"!` c`ү"v?@4b]b j B@@B@x@A$Aj@> @6`aj 6)d="@@~!jnB B$B B@@B@x@AAJ@> @bҀe@6 @j$X 6 >  b؀{% F" 9@Ӏ@ / @b& B6 14Ebb ! b B@@B@x@A$A@> @6`  b 6)N`=^"@tL@,JKJ >c B@@B@x@A AR@> @aaR 6 >   =R" 9@@ cs*? WybBb{b  B@@B@x@A$Aj@> @6jaj 6)Ā=(%-(+c@`À@ B€B $  B@@B@x@AAJ@> @|e@6 @F$X 6 >  {% " 9@E}@$X  Ab , B@@B@x@A$A@> @7ab 6)=j)j%@@,J}J@ܦ{ B@@BzFB@@x@A AR@> @6aRA@ A 6@  I=" 9@@ o$F!` Ho"QёaBb bj B@@B@x@A$Aj@> @6j` jaj 6 >  m=%-% 9@@$BclJ B@@B@x@AAJ@> @%aJ J@=@6 @+8|vy 6G+{% %@&@ dAbBb B@@B 4  @'@x@A$Aj@ @6b 6jQaj$j%@@,JJ@nc B@@B@x@A AR@> @6Xa aR 6R_="@:[@ , @&QBZb½ B@ B* @x@A$AjEj G =jM=j @@ ' @"AJB B B@JBB@x@AAJEGπ e=ȤF$X =7Հ{F% F% @Ѐ@ @b& A b ! bc B@aB,@x@A$A EΊ`b =jz= @ڍ@,JFJA# B@aRBy@x@A AREUFaR   aR =RM=R"@MI@ c a K_Ea bHb j B@jB,@x@A$AjEaj j  aj =jc=j%p(+c@@~BB$  B@JBc@x@AAJEb J  e=j = ÀVÀ{% "@@ v b ! ! B@`Bc@x@A$A"Exb  b# =j 9`="$@7@,%J6J(Rc& B@aRB@x@A AR'E aR( = = )@@$X * $X*bfb@&+ B@B]L@x@A$Aj,E}b jaj- =j=j%-(%.@Z@~r/B B$ 0 B@JB@x@AAJ1EgJ >2=yF,3 =l{% %4@?h@$X G$X5b ! 6 B@aB@x@A$Aj7E"ab8 = `="9@ @,:JyJ F; B@B* @x@A AR<EaR FaR= =R?=R">@@ c _EasD?bb½@ B@jBc@x@A$AjAEUaj jajB =jX=(%-(%C@W@ DBOB$ E B@JB@x@AAJFEaJ JeG=*#|yH ={% "I@@ c AJbAb! K B@aBc@x@A$ALE,̀ bM =jЀ=j3N@<π@,OJΠJ P B@aRB@x@A ARQEbaRR =RP=R"S@@ \_ |aBTbb jU B@jB@x@A$AjVE:CajajW =jF=j%-(%X@@~YBzE B$ Z B@JB@x@AAJ[E e\=ȤF] = Àa:% "^@ y! @`_ A_b\b! b` B@`B@x@A$AaEG,cbb =jpza j c@x@,dJ>J e B@aRB@x@A ARfET1a aRg =R9=FR"h@]4@ yֿK_EavBib3b½j B@jB@x@A$AjkE jajl =jW=%-(%m@@ n$( @(@BnBB J$Bo B@JB@@x@AAJpEbeq=Fyr =@N{% "s@@ y ^Atb ! 륱u B@`B@x@A$AvEca bw =j$`=j^"x@s"@,yJ!JA#cz B@aRB@x@A AR{E F!!aR| =R="}@ހ@ ; AajB~br݀b j B@jB@x@A$AjE}b j""aj =j=%-(%@a@$BŘB c B@JB@@x@AAJERJ##e=dFs =@W{%@>S@ ': @` c tAb  B@`B,@x@A$AE `c$%b = `̀=j @ ̀@ m' @* JyˠJ  B@B@x@A AREaR F&&aR =RE=R"@@ /*a@Bb bf B@jB@x@A$AjE@j''aj = C=%-@B@ $( @n(AJB^B$B B@B@@x@AAJE ((e=&|| =@a%p"@ nb$F @`_ E AbAb ! bc B@`B@x@A$AE,,c)*b =^wa %pj"@u@ m!j @J+J  B@aRB@x@A ARE9.a ++aR =R5=!R"@91@, hqB"\}hJBb0bA;# B@jBF@x@A$AjE j,,aj =jP=j%-j+c@@ B B$ c B@JB@x@AAJEG--e=ȤF =+{% "@}@, uAb ȥ!  B@aB |- B@x@A$AE`ac./b =j `="@L@,JJ F B@aRBc@x@A ARE F00aR =R߀= @ڀ@ c#9Q/>ttaKBbOb j B@jB,@x@A$AjEbb j11aj =jږ=j%-(%@6@~B B$  B@JB@x@AAJENJ22e=jaF =T{F% %@#P@, ;AbOb  B@aBy@x@A$AEo a34b =jʀ=j^+c@ɀ@,JnȠJ  B@aRB@x@A ARE}aR F55aR =R/=R"@@o!` N!(o3t\9<@Bbbĩ B@jB /@x@A$AjE=` j66aj =j@="@?@~BCB B@JCB !Ky@x@AAJE J77e= |x =@{% @@ y! @b& _YBb&b ! b B@`B@x@A$AE`88b =j=^%@ @ mJyJ  B@aRB* @x@A AREoaR 99aR =RDw=R%@r@ cCl@KӐ:Bbb$ B@jBt@x@A$AjE+j::aj =j.=(*(+c@-@ $( @"@BB^B $Bc B@JBc@x@AAJE ;;e=F ={% "@@ $F @b& E cAbAbǝ! bc B@aB 'Z@x@A$AE,`<=b =j\b`=j)j"@`@,J*J  B@aRB@x@A ARE9a>>aR =R ="@I@ SK` sP<0y-Bbb jc B@jB* @x@A$AjEԀj??aj =jL؀=%-%@׀@$B B J$ y B@JB@$c@x@AAJEGb @@e=ȤFzc =@[{% %@@ y': @` E.W wb  B@`Bc@x 9A$A @EK`ABb = ?`= `=j @T @ m' @y J J  B@B@x@A ARE€FCCaR = ʀ="@ŀ@ cې;`&6s &5nb_b j B@B o@x@A$AjEa~b fDDaj =j⁀=j%-(%@A@$( @n(@BB B B@JB@@x@AAJE9aJEEe=iLF{ =@?{F%p%@*;@ c 5nb:bj! c B@`Bc@x@A$AEocFGb =aj%pj" @@,!JbJ " B@aRB@x@A AR#E|la FHHaR$ =R1t=R"%@o@ s6sޚDf&bnb½' B@jB@x@A$Aj(E(jIIaj) =jw+=j%p%*@*@ +B3B$ c, B@JB@x@AAJ-E JJe.= F/ =z{% "0@@ c zhA1b&b ! y2 B@aB @x@A$A3EbKLb4 = `2_`= 5@]@,6J\J 7 B@B@x@A AR8Ea MMaR9 =R=R":@*@@  =@Հ=j%-(%?@tԀ@ @BB$ A B@B@x@AAJBE+OOeC=FD = {% "E@e@ y/ @` Q6 _5nFb ! bG B@aB .X@x@A$AHEH`PQbI = ```="J@A@,KJJ L B@B@x@A ARME FRRaRN = iǀ= O@€@ L1,lL6Pb0b jcQ B@BB@x@A$AjREF{b jSSajS =j~=j%-(%T@"@~UB} B$ V B@JB@\$@x@AAJWE6JTTeX=NIF,Y =@<{% %Z@8@ ! @` E( A[bi7b b\ B@`Bc@x@A$A]ET UUb^ =j="_@X@ m'$`JJ a B@aRBژ@x@A ARbEۭbVVaRc =R=R"d@װ@ cL6q ebCbĩf B@jBW@x@A$AjgEaiaj WWajh =jl=(%-(%i@;@ $( @4@BjBkJk B@JBc@x@AAJlE$JXXem=F; n =*{% "o@$&@ W$F @` E+c qSApb%b b륱q B@aBc@x@A$ArEoYZbs =ja ^"t@@@Sf!juJZJA#Jv B@aRB@x@A ARwE|Wa F[[aRx =@(_=R"y@Z@$X L60tBzbYbf{ B@B y@x@A$Aj|Ej\\aj} =js=(%p(%~@@ rB7B J B@JB@!Kc@x@AAJE ]]e= Fuy =@zԀ{% "@π@ SAb&b ! B@`Bc@x@A$AE^^b =j=j%pj%@@@SmJ|JA#J B@aRBB@x@ =AR @`EEaRu__aR =@`=PM=R"@H@ c 0Ӕ_P91$`̠Bbb j B@B@x@A$AjEaj f``aj =j=j @ @ Bj$  B@JBc@x@AAJE Jaae=F =€{% F"@߽@ $F @b& c BbAb b B@aB@x@A$AE+x`bcb =jY8`=%@6@,J&JA# B@aRB@x@A ARE9@Y ddaR =R="@E@ co`'$/+an`,ƱBbb½ B@jB@x@A$AjEb jeeaj =jL=%-(+c@@~BB B@JB@x@AAJEFfJffe=ǤF|c =6l{% "@g@ ! @` E.W bCAb ! b B@aBc@x@A$AE!`ghb =j=^%@X@,JߠJA#(Rc B@aRB@x@A AREژaR FiiaR =R=R"@⛀@ ,$?o`kpepa :BbNbj B@jB$X@x@A$AjEaTajjjaj =jW=j @I@ $( @"AJBV B$Bc B@JB@x@AAJEaJ kke=i"| ={% @"@ W c Abb! c B@aB@x@A$AEnˀclmb =ja"@@,JaJA# B@aRB@x@A ARE|Ba nnaR =R&J= @E@ e`P Kf$XbDb½ B@jB~@x@A$AjE jooaj =jaj%-(+c@@ BKB$  B@JB@x@AAJE Jppe=̄F, =z{F% (@ĺ@ ! @a c V!b%b ! b B@aB@x@A$AEu`qrb =j+5`=j^K@3@,J2JA# B@aRB@x@A ARE쀈 ssaR =R=R"@.@ |`%qbb j B@jB}@x@A$AjEb jttaj =j0="@@ƹ$(n"EB쩀BB$B B@JB@x@AAJE+cJuue=F =i{% @fd@ c qb  B@aB@x@A$AEavwb =jހ=^"@5݀@,,JܠJA# B@aRB@x@A AREaR FxxaR =Rr=R%@Ϙ@ `l Q`Nb;b½ B@jBc@x@A$AjEFQjyyaj =jT=(%-(+c@3@ rBS J B@JB@x@AAJE Jzze  N| ={% "@@ l6bi b  B@a$B@x` =A @ESȀc{|b =ja^%@ކ@@Sf'+> ! $ C>JJJ J B@a B]L@x@A AR Ea?a }}aR =@ G=R" @iB@B #Fhqa+Aj bAb B@B@x@A$AjEc~~aj =jt=( @@$B0B B@JB@x@AAJEnbe=F =c{% @@$X Bbb !  B@aB@x@A$AEqab =j2`=%pj%@x0@,J/JA# B@aRB@x@A ARE aR =R=R%!@@ 3B"K^d?` n@"b{b c# B@jBy@x@A$Aj$Eb jaj% =j= &@w@ c'BզB$ ( B@JB@x@AAJ)E`Je*=rFq+ =f{% F",@La@ ! @` E-b  b. B@aB @x@A$A/E`b0 =jۀ=j^%1@ ڀ@,2Jy٠J 3 B@aRB; @x@A AR4EaR FaR5 =RL="6@@ C`zqBE %s)B7bb½8 B@jBB@x@A$Aj9E+Njaj: =jQ=%-(1;@ @$<BgP J= B@JB@x@AAJ>E aJ e?=3|}@ ={% %A@ @ / @` E+c ABbNb! b C B@aBc@x@A$ADE8ŀcbE =j`a j F@Ã@,GJ/J cH B@aRB@x@A ARIEFb½M B@B o@x@A$AjNE jAO =jQ= P@@ ) @(AJQBBBcR B@JB@!K@x@AAJSESeT=ԤF; U =@G{% V@@ c AWb X B@`B !* @x@A$AYEnacbZ =j/`= [@e-@,\J,J ] B@aRB@x@A AR^E FaR_ =R=R%`@@ @a Ho%cn+\?g@]ab_b jb B@jB @x@A$AjcEn` jajd =j=j%-(+ce@L@~fB B$ g B@JBc@x@AAJhE\Jei=voFj =b{ k@1^@c lb]b m B@aB@x@A$AnE|abo =j؀="p@׀@,qJr֠JA#jr B@aRBc@x@A ARsEaR FaRt =RI=%pF%u@@ sEqBaDvbbĩw B@jBژ@x@A$AjxEKjajy =jN=j%-j%z@M@ {B\B| B@JB@x@AAJ}EJe~=| = À {F% "@@ ! @` Ab2b ! b B@`B* @x@A$AE€@b =jFa j^.@@,JJ  B@aRB@x@A > E+9a aR =R@=R"@3<@ y1N`3da,b;b j B@jBQ@x@A$AjEaj =j=="@@ƹ$(%"DBB J$Bc B@JB@x@AAJE8b e=„F~ =,{% @u@ 5n 2b ! c B@aB@x@A$$^Ekab =j+`=^"@F*@,J)J >c B@aRB@x@A ARE aR =Ra=R%@@h `䐃, a7VDfb,b  B@jBɂ@x@A$AjESb faj = ߡ=(%-(+c@:@ rB , B@B@x@AAJEYJe=[lFc =_{% "@[@ @` c AbvZb b B@aB 4 @x@A$AE``c9 =jՀ=^%@Ӏ@,JWJ F B@aRB$X@x@A AREnaR FaR =R=R"@v@ c-l`aӋBb⎀bf B@jB*@x@A$AjEG2 2 jaj =jqK=(%-(%@J@~B-B J ` B@JB@x@AAJE{aJ Je=FF =p {% "@@ y c wyAbb!  B@aB@x@A$AEb =ja @}@,J|J  B@aRB@x@A ARE6a aR =R==R"@9@ $F @fO o%|Np]Lybp8b½ B@jBy@x@A$.!E jaj =j"= @~@ !j @(DBBJ$B B@JB@x@AAJEb Je=F ={% `3b@W@ 5n! @%yb  b륱 B@aBy@x@A$AEhayb =j(`=j$j"@/'@ ' @J&JrR B@aRB{ @x@A ARE aR =Rg="@@ zy `Dfb%b j B@jB; @x@A$AjE8b jaj =jĞ=%-+c@ @$Bµ B@JB@x@AAJEVaJ Je=@iF = À\{% (@W@ $F @ 9Ab[b b B@`B@x@A$AEE ab =joҀ=j%pj%@Ѐ@,cJAb 'Z bc  aB@x@A$AE绀b =j | a @jz@,JyJA" B@aRB@x@A ARE2a aR =R:=R"@5@ y,Kea BB bhbA;j B@jB*@x@A$Aj E{@Y jaj =j=j%-(+c @W@~$( @ťB B$B B@JBc@x@AAJEe=F*  =ꯀ{% "@=@ c  rAb ! 륱 B@aBV@x@A$AEeab =j%`="@$@,J#JA#R B@aRBc@x@A ARE FaR =R<= @߀@ c] ^U_a@bb j B@jB* @x@A$Aj Ebcaj! =j=j%-(%"@@ #B]B$ y$ B@JB@x@AAJ%ESaJ e&=%fFc' =Y{% %(@T@ ! @c ?@)b?b^! bXF* B@aBc@x@A$A+E*acb, =jMπ="-@̀@,.JJA#/ B@aRB@x@A AR0E8aR aR1 =Rꍀ=R"2@D@,%%` "xED3bb½4 B@jB@x@A$Aj5EAjaj6 =j?E=j%-(%7@D@ $( @ūc8BCB$B9 B@JB@x@AAJ:EE e;=ƤF@< ==a"=@h >b ! ? B@aB@x@A$A@E̸,cbA =xaj"B@[w@,CJvJ cD B@aRB@x@A AREE/aaRF =R7=R%G@2@o!oqBqB}|l?"l1$HbQb I B@jB o Ǝ@x@A$AjJE` fajK =j=(%-L@E@ rMB퀃 N B@JB@x@AAJOEb JeP=hFQ =笀{% "R@"@ ! @BSbb bcT B@aB " @x@A$AUEmbacbV =j"`=^%W@ @,XJXJ cY B@aRB5n@x@A ARZE{٠@Y aR[ =R&=R"\@܀@ c#pA17@B]bۀb½^ B@jB @x@A$Aj_Eb; aj` =j=(%-(&a@뗀@$bBJB J) c B@JB@x@AAJdEPaJ ee= cFf =yV{% "g@Q@ =Ahb$b! i B@aBy@x@A$AjE abk =jJ̀=)j%l@ʀ@,mJJA#(Rcn B@aRB@x@A >oE aR aRp =RЊ=R"q@-@3\D}a8Brbbjs B@jB@x@A$AjtE>!aj jaju =j+B=%-%v@A@ ) @"@BwB@BJ$Bx B@JB@x@AAJyE* Jez=F5n{ ="a% "|@fc A}b  륱~ B@aB@x@A$AE,1 b =jS=j^"@@,J!J  B@aRB,@x@A ARE7q#b RaR =Rx=R"@Ht@ C"ET%"x\|eFBbsb  B@jB@x@A$AjE,$jaj =jJ0=j%-(%@/@BB B@JB@x@AAJEE e=Mh =-{% "@{@ c Ab ! B@aB W.X@x@A$AẸ%bb =jd&`=%@bb@,JaJ B@aRB@x@A ARE'a aR =R"="@@BS}-~abYb½ B@jBB@x@A$AjE`ր jaj =jـ=j @C@~B؀ B$  B@JB@x@AAJE(b Je=hF{ =ۗ{% @!@ B! @ qbb! b B@aB@x@A$AEmM)aBb =j *`="@ @,JXJ c B@aRB@x@A ARE{Ġ@Y aR =R̀= @sǀ@ ! @ c4+m;  RHobƀb½ B@jB,@x@A$AjE+baj =j=%-(+c@ႀ@ $( @(@BBFBB B@JB@٢o)B@x@AAJE;,aJ e= NFy =@xA{% c@<@ Ab$be B@`B@x@A$AEcb =j5-a$j"@@,JJ  B@aRB@x@A AREn.a aR =Ru=R"@$q@ cs1Ӑ5v",?Bbpb½ B@jBc@x@A$AjE)/jaj =j#-=(%-%@,@ B+B$  B@JBc@x@AAJE*倈e=Fc ={ @h@ ! @ 5Abʡ ! bc B@aB@x@A$AE0bb =j`1`=^%@C_@,J^JA# B@aRB@x@A ARE2a aR =Rh=R%@@ c {"E1nb5Bb.b B@jBc@x@A$AjEEӀ jaj =jր=(%-@%@ $( @"AJBՀJ$B B@JB@x@AAJEˎ3e=MF ={% "@@ Abgb 륱 B@aB@x@A$AERJ4a,b =} 5`=j c@@,JIJ B@aRB@x@A ARE` FaR =Rɀ=R"@dĀ@ yN"45:sBbÀb jc B@jBnb@x@A$AjE|6b jaj =jv="@@nB2BB B@JB@x@AAJEm87Je=F =a>{% @9@ c Jb b B@aB@x@A$AEcb =j"8aj @@@S= @/ @7aﱠJ J B@B@x@A AREk9a FaR@r=" ~@n@ E6AEbmbf B@jB@x@A$AjE&:jaj =)*=*(1 @m)@ r B(B J B@JB@C@x@AAJ E e =F =@{% (@J@ $F @ ;Ab ! b  B@`B@x@A$AE;bcb =j]<`=j%pj.@(\@ m!j @ .` nb [J ( B@aRB @x@A ARE=a aR =j="@@Q7$>8Vg3$b'b½ B@jB@x@A$AjE*Ѐ2 jaj =jӀ= @@~BnҀ J$ , B@JBc@x@AAJ!E>e"=1F # ={% F%$@쌀@; B%bLb & B@aBQ@x@A$A'E7G?a,b( =jr@`=%pj%)@@,*J>JA#j+ B@aRB@x@A AR,EDaR- =Rŀ=R".@I@ $9%aeB/bb 0 B@jB@x@A$Aj1EyAbjaj2 =jS}=j%-&3@|@ 4BB 5 B@JB@x@AAJ6ER5BaJ @=7=פF8 =J;{% "9@6@F A:b ǝ! ; B@aBL6@x@A$Aj<EA= =jCa >@W@,?JîJA#@ B@aRB@x@A ARAEgDa aRB =Ro=R"C@j@ K["u4ј:%aBDbZb½E B@jBW@x@A$AjFEm#EjajG =j&=j H@R@ IB% B$ cJ B@JB@x@AAJKEހ} eL=uFM ={% N@0@ ! @+c BOb߀b! bcP B@aB @x@A$AQEzFbbR =jZG`="S@X@,TJiJ U B@aRBh@x@A ARVEHaaRW =R;= X@@ ;% <  a BYbb jZ B@jB@x@W =Aj[ @`È f  aj\ = {=Ѐ=%-(+c]@π@~$( @.`@B^BKBB_ B@aB@@x@AAJ`EIb J  ea=F`=vBb =@{% (c@щ@ y5\ @+c (Adb1be B@`B@x@A$AfEDJajc  bg =jDK`=^"h@@,iJJ j B@aRB@x@A ARkE)  aRl =R€=R"m@9@ E=% %> X2jnbb jo B@jB@x@A$AjpEvLbjajq =jDz=(%-(%r@y@ sBB$ t B@JB@@x@AAJuE72MaJ ev=Fzw =@/8{%p"x@s3@ ! @+c yb z B@`B5n@x@A$A{Eb| =jڭNa^%}@<@ m'% .`DFy~ J@/ B@aRBb@x@A AREdOa aR =l=R"@g@BE?% %@  DbGb½ B@jB@x@A$AjER Pjaj =j#=(%-(%@<@@r$( @n"@BB" J B@JB@x@AAJE e=ZMay ={%p"@݀@ $F @ nAbt܀b b B@aB@x@A$AE_Qajb =jWR`=^"@U@@S!jJbJA#J B@aRB@x@A AREmSa aR =@=R"@u@cEA%%B `Bbb j B@B@x@A$AjE jaj =jẁ=(%p @3A@̀@ƹB3B J B@JB@x@AAJEzTb Je=Fz =ATn{% "@@ ! @  "Abb! b B@`B@x@A$AEAUacb =j&V`=^%@~,JJ > B@aRB@x@A ARE aR =R=R"@@ #EC%@D z`bBbzb½ B@jBژ@x@A$AjEsWb jaj =jw=( @tv@ r$( @J "AJBuBJ$B B@JB@@x@AAJE/XJ@==F =@5{% @Y0@ ژb  c B@`B@x@A$AjEc b =j۪Yaj+"j"@=@ mcJJ  B@aRB$X@x@A AREaZa\!!aR =Rei="@d@ 3EE%F p5Dfb(b j B@jB@x@ =Aj @`E7[aj@ ""aj =j =%-1@@$B J B@aB@١K@x@AAJE J##e=BF =@ހ{% (@ـ@ ': @c AbYb b B@`B@x@A$AED\b$%b =juT]`= @R@ mJCJ  B@aRB@x@A ARER ^a &&aR =R =R"@^@BC% GS"S""1aBb b# B@jBnb@x@A$AjEƀ''aj =jhʀ= @ɀ@ $( @(AJB$B  B@JB@@x@AAJE__b ((e=F =@S{% @@c BxAb ! c B@`B@x@A$AE=`a)*b =j=j^"@y@@SmJJ J B@aRB@x@A AREaaR++aR =@=R%@緀@ cS,"Q"BbSb½ B@B* @x@A$AjEzpbj,,aj =j t=j%p`3A@es@ BrB$  B@JB@x@AAJE,caJ> --e=>| =1{% "@;-@ c eAb !  B@aB@x@A$AEc./b =jda%@@,cJfJA# B@aRB@x@A ARE^ea 00aR =RVf="@a@ ,c}|n"@?" Rbbb½ B@jB5n@x@A$AjEfajf11aj =j=%-(1@@ B`BJ$  B@JB c@x@AAJE 22e =#F$X =@ۀ{% " @ր@ , coA b>b !  B@`B@x@A$AE)gb34b =jSQh`=^%@O@,J JA#c B@aRB@x@A ARE6ia 55aR = =R"@C @ su6+g" 6]Bb b f B@B@x@A$AjEÀj66aj =jIǀ=j%-(%@ƀ@~nBB$  B@JBc@x@AAJEDj77e=ŤF/ =<{% " @~@ ! @ A!b ! b" B@aB@x 9A$A# @E:ka89b$ = ?`=="%@A@,&JJ ' B@B/@x@A AR(EرlaR ::aR) =R=R"*@䴀@ 7]F"qB\l+bPb½, B@jB@x@A$Aj-E_mmj;;aj. =jp=(%-(%/@?@ $( @(@B0Bo J$B1 B@JB@x@AAJ2E(nJ<b9 =joa^":@뢀@,;JWJA#c< B@aRB@x@A AR=Ez[pa@Y ??aR> =R(c=R"?@^@y 8"8%\bDf@b]b A B@jBy 7b@x@A$AjBEqaj j@@ajC =j=j%-(%D@@ EB@BJ$ cF B@JB !KL6@x@C =AJG @EҀ JAAeH=FcI =@{s؀{"J@Ӏ@%N *^AKb#b bcL B@`B@x@A$AMErBBbN =j=j%O@ @,PJvJA#Q B@aRB P 5@A ARR @`EIsaR CCaRS =RMQ=FR%T@L@ y"1Ӫ-t`̠QBUbb jcV B@a$BB@x@A$AjWEtaj jDDajX =j=%-Y@@ZB_BB[ B@JB@x@AAJ\E JEEe]=F]L^ =ƀ{% "_@@, ]B`b>b a B@aB@x@A$AbE)|ubFGbc =jYE@YA WWaR =R=%p@'@ c"T333TB9"3@pbbbĩ B@jBW@x@A$AjEb jXXaj =j&=j%pj%@@~$( @ťBB$By B@JB@x@AAJE)gaJYYe=F =m{% %@bh@ $F @+c hbɡ ! b륱 B@aBt@x@A$AE"acZ[b =j=G@² "@:@,JJ  B@aRB@x@A AREaR,\\aR =Rz=R"@՜@ `ӔPaDbAb j B@jB@x@A$AjECUaj ]]aj =jX=j%-(%@'@ BW B$ , B@JB@),@x@AAJEJ^^e=O#| =@{"@@ !%N.W Abfbj! b, B@`By@x@A$AEQ̀c_`b =ja%@䊀@,JPJA# B@aRB@x@A ARE^Ca FaaaR =RK=R%@bF@ @<aNbEb@$j B@jB@x@A$AjE jbbaj =!bia(%-@@ n$( @"DB)B J$B B@JB@@x@AAJEl Jcce=Fo =@T{% "@@ m c >6bb B@`Bc@x@A$AEubdeb =j6`=^"@e4@ m!j%J3J  B@aRB{ B @x@A ARE ffaR =R=R"@@o!RoÔ%"0}@*[bdb j B@jB@x@A$AjEb jggA =j=(%-(!@g@$BǪB  B@JB@@x@AAJE dJhhe=vF =@j{ @He@ * ': @/Ab  bc B@`B,@x@A$AEaijb =j߀=$%@#ހ@ mJݠJA# B@aRB@x@A AREaR FkkaR =RL=R%@@ " @#aҐq0C#"a.Bbb j B@jBc@x@A$AjE(Raj jllaj U=%-%@@!j @"@BBpTBc B@B`x@9 AJ @E Jmme=0 |B ={% "@@ $F @+c AbKb b륱 B@abB@x@A$A E6ɀcnob =jXaj$j" @@, J%J > B@aRB@x@A AREC@appaR =RG="@OC@ c 3"0"@"z`BbBb  B@jBh@x@A$AjE qqaj =jZa%p%@@ rBB JaIJB@x@AAJEQa  Jrre=ҤF =M{% %@@ c Ab Z 6! B@bBh@x@A$AEracstb =j2`=j !@^1@,"J0J c# B@aRBb@x@A AR$E逈uuaR% =R=R"&@@ cC0G1 `HB'b]b j( B@jBt@x@A$Aj)Elbvvaj* =j=j +@T@ ,B B- B@JB@@x@AAJ.E`aJ wwe/=wsFQ0 =@f{% 1@.b@ c iB2bab3 B@`B @x@A$A4Eyaxyb5 =j܀=j 6@ۀ@ m, @7JpڠJA#c8 B@aRBc@x@A AR9EaRy zzaR: =RA=F!;@@ $F @!6S}}B<bb½= B@jB $X@x@A$Aj>E Oaj j{{aj? =jR=j @@Q@~!j @w.`AJABYBB B@JB@!K,@x@AAJCE J||eD=`=hE =@{ F@ @ ?iAGb0b ! cH B@`Bc@x@A$AIEƀ }~bJ =jZa"K@@@SmLJ&JA#JM B@aRB@x@A ARNE(=acaRO =@D=%pP@0@@Ex$F @c+[ Q0aBQb?b R B@B$X@x@A$AjSEjajT =j?=j%pj1U@@ !j @ťVBB$ByW B@JB@x@AAJXE6b> eY=F,Z ="{% .W[@m@ 6$F @ 7A\b ǝ! b륱] B@aB@x@A$A^Eoab_ =j/`="`@K.@ !j$^caJ-J b B@aRB@x@A ARcE aRd =R=R"e@@ s} +d ulum&BfbJb jg B@jB~c@x@A$AjhEPbaji =jݥ=j(%j@:@kB B$  l B@JB @x@AAJmE]Jen=XpFo =c{"p@_@ !%N+c ~Aqbw^b ! b,r B@aB - @x@A$AsE^abt =j~ـ=%p(fu@׀@,vJMJ w B@aRBx @xu 9A ARx @`EkaR aRy =R'=R%z@@c unuoupu}l{ `̠_B{b뒀bĩ| B@a$Bc@x@A$Aj}EKjaj~ = jO=(%-%@N@ n$( @"@BB.B J$B B@B@$ @'@x@AAJEyJe=Fuc =@@i {% "@@ m) @+c 8Abb b륱 B@`B@x@A$AEÀ,b =j,a^"@@,JJA#c B@aRB$X@x@A ARE :a aR =RA=R"@=@ BDTݐpD{a Bb e=FB =m{% "@@  c 5Abb ! B@aBc@x@A$AEbb =jn`="@~l@,cJkJ  B@aRBc@x@A ARE %a aR =R,= @(@ 9Ґ"+cpc@Bb'b B@jB @x@A$AjEfaj =j=j @x@ BBJ$  B@JB@x@AAJEe=F ={% @U@ ! @W Zb  b B@aBc@x@A$AEWab =j`="@0@,JJA# B@aRB@x@A ARE aR =R]ր=R(o@р@ ]}3aZEbb fc B@jBc@x@A$AjE5b jaj =j=(%-(+c@@ $( @.`@BByB B@JB@y@x@AAJ  EEJe==XFh =@{K{% "@F@ B c yAbXb 륱 B@`BB@x@A$AECaBb =j~=^"@ݿ@ m!j*Qy JIJA# `JR- B@aRB @x@A AR EPxaR FaR =R=R" @X{@ U>%+i-lBbzbf B@jB @x@A$AjE3aj.aj =j_7=(%-(%@6@$BB Jc B@JB @x@AAJE^ e=F =@N{% "@@ 7Ab ! ^ B@`B,@x@A$AEbBb =jk`=^%@wi@ mJhJA# B@aRB@x@A AR E!a aR! =R)=R""@$@c ґ0}<1/*#bbb½$ B@jB@x@A$Aj%Ex݀ jaj& =j=j '@^@$(BB$ g) B@JBB@x@AAJ*Ee+=F, = À{"-@7@, .bbj! / B@`B WB@x@A$A0ETb1 =j$Y=j+"%2@W@,3JVJ 4 B@aRBy@x@A AR5E aR FaR6 =R=R%7@@* .1}}?a?w*[8bb j9 B@jB@x@A$Aj:E jaj; =jπ=j%-!<@w΀@=B̀B J$ > B@JB@x@AAJ?Eb J@=@="U|,A = {% "B@X@ y! @1#  ACb ! bD B@aBc@x@A$AjEEBa,bF =j`=j G@(@,HJJ >cI B@aRB@x@A ARJEaRK =Rg=R"L@@ #‘0u *uaBMb&b½N B@jB@x@A$AjOE5ubjajP =jx=j%-(%Q@$@ $( @(@BRBw B$BcS B@JB@x@AAJTE0aJ eU==CFV =6{% "W@1@ c Y2AXbXb! 륱Y B@aB@x@A$AZEByb[ =jhaW \@ɪ@,]J5JA#^ B@aRB@x@A AR_EPca aR` =Rj=H  S;"a@Xf@3, 0}r1}D@z$bbeb@%c B@jB@@x@A$AjdEaj jaje =jg"=j%-(!f@!@ gB#B$ h B@JB@x@AAJiE] Jej=Fk =n{F% "l@ۀ@ @c  Amb!b ! bcn B@aBL6@x@A$AoEbbp =jV`=j q@gT@,rJSJA#cs B@aRB@x@A ARtE a aRu =R=FR"v@@ ! @C+\ 0TґUajBwbjb x B@jBx@x@A$AjyExȠ@Y jajz =jˀ=j%-(%{@T@r|Bʀ B$ } B@JB@x@AAJ~Ee=F| ={% "@9@ W; @6 Abb b B@aBc@x@A$AE?a,b =j="@@@S(!j$^,JJ R `JR, B@aRB@x@A AREaR FaR = E=R"@@ S1tF,Ô [PBb b B@B@x@A$AjErajaj =ju=(%-(%@@ Bbt J B@JB@x@AAJE-aJBe="@| =3{% "@.@ AbAb ! ^ B@aB @x@A$AE'cb =jVa^3@@,cJ"JA# B@aRB@x@A ARE5`a aR =Rg=R"@=c@ c!@UQ bIBbbb j B@jB@x@ =Aj @`Ejaj =j@=j @@ BB$ c B@aB,@x@AAJEB׀e=äFc = À*݀{"@}؀@ )%Nc Bb ! bc B@`B,@x@A$AEɒbb =jR`=+"%@XQ@,JPJA#F B@aRBw5n@x@A ARE aaR =R}=R%@ @  sҐ‘+gґa@*BbFb j B@jB @x@A$AjE]ŀ aj =jȀ=(%-+c@>@$Bǀ̮$  B@JBc@x@AAJEe=eF =Ԇ{% "@#@ @+c Abb b B@aB@@x@A$AEk B@aRB@x@A AREaaR =Rz=R(o@ @!5mHHcb/b f B@jB@lc@x@A$AjEB€jaj =jŀ=j @3@ $( @"DBĀ Bc B@JB@x@AAJE}e=RF =Ƀ{% F"@@ ) @ i~b b B@aB$X@x@A$AEO9ab =j=$j"@@,JNJA# B@aRBL6@x@A ARE]aRFaR =R=R" @q@ +䐃/ aDf bݲb j B@jB@x@A$Aj Ekajaj =jto=j%p1@n@ B0B B@JB@x@AAJEj'aJe=F~c =[-{F% "@(@ ! @6 @{@ c q?bNb ! ^@ B@aB@x@A$AAE46acbB =jY=^(fC@@,cDJ'JA#E B@aRBy@x@A ARFEBaRFaRG =R=R"H@V@ 0ӔPR}@QavDIb¯b jJ B@jB{ o@x@A$AjKEhajfajL =jUl=j M@k@ NBB$ O B@JB @x@AAJPEO$aJeQ=ԤFQR =?*{"S@%@ c BTb ! U B@aB@x@A$AVE߀bW =ja%X@U@,YJJA#Z B@aRB@x@A AR[EVaaR\ =R^=R%]@Y@  I.p %a(lB^bXb _ B@jB@x@A$Aj`Ejajl caja =j=(%-+cb@]@ cBB $ d B@JBc@x@AAJeÈef=vFg =Ӏ{% "h@2π@ ! @1a0/ Aib΀b b j B@aB@x@A$AkExbnbbl =jI`=j m@G@,nJjJA#co B@aRB$X@x@A ARpEaFaRq =R:="r@@c$@t$U}$Qa4Bsbb jt B@jB@x@A$AjuE  @Av =j=%-(%w@۾@$xB@Bµy B@JB ,@x@AAJzEwb J!`={=Fny| =@}{% %}@x@, VA~b/b ! !n@* B@`B@x@A$AE3a "EB =j>=j @@ m, @ lJ J@0 B@aRB@x@A ARE' "_$F = Ա="@/@ 1z %K`%fjcnbbb" at B@B@x@A$AjEej1|!j =j2i=j @h@ ) @.`DBgB J$B B@JBc@x@AAJE4!J`==F{ =$'{F% @r"@#Q! @c nbb ! bc B@aB@x@A$AE܀B =jaj%pj"@B@,JJA#d<R B@aRB@x@A ARESa  AR = }[=R(o@V@,#V }ÔP"0DfbDb½ B@B* @x@A$AjEOj Aj =j=j%-+c@:@ $( @"@BB B$B B@JB@x@AAJE );@==W݄F =Ѐ{% "@ ̀@ J) @+c Abrˀb b륱 B@aB@x@A$AjE\bc B =jnF`= @D@,J;JA# B@aRB@x@A AREj@Y AR =RaRR"@r@ c3_~/D-0@VBb  B@jB@x@A$AjEj#+c =ju=j%- 5`@3A@л@~B1B B@JB@x@AAJEwt bw 2c@==F =ATdz{F% "@u@ y! @K {Abb! b B@`B@x@A$AjE/ aB =j$=j^(f@@,JJ (Rc B@aRB@x@A ARE aR+ AR =R=FR"@@ C} D}8,bb jc B@jB@x@A$AjEb aj j@u% =jf=j @^e@~r$( @"DBdB$Bc B@JB@@x@AAJE J`==0|$X =@ ${% @S@ c >b ! c B@`B@x@A$AEـB =jʙa"@+@, JJA# B@aRB@x@A AREPa FAR =R[X=R%@S@ S%  ȏ`Dfb!b B@jB@x@A$AjE4 j9% =j=(%-(1@ @ rB J$  B@JBc@x@AAJEǀ `==<ڄFc =̀{% "@Ȁ@! @ӟQ AbWb b, B@aB; @x@A$AEAbcB =jtC`=^(f@A@ '*Q#j` yJ@J@/ B@aRB; @x@A AREO@Y  6$F  +$R"@K~ 8,$F!Rc2o Bbb B@B@x@A$AjEֵ,fAj =jZ=(%-(%@@~!j @"@BBB B@JB@٢oc@x@AAJE\qbw`==ݤF|y =@Tw{% "@r@ $F @6 /Ab ! b륱 B@`B? @x@A$AE,a ^ B =j1=j$jB#;@/@,JSJ!c B@aRB @x@A AREj $ R!!AR =="@z@ s`26T ^Bbb@&#a B@jB @x@A$AjE""Aj =j}=j%p% @צ@$ B9B$ Jc B@JBB@x@AAJ Ew_J#*@==-]L =ge{F% "'@`@, Ubb ! c B@aB@x@A$AjEa$%B =jۀ=j @ـ@,JؠJA#F B@aRBy@x@A ARE aR &&AR =R=FR"@@ d`CqSq\aDbb@$j B@jB| o@x@A$AjEMj''Aj =jQ=%-(%@mP@$ BOB J$ ! B@JBc@x@AAJ"E J((`=#=|$ ={% "%@R @ B c aA&b ! ' B@aBF@x@A$A(EĀ)*B) =jaj^+c*@@,+JzJA#F, B@aRB@x@A AR-E;a i A@++AR. =@]C=R"/@>@ "‘ґ"F$* =:B0b!b½ac1 B@Bh@x@A$Aj2E4 j,,Aj3 =j=j%-(%4@@~5B B$ J6 B@JB@x@AAJ7E--`=8=<ńF9 = À{% ":@@ ! @.W A;bVb b< B@`B@x@A$A=EAna./B> =jf. `=j ?@,@,@J4J A B@aRBc@x@A ARBEOy F00 C =R=FR"D@_@ `S`npaX$`aaBEbb jF B@jB@x@A$AjGEՠ!b j1#!jH =ja=j%-(%I@@~JBBK B@JB@x@AAJLE\\"J22`=M=ݤFyN =Hb{% "O@]@ c  APb ! Q B@aB@x@A$ARE#ac34BS =j؀="T@bր@,UJՀJ(RcV B@aRB@x@A ARWE$aR F55ARX =!J= Y@@ ~(}`TF BZbdbj[ B@jB*@x@A$Aj\EwJ%j66Aj] =jN=j ^@^M@ ) @.`AJ_BLB$Bc` B@JB@x@AAJaE&J77`=b=|c = {% d@;@ 5n! @'`, Aebb ! bcf B@aB 4  @'@x@A$AgE ^88Bh =jƀ=G r ^"i@}Ā@@S'$,jJàJ Jk B@A*B,@x@A ARlE }'99ARm =@=R(on@@  +Y xI|}{zar=Bobb@$p B@BQ@x@A$AjqE8(j::Ajr =j<=(%-(+cs@{;@ r$( @"@BtB:B J$Bu B@JB@x@AAJvE ;;`=w= ƒ|yx = À {"y@T@ Azb ȥ! 륱{ B@`By@x@A$A|E)b<=B} =jo*`=j%p"~@6n@@SJmJA#J B@aRBL6@x@A ARE&+a >>AR =@D.="@)@ ByacNb b½ B@B]L@x@A$!1E45n??Aj =j=j%-%@@B|䀃 B$ y B@JB )* @x@AAJE,@@`==;F =@{F%@ @, Wbjb !  B@`B@x@A$AEAY-aABB =jt.`=j @@,J@JA# B@aRB@x@A ARENР@Y CD5\ = +؀=FRc@cӀ@ %  R"a ; bҀb f B@B@x@A$AjEՋ/bjDDAj =je=%-@Î@ B!B$  B@JBc@x@AAJE\G0JEE`==ݤF =LM{% "@H@ ) @ ; b  b B@aB@x@A$AE1aFDB$^ =j€=j^+c@Y@,JJA#(R B@aRB@x@A AREy2aR HA6!R =R=R"@|@ $F!?7RNaxV6albhbj B@jB @x@A$AjEw53jIIAj =j8=j%-(+c@U@$B7 J B@JB@x@AAJE JJ`==| ={% "@:@ *" @tAbb ! b B@aB@x@A$AE4bcKLB =jl5`=%@ k@,JwjJA# B@aRBL6@x@A ARE#6a MMAR =RA+=%p@&@cjXXAj =j5=(%-(%@:@ $( @(@BB4 B$B B@JB c@x@AAJE YY`==d|W =@{% "@@ ) @+c @b~b b륱 B@`B@x@A$AEi?bZ[B =jvi@`=j"@g@,JDJA# B@aRB@x@A AREw Aa \\AR {'(=R"@#@ 3$paQp[SEDfb"b B@jB`x@9 Aj @`Eۀ j]]Aj =j߀="@ހ@ BEBJ B@aB@x@AAJ EBb J^^@= =?`=c =p{%  @@ ! @( }B b b b  B@aB@x@A$AjE SCaj_`B = `0D`=^%@@, JJ  B@B@x@A AREʀ aaAR =Rр=R%@(̀@ CJqKHWBb̀b½ B@jB@x@A$AjEEb jbbAj =j/=(*(+c@@ r$( @"@BB뇀BJ B@JB@x@AAJE&AFaJ Jc"q@==Fh =G{% "!@cB@  c >A"b¡ c# B@aB@x@A$Aj$EcdeB% =jռGaj c&@7@,c'JJ c( B@aRB$X@x@A AR)EsHa ffAR* =Rg{="+@v@ cSH@B:EÔP@B,b2b@&j- B@jB* @x@A$Aj.EA/IajfggAj/ =@݀2=%p(%0@@~n1B}1 B2 B@B@x@AAJ3Eꀈ hh`=4=HF5 ={% %6@@ y! @c 'FA7bcb! b 8 B@aBc@x@A$A9ENJbijB: =jkfK`=j ;@d@,<J9JA#F= B@aRB@x@A AR>E[La kkAR? =R %="@@d @ ccPQ" (RBAbb fB B@jBc@x@A$AjCE؀jllAjD =jf܀= E@ۀ@ $( @ūcFB"BBG B@JB@x@AAJHEiMmm`=I=FJ =Q{% K@@ B c DBLbb iXFM B@aB m8p @'@x@A$ANEONanoBO =jO`=j P@~@,QJ J R B@A*B@x@A ARSE ppART =R΀=R(oU@ɀ@ cs`#G7Ft"*K.BVbib@'W B@jBy  @x@A$AjXEPb jqqAjY =j=j%-(!Z@l@$[B̄B\ B@JB@x@AAJ]E >QaJ Jrr`=^=PF_ =C{% "`@E?@ c $Aab  b B@aB@x@A$AcEcstBd =jRa(fe@@,fJJ Fcg B@aRBc@x@A ARhEpSa uuARi = Xx="j@s@* " R URT.Bkbb½l B@BF@x@A$AjmE%,TjvvAjn =j/= o@ @ cpBj.(J/l q B@JB@x@AAJrE瀈 ww`=s=-FFt ={% u@@ 5n c BvbHb cw B@aB@x@A$AxE3UbcxyBy =jecV`=^%z@a@,{J2JA#| B@aRB@x@A AR}E@Wa zzAR~ =R!=R%@L@ c+] "+Q/tFBbb j B@jB* @x@A$AjE j{{Aj =jGـ=j%-(+c@؀@ BB B@JBc@x@AAJENX||`==ϤF =>{% "@@ y! @ $"Ab  B@aB@x@A$AELYa*}~B =j Z`="@k @,J JA#F d* B@aRB@x@A AREÀ@Y FAR =Rˀ=R"@ƀ@ c01zU@ BbVb j B@jBc@x@A$AjEi[b jAj =j=( @Q@~$( @r(AJB B B@JB@x@AAJE:\J`==qMY`= =@{%p@&<@ 5n c oAb;b c B@aBy@x@A$AEv#A =j]aj"@@,JqJA#(> B@aRB@x@A AREm^a FAR =R.u=R%@p@ y-`UÐ-A HBbob j B@@]B}@x@A$AjE )_aj jAj =j,="@+@rBNBB B@JB@x@AAJE J`==Fc =}_a% @@ c +Bb-b B@aB@x@A$AE`a,@% =jC`a`=^%@^@@Sf'%JJA#J B@aRB@x@A ARE%ba AR =@=R%@-@ %|p1Ua)0 bb@$j B@B@x@A$AjE jAj =j(ր=(+"(!@Հ@ rBԀB J B@JB@=$X@x@AAJE3c`==F =@{% "@n@ B$F @ Ab  B@`B@x@A$AEIdacB =j e`=j%pj%@8@ m!j @JJA# B@aRB5n@x@A ARE FAR =RrȀ="@À@ cQ/.daBb;b j B@jB5n@x@A$AjEN|fb2 jAj =j= @;@~B~ J `y B@JBc@x@AAJE7gaJ J`==UJF/ =={%pF%@9@ ! @?m :Bbp8b! by B@aB@x@A$AE[B =j~ha%pj%@ޱ@,JJJA# B@aRB@x 9A AR @`Ehjia AR =Rr=R"@}m@ c "% `̠UBblb  B@a$Bc@x@A$AjE%jjAj =j{)= @(@ $( @"AJB7B,B B@JB 5@AAJ @Ev `==F =Z{% F"@@ ) @+c Abb b륱 B@abB -  @'@x@A$AEkbB =j]l`=j^"@k[@,JZJ B@A*Byb@x@A ARE ma AR =R=R"@@ y1 .p}0ykBbrb½ B@jBV@x@A$AjN`E jAj =j!Ӏ=j%-(1@Ҁ@$BрB J) c B@aB@x` =AJ @Enb J`==F ={% "@T@c QAH ! c @abB@x@A$A EFoacB =p`=% @9@,JJA#c B@aRB5n@x@A ARE AR =Rcŀ="@@.` 0taBb,b j B@jB; @x@A$AjE2yqb jAj = |=%-(%@@~cBw{(B$  B@B@)@x@AAJE4raJ`==:GF$X =@:{% "@5@c 1 AbYb !  B@`B@x@A$A E@ B! =j=^%"@<@,#JJ $ B@aRB @x@A AR%Eǫsb RAR& =Rp=R"'@ˮ@ o!!% 0䐃H60PB(b7b j) B@jB@x@A$Aj*EMgtaj jAj+ =jj=(%-(%,@!@n-BiB$ . B@JBc@x@AAJ/E"uJ`=0=ܤFW1 =({% "2@$@ @A3bp#b b4 B@aB@x@A$A5E[ހB6 =jva^%7@@,8JZJ >9 B@aRB@x@A AR:EhUwa FAR; =R]=R"<@pX@ #ßPa"Q` B=bWbf> B@jB`@x@A$Aj?ExjAj@ =jo=(%-(%A@@ rBB+B JC B@JB@xA 9AAJD @Ev̀ `=E=F F =fҀ{% "G@̀@ X1!Hbb! I B@abB@x@A$AJEybAK =j#Hz`=^%L@F@,MJEJA#cN B@aRB@x@A AROE ARP =R{$R"Q@ @3-"8$p$a FRbvb jS B@jBB@x@A$AjTE2 jAjU =j!=( V@}@~WBݼB J `cX B@JB@x@AAJYEv|bw J`=Z=F[ =|{F% \@Rw@ @c F]b ! bc^ B@aB@x@A$A_E1}aB` =j=)j%a@1@,bJJ c B@aRB@x@A ARdE~aR ARe =RR=R%f@@ C`9`p%A Hogbb½h B@@]BB@x@A$AjiE2daj jAjj =jg= k@f@ lB^BJ$ m B@JB@x@AAJnEJ`=o=:2|p =%{% F"q@ @ c RBrbUb s B@aB@x@A$AtE@ۀ Bu =j߀=j^%v@<ހ@,wJݠJA#x B@aRB,@x@A ARyEƖARz =Rn=R"{@ϙ@ S%$e:puvaMB|b;b } B@jBF@x@A$Aj~EMRjAj =jU=j%-(/)@#@ rBT B B@JB@x@AAJE J`==ܤF ={% "@@c Abpb B@aBc@x@A$AE[ɀB =ja%@@,JaJA# B@aRBy@x@A AREh@a AR =R$H="@xC@ cuw~TT0a`hBbBb B@jB  @x@A$AjE jAj =j=j @@ B;B B@JB@x@AAJEvb J`==FL6 =n{% @@ c ĚBbb! B@aBb@x@A$AEraB =j 3`="@k1@,J0J  B@aRB@x@A ARE AR =R= @@ cs:uv%eBbb j B@jB,@x@A$AjEbAj =j!= @{@ BݧB F B@JB5n@x@AAJEaaJ `==sF = g{% @Xb@ ! @ sBb  B@aBc@x@A$AEacB =j܀=^+c@-ۀ@,JڠJA# B@aRByc@x@A AREaR AR =R`=R+c@@ !8%; ʇBb'b½ B@jB@x@A$AjE2OjAj =R=(%-(1@@ n$( @n"@BBzQ J)B B@JBc@x@AAJE J`==:|c ={%p"@ @ c nuAbUb  B@aB@x@A$AE@ƀB =jea^"@Ƅ@,J2JA# B@aRB@x@A AREM=a AR =RD=R"@E@@ 1 D!:dpX[xBb?b B@jB@x@A$AjE jAj =j\=(%-(%@@$BB J B@JB@x@AAJEZ`==ܤF =O{% "@@ ': @c Ab ! b B@aB 4 @x@A$AEoaB =j0`=j c@t.@,J-J  B@aRB@x@A ARE怈 FAR = =R"@@ o"!w6$b_b jc B@B @x@A$AjEub jAj = ="@]@!j%(AJBBB$Bc B@B@x@AAJE]J`==}pF{* =c{% @6_@  c Ab^b c B@aB@x@A$AEacB =jـ=j$j"@؀@,5nJnנJA# B@aRB@x@A AREaR FAR = V="@@o! ZS":6 b bf B@B@x@A$AjELjAj = O=%-&@N@ rBWB J!  B@x@AAJEJ`==| = {% (@@ ! @b:b b  B@aB@x@A$AE$ÀcB =jVaj$j% @@,c J#JA# B@aRB @x@A AR E2:a AR =RA="@2=@ o"!D"T:aBq bB@B? B@JB@x@AAJ@EaJ `=A=xB =o {% "C@@ c ADbb! 륱E B@aBc@x@A$AFE cBG =j8a"H@~@,IJJA#J B@aRB@x@A ARKE7a ARL =R>= M@:@ :9a";/"aHyNb9b½O B@jB@x@A$AjPE jAjQ =j*=%p(%R@@ SBB cT B@JB @x@AAJUE$`=V=F; W ={% %X@_@ ! @c mYb Z B@aB@x@A$A[EiacB\ = `)`=^(f]@2(@,^J'J _ B@B@x@A AR`E FARa =Rk=R"b@@5nI~(}q"ulDcb0b jd B@jB o,@x@A$AjeE?b jAjf =jϟ=(%-(%g@,@ n$( @n"@BhBBi B@JBc@x@AAJjEWJ`=k=GjFl =]{%p"m@Y@ m c AnbbXb co B@aB "@x@A$ApEMaBq =j|Ӏ=^"r@р@,sJGJ t B@aRBL6@x@A ARuEZaR FARv =R =R"w@f@c$4"䐃(a9BxbҌbfy B@jBc@x@A$AjzEEjAj{ =jaI=(%-(%|@H@$}BB J) c~ B@JB@x@AAJEgJ`==F =X{% "@@c 0Abb  B@aB@x@A$AEB =j}aj c@y{@,JzJA# B@aRB@x@A ARE3a AR =R;=R"@6@,#)Pn-0d"BbdbA;j B@jBQ@x@A$AjE@Y jAj =j="@p@ƹcBBB B@JB@x@AAJE b J`==F ={% @A@ c ?Bb  B@aB@x@A$AEfacB =j&`=j @%@,J$J >@ B@aRBL6@x@A ARE݀AR =RQ="@@3GcB!b½ B@jB o@x@A$AjE$b fAj =j=*(+c@@ rBpJ B@JB@x@AAJETaJ%`==,gF =Z{% (@U@ ! @ [AbKb b B@aB@x@A$AE1a  B =jYЀ=j @΀@,J(J  B@aRBy@B.Q @x@A ARE?aRAR =R="@_@ CmpL6$Ea=Bbˉb j B@jB@x@A$AjEBaj2 !_Aj =jNF= @E@~/$( @4AJBB J B@JB@&Q@x@AAJEL J'N@==ѤFv, =@=a% @ c %Ab !  B@`B@x@A$AjEӹ,b =jzaj @fx@ m, @ JwJ@0 B@aRB@x@A ARE0a aR =R8=R(o@3@!RoSV np3]LBbAb½ B@jB@x@A$AjEg j r =j=j%p(+c@E@~B B$  B@JBc@x@AAJEb Je=oF$X =歀{% "@+@c R5Abb!  B@aB @x@A$AEucab =j#`=$j(f@!@,J_J R B@aRB@x@A AREڀ   aR =RB=R"@݀@ c('LU1KoBbb½ B@jB@x@A$AjE b j  aj =j=j%-%@瘀@~BIB$  B@JB@x@AAJEQaJ J  e=dFzc =W{% "@R@; Ab,b!  B@aB@x@A$AE ac  b =jC̀="@ˀ@,cJJ c B@aRB@x@A ARE$aR aR =R؋= @0@  s U+ccJbb½ B@jB@x@A$AjE?jaj =j3C=%-(%@B@ BAB ; B@JB@@x@AAJE1  >=F$X =@a% %"@nW @W R Ab  B@`By@x@A$AjE,b =jvajG b,ژ@/u@,JtJ c B@aRB@x@A ARE-a aR =Rz5=R" @0@ !%($""D,DaB bAb½ B@jB@x@A$Aj EL j r =j=(%-(%@'@ nB뀃J$  B@JB@@x@AAJEӤ@==TF =@ê{%p"@@ 5\ @+c EAbob bc B@`B@x@A$AjEZ`ab =jw `=^K@@ m!j$^JDJA# B@aRBzy@x@A AREgנ@Y FaR =R ߀=R"@{ڀ@ y) (dpK@p'B bـbf! B@jB@x@A$Aj"Eb jaj# =v=(%-(C$@ѕ@ R%B2BB& B@JB@x@AAJ'EtNJe(=`F) =iT{% "*@O@ B! @K A+bb ! b , B@aB@x@A$A-E b. =j=j%pj%/@ @,0Js J (1 B@aRB @x@A AR2E FaR3 =R1̀="4@Ȁ@ - %aB5bǀb j6 B@jB~c@x@A$Aj7E aj8 =j= 9@@ $( @"AJ:BUB J$B; B@JB@x@AAJ<E<Je== > =B{% F%?@=@ W) @+c ;AA@b+b bcA B@aBc@x@A$ABE bC = `Aa D@@,EJ J F B@B@x@A ARGE$oa !!OH =Rv=R"I@ r@ B%K]10#azBJbqbK B@jBz@x@A$AjLE*aj j""ajM = B.=j%-(+cN@-@ OB,BJP B@B@x@AAJQE1 J##eR=FɂS = À{% "T@m@ S! @+c BAUb  b V B@`B 8p@x@A$AWEb$%bX =ja`= Y@G`@@Sj' @ZJ_J Jc[ B@aRB$X@x@A AR\Ea &&aR] =@| =!^@@ " @%aB_b9b½` B@B @x@A$AjaELԀ j''ajb =j׀=j c@9@~!j @ūcdBր Be B@JB@x@AAJfEӏb> J((eg=TFBh =˕{% i@ @ 5n$F @+c o2Bjbob bck B@aB "@x@A$AlEYKac)*bm =j `="n@ @,coJ\J p B@aRB@x@A ARqEg @Y ++aRr =Rʀ=%ps@sŀ@ ӝ@%1@8BtbĀbĩu B@jB @x@A$AjvE}bf,,Lw =jv=j%pj+cx@Ӏ@ yB2BJz B@JB@x@AAJ{Et9aJ --e|=F~3:  K >} =`?{% +c~@:@ @+c Abb b B@aB,@x@A$AE./b = `#a"@@,JJ 4b4c B@B@x@A AREla 00aR =Rs=R"@o@ 6t"+fW1Bb}nb f B@jB@x@A$AjE'j11aj =j+=( @u*@rB)B ` B@JB@b& @'@x@AAJE 22e=Fy =@@{% @R@F c Bb ! , B@`B@x@A$AEb34b =j^`=^.@/]@ m!j*Q`# ! !% ARJ\J  `JR- B@aRB@x@A AREa 55aR =Rb=R%@@ cw OanAjb*b½ B@jBc@x@A$AjE1р j66aj =jԀ=( @Ӏ@ BaBJ$  B@JBc@x@AAJEb J77e=9F ={% @@ c ]BbTb  B@aB W'Z@x@A$AE>Hac89L =jg`=^%@@,J5J B@aRB@x@A AREL@Y: ::aR =R ǀ=R%@\€@'!(T:@T>Bbb½ B@jB@x@A$AjEzb j;;aj =jW~=j%-(1@}@ `@"@BBB B@JBB@x@AAJEY6J<<$5=ڤF =M<{"@7@c Ab ! B@aB@x@A$AjEc=>b =jaj$"@_@,J˯J (Rc B@aRB@x@A AREha* ??aR =Rp=FR%@l@c%TJE /a,kcbmkb j B@jB o@x@A$AjEt$aj @@Ho =j'= @W@$B&µ$ c B@JBc@x@AAJE JAAe=|F ={% F"@8@/ kcbb c B@aB "c@x@A$AEbBCb =j[`=j^%@Z@,JtYJ B@aRBB@x@A AREa DDaR =R<="@@#kYEkeaHobbf B@jB @x@A$AjE jEEaj =jр=%-(+c@Ѐ@$BZB J B@JB@!K@x@AAJEFFe=F =@{% %@ڊ@ `@W+c ZAb8b B@`B@x@A$AE#EaGHb =jD`= @@ m!j @JJ c B@aRB@x@A ARE1 FIIaR =RÀ=R"@=@ c3%Se%aaBbb j B@jBc@x@A$AjEwb jJJKc =j?{=j @z@ƹByBB B@JB@@x@AAJE>3JKKe=EF$X =@.9{% @|4@ ! @( #Bb  b B@`B@x@A$AEcLMb#ja%pj+c@P@, JJ  B@aRB`x@9 AR @`Eea FNNaR ={m=R%@h@ cCTWT.tT`̠BbBb½ B@a$Bc@x@A$Aj EY!jOOaj =j$=j @@@ $( @"AJ B# B B@JB@x@AAJE PPe=aF = À{% F"@ހ@ ) @ mAb|݀b b B@`BB@x@A$AEfbcQRH =jX`="@V@,cJUJA#j B@aRBv B@@x@A AREta SSaR =R)= @@S%t+_Kk0 bb!&' B@jBy@x@A$AjEʀ jTTaj =j΀=j%p(! @̀@ !BCB" B@JB@x@AAJ#EUUe$=FQ% =n{F% %&@@m @ q'bb ! b ( B@aB@x@A$A)EBVVE* =jF=j^(f+@ E@,,JxDJA#n- B@aRB@x@A AR.E FWWaR/ =RVaR"0@@ ccvEqa‰D1bb j2 B@jB]L@x@A$Aj3E jXXaj4 =j=j 5@@0k6BZB$  7 B@JB,@x@AAJ8EtbwJYYe9=F$X: =z{:% ;@u@  c f{B<b8b! y= B@aB@x@A$A>E#0ZZb? =j4=j @@73@,AJ2J B B@aRB@x@A ARCE F[[aRD =RG=FR%E@@ cs%Td%@ 1BFbb@(jG B@jBxc@x@A$AjHE0\\ajI =j=j%-(+cJ@@$KBt J$ L B@JBc@xJ 9AAJM @EbJ]]eN=FlO =h{F% "P@c@$X nAQbSb R B@abBc@x@A$ASE>^^bT =j"=^+cU@>!@,VJ JA#FcW B@aRB$X@x@A ARXEـ@Y __aRY =R=R"Z@܀@ ckYE @-B[bIb \ B@jBc@x@A$Aj]EK``aj^ =jӘ=j%-(%_@0@ rc`B Ba B@JB@x@AAJbEPS aa$5c=ڤFcd =V{% "e@ R@ c AfbnQbcg B@aB >"@x@A$AjhEY aybcbi =j̀="j@ʀ@,ykJ\J (Rl B@aRB@x@A ARmEfaR ddaRn =R= o@z@ ,%EqKe,dBpb慀bjq B@jB{ @x@A$AjrE>jeeajs =jB=j t@A@ uBABv B@JB@x@AAJwEt> ffex=F* y =Xa z@ ! @ B{bb ! b c| B@aB "@x@A$A}E,ygh"~ =j&vaj"@t@,cJsJ F B@aRB@x@A ARE-a iiaR =R4=R(o@ 0@ T+Y%E$cVBbx/bj B@jB; @x 9A$Aj @`E耈fjjaj =j=( @t@~$( @.`AJBB$B B@aB@x@AAJEkke=F ={% c@Q@ y Ab !  B@aB@x@A$AE_almb =j `=^"@#@,JJA# B@aRB* @x@A ARE nnaR = =ހ=R%@ـ@ SN|QtBb b j B@B* @x@A$AjE0 b jooaj =j=(%-(1@ @ Bp  B@JB@)B@x@AAJEM Jppe=8`Fu* =@S{% "@N@ c 1AbSb  B@`B@x@A$AE> aqrb =jjɀ=^%@ǀ@,J8JA#c B@aRB @x@A AREK aR FssaR =R=R"@S@ (}O[}@aBbbf B@jB !@x@A$AjE;jttaj =jJ?=(%-(%@>@$B B J)  B@JB@!K@x@AAJEY uu$5=ڤFy =@M{% "@@  >ɂb !  B@`B@x@A$AjE߲bcvwb =js`=jt%@rq@ m, @JpJ  B@aRB@x@A ARE)a xxaR =R1="@,@ N` EDbeb½ B@jB@x@A$AjEs jyyS =j=j%-(%@N@$B瀃 B$  B@JB@x@AAJEzze={F =ꦀ{F% %@7@ , @ Abbj! b B@aB@x@A$AE\ac{|b =j`=j%pj%@@,JJA# B@aRB@x@A ARE F}}aR = Cۀ=FR"@ր@ %Sre$U0D B$  B@JB"Mc@x@AAJEG!Je $`ZF! =@=qM{% "@H@ ! @(B#bb ! b$ B@`B@x@A$A%E"acD^& =j9À="'@@)f(JJ IJ) B@aRB@x@A AR*Ez#aR FaR+ =@=R.!,@}@ #"T"tU/b\B-b|bĩ. B@Bc@x@A$Aj/E5$aj jB0 =j,9=(%-(11@8@~$( @4@B2B7B J$B3 B@JB@x@AAJ4E" Je5=F6 ={% "7@\@ l8b ! 륱9 B@aB@x@A$A:E%b@Y b; =jU="<@@,=J!JR> B@aRBB@x@A AR?E0h&RDF@ =Ro=R"A@4k@o @o34"DD~hoBbjb jC B@jBc@x@A$AjDE#'jajE =G'=j%-(%F@&@ Rc} B@aRB@x@A AR~Ee1a FaR =Rl=R"@)h@ c101n'bgb j B@jB]L 5@A$Aj @`E 2aj jaj =j$=j @q#@~n$( @(DB"B B@aB@)@x@AAJE" Je=FB =@"{% @_݀@ ; @1! @# +c Ab ! bc B@`B@x@A$AE34@Tcb =jW4`="@(V@ m!j%JUJ(R B@aRB$X@x@A ARE5a aR =Rb=!@@ c sÐ-@a$&bj B@jBB$A$Aj @`E=ʀ jaj =j̀=j%p`k3C@@~B̀ B$  B@aB@x@AAJEą6@==EF ={% (@@ ! @4 +b`b b B@aB@x@A$AjEJA7ac[& =8`="@~@SJMJ J B@ Bc 5@A AR @ 5\ FAR =@`== @X@ ccܚDbĺbĩ B@B ox@x@A$Aj$`DEs9b jaj =jow=j%-(1@v@ $( @ @ (@BB+B B@JB@x@AAJEe/:aJ Je=F, =b5{F% %@0@h xbb! 륱 B@aB "@x@A$AED^ =j ;aj^"@k@,JרJ  B@aRB@x@A AREaBbĥb½%  jB@x@A$AjE^Gjaj =jWb=G@ j%-jC %@a@ $( @K f BBBc B@JBc@x@ =AJ @EeHJ@==F;  =] {% % @@ B$F @+c _A bb ! b륱 B@abBy@x@A$Aj EՀcH =jIa%pj"@s@,JߓJA# B@aRB@x@A ARELJa aR =RT=R"@O@ ﵌Uu䐃"}paBb]b B@jBQ@x@A$AjEKjaj =j =j%p%n@m @ B B$  B@JB@x@AAJE e=քFB =ɀ{% "@Bŀ@m @+c 6@Ab ! bc B@aB@x@A$A!ELbcb" = `?M`="#@ >@,$J=J % B@Bc@x@A AR&E G' = J= (@@ }ZT w4B)bb jc* B@BB@x@A$Aj+E"Nb jaj, =j=j%-(%-@ @~.Bj B$ / B@JB@x@AAJ0EmOJe1=*F2 =s{F% %3@n@  c m4bDb 5 B@aBc@x@A$A6E/)PaA7 =jK=j^+c8@@,9JJA#c: B@aRB @x@A AR;E=QaR FaR< =R=R"=@Q@ D"uu ҽy*>bb j? B@jB@x@A$Aj@E[Raj jajA =jC_="B@^@CB]BBD B@JB@@x@AAJE%  EJSJeF=ˤFG =@{>{% H@@ * ': @c #GIbb b J B@`B@x@A$AKE EL =js׀=j M@Հ@@Sm' @NJAJ JO B@aRBz B  @xM 9A ARP @`EWT rQ =@`= ="R@d@u "SPSuS`̠otSbАb T B@B/@x@A$AjUEIUjajV =jbM=*(+cW@L@ $( @(@BXBB Y B@JB@x@AAJZEeVJ@=[=mӃnb\ =U {% (]@@ $F @ {A^bb bc_ B@aB@x@A$Aj`ED^a =jWaj%pj"b@v@,cJ~J nd B@aRB@x@A >eE7Xa aRf =R?="g@:@q$S"S#S#vahbibi B@jB*@x@A$AjjE jajk =j=%p%l@\@$mBBJn B@JB c@x@AAJo%  EYb Jep=Fw,q =@{{% %r@A@ m @ 8Asb  b t B@`B@x@A$AuEjZa!v =j*[`=j w@ )@ m' @cxJ(JA#y B@aRB@x@A ARzE aR{ = H=R"|@@ #wu’u*u@B}bb j~ B@B@x@A$AjE!\b@Y jaj =@݀= @@ `@(AJBn B B B@B@١%` AJ @EX]aJ Je=)kF =@{^{% @Y@ $F @+c !6AbDbe `bc B@`B@x@A$AE/^acb =j]Ԁ=j%pj"@Ҁ@,J*JA# `R- B@aRB@x@A ARE<_aR aR =R֒=R%@8@ 3Ғ"6 u "a8Bbb½j B@jBz@x@A$AjEF`jaj =jGJ=j @I@  `@r"AJBB$ B B@JB@x@AAJEJaJe=ˤF =:{%pF"@@c yAb ! y B@aB@x@A$AEѽKc =j~ba @c|@,J{J  B@aRB@x@A ARE4ca aR =Rx<=R"@7@ CS6r N&BbBb½ B@jBt@x@A$AjEe jaj =j=j%-(1@Q@~B B$ g B@JB@x@AAJEd@==mF =ܱ{% "@&@ `@c Abb  B@aB $X@x@A$Aj%  Ergeab =j'f`="@&@,Jm%J@ B@a B@x@A AREހ FG =R2= @@ o @3S"X t aBbb@$j B@jBz =@'@x@A$Aj%`DEgb jaj = {==j%-(%@㜀@~BFB B@A"B@@x@AAJ%`EUhJe=hFB =@{y[{% %@V@6 @`Ab)b ! b B@`B@x@A$AEiacb =j;р="@π@ m'$^JJ(> B@aRB@x@A ARE!jaR FaR =R=R"@@ ccp %ucC%a bbj B@jBc@x@A$AjECkjG =j$G=( @F@ rcBEB J$ c B@JBc@x@AAJE/ e=Fc = À#la @j@ $F @6 b ! bc B@`B c@x@A$A%  E1 B =j_=j%p3@½@ 5!j @J.J j B@a B/@x@A ARE@)@~?Bڀ B$ c@ B@JB@@x@AAJAEГ}  eB=QF; C =@{% %D@ @c Eblb cF B@`B@x@A$AGEWO~ac bH = ``=^(fI@ @ m2 @yJJZJ K B@B@x@A ARLEdƠ@YA FaRM =R ΀=R"N@lɀ@ @NDObȠb@%ĩcP B@jBF@x@A$AjQE끀b jajR =j{=(%-(%S@ׄ@ wTB7BJU B@JBc@x@AAJVEr=JeW=FcX =ZC{% "Y@>@ $F @ =!Zbb b [ B@aB@x@A$A\Ecb] =j#a^%^@@,_JﶠJA#F` B@aRB@x@A ARaEpa FaRb =Rw=R"c@s@ (2azBdbzrb@$je B@jB@x@A$AjfE+jajg =j/=( h@u.@$iB-B Jj B@JB@x@AAJkE el=Fm ={% n@N@ ': @6 Bob Z b p B@aB@x@A$AqEbbr =jb`=j+"j%s@)a@,tJ`JA#Fu B@aRB@x@A ARvEa aRw =Rf!=R%x@@ o"!﵌aByb$b½z B@jB B@x@A$Aj{E.Հ jaj| =j؀="}@@ !j%"AJ~Br׀J B@JB@x@AAJEb Je=6F ={% F"@@h .AbQb  B@aBh@x@A$AE>e=F = À :{% @]5@ c Bb   B@`B @x@A$AE,?@b =j̯aj%pj%@.@,JJA#F B@aRB@x@A AREfa FAAaR =RMn=R% @i@  cipWuij<j3B!bb j" B@jB@x@A$Aj#E;"4  jBBaj$ =j%="%@@c&Bw$B' B@JB@x@AAJ(E JCCe)=GF** ={% F"+@ހ@ 5n) @ VB,b^b b - B@aB - @x@A$A.EIbcDEb/ =jaY`=j 0@W@, 1J0J 2 B@aRB@x@A AR3EVa FFaR4 =R ="5@f@ s`$ppaCB6bbf7 B@jBb@x@A$Aj8E jGGaj9 =jeπ= c:@΀@ r;B!B J< B@JB@x@AAJ=EdHHe>=Fz8/|; `A? = X{% @@@@a! @.W gBAbb b B B@`B@x@A$ACEBacIJbD = `=j E@q@,cFJJ cG B@aRBz"vB@x@A ARH' / `E FKKaRI =R="J@케@  t䐁@BKbXb jL B@a$B@x@A$AjMEub2 jLLajN =jy= O@jx@~ƹ$( @.`AJPBwB JQ B@JB@x@AAJRE1aJ JMMeS=CFT =6{% U@C2@ c  mCVb ! yW B@aB* @x@A$AXENObY =ja Z@@,[JsJA#g\ B@aRB@x@A AR]Eca PPaR^ =R6k=Rc_@f@ D<T B@aRBwc@x@A AREc]a FnnaR = e="@_`@ $F!opTq0 uBb_ B@Bnb 5@A$Aj @`E jooaj =j=%-(%@@ r!j @(@BB>B,J B@aB@)$X@x@AAJEqԀ Jppe=F =@]ڀ{% %@Հ@ ! @pAb b b륱 B@`B@x@A$AEbcqrb =jP`=j$j"@~N@,JMJA# B@aRB|@x@A AREa ssaR =R="@ @C#Srvw]aBbq b½ B@jB@x@A$AjE€2 jttaj =jƀ=%@qŀ@~$( @"@BBĀB J B@JB@@x@AAJE~uue=F =@{% F%@N@ 5\ @ dAb ! b륱 B@`B@x@A$A(  E9av*xA!f =j=j @ @ m!j @JJ@ B@a B@x` =AR @`EaR FxxaR = ?`=M=F!@@y1 p҃aӔ@ .;Bbb½ B@Bx@x@A$Aj E-laj jyyaj =jo=j%pj% @@~ Byn B$  B@JBc@x@AAJE'Jzze=5:|5n =-{% `3E@(@ ! @ AbPb b B@aBy@x@A$AE; {$% =j="@/@,JJA# B@aRB#B @x@A ARE|+!R =Rf=R(o@ơ@ #[`p ]{Bb2b$ B@jB{ y@x@A$AjEHZj}}aj =j]=(%-(%!@@ $( @ūc"B\ # B@JB@x@AAJ$EaJ > ~~e%=פFy& = i{% "'@@ y) @+c A(bob b륱) B@`Bc@x@A$A*EVрb+ =ja^",@䏀@,-JPJA#rc. B@aRB- 5@A AR/ @`EcHa aR0 =RP=R"1@kK@3D"zu `̠B2bJbjc3 B@a$Bژ@x@A$Aj4Ejaj5 =jn=(%p(%6@@$7B*B J8 B@JB@x@AAJ9Ep e:=F* ; =eŀ{% "<@@ 5n': @ 1A=b b ! b > B@aB (,@x@A$A?Ezbb@ =j;`=^%A@~9@,BJ8J C B@aRB@x@A ARDE aRE =R=R"F@@ Ct,4 tZBGbb jH B@jBy@x@A$AjIEb jajJ =j=j K@o@ $( @"AJLBϯB$BM B@JB@x@AAJNEiJeO={F{ P =n{"Q@Hj@ * c /ARb  S B@aBy@x@A$ATE$acbU =j=jt"V@@,WJJA#X B@aRBv@x@A ARYEaR FaRZ =RX=FR%[@@ S.tt/aB\bb½] B@jB o@x@A$Aj^E-Wjaj_ =jZ=%-+c`@@$aBqY Jb B@JB@x@AAJcEJed=5%|e ={% "f@@ ': @c =AgbPb bh B@aB@x@A$AiE:΀cbj =j`aj^%k@@,lJ-JA#m B@aRB7B@x@A ARnEHEa aRo = p M="p@dH@ c+_ "y`a >BqbGbr B@B@x@A$AjsEjajt =jO= u@@$vBB Jw B@JB@&@x@AAJxEU ey=֤Fxcz =@F€{% {@@ c !B|b ! } B@`B@x@A$A~Ewbb =j8`= @o6@ m!j @,J5J  B@aRB@x@A ARE aR =R=R(o@@ sÔ@%U7t-QPuBb^b jc B@jB@x@A$AjEpb jaj =j=j @\@~) @K +BB$B B@JBc@x@AAJEeJe=xxF =!k{% @2g@ c Abfb ! c B@aB@x@A$AE~!aFb =j=%pj"@@,@J}ߠJA# B@aRB@x@A AREaR FaR =R9=R%@@ , bUqqt 4^BbbA;j B@jB@x@A$AjETjaj =jW=j%-%n@V@ B^B$  B@JB@x@AAJEaJ> e="| ={% "@@ , Ab5b ! 5n B@aB@x@A$AEˀcb =jVa"@@,cJ"JA#Fc B@aRBc@x@A ARE-Ba(aR =RI= @9E@ 텱``$aBbDb@$j B@jB@x@A$AjEfaj =j8aj%-(%@@ B  B@JB@x@AAJE: e=F =&{% %@u@ * ! @ vAb  b B@aB@x@A$AEtbb =j4`="@H3@,J2J F B@aRB@x@A ARE aR =Ru=R"@@ Vqt'+aA Bb?b(½ B@@]B$X@x@A$AjEUb jaj =jժ=( @2@ $( @.`AJBJ)Bc B@JB@@x@AAJEbJe=]uF =@h{% @d@ 5\ @ Abxcb bc B@`B@x@A$AEcat b =j"=j"@W!@ m!j @J JA# B@aRBB@x@A ARE RaR =R="@܀@ %6% Eqa BbYb j B@jBF@x@A$AjEpb ]aj =j=%-(+c@_@JtJA#c? B@aRB@x@A AR@Ena aRA =R2v=R"B@q@y3Y+]qBCbpbD B@jB; @x@A$AjEE*jajF =j-=j%-(%G@,@ $( @"@BHBVBI B@JBW@x@AAJJE eK=FL ={"M@@ ;TANb4b! O B@aB@x@A$APEbbQ =jOa`="R@_@,SJJA#T B@aRBxJeBc@x@A ARUE,a aRV =R=R%W@1@ 7!oDa+BXbb½Y B@jB5n@x@A$AjZE jaj[ =jC׀=(%p\@ր@$]BՀB J$ c^ B@JBc@x@AAJ_E:e`=Fa =&{% "b@v@ Bcb  `cd B@aB@x@A$AeEJabf =j `=j$j%g@S @,hJJA#ni B@aRBy@x@A ARjE FaRk =Rɀ="l@Ā@ o)!#3a|[BmbBb jn B@jB <@x@A$AjoEU} b jajp =jŀ=%-+cq@#@$rB s B@JB@!K$X@x@AAJtE8 Jeu=]KFuyv =@>{ w@:@ ةAxbw9b y B@`B !@x@A$AzEbcb{ =j aj$%|@@,}JaJ ~ B@aRB@x@A AREpk a FaR =Rs="@`n@ 3!:6[qbmbf B@jB @x@A$AjE& jaj =*=j%-%@)@ Rr' @"@BBRB J)B B@JBc@x@AAJE} e=Fx =m{F% +c@@$X qbb! 륱 B@aB@x@A$AEb =j= @@,JpJA#j B@aRB@x@A AREYaR aR =R0a=R"@\@C3D Je=F =*{% "@t{@, ?Ab֡ !  B@aBc@x@A$AE5ab =j=^.@S@,cJJA#c B@aRB @x@A AREάaR aR =Rt=R"@֯@ cckXQ@@ c Zv++aNY@>B?bqb½@ B@jB@@x@A$AjAE,b jajB =j =(%-(%C@{@~DBܨB J$ E B@JB@x@AAJFEb-aJ,eG=FH = À h{% "I@Xc@ , AJb K B@`B @x@A$ALE.a* bM =j݀=G@² .N@8܀@,OJ۠J P B@aRB@x@A ARQE/aRaRR =Rh=R"S@@ N$F+!3 FUEat2BTb'b U B@jBc@x@A$AjVE9P0aj  AW =jS="X@@ !jn"AJYB}RAZ B@JB@x@AAJ[E 1J@=\=E|w3; ] = À{% ^@ @ A_b\b ` B@`Bc@x@A$AjaEGǀbb =jx2aj$j"c@م@,dJEJ 4` e B@aRB@x@A ARfET>3a FaRg =R F="h@\A@ ! elBib@bjj B@jB`@x@A$AjkE jajl =j_=%p+cm@@$nBB Jo B@JB@b2a,@x@AAJpEb4b Jeq=Fcr =@Z{% (s@@ J 5Atb ! u B@`B !,@x@A$AvEp5abw =j 16`=j$j%x@k/@ m!j @ @ yJ.J@.z B@aRB@x@A AR{E   aR| =R="}@@ e ÔPaJB~bnb j B@jB@x@A$AjE|7b j  aj =j =j @i@cBɥB$  B@JB,@x@AAJE_8J  e=qF|c =d{F% F%@>`@ ! @ @Bb ! b B@aB,@x@A$AE9ac  b =jڀ=j%pj%@ـ@,JؠJA# B@aRB@x@A ARE:aR FaR =RI=R"@@c1 ,$ a@ aBbb½ B@jB @x@A$AjEM;jaj =jP=j$+c@@$BfO J$  B@JB@x@AAJE<J >=&| ={% "@ @/ BAbAb h B@aBc@x@A$AjE,Āb =jR=a @@,JJ  B@aRB@x@ =AR @`E9;>a aR =RB=R"@I>@ Qke`BBb=b B@a$B ,@x@A$AjE jaj =jP=j%-(%@@ ) @(@BB B B@JB@x@AAJEF?e=ĄF =;{% "@@ ! @c WHAb ! b륱 B@aB "@x@A$AEm@ab =j-A`="@X,@,J+J B@aRB@x@A ARE䀈y FaR =R= @@#*t*Qb1aBbSb jc B@jBc@x@A$AjEaBb jaj =j٣=j%p(%@6@~ `@ťB B$ Bc B@JB@$/@'@x@AAJE[CJe=inF =@@+"a{% %@#]@ 5\ @+c G Ab\b bc B@`Bc@x@A$AEoDacb =j׀="@Հ@ m!j$JbJ(> B@aRB@x@A ARE|EaR FaR =R/=R"@@3*

=j=j%-(%?@@~@Bw B$ A B@JB@x@AAJBEUYJ88eC=3hFD =[{% "E@V@ ; @W GAFbNb bG B@aB 'Zc@x@A$AHE9Za9:bI =jmр= J@π@,KJ;J L B@aRB@x@A ARMEF[aR F;;aRN =R=R"O@R@ "~u$a*ZBPbbĩQ B@jB c@x@A$AjREC\j<?b^ =jz^aj"_@ay@,`JxJ ca B@aRB@x@A ARbE1_a @@aRc =R9= d@4@ 4"eaBebXbf B@jB/@x@A$AjgEn jAAajh =j= i@Q@~jB B$ ck B@JB@$@x@AAJlE`BBem=vF* n =@宀{% o@2@ c @#Bpbb cq B@`B@x@A$ArE|daacCDbs =j$b`=^6t@#@ m!j @ uJ{"J v B@aRB@x@A ARwEۀ FEEaRx =R>=R(oy@ހ@ $F!RoEeaBzb݀bĩc{ B@jB o@x@A$Aj|Ecb jFFaj} =j=(%-(+c~@@ r!j @"@BBPBJ B@JBc@x@AAJERdJGGe=eFc =X{% "@S@ c Ab3b 륱 B@aBc@x@A$AEeaHIb =jS΀=^"@̀@,J JA#j B@aRB5n@x@A ARE+faR FJJaR = Ȍ=R"@'@ c+WE"䐃)} Bbbf B@B@x@A$AjE@gaj2 jKKaj =j:D=(%p(%@C@~BBB J B@JB@x@AAJE8 JLLe=F/ =)ha% "@v c \*Ab ! B@aBc@x@A$AE,MNb =jwij)j%@Nv@,JuJA# B@aRB@x@A ARE.ja OOaR =Rz6=R"@1@ y~`paHBb9b½ B@jB; @x@A$AjES jPPaj =j="@7@ B쀃J$&5 B@JB@x@AAJEڥkb JQQe=[F =Ϋ{% F"@@ y! @W 'Lbvb b B@aBy@x@A$AEaalaRSb =j!m`=j^%@@ JXJ  B@aRB@x@A AREn؀ TTaR =R="@vۀ@ !d`%Kc}daEbڠb@$j B@jBb@x@A$AjEnb jUUaj =jy=%-(+c@Ֆ@ r$( @"@BB5BB B@JB@x@AAJE|OoaJ JVVe=F =dU{% %@P@ b$F @ Abb b륱 B@aBF@x@A$AE pacWXb =j0ˀ=j$j"@ɀ@,cJȠJA#F B@aRB{ B\ @x@A AREqaR YYaR =R="@@ p&r0!g {Bbb½ B@jB; @x@A$AjE=rajF jZZaj =jA=j%-%@s@@~B?Bc B@JB; @x@AAJE J[[e=F$X = {F% %@X@ hv/G?Ab ! B@aB@x@A$AEsb\]b =jtt`=j @/s@,JrJA#n B@aRB@x@A ARE+ua ^^aR =R[3=R"@.@+_ьYKoBb"b  B@jB@x@A$AjE8(__aj =j=j @(@B逃 B$  B@JBc@x@AAJEv``e=@F@ =㨀{% @.@ c KBbb  B@aB@x@A$AEF^waabb =jlx`= @@,J8JA# B@aRB{c@x@A ARESՠ@Y ccaR =R܀=R%@[؀@EKpd}Bbנb@%ĩ B@jB@x@A$AjEڐyb jd1A =jb=j%-(+c@@~BB B@JB@x@AAJEaLzaJ Jeee,  Fc =MR{% "@M@ @ UAb ! b  B@a$B* @x` =A @E{acfgb =jȀ="@vƀ@,JŠJ@Fc B@a B@x@A AR E~|aR hhaR =R= @@ @#+^+kFɂ bqb½ B@jB@@x@A$AjE{:}jiiaj =j>= @d=@ BEEIJtte?=ǤF@ =6O{% A@J@ 6 cCBBb  C B@aB@x@A$ADEauvbE =jĀ= cF@SÀ@,GJ J (RH B@aRB@x@A ARIE{aR FwwaRJ =Ry=R%K@~@ cS}ÔPz )a+BLbFb@$jM B@jBc@x@A$AjNE`7aj jxxajO =j:= P@J@rcQB9BR B@JB@x@AAJSE JyyeT=h|U ={% V@@ ! @c  BWbb b X B@aB $X@x@A$AYEnbz{bZ =jn`=j^+c[@l@@S' @\JeJ J] B@aRB; @x@A AR^E{%a ||aR_ =@3-="`@(@ c c0#~FP bhƀb ! c? B@aB/@x@A$A@ERbcbA =jA`=^"B@?@,CJQJ FcD B@aRB@x@A AREE`@Y aRF =R$R"G@p~LpRtZ)pKv@=4HbbjI B@jB@x@A$AjJE況, jajK =jo=%-(+cL@ʶ@ MB*BJ$ N B@JB@x@AAJOEmobw JeP=FQ =Uu{"R@p@, ASb b T B@aBL6@x@A$AUE*acbV =j%=j)%W@@,XJJA#(RY B@ B@x@A ARZEaR aR[ =R=FR%\@ @ #d`9n` t.Q! *@B]bubj^ B@jB @x@A$Aj_E]jaj` =ja=%-%a@l`@$bB_B Jc B@JB@x@AAJdEJee=+|f ={H yg@I@ Ahb ! i B@aB@x@A$AjEԀcbk =jÔaj^%l@$@,mJJA#n B@aRBB@x@A ARoEKa aRp =ReS="q@N@!3bKf aFBrb#Nb½s B@jB@x@A$AjtE*jaju =j = v@@$wBf J$ cx B@JB@8 @x@AAJyE ez=2ՄFwL6{ =@Ȁ{% :(|@À@c B}bLb ~ B@`B@x@A$AE7~btb =je>`=$j%@<@ m' @ J2J  B@aRB@x@A AREE aR =R=R"@M@yC@䐃(yEBbb B@jBy@x@A$AjE˰b jaj =jS=j%-+c@@~BB B@JBc@x@AAJERlJe=ӤF| =:r{% "@m@ 5n$F @+c  Ab ! b B@aB@x@A$AE'ab =j=%pj%@h@, JJA# B@aRB@x@A AREaR FaR =R=R"@@ S%`uvuw+haCaBb^b@$jc B@jBz@x@A$AjEmZjaj =j]=j @Q@ B\ B$  B@JB@x@AAJEaJ> e=u(| ={% F"@*@W @(SBbb bc B@aB; @x 9A$A @Ezрb =ja"@@,cJiJ@ @c B@a Bc@x@A AREHa aR =R&P= @K@ cG\uvdpEbbJbA;j B@jB@x@A$AjEajfaj =j=j%-(+c@@ B[B  B@JB@x@AAJE e=҄F; =ŀ{% %@@ c bb1b 'Z  B@aB@x@A$AE{bb =j7;`="@9@,JJ F B@aRB@x@A ARE) aR =R=R"@2@5ns~ }{zy5@;Dbb f B@jBy@x@A$AjEbjaj =j4=(%-(%@@ BB B@JB@٢oB@x@AAJE7iJe=Fx =@+o{% "@rj@ * ! @c  Ab e B@`B W!B@x@A$AE$ab =j=^1@D@ m'*QJJ  B@aRBL6@x@A ARE˛aR aR =R^=R"@@ c T7` cBb+bA;# B@jBc@x@A$AjERWjaj =jZ=( @?@$BY J$ c B@JBc@x@AAJEaJ e=Z%| ={%p@@ c nBbub B@aBc@x@A$AE_΀cb =ja^%@@,J^JA#F B@aRB@x@A AREmEa aR =RM=R%@qH@ cvwd`Xa8BbGb½ B@jBW@x@A$AjEjaj =j=j%-(+c@@ `@n"@BB<B B@JB@x@AAJEz e=F = Àf€{"@@$X  Abb!&U B@`B@x@A$AExbcb =j,8`=j+""@6@,.5J   B@aRB@x@A ARE aR =R=FR%@@ D %zNbrb j B@jB/@x@A$AjEb@Y jaj =j!= @@$ BݬB J$ c B@JBc@x@AAJ EfaJ Je =F = l{% F"@Xg@ / @1a0 6b  B@aB$X@x@A$AE!ab =j=j^%@5@,JߠJA#Rc B@aRB$X@x@A AREaR aR =RX="@@ c &v$p? Ba Hobbj B@jB $X@x@A$AjE7Tjaj =jW=%-(+c@ @$BoV J B@JB@x@AAJ!EJe"=?"`=# ={%p%$@@ 5A%bYb Zy& B@aB @x@A$A'EDˀb( =jta )@׉@,*JCJA#+ B@aRB@x@A AR,ERBa aR- =RI=R".@FE@ EG$1SF`xb/bDb j0 B@jB@x@A$Aj1E jaj2 =j`aj 3@@ 4B B5 B@JB &B@x@AAJ6E_ Je7=Fzc8 = O{ 9@@ c h:b ȥ! ; B@`Bc@x@A$A<Etbcb= =j5`= >@y3@, ?J2JA#c@ B@aRB; @x@A ARAE뀈 aRB =R=R%C@@ G`vwxez`4a^CHoDbkb½E B@jB@x@A$AjFEzb jajG =j=j H@X@~IB B$ J B@JB@x@AAJKEcJeL=uFM =h{% cN@9d@ , BOb ! cP B@aB@x@A$AQEabR =jހ="S@݀@ 2$,TJܠJA#U B@aRBc@x@A ARVEaR\aRW =RA=R%X@@ , -Q8ayYb b@$jZ B@jB{@x@A$Aj[EQaj?aj\ =jT=(%-(C]@ @ ^BlS g$ _ B@JB@x@AAJ`E aJea='|b ={% "c@ @ %mdbBb ! e B@aB@x@A$AfE)ȀAg =jVaj%pj6h@@,iJ$JA#Fcj B@aRB@x@A ARkE6?aaRl =RF=R"m@GB@ |}Qxua!DnbAb fo B@jB@x@A$AjpEjajq =j=="r@@ sBB $ t B@JB@@x@AAJuEDev=ɤFvw =@4{% F"x@@ ! @yK }Byb b z B@`B@x@A$A{Eqa ZW b| =j1`=^%}@U0@ mc~J/J ̶ B@aRB@x@A ARE耈FaR =R=R"@@$qGQ@l.Bb\b j B@jB@x@A$AjE_bjaj =j맀=(%-(+c@F@$B J B@JBc@x@AAJE_aJe=krFc =e{% "@'a@ >$F @  Ab`b ! b B@aB@x@A$AElayKc =jۀ=j c@ڀ@,Jo٠JA#{ B@aRB@x@A AREzaRA@aR =@3="@@ (o!%xү !tސBbbj B@B@x@A$AjENjaj =jQ=j @P@ !j @(AJBIB$B B@JB @x@AAJE J@== |/ ={F% @ @ ! @zbAb#b ! b B@aB@@x@A$AjEŀyb =j/aj$j"@@,JJA# B@aRB@x@A AREb B@jB@x@A$AjE jaj =j2=%-+c@@$BB J$  B@JBc@x@AAJE)e=F; ={% c@c@ Ab !  B@aB@x@A$AEna  b =j.`=j^%@B-@@SW`@yJ,JA#J B@aRB$X@x@A ARE F  aR =@f=R"@@ 3`GFcyvb-b jc B@B@x@A$AjEDb  aj =jԤ=j%-(%@/@ ) @"@BB B$B B@JB@x@AAJE\aJ   e=LoF =b{% "@^@ ! @c Abf]bĩ! b륱 B@aB@x@A$AEQb =j=j @Y@,JJA# B@aRB@x@A ARE aR =Rۀ=FR"@ր@ CGS0v"FadBbLb j B@jB@x@A$AjE_b jaj = ے=j(%@8@ $( @ťB B$B B@B@x@AAJEJaJ Je=Fnb =P{F% @!L@ ) @+c 7AbKb B@aB@x@A$AElayb =jƀ=j^"@Ā@,JgJ  B@aRB @x@A AREz}aRFaR =R)=R%@~@7!1S% w"$`%$abb½ B@jB@x@A$AjE9aj faj =jx<="@;@ B =R+=R"?@'@ C#6 uvuwa?B@bp&bA B@jB@x@A$>BE j))ajC =j =j%-%D@f@$EBB JF B@JB@x@AAJGE **eH=FI ={% "J@K@ W': @9 ]AKb ! b L B@aB@x@A$AMEV ac+,bN =j `=%O@@,PJJA#cQ B@aRBc@x@A ARRE F--aRS =RbՀ= T@Ѐ@ %$ E4a&BUb&b jcV B@jB@x@A$AjWE( b j..ajX =j= Y@@~$( @"AJZBp B$Bc[ B@JB@x@AAJ\EDJ//e]=0WF^ =J{% _@E@ @ c A`bKb ca B@aB@x@A$AbE600bc =j=^"d@B@,eJJ f B@aRB @x@A ARgEy F11aRh =RkÀ=R(oi@ɾ@ c0 Qb/Bjb5bĩk B@jB} o; @x@A$AjlECw"  j22ajm =jz=(%-(+cn@@ oB{yJp B@JB@!K5n@x@AAJqE2J33er=ҤFs =@8{% "t@4@ ! @c pAubf3b b v B@`B@x@A$AwEQ 44bx =j=j y@U@ m' @yzJJA#{ B@aRBc@x@A AR|Eש55aR} =R="~@଀@ 5a XBbLb  B@jB,@x@A$AjE^ej66aj = h= @E@ $( @(AJBg  B@Bc@x@AAJE J77e=F =&{% @""@ $F @6 Ab!b bc B@aB@x@A$AEl܀89b =jaj%pj"@Ꚁ@,JVJA# B@aRB@x@A AREySa ::aR =R.[="@V@ $F!3@zT˵zuy"av,BbUb@$j B@jB; @x@A$AjEj;;aj =j=%p+c@@$BHB J B@JB@c@x@AAJE <b =j:F`=j$j%@D@ m' @cJJA#c B@aRB@x@A AREu??aR =R$R"@'@zzÔ "(t]LBb j B@jB|@x@A$AjE@Y f@@aj =j2= @@ ' @"DBB$B B@JB@x@AAJE(tbwJAAe=F =z{F% F"@fu@ $F @+c Bb  bc B@aB@x@A$AE/acBCb =j=%pj"@B@,JJA#R B@aRB@x@A AREaR FDDaR = U=R"@@ 䐃NaZThްDfb$b j B@By@x@A$AjECbjEEaj =je=j%-+c@@ Bsd B B@JB@x@AAJEJFFe=K0| =#{% "@@ ! @.W zBAbfb b B@aB@x@A$AEQـGHb =jf a"@Ǘ@,J3JA# B@aRBc@x@A ARE^P!a IIaR =RW= @bS@ Fzxy}a0mBbRb@$j B@jB@x@A$AjE "jJJaj =jm=j @@ $( @(AJB)B B@JB@x@AAJEk KKe=F =\̀{F% @Ȁ@ Abb ! c B@aB@x@A$AE#bLMb =j C$`=j^"@A@,J@J Fc B@aRBz B@x@A ARE+ NNaR = p%$R(o@~ ~Y`vBblb j B@B @x@A$AjE, jOOaj =!!="@Z@BB$ B@JB@x@AAJE q&bw JPPe=Fw =w{% @Gr@$X Bb  B@aB$X@x@A$AE,'acQRb =j="@'@@S#2%JJA#J B@aRB@x@A ARE(aRSSaR =@[=R%@@!R! # mbbb½ B@B@x@ =Aj @`E(_)jTTaj0 `=b)`=(%-(1@ @ rBla J B@B`x@9 AJ @E*a  UUe=0-|c = {% "@@ $F @+c hbKb! b B@abB >"$X@x@A$A E5րcVWb =j`+aG r%dj+c @@,c J,J  B@aRB{c@x@A ARECM,a XXaR = U=R"@SP@ $F 3SSSS ADbOb½ B@B  @x@A$AjE-aj2 jYYaj =jR =( @ @~!j @"AJBB J$Bc B@JB@x@AAJEPĀ JZZe=ѤFy =Yʀ{% F"@ŀ@ ! @( xAb ! b B@aB@x@A$AE.b[\b =j@/`=$j"!@f>@,"J=J c# B@aRB@x@A AR$E ]]aR% =R=R"&@@ cCSStZ/Q'b]b ( B@jBc@x@A$Aj)Ek0b^^aj* =j=%-+c+@X@ $( @"@B,B $B- B@JB@x@AAJ.Em1J__e/=sF0 =s{% "1@-o1` c aA2bnb 륱3 B@aBc@x@A$A4Ey)2``ab5 =j=j^"6@ @,7JwJ Rc8 B@aRB@@x6 9A AR9 @`E3aR bbaR: =RA=G@Qx ";@@!oSTQTSu@z,B<bbo= B@a$B oc@x@A$Aj>E \4jccaj? =j_=%-`3A@@@$ABa^ JB B@JB@x@AAJCE5aJ ddeD=*|tE ={% "F@@t AGb0b! H B@aB@x@A$AIEӀcefbJ =jI6aj$j%K@@,cLJJA#M B@aRB@x@A ARNE(J7a ggaRO =RQ=R"P@0M@ o)!cSuSuSu"tc/]L3 QbLb½R B@jB@@x@A$AjSE8jhhajT =j7 = U@@ !j @"DVBBBi2rW B@JB @x@AAJXE5 iieY=F*Z =-ǀ{% F"[@q€@ c  \b ] B@aB@x@A$A^E|9bcjkb_ =j<:`=$j"`@;;@,aJ:J b B@aRB@x@A ARcE llaRd =R|=R"e@@ ,s4"md`& fbAb jg B@jB; @x@A$AjhEP;b jmmaji =j貀=j%-1j@E@~ `@ kB B$ Bl B@JBc@x@AAJmEj<Jnnen=X}Fo =p{ p@l@ ! @  qbskb b5nr B@aB@x@A$AsE^&=aopbt =j="u@@,vJTJA#w B@aRBc@x@A ARxEk>aR FqqaRy =R= z@w@  $cpttc.paD{b㟠b@$j| B@jBnb@x@A$Aj}EX?jrraj~ =jv\=j%-@[@ $( @(AJB2B B@JB@x@AAJEx@Jsse=F =]{F% (@@ W c lAbb ! 륱 B@aBV@x@A$AEπtub =j#Aaj^"@@,JJA#F B@aRBL6@x@A ARE GBa vvaR =RN=R"@ J@ d`eaBbyIb@$j B@jBz@x@A$AjECajywwaj =j'="@@cBB J$c B@JB@x@AAJE xxe=ЄF =Ā{% @V@ ': @c _Bb ! b B@aB@x@A$AEyDbcyzb =j9E`=^%@08@,J7J > B@aRB@x@A ARE {{aR =R^=R%@@ "*t aJVb*b@$ B@jBc@x@A$AjE5Fb j||aj =j=(%-(1@@ r$( @"@BBiB B@JBW@x@AAJEgGJ}}e==zFW =m{% "@h@ c AbXb c B@aB@x@A$AEB#Ha@Y| ~~b =j'=j c@W&@@S~°!j @BJ%JJ B@aRBB@x@A AREހRaR =@{="@@   ! !!2a(BbIb@$c B@B@x@A$AjEPIb aj =jĝ=%-(%@ @ rBµ) y B@JBc@x@AAJEUJaJye=Fb = À[{% %@W@$X lAbwVb !  B@`B WB@x@A$AE]Kab =jр=j%pjK@π@,cJ`J F B@aRBy@x@A AREkLaR FaR =  =R"@@ !E!Q![!`!o Bbb@& B@B@x@A$AjECMajF jaj = G=%-%@F@ B>B B@B,@x@AAJEx Je=FB =`Na% "@@ ) @ AVAbb  B@aB@x@A$AE$A = `!{Oaj @y@,JxJ F B@Bc@x@A ARE 2Pa aR =R9=R"@5@@ !!e!JK!aBb4bfc B@jB o @'@x@A$AjE jaj =j#=j @@ BB$ a5 ic B@A"Bc@x@AAJEQe=F = {%p@T@ @ Vvb ! bc B@aB,@x@A$AEdRab =j$S`="@7#@,J"JA# B@aRBc@x@A ARE FaR =R\= @ހ@'a$F @ !Qi  U`Eb*b aj B@jBy@x@ =Aj @`E5Tb jaj =j=j%-(+c@@~!j @.`@BB} B$B B@aB@x@AAJERUJe==eF =X{% (B ÀS@ hv/G?RAXb 륱 B@`B,@x@A$AEBVacb =)e΀="@̀@, J1JA# B@aRB@x@A AR EPWaR F-, =R=R" @T@ I!aaBbb@$j B@jByc@x@A$AjE@Xj#!j = cD=j%-(%@C@ BB J B@B@x@AAJE] e=ޤF =IYa"@!!%N .`# +c gA 3 ǝ! b  B@aBy@x@ =A @E䷁,b ==j)(f@@,J\J@Fc B@a B@@x@A AR EksZb aR! =R{=FR%"@ov@!+V?ve+i|B#bub j$ B@jBQ@x@A$Aj%E.[aj j//%& =jq2= '@1@ `@"AJ(B-BB$ Bc) B@JB@x@AAJ*Ex Je+=Q, = Àd{% F"-@@ cA.bb / B@`B@x@A$A0E\bb1 =jf]`=j^"2@rd@,,3JcJA#4 B@aRB@x@A AR5E ^a aR6 =R$="7@ @c!  B@JB@x@AAJ?E_e@=Fz$XA ={% %B@W@.! @ .`c u6C 3 D B@aB@x@A$AEEO`abF =a`=j G@+@,cHJ JA#FcI B@aRBy@x@A ARJE FaRK =RV΀=R"L@ɀ@ #+VŴJo!a}cDMb"b@$jN B@jB@x@A$AjOE5bb2 jajP =j=j Q@@~$( @w(AJRBy BS B@JB C=b@x@AAJTE=caJ JeU=@PFwV = C{%pW@>@ c ّAXbWb! cY B@`B@x@A$AZEBb[ =jgda"\@ɷ@ m!j%` ! !% AR]J5J F^ B@aRBc@x@A AR_EOpea aR` =Rw=!a@\s@ 3 {Ajbbrb fc B@jB@x@A$AjdE+fjaje =jV/=j%pjCf@.@gBB$ ,h B@JBc@x@AAJiE]Fej=ޤF{k =M{% (l@@ TAmb ! n B@aB@x@A$AoEgbbp =jch`="q@za@,rJ`JA#s B@aRBc@x@A ARtEia aRu =R!= v@@ C+V % !aywbab@$jx B@jBy@x@A$AjyExՠ@Y jajz =j؀=j%-(%{@S@~|B׀ B$ } B@JB@x@AAJ~Ejb i A8e=F =㖀{% %@4@ ) @ .`Q6 m 3b! b B@aB,@x@A$AELkacb = l`="@ @,J J Rc B@aRB@x@A ARE aR = @ˀ=R"@ƀ@ SYr0|uJ@hDbb j B@Bx@x@A$AjEmb jaj = =j%-(%@@ cB^B$  B@BB@x@AAJE:nJe=!MF = À@{"@;@ !%N .`E+c UA 3zaj^%@@, J JA# B@aRB@x@A AREj{a FaR =Rq="@)m@ cTD䐁Xa8Hoblbf B@jB~c@x@A$AjE%|jaj =j()= @(@ r5> @"AJB'B Jc B@JB@x@AAJE' e=F ={% @b@c Ab ! c B@aBc@x@A$AE}bcb =j\~`=j @8[@,cJZJA# B@aRB|@x@A AREa aR =RY=R(o@@ A%7ԣ@b'b@%j B@jBz@x@A$AjEBπ2 jaj =jҀ=j( B@aRBc@x@A AREfaaR =Rn= @j@ < E䐃a Bb~ib@$ @j B@jBW@x@A$Aj!E"aj aj" =j&=j #@b%@~r$B$B$ c% B@JB@x@AAJ&E ހ Je'=F( ={% )@C߀@ ) @+` $XB*b ! bc+ B@aBc@x@A$A,Ebcb- =jY`=".@X@,/JWJA#F0 B@aRB@x@A AR1Ea aR2 =RO=R(o3@@ *fV$ÔPOa[B4bb@$j5 B@jB@x@A$Aj6E&̠@Y jaj7 =jπ=j%-(+c8@@9B_΀ B$ : B@JB @x@AAJ;Ee<=.Fc= =!?{">@鈀@c A?bIbj! @ B@aB@x@A$AAE4CbB =jG=j9|C@8F@,DJEJA#RE B@aRBz@x@A ARFE FaRG =RdaFR%H@@ yTp +tt5BIb/b jJ B@jB@x@A$AjKEA jajL =jѽ=%-M@-@rNBB$ O B@JBc@x@AAJPEubw JeQ=ФFw!^|    AR = ){{% "S@v@ c BTbdb cU B@aB@x@A$AVEO1aɂbW =jx=j^%X@@,YJFJ >Z B@aRB@x@A AR[E\aR aR\ =R ="]@d@ ӔPŵaB^bЪb½_ B@jB@x@A$Aj`Ecjaja =jkg=%-(+cb@f@ rcB'B Jd B@JB@x@AAJeEjJef=F g =V%{% %h@ @ ! @K  Aibb b j B@aB@x@A$AkEڀbl =jaj m@s@,nJߘJA#Fo B@aRB{c@x@A ARpEQa aRq =RY=R"r@U@"t c,iBsb~Tbt B@jB@x@A$AjuE aj2 jajv =j=j w@`@ $( @(AJxBBy B@JB B@x@AAJzE Je{=ۄFxy| =@΀{% }@Fʀ@ c ]A~b ! c B@`B@x} 9A$A @Ebb =jD`=j @%C@ m!j @,JBJ@ B@a Bc@x@A AREFaR =RAaRF!@~ o @o"~$%UxBbb½c B@jB !@x@A$AjE&, faj = {=j%pj+c@@~Bf B$  B@JBc@x@AAJErbw Je=.F| =x{% (@s@ @0AbIb! b B@aB ",@x@A$AE4.b =j2="@81@,J0J B@aRBxc@x@A ARE aR =Rv=R"@@"#%&L$a+Bb?b$ B@jBB@x@A$AjEAaj =j=(%-(%@@ Bu $  B@JBh@x@AAJE`Je=ФFw!^. = )f{% "@b@ y) @+c fAbdab b B@aBc@x@A$AEOat ^b =j!=j)j.@c@,JJA#4` c B@aRBc@x@A ARE RaR =R߀="@ڀ@,"3`<}d`@5nܭBbQbA;a`oc B@jB* @x@A$AjE\b jaj =j䖀=%-%@@@@ c Bb=b B@aBy@x@A$AEb =ja"@@,JJA# B@aRB@x@A AREoaBaR =RYw= @r@ yS"}x~u$aTBbb j B@jB @x@A$AjE+aj+uaj =j.=j @-@ BUB B@JB@x@AAJEe=$F~ ={F% @@ B! @.W  Bb?b ! b B@aB "@x@A$AE&bA =jVb`=j^+c@`@,J%J  B@aRB@x@A ARE3a aR =R =R+c@<@ c4"܌Bbb  B@jB@x@A$AjEԀjaj =j:؀="@׀@~cBB$&5 B@JB@x@AAJEA@==ƤFq =9{% @}@ y c x~Bb !  B@aBy@x@A$AjEKa b =j `=^%3@Z @,J J c  B@aRBQ@x@A ARE aR =Ryʀ=R%@ŀ@ (o'Eos "Qk0S]LBb5b j B@jB oc@x@A$AjE\~b jaj =j聀=(%-(6 @B@ !j @B B BB B@JBc@x@AAJ E9aJ Je=dLF*  =?{% "@;@ ! @CAb:b b륱 B@aB@x@A$AEic  b =ja^"@@,J`JA#gc B@aRB@x@A AREwla   aR = 3t=R"@o@ cC!E ?kBbnb½ B@B@x@A$AjE'j  aj =j~+=j%-(%n@*@ $( @"@B B:B)B! B@JB@x@AAJ"E   e#=Fb$ =p{"%@@; +uA&b b! ' B@aB@x@A$A(E bcb) = `;_`=j)"*@]@,+J J , B@B@x@A AR-Ea aR. =R=FR%/@0@ E%0C! x$ʒB0bb½1 B@jB@x@A$Aj2E jaj3 = +Հ=%-%4@Ԁ@$5BӀB J$ 6 B@Bc@x@AAJ7E&e8=F9 ={% ":@c@ ': @c n`A;b ! b< B@aB .X@x@A$A=EHab> =j`=j^%?@7@,@JJ cA B@aRB@x@A ARBE FaRC =Roǀ="D@€@ % 0 r0QaLBEb:b@$jF B@jB$X@x@A$AjGEA{b.ajH =j~=%-(%I@@$JB} J$ K B@JB@$L6@x@AAJLE6aJ eM=IIFzN =@<{% %O@8@  VAPbc7bQ B@`Bc@x@A$ARENbS =jwa T@ٰ@ m!j @UJEJA#V B@aRB@x@A ARWE\ia aRX =R q=R"Y@`l@ (o!RoDъ0C""v 8BZbkb j[ B@jB o@x@A$Aj\E$aj jaj] = ^(=j%-(%^@'@~!j @n(@B_BB` B@B@!K@x@AAJaEi Jeb=F]Lc =@]{ d@@ c \'Aebb ! cf B@`Bc@x@A$AgEbcbh =j\`=$"i@sZ@ mjJYJ(>k B@aRB@x@A ARlEaaRm =R=R%n@ @ t"/4\~$ÔaBobyb p B@jB@x@A$AjqE f  ajr =jҀ=j%p%s@[р@ tBB$ cu B@JB@x@AAJvE b J!!ew=Fx =!{% "y@@@ m$F @6 Azb ^! b @ { B@aB 'Z@x@A$A|EE""b} = ``>J="~@H@,QJ J j B@BB@x@A ARER##aR =R=R"@ @ c @Pŀtt Ia Bbb½ B@jB c@x@A$AjE f$$aj = '=(%-(%@@ rB羀B J$  @d B@B@c@x@AAJE&xb J%%e=-FB =@~{% "@ay@ , :Ab !  B@`B,@x@A$AE3a&'b =j=^+c@7@ m'*QJJA# B@aRB@@x@A AREaR ((aR =R}=R"@έ@ o$F!RoK "$ %Uf) aBb:b½ B@jBɂ@x@A$AjEAfajf))aj =ji=j%-(%@*@~!j @"@BBh B$B B@JB,@x@AAJE!aJ **e=H4|@ ='{"@"@ $F%N+c Abcb! b륱 B@aB@x@A$AEN݀+,b =jqaj$"@ћ@,J=J  B@aRB@x@A ARE[Ta --aR =R\=FR%@`W@ *%+ , - ac B@aRB@x@A >!0E[?aRA@@@aR4 G=R%@oB@B#3C L䐃,PQ BbAb@&4j B@B; `x@9 Aj @`E jAAaj =A|`=^=(*+c@@ rBB J B@B@@x@AAJ EiBBe =F =@Q{% " @@ @ !#A bb b  B@`B@x@A$AEqaCDb =j"2`=^%@0@ m'+,J/JA# B@aRB@x@A ARE FEEaR =R=R"@@ cC0:Tu @a6Bbqb j B@jB@x@A$AjEb2 jFFaj =j=( @`@~ƹBB B@JB@x@AAJE `aJ JGGe=rF|c =e{% !@Aa@ c 1B"b ! # B@aB@x@A$A$EHHb% =j: =j%pj%&@@,'J J ( B@aRBy@x@A AR)Eנ@Y IIaR* =Rހ="+@,ڀ@ c Su L|h@[B,b٠b@%½- B@jB@@x@A$Aj.EJJaj/ =j/=j 0@@$1BB$ F2 B@JB@c@x@AAJ3E%NJKKe4=-5 =@T{F% F(6@bO@ ': @c B7b ! by8 B@`B@x@A$A9E a,LMb: =jɀ=j ;@3Ȁ@,<JǠJA#c= B@aRB@x@A AR>EaR NNaR? =Rq=R"@@Ƀ@ c cP_Q0U+fJBAb5b½B B@jB* @x@A$AjCE@<jOOajD =j?=j%-(1E@&@ $( @(@BFB> B$BG B@JBc@x@AAJHE PPeI=H |$XJ ={% "K@@ / @ K!Lbcb b륱M B@aB@x@A$ANENbQRbO =!!rs`= P@q@,QJ@J R B@aRB@x@A ARSE[*a SSaRT = 2=R"U@c-@ csu Au I$ 4 BVb,b@&W B@B* @x@A$AjXE jTTajY =jf=j%-(#Z@@ [B"B\ B@JB@x@AAJ]EhUUe^=F_ = ÀY{F% "`@@ @6 ]Aabb ! b b B@`By@x@A$AcE\VVbd =ja=j^(fe@_@,fJgJ Fcg B@aRB@x@A ARhEvRWWaRi =R( =R"j@@ T tZ/`C /OBkbbA;jl B@jBy@x@A$AjmEӀ XXajn =j׀="o@ր@~kpBIB J$&5q B@JB@x@AAJrEYYes=]t =h{% u@@ y c Bvbb w B@aB@x@A$AxE KZZby =jO= z@N@,{JMJA#F| B@aRBc@x@A AR}ER[[aR~ =R==R%@ @ `C ,UagBb b j B@jB@x@A$AjE€2 \\aj =jŀ=j%-(+c@Ā@~BXB B@JB@@x@AAJE}]]e=F =@{F% "@~@ b:b ! B@`B,@x@A$AE%9^^b =j==^+c@1<@,J;JA# B@aRB@x@A ARE/__aR =RQ=R"@@ W0% r0 VWDb bA;j B@jBc@x@A$AjE2``aj = = @@ cBv B$ c B@Bc@x@AAJEkJaae=Fc =q{% @l@ GBbUb c B@aB@x@A$AE@'abcb =jr=^%@@,J?JA#F B@aRB@x@A AREMaR ddaR =R=R%@]@ IAA R|Bbɠb@(j B@jB@x@A$AjEYjeeaj =jL]=j%-(+c@\@ BB B@JB@x@AAJE[Jffe=ܤF =C{% "@@ ! @ "Ab O! b B@aBc@x@A$AEЀghb =ja"@t@,JJA#F B@aRB@x@A AREGa iiaR = O=R"@J@ ,P)Q 7vbgb½ B@B @x@A$AjEvjjjaj =j=( @B@$B J$ c B@JB@x@AAJE kke=~фF{ =Ā{% @3@ vbb c B@aB,@x@A$AEzb Zlmb =j:`=j+c@ 9@,Jv8J F B@aRBw@ Bl@x@A ARERA@nnaR =@?=R%@@@#T?0:TQ[` b b j B@B}$X@x@A$AjEb jooaj =j="@@/)n"IB[BB B@JB@x@AAJEhJppe={Fc =n{% @i@ , ib:b B@aB@x@A$AE%$aqrb =jL=^"@@ =%JJA#n B@aRBc@x@A ARE2aR FssaR =Rݢ=R%@2@!!R!\p ~Aq"aODfbbf B@jB@x@A$AjEVjttaj =jAZ=(%p(1@Y@ rBB J B@JB@y@x@AAJE@Juue=F = 8{% "@{@ / Ab ! B@`B/@x@A$AÈvwb =jaj$j%@Y@ mJŋJA# B@aRB|c@x@A AREDa xxaR =RL="@G@ c|ӐZ0XTuaBbLb½ B@jB~@x@A$Aj5  `E[7@ 2 jy<A = 0`== @H@~B B$  B@Bc@x` =AJ @EỀ Jzze=b΄F;  ={% F%@@ ! @ aB b}b! b B@abBy@x@A$A Ehwb{|b =j7`=j @5@,JkJA# B@aRB@x@A AREu@Y }0$F =R="@n@ $G J@l|Bbb f B@jBt@x@A$AjEb~&!j =j=%-(+c@欀@ $( @(@BBDB,B B@JB@x@AAJEeJe=xF =sk{% %@f@, ȎAb#b 륱 B@aB@x@A$A E !a:#"! =j5=j "@߀@,#JJ ($ B@aRB}c@x@A AR%EaR $!R& = {Ο=R"'@+@c $JT +f D FB(bbj) B@jB@x@A$Aj*ESjaj+ =j"W=j%-(%,@V@ -BUB) c. B@JB@x@AAJ/E%aJ} e0=Fc1 ={% "2@a@c $A3b ! c4 B@aBc@x@A$A5Eʀc4q$^6 =jۊ a(f7@>@,8JJA#9 B@aRBx@x@A AR:EA a aR; =RTI="<@D@ $#@ du`$+jL$B=b!bA;j> B@jBB@x@A$Aj?E?@Y jaj@ =j a%-(%A@ ~BBdB(B$ C B@JB B@x@AAJDEƸ JeE=G˄FvcF =@{% "G@@ y5\ @+c AHbbbe! bI B@`B@x@A$AJEMt bcbK =jq4 `=^%L@2@,MJ@JA#cN B@aRB@x@A AROEZ@Y FaRP = + =R"Q@f@ c34u$ bBRbb½S B@B@@x@A$AjTEb jajU =jU=j%-(%V@@~WBB$ X B@JB@٢o@x@AAJYEhbJeZ=F$X[ =@Xh{% "\@c@ ! @( 9A]bb ! b^ B@`B@x@A$A_Eab` =jހ="a@y܀@ m'$,bJ۠J c B@aRB@x@A ARdEaR FaRe =R=R"f@@ CŲ c4Bgblb#h B@jBc@x@A$AjiEPjajj =!! T=(%-(%k@eS@ $( @(@BlBRB J$Bm B@JB@x@AAJnE Jeo=|ycp ={% "q@B @ tArb ! 륱s B@aB@x@A$AtEǀbu =jaj%pj"v@#@,wJJA#x B@aRB@x@A ARyE>a aRz =RFF=R"{@A@ S RC-t B|bb j} B@jB,@x@A$Aj~E$ jaj =j="@@BpB$ B@JB@x@AAJEb Je=,ȄFc ={% F"@趀@ c wBbGb B@aBc@x@A$AE2qacb =jV1`=j @/@, J%JA# B@aRB@x@A ARE?aR =R="@C@!1`@.!cT?0PT~ WBbb  B@jB2W@x@A$AjEƣb f#%9 =jB=%-(+c@@ rBB  B@JB@!KQ@x@AAJEM_Je=ΤFvc =@Ee{% %@`@ [Ab   B@`B @x@A$AEab =jڀ=j$j+c@Jـ@ mJؠJ  B@aRB$X@x@A AREaR FaR =R="@ᔀ@ c sA"|kk=BbMb½ B@jB@x@A$AjEhMaj2 jaj =jP=%-%@S@~BO Jc B@JBc@x@AAJEaJ Je=o| ={% %@, @ ! @ (b b! b B@aB#'Zh 5@A$A @EuĀ&( =ja @@,JtJ@ B@a B@x@A ARE; a aR =R+C=R"@>@ PQ 0+ia:n!b=b  B@jB| c@x@A$AjE jaj =j=j @@ $( @(EBABBc B@JB@x@AAJE!e=ńF ={% @γ@ ) @+c t b,b bc B@aB-"@x@A$AEn"ab = ``<.#`= @,@,J J c B@B@x@A ARE$@Y: aR =R=R%@$@ T+aq0BDfbbf B@jB @x@A$AjE$b jaj =j7=j%-(+c@@~BB) Q B@JB@x@AAJE2\%aJ} Je=F =b{% "@k]@ @4 2Ab ! b B@aBy@x@A$AE&acb =j׀="@?ր@,JՠJ (R B@aRB@x@A AREƎ'aR aR =R= @֑@ @ ,Sq}6FvbBbj B@jBɂ@x@A$AjELJ(jaj =jM=%-(%@0@ BL(J$  B@JB@&@x@AAJE)Je=T|* =@ {% %@ @x Ovbob !  B@`B0 c@x@A$AEZcb =j*a$j.@@,JMJ! B@aRB@x@A AREg8+a aR = @=R"@k;@* $6 #0/S# MDb:bf B@B@x@A$AjE jaj =jv=(%-%@@ nB2B J$ &JBc@x@AAJEu,e=Fwy =m{% "@@ ! @ Abb b B@bB@x@A$AEj-ab =j2+.`=^% @)@, J(JA#j B@aRB/@x@A AR E FaR =R=R"@ @ $Xp! S b aBbub j B@jB@x@A$AjE/b jaj =j=(%-(%@x@$B؟B_  B@JB@x@AAJEY0Je=kF =_{% "@QZ@ GJAb  XF B@aB@x@A$AE1ab =jԀ=j c@$Ӏ@,JҠJA# B@aRB@x@A AR!E2aR@Y FaR" =RQ=R"#@@ c @B$bb j% B@jB !@x@A$Aj&E1G3aj Iaj' =jJ="(@@)n(AJ)B}I Jc* B@JB@x@AAJ+E4aJ e,=9|c- ={% .@@ ! @c .A/bTb! b 06`aB "@x@A$A1E? b2 =j€=j 3@K@,4JJ (Rc5 B@aRB @x@A AR6Ey5b RaR7 =Ra=R%8@|@ $Xl1 "0TaqB9b.bj: B@jB@x@A$Aj;EL56aj jaj< =j8=j%p(+c=@.@~$( @ť>B7 B$Bc? B@JB@x@AAJ@E JeA=ۤFWB ={% "C@@, ADbob cE B@aBL6@x@A$AFEZ7bG =j ="H@j@,IJ֮JA#>J B@aRB@x@A ARKEg8RaRL =Ro="M@j@ prS"arBNbMb$O B@jB@x@A$AjPEg#9jajQ =j&=j%-(%R@O@ rSB% BT B@JB@x@AAJUE eV=FW ={% "X@(@ ! @c AYb߀b b Z B@aB@x@A$A[Eu:bb\ =jZ;`="]@Y@,^JoXJA#_ B@aRB@x@A AR`Eabq = `A?`=^"r@@,sJ J t B@B@x@A ARuE$@Y: aRv =R€=R%w@(@ +\ qSq"#0@,Dfxbb½y B@jB@x@A$AjzEv@b jaj{ = 3z=j%p(+c|@y@ }BxBJ$ ~ B@B@x@AAJE12AJe=F =%8{"@l3@ !%Nc Ab  by B@aB@x@A$AE1 b =je=j%@@,J4J (R B@aRB@x@A ARE?Bb RaR =R簀=FR%@?@ #S#u aovCbb j B@jB y@x@A$AjEdCaj jaj =jIh=%-@g@r$( @"AJBBB B@JB@x@AAJEL DJeT =<&{% "@!@  c#˄Ab  륱 B@aBc@x@A$A0Eۀb jEaj^"@f@,* JҙJ ݡ B@x@A AR @RFa FaR =RZ=R"@U@ %3 +b6`7HBbPb½ B@a$B$X@x@A$AjؠEgGjaj j=j%p(&@U@ B B}B@x@AAJ}E e @o܄F/ =π{% "@+ˀ@ y! @c3Abʀb b á$B@x@A$ASEtHbbEI`=%@C@,cJkJ c B@x@A ARc  aR2JaR"@~ cCS#Kb  |lbb$Bc@x@A$Aj˱E ,aj=j @@ $( @"DBYB gƹB@x@AAJIEsKbw )e @F =y{F% @t@  c lb/b ! c B@a$B@x@A$At$B@@x@A$Aj=ΠE#[  jaj# -=j%-(%$@@~%Bc B4ڡB@x@AAJ7EL\  Je6%=F) kR{% "*@M@, hv/G?A+bFb j! 0RkB@x@A$A- @1]`*b. -jnȀ="/@ƀ@@Sf0J^ uaR3 -@a놀= 4@F@ cT% Gu!YtɂB5bb !BB@x@A$Aj E:_ jaj8 -jY>=j 9@=@ :BB$ ݡB@x@AAJELwee=ѤFy>D{% ?@@ +B@b ^! /$Bc@x@A$AEұ` QbC -qa #D 0@Up@,cEJoJ B$X@x@A AR E(b #A  aRH -0=(oI@+@o%+ctc B@x@A ARE aRLe;ڀ=R%J@Հ@ B t 䐃R +l |BHbb fҡ$BB@x@A$AjEfb jajEx=(%-%C@Ԑ@ CoB4Bµ$ Bc@x@AAJEIgJ @=-=\@] ch kOM% "i@J@ / @ }Ajb+b bck B@o>B@x@A$AjlE@,bm =j?ŀ=j n@À@,oJ J p B@aRB@x@A ARqE#|An  F  aRr = *у="s@3@ <}YE7 *Btb~b@&u B@Byc@x@A$AjvE7B  j  ajw =j2;=%-(%x@:@$yB9Bz B@JB@x@AAJ{E0 J  e|=F} ={% %~@k@ , iAb͡ 'Z B@aB5n@x@A$AE  b =jnj @>m@,JlJA#Fc B@aRB@x@A ARE%E  aR =Ro-="@(@ 9KXo`-%$Z Bb=b@&j B@jB@x@A$AjEK@Y jaj =j=j @(@ cB〃 B@JB@x@AAJEҜH J >=Sk` -¢{F% @ @  c $Bbnb B@aBc@x@A$AjEYXJ0cb =jwjj1@@@Sr°!j @ JDJ J B@aRB@x@A AREfϠ@Y aR =@ր=R(o@ZҀ@ p#-0QD<@{BbѠb  B@B @x@A$AjE튌 jaj =ju=j @ҍ@$B1BJ)  B@JB@x@AAJEtFL Je=Fx =XL{% @G@ c DBbb  B@aB W9@x@A$AENs b =j=%pj%@@,,J_J F B@aRB*@x@A AREyaR =Rŀ=R%@u@,&,Q[`QD>Bb῀b  B@jB@x@A$AjEyQ5 aj =j|=j%-1@{@ cBHB B@JB@x@AAJE4 Je= =:{% "@5@, Ab+bZ B@aBc@x@A$AEb =j6W"@@,cJJ  B@aRB@x@A ARE#gT)  aR =Rn=R"@'j@Q&1 Qz|tI/*-Bbib½ B@jB o,@x@A$AjE"U jaj =j2&=( @%@ $B$B B@JB@x@AAJE0ހ Je=F/ =${% `3b@i߀@, -Bb ! B@aB "c@x@A$AEX b =jY^+c@>X@,JWJ (R B@aRB@x@A ARE !!aR =Re=R%@@ #4tɂrژb%b  B@jB @x@A$AjEK̀j""aj =jπ=( @2@ B΀µ$  B@JB@x@AAJE҇K ##e=WFx =ʍ{% @@ b5\ @, KEbnb b B@aB@x@A$AEYC\$%b =j\j%@@,JWJ B@aRBz  @' @x@A AREf DE &&aR =R€="@n@&3RFt"Q bڼb½ B@ABB@x@A$AjEu` j''aj -j}y=(%-(1@x@$B9BJ B@JB@x@AAJEt1 J((e=Fc =d7{% (@2@ y': @ Abb b B@aB@x@A$AE1 ))b8j=j @@,JfJ (Rc B@aRB$X`x@9 AR @`E R**aR =?=R"@@ cCk0SC!Qk0`̠Ǝbbj B@a$Bc@x@A$Aj Edb j++aj =jg=j @f@~$( @(D BLB B@JB@x@AAJEJ,,e= =%{ @ @ c ,vb*b ! c B@aBc@x@A$AEۀ --b =j߀=j @%ހ@,JݠJA#  B@aRB@x@A ARE..aR =RH=FR%@@ S ?kE%0aDfbb j B@jB@x@A$AjE#R  //aj =jU=j%p+c @ @ !BoT B$ " B@JB@x@AAJ#E  J00e$=FL6% ={F% "&@@ ! @c @aF33aR/ =RG=R"0@FC@ &cC! 0 0aGB1bBb½2 B@jBh@x@A$>3E f44aj4 =jP="5@@ $(n"AJ6B BJ$B7 B@JB@x@AAJ8EKb J55e9=̤F: =7{% ;@@ c 8A<b  = B@aB 9t@x@A$A>Era67b? =j3^"@@e1@,AJ0J B B@aRB@x@? =ARC @`E逈88aRD =R=R%E@@ s r0QDъ0`̠~BFbGb½G B@a$BF@x@A$AjHEfb f99ajI =jꨀ=(%-(+cJ@E@ rKBJL B@JB@x@AAJME`aJ J::eN=nsFO =f{% "P@*b@ ! @c AQbab b R B@aB@x@A$ASEsac;>aj^ =jR=j _@Q@ $( @(AJ`BPBa B@JB* @x@AAJbE J??ec=Qd ={F% e@ @ c Afb.b ! g B@aB@x@A$AhEƀ@Abi =j:ej j@@,kJJA#l B@aRB@x@A /mE"=a BBaRn =RD=FR(oo@3@@ o @o4"䐃()@CVBpb?bq B@jB5n@x@A$AjrEfCCajs =j9=%p(+ct@@$uBB J$ v B@JBc@x@AAJwE0DDex=Fy ={% "z@f@ ': @ژ{b ! b| B@aB$X@x@A$A}EoEFb~ =j/j$j(f@5.@,J-J  B@aRB@x@A ARE GGaR =Ri=R"@@ PGtZ/Ô@7JFDb0b jc B@jB@x@A$AjEKfHHaj =jӥ=j%-%@.@ $( @"@BB B$B B@JB@x@AAJE]JIIe=WpF5n =c{% "@ _@F Abm^b 륱 B@aB@x@A$AEXacJKb =jzـ="@׀@,JGJA# B@aRBc@x@A AREf$J oA@LLaR =@="@j@ 3$U00*vqBb֒bo B@B@x@A$AjEKb jMMaj =jmO=%-(%@N@~B,B B@JB@:W@x@AAJEsJNNe=F* =@c {% "@@ 5n! @c  rAbb ! b B@`B@x@A$AE€cOPb =j$a^%@@ m2 @* JJ  B@aRB@x@A ARE:a QQaR =RA=!@=@ , 1$"QD@ a&Bb B@aRBB@x@A AR?EefrraR@ =Rn=R"A@qi@, '3> "Kc$"rBBbhb4½C B@jBy@x@A$AjDE!jssajE =j`%=( F@$@ rGB B J$ ,H B@JB @x@AAJIEs݀ tteJ={|*K =@c{% L@ހ@, tBMbb! !n@ cN B@`By@x@A$AOEbuvbP =j(YNsj%pj+cQ@W@ mRJVJA#S B@aRB; @x@A ARTEOB wwaRU =R="V@@!!!C4eu*zBWb{b½X B@jB; @x@A$AjYEˀ2 jxxajZ =jπ=%-+c[@{΀@~\B̀B J$ ] B@JB@x@AAJ^EQ Jyye_=Fx` = {% (a@P@ !Abb ! c B@aB5n@x@A$AdEBz{be =j`=$j%f@*@,gJJ h B@aRB@x@A ARiE ||aRj =R`=R"k@@  St-/4%~Ozalb%b m B@jB@x@A$AjnE/ub j}}ajo = x=%-%p@@ qBsw r B@B@x@AAJsE0aJ~~et=7CF}]Lu =6{% "v@1@ ! @ $wbVb bx B@aB@x@A$AyE=bz =jhWj^%{@Ǫ@,|J3JA#} B@aRB} BL6@x@A AR~EJcb FaR =Rj=R"@Zf@ ycRYdp%QYpn!beb@(' B@jB; @x@A$AjEjaj =ja"=j%-(%@!@ $( @"@BBB)B B@JB@x@AAJEXڀ@Y e=ݤFy =D{% "@ۀ@ b/ @6 t b Z b륱 B@aB@x@A$AEޕbb =jV\"@qT@,JSJA#rR B@aRB@x@A ARE ]\ aR =R="@@ csUVSBa[Dfb`bj B@jB @x@A$AjErȀ jaj = ˀ=%-(%@H@~Bʀ(B B@B@@x@AAJEd Je=zFW =@퉀{% "@5@ y! @6 Abb b B@`By@x@A$AE?acb =j=^%@ @,JwJ  B@aRB@x@A AREaR aR =R9=R"@@ yx|?C!Bbb½ B@jB; @x@A$AjErcT jaj =ju=j @t@~$( @"AJB@B B@JB @x@AAJE-e=@| =@3{% @.@ 8b c VhAb7b ! c B@`By@x@A$AE"b =jJa"@@ m!j$,JJA# B@aRB@x@A ARE/`a FaR =Rg=R%@#c@  ttps"\x$aBbbb j B@jB@x@A$AjEjaj =j>=(%p(+c@@ BB J$  B@JB@x@AAJE< e=F =!݀{% "@u؀@m @c ZAb ǝ! b|y B@aB 'Z@x@A$AEÒbb =jR`=j%pj(f@RQ@,JPJ F B@aRB@x@A ARE k~ aR =Ry=R"@ @ \v@$uaTBb=b j B@jB@x@A$AjEWŀ jaj =jȀ="@2@BǀB$ B@JB@x@AAJEހc Je=_`=c =҆{% F"@@ ) @6 Bbzb b B@aB@x@A$AEe B@aRB@x@A AREraRuaR = ="@v@  "u " u Bbⵀb  B@B@x@A$AjEnqv faj =j}r=%-(+c@q@ rB=B c B@JB@)@x@AAJE*r{e== = t0{% %@+@ ! @6 #Ab b bc B@`B@x@A$AEcb = `'dj @@ m' @JJ  B@B$X@x@A ARE]aaR =Rd="@$`@ u JE~%u Ib_b j:  jB@x@A$AjEaj2 aj =j#=%-(%@~@~n$( @.`@BBB Jc B@JBc@x@AAJE! Je=ʤFS, =ڀ{% % @_Հ@ $F @+c f b ! b륱 B@aB@x@A$A Exb =jOx%pj"@?N@,JMJA#j B@aRB@x@A AREdF aR =R^=R"@ @ s<C""t!/a}Dfb.b  B@jBx ! u@x@A$AjE< @Y jaj =jŀ=j%p%@(@ BĀ c B@JB@x@AAJE}e=DF$X ={% "@@ ! @+c \Ab_~b bc B@aB@x@A$A!EJ9ab" =js= #@@@Sr°' @c$J@J R% B@aRB@x@A AR&EW~c FaR' = =!(@c@ cv "P0 nb6ɂ)bϲb* B@B@x@A$Aj+Ek jaj, =jRo=j%-j%-@n@~$( @(@B.BB)B/ B@JB@x@A>0Ee'd} Je1=F2 =Q-{% %3@(@ , ɂ4bb! 륱5 B@aB@x@A$A6Ecb7 =j!a"8@@,c9JJA#: B@aRB@x@A AR;EYa aR< =Ra= =@]@ y (SP"("CaMDf>bm\b½? B@jB@x@A$Aj@E5 jajA =j=%-(%B@c@~CBB cD B@JBb@x@AAJEEр JeF=FL6G =ր{%H@@Ҁ@t AIb J B@aB@x@A$AKEbL = `L^(fM@K@,NJJJ O B@B@x@A ARPEe@Y: aRQ = +D =R"R@@ $F!!(C[CgB0hBSb b jT B@B@x@A$AjUE! jajV =j€=(%-W@@ n!j @`" "AJXBeBY B@JB@٢oB2ay@x@AAJZEze[=)Fc\ =@{ ]@{@c #4^bHb c_ B@`B@x@A$A`E/6aba =j]=^"b@@,cJ)J cd B@aRB/@x@A AReEc B@aRBw* @x@A AREa FaR =R.="@@ 3($)St).SnbDfbbf B@jBy@x@A$AjE jaj =j=%-%@뾀@ rBJB J B@JB@x@AAJEwe=F =y}{% %@x@ ! @( Zhb)b b B@aB@x@A$AE3cb =j2=j @@,JJ c B@aRB|@x@A ARE! FaR =Rñ=R"@!@ Ct .?1 ֐)Dbb B@jB@@x@A$AjEedfaj =j,i=j @h@$BgB J B@JB@@x@AAJE.!aJ e=F} =@'{G e @j"@ c }Bb ! B@`B !@x@A$AE܀b =jaj @H@ m!j @JJ c B@aRB@x@A ARESD aR =Rm[=F!@V@ Sp;/*`aBb7b½c B@jB@x@A$AjEIi jaj =j=j @&@~) @w.`AJB B$Bi B@JBc@x@AAJEʀe=Qd =Ѐ{% @̀@c ^JAbpˀb  B@aB@x@A$AEWb =jFa"@D@, JRJA#j B@aRB@x@A AREd FaR =RaR @h@ c%?)e p`Bb ĩ B@jB* @x@A$AjE븀jaj =jo=j%-(1@̻@~B+B B@JB@),@x@AAJErtbw> e=F =@^z{% .W@u@ y) @6  Abb! b B@`B@x@A$AE/acb =j,="@@,cJJ  B@aRB@x@A ARE aR =R=R"@@(sR`<|Bb~b@(j B@jBy@x@A$AjEbd jaj =jf=j @ne@~BdB c B@JB @x@AAJE( Je=0| =#{"@O@/ zBb  B@aB@x@A$AEـb =jʙc1.@-@,JJA#F B@aRB; @x@A AREPa aR =RSX=R%@S@ e#a\iBbb@$j B@jBc@x@A$AjE.  jaj =j=(%-+c@@ n;f  B@JB c@x@AAJE Je=6ڄFu =@̀{*c@Ȁ@ c AbQb  B@`B@x@A$AE<d^b =jpC`=^% @A@, J>JA#F-ȯj`'F ;` >A8 y  `  @'  5  > @@IE@`;    A =@`=`G "=  @@3c@I~ %~@ `@1 @# K (R`<[+` !C bbb`bB aj B@B j@x@A$AjEе, DE jaj =A6T=H <=!jj!@@ ?@!j @K $ BBB J BiJ B@B@J!K@x@AAJEVqbw J@==؃`=}3#:  B " =@Cw{"@r@@at! @ hv/G? Ab ,! biX$^ B@`B !@x@A$AjE, aj 5!b =j=j^"@h@ m`@> ! @ AR JJ R `JR-! B@aRB@x@A AR"E aRRA@aR# =@=G "$FR"$@@ @pS%"ϥ yAj%b_b@&j& B@B{ @x@A$Aj'Eq_ aj jaj(; ] c=%p%n)@\b@r$( @r"@B*BaBB$B+ B@B@!K@x@AAJ,E aJe-=y-|z. =@ {% "/@5@ $F @ =A0bb b륱1 B@`B !@x@A$A2Eրb3 = `` aj$j"4@@@Sm!j @5JzJ J6 B@B@x@A AR7EMa FaR8 =@=U="9@P@?ㅲaxB:bbf; B@B @x@A$Aj<E jaj= = { =%-%>@ @ r?BSB J@ B@JB@x@AAJAEĀ eB=ׄFC =ʀ{% %D@ŀ@ @.W AEb6b! b F B@aB "@x@A$AGE bcbH =jI@`=j%pj%I@>@,cJJJ K B@aRBzLW?@x@A ARLE.@YE aRM =R=R"N@&@ cŲ&䐃(@BObb@%½P B@jB| @x@A$AjQEb2 jajR =jA=j S@@ TBBJ$ U B@JB C!Kc@x@AAJVE;naJ JeW=FwcX =+t{% F"Y@yo@ ) @+c BZb  b[ B@aB@x@A$A\E)ab] =j=%^@M@ m!j%c_JJA#Fc` B@aRBc@x@A ARaEϠ8@T aRb =R|=!c@أ@ 1P)t/`%@BdbDb fe B@jB@x@A$AjfEV\jajg =j_=jj+ch@9@iB^ Bj B@JB@@x@AAJkEJel=^*|{m =@{% n@@c Aobyb p B@`B@x@A$AqEdӀbr =ja"s@@ mtJZJA#u B@aRBc@x@A ARvEqJa aRw =R*R=R(ox@yM@ %o.!3$4ǏacBybLbĩz B@jB@x@A$Aj{Ejaj| =j =( }@@ ~B<B B@JB@x@AAJE~ e=ԄF = 5wǀ{% @€@ $F @+c aBbb ! b B@aB 'Z@x@A$AE}bb =j;=`=^+c@;@,JJ B@aRB@x@A ARE@Y: aR =R=R%@@ 1 $1 "@Bbb@% B@jBx ,@x@A$AjEb.aj =j"=j @}@ BݱB J$  B@JB@x@AAJE kaJ @==Fc = Àq{"@fl@ !%N( #yBb ! b B@`B "@x@A$AjE&acA%b \ ``=)%@2@,JJ >c B@B@x@A ARE aRaR =Rf=R%@@ !)E"e",/Bb,b½c B@jB @x@A$AjE;Y!jaj = \=(%-1@@ r$( @"@BB[ J$B B@B@?!K  @x@AAJE"Je=C'| =@{% "@@ W c LAb^b 륱 B@`B@x@A$AEHЀb =jn#aj$j"@ώ@ m!j @` J;J , B@aRB{y@x@A AREVG$aaR =RO="@jJ@)7$/pAu$`A aq@BbIb j B@jB@x@A$AjE%aj2   aj =j]=%-%@@~ƹBB J B@JB@@x@AAJEc J  e=F| =@TĀ{% %@@ @ ղAb ! b B@`B@x@A$AEy&b  b =j:'`=%pj%@i8@ mJ7JA# B@aRB@x@A ARE  aR =R=R"@@ #Aq8ӔP8d`aBbtb  B@jByc@x@A$AjE~(bjaj =j = @g@ BƮB $  B@JB@x@AAJEh)aJ e=zF =m{% F"@E-aJ} e=($| ={%p"@@!! @ AbCb B@aB@x@A$AE-̀yb =jd.a%@ċ@,J0JA#c B@aRBc@x@A ARE;D/a aR =RK="@GG@ cC A8ÔPAm`OBbFb½ B@jB~@x@A$AjE jaj =j60` @@ $( @ "AJBBBc B@JB@&@x@AAJEH Je=ɤFy< 4{%p@@ y c OAb什b'Z  B@`B`x@9 A @Ev1bcb =j62`=^"@Z5@,J4J@ B@a B@x@A AR E aR =R{=R% @@ cSӔ@%O8TAsB bHbA;j B@jB@x@A$AjEc3b jaj =j笀=j%-(+c@H@~B B$  B@JB@@x@AAJEd4Je=kwF$X =@j{% "@%f@ ! @c Abeb b, B@`B@x@A$AEp 5ac b =j="@ހ@ m'$yJgJA# B@aRB@x@A ARE~6aR\!!aR =R1=R" @@!Rc(T<9T`zYB!bb j" B@jBc@x@A$Aj#ES7j""aj$ =jV=(%-(%%@U@ $( @(@B&BQB J' B@JB@x@AAJ(E8aJ ##e)=!* ={bxe "+@@ $F%N6 fA,b'bǝ! b륱- B@aB 'Z@x@A$A.Eʀ$%b/ = ``69aj$j"0@@,1JJ 2 B@B@x@A AR3E A:a &&aR4 =RH=R"5@(D@!3 s:T%; a76bCb½7 B@jB @x@A$Aj8E''aj9 =j6;a":@~ ;BB $c< B@JB@x@AAJ=E-  ((e>=Fc? =%{% F"@@i@ ! @Y Ab ! b B B@aBB@x@A$ACEsb f,,ajN =jȩ=%-+cO@&@ r$( @"@BPBJ$BQ B@JB@&@x@AAJREa?aJ J--eS=PtFuT =@g?`{% %U@ c@ $F @+c AVbkbb bcW B@`Bc@x@A$AXEU@a,c./bY =j݀=jj"Z@ۀ@,c[J\J \ B@aRB$X@x@A AR]EcAaR 00aR^ =R="_@k@ %=QT>@/axB`bזb½a B@jB@x@A$AjbEOBj11ajc =jrS=j%-(%d@R@ eB2Bf B@JB@@x@AAJgEp CJ22eh=Fi =@l{F% %j@ @ ! @.W /Akb b ! b l B@`B !@x@A$AmEƀ34bn =j'Daj o@@,pJJ q B@aRB@x@A ARrE>Ea 55aRs =RE=R"t@A@ %t_TtaBub|@bv B@jB @x@A$AjwE j66ajx =j=j y@u@ $( @(AJzBB$Bc{ B@JB@x@AAJ|EF77e}=DŽF~ ={% @K@ c 6Ab ! c B@aB "@x@A$AEpGa89b =j0H`= @+/@,J.J B@aRB@x@A ARE F::aR =R^=R%@@ q +i?ab =j_ڀ="@؀@,J-JA# dR- B@aRB@x@A AREHLaR??aR =R= @P@ yS%@QaA%aBbb j B@jB/@x@A$AjELMaj @@aj =!bSP=%-(%@O@~BB$  B@JB@&@x@AAJEUNJAAe=֤F$X =@Q{% %@ @ , _Ab !  B@`B@x@A$AEÀcBCb =jOa^.@c@ m, @yJρJ  B@aRB@x@A ARE:Pa FDDaR =RB=R"@=@ y B@`B@x@A$A?E4faybcb@ =j=^%A@>@ m' @BJJ C B@aRB@x@A ARDEgaR ddaRE =R`=!F@@ cCt+:(tȏ`;nbGb-b½H B@jB@@x@A$AjIEGghaj jeeajJ = j=%-j1K@5@ LBiJ$ M B@Bc@x@AAJNE"iJffeO=O5|* P =({% (Q@$@ @$F @#!8+c ARbj#b bS B@aBc@x@A$ATEUހ@ghbU =jxjaj^%V@ל@,WJCJA#X B@aRB}UBnb@x@A ARYEbUka FiiaRZ =R]=R"[@rX@ SP/ QmaPB\bWb@$'] B@jB{y@x@A$Aj^Eljjjaj_ =jm=j%-(%`@@ aB)Bb B@JB@x@AAJcEp̀} kked=Fe =\Ҁ{"f@̀@ ! @6 kAgb bZ b h B@aB@x@A$AiEmllbj =j=%k@@,lJbJA#Fcm B@aRB@x@A ARnE}CnRmmaRo =R.K=R"p@F@*c` 1 F?BqbEb jr B@jB@x@A$AjsE fnnajt =joa( u@@ $( @`" "AJvB@Bw B@JB@x@AAJxE Jooey=z = À{{% :"{@@  c YA|b'b$} B@`B@x@A$A~Evpbpqb = `G6q`=^"@4@,JJ  B@B@x@A ARE rraR =R=R"@@ *s0Cŀeb{b j B@jB @x@A$AjErbyssaj =j.=j%p(+c@@wB骀B J B@JB@x@AAJE,dsaJ tte=F =j{"@fe@ eb !&U B@aB@x@A$AEtauub =j_$=j @"@,J+JA# B@aRB@x@A ARE:ۀ(vvaR =R=FR%@>ހ@ yL6d`89aҪDb݀b  B@jB@x@A$AjEub fwwaj =jP= @@ B B  B@JB@x@AAJEGRvJxxe=S y =3X{% :"@S@ vZb   B@aB@x@A$AE wayzb =j΀=j^+c@è@,BJˠJA# B@aRB|JeB@x@A AREۄxaR F{{aR =Rz=R"@ۇ@ :C""#0S#Q(EbGbA;' B@jB@x@A$AjEb@yj||aj =jC=j%-(+c@L@ rBB J)  B@JB@x@AAJE }}e=j| =za% "@% ! @ Abb b B@aBc@x@A$AEo,y~b =jw{aj%@v@,cJruJ F B@aRBF@x@A ARE}.|aaR =R46="@1@ (1P_$Z (BbbA;j B@jB@x@A$AjEfaj =j|=j%-(%@@ $( @"@BB@BB B@JBF@x@AAJE}b e=Fr3(BEO A = v{% "@Ǧ@ y ,Ab&b 륱 B@`Bc@x@A$AEa~ab =j9!`="@@,JJA#4`  B@aRB@x@A ARE؀aR =R߀= @+ۀ@   aRB@x@A AREb+a aR =R2=R%@V.@ 33yBb-b½ B@jB@x@A$AjE怈caj =ju=j%-(+c@@  B1B$  B@JB@c@x@AAJ Eoe =F~ =@g{"@@ !%NK FAb b ! bAf@`BF@x@A$@A @E]aBb =j#`=%@@,JJ@c B@a B@x@A ARE aR =R܀=R%@؀@ ]`1k1WBb׀b j B@jB; @x@A$AjEbaj =j=(%@a@$BƒB J$  B@JB@@x@AAJ ELJe!=^F" =@ R{% :"#@RM@ , vA$b % B@`B !$X @x@A$A&Eacb' =jǀ=jj%(@ƀ@ m, @)JŠJ  `JRL6* B@A*B @x@A AR+E~aR laR, =RR="-@@ ːa"[a.bbc/ B@jB@x@A$Aj0E,:jaj1 =j==%-(%2@@$3Bd< J4 B@JB@x@AAJ5E e6=4|7 ={% %8@@ c 39bNb ^: B@aB@x@A$A;E9bb< =jrq`=%pjK=@o@,>J@J j? B@aRB@x@A AR@EG(a aRA =0=R"B@[+@ +y`0 ;BDCb*b jcD B@jBB@x@A$AjEE〈cajF =!bY= G@@ƹ$( @"AJHBB JI B@JB@x@AAJJETb eK=դFL =D{% F"M@@ y) @ ANb ! b륱O B@aB@x@A$APEZaybQ =j`=j^"R@r@, SJJA#T B@aRB@x@A ARUEрaRV =Rـ=R"W@Ԁ@  zzxy"*K0qXbdb½Y B@jB@@x@A$AjZEob faj[ =j=j%p(!\@^@~]BB$ ^ B@JB@x@AAJ_EHJe`=w[Fa =N{% "b@,J@ ! @( qcbIb ! bd B@aBc@x@A$AeE|acbf =jĀ=%g@À@,chJ JA#i B@aRB@x@A ARjE{aR FaRk =RG="l@~@+#r~=}{zy@ Dmbbĩn B@jB@x@A$AjoE7aj_ajp =@݀:=j%-(%q@@ $( @"@BrBa9 g$Bs B@B@x@AAJtEeu=|cv =!?{% "w@@, `Axb7b !륱y B@aBc@x@A$AzEb ^b{ =jӲ="|@2@,}JJA#~ B@aRB|@x@A AREiaRaR =RPq=R"@l@ +30 ;$bb@%j B@jB@x@A$AjE+%aj aj =j(=j%-(!@ @ nBl' B$  B@JB,@x@AAJEJe=F; ={"@@ 0AbNbȥ! B@aB @x@A$AE9bb =jY\`=(f@Z@,J(JA#F B@aRBB@x@A AREFa FaR =R=Rc@R@ CÔP aBbb@$j B@jBB@x@A$AjE jaj =jYҀ=(%-@р@ nBB J$ B@JBc@x@AAJETe=٤F =@{% "@@ uBb  B@aB@x@A$AEEat b =J=j @H@,JSJR B@aRB@x@A AREaaR RaR =R =R"@i@ S@Kc a!dbbj B@jB@x@A$AjE jaj =jx=j%-(+c@Կ@ rB4B J B@JB@x@AAJEoxe=wF5n =c~{ @y@]L G'Lb b Z B@aB@x@A$AE3a b =j=+c@@,JJ  B@aRB@x@A AREaRaR = =%@@ ccpbt"iuDb{b j B@B@x@A$AjEfaj2 aj =jj=j @ui@ BhB B@JB@x@AAJE"aJe=4|@ ='{F% :"@N#@  , ͇'b ! B@aBy@x@A$AE݀yb =jɝaj^%@*@,JJA#F B@aRB@x@A ARETa@Y FaR =Rf\=FR"@W@ 7 @1@ # (ost%tEc|b-b(c B@jBc@x@A$AjE+aj jaj =j=j @@~Bg B$ , B@JB@x@AAJEˀe=3ބF =р{% @̀@ @lbRb bc B@aB .B@x@A$AE9bb =j֋="@9@,JJ  B@aRB@x@A AREBaR FaR =RWJ=R%@E@  Ӫ)tRawHob b$ B@jBW@x@A$AjEF@Y jaj = +a(%-()w@ @ B~, B@B@x@AAJE͹ Je=٤F; = À{% "@ @ Ix c Abib B@`B,@x@A$AETubt b =jy=j)j+c@\x@,JwJA#/ B@aRBc@x@A ARE0aRFaR =R|8="@3@ % kpepB?Bb j B@jBc@x@A$AjEa aj =j= @M@̀=j $@ˀ@ m!j @`# %J J (& B@aRBc@x@A AR'EaRaR( =Rϋ=F!)@*@ c`$ aɠHo*bb j+ B@jBc@x@A$Aj,E?ajaj- =j$C=j .@B@ /BAB$ 0 B@JBc@x@AAJ1E+ e2=F3 = Àa% 4@e ! @ mRB5b ǝ! b6 B@`B@x@A$A7E,b8?j[="9@@,:J*JA#j; B@aRB@x@A AR@iu@ y U%% l1B?btb @ B@jB  ,@x@A$AjAE-jajB =j71=(%-(6C@0@ $( @4@BDB/B,BE B@JBF@x@AAJFEF逈eG=RH =>{% "I@@  AJb 륱K B@aB "@x@A$ALEͤbcbM =jd`=^"N@Oc@,OJbJ P B@aRBy@x@A ARQEaaRR =R#=R"S@@ 1 I1KfJsBTbVb jU B@jB; @x@A$AjVEa׀ ajW =jڀ=(%-(%X@?@$YBـZ B@JB@$@x@AAJ[E蒿be\=iFB] =@{% "^@(@/ .!_bb c` B@`B@x@A$AaEnNabb =`=j%c@ @ mdJiJ  `5ne B@aRB@x@A ARfE|ŀ ףA FaRg =R4̀="h@Ȁ@ TD@V7#Bibǀb jj B@jB@x@A$AjkEbajl =j=j m@郀@ cnBKB$ co B@JB@٢o@x@AAJpE<Jeq= OF@r =@}B{F s@=@ $F @ Btb)b ! bu B@`B@x@A$AvEcbw =jDaj x@@,yJJA#z B@aRB@x@A AR{EoaFaR| =Rv=FR(o}@-r@ TupD{1aTB~bqb j B@jB; @x@A$AjE*jaj =j8.=%-)w@-@$B,B J$  B@JB@x@AAJE+怈e=F; =#{% "@l@ ': @+c$Abˡ b B@aBc@x@A$AEb b =ji=^+c@ʤ@,J6J > B@aRBy@x@A ARE8]aRaR =Rd= @<`@,a\Bb_b  B@jB@x@A$AjEjaj =jG=j%-(%@@ $( @"@BBB B@JB@x@AAJEFԀe=R =:ڀ{% %@Հ@ GAb ! 륱 B@aBc@x@A$AȄbb =jP`="@cN@,JMJ c B@aRB@x@A AREaaR =R=R"@ @ pE`Ô@a]BbFb j B@jB,@x@A$AjEa€oaj = ŀ=(%p(%@I@ BĀ B$ c B@B@x@AAJE}e=lF| =䃀{% "@&@ Ab~b c B@aB@x@A$AEn9ab =j=j(f@@,JqJA# B@aRB@x@A ARE|aRFaR =R2=R"@@ ,#Ӕ@uEa-Bbb  B@jB @x@A$AjElajaj = {o="@n@ BJB  B@JB@x@AAJE'Je=:| =y-{% @(@$X "Bb)b B@aBB@x 9A$A @Eb =j=j @@,JtJ@ B@a B@x@A AREaR =RC="@@ 3ǐE}2Bbb  B@jB5n@x@A$AjEZjaj =j]=*(+c@ @ Bm\  B@JB@x@AAJEJe=F ={% (@@ 5n! @ EAbDb b B@aB@x@A$AE+рyb =jOaj @@,JJ  B@aRB@x@A ARE8Had ,aR =RO=R"@TK@ C%% BbJb  B@jB|  @x@A$AjEajoaj = K=j @@$BB J B@B@x@AAJEF,e=ˤFv =:ŀ{% @@ , vBb ! B@aB@x 9A$A @Ezbb =j;`=1@c9@,J8J@Fy B@a Bc@x@A ARE `xA =Rv=%@@ S%% BbBb@j B@jB@x@A$AjE`b j"Aj = 鰀= 7@C@~ƹ) @"AJX&B B$B @JB@x@AAJEhJ`==p{Fs =n{% @%j@ ! @c =Fbib b B@aB@x@A$A En$B =j )=^" @j'@, J&JA#F B@aRB@x@A ARE߀ F60% =R=R%@@ c%% v2zbmbA;j B@jB@x@A$AjE{+!j =j=(%-(1@\@B J B@JB@x@AAJEWJ`== % =\{% "@=X@ 8bb !  B@aB@x@A$AEB =j2=j @@,!JJ F" B@aRBc@x@A AR#EΠ AR$ =RՀ="%@ р@&~Z(o!oss($t&bxЀb ' B@jB%J@x@A$Aj(E Aj) =j= *@y@ !j @(E+BڋB,$Bc, B@JB@x@AAJ-EEJ `=.=%/ =K{% 0@ZF@ eA1b  c2 B@aB @x@A$A3Eat ^ B4 =jC=j$j"5@@,6JJ (R7 B@aRBc@x@A AR8E*@Y R AR9 =RÀ=R(o:@.@ o$F! 4t/ @;bbj< B@jB @x@A$Aj=Ewb j Aj> =j9{=j%-+c?@z@~!j @"@B@ByBA B@JB@x@AAJBE83J`=C=@D = À,9{% "E@o4@ L6 3!Fb ! 륱G B@`B@x@A$AHEW5w.WI =ja"J@E@,KJJ (RL B@aRB@x@I =ARM @`Eea F!!RN =Rm`=%pO@h@ c1 I$tFBPbLbjQ B@a$B@@x@A$AjRES!a, jAjS = $=j%pj#T@7@~UB# B$ "$`4cV B@B@x@AAJWE J`=X=[FY ={% %Z@ހ@ B! @ ;"A[bu݀b b \ B@aB W.X* @x@A$A]E`@u$^^ =j="_@p@,`JܚJ a B@aRB@x@A ARbESRARc =R[=R"d@V@ B  eebOb jf B@jB$X@x@A$AjgEnaj@Y fAjh =j=(%-(%i@P@~jB Jk B@JB@x@AAJlE J`=m=Fn =Ѐ{% "o@-̀@ B c #epbˀb q B@aBc@x@A$ArE{b Bs =j&=+ct@@,uJJ >cv B@aRBc@x@A ARwEBRARx =RI=R"y@E@ %%% ,?Dzb~Db j{ B@jB o@x@A$Aj|E2 Aj} = a ~@u@~r) @"AJB B B@B@x@AAJE J`==| ={% @J@ ! @c Ab  bic B@aB@x@A$AEtb/B =j4`=j^"@!3@,J2JA# B@aRB@x@A ARE AR = J="@@ ,% Bbb  B@B,@x@A$AjE*b Aj =j=%p(+c@@ $( @"@BBn  B@JB@x@AAJEbaJ  `=8`2uFt =h{% (@c@ m c aAbMb B@a$B,@x@A$AE8a!"B =j^ހ=j @܀@,J*JA# B@aRB|; @x@A AREEaR ##AR =R=R"@Y@ ctt  Bbŗb½ B@jBc@x@A$AjEPj$$Aj =jLT=j%p(%@S@ BB B@JB@x@AAJES aJ} %%`==ԤF =O{ @ @ ! @c 4Ab ! bc B@aB .X@x@A$AEǀc&'B =ja(f@d@,JЅJ B@aRBc@x@A ARE>a ((AR =RF=%@A@ c+c+[ݟ1a:Bb[b@$ B@jBy@x@A$AjEm@Y j))Aj =j= @N@~$( @"AJB(B$By B@JB@١B$@@x@AAJE**`==uȄFyB =@ເ{% :"@/@ c  Abb ! 륱 B@`Bc@x@A$AE{qac+,B = `1`=^"@0@,J~/J  B@B@x@A ARE F--AR =R7=R"@@ yPt1 aqbb j B@jB@x@A$AjEb j..Aj =j=(%-(+c@@~+BWB$ / B@JB@@x@AAJE_J//`==rFc =@e{% "@`@ ,  qb2b !  B@`By@x@A$AEac0,WA =jLۀ=^%@ـ@ m'%;` WJJ ( B@aRB@x@A ARE*aR F22AR =Rř=R"@&@c-됂T/bb@(# B@jBc@x@A$AjEMj33Aj =j9Q=(%-(%@P@ BOB J$  B@JB@x@AAJE7 9@T 44`==F = {% "@s @c Ab ǝ!  B@aB@x@A$AEĀ55B = `hɀ=%pj%@ǀ@,J6J F B@Bz@x@A AREE66AR =R=R"@U@c-`D~7a fBbb j B@jBF@x@A$AjE;aj2 f7 (o =j@?=%-`3A@>@~BB$  B@JB C@x@AAJER J88`=AN@ZŃB! =G{% "@@ Ab !  B@a$Bc@x` =A @Eٲbc9:A =jr`=^%@Xq@ mcJpJ@ B@a B@x@A AR E)a ;;AR =R1= @,@` @--`<K -#6 E` Akn bWb  B@jBژ@x@ =Aj @`Emj<<Aj =j=%-(+c@Y@ B瀃  B@aBc@x@AAJE==`==uF =즀{% %@*@ y$F @+c qVbb b B@aBc@x@A$AE{\a5n>?B =j`=^%@@,JJA# B@aRBzc@x@A !E @@AR =R=ۀ= !@ր@ c39aq#D"bb j# B@jBF@x@A$Aj$E b jAAg 9% j=jj%&@@~n'BOB) ( B@JB@x@AAJ)EJ aJ} JBB`=*=]F+ =P{%,@K@ c W-b2b! . B@aBL6@x@A$A/E acCDB0 =jFƀ="1@Ā@,2JJA#3 B@aRB@x@A AR4E*} aR EEAR5 =R߄= 6@:@ Ctb"TC""aD7bb½8 B@jB@x@A$Aj9E8 jFFAj: =j5<=%-%;@;@ <B:B = B@JB@,@x@AAJ>E7 G!.@=?=Fx3(  K 9@ @'{% %A@q@ W c [VABb C B@`B@x@A$AjDEb,HI FE jo`=^+cF@Mn@,GJmJ *RH B@aRB@x@A ARIE&2C  JJARJ =Rm.=R"K@)@ S4"t@TUBLb3b½M B@jB@x@A$AjNER jKBy 9O j=(%-(%P@6@ nQB䀃J$ R B@JBc@x@AAJSEٝLL`=T=ZFcU =ѣ{ V@@ ! @4 AWbub bcX B@aB@x@A$AYE`YaMNBZ =j`=^%[@@,\JbJA#] B@aRB@x@A AR^EmР@Y FOOAR_ =R؀=R%`@yӀ@ c8/"7$M`3$pS+QTBabҀbfb B@jB@x@A$AjcEb jPPAjd =jx=(%-e@Ԏ@$fB4BJg B@JB@x@AAJhEzGJQQ`=i=YFj =kM{% "k@H@ `@6 Blbb m B@aBc@x@A$AnERRBo =j= cp@@,qJqJA#(cr B@aRBy-Bx[ @x@A ARsE FSSARt =R@ƀ=R"u@@ sӔP$8 Np Q`xvbb jw B@jB* @x@A$AjxEzb2 jTTAjy = }= z@|@~r `@(D{BKB J| B@B@@x@AAJ}E5aJ JUU`=~=F*  =@;{% @6@ 0xb1b! c B@`B,@x@A$AEL6VWB =jLa^"@@ m' @> JJ  B@aRBy@x@A ARE*ha XXAR =Ro=R%@k@ {x8|}aIDfbjb½ B@jBW@x@A$AjE#aj jYYAj =j@'=j @&@ B%BJ$  B@JBc@x@AAJE7 JZZ`==Fnb = À{% @t@ L6$F @c Jb  b B@`B @x@A$AEb[\B =jZ`=%pj%@EY@ 5!j @JXJA# B@aRB*@x@A AREa ]]AR =R=!R%@@ $F @3a!&~aEb;b½ B@jB @x@A$AjER̀ j^^Aj =jЀ=j%-j6@,@~!j @"@BBπ B B@JB@x@AAJEوb@Y> J__`==ZF =Վ{% "@@\۠ @O5Abyb ^! b륱 B@aB@x@A$AE_D ac`aB =j|!`="@@,cJJJA#c B@aRBc@x@A AREm@Y bbAR =RÀ=%p@y@@-}{zy8U@ϾBb彀b  B@jB4@x@A$AjEv"b jcC 9 jtz=%pj%@y@~B0B c B@JBx@x@AAJEz2#Jdd`==F =j8{% %@3@ ) @ kLAbb  B@aB@x@A$AEefB =j&$a^(f@@,JJA#(> B@aRB@x@A AREe%a FggAR =Rl=R"@ h@ L`C"`C"HzBbvgbj B@jB@x@A$AjE &jhhAj =j%$=(%-(%@#@ nB"B J$ B@JBc@x@AAJE ii`==Fc = À {%p"@Y݀@ ! @( JAb ! b B@`B@x@A$AE'bjkB =jW(`=^%@5V@,JUJA# B@aRB@x@A ARE)a@Y llAR =RU=R"@@Ex~Z"'E"6$` $`$@Bbbb`b B@jB@x@A$AjE7ʀ jmmAj =j̀=(%-(%@"@$B̀J$ Jc B@JB@x@AAJE*nn`==?F ={% "@@  c cAbZb !  B@aB@x@A$AEDA+acopB =jv,`=^%@~,JCJA#c B@aRB@x@A ARER FqqAR =R=R"@^@ ypKˀ;S"CB(Bbʺb j B@jBx@x@A$AjEs-b jrrAj =j]w=j%-(%@v@n' @"@BBBB$B B@JB@x@AAJE_/.Jss`==F = ÀC5{"@0@ y!%N vb  b륱B  `B 9; @x@A$AEctuB =j/9 mj)K@q@,JݨJ > B@aRB@x@A AREa0a FvvAR =Ri=FR%@d@ "L" $zDf bgb½ B@jB @x@A$Aj Ez1jwwAj =j =(%-! @Z@ r$( @K BB J B@JB@x@AAJE xx`==F =ހ{% "@;ڀ@ W`@6 yAb ! 륱 B@aB; @x@A$AE2bcy:B =jT3`=j^"@S@,J~RJA# B@aRBB@x@A ARE 4a {;AR =R:=R"@@ c"pQ"0aknb b@$j B@jBy o,@x@A$Aj Eǀ2 j||Aj! =jʀ=j%p(%n"@@~#Bdɀ J$ `$ B@JB !K @x@AAJ%E5}}`=&=#F' =@{% "(@݃@ c qV)b>b * B@`B@x@A$A+E)>6a ,&, =jW=j -@@ m' @.J$JA#F/ B@aRBc@x@A AR0E77aR FAR1 =R伀=F!2@7@ @oK .S"q0aD3bbĩ4 B@jBW@x@A$Aj5Ep8aj jAj6 =j=t=j%-j%7@s@~8BrB9 B@JB@@x@AAJ:ED,9J`=;=ɤF< =@<2{% %=@-@ L6$F @ 4+A>b ! b ? B@`B@x@A$A@EBA =:a"B@^@, CJʥJ  dR-D B@aRB@x@A AREE^;a F BF =R~f=%pG@a@.L aBHbDb½I B@jBF@x@A$AjJE_<jAjK =j=j L@7@ MB B$ cN B@JB@x@AAJOEՀ> !@=P=gF@Q =ۀ{% R@׀@ y! @ LBSbրb! bcT B@aB W'Z@x@A$AjUEl=bBV =jQ>`="W@O@ '$cXJkJ Y B@aRB{Je@x@A ARZEz?a AR[ =R =R(o\@~ @.#Yeda]b b½^ B@jB} @x@A$Aj_E?ajF jAj` =ǀ=j%-(+ca@ƀ@~$( @4@BbB=B$Bc B@JB@!K@x@AAJdE@aJ J`=e=Ff =@w{"g@€@ W$F%N+c  hb#b! b륱i B@`B@x@A$AjE;AaBk =j==%p"l@@,mJ JA#cn B@aRB@x@A ARoEBaR ARp =Rʹ=R%q@(@ 3`6 x4|{zyarbb½s B@jB~@x@A$AjtEmCjAju =j*q=(%-%v@p@$wBoB J$ x B@JB@@x@AAJyE))DJ`=z=F{ =@/{% "|@d*@ y': @K hv/G?}b ! bc~ B@`Bc@x@A$AEB =jEa^%@B@ m'%cJJ  B@aRBQ@x@A ARE[Fa AR =Rlc=R"@^@ C+b u6@Db1b½ B@jB@x@A$AjEDGjAj =j=(%-(%@)@$B J$  B@JB@x@AAJE `==LFc =؀{% "@Ԁ@ * $F @+c $AbgӀb ! b B@aB@x@A$AEQHbcB =jNI`=j%pj%@L@,JPJ  B@aRB@x@A ARE_Ja AR =R ="@o@,.SӔ@dup^ abbb@$j B@jBvc@x@A$AjE jAj =jrĀ=j%- @3C@À@ ) @"@BB-B J$B B@JB@x@AAJEl|K`==F}@ =ATT{F% %@}@, hbb 륱 B@`B,@x@A$AE7LacB =j=j @v@,JJA#Fc B@aRB@x@A AREMaR FAR =R=R"@@ c% `$Y` ZaBDfbdb@$j B@jB/@x@A$AjEjNajt= =jn=j%-(+c@em@$BlB J B@JB@x@AAJE&OaJ `==8| =+{% "@J'@ c BAb W! B@aB @x@A$AEcB =jPa @@,J{J F B@aRB/@x@A AREXQa AR =RM`=R"@[@ s0D0Ayp97ôBbb@(j B@jB@x@A$AjE)Raj2 jAj =j=j @@  `@ūcBu B$ Bc B@JB@x@AAJE J`==0Fɂ =Հ{F% @Ѐ@ ! @+c WBbKb b B@aB@x@A$AE6SbB =jVKT`=j^"@I@,J%JA#F B@aRB@x@A AREDUaFAR =R =FR%@H@   a[Bbbo B@jB@x@A$AjEʽ fAj =jJ=j%-(+c@@~$( @"@BB B B@JB@y@x@AAJEQyVb J`==ҤF$X =@I{% "@z@  c EA.] ! 륱 B@`B @x@A$AE4WaB =j="@_@, JJA# B@aRB@x@A AREXaRAR =R=R"@鮀@ c\w 'U2$anbUb@$j B@jBc@x@A$AjElgYjAj =jj=(%p(%@J@ rBi J$ y B@JBc@x@AAJE"ZaJ `==t5|y =({% "@/$@, @c  b#b B@aB@x@A$AEyހcB =j[a^(f@@ '*Q,CB BlJ F#@aRB5n@x@A AREU\a AR =/]=R"@X@ @zT˵zuya@bWb@$ B@jB@x@A$AjE]ajF jAj =j=(%-( < @@~ BNB B@JB@x@AAJ È J`= =߄Fc =GxҀ{%p"@̀@ $F @ jAb0b! bZ B@aB W- B@x@A$AE^bB =jDH_`=^%@F@,JJ F B@aRBy B@x@A ARE( DE AR =R`$R"@9@ $F 3 1  S YsBbb c B@jB B@x@A$AjEjAj =j+=( @@ !j @"AJB缀BBc B@JB@x@AAJ!E6vabw `="=F# =2|{% $@sw@ ! @ A%b  b륱& B@aB@x@A$A'E1ba ZDB( =j=j$j")@C@,*JJ R+ B@aRB@x@A AR,EʨcaRRA@!sA- =@{=%.@ګ@ c""zu$t5@/bFb j0 B@B@x@A$Aj1EQddjAj2 =jg=%- <3 @(@$4Bf J) 5 B@JB@&@x@AAJ6EeaJ @=7=Y2|8 =@%{% "9@!@ B c :bt be! c; B@ B !c@x@A$Aj<E^ۀ$=B= =jfaj >@陀@ m!j @?JUJ R@ B@aRB@x@A ARAElRgaRA@ARB =@Z=R"C@tU@ y}~" DDbTbjE B@B c@x@A$AjFE hjAjG = o=%-( <H @@ ) @(@BIB.B J$BJ B@B@@x@AAJKEyɀ `=L=FwM =@qπ{% "N@ʀ@Q _AObb! 륱P B@`By@x@A$AQEibcBR =j)Ej`=%pj"S@C@ mTJBJA#U B@aRB@x@A ARVE ARW =RkaRR"X@~  (" S"/aBYb}b jZ B@jB@x@A$Aj[E, jAj\ =j$=j%- <] @@~^BB$ c_ B@JB@x@AAJ`Eslbw J`=a=F; b = y{% "c@Qt@ $F @6 Nldb ! bce B@aBc@x@A$AfE.maBg =j="h@4@,iJJA# @Rj B@aRBc@x@A ARkEnaR ARl = h= m@è@ cL"x4| Dnb/b½`L6o B@B@@x@A$AjpE6aojAjq =jd=j%-( <r @@ sBzc B$ Jt B@JB@x@AAJuEpJ`=v=>/|w =G"{F% %x@@ $X %AybXb z B@aB@x@A$A{EC؀B| =jmqaj^+c}@Ζ@,~J:JA# B@aRB@x@A AREQOra+ AR =RW=R"@aR@/}"6$~p!Lu'bQb j B@jBc@x@A$AjE saj jAj =jW="@ @ƹBBB B@JB@2a@x@AAJE^ƀ J`==ߤF =@Ǹ{% @ǀ@ c Eb  B@`B@x@A$AEtbcB =jBu`=^%@t@@@Sm!j%J?JA#Jc B@aRB@x@A AREAR = v$R%@~ vw"x|}V Bbjb½ B@Bc@x@A$AjEy, fAj =j=(*( < @a@ rBBJ ` B@JBc@x@AAJEpwbw J`==Fc = su{% "@:q@ 5n! @( VYAb bc B@aB@x@A$AE+xacB =j="@ @ JuJA# B@aRB5n@x@A AREyaR AR =RF=R"@@ 7"'#y~1'Bb bf B@jB@x@A$AjE^zajfAj = a=( @@~!j @(DBg` B B@B 5@AAJ @E{aJ `==",|/ ={% @@/ Bb=b! 륱 B@abB@x@A$AE(ՀB =jR|aj$j"@@,JJA# B@aRB@x@A ARE5L}a@Y AR =RS="@FO@; /39ِ.7hDfbNb f B@jB@@x@A$AjE~aj jAj =jD =%p < @ @ BB c B@JB@x@AAJECÀ J`==ĤF = 3ɀ{% (@~Ā@ @ GAb  bc B@aB@x@A$AE~bB =j>`=j @X=@,J=R"@=:@ sґҐ",\Bb9b½ B@B@x@A$AjE jAj =jD=(%-(%@@ rBB J$  B@JB@x@AAJEC`==K|@ = 3{% " @@ , A!b ! " B@aB@x@A$A#EiaB$ =)`=j+c%@T(@,c&J'J ' B@aRB@x@A AR(E FAR) = ="*@@ /t/Ƀ B+bGb j, B@Bc@x@A$Aj-E^b2 jAj. =j=%-(%/@I@~0B J$ 1 B@JB@x@AAJ2EWaJ J`=3=ejF4 =]{% %5@"Y@/ !A6bXb! XF7 B@aB@x@A$A8EkB9 = `= :@g@,;JJ < B@By@x@A AR=E AR> =Rր=R"?@Ҁ@ te]LB@brрb jA B@jB@x@A$AjBExb@YW j C =j=(%-(%D@a@ EBB $ F B@JB@x@AAJGEEJ`=H=w!^ `AI = )K{H =`=e "J@?G@ !%N, DxAKbFb ! bL B@aB@x@A$AMEa* AjN =j= O@@,PJJA#a!Q B@aRB@x@A ARRExaR F pARS =RN=R"T@{@  @ 9~a;UbbjV B@jB5n@x@A$AjWE4j AjX =j7=j%-`3CY@6@  `@.`@BZB^B$ B[ B@JBc@x@AAJ\E @=]=&|y* ^ ={% "_@@F `b=b ! a B@aB@x@A$AjbE(byBc =jOk`="d@i@,eJJA#f B@aRBc@x@A ARgE5"a aRh =R)=%pi@)%@ }"6a=Dfjb$bk B@jB@x@A$AjlE jajm =j<=j%-j+cn@@ oBB$ cp B@JB@x@AAJqEBer=ĤFys =7{% %t@z@ ! @ Aub ! bv B@aB W?n@x@A$AwETa bx =j`="y@<@,zJJ { B@aRBx @x@A AR|Eˀ F  aR} =RӀ=R"~@΀@%t  a BbKb jc B@jB + @'@x@A$AjE]b j  aj =j劀=(%-(%@C@$( @ūcBB$B B@A"B@x@AAJEBJ  @==eUF =H{% "@ D@ W`@+c {AbCb 륱 B@aB "@x@A$AjEkc Kc = ``a^"@@,JnJ > B@B@x@A ARExua FaR =R0}=R"@x@ Q "$h\{"$aBbwbf B@jBW@x@A$AjE0jaj =j4=(%-(%@3@ rBCB J B@JB@\$@x@AAJE e=F2(EO A =@z{% "@@ ! @?m 8Ab"b! b B@`B@x@A$AE bcb =j   @==F~, =W{% "@@ ) @#!8 AbbZ b B@aB@x@A$AjEbc!"b =je`=%@|c@,cJbJ Fc B@aRB@x@A AREa ##aR =R#="@@ $F @%)Lp+MTp 8Bb{b½`bF B@jB y@x@A$AjE׀F j$$N! =jۀ= @\ڀ@ !j @"AJBـBJ B@JB@!K@x@AAJE %%e=F@ =@{% @I@, JAb  c B@`B@x@A$AENa&'b =j`=$j"@* @,J JA# B@aRB@x@A ARE F((aR =RM̀=R%@Ȁ@0#KYM0TerBbb j B@jB; @x@A$AjE'b j))ajEj=j%p+c@ @~Bk B B@JBBD`x@9 AJ @E<J**e=/OF =@{B{% "@=@  c `AbJb B@`B@x@A$A E5+,b =jVa" @@, J#JA# B@aRB@x@A AREBoa F--aR =Rv=R"@Rr@,03PMN1~aakɂbqbĩ B@jB$X@x@A$AjE*j..aj =jY.=( @-@ rBB J B@JB@x@AAJEO //e=ѤF =D{% @@, LEb !  B@aB,@x@A$AE֡bc01b =ja`=^+c!@U`@,"J_J ># B@aRB@x@A AR$Ea 22O% =y =R%&@@ C%Ǵt"Q@ u+B'bHb@&j( B@jB@x@A$Aj)EjԠ@Y j33aj* =j׀=j +@N@ `@"AJ,Bր$ By- B@JB@x@AAJ.E44e/=rFc0 =ٕ{"1@+@nE c 2bb 3 B@aB - @x@A$A4ExKac56b5 =j `=j"6@ @,7Js J >8 B@aRB@x@A AR9E€ F77aR: =R3ʀ=FR%;@ŀ@ SӔP Qy ^Df<bb@$ = B@jBy @x@A$Aj>E ~b j88G? =j=%-1@@쀀@ rABLBkB B@JB@x@AAJCE9J99eD=LFE ={?{% "F@:@ y! @ ;AGb/b bZ,H B@aB@x@A$AIEc:;bJ =j+aj^%K@@,LJJA#FM B@aRB$X@x@A ARNE'la F<>eY=FFZ =%{% [@o@F  A\b ! 륱] B@aB@x@A$A^Eb?@b_ =j^`= `@2]@,aJ\J b B@aRB@x@A ARcEa AAaRd =Rr=R(oe@@ $F!osu$4ǏBfb1b½g B@jB@x@A$AjhEOр jBBaji =jԀ=j%p(+cj@7@~!j @ťkBӀ B$Bl B@JB@x@AAJmE֌b JCCen=[Fo =Β{% "p@@/ f-Aqbrb! 륱r B@ B,@x@A$AsE]HaDEbt =j`=$j"u@@ 2 @ @ ! !% ARvJ\J w B@aRB@x@A ARxEj FFaRy =Rǀ=!R"z@r€@ c"+YpǬDx,{bb½| B@jB@x@A$Aj}Ezb jGGaj~ =je~=j%-j%@}@~B!B$ B B@JB@x@AAJEx6aJ> JHHe=F =l<{% "@7@ $F @+c ,bb! b B@aB 4 @x@A$AEcIJT =j!a"@@,cJJ B@aRBw&BW@x@A ARE ia KKAR =Rp= @l@ Eǭ cqbkb@& B@jBQ@x@A$AjE$ajF jLLaj =j(=%-(%@{'@ B&B  B@JB; @x@AAJE JMM@==a* ={% %@T@ y! @+c qb  b B@aBB@x@A$AjEajNOb =j[`=^+c@7Z@,JYJ F B@aRB{@x@A AREa PPaR =RL=R"@@ E"\T &abb f B@jBy@x@A$AjE4Π@Y jQQaj =jр=(%-(%@@ $( @"@BB|ЀB B@JB@١B)* @x@AAJERRe=H@ m!j @JGJA# B@aRBB@x@A AREaR RTTaR =R="@@ +c 13OafDfbDb# B@jB@x@A$AjEO jUUL =jϿ=%-(%@*@<BB/l c B@JB@x@AAJEwVVe=ޤFF =}{% %@y@ y! @c Abrxb bc B@aB@x@A$AE\3acWXb =j=j%pj(f@@,JOJ  B@aRB@x@A AREjaR FYYDF =R#="@z@ ЂЃQyT1aBb欀bf B@jB@x@A$AjEejZZaj =jyi=%-%@h@$B9B J B@JB@c@x@AAJEw!J[[e=F@ =@h'{% %@"@ ) @6 Abb b B@`B,@x@A$AE܀\]b =j#a @@ m!j @cJJA#c B@aRB@x@A ARE Ta ^^aR =R[=R"@ W@ $ "<|6+mazBbxVb j B@jB@x@A$AjEaj* __aj =j=j @y@  `@(AJBB B@JBc@x@AAJE ``@==݄F* = р{% @Ẁ@ c kAb ȥ! c B@aBc@x@A$AjEb,abb =jF`=%pj"@7E@,,JDJ  B@aRB@x@A ARE ccaR =RVaRR%@@ $XmaBb!b½ B@jB} !B@x@A$AjE4 jddL =j=j @@~Bd B$  B@JB@x@AAJF  Etbw> Jeee= B@JBc@x@AAJ?Eqbw Jtte@=!FA =w{% "B@r@ ': @( WACb@,rJdJ s B@aRBnb@x@A ARtEw@Y: aRu =R=R"v@{@ cCA1P1 @wbb@%x B@jB@x@A$AjyEb jajz =j=j {@굀@~c|BJB} B@JB@x@AAJ~EnJ@== F =qt{% @o@ c Eb b ! B@aB@x@A$AjE *ayb =.=j @@,JJ Fc B@aRBc@x@A AREaRy FaR =RǨ=FR%@)@ SŰE%a&UBbb@$ B@jB@x@A$AjE\aj jaj =j/`=j @_@~B^B$ y B@JB@x@AAJE&Je=F$X ={% @a@ ': @+c Bb ! by B@aB@x@A$AEӀcNW =ja"@@@#P2$yJJ IF B@aRB@x@A AREJa F4y2h =@iR=!@M@ c"Q p KJ}Bb6b@) B@Bb@x@A$AjEAj$!j =j =j%-j1@&@ $( @.`@BB B$B B@JB ,@x@AAJE e=IԄF =@ǀ{% (@À@ $F @6 (Abd€b b륱 B@`B@x@A$AEN}bcb =jz=`="@;@,cJEJA#F B@aRB@x@A ARE\@Y aR =R=R"@d@ , s@%K`R11P@Bbbo B@jBc@x@A$AjEb jaj =js=(%-(%@в@~B/B J B@JB@x@AAJEik:@T Je=Fc =Vq{% "@l@ , |Abb! B@aBc@x@A$AE&aH =j=j(f@w@,JJ (R B@aRBx@x@A AREaR aR =R=R"@@ o$F!o.! $1uP aSBbnb  B@jB@x@A$AjEYjaj =j ]="@j\@ !j%"AJB[B $Bc B@JB@x@AAJE Je='| ={% @G@$X Ab ^! c B@aBx@x@A$AEЀb =jaj$j"@ @,JJA# B@aRB@x@A AREGa aR = [O="@J@ puT}uU} tT/,5Bb'b j B@B@x@A$AjE&jaj =j=%-+c@@$Bj J$  B@JB@@x@AAJE e=.фFu$X =@Ā{% (@鿀@ y': @ AbIb B@`Bc@x@A$AE3zbcb =jS: `=j @8@,J"JA#c B@aRB@x@A AREA(aR =R="@U@ tU%C$)YBbb j B@jB*@x@A$AjEǬ b fN! =jT=j%-(!@@ $( @n(@BBB B@JB@@x@AAJENh Je=ϤFz =@Bn{F%p%@i@  vB bZB@x@A$AE# acb ==j @`@,JJ  B@9B@x@A ARE aR FaR = =R" @杀@ !$F!o1AӔP&FLDf bRb½ B@B/@x@A$Aj EiVjaj =jY=j%p(%@?@ !j @ BX B B@JB@x@AAJEJe=q$| ={ @)@Q Abb  B@aB@x@A$AEẁH =ja %d(f +@@,JqJ  B@aRB@x@A AREDa aR =8L=%pR%@G@ $@6|{a-BbFb B@jB o/@x@A$Aj!E jaj" =j=j%pj%#@@ $BWB% B@JB@x@AAJ&E e'=΄F( ={ $F% ") +@μ@Q Z*b-b ! + B@aB "B@x@A$A,Ewbb- =)97`=  j^%. +@5@,/JJ 0 B@aRB@x@A AR1E&+ aR2 == "R"3 +@6@  P $Bs%u,]D4bb jc5 B@jB@x@A$Aj6Eb jaj7 =),="8@@~ƹ9BB$&50: B@JB@\$* @x@AAJ;E3eJe<=F@= =@#k{% >@pf@ , ZB?b !  @ B@`B$X@x@A$AAE acbB =j= , ^%C +@A߀@ m2$ DJހJ(RE B@aRB@x@A ARFEǗaR F!G =x=  R%H +@Ӛ@ CudǴt"* Ib?bjJ B@jB/@x@A$AjKENSjajL =)V=(%-(+cM@1@ rNBU J$ O B@JBc@x@AAJPEJeQ=V!|cR ={% "S@@ $F @ +c * Tbqb bU B@aB@x@A$AVE[ʀcbW =ja ^%X +@@,YJ^J Z B@aRBz@x@A AR[EiAa aR\ =I= ] +@yD@ c6%At"Q@$`#C^bCb_ B@jB@x@A$Aj`E2 jaja =)xa(%-(%b@ ~cB4B Jd B@JB@x@AAJeEv  Jef=F{yg =k{ %"% %h +@@ y! @6 Ixibb! b j B@aB@x@A$AkEsbbl =)*4`=+"j%m@2@,nJ1J o B@aRB@x@A ARpE aRq =R= "R"r +@@2]F  Qy` yRDsbb(½ct B@jBx @x@A$AjuE b jajv =)= w@q@ $( @"AJxBѨBJ$Bcy B@JB@x@AAJzEb!aJe{=tF| =h{% F"}@Vc@  c A~b c B@aB@x@A$AE"ab =j݀=j^"@"܀@![ !j @ nbJۀJ R B@aRB@x@A ARE#aR FaR =@N= a +@@2p`yu yBbbf B@B; @x@A$AjE3P$jaj =)S=%-j+c@@$BR J B@JB@x@AAJE %aJ e=o|Gc ={% %@ @  @c 'AbVb! b B@aB@x@A$AE@ǀcb =jj&aj%pj%@˅@,cJ7JA# B@aRB@x@A AREN>'a aR =RE=R"@ZA@2#$"TpGӔyQqb@b½ B@jB@x@A$AjE jaj =je= 1' +@@ cB!B  B@JB ,y@x@AAJE[(e=ܤF$X =@K{% F"@@ ) @+c qb  `b B@`B@x@A$AEp)ac[& =j1*`= ," +@i/@,J.J  B@aRB@x@A ARE FaR ==R"@@ c 3 Q %ctHobcb j B@jB; @x@A$AjEv+b jaj =j=j%-(+c@V@~B B$  B@JBc@x@AAJE^,Je=~qF =d{%p"@7`@ y! @( [Ab_b b B@aB@x@A$AE-ab =jڀ= % +@ـ@,J~ؠJ B@aRBc@x@A ARE.aR FaR =7=  R" +@@ C$yD@ yS$p_KdEJpWI@K[Bb=b@& B@B@x@A$AjE jaj =@݀A="@@ƹBB J$c B@B@x@AAJE@3b Je=Fc =0{% @{@ ! @c 1Bb ! b B@aBc@x@A$AEm4acQ =j-5`=  ^% +@R,@,J+J > B@aRB@x@A ARE䀈aR == R% +@@ ccSXKCXFLpD y*bXb½ B@jB@x@A$AjE[6b faj =)㣀=(%-(+c@@@ r$( @"@BBJ$B B@JB ?@x@AAJE[7Je=cnFx3ɂ =@a{% "@]@ y c 9b~\b c B@`B@x@A$AEh8a Z! 8b =j׀=jtB +@Հ@ m!j @,J_J  B@aRB; @x@A AREv9aRR@aR =@1=!g X"@@ sK+\>Idpq yhobbj B@B@x@A$AjEI:aj2 jaj =jyM=  j%-(% +@L@~ B9B J B@JB@1@x@AAJ E;aJ J@==} =@t {% "@@, nWbb!  B@`B@x@A$AjE b =j=b J@=#=F$ =G{ !% % +@]@ $F @ B&b  by' B@aB@x@A$Aj(Ej?aQ) =)*@`=  j^+c* +@?)@, +J(JrR, B@aRB}LW% @x@A AR-E aR. =d=  R(o/ +@@ PMEyB0b1b '1 B@jB; @x@A$Aj2E@Ab jaj3 =)Ƞ= !j%-(+c4 +@%@~r5B B6 B@JB@x@AAJ7EXBaJ} Je8=Hk?`=9 =G^{  % ": +@Z@ B! @K A;bcYb! b < B@aB@x@A$A=EMCajcb> =)iԀ=%?@Ҁ@,c@J8J nA B@aRBF@ 5@A ARB @`E[DaR GC =R= $F"D +@S@ c+ZQ@ %MQ ycKBEbb½F B@a$Bc@x@A$AjGEFEjajH =)bJ= I@I@ $( @"AJJB&BBcK B@JB@@x@AAJLEhFJeM=F* N =@X{ "% O +@@ y c ǹAPbb Q B@`B@x@A$ARE`bS =)~Ga^"T@v|@,UJ{JA# `>-_V B@aRB@x@A ARWE4Ha aRX =R<=R%Y@7@ B&!%|T5nTZbhb[ B@jB/@x@A$Aj\E jaj] =j= 1$(%-(+c^ +@a@~ `@r"@B_BB B` B@JB@@x@AAJaE Ieb=Fc =@{  d +@F@x Zeb !&Uf B@`B !5n@x@A$AgEgJayJh =)'K`=^"i@#&@ mefyjJ%J k B@aRB/@x@A ARlEހ!ARm =R>=R%n@@  "ML aBDfobb@$#a p B@jBw $X@x@A$AjqE%Lbjajr =j= $( s +@ @ tBm J$ Ju B@JB@x@AAJvEUMJ@=w=-hFx =G[{  % :"y + -V@ c BzbKb { B@`B "@x@A$Aj|E2Na,b} =)[р=^%~@π@,J)J F B@aRB@x@A ARE@OaR@Y aR =R쏀=R"@L@ dpKEJpQPHhBbbo B@jB@x@A$AjECPaj@A =jNG=  +@F@$B B J B@JB@x@AAJEM !!@==ҤF5n =G]Qa  %  +@@ Bbb,! B@aB@x@A$AjEԺyB =)zRj @Oy@,JxJ > B@aRB@x@A ARE1Sa aR =R9="@4@ 7)! 0 Op M3!aBb]b½E c B@jB oc@x@A$AjEh jaj =j=  (*(1 +@O@ r!j @(@BBJ$Bc B@JB@x@AAJETe=pF =G㮀{  :% ( +@,@ , ]gAbb c B@aB@x@A$AEudUab =)$V`=  j#j" +@"@,JXJ  B@aRBz,@x@A ARE۠@YcaR =$="@{ހ@ 6+\ P0M@8Bb݀bf B@jB@x@A$AjE Wb2   aj =j= c +@홀@~@BRB J B@JB@o&* @x@AAJERXaJ J  e=eF =@X{% F%@S@ ! @ Bb,be! b B@`B@x@A$AEYa  b =j0΀=j @̀@ mJˠJ  B@aRBc@x@A ARE%ZaR   aR =Rٌ= $FR" +@5@,3pJTJU`\}Bbb½ B@jB@@x@A$AjE@[aj jaj =)7D= 1!j*(+c +@C@~$( @(@BBBB$Bc B@JBc@x@AAJE2 Je=F{3"<| =G"\a =`G!% " +@p W$F @ Ab ! bc B@aB,@x@A$AE,b =)w]aj"@Dv@, JuJ > B@aRBc@x@A ARE.^a aR =Rk6=  +@1@ %efN`NuOnbhb6b B@jB}c@x@A$AjEM jaj =)=  j%-(% +@4@~B쀃 B$  B@JB@x@AAJEԥ_b Je=UF =G{  % % +@ @ y! @( `hbpb! by B@aB - y@x@A$AEZa`acb =)!a`="@@,cJQJ  B@aRB@x@A AREhؠ@Yh' aR =R =R"@pۀ@y3#PE@ud yDbڠb@&½ B@jBF@x@A$AjEbb faj =j{=j%-(%@֖@~$( @ūcB7BB B@JB@$@x@AAJEuOcJe=F =@]U{ c +@P@c aAbb  B@`B@x@A$AE dab =)*ˀ="@ɀ@@Sm, @JȠJA#J B@aRB@x@A ARE eaR FaRIR=R%@@ c3udǵfef VBb}bĩ B@jB oy`x@9 Aj @`E=fjaj =Bi`=A=a1$(%-@t@@ nB?B J ` B@B@!K@x@AAJ E e = | =@{*c @Q@ c $B b ! c B@`B@x@A$AEgb b =jth`=^%@,s@ mJrJA# B@aRB/@x@A ARE+ia !!aR =Ra3=R"@.@ C"F =@{% !@@ $F @6 B"bTb b# B@`TB !@x@A$A$E?^kac$%b% =jel`=j+"j%&@@,'J2J ( B@aRB@x@A AR)EMՀ F&&aR* =R܀=G@)R%+@U؀@,3ST~%HT@zl@,I׀b j- B@jB,IA$Aj. @`EӐmb j''aj/ =jT=j%-C0@@nc1BBB2 B@aB@x@AAJ3EZLnJ((e4=ۤF; 5 =JR{H@ % "6@M@, @7b  8 B@aBc@x@A$A9Eoac)*b: =jȀ=j^K;@pƀ@,<JŀJ >= B@aRB@x@A AR>E~paR F++aR? =R=R"@@@ $F!o"c+gKg cDAbZb½B B@jB@x@A$AjCEu:qj,,ajD =j>=j E@^=@ r!j @"AJFBb =jn~`=^+c@l@,cJZJA#F B@aRBF@x@A AREu%a(??aR =R0-=R"@(@ , %6Kԣab'b j B@jB~ @x@A$AjEF f@@aj =j=j%-(%@@~B@B B@JB J!K@x@AAJEb JAAe=Fc =@n{"@@ c  bb!&Z, B@`B@@x@A$AE XaBCb =j-`=+"%@@,JJA# B@aRBx B\ @x@A ARE DDaR =Rր=R%@'Ҁ@ Jd`=Mp? ]WDbрb j B@jB@@x@A$AjEb jEEaj =j=( @w@$BՌB c B@JBc@x@AAJE$FJFFe=F =L{F"@`G@ b   B@aB 'Z@x@A$AEaGHb = ``=j @=@,JJ n B@B$X@x@A ARExaR FIIaR =Rh="@{@  (MdpR%@Eb4bA; B@jB5n@x@A$AjE?4jJJaj =j7=%-o+c@@$Bw6 J)  B@JB@$c@x@AAJE KKe=G|V =@{% %@@ 'Abbb !  B@`Bc@x@A$AELbcLMb =jpk`=j @i@,J?JA# B@aRB@x@A AREZ"a NNaR =R*=R"@f%@ KMa9Bb$b@$j B@jB@x@A$AjE jOOaj =ja=%-(%@@ B B J$  B@JB @x@AAJEgPPe=F5n =@S{% "@@ ! @ :Abb b B@`B@x@A$AETacQRb =j`= @}@ myJJA# B@aRB@x@ =AR @`Eˀ FSSaR =RӀ=R"@΀@ K R`~[M`̠ BbWb! B@a$B5n@x@A$AjEb jTTaj =j =j%-(%@d@@r$( @4@BBƉB B@JB@x 9AAJ @E CJUUe=U`= =H{% "@BD@ $F @ EAb ! b륱 B@abB@x@A$AEcVWb =ja"@@,JJA# B@aRBc@x@A AREua FXXaR =R=}= @x@ KWK~a' Bbb½ B@jB@x@A$AjJ  `E$1aj@ jYYaj =j4=j%p(%@ @ Bl3 B$ c B@aB@x` =AJ @E JZZe=+F*  ={F% %@@Q A bFb c B@abB@x@A$A E1b[\b =jVh`=j^(f @f@,J$J  B@aRB@x@A ARE?aF]]aR =R&=R"@O"@y4G Kea/Bb!bj B@jB ,@x@A$AjEڀ f^^aj =jAހ="@݀@ BBJ B@JB@!K@x@AAJELb 4E J__e=ͤF]L =@<{% @@  c xBb  B@`Bc@x@A$A EQa`ab! =j`=^%"@b@, #JJ $ B@aRB@x@A AR%EȀ bbaR& =!JЀ=R%'@ˀ@ $F'E!%KW3<}p/S`+ B(bDbf) B@jB@x@A$Aj*Egb jccaj+ =j燀=(*(+c,@B@ r!j @"@B-BJ. B@JBc@x@AAJ/E?aJ Jdde0=RFV@1 =E{% "2@+A@c ,MA3b@b c4 B@aBc@x@A$A5Etcefb6 =jaj$j"7@@ ' @ ! !% AR8JoJ 9 B@aRB5n@x@A AR:Era ggaR; =R3z="<@u@4#+\%Kdpa=btb½> B@jBF@x@A$Aj?E .ajF jhhaj@ =j1=j%p%A@0@~BBQBC B@JB @x@AAJDE JiieE=FF ={F% %G@@ Hb+b! I B@aB@x@A$AJEbjkbK =jCe`=j%pj%L@c@,MJJ N B@aRB@x@A AROE#a llaRP =R#=FR"Q@@ 3|}J ,IDRbb S B@jB@x@A$AjTE׀jmmajU =j:ۀ= V@ڀ@ WBـB X B@JBc@x@AAJYE1nneZ=F[ =%{% F"\@k@ 7B]b  ^ B@aB@x@A$A_ENaopb` =j`=j^%a@. @,bJ J c B@aRB@x@A ARdE qqaRe =Rr̀=R"f@Ȁ@ 4CMp!BgbAb@(ja]Lh B@jBy@x@A$AjiELb jrrajj =j܄=j%-(+ck@8@$lB Jm B@JB@@x@AAJnE<Jsseo=TOFp =@B{% "q@ >@ / @ +c OArbo=b cs B@`B@x@A$AtEYctubu =jwa%v@ض@,wJDJA#x B@aRBc@x@A ARyEgoa FvvaRz =R w="{@{r@ cS~"efa9|bqbf} B@jB; @x@A$Aj~E*jwwaj = z.=%-(%@-@ cB9B$ B@B@@x@AAJEt xxe=F$X =@d{%p"@@ WAbb ! XF , B@`B* @x@A$AEbcy$A =jb`=^K@v`@ m' @J_JA# B@aRB B@x@A AREa {+!R =R =!@@ ccabQ HBb|b@&j B@jB* @x@A$AjE j||aj =j؀=j%-j%@{׀@ BB$  B@JBc@x@AAJE}}e=F ={% %@P@ m$F @c iAb ȥ! b B@aBc@x@A$AEKa Z|D~b =j `="@3 @,J J R B@aRB%IB@x@A ARE RA@aR =@Pʀ=R"@ŀ@ sdud:ȀǴ pBbb@&j B@B@x@A$AjE1~b jaj =j=(%-(%@@~rBq J$  B@JB@x@AAJE9aJ Je=b =jyaj+c@ٳ@,JEJA#F B@aRB@x@A ARELla aR =Rs=R"@Po@ 2 "fe"NaBbnb½ B@jB@x@A$AjE'aj jaj =jb+="@*@ $(n"AJBBJ$B B@JB@x@AAJEY Je=ڤFc =E{% @@ c EAb  B@aB @x@A$AEbb =j_`=j @o]@, J\JA# B@aRB@x@A AREaaR =R="@@ $F!o|%O>faBbqb½ B@jBy@x@A$AjEt faj =jԀ=%-(+c@Y@ r!j @ťBӀ J B@JB@@x@AAJEb Je=|Fc =@{% (@6@ @*!bb! bc B@`B@x@ =A @EHacb =j`=j$j"@@ m' @JdJ c B@a B4B@x@A ARE D aR =RFǀ="@€@ !Re"{zǑ +Bb b½ B@jBL6@x@A$AjE{bF jaj =j~=j%p#@}@~BZB$  B@JB@@x@AAJE6aJ Je=IF5n =@<{F% %@7@ TAb8b! X\y B@`B ! O*@x@A$AE#b =jBaj$j%@@,JJ  B@aRB@x@A ARE0ia aR =Rp=R"@5l@ c A@ a.Bbkb  B@jBc@x@A$AjE$jaj =j?(=j%-%@'@B&B$ CG@JB@x@AAJ#@E> e=F =2{% "@v@ c 6Ab !  B@abB@x@A$AEśbb =j[`= @[Z@, JYJ j B@aRB@x@A AR Ea aR =Ry=R"@@ $X A D7a*BbBb½ B@jB $X@x@A$AjEY΀ jaj =jр=j%-(%@F@~BЀ B$  B@JB@x@AAJEb Je=aF = ÀЏ{% "@@ / @W Ab|b! b B@`B@x@A$AEfEacb =j`="@@,JUJA# B@aRBx@x@ =AR! @`Et@ aR" =RĀ= #@l@ +c,o%Cx@ B$bؾb½% B@a$B@x@A$Aj&Ewb@Yjaj' =j{=%-(%(@z@ )BFB$ * B@JB@x@AAJ+E3aJ e,=FF$X- =u9{% %.@4@ A/bb! 0 B@aBc@x. 9A$A1 @Ecb2 =j(a^ <3 @@,4JJ@ݣRc5 B@a Bx@x@A AR6Efa aR7 =m=R"8@i@ ΢QagB9bhbj: B@jB@x@A$Aj;E!jaj< =j$%=(%-(%=@$@ w>B#B J$ ? B@JB@,/@x@AAJ@E# eA=FVB =@{% "C@_ހ@ c nADb ! E B@`B@x@A$AFEbcAG =jX`=^ <H @4W@ m!j% * IJVJ@0J B@aRBy@x@A ARKEa aRL = o=R"M@@ $F!R!K 4"S!t$XqBNb/b½O B@B@x@A$AjPE>ˀ i AXajQ =@΀=(%-(%R@ @$SB̀J$ T B@Bc@x@AAJUEĆeV=FFW = À{% "X@@ m @AYb`b bZ B@`Bh@x@A$A[EKBab\ = `w`=$j <] @@,^JFJ _ B@B@x@A AR`EY FaRa = =R"b@i@c5"hBcbջb jd B@jBc@x@A$AjeEtb jajf =jgx=%-%g@w@n' @ :@Bh #BBi B@JB@x@AAJjEf0Jek=Fz3(.@l =R6{% "m@1@ ) @ Anbb b륱o B@aB @x@A$ApEbq =jaj^ <r @x@ !j @csJ䩠JA#4` ct B@aRB@x@A ARuEba FaRv =j="w@e@ $F!R% ͣalBxbbbjy B@jBc@x@A$AjzEjaj{ =j"=%p(%|@\!@ r!j @ :@B}  B J~ B@JB@x@AAJEڀ e=F ={% %@Eۀ@ ! @'Ab b륱 B@aB @x@A$AEbcb =jU`=j$j < @T@,cJSJA#eF B@aRBz((Bb@x@A ARE a aR =P=R"@@ c#\s,-/7 Bbb B@jB5n@x@A$AjE#Ȁfaj =jˀ=j%p%@@ $( @ :@B gʀJ B@JB C@x@AAJEe=*F ={% "@焀@ ) @+c 5@AbEb ! b륱 B@aB,@x@A$AE0?ab =ja= < @@ m!j%cJ/JA# B@aRBc@x@A ARE=aR aR ==!@J@ 3%-.9RBbb fc B@jB@x@A$AjEqjaj =jPu=j%pj%@t@nBB$  B@JB@@x@AAJEK-Je=̤F{ =@;3{% %@.@W @.W Ab ! b B@`B@x@A$AEb =ja"@X@ mJĦJA#c B@aRBc@x@A ARE_a aR =Rg=H@@b@ o$F%Cx-@zhobKb½ B@jB5n@x@A$AjEfjaj =j=j%-(%@A@ !j @ :@B  B B B@JB@x@AAJEր e=rFQ =܀{% %@"؀@, nWb׀b! 륱 B@aB@x@A$AEsbcb =jR`="@ Q@,JvPJ  B@aRB@x@A ARE a aR =R:=R"@ @ S$:$ ad Dfbb½ B@jBc@x@A$AjEŀ ,aj =jȀ=j%p(%@ǀ@ BLB$ c B@JBL6@x@AAJEe=Fc =~{"@ȁ@ !%N Ab*bj! bc B@aBc@x@A$AE e(=9|) =-{% "*@P(@ ! @A+b ! b륱, B@aBc@x@A$A-Ecb. =ja"/@@,c0JJA#1 B@aRB@x@A AR2EYa(aR3 =Rca=%p4@\@ y4h15b)b j6 B@jB /@x@A$Aj7E0ajfaj8 =j=j%-j%9@ @ $( @"@B:Bl$B; B@JB@x@AAJ<EЀ e==7F}> =ր{% %?@р@ y) @+c @bRb b륱A B@aB "@x@A$ABE=b bC =jߐ="D@A@,EJJ F B@aRB @x@A ARGEGRaRH =R\O=R"I@J@L65"AaEDfJb(b@$K B@jB{ @x@A$AjLEJjajM =j=j%-(%N@5@ nOB BP B@JB,@x@AAJQEѾ eR=٤Fw!^P/ AS = )Ā{"T@@ W%N AAUbmbV B@aB@x@A$AWEXzbbX =j:`=(fY@8@,ZJSJA#4[ B@aRB@x@A AR\Ee@Y aR] =R=R%^@e@ % BV @@0B_bb@%jaoy` B@jB|@x@A$AjaEb jajb =jx=( c@ӯ@ ndB4BJe B@JBc@x@AAJfEshJeg=Fzch =_n{%p:"i@i@ c +Bjbb ck B@aB@x@A$AlE#abm =j=j n@x@,oJJA#Fp B@aRB@x@A ARqEaR FaRr =R="s@@ 5uŃ%Ńs5ahBtb{bfu B@jB @x@A$AjvEVjajw =jZ= x@qY@$yBXB Jz B@JB@x@AAJ{E;@T e|=$|} ={% ~@R@ ': @c qVb ! b B@aB@x@A$AÈb =j̍a @.@,JJA# B@aRB@x@A AREDa aR =R]L=R(o@G@ 666L6a6naLEb%b j B@jB@x@A$AjE/aj jaj =j=j @@$( @.`AJBsB$B B@JB@x@AAJE Je=7΄F{c ={% @@ c |AbRb c B@aB@x@A$AE=wbcb =jj7`= @5@,,J8JA# B@aRB@x@A AREJ aR =R=R%@N@ e6ѻ6L6ͥBbbA;j B@jBy@x@A$AjEѩb jaj =j]=j%-(6@@~nBB B@JB@x@AAJEXeJe=٤F =a aR =F=FR"@vA@ c Cswoѻkq^Bb@bĩ B@a$B~ B@x@A$Aj E jaj =jy= @@ n$( @"AJ B5B J$B B@JB@x@AAJEe=ȄF = Àp{% F"@@ ) @+c fAbb b B@`B "y@x@A$AEqab =j-1`=j^"@/@,J.J  B@aRB|/@x@A ARE FaR =R=R"@ @ Scq vuVA|abb j B@jB@x@A$AjEb jaj = '=j%-(+c @@~ƹ!BB" B@B@x@AAJ#E!_Je$=Fx% =e{% "&@]`@ ! @+c Q 'b ! b ( B@aB@x@A$A)Eab* =jڀ=j +@/ـ@,,JؠJA#c- B@aRBc@x@A AR.EaRy FaR/ =RX=FR"0@@$X6c}4%aD1bb j2 B@jB oc@x@A$Aj3EEJĀcb? =jy"a"@@ق@,AJEJ(>B B@aRB@x@A ARCEW;#a(aRD =R C= E@k>@ s+]\t@8BFb=b@(G B@jB{ @x@A$AjHE   ajI =jn=j%-(+cJ@@ KB*BL B@JB@x@AAJMEe$b J!!eN=ĄFO =M{% (P@@ @c eAQbb^! b R B@aB "@x@A$ASEm%ac"#bT = `` .&`="U@j,@,VJ+J FW B@B@x@A ARXE $$aRY =R=R"Z@@ \r2\r-wA|[bb j\ B@jB~ @x@A$Aj]E'b j%%aj^ =j=( _@a@~`BB J$ Fa B@JB@x@AAJbE\(aJ J&&ec=nFd = Àa{% e@>]@ , lfb ! g B@`B "@x@A$AhE)a'(bi =j׀=j.j@ր@,kJՠJ l B@aRBy@x@A ARmE*aR ))aRn =RN=R%o@@ d p |} _Hopbb q B@jB@x@A$AjrE!J+j**ajs =jM=H@*e-(+ct@@ uBeL, v B@JB@x@AAJwE,J++ex=)|y = {% "z@@ ! @c A{bDb b | B@aB@x@A$A}E/,-b~ =jV-aj @@,J%J Rc B@aRByy@x@A ARE<8.a ..aR =R?="@D;@!!~%Q 1raBb:bf B@jB oc@x@A$AjE j//aj =j;=%-(%@@$BB J) c B@JB@x@AAJEJ/b J00e=ˤF =B{% %@@, wAb ! c B@aB "c@x@A$AEj0ac12b =j+1`=j$j+c@c)@,cJ(J 1 B@aRB@x@A ARE 33aR =!J="@@ OV"udǴt MBbRb@& B@jB@x@A$AjEd2b j44aj =j=j%-%@K@) @"@BB B$B B@JB@$@x@AAJEX3J55e=lkF/ =@^{F% %@(Z@ ! @ AbYbj! b륱 B@`B,@x@A$AEr4a67b =jԀ=j @ Ӏ@,JuҠJA# B@aRB@x@A ARE5aR F88aR =R+=R"@@ c"Ô"pWUBbb½ B@jB oc@x@A$AjEG6j99aj =jJ=j%-(%@I@ $( @ťBJB$B B@JB@x@AAJE7J::e=| ={% "@@ L6 {Ab)b ! 륱 B@a5 "c@x@A$AEE;>aj =j8=j%-(%@@ BB$  B@JB@x@AAJE.:b??e=F ='{% "@o@ ! @c b AbΡ ! b B@aB@x@A$AEg;ac@Ab =j'<`="@@&@,J%JA#c B@aRB@x@A AREހ FBBaR = o= @@ " Bb3b j`F B@B@x@A$AjEI=b jCCaj =jɝ=H@1y@$@~ƹ$(ncB B$B B@JB@)@x@AAJEU>JDDe=QhFh =@[{% @ W@ c =`BblVb `Bc@x@A$A @EW?acEFb =jр="@π@ m!j$$XJRJ(> `JR- B@a B@x@A AREd@aR FGGaR =R=R(o@p@  kkaaBb܊bj B@jB o@x@A$AjECAjHHaj =jsG=(%-(+c@F@ rB/B J B@JBc@x@AAJEr IIe=Fz/ =nBa% "@@ Abb! ^ B@aB "@x@A$AEBa,cJKb =j&{C`=^(f@y@,JxJ j B@aRBz,@x@A AR`E2Da LLaR =R9=R"@5@ '1 @# K 7 "֨ &VBbr4b½ B@a$B @x` =Aj @`E퀈2 jMMaj =j=( @x@~BB J$ g B@aB@x@AAJ EENNe =F/ ={%  @U@ ) @ Bb ! b B@aB,@x@A$AEdFaOPb =j$G`=$j%@-#@,J"JA# B@aRB@x@A AREۀ FQQaR =R]=R%@ހ@ $F @%L"d"b$b½ B@jB@x@A$AjE.Hb jRRaj =j=%-+c@@ !j @"@BBnJ$B B@JB@x@AAJERIJSSe =6eF! =X{% ""@S@ @ #bQb b륱$ B@aB (c@x@A$A%E<JaTUb& =jm΀=j$j"'@̀@@Sf' @(J;J J) B@aRB@x@A AR*EIKaR FVVaR+ =@=",@e@ !R#dtTM,` -bчbf. B@B c@x@A$Aj/E@LjWWaj0 =j`D=%-C1@C@ r2BB J3 B@JB@x@AAJ4EW XXe5=ؤF6 = ÀSMa% %7@ $F @+c ee8b ! b 9 B@`B@x@A$A:Eݷ,cYZb; =jwNj$j%<@Xv@,c=JuJA#> B@aRB$X@x@A AR?E.Oa [[aR@ =R6=R"A@1@ $F @+c3$"% Th:FDBb_b½C B@jB@x@A$AjDEq j\\ajE =j= F@Q@~!j @"AJGB쀃 $BcH B@JB@&y@x@AAJIEP]]eJ=yF/K =@諀{% F"L@3@ @AMbb ! bcN B@`B !c@x@A$AOEaQaJ^_bP =j!R`=$j"Q@ @,RJJ S B@aRBc@x@A ARTE F``aRU =R2=R"V@ۀ@ C"VÔQx"aeWbڀb jX B@jB/@x@A$AjYESb jaaajZ =j=j%-+c[@@~\B_B$ ] B@JBc@x@AAJ^EOTJbbe_=bQ`=|y` =U{% "a@P@ c L0Abb6b ! c B@aB@x@A$AdE! UajcdAe =jKˀ="f@ɀ@,gJJ jh B@aRBc@x@A ARiE.VaR FeeaRj =RЉ=R"k@*@ )%oSt&t6C!~!dlbbĩm B@jBc@x@A$AjnE=Wjffajo =jAA=(%-(%p@@@ !j @(@BqB?B$Br B@JB@x@AAJsE; gget=Fu =0{% "v@r@ y! @2_Awb ! b륱x B@aBc@x@A$AyE´Xbhibz =jtY`=^"{@Ms@,|JrJA#} B@aRB@x@A AR~E+Za jjaR =Rr3=R"@.@ yctEBb8b j B@jB@x@A$AjEV jkkaj =j=(%-(%@7@$( @"@BB逃B$B B@JB@x@AAJEݢ[b Jlle=^Fc =Ѩ{% "@@ c Abyb 륱 B@aB@x@A$AEd^\acmnb =j]`=^"@@,J_J >c B@aRB@x@A AREqՠ@Y: ooaR =R,݀=R"@؀@ cs"$Yp9y@Bb׀b½ B@jB y@x@A$AjE^b jppaj =jt=(%-(%@ѓ@ rB4BJ B@JB@٢oB!K @'@x@AAJEL_Jqqe=_F = 5sR{% "@M@ , ^Abb B@AB@x@A$AE`acrsb =j8Ȁ=j/j%@ƀ@ m`@,JJ  B@aRB; @x@A AREaaR FttaR =Rʆ="@#@ cp5ô. aBbb# B@jB@x@A$AjE:baj2 juuaj =j*>= @=@~B e=F, =PO{% +c@J@ $F @+c NAbb! b B@aBB@x@A$AEkacb =  ŀ="@mÀ@,cJ J F B@aRBF@x@A ARE{laR aR =R=%p@ @ c˵zuy"4,IBbx~b½ B@jBc@x@A$AjE~7mjaj =j ;= @h:@ B9B c B@JBh@x@AAJE e=|~ ={ @A@ ! @+c Bb  B@aBc@x@A$AEnbb =jno`=^+c@m@@S' @JlJA#J B@aRB@x@A >E%pa aR =@I-=R(o@(@ o"!RS"",aBb b½O  B@x@A$AjE  jaj =j=(%-+c@@ n!j @n"@BBd〃J$B B@JB@y@x@AAJEqe=(Fy =@{%p" @㝀@ $F @+c q5A bCb b B@`B$X@x@A$A E.Xrab =jVs`=^"@@ m!j$^J$JA# B@aRB/@x@A ARE;Ϡ@Y FaR =Rր=R"@3Ҁ@ $F!R(o""Hh*Bbрbf B@jB@x@A$AjEŠtb jaj =j>=(%-(%@@$BBJ B@JB@x@AAJEHFuJe=ʤF =IL{% "@G@ @Ab ! b B@aB 'Z@x@A$A!Evacb" =j€=^%#@b@,$JοJ (% B@aRB@x@A AR&ExwaR FaR' =!J=R"(@{@ pKJP P  =B)bYb@&j* B@jB@x@A$Aj+Ec4xaj jaj, =j7=j(%-@F@' @"@B.B6B$Bc/ B@JB@x@AAJ0E Je1=k|5n2 ={"3@#@ A4bb 5 B@aBy@x@A$A6Eqybcb7 =jkz`=j)"8@j@,9JpiJ >: B@aRB@x@A AR;E~"{a aR< =R*=FR%=@r%@ 0uP "uPKa>b$bA;? B@jBV@x@A$Aj@E jajA =j=%-%B@@ rCBIB JD B@JB@x@AAJEE|eF= FG =|{% "H@ɚ@c ?dAIb(b J B@aB@x@A$AKEU}acbL =j?~`=j^%M@@,NJ JA#FO B@aRB$X@x@A ARPE ̠@Y FaRQ =RӀ=R"R@0π@y8MJ$``$`@BSb΀baFT B@jB5n@x@A$AjUEb2 jajV =j=j W@z@~XB߉B JY B@JB@٢o/lB@x@AAJZE-CaJ Je[=Fw\ =@I{% ]@kD@ `@ kB^b e! _ B@`B@x@A$A`Eba =jݾaj b@?@ m!j @cJJ d B@aRBc@x@A AReEua aRf =Rv}=F!g@x@ |}N$ `TT'W%qhb>b½i B@jB@x@A$AjjEH1aj jajk =j4=j l@/@~mB3 B$ ,n B@JB@ `@x@AAJoE Jep=TFq =@{H e r@ @, Esbkb t B@`B@x@A$AuEVbbv =j{h`="w@f@@SmxJIJA#Jy B@aRB@x@A ARzEca aR{ =@'= |@c"@ #``$`$pq|a;&B}b!b#`fc~ B@B} @x@A$AjE jaj =jzހ=j%-(1@݀@ B6B B@JB@x@AAJEqb> Je=F* =Y{% .W@@ $F @c Ab b! bc B@aB "y@x@A$AEQat b =jV="@T@ !j$cJcJ B@aRB@x@A ARE~ RaR =R9=R"@@83}<|}4kcbb j B@jB@x@A$AjE aj =j̀=( @ˀ@ BMB J$ c B@JB@$@x@A>Eb Je=R =@{% @Dž@ ;kcb(b B@`B@x@A$AE@ab =1`=j%pj/o@~ mJJ c B@aRB@x@A ARE  aR =RϾ="@(@ C},|}$aHobb½ B@jB@x@A$AjErb jaj =j3v=j%-+c@u@ BtBJ B@JBc@x@AAJE-.Je=F =4{bxs@i/@ $F%c CAb  bZ B@aB@x@A$AEb =j멏aj @K@,JJA# B@aRB@x@A ARE`a FaR =Rmh=FR+c@c@,8STdpI2PDJ!aSBb9b@$j B@jB{ o@x@A$AjEHjaj =j`= @6@$B J B@ B@x@AAJE e=PF =݀{% :"@ ـ@ ': @ GBbk؀b b B@aB@x@A$AEUbcb =jaS`=j^+c@Q@,J0JA#F B@aRB@x@A AREc a aR =R =R"@g @ cMu$r4]L]Bb bo B@jB@x@A$AjE jaj =jfɀ=j @Ȁ@ $( @"AJB"B B@JB@x@AAJEpe=Fu, =a{% @@c Ab b ! c B@aBc@x@A$AE e=5F =ڀ{% %@Հ@ , AbPb 륱 B@aBc@x@A$AE:bcb =jgP`=" @N@ 82$, J5JA# B@aRBynb@x@A AR EHa aR =R=R"@H @ %e4Z@Bb b B@jB@x@A$AjE€faj =jWƀ=(%-(%@ŀ@~BB B@JB@x@AAJEU~e=֤F =A{% "@@ ! @c uAb ! b  B@aB@x@A$AE9ab =j=^(f@[@, JJ ! B@aRB@x@A AR"E鰢aR aR# =R=R"$@@'%t"ta9?B%b^b c& B@jB* @x@A$Aj'Epljaj( =jo=( )@K@ $( @"AJ*Bn,Bc+ B@JB@x@AAJ,E'Je-=x:|. =-{% /@4)@ A0b(b c1 B@aBh@x@A$A2E~〈 Z^iqb3 =jaj$j"4@@,5J|J 6 B@aRBz@ B @x@A AR7EZaRA@aR8 =@8b="9@]@ ,"VHT`$`%$ |y:bb@&'; B@B$X@x@A$Aj<Ejaj= =j=%-+c>@@$?BFB J) V@ B@JB@@x@AAJAE eB=FvyC =@׀{% (D@Ҁ@ , &|yEb5b ! F B@`Bc@x@A$AGEbH =j=^%I@@ mJJJA#K B@aRBw@x@A ARLEHRaRM =R@P="N@K@ ypSITpMIakcObb jP B@jB @x@A$AjQE-jajR = =j%-(%S@ @  `@"@BTBm B$ BU B@Bc@x@AAJVE eW=FBX =ŀ{F% "Y@@c kcZbOb [ B@aB@x@A$A\E:{bb] = `];`=j^"^@9@,_J)J ` B@BL6@x@A ARaEH aRb =R=FR"c@\@ HTop?I%$VaDfdbb je B@jB@x@A$AjfEέb jajg =jV=j%-(%h@@~͓iBBj B@JB@c@x@AAJkEUiJel=֤Fym =@Eo{% "n@j@ TAob ! p B@`By@x@A$AqE$abr =j="s@g@@SmctJJA#Jcu B@aRB@x@A ARvE雰aR FaRw =@=R"x@@ *kd,+dpxBybeb½z B@B@x@A$Aj{EpWjaj| =jZ=( }@I@ r~PY J$ q B@JB@@x@AAJEJe=x%|h =@{% @2@ $F @ mBbb bc B@`B@x@A$AE}΀cb =ja^+c@@ m!j*Q,JtJA# B@aRB@x@A AREEa aR =R>M=R%@H@ 6 uabb B@jB@x@A$AjEaj2 jaj =j=(%-(+c@@~BZB J B@JB@x@AAJE Je=τF =€{% "@н@m @K `Ab4b! b B@aB@x@A$AExbb =jR8`=%pj%@6@,JJA# B@aRB@x@A ARE, aR =R=R"@=@y9I$P4@Bbb c B@jBz@x@A$AjEbjaj =jC= @@ BB c B@JB@x@AAJE:fJe=xF =&l{% F"@vg@ y) @ /Bb  bc B@aB@x@A$AE!a,b =j=j^%@;@,JߠJA#(Rc B@aRB@x@A AREΘaR aR =R~="@ޛ@ %aYJBbJb@$j B@jB$X@x@A$AjEUTajaj =jW=%-(+c@3@$BV J)  B@JB@x@AAJEaJ@Y e=]"|y, ={% %@@ y': @( hv/G?Ab|b ! b B@aB@x@A$AEbˀb =jaj @@,JiJA#r B@aRB@x@A AREpBaFaR =RJ=R"@|E@9#Sua+BbDb j B@jBB@x@A$AjEjaj =ja%-(%@@ $( @(@BBCB$B B@JB@@x@AAJE}Je=̄F =@q{% "@@ c )!Tbb ! 륱 B@`B@x@A$AEubyb =j45`= @3@,JJA#c B@aRB@x@A AREaR =R=R"@!@ c3zBbb j B@jBx@x@A$AjEbjaj =j(=j%-(#@@ BB$ c B@JB@x@AAJEcJe=uF = i{% "@_d@l @c DAb ! bc B@aB 'Z @'@x@A$AEa b =jM#="@!@,JJ  B@A*BB@x@A ARE,ڀFaR =R=R"@D݀@ CǨ999alBbܠb@$ B@jBy 5n@x@A$AjEbjaj =jC=(%-(%@@ rBB J$  B@JB@x@AAJE:QJe=F@Q&W{% "@xR@ c Abڡ  B@aB`x@9 A @E ayA =̀=^.@Sˀ@,JʠJ@F B@a B@x@A AR E΃aR aR =Rp=R" @҆@ S9 :::3:T :aNB b>b@$<c B@jB@x@A$AjEU?aj2 jaj =jB=(%-(%@A@~ӈBA J B@JB@x@AAJE J @==d |t =a% "@ ! @cS$Abwb b  B@aB@x@A$AjEb,b =jvaj%@t@,J]JA#F B@aRB@x@A AREp-a aR =R5=R" @`0@ cq$:T(:q,:T0:q4:TկB!b/bĩ" B@jBQ@x@A$Aj#E耈 jaj$ == %@@ $( @"AJ&B>BJ' B@JB@x@AAJ(E}b J  e)=F * =i{% +@@ , yq]A,bb c- B@aB,@x@A$A.E`a,  b/ =j `=j^"0@{@ 5, @ 1JJ R2 B@aRB|@x@A AR3E׀  aR4 =Rހ="5@%ڀ@ cs8:q<:T@:qD:aӡB6b٠b@$7 B@jB@x@A$Aj8Eb f  aj9 =j =%p(C0;@}@$;BܔB B< B@JB@x@AAJ=ENaJ Je>=F? = À T{% (@@ZO@ ': @c wIxAb  bB B@`B@x@A$ACE acbD =jɀ=j%pj%E@0Ȁ@,cFJǠJ FG B@aRB@x@A ARHEaR aRI =RY=R"J@@ c HI K Jao>DKb#b½L B@jB@x@A$AjME9<jajN =j?=%-%O@@ $( @"@BPB~>(JcQ B@JB@8W@x@AAJRE eS=A | T = {% "U@@$X AVb\b 륱W B@`B !y@x@A$AXEGbY =jٷ= Z@;@,[JJ \ B@aRB@x@A AR]EnRaR^ =Rv=R"_@q@ %M L O NaB`bJb ja B@jB@x@A$AjbET*jajc =j-=j%p(%d@3@ eB, Bf B@JBc@x@AAJgE eh=Fyi ={"j@@ !%Nc `Akbwb b l B@aB@x@A$AmEbbbn =ja`= o@_@,pJ]JA#jq B@aRB@x@A ARrEoa aRs =R$ =R%t@{@ 9udǩt >`Bubbov B@jBژ@x@A$AjwE jajx =j׀=( y@ր@ $( @ūczBBB J{ B@JB@)c@x@AAJ|E}e}=F~ =@q{% :"@@ 5\ @+c .OBbb bc B@`Bc@x@A$AEKab =j5 `=^"@ @ m!j% ! $ ARJJ  B@aRB@x@A ARE€aR =Rɀ=R"@-ŀ@7!R% QTSPA6tNZWAjbĀb j B@jB @x@A$AjE}b2   aj =j(=(%p(+c@@~ƹBB J B@JBc@x@AAJE9aJ J!!e=Fnb =?{% "@\:@B! @\Ab ! b B@aBQ@x@A$AE"#b =jδa$j%@0@,JJA# `RJ B@aRB@x@A AREka $$aR =RPs=R"@n@ yP 0; Bbb½ B@jBB@x@A$AjE9'aj%%aj =j*= @)@ $( @r"AJB) $By B@JB@x@AAJE &&e=AF = À{% F"@@ y) @+c CvAb`b bc B@`B@x@A$AEGb'(b = `l^`=j^"@\@ 5!j @cJ:J B@B@x@A ARETa ))aR =R =R"@h@  t^t aBbb B@jB _c@x@A$AjE j**aj =jkԀ=j%-(+c@Ӏ@ rB'B J B@JB@x@AAJEbb J++e=Fc =N{% "@@c LAb ! B@aB,@x@A$AEGac,-b =j`=%@@,cJJA#c B@aRB@x@A ARE ..aR =Rƀ=&@@ cK[ 0 ucr aJ,bfb@$jc B@jBB@x@A$AjE}zbf//aj =j}= @@@ B|  B@JB &@x@AAJE6aJ 00e=HF =@;{% @=7@ ) @c ,b  b B@`BB@x@A$AE12b =ja^%@@,J}JA# B@aRB@x@A AREha 33aR =RIp=R(o@k@ cCSPCPCPQDak}Hobb f B@jBc@x@A$AjE$j44aj =j'=j%-(+c@@Bf& B)  B@JB@@x@AAJE 55e=&F =@{% "@@h ϏAbAb  B@`B@x@A$AE,b/67b =j^[`="@Y@ m'$J*JA# B@aRB@x@A ARE9a 88aR =R=R"@E@:C PaBbbĩ B@jB@x@A$AjE j99aj =jLр=(%-(%@Ѐ@ BB J B@JB@x@AAJEF::e=ȤF =7{% "@@, (%Ab ! B@aB@x@A$AR  EDac;>aj =jz=j @F@  By$ c B@JB@x@AAJE2aJ??e=iEFc =8{"@'4@ VBb3b  B@aB@x@A$AEoc@Ab =jaj+"%@@,JnJA# B@aRB@x@A ARE|ea FBBaR =R+m=FR%@h@:#(QȀd +gYaBbgb@$j B@jB@x@A$AjE!jCCaj =j$=%-+c!@#@$"BOB J# B@JB@x@AAJ$E܀ DDe%= F& =v{% "'@݀@ y/ @ m4(b&bZ bZ) B@aBy@x@& =A* @EbcEFb+ =jCX`=j^%,@V@,-JJ@F. B@a B@x@A AR/Ea GGaR0 =R="1@.@ c3Ǫtkb`p a@D2bb½3 B@jB@x@A$Aj4E jHHaj5 =j5΀= 6@̀@$7B̀B J8 B@JB@x@AAJ9E+IIe:=F; ={% <@g@ , @( lB=b ! ba${ > B@aBc@x@A$A?EAaJKb@ =j`= A@=@,BJ C B@aRB@x@A ARDE FLLaRE =Rf=R(oF@Ȼ@ c C6E5@+g0GBGb4b½H B@jBc@x@A$AjIEFt<@T jMMajJ =jw=j K@+@~$( @(AJLBv B$BM B@JB@x@AAJNE/JNNeO=NB|P =5{% `3bQ@ 1@ c xARbi0b 륱S B@aB,@x@A$ATET뀈yOPbU =ja V@㩀@ , @yWJOJA#cX B@aRB@x@A ARYEaba FQQaRZ =Rj=![@ie@ S8Ǩ$QaxB\bdb@$j] B@jBQ@x@A$Aj^EjRRaj_ =jp!=j%-j1`@ @ aB,Bb B@JB@x@AAJcEoـ> SSed=Fe =[߀{% cf@ڀ@ Agb bǝ! h B@aB@x@A$AiEbcTUbj =j!U`="k@S@,clJRJA#Fcm B@aRBc@x@A ARnE a VVaRo = = p@@  cQ3<5nBqbb½r B@B* @x@A$AjsEǀF jWWajt =jˀ= u@oʀ@ vBɀBJ$ yw B@JB@x@AAJxEXXey=Fz ={% {@L@ ) @6 CnW|b  b} B@aB@x@A$A~E> aYZb =!b=^.@@,JJ @ B@aRBQ@x@A ARE aR F[[aR =RH=R(o@@ s+``%IoEbb f B@jB@x@A$AjE+q j\\aj =jt=(%-(+c@@ Bgs,  B@JB]L@x@AAJE, J]]e=3?| =2{% "@-@ ! @6 AbNb b B@aB@x@A$AE9W^_b =je a^%@Ǧ@,J3JA# B@aRB/@x@A AREF_a ``aR =Rf=R"@Rb@ `aBbabf B@jB@x@A$AjEjaaaj =jQ=(%-(%@@$BB Jc B@JB@B@x@AAJET bbe=٤F =@D܀{% "@׀@ c OuAb ! B@`B@x@A$AEڑbccdb =jR`=j c@eP@ m!j @JOJA# B@aRB@x@A AREa eeaR =R="@ @ ВЄ(Bb\bA;j B@jB@x@A$AjEnĠ@Y jffaj =jǀ=j @Q@$Bƀ F B@JB@x@AAJEgge=vF5n =݅{F% @2@ y c fBbb c B@aBy@x@A$AE|;hhb =j'@=j%pj+c@>@,J=J  B@aRBy@x@A ARE FiiaR =R=R(o@@ ЂЃ2taqBbgb j B@jB@x@A$AjEjjaj =  =j%-+c@i@ $( @"@BBɴB)B B@B@c@x@AAJEnJkke= e!='|F" ={% #@@4 b$b7b ! % B@aBc@x@A$A&EЀcb' =jX&aj0j%(@@@S')J$J J* B@aRB@x@A AR+E+G'a aR, =@N="-@/J@ cpeΐǵ%a_Ho.bIb/ B@B @x@A$Aj0E(jaj1 =j*=j 2@@ ) @"AJ3BB$Bi,4 B@JBB@x@AAJ5E8 e6=դFa7 = Ā{F% F(8@t@ , 4A9b ! : B@aB,@x@A$A;Ey)bcb< = `9*`=j =@J8@,>J7J ? B@B@x@A AR@E aRA =R=R"B@@;Eepd`ud"HtBCbLb D B@ B@x@A$AjEES+b j! AF = ׯ=j%-(6G@5@$HB I B@Bc@x@AAJJEg,JeK=[zFL =m{% "M@i@ ) @  8ANbvhb bO B@aBQ@x@A$APEa#-abQ =j= R@@,SJ[JA#FcT B@aRB@x@A ARUEn.aR F+$FV =R=R"W@r@$) @%pP0txBXbޜb@%jY B@jB@x@A$AjZEU/jaj[ =jY=H@e-(%\@X@ !jnc]BAB)B^ B@JB@x@AAJ_E{0Je`=Fa =h{F% "b@@ @"Acbb ! b d B@aB$X@x@A$AeÈ,5$^f =j&1aj$j"g@@,hJJA#i B@aRB@x@A ARjED2ay aRk =RK=FR"l@G@!$F @#~3%!0 8_|BmbFb jn B@jB@x@A$AjoE jajp =  3aj%-%q@h@ !j @"@BrBB$Bs B@B@x@AAJtE Jeu=Fv ={% "w@Y@, Axb  cy B@aB@x@A$AzEv4bcb{ =j65`="|@75@, }J4J ~ B@aRB@x@A ARE퀈 aR =Ra=R"@@ 3S!q'SBߘBb%b½ B@jB@x@A$AjE86b jaj =j=(%-(%@@ rB|J B@JB@?) @x@AAJEd7Je=@wF = @j{% "@e@ ! @ {jAb[b b B@`B@x@A$AEE 8acb =jv=^(f@ހ@ m2* JDJ c B@aRB@x@A ARES9aR F>, =R=R"@W@ C ! T` axbÙb@$j B@jB@x@A$AjER:aj2 j#!j =jfV=( @U@~n$( @"DB"B B@JB@x@AAJE`;aJ Je=F =E{% @@ $F @+c 4`b ! bc B@aB@x@A$AEɀ,b =ja%p+c@^~ !j @"@BBBB B@JB@x@AAJE  e=ʄF ={% "@?@$! @eb  b륱 B@aB@x@A$AEs?bb =j3@`=j$j"@ 2@,Jw1JA# B@aRBz[{B@x@A ARE aR =RK="@@ c${Dfbb j B@jB@x@A$AjEAb jaj =j=%-%@@$Be , B@JB &@x@A>EaBaJ Je=%tF =@g{% %@b@. c cAb@b , B@`B@x@A$AE*Cab =S݀=j @ۀ@ m!j @J!J c B@aRB@x@A ARE8DaR aR =Rܛ=R"@8@ (o!Roszy1U"a}QBbb½ B@jB@x@A$AjEOEjaj =j?S=%-(%@R@ !j @(@BBQBB B@JB @x@AAJEE FJe=ʤFw =5{% "@ @ @٣Ab  B@aB@x@A$AEƀcb =jGa$j"@W@,JÄJ   B@aRB@x@A ARE=Ha aR =RE=R"@@@ c% ~}{%q.BbIb½ B@jIBc@x@A$AjE` jaj =j=j%-%@C@~B B$  B@JB@x@AAJEIe=hDŽFh =ۺ{%p"@$@8) @6 Abb bc B@aBc@x@A$AEnpJab =j0K`="@.@,J`J  B@aRBc@x@A ARE{@Y FaR = +4= @@!d @6y!$q 21BTbĩ B@B@x@A$AjELb jaj =j=j%-(%@@~BNB B@JB@x@AAJE^MJe= qF =}d{F% % @_@5 J! @A b$b ! b B@aB@x@A$A ENab =j9ڀ=j$j+c@؀@,JJ c B@aRB 5@A AR @`EOaR FaR =RΘ=R"@)@ c+\\y%`̠PBbb j B@a$B@x@A$AjELPaj jaj =j#P="@O@BNBB$&5 B@JB@x@AAJE*QJe=Fc ={% F"@d @ c &B b  c! B@aB@x@A$A"EÀcb# =jRa^%$@H@, %JJA#& B@aRB@x@A AR'E:Sa FaR( =RdB=R")@=@ zy` a)B*b2b@$j+ B@jByc@x@A$Aj,EE* aj- =j=(%-(+c.@-@ r) @"@B/B J0 B@JB@y@x@AAJ1E̱Te2=MĄF3 =@{% "4@ @c SA5blb 륱6 B@`B$X@x@A$A7ERmUacb8 =j|-V`=j c9@+@ m2 @y:JIJA#c; B@aRB{,@x@A AR<E`@Y aR= =R=">@p@ yVy4 WB?bbo@ B@jB@x@A$AjAEWb2 jajB =jo=(%C@Ϣ@~DB/B JE B@JB@٢o@x@AAJFEm[XaJ JeG=F|yH =@Va{% I@\@ $F @6  AJb be! b K B@`B@x@A$ALEYabM =j׀=%pj(fN@Հ@ m!j @OJԠJA#P B@aRB@x@A ARQEZaR aRR =R=!R(oS@@ o @6%Q % ~aVBTbjb U B@jB@x@A$AjVEI[jajW =jM= X@qL@ YBKB, Z B@JB@x@AAJ[E\Je\=|] = {% ^@F@ @B_b dh` B@aB$X@x@A$AaEbb =jĀ]aj$j%c@$@,dJ~JA#aHRe B@aRB/@x@A ARfE7^a aRg =RU?=R%h@:@ Bq%a5nibb½j B@jB@x@A$AjkE* jajl =j=j%-+cm@@~nBn B$ o B@JB@x@AAJpE_b} Jeq=2Fr ={%p"s@쯀@ B) @ ,5ntbMb! bu B@aBB@x@A$AvE7j`acbw =jZ*a`=%x@(@,yJ&JA#z B@aRBc@x@A AR{EE@Y aR| =R="}@Y@ ct/t /Q@~bb½ B@jB~@x@A$AjE˜bbaj =jT=%-(%@@ BB c B@JB@٢o@x@AAJERXcaJ e=ӤF/ =@F^{% "@Y@ y! @.W b(b e B@`B !L6@x@A$AEdacb =jӀ=^%@\Ҁ@,JѠJ c B@aRB@x@A AREeaR aR =R=R"@捀@,<"+Y~+h@ aCDbRb½ B@jBF@x@A$AjEmFfjaj =jI=(%-(%@N@ n$( @n"@BBH J$B B@JBc@x@AAJEgJe=u|c ={%p"@/@ c  Abb c B@aB@x@A$AE{b =j}ha^"@ |@,Jy{J j B@aRB@x@A ARE4ia aR =R/<=R"@|7@ $F'Eo<%}" %8,b6b B@jB o$X@x@A$AjE jaj =j=(%-(%@@$BOB J B@JB@x@AAJEje=F ={% "@լ@ < {>b5b B@aB@x@A$AEgkab =jH'l`=j$j%@%@,JJA# B@aRB@x@A ARE*ހ FaR =R=R"@2@<#!0S!zy@Dbb jc B@jB@x@A$AjEmb jaj =@݀@="@@ƹ'%"AJBBB B@B@x@AAJE7UnJe=Fy, =#[{% F"@oV@ S c _Ab  c B@aBy@x@A$AEoacb =jЀ=j @Eπ@ !j @,JΠJA#c B@aRB; @x@A AREˇpaR FaR =R{="@ϊ@ 3% ~ƑDa?Bb;b½ B@jB/@x@A$AjERCqjaj =jF=%p(+c@@@ rBE J B@JB@x@AAJE e=Z| = Àra% %@@ * ! @W bu ! b B@`B@x@A$AE_cb =jzsj%pj(f@x@,cJVJ  B@aRB$X@x@A AREm1ta aR =R9=R"@q4@ C"S!tkcb3b½ B@jB@x@A$AjE쀈2 jaj =jl=j @@~$( @"EB0B J$B B@JBF@x@AAJEzue=F/ =o{% F"@@ ) @6 tkcbb bc B@aB@x@A$AEdvab =j1$w`=j^"@"@,JJ  B@aRBc@x@A ARE۠@Y FaR = {=FR"@ހ@ S% ""0S" Dfb{݀b  B@jB@x@A$AjExbjaj =j!=j%-(+cU@@~BB B@JB@٢o85n@x@AAJERyJe=Fz =@X{% "@XS@ @4 YBAb e! b  B@`B@x@A$A E zab =j̀=" @%̀@ m'$ JˠJ@0 B@aRBc@x@A ARE{aR aR =R`=!@@}@~B;bsڀb j< B@jBc@x@A$Aj=Ezb  A> =@݀=(%p%?@U@$@B A B@B@@x@AAJBEOJ@=C=aFrD =@T{% "E@>P@  c AFb  G B@`B !B@x@A$AjHE abI =jʀ=j J@ɀ@ m!j @KJȠJ cL B@aRB/@x@A ARMEaR FaRN =RN="O@@<"KX S!t$wBPbb½Q B@jB5n@x@A$AjRE=j ES =j@=%-(%T@@$UBh? J) V B@JB@x@AAJWE eX=$ |zY ={% %Z@@c yA[b>b \ B@aB5n@x@A$A]E)bb^ = `Jt`=%pj+c_@r@,`JJ ja B@B@x@A ARbE7+a   aRc =R2=R"d@3.@ c "~$`/0N$eb-b jf B@jBQ@x@A$AjgE j  ajh =jE=%-!i@@ƹ/ @"@BjBB Jk B@JB@x@AAJlEDb J  em=ŤF5nn =<{% "o@@$X _Apb ! 륱q B@aB@x@A$ArE]ac  bs = ``=j^"t@^@,uJJ >v B@B@x@A ARwEԀaRx =R܀=R"y@׀@ yy"!0 S!ߑB'zbPbA;{ B@jB/@x@A$Aj|E_b faj} =j=j%p(%~@J@~B B$ c B@JB@x@AAJEKJ >=g^F =Q{% "@ M@ 5n! @ S,bLb bc B@aB5n@x@A$AjElacb = `ǀ=%@ŀ@,JgJ F B@B@x@A AREz~aR FaR =R+="@~@ cÿBBBDbꀀbĩ B@jBL6@x@A$AjE:aj jaj =j==j%-(%@<@ $( @"@BBEB B@JB@x@AAJE Je= =x{F% "@@ \eAb#b ! 륱 B@aB@x@A$AEbb =j8q`=j^"@o@,JJA# B@aRB@x@A ARE(a aR =R/=R"@ +@ * q x|aYBb*b( B@jB@x@A$AjE〈jaj =*="@@~BB$c B@JB@: @x@AAJE)e=Fs =@!{% @c@ , Bb ! B@`B@x@A$AEZab = ``=^%@B@ myJJ  B@B/@x@A ARE aR =Rـ=R%@Ԁ@ o$F'E! C!"$_xaMBb9b jc B@jB o* @x@A$AjEDb jaj =j̐=(%-(+c@(@ !j @"@BBB B@JBc@x@AAJEHaJ~v Je=L[F =N{% "@ J@ y$F @6 CAbkIb  B@aB ",@x@A$AEQac !b =jĀ=^"@€@,JLJ r B@aRB@x@A ARE_{aR ""aR = =R"@g~@ 8|}{zy"r0 2FBb}b@&j B@B@x@A$AjE6j##aj =jj:=j%-(%@9@ B&B$  B@JB @x@AAJEl $$e=d3`A =P{"@@@a!K Abb! b B@aB@x@A$AEbc%&b =j%n`=j)%@l@,JkJA# B@aRB@x@A ARE%a ''aR =R,=FR%@(@= sC#"pBDBb|'b@&j B@jBژ@x@A$AjE j((aj =j=%-%@l@$BB J$  B@JBc@x@AAJE))e=F ={% "@I@ c FAb  B@aB/@x@A$AEWa*+b =j`=jj%@@,JJA#F B@aRB5n@x@A ARE F,,aR =RYր="@р@Ex(o!!%~ Ӕ~P~ a:|bb j B@jB o@x@A$AjE)b j--aj =j=%-(%@@$Bq B@JB@!K@x@AAJEEJ..eV  1XF =@=K{ @F@ " @lbKb bc B@`B@x` =A @E6a/0b =je=j$%@ſ@ m' @BJ1J@ B@a Bc@x@A AR EDxaR F11aR =R=F!R(o @D{@y =#"7ː7Vdp8a9D bzb j B@jBF@x@A$AjE3aj j22aj =jR7=j%-j%@6@~n$( @K BBBc B@JBc@x@AAJEQ J33e=ҤF} =E{% "@@/ hAb ! 륱 B@aB 'Z@x@A$AEتbc45b =j k`="@ki@,JhJ(R B@aRB@x@A ARE!a 66aR =R)= !@$@c3ѡt!Ѯљt8/mB"bYbj# B@jB@x@A$Aj$El݀ j77aj% =j=j%p(%n&@Q@~'B߀ B$ ( B@JB@x@AAJ)E88e*=t`=+ =㞀{% %,@.@w @1a0" ( -A-bb bc. B@aB@x@A$A/EyTajc9:b0 =j`="1@@,2JtJA#c3 B@aRB@x@A AR4E F;;aR5 =R.Ӏ=R"6@΀@%C4$3a 7b̀b j8 B@jB o@x@A$Aj9Eb j<EBaJ J==e?=UF@ =H{% "A@C@ L6/ @+c   Bb0b! b C B@aB "c@x@A$ADE >>bE =ja$j.F@ @,GJwJ H B@aRBz>B@x@A ARIE DE R??aRJ =RK=R"K@@ S,-<]L"2DLbb jM B@jB @x@A$AjNE(ub j@@ajO =jx=j P@ @$QBmw B$ cR B@JB,@x@AAJSE0JAAeT=FU =6{F% F"V@1@ BWbKbjX B@aB@x@A$AYE6cBCbZ =j_aj^%[@@,\J-JA#] B@aRB@x@A AR^ECca/DDaR_ =Rj=FR"`@Pf@  c "1 Qy@abeb jb B@jB4@x@A$AjcEaj EEajd =j:"=%-(+ce@!@ nfB B g B@JBc@x@AAJhEQ JFFei=ҤF j =={ k@ۀ@ c @lb  m B@aB,@x@A$AnEؕb5nGHbo =jU`=j^%p@^T@,qJSJA#r B@aRB@x@A ARsE a IIaRt =R=R%u@@ csљх$ lbDvbYbq w B@jB@x@A$AjxElȀ jJJajy =jˀ=j%-z@W@~{Bʀ Bc| B@JB@x@AAJ}EKKe~=tF =㉀{% "@-@ y! @6 wbb b B@aB@x@A$AEy?acLMb =j=%@@,J|J  B@aRBc@x@A AREaRy FNNaR =RH="@@ ;౟B$ iSEbb j B@jB$X@x@A$AjE raj jOOaj =ju= @t@~BIB B@JB@5@x@AAJE-JPPe=@| =@3{% @.@  c ͡Bb0.b ! B@`B@x@A$AE逈cQRb =j1a^%@@ m!j @JJ(>, B@aRB@x@A ARE(`a FSSaR =Rg=!@0c@ yCE"% $Xbbbj B@jBy@x@A$AjEjTTaj =j7=j @@ ) @"DBB$B B@JBc@x@AAJE6 UUe=F =.݀{% @p؀@ y! @c c$Xb ! b B@aB@x@A$AEbcVWb =jR`="@GQ@,JPJA#j B@aRB@x@A ARE a XXaR =Rx=R+c@ @ cb½ B@jB@x@A$AjEQŀ jYYaj =jȀ=(%-(6@1@~$( @ťBǀA$B B@JB@x@AAJE׀ZZe=XF =̆{% "@@ c bsb 륱 B@aB @x@A$AE^E a'Bb_bf B@jB@x@A$AjEjccaj =j=*(+c@u@ rBB J B@JB@@x@AAJEԀ dde=F =@ڀ{% (@VՀ@ 5\ @ Ab ! b B@`B@x@ =A @Ebcefb =jO`=j @ N@ m!j @JMJ@ c B@a BB@x@A AREa ggaR = j="@ @ $F!R?7?E;Bb'b½ B@B@x@A$AjE5€ jhhaj =jŀ=j @#@!j @.`AJBĀ B$B B@JB @x@AAJE}iie==FB ={F% @~@ @SAbXbj! bW  aBB@x@A$AEC9ajkb =j^=j$j"@@,J*JA" B@aRB@x@A AREPaR FllaR =R =R(o@]@ @E aUB bȲb½ B@jB @x@A$Aj Ekajmmaj =jko=j%-+c @n@ B'B$ / B@JB@x@AAJE^'aJ nne=ߤF$X =V-{% "@(@ c \Ab !   B@aBc@x@A$AEopb =ja @w@,J㠠JA# B@aRB@x@A AREYa qqaR =Ra=R"@\@ AE` bfb½ B@jBh@x 9A$Aj @`Eyjrraj! =j=j%-(%"@Z@ #B B$ $ B@aB@x@AAJ%E sse&=Fc' =ր{% "(@;Ҁ@ y! @c e)bрb b* B@aB (@x@A$A+Ebctub, =jL`="-@ K@,.JuJJ / B@aRBy@x@A AR0Ea vvaR1 =R; =R"2@@>"tD3bb4 B@jB~ c@x@A$Aj5E jwwaj6 =j€=j%-(%7@@~$( @.`@B8Bb B9 B@JB@x@AAJ:Ezxxe;="F< ={"=@{@ 5\%N V&A>b=b b륱? B@aB "@x@A$A@E(6acyzbA =j\="B@@,CJ+J D B@aRB@x@A AREE5aR F{{aRF =RѴ=R%G@-@ ъtfakBHbbĩcI B@jB @x@A$AjJEhj||ajK =j8l=(%pL@k@ rMBjB JN B@JB@\!KB@x@AAJOEC$J}}eP=ĤFQ =@7*{% "R@%@ ! @( {BSb ! b T B@`B@x@A$AUE߀~#[AV =ja^%W@X@ m'%yXJĝJ Y B@aRBy* @x@A ARZEVa aR[ =Rw^=R"\@Y@F># Bё;TOaCbB]bCb@$j^ B@jB& BJ@x@A$Aj_E^aj2 jaj` =j=( a@D@~$( @ "AJbB J$Bc B@JBc@x@AAJdE Jee=iFvf =Ӏ{% g@!π@F Ahb΀b ci B@aBb@x@A$AjEkbbk = `I`=%pj"l@G@,mJ^J Fn B@B@x@A ARoEya aRp =R)=R%q@@ c3JECE$a*PBrbbĩs B@jB@x@A$AjtE jaju =j=%-1v@⾀@ wBCBJx B@JB@x@AAJyEwb Jez=Fz{ =r}{% "|@x@ ! @c ɂ}b"b b ~ B@aB (@x@A$AE 3ab =j==j^%@@fcJ J I B@aRB@x@A AREaR aR =@α=R"@&@* >C KcD\t@ ENDbb½ B@B(c@x@A$AjEejaj =j1i=j @h@ r$( @"AJBgB J B@JB@x@AAJE(!aJ e=F ='{% @e"@ $F @+c Ab ! bc B@aB "@x@A$AE܀b =ja"@!@,cJJ c B@aRB@x@A ARESa aR =Rx[=%@V@>SEEF@ aQb4b½ B@jB @x@A$AjECajfaj =j=%p(+c@@ BJ$  B@JB@!K@x@AAJEʀ e=J݄Fw34P]L =@Ѐ{% "@̀@ ! @ LQbeˀb b, B@`B @x@A$AEPbb =jF`=^%@D@,JOJA#4`  B@aRB@x@A ARE]@Y`~ 28 =R aRR"@^@ @c6GEEH  DDb j B@jB-$Bc@x@A$AjE一jaj =jd=j%-(%@@~$( @"@BB B B B@JBc@x@AAJEktbw e=F5n = Àgz{% "@u@ c Abb! 륱 B@`B@x@A$AE/a1}$^ =j="@p@,JJ (R B@aRB/@x@A AREaR aR =R=R"@@ s$EIEABbcbj B@jB@x@A$AjEbjaj = f=(%p(%@qe@ BdB J$ c B@B@x@AAJE aJe=0| = ${% `3B@N@ ! @ /Ab ! bc B@aB@x@A$AEـcb =ja^(f@@,J~JA# B@aRB@x@A AREPa aR =RJX=R%@S@ ,"8"TaBbb@$j B@jBxc@x@A$AjE' ajfaj =j=j%-(%@ @ $( @"@BBk J$B B@JB@x@AAJE e=/ڄF =̀{"@ ɀ@ c /CAbnȀb B@aB/Z " .X@x@A$AE5bcb =jRC`=j/"@A@,J J >c B@aRB@x@A AREB@Y aR = +aRFR%@F~ yT"e" ĜBbbz B@B @x@A$AjEɵ, jaj =jQ=%-%@@ r `@ B BJ B@JB@x@AAJEPqbw Je=ѤF =@w{% "@r@ y! @c `nAb  bc B@aB]L@x@A$AE,acb = `$=j^%@a@,XJ (R B@B/@x@A AREaR -2h =!J="@@ y,"~ p$Xbdbj B@jB@x@A$AjEk_aj2 jaj =jb= @C@~$( @"D Ba J B@JB@x@AAJ E=@T Je =r-|5n = {% @,@ * c $Xbb! 륱 B@aB(= c@x@A$AExրb =ja @@,JsJ! B@aRB@x@A AREMa aR =RCU=R(o@P@ c%nc,"aSDfb b½ B@jBc@x@A$AjE aj jaj =j =j%p(+c@ @~BPB$  B@JB@x@AAJ!E Je"=ׄF# =ʀ{% "$@ŀ@ c T%b/b ! XF& B@aBc@x@A$A'Ebb( =j@@`= )@>@ v' @*J J IJ+ B@aRB@x@A AR,E'\aR- =@=!.@3@ S!V@'/bb 0 B@@]B o@x@A$Aj1Eb faj2 =j>=j%-j%3@@~4BB$ 5 B@JB@x@AAJ6E5naJ> Je7=F8 =!t{% %9@ko@ $F @ ,:b ! b; B@aB "@x@A$A<E)acb= =j=">@N@,c?JJ @ B@aRBc@x@A ARAEɠ aR aRB =Rs=R"C@ɣ@ )%K >ъaoDDb5b½E B@jB @x@A$AjFEP\ ajfajG =j_=j%-(%H@0@ !j @űIB^J$BJ B@JB; @x@AAJKE aJ eL=W*|M ={"N@@/ AObrb P B@aB@x@A$AQE]ӀbR =j a$"S@@,TJ`JA#cU B@aRB@x@R =ARV @`EjJ a aRW = ?`=R=R%X@oM@ ъ 6CYbLb fZ B@B}@x@A$Aj[Ejaj\ =jm =(%-%]@@ ^B-B, _ B@JB@&B@x@AAJ`Ex ea=FFb =@hǀ{% "c@€@ c Ixdbb ce B@`B@x@A$AfE|bbg =j=j h@@ m!j @iJsJA#j B@aRBB@x@A ARkE8aR aRl =R>@="m@;@ $F!Rc"daDnb:b½o B@jB@x@A$>pE jajq =j=%-(%r@@$sBXB Jct B@JB@x@AAJuEJev=}|tw ={% %x@а@ @" @J5Ayb/b b z B@aBc@x@A$A{Ekacb| =j:+`=j$j+c}@)@,~JJA# B@aRB@x@A ARE' aR =R=R"@7@ "! ?@"EP@2Bbb B@jBc@x@A$AjEb jaj =j6=j @@$BBJ B@JB@٢oBc@x@AAJE4YJe=F/ =@9_{% F"@rZ@ ) @ Bb e! b B@`B !@x@A$AEa$Xb =jԀ=j$j%@JӀ@ m!j @cJҠJ c B@aRBc@x@A AREɋaR FaR =R=F!R"@Վ@c?""9":albAb½c B@jBc@x@A$AjEOGaj jaj =jJ=j @2@~' @"DBI B$B B@JBc@x@AAJEJe=W| ={% @@  Je=F =t{% (@@ ) @c W.Abb ! b B@aB@x@A$AEga b =jl="@j@,cJjJA# B@aRBQ@x@A ARE#RaR =R0+=R"@&@y?3^BRp 3QBb%b j B@jBL6@x@A$AjE aj =j=( @@ƹBLB J$ / B@JB@x@AAJEb Je=h ={% @Λ@ @ Bb/b! by B@aBy@x@A$AEV ab =jL!`=j.@@,JJA# B@aRB@x@A ARE'͠@Y aR =RԀ="@#Ѐ@ C+]%~ÏBbπb½ B@jB !/@x@A$AjE"b jaj =j6=j%-( =R$=R"?@O @7+o IFEa kB@bbA B@jB !y@x@A$AjBE؀ jajC =jf܀=(%-(%D@ۀ@~EB"B JF B@JB@x@AAJGE\5eH=ݤFcI = ÀQ{% "J@@ ! @AKb ! b L B@`B "@x@A$AMEO6abN =j7`=j$j%O@b@,PJ J Q B@aRB@x@A ARRE FaRS =!J΀=R"T@ɀ@!!I  I"BUbeb cV B@jB @x@A$AjWEw8b jajX =j="Y@R@ $(%"AJZBBc[ B@JB@x@AAJ\E=9aJye]=PF^ =D{% F"_@??@ A`b>b ca B@aB "@x@A$AbE,bc =j:aj$j"d@@,eJJ f B@aRB@x@A ARgEp;aaRh =RKx="i@s@ IC!""Qn}Bjbb jk B@jB@x@A$AjlE,<jajm =j/=%-+cn@.@$oBEB J) p B@JB \$@x@AAJqE(aer=%Fs = {% %t@@ ': @ ʏAub@b ! bv B@`B@x@A$AwY`DE&=bbx ==Sc>`=j y@a@ m2 @ l@ ! ( ARzJ!J { B@aRB@x@A AR|E4?a  A} =R!=R"~@4@ IF"4xBAjbb B@jB oc@x@A$AjE jaj = Cـ=%-(%@؀@ Rr$( @(@BBB$B B@B @x@AAJEA@@==ƤFb =={% "@@ {`b ! 륱 B@aB@x@A$AjELAayb =j B`=%pj"@[ @,J JA# B@aRB@x@A ARE FaR =Rˀ=R"@ƀ@ "}{zy~aDfbEb j B@jB@x@A$AjE\Cb j G =j삀=j%-%@G@~B B$ c B@JB@x@AAJE:DJe=dMF =@{% "@ <@ y! @c VAb;b bc B@aBy@x@A$AEj b = `Ea"@@,J`J B@Bc@x@A AREwmFa F  aR = $u= @wp@ c"0 S"~ű$`>Bbobĩ B@B@x@A$AjE(Gj  aj =j,=j%-(%@+@ $( @(@BBJB B@JB@x@AAJE   e=F =y{F% %@@ / @ Ab b ! b륱 B@aB W.X@x@A$AE H  b = ``=j^"@ @,JwJ B@B@x@A ARE[IRaR =RAc=R"@^@ c"S!aTפb]b j B@jB@x@A$AjEJaj faj =j="@@~ƹBe J$c B@JB@x@AAJE J >=F$X =؀{% @Ӏ@ y! @K ݌b;b b B@aB@x@A$AjE&Kb&b = `ܒ=j%@>@,JJ B@Bc@x@A AREILaR aR =RvQ=R%@L@y@%   aHob-bĩ B@jBy@x@A$AjE4Maj2 jaj =j=j%-(+c@@~$( @"@BBd B B@JB@x@AAJE Je=ƤF* =ƀ{F% "@@, :AbVb! c B@aB@x@A$AEA|Nbb =jvENހ f**aj? ==j%-(+c@@)@~AB BB B@ B@!K@x@AAJCEՙ^++eD=ݤF`E =@ɟ{"F@@ .rAGbqb H B@`B !@x@A$AIE\U_a| ,,bJ =j Z=%K@lX@,LJWJ cM B@aRBh@x@A >NE`aR R--aRO =R=R"P@@ o$F'E!c11E%PaXBQbVbĩR B@jBc@x@A$AjSEì j..ajT =jπ=( U@N@~!j @"AJVB΀ BW B@JB@x@AAJXEa//eY=FZ ={% :"[@+@ A\bb c] B@aBc@x@A$A^EwCbay01b_ = `c`=^"`@ @,aJuJ jb B@B@x@A ARcE F22aRd =R'€=R"e@@s >utBfbb@(jg B@jBz oc@x@A$AjhE vdb j33aji =jy=(%p(+cj@x@kBWBBl B@JB@!Kc@x@AAJmE1eJ44en=DFto =@z7{% "p@2@6 @ qAqb-b b r B@`B ! @x@A$AsE 55bt =j=)j%u@@,vJJ w B@aRB@x@A ARxEf66aRy =R\=R"z@@ c4:"azB{b#b j| B@jB@x@A$Aj}E&dgaj2 77aj~ =jg= @@~ncBff J B@JB@x@AAJEhaJ@Y J88e=F =%{% F"@ @ \BbLb B@aBc@x@A$AE3ۀy9:b =jdia^%@ƙ@,J2JA#r> B@aRB@x@A AREARja ;;aR =RY=R"@QU@   ƣ x BbTbj B@jB@x@A$AjE kaj j<?b =jDm`= @TC@@Sf2 @" /JBJrR B@aRB@x@A ARE @@aR =@naR!@~ c $ $@ aBbZb j B@B @x@A$AjEi, jAAaj =j=j @X@~rB B B@JB@x@AAJErobw} JBBe=uF/ =x{% @)t@ pBbsb! B@aB@x@A$AEv.pacCDb =j=G@p ^+c@ @,cJuJ  B@aRB@x@A AREqaR_EEaR =R9= @@ 1t 5n%Bbb j B@jB@x@A$AjE araj fFFaj = {d= @c@~BWB F B@JB@x@AAJEsJGGe=/|* ="{% @@ OBb-b `B B@aB@x@A$AE؀HIb =jFta^%@@,JJA#= B@aRB@x@A ARE%Oua FJJaR =RV=R1@1R@yV"BbQbĩ B@jB@x@A$AjE vjKKaj =j0=(%-( B@Bc@x@AAJ?ENb Jcce@=ӤFyA =>{% "B@@ ^ACb ! D B@aBc@x@A$AEEZadebF =j `=^%G@k@,HJJA#I B@aRB@x@A ARJE ffaRK =Rـ=R"L@Ԁ@ )'ő_7K A#i1%%"kaBMbRb jN B@jB@x@A$AjOEhb jggajP =j=j Q@M@!j @"AJRBB$BcS B@JB@x@AAJTEHJhheU=p[FV =N{"W@&J@ )%N+c 5AXbIb bcY B@aB @x@A$AZEvaBijb[ =jĀ=j$"\@À@,]Jm JA#^ B@aRB@x@A AR_E{aR FkkaR` =:`=FR%a@~@ 3%&% 5(Bbb}`fc B@jB; @x@A$AjdE 7`llaje =j:=+cf@9@$gBVB Jh B@JB@x@AAJiE mmej=|*k ={% F"l@@ ': @( Amb-b! b n B@aB m2$X@x@A$AoEbcnobp =j>n`=j^%q@l@,rJ J s B@aRB|5n@x@A ARtE%%a ppaRu =R,=R"v@-(@ CkZ'%1ia6wb'b½x B@jB~ ,@x@A$AjyE jqqajz =j =j {@|@ $( @"D|BB$Bc} B@JB !K @'@x@AAJ~E2rre=FB =@@#{% @n@ c 2b ! c B@`B@x@A$AEWastb =j`=j @H@ m, @JJ  B@aRBc@x@A ARE΀@Y FuuaR =R}ր=F!@р@ c S$4% cʖDfbCb½ B@jBc@x@A$AjEMb jvvaj =j͍=j%-j+c@*@~B B$ , B@JBc@x@AAJEEJwwe=UXF@ =K{% (@G@ B! @c ndAbpFb b, B@aB 'Zc@x@A$AE[ayxyb =jv="@ֿ@,$XJBJ (> B@aRB@x@A AREhxaR FzzaR =R= @d{@ ccKc "u"BP=Bbzbj B@jB @x@A$AjE3j{{aj =j7=j%-(%@6@ $( @ūcB;B B@JB@x@AAJEv> ||e=F =f{% %@@ y) @6 Abb ! b륱 B@aBc@x@A$AEby}~b =j(k`="@i@,cJhJA#F B@aRB@x@A ARE "a\aR =R)=R"@%@ s-1aS+b$b j B@jB@x@A$AjE݀faj =j!=(%p(!@~@~rBB$ c B@JB@x@AAJEb e=F ={% "@S@ c O*b ! c B@aB@x@A$AETab =j`=^(f@-@,JJ  B@aRB@@x@A ARE aR =RWӀ=R"@΀@ tBtZ)` a+ZDb$b  B@jB@@x@A$AjE2b jaj =jŠ=(%-(%@ @ B~  B@JB 5@AAJ @EBJe[`<=:UF ==H{% "@C@ c AbUb  B@abB,@x@A$AE@b =jcaj%@Ƽ@@S, @`# !*J2J (Jc B@aRB@x@A AREMua FaR =@}="@ax@ v$F!R!?71  1MaBbwbf B@BQ^@x@A$AjE0jaj =jP4=%-(%@3@$BB J)  B@JB@x@AAJE[ e=ܤFu, =_{% %@@ Ab !  B@aB@x@A$AE᧥bcb =jg`=j$j%@\f@,JeJA# B@aRB@x@A AREa aR =R&="@"@  M+[ M`1]]a66Bbo!b½ B@jB@x@A$AjEu jaj =j݀=j%-%@D@$B܀ J$  B@JBc@x@AAJEe=}`= ={F% %@9@ ) @6 A\b b B@aB*@x@A$AEQajcb =j`=j @@,JzJA# B@aRB@x@A ARE FaR =R@Ѐ=R" @ˀ@ ]`"a`1B bb j B@jB@x@A$Aj Eb jaj = =j%-(%@@~ƹ' @(@BBSB B@B@x@AAJE?Je=RF =E{% "@@@ ! @+c Ab:b ! b륱 B@aB .X,@x@A$AE$cb =jUa @@,J#J  B@aRB@x@A ARE2ra FaR =Ry=R"@Bu@ y%"$4aSBbtbĩ B@jB$_@x@A$Aj!E-jaj" =jE1=j%p(%#@0@ $( @ť$BB$Bc% B@JB@x@AAJ&E?/e'=F( =8{F% ")@@ y c wA*bߡ ! 륱+ B@aB@x@A$A,EƤb,b- =d`=j^".@Uc@,/JbJ 0 B@aRB@x@A AR1Ea+ aR2 =R#=R"3@@ %u$EasB4bHb5 B@jB@x@A$Aj6EZ׀ jaj7 = ڀ="8@=@ 9BـJ$ : B@B@&B@x@AAJ;Eᒳb 4E Je<=bF= =@՘{% >@"@ y! @c /B?bb b @ B@`B,@x@A$AAEhNabB =j`=^%C@ @, DJ_JA#E B@aRB@x@A ARFEuŀ aRG =R*̀=R%H@Ȁ@ BEaS;BIbǀbfcJ B@jB@x@A$AjKEb jajL =j=(%-(+cM@烀@ r$( @"@BNBHBJO B@JBc@x@AAJPE` sb5b t B@Bx @x@A$AjuE?Ԡ@YW jajv =j׀= w@+@ xBր ,y B@JBc@x@AAJzEƏe{=GF| ={% F"}@@ ) @ e~bbb b, B@aB "y@x@A$AEMKab =jv `=j^%@ @,JCJ `R- B@aRB@x@A AREZ @Y FaR =Rʀ=R"@jŀ@cBOdENpuVGMd@ƎbĀbj B@jB5n@x@A$AjE}b jaj =ji=j%-(+c@ŀ@$B%BJ)  B@JB@x@AAJEh9aJ@Ye=F =X?{% "@:@c mAbb !  B@aBB@x@A$AEyb =j a%@m@,JٲJA#r B@aRBc@x@A AREka FaR =Rs="@o@ #p(S`$K %aBbtnbj B@jB@x@A$AjE'jaj =j+=%-(C@b*@ cB)B$ g B@JB@)y@x@AAJE e=F/ =@{% "@C@ y c ,VAb !  B@`B@x@A$AEbcb =j^`=^%@#]@ m!j @J\JA# B@aRB@x@A AREa aR =RS=!@@ 3_+%%aGBbb½ B@jB/@x@A$AjE$р jaj =jԀ=j%-j%@@~BdӀ B$  B@JB@@x@AAJEe=,`=@ =@{% %@區@ ! @+c uAbGb b B@`B@x@A$AE1Hajcb =jp`="@@ mJ ǭ^aD)Dfbb j B@jB@x@A$AjEejaj =jNi=%pj+c@h@  BB $ y! B@JB@c@x@AAJ"EL!Je#=T$ =@@'{% 4 %@"@ hv/G?A&b ' B@`B ! @x@A$A(E܀b) = ``a^%*@j@,+J֚J , B@B@x@A AR-ESa aR. =R[=R"/@V@  Ǭe "aUB0bPb½1 B@jB@x@A$Aj2Egjaj3 =j=(%-(%4@N@ n5B J6 B@JBc@x@AAJ7E e8=o݄Fzc9 =Ѐ{ :@+̀@ pA;bˀb Z,< B@aB@x@A$A=Eubt b> =j=j ?@}@,@J鈀JFA B@aRB@x@A ARBEAaR RaRC =RI="D@D@ c$@"a#BEbgbjF B@jB o,@x@A$AjGE jajH =ja I@K@ rJB JK B@JB@c@x@AAJLE JeM=|w  y`\yN = ){% :cO@D@ b5\ @ +c hBPbb b Q B@aBc@x@A$AREtb,bS =j4`=j T@3@ m!j @UJ2J *RV B@aRB@x@A ARWE뀈@Y FaRX =RT="Y@@ tP. T@oBZbb j[ B@jB@x@A$Aj\E$b2 jaj] =j= ^@ @~_Bh J` B@JBc@x@AAJaEbaJ Jeb=/uFycc =h{% d@c@ ! @6 VBebFb! b f B@aBe 5@A$Ag @E1abh =jTހ=%pj1i@܀@,jJ J@ݥ(Rk B@a B@x@A ARlE>aR aRm = 휀=Rcn@G@ UtQ.v%`J\Bobb p B@B@x@A$AjqEPajajr =jMT=j s@S@r$( @`" "AJtB B$Bu B@JB@x@AAJvEL aJ ew=ͤFx =D{% F"y@ @ y) @+c  Azb ! baai{ B@aBy@x@A$A|Eǀ Z b} =ja ~@]@,JɅJ R B@aRB@x@A ARE>aRA@aR =@F=R"@A@ PPÔPuP BbTb@&4j B@B@x@A$AjEg@Y jaj =j=j%-(6@L@~B B$ y B@JB@x@AAJEb Je=oȄF, =һ{% "@%@ m @+c JnAbb! ba"  B@aBB@x@A$AEtqacb =j1`="@/@,JgJA#R B@aRB]L@x@A ARE@Y aR =R5=R"@@ yuPdq[Ǯt@_Bbbj B@jBb@x@A$AjEb jaj =j=j%-(%@@~BMB  B@JB5n@x@AAJE_Je=rF ={e{"@`@ c rAb+b  B@aB @x@A$AEb =j=.@@,JJA#(> B@aRB$X@x@A AREր FaR =RJހ=R%@ـ@  X.Bbb j B@jB,@x@A$AjE#aj =j=(%-@@rBo J$ B@JBc@x@AAJEMJe=Fy =S{*"@N@ BbFb  B@aB@x@A$AE1 b = ` =j @- @,J J B@B@x@A AREĀ aR =Rj̀=R"@ǀ@ o$F!!C%  t?Nb,bĩ B@jBB@x@A$AjE>aj = ΃=j%-(+c@,@ !j @(@BB  B@B@x@A>E;Je=ͤF =A{% "@=@ c ZAba{% " @9@ ! @(  bFb B@aB@x@A$AE1  b =jaa^%@ò@,J/JA#1R B@aRB@x@A ARE>ka   aR =Rr=R"@Fn@'ECAt.'t 1a^]Lbmb½ B@jB@x@A$AjE&aj  aj =j1*=( @)@$B(B J B@JB@2a @'@x@AAJEK Je=ͤF, =@@({%p!@@  c ]L"b ! c# B@`B !$X@x@A$A$Eҝbcb% =j]`=j$j%&@Y\@ m!j @'J[J ( B@aRB@x@A AR)EaaR* =Rx="+@@ St@% P"u@aJ[,bE aR FaR? =R =R"@@ @ Cc"t! QBAbwb jcB B@jB y@x@A$AjCE jajD =j€=j%-1E@a@ $( @ FBBG B@JB@!Kc@x@AAJHEz b JeI=H|J =@{% "K@C{@c ALb Z 륱M B@`B,@x@A$ANE5 bO =j4:= P@8@ mcQJJA#cR B@aRB@x@A ARSE@Y aRT =R=R"U@@ ,s*% "¯q@4BVbb½W B@jB@x@A$AjXE ajY =j =(%p(%Z@j@ [BЮB,J\ B@JB@x@AAJ]E#hJe^=+6_ =n{% "`@_i@/C  Aab  b B@aB@x@A$AcE#abd =j=^.e@(@,fJJ (Rg B@aRB@x@A ARhE@ aRi =Rk=R"j@@ - L6kb+b jl B@jB@x@A$@mE>Vb|ajn =jY=( o@$@$pBX Jq B@JB@c@x@AAJrEaJ   es=F$|t =@{% u@@ L6vbab^! w B@`B@x@A$AxEK̀F!"by =jaj cz@⋀@ mc{JNJA#| B@aRB@x@A AR}EYDa ##aR~ =RK="@QG@ ,  t+hD"aHobFb j B@jB@x@A$AjE$$aj =jpaj @@ cB,B$  B@JB@x@AAJEf %%e=F =J{F @@ L6! @ Bbb B@aB 'Z"@x@A$AEvb,&'b =j7`=j @t5@,J4J F B@aRB@x@A ARE ((aR = =FRc@ @ ÔPtQ wbvb j B@vB@x@A$AjEb j))aj =j=%-1@l@$BͫB B@JBy@x@AAJEeJ**e=wF =j{%p"@Ef@ b   B@aB@x@A$AE a+,b =j=j^1@߀@,J}ޠJA# B@aRB$X@x@A AREaR F--aR =RL=R"@@ $~01{Dbb½ B@jBL6@x@A$AjE#Sj..aj =jV=j%-(%@ @ ) @"@BBoU Bc B@JB@x@AAJEJ//e=+!| ={% "@@ ! @c &AbEb b륱 B@aBc@x@A$AE0ʀ01b =jQaj @@,JJ  B@aRBc@x@A ARE>Aay 22aR =RH=FR"@ND@ 88$ `a$`q5n6bCbA;j B@jB| ,@x@A$AjE j33aj =jL aj%p(%@ ~ƹ$( @ťBB B@JB@x@AAJEK  J44e=̤F{ =7{% "@@ 5\ @ b ! b륱 B@aBB@x@A$AEs!b55b =jrx="@v@,J>JA#Fc B@aRB@x@A AREX/"R66aR =R7=R"@i2@ !$F%?79: ; ?b =jˀ=j)j%@%ʀ@,cJɠJ R B@aRB@x@A ARE)aRRA@@@aR =@[="@@ !)!oҐ?>rybbj B@B5n@x@A$Aj_  `E">*jAAaj =jA=j @@!j @"DBc@ Bc B@aB,@x` =AJ @E BBe=* | ={F% F%@@  m bEb! c B@abBL6@x@A$A E0+bcCDb =jcu,`=j$j" @s@,J/JA#{R B@aRB@x@A ARE=,-a EEaR =R3=FR"@M/@DA%@r@ Dfb.bA;j B@jB@x@A$AjE jFFaj =jP=%p+c@@ nB BAJ$  B@JBc@x@AAJEK.GGe=̤F =?{% "@@ c >Ab ^! , B@aBy@x@A$A E^/aHIb! =j0`=^%"@X@,#JJ $ B@aRB@x@A AR%E FJJaR& =R݀= '@؀@,DBp}pB(bcb j) B@jB@x@A$Aj*Ef1b jKKaj+ =j=j%-(%,@S@~-B B$ . B@JB@x@AAJ/EL2JLLe0=n_F1 =R{% %2@(N@ @W pA3bMb b4 B@aB@x@1 =A5 @Es3a,MNb6 =jȀ="7@ǀ@,8JrƠJ@9 B@a B@x@A AR:E4aRy FOOaR; =R<= <@@ c#% =ZB=bb j> B@jB@x@A$Aj?E;5aj jPPaj@ =j>=j%-(%A@=@~BBCBC B@JB@x@AAJDE JQQeE= |v,F ={% %G@@ v c AHb*b ! I B@aB@x@A$AJE6RRbK =j="L@@,MJJA#N B@aRBzWB @x@A AROEm7aRSSaRP =REu=R"Q@p@ c3<':%9aBRb bĩS B@jBB@x@A$AjTE")8aj fTTajU =,=( V@@ cWBf+J$ cX B@JBc@x@AAJYE JUUeZ=F$X[ ={% \@@ c {B]bEb c^ B@aBy@x@A$A_E09bVWb` =jU`:`=j1a@^@,,bJ#J nc B@aRB@x@A ARdE=;a XXaRe =R="f@Q@ DCT% p0{Bgbb@(jh B@jBy ! @x@A$AjiEҀYYajj =jHր=%-(+ck@Հ@ rlBB Jm B@JB@!K/@x@AAJnEK`=j v@\@ m!j @; wJJ rȭx B@aRB @x@A ARyE ]]aRz =RȀ="{@À@ S>aa|b[b j} B@jB @x@A$Aj~Ee|?b j^^aj =j=j @N@ B~ B$ c B@JB@@x@AAJE7@aJdE J__e=mJF =@={F% @/9@ y! @  b8bj^! bc B@`B@x@A$AEs`ab =jAaj%pj+c@@,JfJA#rR B@aRB@x@A AREjBa bbaR =Rr=R(o@m@ Dc?U"uvuw"agHoblbj B@jBL6@x 9A$Aj @`E&Cjccaj =j)=j%-+c@(@ $( @"@BBSB$B B@aB@x@AAJE dde='Fd =~{% "@@ |Ab*b ! 륱 B@aB@x@A$AEDbefb =j;]E`= @[@,JJ  B@aRB@x@A ARE"Fa ggaR =R=R"@"@ cs$ >"}l{ * lBbb B@jB @x@A$AjE jhhaj =jӀ=j%-(%@yҀ@ BB$ c B@JB@x 9AAJ @E/Giie=Fc =({% "@j@ y! @c Ab̡ bc B@abBc@x@A$AEFHacjkb =jI`="@9@,JJA# B@aRB@x@A AREĽ FllaR =R~ŀ=R"@@ y1U>2`a[BbDb jc B@jBF@x@A$AjEJyJb jmmaj =j|=j%-(%@'@~ƹ$( @ūcB{ B B@JB@x@AAJE4KJnne=RGF =:{"@ 6@ y5\%N+c JAbm5b b륱 B@aB @x@A$AEX5nopb =jLa"@简@,JSJ  B@aRB@x@A AREegMa FqqaR =Ro=R%@mj@ c 1/EA \?Bbibĩ B@@]Bc@x@A$AjE"Njrraj =jp&=(%p@%@ rB,B J B@JB@x@AAJEsހsse=F =o{% "@߀@ c Bbb B@aBc@x@A$AEObytub =j)ZP`=^%@X@,JWJ c B@aRBB@x@A AREQavvaR =R=R"@@ ґҐ"ґ@bbb j B@jB$X@x@A$AjÈ2 wwaj =jЀ=( @wπ@~ƹB΀B J B@JB@x@AAJERb Jxxe=FB ={% @P@ , eEb ! B@aB,@x@A$AECSayzb =jT`= c@.@,JJA# B@aRB@x 9A AR @`E {{aR =RT€=R%@@ c T<}0ґ`̠MBbb½ B@a$BQ@x@A$AjE/vUb j||aj =jy= @@ B{xJ$ ``JB@x@AAJE1VJ}}e=7DF =7{% `3b@2@ , BbRb  B@aB@x@A$AE=~b =jtWaj^+c @ԫ@ 2 @ J@J  B@aRB@x@A AR EJdXacaR =  l=R%@bg@ CqSqc}]Lbfb  B@B@x@A$AjEYaj aj =ja#=j%-(6@"@~nBB B@JB@x@AAJEXۀ> Je=ݤFy, =T{% "@܀@ $F @ Ab ! b  B@aB@x@A$AEޖZbcb =jW[`=%@iU@,cJTJA#nb B@aRB@x@A AR!E \aaR" =R=&#@@ZX @K DBtZ/va6N$b\b j% B@jB @x@A$Aj&Esɀaj' =j̀= (@D@ )Bˀ( c* B@JB@B@x@AAJ+E]b e,=~F- =@{% .@5@ W! @E/bb bc0 B@`BW@x@A$A1E@^b2 =jE=$j%3@D@,4JpCJA#y5 B@aRB@x@A AR6E aR7 =R_aR(o8@~ ,%UL@pBѹBB? B@JBc@x@AAJ@Es`bw JeA=AB =y{"C@Nt@ y c !D`  E B@aB@x@A$AFE.aa bG =j= H@"@,IJJ J B@aRB@x@A ARKEbaR aRL =RZ=R%M@@ ± e"Nb(b@%jO B@jB@x@A$AjPE/acjajQ =jd=(%-R@c@$SB[B J/l T B@JB@c@x@AAJUEdaJeV=7/|wW =@"{"X@@ y': @c BYbVb bcZ B@`By@x@A$A[E<؀b\ =j&eaj ]@@ m' @^JJA#c_ B@aRB{* @x@A AR`EJOfaaRa =RV="b@JR@,E[p `*BcbQb jd B@jB@x@A$AjeE gaj2 ajf =jY=%-g@ @~w$( @ūchBB Ji B@JBc@x@AAJjEWƀek=٤F$Xl =L̀{% %m@ǀ@ 7Bnb io B@aB@x@l =Ap @Eށhbbq =jBi`=%pj"r@e@@,sJ?J@t B@a B@x@A ARuE FaRv =RzjaRR"w@~ tt3p,cCBxbHbĩy B@jBc@x@A$AjzEr, jaj{ =j=%p,k|@W@ }BJ~ B@JB@x@AAJEokbw J@==~Fxc =u{% "@4q@ ! @ȅ#( :Abpb b B@aB@x@A$AjE+lab =j=j^%@@,,J{JA# B@aRB@x@A AREmaR aR =R/=R"@@ #1x|}䐁aBbb½ B@jB @x@A$AjE^njaj =ja=j @`@ $( @"AJB\B B@JB@x@AAJEoaJe=,|B = À{% @@ / @+c 5Ab;b ^! bic B@`B@x@A$AE!Հcb =jPpa"@@,cJJA# B@aRB@x@A ARE/Lqa aR =RS=%@;O@yE33L@$ IBbNb B@jB @x@A$AjErajF jaj =jB =%p(+c@ @ B BJ `, B@JB@@x@AAJE<À Je=ɤFqc =@0ɀ{% "@wĀ@ ! @ aO*b  b B@`B !5n@x@A$AE~sbb =j?t`=^%@b=@,J=R"@7:@ s0D𐁫6\aqb9b@$j B@jBy@x@A$AjE jaj =j9=j @@ BB B@JB@2aL6@x@AAJE =@{% ?@[@$X A@b ao! 륱A B@`By@x@A$ABEfaxbC =j&`=^"D@F%@ mcEJ$JA#aHRF B@aRB@x@A ARGE݀aRH =Rg=R%I@@ U:U@{BJb%b jK B@jB]L@x@A$AjLE e9=ÄF,: ={% ;@ݱ@ / @+c (A<b>b ! b= B@aB@x@A$A>E la zA? =jG,`="@@*@,AJJ rB B@aRBc@x@A ARCE.  qARD = =%E@N@ s$p#ќ`Su0BFbbjG B@jB@x@A$AjHEb j AjI =j-=%p(+cJ@@~KBB$ L B@JB@٢o/l@x@AAJME;ZJ`=N=ĤFQO =@+`{% "P@v[@ @9 AQb e! bR B@`B@x@A$ASEa* 1|%T =jՀ=^%U@AԀ@ m' @VJӠJA#W B@aRB@x@A ARXEόaR FARY =Rz=R"Z@ۏ@ ,+"HK/}/Na J[bGb@$j\ B@jB,@x@A$Aj]EVHj(p"^ =jK=(%-(%_@;@ `BJ B$ a B@JB@@x@AAJbEJ`=c=^|,d =@ {% "e@@ $F @+c &Afbyb bg B@`Bc@x@A$AhEc ^ Bi =jĀ=^%j@p€@@Sm!jkJJA#Jl B@aRBy@x@A ARmEz ARn =@=R"o@~@ b S "ѣE=pbn}bA;jq B@BF@x@A$AjrEq6j Ajs =j9=(%-(Ct@L@ ruB8 Jv B@JB@@x@AAJwE  `=x=|yy = 5{% "z@5@ ! @+c oA{bb b | B@aB@x@A$A}E~b B~ =jm`=j%pj%@ l@ mJukJ  B@aRB$X@x@A ARE$a !) =RA,="@'@ q $ÔP" aX2zbb! B@jB$X@x@A$AjE2 jAj =j= @@~$( @"DBSB B@JB C@x@AAJE`==F ={% #)`@3e@ל@ B$F @+c 8bb5b ! bc B@aB0!,@x@A$AE WB =A6`[=^"@$Z@ m!j%JYJ  B@B@x@A ARERAR =RI=!@@ c #  0 S`-M#Dfbb@' B@jB@x@A$AjE-Π@Y @u' =jр=%pj+c@@~BzЀ $  B@JB@١$@x@AAJE`==Fc =@{% +c@@ ': @+c ;AbPb ! b B@`Bc@x@A$AE;EaB = `r`=^%@@,J>J  B@B@x@A AREH@YA FAR =RÀ=R"@X@  Ô}@~$@Bbľb# B@jB o@x@A$AjEwb j'% =jK{=j%-(C@z@~$( @"@BB B$B B@JB@٢o!KB@x@AAJEV3J`==EF} =@J9{% "@4@ ) @1! @# +c hv/G?*Ab e! b륱 B@`Bc@x@A$AEB =ja"@w@ m!j$#j`4! !% ARJ㬠J  B@aRBb@x@A AREea FAR =Rm=R"@h@7!R(o~0abbb½ B@jB@x@A$AjEq!jAj =j$=(%-(C@Z@ `@ťB# J$ B B@JB@x@AAJE܀ `==yFy ={% "@7ހ@h b݀b c B@aB5n@x@A$AE~b !B =jX`=j$j"@W@,JmVJA# B@aRB@x@A AREa ";) =R7=R"@@!!Cu"w$`p##ED[Dbb j`b* B@jB@lc@x@A$AjE j#I[!j =j΀="@@ƹBf̀ J B@JB@x@AAJEb J$$`==FFQc ={% F"@Շ@c "'b5b!  B@aB " O@x@A$AE Bac%&B =j?`=j$j%@@ cJJ c B@aRBB@x@A ARE- ''AR =R="@5@ yw"ќ` `vw&HBbb½ B@jB @x@A$AjEtb j((Aj =j8x=*#@w@ rBvBJ$  B@JB@x@AAJE;0J))`==F =+6{% %@w1@ ! @  1Ab  bc B@aB "@x@A$AEc*+B =jaj @P@,cJJ B@aRB@x@A ARBB@`Eba F,,AR"R{j="@e@GYp #`B`̠+BbCb½ B@a$B@x` =Aj @`EVaj2 j--Aj =j!=%-(%@0@~$( @(@BB J B@aB@$@x@AAJ E J..`= =]Fzc =@߀{%  < @ۀ@ ) @ .Abxڀb b륱 B@`B@x@A$AEcb //B =)"=jj"@@ m!j @JJA#c B@aRB@x@A AREPR00AR =RX=F!@S@yG"ӔP`"Q=sBbbb j B@jB$X@x@A$AjEp j1#1 =j=%pj%@T@ B  B@JB@@x@AAJEǀ 22`= =F,! =@̀{%  <" @1ɀ@ y! @!`\y A#bȀbǝ! b $ B@`B@x@A$A%E~bc34B& =)C`=^%'@ B@,(JyAJA# @R) B@aRB@x@A AR*E 55AR+ =RA$R <, @~ c#K]œ ]wB-bb j. B@jB@x@A$Aj/E, j66Aj0 =)=( 1@@ n$( @"AJ2BRBB3 B@JB@x@AAJ4Eqbw J77`=5=F 6 =w{% 7@r@ m) @+c  A8b5b bc9 B@aB@x@A$A:E -a89B; =jN="<@@,=JJA#> B@aRB@x@A AR?E-aR ::AR@ =R׫=R <A @9@ G3uY"ѣKVu6*Bbb@(jC B@jBy @x@A$AjDE_j;; FE =)@,gJZJ >h B@aRB@x@A ARiEp@Yg DDARj =R+="k@@o!oSw 1`w pAlbbfm B@jB@x@j =Ajn @`Eb fEEAjo = ?`==%-(%p@䵀@ rqBCBJ) r B@B@x@AAJsE~nJFF`=t=Fu =vt{%  <v @o@ ! @]Awbb bx B@aB @x@A$AyE*GDB%z =).=^3{@-@,|J},JA#(} B@aRBx B @x@A AR~E倈 FHB!R =R;=%p@@ c"ќ`Su~`n!bbj B@jB !@x@A$AjEIIAj =j=j%-j%@@ $( @"@BB^B B@JB@x@AAJE\aJ> JJ`==Fh = Àb{%  < @]@F t b5b! 륱 B@`B "@x@A$AEaKLB =)P؀="@ր@,cJJ n B@aRB@x@A ARE-aR MMAR =R門=R < @A@  s` ` Yahobb½ B@jB@x@A$AjEJajF jNNAj =)bgb½? B@jB@x@A$Aj@E jttAjA =j+$=j%p(%B@#@$CB"B JD B@JBc@x@AAJEE: uu`=F=FG =.{%p"H@p݀@ @': @K }AIb ! bcJ B@aBc@x@A$AKE bvwBL =jW `= M@KV@,NJUJ O B@aRB@x@A ARPEa xxARQ =R{=R"R@@HÔPB_(+FBSb:b½T B@jB@x@A$AjUEUʀ jyyAjV =j̀=j W@0@~$( @ūcXB̀ B$BcY B@JB@x@AAJZEۅzz`=[=]F\ =̋{F% ]@@, [B^bwb 륱_ B@aB@x@A$A`EbAa{|Ba =jx`=j^"b@~,cJEJ d B@aRB@x@A AReEpy F}}ARf =R=FR%g@x@yHp-Td` yBhb亀b ji B@jBB@x@A$AjjEsb~~Ajk =jw=j%-(+cl@v@ mBBBn B@JB@2ay@x@AAJoE}/J`=p=F$Xq =@q5{% "r@0@ y! @c gAsbb ! b t B@`B@x@A$AuEcBv =j*a"w@@, xJJA#y B@aRB@x@A ARzEba AR{ =Ri=R"|@!e@yH#~aB}bdb> 4~ B@jB@x@A$AjEj=MA =!=( @t @ r$( @(AJBB J Bc B@JB@a@x@AAJE `==Fc =߀{%  +@@3b@Yڀ@ c !'b ! c B@aB@x@A$AEbcB =A6`T`=^"@,S@ m82*Q JRJA# B@B{,@x@A ARE a@Y C$F =RQ=R%@@ 37EV/9xJBbb½ B@jB@x@A$AjE:ǀ2 j@!j =jʀ=(%p(#@@~B~ɀ B$ F B@JB@x@AAJE`==AFc = À{% "@@ Ab\b ! , B@`B 'Z,@x@A$AEG>aB =jm=j%pj%@@,J:J (> B@aRB@x@A ARETaR FO$F =R="@a@ C@%@~?B B@ B@JB C@x@AAJAEπ `=B=NF/C =Հ{% %D@ р@$X "AEbiЀb! F B@aBc@x@A$AGET8b BH =j㏀=^+cI@D@ m=$^BJJJA#jK B@aRB@x@A ARLEF9RARM =RN=!N@I@ c1Kf tE4anBOb[b jcP B@jBB@x@A$AjQEa:jAjR =j= S@5@ TB$U B@JB@@x@AAJVE轀 `=W=|w   AX = )À{% Y@"@ $F @ |Zbb[ B@aB @x@A$A\Eoy;bAj] =j9<`=^%^@8@,_Jn7JA#*R` B@aRB@x@A ARaE| ARb =R.=R(oc@@ Ekfa dbb je B@jB@x@A$AjfE=b jAjg =j=j h@㮀@~iBCB) j B@JB@x@AAJkEg>J`=l= zFm =zm{%pn@h@ ! @+c 8 ob&b ! bp B@aB@x@A$AqE#?aBr =j;="s@@,tJJA#u B@aRB@x@A ARvE@aR FARw =R=R%x@@ 1$t%~acSKcybbĩz B@jBc@x@A$Aj{EUAjAj| = -Y=(%-(1}@X@ $( @(@B~BWB J$B B@B@x@AAJE+BaJ+U`==F = {% "@j@ c ǡAbˡ 륱 B@aB@x@A$AÈB = `ՌCaj"@5@,JJ B@B@x@A ARECDa AR =R^K=R"@F@ 7//9/cwBb(b j B@jB@x@A$AjEF jAj =jEa"@'@ƹBB B@JB@x@AAJEͺ J`==VBaJty ={% `3b@@ ! @c XBbib b B@aB@x@A$AETvFaj B =j{=j @hy@,JxJA#c B@aRBy* @x@A ARE1GRAR =R9=R%@4@ I"t/VBbSb  B@jB{y@x@A$AjEa Aj =j=j*(+c@H@ $( @(@BB B@JB@x@AAJEH`==F =خ{"@%@, Bbb c B@aB@x@A$AEodIaB =j$J`="@6#@ 5,%yJ"J R `JR- B@aRB@x@A ARE| F9 =R,=!F"@ހ@ o @o I"t/ ayb݀b j B@jB@x@A$AjEKb jAj = =jj%@噀@~nBGBc B@B@x@AAJERLaJ} J`== eF, =X{% @S@ mb&b! ^ B@aB@x@A$AEMaB =j9΀="@̀@,cJJA#c B@aRB@x@A ARENaR AR =RŒ= @@I#"6t"uaDbb½ B@jBW@x@A$AjE@OjAj =j-D= @C@ BBB g B@JB$X@x@AAJE+ 0@==F =Pa% @h ) @+c BMBb  B@aB * @x@A$AjE,B =jZ=^+c@@,J&J B@aRB@x@A ARE9sQb AR =Rz=R+c@5v@WI3腌 Bbub@( B@jBy * @x@A$AjE.Raj jAj =jH2=j%-(+c@1@BBB$ B@JBc@x@AAJEF J`==N =2{"@@ W! j+Ab  bXF B@aB@x@A$AEͥSbB =jeT`=j @Td@,JcJ > B@aRB@x@A AREUaAR = $=FR%@@ cC"0tt1{ bVb½ B@B@x@A$AjEa؀ fAj =jۀ=%-@B@ r$( @(DBڀJ B@JB@?&@x@AAJEV`==iF|3 S 4 fܙ{% "@#@@atE c ( bb 륱 B@aB`x@9 A @ nOWAj =B`= T=^"@R@,JQJ  B@Bx"vB k @x@A AR E XRAR = p= @ @ yS1@vv"t  bu b  B@Bc@x@ =Aj @`E| Aj =jɀ=j%p(+c@R@ BȀ B B@aB@x@AAJEYb@Y J`== P| ={% %@@@ ! @+c b ^! b  B@aBy@x@A$AE=ZaB = `="@ @,cJxJ rȭ B@B@x@A ARE[aR AR =RC= @@ ct1{1Pvv"D!b bj" B@jB@x@A$Aj#Ep\ajF jAj$ =js= %@@ $( @(AJ&BjrS3$B' B@JB@٥kBc@x@AAJ(E+]aJ J`=)=%>|* =@1{% +@,@ 'A,b@be! c- B@`B@x@A$A.E+B/ = `X^a^"0@@,1J&J 2 B@B@x@A AR3E8^_a AR4 =Re=R(o5@Aa@ s&6"B6b`b f7 B@jBy@x@A$Aj8E`j=M/9 =jG=(%-(+c:@@ ;BB, y< B@JB@@x@AAJ=EFՀ `=>=ǤFy? =@6ۀ{% "@@ր@ ! @c 4PAAb  bB B@`Bc@x@A$ACE͐abt  D =jl=j E@͓@ m2 @FJ9J G B@aRB@x@A ARHESLbaR RARI =RS="J@[O@ y"땂ddayKbNb@$jL B@jBJ@x@A$AjMEcjAjN =jf =%-(%O@ @<$( @(@BPB"B J)BQ B@JB@x@AAJREaÀ `=S=iyT =Mɀ{% %U@Ā@ y$F @ GyVb ! b륱W B@aBc@x@A$AXE~dbcBY =j?e`=j%pj"Z@r=@,[Jn@Q :kb  cl B@`B  Q@x@A$AmE(haBn =j=$j%o@@ m' @cpJ|J!q B@aRB@x@A ARrEiaR FARs =RA=R"t@@ Ta:Dub b jv B@jB~ @x@A$AjwE[jaj jAjx =j^=j%-%y@]@~' @"@BzB]B)B{ B@JB@ ,@x@AAJ|EkJ`=}=%)|@~ =@{% "@@ $F @+c mAb@b ! b륱 B@`B@x@A$AE+ҀB = `Zla%pj"@@,,J&J  B@B@x@A ARE8Ima FAR =RP=R"@(L@ c|$>0D+ɂbKbĩ B@jB@@x@A$AjEnjAj =jG=j%-%@@ BB$ c B@JB@x@AAJEF,`==ǤF =>ƀ{% "@@  YAb ! c B@aB (c@x@A$AE{ob 5^ B =j;p`="@W:@ '$cJ9J F B@aRBc@x@A ARE-AR =R=R"@@ 1FkbT|aBbNb j B@jB@x@A$AjEaqbAX Aj =@局=(%-(%@B@ Bµ$  B@B@x@AAJEiraJ J-@==l|Fy =o{% "@#k@ $F @c KAbjb b B@aB@x@A$AjEn%sB =j *=j%pj+c@n(@,J'JA#{R B@aRB; @x@A ARE aR =R="@@ B u-5Kea` b]b j B@jB@x@A$AjE{taj =j=j%-%@o@ nBОB B@JB@x@AAJEXuJe= &t =]{F% %@8Y@ aeb ^! B@aBy@x@A$AEvab =jӀ=j @Ҁ@,J|ѠJA# B@aRB@x@A AREwaR aR =RP=FR"@@ @o9D?1aDbb½ B@jB@x@A$AjEFxj  aj =jI= @@$BaH J$ c B@JBc@x@AAJEyJ  @==%| ={% @@ ?GBb@b c B@aBL6@x@A$AjE+  Kc = ``}zaj$j+c@{@,J-J B@B@x@A ARE84{a   AR = ;=R%@P7@ % C!"0 >Bb6b B@B@x@A$AjE jaj =j7=j%-+c@@ cBB B@JB@x@AAJEE|e=ǤFw =>{% "@@ ,Ab ! B@aB@x@A$AEf}a,D^ = `&~`=j @C%@,J$J B@Bc@x@A ARE݀y FaR =Rs=FR"@@JtQ$@v"EaQBb:b jc B@jBF@x@A$AjE`b jaj =j=j @L@~B B$ c B@JB@x@AAJETJe=hgF$X =Z{% @!V@F hv/G?(BbUb c B@aB,@x@A$Ag  EnE =j="@v@,JJ@ B@a B$X@x` =AR @`E FaR =RӀ=R%@π@cJtQ$Pv"&`̠bbm΀bĩ B@a$By@x@A$Aj E{B =j=(%-(+c @]@  B  B@JB@x@AAJECJe= / =H{% "@>D@ ! @ mhb ! b  B@aB@x@A$AE1 ^b =jajK@@,JJA# B@aRBz B* @x@A ARE RDF =R="@@ !#"dd" Zvb{b j B@jBy !j@x@A$AjEub jaj =jy= !@vx@$"BwB c# B@JB@x@AAJ$E1J@=%=%Fy& = 7{% '@W2@ ,J v(b  c) B@aBy@x@A$Aj*Et b+ =jD=^%,@@,-JJA#n,. B@aRB{@x@A AR/E*aR0 =Rկ=%p1@.@ c3t!%~4bYEJ[2bb j3 B@jB @x@A$Aj4Ecaj Iaj5 =j1g=j%-j+c6@f@ ) @"@B7BeB)B8 B@JB@x@AAJ9E8?@T   e:=@c; = n8%{% +c<@{ @ c h=b > B@`B@x@A$A?Eڀ!"b@ =ja"A@U@,BJJ C B@aRB|@x@A ARDEQa ##KcE =RwY= F@T@,JCt'%~"Q`~DfGb4b½H B@jBnb@x@A$AjIES aj@Y j$$AjJ =j=j%-(%K@@@ LB BM B@JB@x@AAJNE J%%@=O=ZۄF~yP = À΀{F Q@ʀ@ NARbuɀb cS B@`B@x@A$AjTE`b&'bU =jD`=j^(fV@B@,WJSJA#FcX B@aRB@x@A ARYEn@Y ((aRZ =R$R(o[@z~JSE%1@:B\bbj] B@jB@x@A$Aj^E, j))aj_ =jx="`@ֹ@ aB4BJb B@JB@x@AAJcE{rbw J**ed=Fe =kx{% :"f@s@ @ lBgbb b h B@aBy@x@A$AiE.a+,bj =j6="k@@@S2%lJJA#Jm B@aRB@x@A ARnEaR --aRo =@Ϭ=R"p@'@ ,c$ V4%,Bqbbr B@B@x@A$AjsE`j..ajt =jd=( cu@pc@ rvBbB Jw B@JB@8C@x@AAJxEaJ //ey=Fz =@"{% {@Y@ B$F @+c T|b ! b } B@`B !@x@A$A~E׀01b =jחaj%pj+c@:@ m!j @JJ  B@aRB; @x@A ARENa 22aR =ReV="@Q@ csC!@e NEb-b½ B@jB; @x@A$AjE8 ajf33Q =j =j @@~ `@"AJBx B$ B B@JB@x@AAJEŀ 44e=?؄F]L =ˀ{F% F(@ƀ@ ! @.W UAbZb! by B@aBc@x@A$AEEb 55b =j؅=%pj"@9@,JJA# B@aRB,@x@ =AR @`E<R66aR =R}D=R"@?@ C"% Kq0 `̠~BbDb j B@a$B @x@A$AjER 77aj =j=%-6@6@~$( @"@BB B$B B@JB@x@AAJEٳ88e=Fy ={% "@@; QAbub 륱 B@aBc@x@A$AE`oac9:H =j/`=^"@-@,J_JA# B@aRB@x@A AREm@Y F;;aR =R#=R"@}@ q4QCq S"":S"@Bbbĩ B@jBW@x@A$AjEb j<?b =j8ـ="@׀@,JJ (> B@aRB@x@A AREaR F@@aR =R=R"@#@FJC"Cq"qVu6u7a%Bbb@$j B@jB @x@A$AjEKjAAHo =jO=( @nN@ BMB J$  B@JB@x@AAJEJBBe=F$X = {% @S@ y`@+c Bb ^! XF B@aB @x 9A$A @E€CDb =jւaj+c@6@,JJ@ @R B@a B@x 9A AR @`E9a EEaR =R[A=R%@<@ c8$``$p%Bb%b½af B@a$Bx@x@A$AjE7 jFFaj =j="@@ BgJ$ B@JB@x@AAJEb JGGe=?ÄFc ={% @@ ! @( a,BbZb b B@aB@x@A$AEElHHb =jp=j @Qo@,JnJA# B@aRBy@x@A ARE'RIIaR =R/="@*@!!L9NaBbLb  B@jB,@x@A$AjER fJJaj = =%-(1@-@ $( @(@BB倃Bc B@B@x@AAJEٞKKe=F =Ť{% (@@ y c Abub 륱 B@aBc@x@A$AE`ZaLMNW =j`=j$j"@@,J^JA#h  aRB@x@A AREmѠ@X FNNaR =R%ـ=R"@Ԁ@ ~$F!9: 9K:Q 8ABbӠb@&b B@jB| @x@A$AjEb jOOaj =j=j%-%@ڏ@~j @"@B B<Bc B@JB@x@AAJ E{HaJ} JPPe =F, = ÀgN{% "@I@ y" @ Abb! b륱 B@`B@x@A$AEaQRb =j"Ā="@€@,JJA#F B@aRBc@x@A ARE{aR SSaR =RÂ=%p@~@  -L\y<a;{Bb}b½ B@jB@@x@A$AjE6jTTaj =j:=%pj%@h9@ $( @"@BB8BBc B@JB@& @x@AAJ E UUe!=F5n" =@{% %#@Y@ / A$b % B@`B ! @x@A$A&EVVb' =j7=^"(@@,)JJ * B@aRB@x@A AR+E*iaR WWaR, =Rp=R"-@.l@ :; < = aaB.bkb j/ B@jB@x@A$Aj0E$aj jXXaj1 =j4(=(%-(%2@'@3B&BB$ 4 B@JBc@x@AAJ5E7 JYYe6=?7 =+{ 8@r@ ! @c A9b  by: B@aB@x@A$A;EbZ[b< =j[`=j =@MZ@ 2 @ ! !% l>JYJ j? B@aRB@x@A AR@Ea \\aRA =R~="B@@K>%? @ A BCbGb½D B@jB@x@A$AjEER΀ j]]ajF =jр=%-G@7@ r$( @(AJHBЀJI B@JB@)c@x@AAJJEى^^@=K=ZFx3#xL =@͏{% (M@@ (ANbub 륱O B@`B@x@A$AjPE_Ea_`bQ =j`=j%pj"R@@ mSJZJ 4` T B@aRB* @x@A ARUEm@Y`~ FaaaRV =RĀ="W@u@ B%C D E  B2BXbᾀbjY B@jB@x@A$AjZEwb2 bbY[ =j|{=%p+c\@z@~]B8B J^ B@JBc@x@[ =AJ_ @Ez3aJ Jcce`=Fa =o9{% %b@4@ ! @c Acbb! b d B@abB@x@A$AeEdebf =j,a g@@,hJJ (Ri B@aRB@x@A ARjEfa ffaRk =Rm=R"l@i@ #F%GC"`QCGaBmb{hb n B@jB@x@A$AjoE!jggajp =j%=j q@z$@r$( @(AJrB#B$Bs B@JB@x@AAJtE hheu=F* v = {% w@Xހ@ ) @+c  Axb ! bcy B@aB@x@A$AzEiib{ =j9= |@@,}JJA#~ B@aRBy@x@A ARE)TRjjaR =R[=R%@)W@ 3"C"4~5BbVb j B@jB ,@x@A$AjEjkkaj =j<=(%-(+c@@ BB$  B@JB@x@AAJE7 lle=? ='р{% "@t̀@ Ab  B@aB@x@A$AEbmnb =jF`=^(f@LE@,JDJA# B@aRB@x@A ARE ooaR =Rx$R"@@/KC6+g7 8 aOBb;b j B@jB@x@A$AjER jppaj =j⼀=(%-(%@=@ kB B@JB@x@AAJEtbw Jqq@==ZF =z{ @v@ y c  Abtub y B@aB (,@x@A$AjE_0rrb =j5=j c@g3@,J2J  B@aRB@x@A ARE ssaR =R=%@@ KS9Keu4G5$X|b^bA; B@jBz c@x@A$AjEmb2 jttaj =j=%-@N@~B Jc B@JB@x@AAJEbaJ Juue=Fc =h{% "@/d@ ;lbcb! B@aB "@x@A$AEzav!A = ``ހ= @݀@,J}ܠJ F B@B@x@A AREaR xxaR =R:=R"@@ c1`p-@}Hobb½ B@jB~ @x@A$AjEQaj jyyaj =jT=j @S@~BNB$ F B@JB@x@AAJE Jzze=| =}{% @ @ c DBb1b ! c B@aB "@x@A$AEȀ{|b =jGaG r% @@,JJ  B@aRB{ ;V/ @x@A ARE)?a F}}aR =RF=%@AB@ s} uvu /$XbAbĩ B@jB @x@A$AjEQ~~! =j8=j%-(1@@ BB$  B@JB@x@AAJE7bxe=F ='{% "@r@  @yK $Xbס ^! b B@aBc@x@A$AEqa* b =j1`="@L0@,cJ/J n B@aRBc@x@A ARE aR =Rv= @@ w"Kf$``$FNb?b j B@jB@x@A$AjERbF jaj =j⧀=%-(%@?@ B$  B@JB@@x@AAJE_aJ Je=YrF~ =e{% %@a@ c_Tbt`b  B@aBy@x 9A$A @E_ab =jۀ=^K@ـ@,JNJ@ B@a BL6@x@A ARElaR ,Y) =R=R"@u@ pKYw"`Dib j B@jB{c@x@A$AjEMj"`!j = sQ=(%-(%@P@ nB7B J ` B@B@,B@x@AAJEz Je=F =@f{% " @ @c 7A bb  B@`Bc@x@A$A Eŀ'" =j9a^%@@#P"J'%JJ IJc B@aRB@x@A AREbo B@B@x@A$AjE jaj =j=( @{@$BB J B@JB@C@x@AAJEe=FB =@{% @X@ ڢB b ! ! B@`B !@x@A$A"Enacb# =j.`=j%pj%$@!-@ m%J,J & B@aRB@x@A AR'E FE( =Rh=")@@o!"[ aIB*b,b j+ B@jB@x@A$Aj,E6b jAj- = =j .@@ c/Br$ c0 B@B'$c@x@AAJ1E\Je2=>oF3 =@b{F% F(4@]@c ,5bYb c6 B@`B@x@A$A7EDacb8 =jl؀=j$j%9@ր@,:J;J >; B@aRB@x@A ><EQaR FaR= =R=FR">@a@ 䐂a"7KaVE?b͑bf@ B@jBB@x@A$AjAEJjEB =j\N=%-1C@M@ r `@"@BDBB JE B@JB@x@AAJFE_JeG=FH = ÀO {% "I@@ ) @ aAJb #! b륱K B@`B@x@A$ALEcbM =ja^"N@p@,OJJA#P B@aRB@x@A ARQE8a aRR =R@= S@;@ LL/LRL^MMa|BTbgb@$jU B@jB| o,@x@A$AjVEz2 jajW =j=j(%Xi@`@ YBB$ cZ B@JB@x@AAJ[Ee\=„F{y] = s{F% ^@=@ ! @K A_b ` B@aB@x@A$AaEkaQHb =j+`=j^%c@*@,dJ)JA#Fe B@aRB@x@A ARfEaFaRg =R9=FR(oh@@ @ FMTMM6NFTKBibb½j B@jB@x@A$AjkEaj ajl =j=j%-(%m@@~$( @n"@BnB[Bo B@JB@x@AAJpEYJeq=#lFr =_{%p"s@Z@ c tb>b ! cu B@aBy@x@A$AvE)abw =jFՀ="x@Ӏ@@Sf!j$^yyJJA#Jz B@aRB@x@A AR{E6aR FaR| =@㓀=R"}@>@ $F!R90CqSq90an6Df~bbĩ B@B@x@A$AjEGjaj =jEK=(%p@3A@J@ r!j @ťBB J$Bc B@JB@x@AAJEDaJ ${=ŤF = À4 {% "@@ c Ab ! 륱 B@`Bc@x@A$AjEʾKc = `~a^"@Y}@,cJ|J B@B$X@x@A ARE5a aR =R==R"@8@cLCr%r% akcb\b@$ B@jBx@x@A$AjE^@Y jaj =j=j%-(+c@B@B B$  B@JB@١2ay@x@AAJE@==fFc =@ɲ`{"@@c kcbbje! B@`B !,@x@A$AjElha, E =jm=j)%@hk@,JjJ c B@aRBB@x@A ARE#RaR =R+=FR%@&@ KSrK90aWFbWb@$j B@jB$X@x@A$AjEyߠ@Y aj =j=j%-%@c@ nBB B@JBc@x@AAJEe=i|$X = s{F% "@<@ W @W Fb  bXF B@aBc@x@A$AEVaE =j`=j^%@@,JnJ > B@aRB@x@A ARÈ FaR =R?Հ=R"@Ѐ@ L#DKT 8 aJDb b½a B@jB{ oy@x@A$AjEb jaj ==j%-(%@ @~Bk B) Jc B@JB@x@AAJEDJe=#WF$X =J{% "@E@ 5\ @+c yb>b b B@aB@x@A$AE(ab =jL= @@,JJA#o> B@aRB@x@A ARE6waR FaR =R~=R"@Bz@ o @%3%q -0avDbybj B@jB @x@A$AjE2aj jaj =jM6=j%-(%@5@ B B B@JB@x@AAJEC J${=ĤF =8{F% "@@ @Ab ! b B@aBB@x@A$AjEʩbb =jj`=j$j+c@eh@,JgJ  B@aRB@x@A ARE a aR =Rs(=R"@#@ c C"D1@;Bb @==fF =ٝ{% F"@%@ y c 7Bbb c B@aB@x@A$AjElSab =j`=^%@@,JjJA#rc B@aRB,@x@A AREyʠ@Y aR =R!Ҁ=R"@ỳ@ c S389Q0*  .Bb̀bj B@jBc@x@A$AjE@@T jE =j=(%-(+cj@∀@$BDB [,J B@JB@٢oB2ay@x@AAJEAaJ Je=0TFQ  =@{G{% "@B@ c Ab#be!  B@`B !y@x@A$A E cb =j)aj c @@ m' @ JJ  B@aRB@x@A AREta aR =R{="@w@ y c 8d` pBVBbvbA;j B@jB@x@A$/E/jaj =j*3=j @2@ cB1B J$ c B@JB@x@AAJE( e=F = {F% @a@ $F @6 VpBb  B@aB,@x@A$AEbcH =jf`=j%pj+c @>e@,!JdJ F" B@aRB@x@A AR#Ea aR$ =Ra%=FR(o%@ @ sÔ@0.:B&b,b@(j' B@jBB@x@A$Aj(EC٠@Y jB) = +܀=%-+c*@@ R~+B{ۀB, B@B@x@AAJ-Eʔe.=KF/ ={%p"0@@ ! @9 A1bfb bZ2 B@aBy@x@A$A3EPP acb4 =jm `=j^%5@@,6J;JA#7 B@aRB @x@A AR8E^Ǡ@Y FaR9 =R΀=R":@Vʀ@ `:t8* B;bɠbj< B@jB@x@A$Aj=E b2 jaj> =j]=j ?@@ $( @"AJ@BBA B@JB@٢o/l@x@AAJBEk> aJ J@=C=FtD =@XD{% E@?@ c rAFbbe! 륱G B@`B@x@A$AjHEbI =j# aj J@@ m!j @KJJA#L B@aRBc@x@A ARMEqa@Y aRN =Rx=F!O@ t@ t @@Pbxsb½Q B@jB@x@A$AjRE,aj jajS =j0=j%pj+cT@m/@~UB.B$ V B@JBc@x@AAJWE JeX=FBY ={% (Z@J@ B! @c I>A[b ! bc\ B@aB@x@A$A]Ebb^ =jc`="_@#b@,`JaJ >a B@aRBc@x@A ARbEa Oc =RF"=R"d@@ , b c8XBebb½f B@jB@x@A$AjgE(ր jajh =jـ=`Ze-(%i@@~@$(ncjBt؀ B$Bk B@JB@x@AAJlEb Jem=0Fn ={% "o@쒀@ y) @6 ApbKb! b q B@aBy@x@A$ArE5MaL6bs =jk `=^"t@ @,cuJ8J v B@aRB@x@A ARwECĠ@Y aRx =Rˀ=R"y@3ǀ@ 11+fK<~@Bzbƀb½{ B@jB$X@x@A$Aj|Eb jaj} =jV=j%-(%~@@ BB$ c B@JB@x@AAJEP;Je=ѤFc =8A{"@<@ c kb ! B@aB,@x@A$AEcb =ja%@N@,JJA#(R B@aRB@x@A AREma FaR =Ru=R%@p@ pC9ӔP9pXADb\bj B@jB @x@A$AjEk)jaj =j,=H@1c@O@$B+ J$ B@JBc@x@AAJE e=sF ={% `3b@,@, Bbb Z B@aB @x@A$AEybb =j``=j @^@,JgJA# B@aRB]L@x@A AREa aR =R4="@@ H9"z9 Ea Bbb B@jB@x@A$AjE jaj =jր=%-(+c@Հ@$BIB J B@JB@x@AAJEe=F ={% (@Ώ@!, @K $Ab/b b B@aB@x@A$AEJayb =jP `= @@,JJA# B@aRB@x@A ARE( FaR =RȀ=R"@0Ā@  D9t!/4aϛBbÀb B@jB@x@A$AjE|!b jaj =j6=j @@ $( @.`AJB~BJ B@JB@x@AAJE58"Je=F|B =%>{% @p9@ kiAb  c B@aB@x@A$AEcb =j#a @C@,yJJA# B@aRB@x@A AREj$a FaR =Rgr=R%@m@ * %Te]|yb-b½ B@jB@x@A$AjEP&%j] =j)=j%p(+c@4@ B( B B@JB@x@AAJEဈ> e=XF ={% "@ @ y! @c |ybsb! b B@aB,@x@A$AE]&byb =j]'`="@[@,cJTJA# B@aRBw Bnb@x@A AREk(a!aR =R= @k@M%3 B@aRB@x@A AR4E񺀈  aR5 =R€="6@@  C0:6aB7kab½8 B@jB oc@x@A$Aj9Exv7b faj: =jy= ;@J@ r$( @"AJ<BxJ$B= B@JB@x@AAJ>E18Je?=DF@ =7{% F%A@;3@ Sf) @+c 2qABb2b bcC B@aB "c@x@A$ADE퀈cQE =j9a^"F@@,GJ|J H B@aRB@@x@A ARIEd:a FaRJ =RJl="K@g@ cSH% `єaBLkbfM B@jB*@x@A$AjNE ;aj2 jajO =j#=j%-(6P@"@ QBRBR B@JB@x@AAJSE JeT=!FFU = À{F% "V@܀@ ! @+c wAWbaFaR_ =R=FR"`@=@ cPP%@H| Ӑ8Babbb B@jB@x@A$>cEɀ fajd =;̀=j e@̀@~$( @"AJfBB$Bcg B@JB@)@x@AAJhEB?b Jei=×F$Xj =@2{% k@~@ W c ,Alb ! cm B@`B@x@A$AnE@@abo =jA`="p@X~, qJJA#cr B@aRB@x@A ARsEַaRt =R=R%u@ں@ $F%os*ps\aBvbFb½cw B@jB@x@A$AjxE]sBb fajy =jv=(%-(+cz@G@ r!j @ť{BuJ$B| B@JBc@x@AAJ}E.CaJ Je~=eA|* =4{% "@ 0@c FAb/b 륱 B@aB@x@A$AEjꀈc b =Da^"@@ '$^yJiJ B@aRB5n@x@A ARExaEa !!aR =R,i=R"@d@5nMt|ExaBbcb@$ B@jB{ @x@A$AjEFj""aj =j =j%-(%@@ B?B B@JB @x@AAJE؀ ##e= Fv =iހ{"@ـ@  $F%N6 NAb!bx B@aBF@x@A$AE Gb$%b =j4TH`=j%p%@R@,JJA#Fc B@aRB@x@A ARE Ia &&aR =RI`=FR%@)@ \|}a(ak I`½ B@jB5n@x@A$AjEI j''aj =j$ʀ= @I`~$BȀB J B@JBc@x@AAJE'J` J((e=F = À{%pF"@d@ @': @( 4b ! b c B@`B,@x@A$AE=Ka)*b =j=j^%@<@,JJA# B@aRB@x@A ARELaR ++aR =R`="@@ $\$R: aHok'b½ B@jB5n@x@A$AjEBpMj,,aj =s= @@$Br J$ 5n B@JB@,@x@AAJE+NJ--e=J>|c =@1{% @-@  c `Bbd,b / B@`B W!,@x@A$AEO瀈c./b =jpOaj @ҥ@ m!j @J>J c B@aRB@x@A ARE]^Pa 00aR =Rf=R(o@ia@ !(o!Ro00acsBk`b j B@jB @x@A$AjEQaj j11aj =js=j%-(1@@~ƹ!j @` (@BB/B B@JBc@x@AAJEj J22e=F/ =bۀ{% "@ր@ B! @ǁAbb ! b륱 B@aB@x@A$AERbc34b =jQS`="@|O@,JNJ(> B@aRB@x@A ARETa 55aR =R=%p@ @ $X8pK$`$`$paBbz bj` B@jB/@x@A$AjE j66aj =jǀ=j%pj%@pƀ@ $( @"@BBB$Bc B@JB@x@AAJE U77e=F ={% %@I@ $X gAb ! 륱 B@aB (c@x@A$AE:Vac89b =!b="@@,JJ c B@aRBc@x@A AREWaR F::aR =RM=R"@@ z Bo 5E qaBbb@$ B@jBx c@x@A$AjE'mXaj j;;aj =jp=(%-(%@@~Bgo J$  B@JB@x@AAJE(YaJ J<b =jbZaj(f@â@,J/JA"Fc B@aRB@x@A AREB[[a@Y ??aR =Rb=R"@N^@ cDրb m]VB b]b  B@jB@x@A$Aj E\aj j@@aj =jD=" @@ BB  B@JB@x@AAJEOҀ JAAe=ФF = ?؀{% @Ӏ@ c VBb   B@aB (c@x@A$AE֍]bBCb =jN^`=j @iL@, JKJrR B@aRB{5n@x@A ARE_a DDaR =R ="@@ cR8I9C :bIBbcbA;j B@jBy c@x@A$Aj Ej jEEaj! =jÀ=%-(C"@N@$#B€J) c$ B@JB@١B!Ky@x@AAJ%E{`b JFFe&=rFb' =@ف{% ((@+}@ , /O*)b|be! c* B@`BQ@x@A$A+Ew7aacGHb, =j=j -@@ my.JbJA#/ B@aRB@x@A AR0EbaR IIaR1 =R+="2@@, NPCA%L%Ta//3bb½4 B@jB@x@A$Aj5E jcjJJaj6 =jm=j%-(%7@l@ c8BPB$ 9 B@JB@@x@AAJ:E%dJKK@=;=8/< =@+{F% %=@&@, />b.b ! ? B@`Bc@x@A$Aj@ELMbA =jKeaj B@@,CJJ D B@aRB@x@A AREE&Xfa NNaRF =R_=R"G@[@c N:Ӕ@@@,1 HbZbA;jI B@jB/@x@A$AjJEgjOOajK =j1=j%-(%L@@ MBB$ N B@JB@x@AAJOE4 PPeP=FQ =Հ{% "R@kЀ@c Sb ȥ! T B@aB@x@A$AUEhbQRbV =jJi`= W@MI@,XJHJA#FY B@aRB@x@A ARZEja SSaR[ =Rj =R"\@@ #:u:%+gX`nD]b8b@"j^ B@jBz@x@A$Aj_EO@Y jTTaj` =j=j%- @3Aa@3@~bB B$ c B@JB@c@x@AAJdExk@  JUUee=WFf =@~{% "g@z@ y/ @y+c IAhbqybe! bi B@`B !@x@A$AjE\4lacVWbk =j="l@@@Sm!j mJKJ Jn B@aRB@x@A ARoEjmaR+ XXaRp =@=R"q@z@ 31 IVTD yXBrb歀b js B@B} @x@A$AjtEfnaj jYYaju =jpj=(%-(+cv@i@~wB0B$ x B@JB@C!K@x@AAJyEw"oJZZez=FF{ =@g({% "|@#@'L @+c A}bb ! b~ B@`B@x@A$AE݀c[\b =j pa^K@m@ mJٛJ(> B@aRB@x@A ARE Uqa F]]aR =R\=R"@X@,NC-L6d`IBbWbj B@jBQ@x@A$AjErj^^aj =j"=(%-(%@@ rBB J$  B@JBc@x@AAJÈM__e=ބFc =Ҁ{% "@X̀@,N zO*b  B@aB 'Zc@x@A$AEsbc`ab =jGt`=^%@*F@,JEJ j B@aRB@@x@A ARE bbaR =RXu$R"@@ c S89:"]I"5nn!b!b j B@jB~ c@x@A$AjE42 jccaj =j=(%-(%@@~ƹBx J B@JB@x@AAJEuvbw Jdde=;F@ ={{F% "@v@ y! @c Bt bVb! b B@aBc@x@A$AEA1waefb =jx=0j%@@,JDJA# B@aRB@x@A ARENxaR ggaR =R=R"@W@! @cTSr"~}aDbêb  B@jBW@x@A$AjEcyjhhaj =jag= @f@ $( @"AJBB,Bc B@JB@x@AAJE\zJiie=ݤF =P%{% F"@ @F 6Ab  c B@aBc@x@A$AEڀjkb =j{aj$j"@v@@SfJᘀJ  B@aRB} @x@A AREQ|a llaR =@Y=R"@T@ cs]A \m\tBbdb½ B@B@@x@A$AjEw }jmmaj =j=j%-+c@d@ BB)  B@JB@x@AAJEȀ} nne=ۄF =΀{% "@:ʀ@ , !Abɀb!  B@aB/@x@A$AE~bcopb =jD`=%@C@,cJBJA# B@ B@@x@A ARE qqaR =R=aR"@~ є% yG ]L$6bb j B@jB@x@A$AjE, jrraj =j=%-(%@김@~BQB  B@JB@2ay@x@AAJErbw Jsse= F/ = x{% "@s@ W) @W+c Ab;b B@`B@x@A$AE&.actub =jV=^%@@,J%JA# `- B@aRB@x@A ARE3aR vvaR =R߬=R"@;@ \x]|}")avfBbb½`j* B@jB@x 9A$Aj @`E`jwwA =jBd=j%-(%@c@  `@"@BBB B B@aB @x@AAJEAJxxe=¤Fz@ =@1"{%p"@}@ ! @?m ҔAb ! bc B@`B@x@A$AE׀yzb =j藆a"@J@ mc5nmJ  B@aRB/@x@A ARENa {{aR =RyV=R"@Q@ Q *$``/`aVBbEb½a B@jBF@x@A$AjE\ j|8% =j =H@e-(% @:@ $(% B J$B B@JB @x@AAJ E }}e =d؄Fy =@ˀ{% "@ǀ@ $F @+c 'Lb~ƀb b륱 B@`B@x@A$AEibt~b =jA`=j"@?@,J\JA# B@aRB@x@A AREw aR =R'$R"@~ B@$pT+_$ ё1 a`2zbb j B@jB@x@A$AjE, jaj =j="@䶀@ƹBEBB B@JB@x@AAJ!Eobw Je"= F# =pu{% $@p@ ĕ'%b b & B@aB@x@A$A'E +acb( = `+=j )@@,*JJ >+ B@B@x@A AR,EaR aR- =Rͩ=%.@$@ y~ :e0au/bb½0 B@jB@x@A$Aj1E]jaj2 =j/a=*(#3@`@ r4B_B J5 B@JB@x@AAJ6E&Je7=F8 ={% "9@c@  c A:b ! ; B@aB - @x@A$A<EԀyb= =j͔aj >@/@,?JJ @ B@aRB2B @' @x@A ARAEKa aRB =R^S=R"C@N@ KWy% &%  BDb"b½E B@ABB@x@A$AjFEAaj2 jajG =j =j H@$@~IB J$ }J B@JB@$y@x@AAJKE JeL=HՄF{M =@Ȁ{% N@Ā@ ! @6 2BObcÀb b,P B@`B@x@A$AQEN~bbR =jk>`=j S@<@ m' @TJ9J U B@aRBc@x@A ARVE[@Y aRW =R=F!X@l@ $@q y%@yBYbb@%ĩZ B@jB@x@W =>[ @`EⰖbjaj\ =jr=j%-j+c]@ͳ@~$( @4@B^B.B_ B@aBc@x@AAJ`EilJea=Fb =]r{H O8P% (c@m@  |Adbb ! 륱e B@aB@x@A$AfE'bg =j,="h@+@,iJp*J j B@aRB<Bc@x@A ARkEv aRl =R)=R"m@@ !$F%o+W$P~ Bnbb jo B@jB@x@A$AjpEajq =jy=(%p(%r@֡@ !j @ťsB9B J$Bct B@JB@c@x@AAJuEZJev=(w   >w = )t`{% "x@[@ y! @iAyb b b륱z B@aBy@x@A$A{E ab| = `Bր=^"}@Ԁ@ mc~J J 4?"  B@B|@x@A AREaR aR =R=R"@@,O "M 1Bbbj B@jB@x@A$AjEHjaj =jL=(%-(%@|K@$BJB J B@JB@b@x@AAJE%Je=F| =@ {% "@a@ $F @ Ab ! b B@`B]L@x@A$AEb =jaj)j%@~@ m!j @J}JA# B@aRB@x@A ARE6a aR =Re>=G@.!!R"@9@cO)1 qKjto!Bb*b j B@jB $X@x@A$AjE@ jaj =j= @@) @K C#BxB$Bc B@JB@x 9AAJ @Eǭ@@@w Je=HF ={H {"% @@ @ Abgb bc B@abB@x@A$Am`ENiacb =ji)`=j^K@'@,J5J > B@a B@x@A ARE[@Y aR =R="@c@ c #zѕng@$Xbba B@jB@x@A$AjE⛤b jaj =jr=%-(%n@О@ rB.BJ B@JB@x@AAJEiWJe=F =Y]{% c@X@ ) @+c hv/G?3$Xbb b B@aB@x@A$Am`DEacb =jӀ=j @rр@,JРJ ( B@aRBy@x@A AREaR FaR =R=R"@@ c3ak\ @c@"n@.Dbabj B@ B@x@A$AjEEaj2 jaj =H=j @Y@ BG B B@JB !,,@x@AAJE aJ D38 J e=| =@O{% @G@ c Bb e! !@Wc B@`B ! @x@A$AEb =j<=j @@,J J  B@aRBz@x@A ARExaR =R=R%@{@ $F!o.!C} " m" 8Bbzb j B@jB 5@A$Aj @`E3ajaj = ?`=#7=j @~6@~!j @.`AJB5B$B B@B@$@x@AAJE% e=-y =@{:% @3b@_@, :Ab ! 륱 B@`Bc@x@A$AEbcb =A6`j`=$jK@?i@,JhJ  B@B@x@A ARE!a aR =Rk)=R%@$@ S"ќ(L'Bb1b½ B@jBW@x@A$AjE@݀ jaj =j=(%-1@@$Bx߀J$  B@JB@x@AAJEǘe=HF]L ={% "@@ ': @1a0# ( Abcb bF B@aB@x@A$AENTat b =jX=j @VW@,JVJA# B@aRB@x@A AREaR RaR =R|="n@@Oc)Ô"u $ RVbHb@&j B@jB@x@A$AjE[ˠ $ jaj =j΀=%-(%@I@ b~b 륱 B@aB@x@A$AEhBacb =j`=j @@,JWJA#F `R/ B@aRBy B@x@A AREv DE FaR =R-=R"@@ ys4p%$ft [Dfbbf B@jBy@x@A$AjEtb jaj =j}x=j%p(%@w@~B9B B@JB@x@AAJE0Je=CFy =x6{% " @1@ A!bb ! ^" B@aB,@x@A$A#E b$ =:aj %@@,&J J (>' B@aRB5n@x@A AR(Ecay FaR) =Rj=H@5 R"*@ f@ y5{"981@z\B+beb,nSjB @x@A$Aj-E@ ?aj. = /""=j /@!@ r0B B$ 1 B@B J!K@x@AAJ2E% e3=FV4 =@{% 5@aۀ@ c  6b ! 7 B@`By@x@A$A8Ebt b9 =jS=":@@ m!j%`# ! !% DF;J J < B@aRB@x@A AR=E2QRaR> = X=R%?@7T@ "1  l5n@bSb$A B@By@x@A$AjBE jajC =j==(%-(CD@@ EBB $ F B@JBc@x@C =AJG @E@ eH=HyI = }{0΀{% "J@zɀ@ ȤAKb L B@`B@x@A$AMEǃb Z%" 8bN =jC`=j%pj3O@UB@,PJAJ RQ B@aRB{@ B@x@A ARRE RA@aRS =@aR"T@~ Tbj B@jBc@x@A$AjE jaj =jI=(%-(%@@ BB$  B@JB@x@AAJE@e=H = À4{% "@|@ ^Ab !  B@`B >"@x@A$AEn@ /b =j.`=^(f@Q-@,J,J c B@aRB@x@A ARE FaR =R=R"@@ cPSr`a9WbPb j B@jB @x@A$AjE[b jaj =j㤀=(%-(%@=@~B J$  B@JB@x@AAJE\aJ Je=boFz/ =b{% "@^@ / Ab}]b!  B@aBc@x@A$AEhab =j؀=j c@׀@@S`@,Jo֠JA#Jc B@aRBy3By 5 A AR @`EuaR-aR =@`=*=R"@@ P$`^`{$`@RBbb  B@B @x@A$AjEJaj faj =jN="@M@ B@Bb$ n`JB@x@AAJEJe=| = Ās {% @@W)a;E! @ |Bbb b B@`By@x@A$AE €b = `"aj @@@SjJJ J B@B@x@A ARE9a FaR =@@="@<@ P+]paa*[b;bf B@B@x@A$AjE jaj =j=%-(!@y@ $( @.`@BBB J B@JB@/l @x@AAJE%b Je=F* =@{% (@b@ $F @ 6Ab ! bc B@`B@x@A$AEkacb =j+`=j @:*@ m!j @cJ)J  B@aRB@x@A ARE aR =Rm=R"@@ %t~a-Bb5b j B@jB@x@A$AjE?b jaj =jġ=%p(%@@~B(B$  B@JB,@x@AAJEYJe=GlF = À_{% "@[@ ! @( 4AbbZb ! b B@`B@x@A$AEMacb =jvՀ=%pj(f@Ӏ@,JDJA# B@aRB@x@A AREZaR FaR =R=R"@j@yP#33a Bb֎bf B@jB @x@A$AjEGjajo ] qK=j%-%@J@ $( @"@BB-B$B B@B@!Kc 5@AAJ @EhJe=F =@{` {% "@@ JAbb ! 륱 B@`B@x@A$A Eタb = `a" @y}@, J|J c B@Bc@x@A ARE5a aR =R== @9@ c3]`"[p aBbp8b B@jBB@x@A$AjE jaj =j =j%-(%@f@ BB$ c B@JB@x@AAJE e=F ={F% %@?@ y! @c 4Ab ! bc B@aB 'Z,@x@A$AEhab =j(`=j^(f!@'@,"Jo&J j# B@aRBv[{By@x@A AR$E߀ FaR% =RS=R"&@@ C[t&A%[B'bb jc( B@jB@x@A$Aj)E$b jaj* =j="+@ @$(n"AJ,BlB$B- B@JB@x@AAJ.EVJe/=,iFc0 =\{% 1@W@ 5\ @+c A2bGb b 3 B@aB,@x@A$A4E2ab5 =jhҀ=^"6@Ѐ@@S#!j%7J5JA#J8 B@aRBc@x@A AR9E?aR,aR: =@퐀=G@a;@G@  S "Cr6U+@z_B<bb = B@Bc@x@A$Aj>EDjaj? =jJH=(%-j+c@@G@ rAB B JB B@JB@1)@x@AAJCEMJeD=ΤFE =@A{% (F@@ RAGb H B@`B@x@A$AIEӻcAJ =j{aj%pj%K@^z@ mLJyJA#M B@aRB@x@A ARNE2a aRO =R:="P@5@ c00%D%auBQbUbR B@jB@x@A$AjSEh2 j BT =j= U@D@~VB JW B@JB@@x@AAJXE@=Y={Fp Z =@߯{% F%[@)@ $F @c 9B\bb b ] B@`B@x@A$Aj^Eueab_ =j%`= `@ $@ m!j @caJx#J b B@aRB@x@A ARcE FaRd =R,=R"e@߀@ $F!Rc B@aRBy@x@A AREǠ@Y FaR = +?π="@ʀ@ c,$Uk"z Dbbf B@B*@x@A$AjE b jaj =j=(%@@ rBQBJ B@JB@x@AAJE>Je=QF =D{% @?@ y! @+c UCAb,b b B@aBc@x@A$AEb =j?aj @@,J J ( B@aRBz@x@A ARE$qaaR =Rx="@0t@ cl`zn{t+:aY1Bbsb joRjB @x@A$AjE,A 2   aj =j+0= @/@~r$( @.`AJB.B J B@JB@,9/@'@x@AAJE1 J!!e=F =@@*{% @o@ nb c Ab ! c B@`B@x@A$AEb"#b =jc`=j @Gb@ m!j @,JaJA# B@aRBc@x@A AREa $$aR =R^"=F!@@ {|`a#JBb&b  B@jB@x@A$AjELրj%%aj =jـ=j%pj+c@7@~B؀ B$  B@JB@@x@AAJEӑ&&e=TF =@×{% 4 @@ y! @c L6bob b B@`By@x@A$AEZM''b =jR="@bP@ mJOJA# B@aRB{c@x@A ARER((aR =R=R"@ @ % "ґ9Ґ61aDbXb j B@jB@x@A$AjEgĀ ))aj =jǀ=(%-(%@<@ $( @ūcBƀ B@JB@@x@AAJp  E**e=F =@{څ{% "@)@ $F @6 )Abb b륱 B@`B@x@A$AEu;a+,b =j=^"@@ m!j$^c JoJA# B@aRB|@x@A AR E aR F--aR =R5=R" @@ Gp% QwBbbA;j B@jBzy@x@A$AjE n j..aj =jq=(%p(%@p@$BEB J B@JB@@x@AAJE) J//e=<| =@|/{% "@*@ ': @6 Ab+b b  B@`BJ@x@A$AE01b =jM ajj%@@ mJJA# B@aRB@x@A AR E$\ a 22aR! =Rc=""@0_@y Q"+d21/3B#b^b j$ B@jB@x@A$Aj%Eaj j33aj& =jB= '@@ $( @"AJ(BBB) B@JB @x@AAJ*E1 J44e+=F, =@ـ{% -@iԀ@ y$F @ 8A.b  bc/ B@`Bc@x@A$A0E55b1 =jf=^"2@ȑ@,3J4JA# f=Rt4 B@aRB@x@A AR5E?JaR 66aR6 =RQ=(o7@?M@QӔ @t ‘aÐaB8bLb½9 B@jB@x@A$Aj:Eaj j77aj; =j= =j%p(+c<@@~=BB> B@JB@@x@AAJ?EL J88e@=ThA =@<ǀ{% "B@€@ ! @ IACb ! b^D B@`B@x@A$AEE|b9:bF =j=`="G@j;@@Sm'$HJ:JA#JI B@aRB@x@A ARJE ;;aRK =@}=!L@@ c#2/ґ"aBMbDb½N B@B$X@x@A$AjOEgb j< J==eU=o}FyV =p{% W@#l@ B$F @+c u7AXbkb! b륱Y B@aBc@x@A$AZEt&a/>?b[ =j="\@@ !j$]JkJA#^ B@aRBz@x@A AR_EaR @@aR` =RC=R(oa@@ $F!R(o3"DTbbbb½c B@jB@x@A$AjdE YajF jAAaje =j}\=j%-(+cf@[@~!j @ťgB9B$Bh B@JB@x@AAJiEaJ JBBej='k ={"l@@c avAmb+b! n B@aB - @x@A$AoEЀCDbp =j<a$"q@@,rJ J s B@aRB@x@A ARtE#Ga EEaRu =RN=R%v@4J@  C҄"1`1aBwbIb x B@jB@x@A$AjyEjFFajz ==(%{@z@ |BB, } B@JBc@x@AAJ~E1 GGe=Fw3(. EO ! =!Ā{"@k@ ) @+c 1(Ab  b B@aB@x@A$AEy!@HIb =j9`=j @B8@,J7J 4b4 B@aRB@x@A ARE JJaR =R{="@@ SґFpcPBbAb j B@jB@x@A$AjELb jKKaj =j̯=%-o%@(@$Bµ) F B@JB@x@AAJEg JLLe=TzFc =m{% %@ i@ B': @.W oAbohb ! b B@aBB@x@A$AEY#!acMNb =j=j @@,JXJA#e B@aRB@x@A AREg"aR FOOaR =R ="@o@Qc"1x|}!Bbۜb@(j B@jB5n@x@A$AjEU#jPPaj =jvY=j%-(%@X@ $( @.`@BB1B J$Bi B@JB@x@AAJEt$JQQe=F =\{F% %@@$X Abb 륱 B@aB5n@x@A$AÈyRSb =j%aj @v@,J⊀J > B@aRB@x@A ARED&a TTaR =RK=R"@ G@NN$F!os11@xү"~aBbxFb B@jB * @x@A$AjE jUUaj =j'aj%-(%@{@ !j @ťBB B@JB@x@AAJE JVVe=̈́F = {% "@M@ c HtAb ! 륱 B@aB$X@x@A$AEv(bcWXb =j6)`=$j"@5@,J4J  B@aRB@x@A ARE YYaR =RP=R"@@ cyxxÔ "ayBbb@$j B@jBB@x@A$AjE1*b jZZaj =j=j%p%@@ Be B$ c B@JB@x@AAJEd+aJ J[[e=8wF =j{F% "@e@ )AbSb B@aB,@x@A$AE> ,a\]b =ji=j^%@ހ@,J5JA#F B@aRB@x@A >EL-aRF^^aR =R=R"@\@ yxu$t5{aBbșb½ B@jB}c@x@A$AjER.aj+u__aj =j^V="@U@ BB  B@JB@,* @x@AAJEY/aJ ``e=ڤFc =@M{ @@ y! @W "Bb ! bZ B@`By@x@A$AEɀabb =j0a^%@g@@Sm'%JӇJA#J B@aRB@x@A ARE@1accaR =@H=R%@C@ "*tGaQBb]b½ B@B$X@x@A$AjEt fddaj =j2a(*+c@a~ r$( @"@BBBJ$Bic B@JBc@x@AAJE  Jeee=|ʄF = À뽀{% "@6@ $F @6 Aqb b륱 B@`B@x@A$AEs3bcfgb =j34`=j%pj"@2@ !j @J1JA# B@aRB@x@A ARE hhaR = :=" @@ op" 4Q`G7  bb j B@B@x@A$Aj E5bF jiiaj =j=j%-%@@~B^B B@JB@c@x@AAJEa6aJ Jjje=tF =@g{F% %@b@ @?m b8b! b  B@`B@x@A$AE#7aklb =jF݀=j%pj%@ۀ@,JJA#c B@aRB| Bh@x@A ARE08aR mmaR =Rܛ=FR"@5@ +EutBfDbb I B@jB@x@A$Aj!EO9jnnaj" = GS= #@R@$$BB J$ c% B@Bc@x@AAJ&E> :Jooe'=F( =*{% F")@v @ $X 4*b ! + B@aB@x@A$A,Eƀpqb- =j;aj^%.@O@,/JJA#n0 B@aRB@x@A AR1E=@@ ! @c NCA?b|b ! b@ B@aB - @x@A$AAEfp>acuvbB =j0?`=%C@.@,DJYJ E B@aRBxc@x@A ARFEt@Y: FwwaRG =R+="H@@ +`Q66*@OBIbbĩJ B@jBQ@x@A$AjKE@b jxxajL =j=%-(%M@ܥ@~$( @"@BNBBB$BO B@JB@٢o$y@x@AAJPE^AJyyeQ=qFvR =@qd{% "S@_@ 8ATbb ! 륱U B@`B@x@A$AVEBacz{bW =j8ڀ=^"X@؀@,YJJ cZ B@aRB@x@A AR[ECaR F||aR\ =RŘ=R"]@@ c jajv =j=(%-(%w@ @~$( @"@BxB J$By B@JB@x@AAJzEıH@={=IĄFc| = À{% "}@@c 8A~b`b 륱 B@`B@x@A$AjEKmIab =x-J`=j c@+@,JFJ B@aRB@x@A AREY FaR =R=R"@]@cRS""~}"IBbbĩc B@jBc@x@A$AjEߟKb jaj =jo="@ʢ@ B+BJ B@JB@x@AAJEf[LJe=F]L =^a{% @\@ `Ebb B@aBc@x@A$AEMb =j=j @@,JaJA#c B@aRB @x@A AREsҠ@Y FaR =R ڀ=R%@hՀ@ #T6U +ndBbԀb  B@jB* @x@A$AjENaj =j=j*(+c@␀@nBBB B@JB@x@AAJEIOJe=Q =qO{% "@J@ y/ @+c 'Lbb ! b B@aB@x@A$AEPab =j:ŀ=.@À@,JJA#(> B@aRB@x@A ARE|QaR aR =R˃="@%@ 3V%%W iDb~bj B@jB$X@x@A$AjE7Rjaj =j,;=j @:@ B9B$  B@JB@x@AAJE# e=FB ={% @]@> @+c ABb ! b B@aB>",@x@A$AESbb =jnT`="@8m@,JlJ  B@aRB@x@A ARE%Ua aR =Re-=R%@(@ !d$F%CX1ZD-KBb'b½ B@jB@x@A$AjE= jaj =j=j%-(+c@*@~!j @(@BB〃 B$B B@JB@x@AAJEĜVe=EF ={"@@ y c Ab`b B@aB@x@A$AEKXWA@Tcb = 0`zX`=$"@@,JFJ B@B@x@A AREXϠ@Y: FaR =R ׀=R%@hҀ@ ySD"-Q@dBbрbĩ B@jB@x@A$AjEߊYb jaj =jg=(%-%@č@B#BB B@JB@x@AAJEfFZJe=F =VL{% "@G@ y! @ Abb bc B@aB@x@A$AE[acb =j€=^%@g@,JӿJA#( B@aRB| @x@A AREx\aR FaR =R=Rc@|@/ Rct𐁷u aCBbz{b@$j B@jB/@x@A$AjE4]aj@Y jaj =j 8=( @f7@$B6B J B@JB@x@AAJE Je=| ={% @C@! c Bb b! B@aB@x@A$AE^bb =jk_`=j%r@)j@,JiJ c B@aRB@x@A ARE"`a aR =RK*=R%@%@ s%v%%wuVBbb½ B@jB@x@A$AjE"ޠ@Y jaj =j= @@ ) @B+ B^J$B B@JB@x@AAJ Eab Je=*F =!?{% @ @ ! @c BbAbEb bc B@aB T@x@A$AE0Ubab =jWc`=j^"@@@Sr' @ @ J#J@.J B@aRB@x@A ARE=̀ aR =@Ӏ=R%@Qπ@ Ӕ@%1aN7Bb΀b B@B@x@A$AjEćdb jaj =jL=j%-(%n@@ r$( @"@B BBJ! B@JB@x@AAJ"EKCeaJ Je#=̤F$ =;I{% "%@D@c A&b  륱' B@aB@x@A$A(Ecb) =jfa"*@X@,c+JļJA#, B@aRB@x@A AR-Euga aR. =R}=&/@x@ yua'B0bcb½1 B@jB@x@A$Aj2Ef1hajfaj3 =j4=%pj%4@7@ 5B3J6 B@JB@)@x@AAJ7E쀈 e8=mFB9 =@{% %:@(@ ! @c  A;bb b < B@`By@x@A$A=Esibb> =jhj`=^%?@f@,@JVJ cA B@aRB@x@A ARBEka aRC =R4'=R"D@"@ tm/4aBEb!b fF B@jBB@x@A$AjGEۀjajH =jހ=j I@݀@~$( @"AJJBKBK B@JB@@x@AAJLEleM=FzN =@{% O@ʗ@ / @ 1lAPb*b ! bcQ B@`B@x@A$ARERma| ^bS =jV="T@U@ m!j$UJTm` RV B@ Bb@x@A ARWE n RaRX =RI=R%Y@@  QQe! HBZbb j[ B@jBB@x@A$Aj\E"ɀ jaj] =j̀=(%p(+c^@ˀ@<_B^BB$ ]L` B@JB@@x@AAJaEoeb=Fw b` gc = ){% "d@ㅀ@ B': @?m !AebEb b f B@aB@x@A$AgE/@p!@cbh =jUq`=j%pj(fi@~ mjJ"J 4b>k B@aRB@x@A ARlE= FaRm =RӾ="n@5@ !! Je=F =r{% "@Ă@ W c 2Ab*b ! 륱 B@aB@x@A$AE={a b =jA="@@@,cJ?JA#Fc B@aRB/@x@A ARE RaR =RO|aR"@~%o""; ]b b j B@jB !h@x@A$AjE", jaj =j=(%p(%@@ BfB$  B@JB@x@AAJEo}bw Je=F =u{% "@p@x AbEb  B@aBx@x@A$AE/+~a,b =j`=^(f@@,J.JA# B@aRB@x@A ARE=aR aR ==R"@U@cS"O 䐃9  -mBbb@&j B@jB; @x@A$AjE]jaj =jPa=j%-(%@`@ B B B@JBW@x@AAJEJJe=ˤF, =6{"@@c Ab  B@aB; @x@A$AEԀb =jaj)%@\@,JȒJA#Fc B@aRB@x@A AREKa aR =RS=FR%@N@ !$F @cSÔ@a~)OQ* bRb½ B@jBc@x@A$AjEejaj =j = @H@$B J B@JBc@x@AAJE e=mՄF =Ȁ{*F"@)Ā@; * bÀb c B@aB @x@A$AEs~bt b =j =^%@k@,J׀JF B@aRB@x@A ARE9aR RaR =RA=%p@<@ S#0+[:+dapHobebj B@jB5n@x@A$AjE jaj =j=j @l@~' @"AJBB B@JB@x@A>Ee=| ={% @>@  Ab !  B@aBc@x@A$AElab =j,`="@+@,J*JA#c B@aRB,@x@A ARE FaRsRI= @@ c3*GE)% GBb b j B@jBc`x@9 Aj @`E"b jaj ==j%p(1@@ Bf B$  B@aB@x@AAJ EZaJ Je =)mF =`{F% +c @[@ B! @W WA bDb B@aBB@x@A$AE/ab =jbր=j^(f@Ԁ@,J.J  B@aRB@x@A ARE {%p!@@ c dVA"b  Zy# B@`B@x@A$A$Eѿb% =ja^"&@S~@ m!j%'J}J ( B@aRB* @x@A AR)E6a aR* =R>=R%+@9@ Sq 3%qea* ,bNb½- B@jB; @x@A$Aj.Ee jaj/ =j=(%p(+c0@N@$1BJ2 B@JBc@x@AAJ3E쭒b Je4=mF|5 =ܳ{% "6@'@ @c /* 7bb b 8 B@aB@x@A$A9Eria1 b: =jn=j%pj%;@rl@,<JkJA#= B@aRBz; @x@A AR>E$RaR? =R,="@@(@ c+qTQ2dUDAbq'b jB B@jB@x@A$AjCE ajD =j= E@S@ rcFB  JG B@JB@c@x@AAJHEeI=j|J =@{% F%K@D@ BLb M B@`B W!L6@x@A$ANEWbO =j5\=^%P@Z@ mcQJJ cR B@aRB@x@A ARSERaRT =R="U@@ s.k[7+gBVbb jW B@jBy@x@A$AjXE΀2 ajY =j#Ҁ=j Z@р@ [BB\ B@JBc@x@AAJ]E!bhe^=)Xy_ ={F `@`@/ ~.Bab ! cb B@aB,@x@A$AcEEabd =`=j^%e@3@,fJJ g B@aRB@x@A ARhEEaRi =RpĀ=R%j@ο@ %1%qkb:b(ol B@jB@x@A$AjmE@ !j @(@BBh  B@JBc@x@AAJE!Je=F ='{% (@#@ B! @ b~"b b륱 B@aB@x@A$AEe݀ ZWUb =jaj$j"@뛀@,JWJ  B@aRB@x@A ARErTaaR =R'\="@W@ caV.%b½) B@Bnb@x@A$Aj*E@Y jaj+ =j^= ,@@ -B!B$ c. B@JBV@x@AAJ/Ed  e0=F; 1 =T{% 2@@ 8B3bb ! c4 B@aBy@x@A$A5Enac!"b6 = /`=^%7@~-@,8J,J ýF `R-9 B@aRB@x@A AR:E F##aR; =R=R%<@@ #6u"Tc)L=bdb j> B@jB@x@A$Aj?Eb j$$aj@ =j=(%-(+cA@U@ wBBµ$ C B@JB@٦B)@x@AAJDE]aJ%%eE=oFVF =@b{% "G@F^@ c 4Hb I B@`B@x@A$AJEa&'bK =j؀=^%L@׀@ m!j%MJ֠JA#N B@aRB@x@A AROEaR F((aRP =RF=R"Q@@ 3le"EfaDRbb#S B@jB@x@A$AjTE!Kj))ajU =jN=(%-`3AV@ @$WBmM JX B@JB@x@AAJYEJ**eZ=)|[ = {% "\@@ ': @, :6A]bCb b ^ B@aB@x@[ =A_ @E.€+,b` =jVa%pj%a@@,bJ%J c B@a B@x@A ARdE<9a --aRe =R@=R"f@4<@ C%EgeBgb;b jh B@jBc@x@A$AjiE j..ajj = J= k@@ƹ$( @"AJlBB Jm B@B@x@AAJnEIb J//eo=ʤFp =5{% F"q@@ B) @6 #Arb ! bcs B@aB@x@A$AtEkA@T00bu =jwp=j^"v@n@,wJDJ@x B@aRB* @x@A ARyEV'R11aRz =R.=R"{@G*@ Sek aiB|b)b } B@jBB@x@A$Aj~E  f22aj =j]=j%p(1@@~BB$  B@JB@x@AAJEd33e=llL6 =T{% "@@! @+c JAbb ! b B@aBc@x@A$AEYa| 44b = ^=%@\@,J_J  B@aRB@x@A AREqaR R55aR =R =R"@e@ c$pBFcBbbĩ B@jBc@x@A$AjE j66aj =jxԀ=(%-(%@Ӏ@ $( @"@BB8B$B B@JB@@x@AAJE77e=Z5n =@o{% "@@  ccAbb ! 륱 B@`B@x@A$AEHa88b =jL=j c@K@ m!j @JzJJF B@aRBB@x@A AREaR F99aR =R: ="@@; TsŮmt̀1Bbbj B@jBc@x@A$AjE j::aj = {€=%-(%@@ rBWBJ$ c B@JB@@x@AAJEz;;e=H|@ =@{% %@{@ * ! @c Ab6b bc B@`B@x@A$AE 6a<=b =jN=j%pj(f@@ mJJA# B@aRB@x@A ARE.aR F>>aR =R="@B@ o"!%  'NZBbbf B@jB @x@A$AjEhaj??aj =j9l=%-%@k@$BjB J B@JB@x@AAJE;$aJ @@e=F =4*{% %@v%@b$F @6 "!b ! b B@aB@@x@A$AE߀ABb =ja$j%@M@,JJA# B@aRB@x@A AREVa CCaR =R^=R"@Y@  % xt ` aABbPb½ B@jB@x@A$AjEVaj jDDaj =j=j @B@~!j @"AJB B$Bc B@JB@x@AAJE JEEe=^FQ =Ӏ{% F"@π@h IAby΀b c B@aB@x@A$AEdbFGb =jI`= @G@@Sf' @J_J J B@aRB@x@A AREqa HHaR =@'=!@@ t%& "aBbbĩ B@B(c@x@A$AjE jIIaj =j=j%-j)w@侀@ BDB B@JB@x@AAJEwb> JJJe=F = 5s}{% %@x@ L6$F @c >Abb! b  B@aB "@x@A$AE3acKLb =j,="@@,cJJ  B@aRBc@x@A AREaR MMaR =R=R"@@P$F%(oEadBbsb½ B@jB @x@A$AjEejNNaj =j*i=j @h@ !j @ūcBgBBcu  JBy@x@AAJE !JOOe=F = €('{"@["@ %NYBb  B@`B "@x@A$AE܀PQb =jڜa$" @:@, JJ  B@aRB@x@A AR ESa RRaR =Rw[=Rc@V@ e$F!e x$4CBb0b½ B@jB @x@A$AjE;jSSaj =j=(%-+c@ @ n!j @ B J$B B@JB !K@x@AAJE TTe=C݄FQ =@Ѐ{%p"@ˀ@ c Ab^b c B@`B !@x@A$AEIbUVb =jtF`=^%@D@ m!j$^J?J  B@aRB/@x@A AR!EV@YA WWaR" =R$R"#@^@ ,|} "l@$b % B@jB@x@A$Aj&EݸjXXaj' =jm=(%-(%(@ʻ@$)B)BJ* B@JB@x@AAJ+Ectbw YYe,=Fc- =Xz{% ".@u@ , /b  0 B@aB@x@A$A1E/acZ[b2 =j=j%pj%3@y@,4JJA#(R5 B@aRB@x@A AR6EaR \\aR7 =R="8@詀@ m n o":abD9bTb jc: B@jB5n@x@A$Aj;E~baj j]]aj< =jf= =@le@rc>BdBB? B@JB@x@AAJ@EaJ$X^^eA=0|/B =#{% F"C@E@c WBDb E B@aB@x@A$AFEـc_`bG =jÙaj H@#@,IJJ >J B@aRB@x@A ARKEPa FaaaRL =R4X=R"M@S@ 5n97867u4u6S"aWsBNbb@$O B@jB$X@x@A$AjPE jbbajQ =j=j R@@ SBd BT B@JB@x@AAJUE cceV=,ڄF$XW =̀{% X@Ȁ@ 5n! @1a0# K BYbCb b Z B@aBc@x@A$A[E-bcdeb\ =jQC`=+c]@A@,^J JA#F_ B@aRBx5n@x@A AR`E; ffaRa = +aR%b@+~c U"++bPBcbbd B@B@x@A$AjeEµ, jggajf =jB=j g@@ $( @"AJhBBi B@JB@x@AAJjEHqbw Jhhek=ɤF~ l =5w{F% m@r@ 5\ @  5Anb ! bo B@aB$X@x@A$ApE,aijbq =j=j^"r@R@,sJJA#(Rt B@aRB@x@A ARuEݣaRFkkaRv =R=R%w@馀@U$4"%aBxbUbjy B@jB@x@A$AjzEc_aj fllaj{ =jb="|@%@~}Ba B$il~ B@JB; @x@AAJEJmme=k-|u = {% @&@ ! @ ~Bbb b B@aB @x@A$AEqրno! =j  ^%@@@Sf'%yJsJ R B@aRB/@x@A ARE~Ma FppaR =@:U="@P@ #T}N"aerBbObĩ B@B@x@A$AjE jqqaj =j =(%-(@ m!jJJ  B@aRB@x@A ARE @YA uuaR =R=G = a@$@ 3zu"‘@z;$bb B@jB@x@A$AjEb jvvaj =j+=j%pj!@@ BB$ c B@JB @x@AAJE-naJ@Yw Jwwe=F5n =t{"@jo@, 0Ab͡j! B@aB@x@A$AE)a xxb =jW.=j%p%@,@,J$JA#rR B@aRB @x@A ARE; RyyaR =R=R(o@S@ 7$F!o" CTD t;aBbb j B@jB* @x@A$AjEzzaj =jJ="@@ !j n"AJB B$B B@JB@c@x@AAJEH\J{{e=P*5n =@8b{% F"@]@ ) @+c )Ab ^! bZ* B@`B,@x@A$AEa|}b =j׀=$j"@Zր@ m!j @JՀJ(R B@aRB@x@A ARE܎B@T~~aR =R=!R"@䑀@ S"u6 @TNBbPb  B@jB@x@A$AjEcJjaj =jM=j%-j+c@S@ BL B$ v B@JBy@x@AAJEaJ ge=k|* = {% "@$@ ! @( Abb^! b B@aB@x@A$AEpV =jaG@ %@@,JcJ dR- B@aRBc@x@A ARE~8a aR =R@@= @;@ 7" @c"& ' (xBb:b½ B@jB @x@A$AjE jaj ==j%-(%@@ !j @r"@BB=B$B B@JB@x@AAJEe=`= ={F% %@ư@ Ab'b ! 륱 B@aB@x@A$AEkaj b =jo=j$j"@&n@,JmJA#= B@aRB@x@A ARE&RaR =R).=R"@)@ cs"t5%hht5gg"`IBb(b j B@jB @x@A$AjE  aj =j=j%-%@@~ƹBh䀃 B$ g B@JB@x@AAJEe=F@ ={:% "v@㞀@ ! @ :AbBb ! bc B@aBc@x@A$AE-Y acb =jY `=j @@,J(J  B@aRB@x@A ARE:Р@Y: %T2 =R׀=FR" @OӀ@ yEYB)"‘ xC v   B   `   @ F B@B `8B @'@x 9 > @@ `@E  @ A =@`=I=H "=  @@3A@@  `@K J!JBB BiJ B@B@B!KJ@x@AAJEHG J@==Y `=z3"<    ! =@ ={F% (?@;@ c 'A@b ! 륱A B@aB "@x@A$ABEbbC = ``Z`=j$j"D@"Y@,EJXJ nF B@B@x@A ARGEaaRH =R3=R"I@@ $F!Ô0" TD"aSBJbb@$K B@jBy @x@A$AjLE͠@Y f//)M =jЀ="N@@!jn"AJOBcπB$BP B@JB@x@AAJQEb JeR='FS ={% F"T@ቀ@ @AUbBb2kT! b V B@aB "@x@A$AWE-DacbX =j``=^"Y@@ `'$^ZJ,J BJ[ B@aRB@x@A AR\E:@Y aR] =@€=R"^@N@1r~Z$F!Rt-%~"d 8B_bb ` B@B @x@A$AjaEvb jajb =jAz=(%-(+cc@y@ r!j @"@BdBB$Be B@JB@٢oB!K@x@AAJfEH2aJ Jeg=ɤFxch =@<8{% "i@3@'$F @6 {4Ajb bck B@`B !@x@A$AlEcbm =jaj$j"n@U@ m!j @oJJ p B@aRB|@x@A ARqEda -)r =Rl="s@g@!R6"t~"+aBtb\b#u B@jB @x@A$8`8صq'!UEdC =@Nַ`=`(f` a&@Հ@@S`6@`@ ^@ݔCJԠJ J `J@ D-F B@B{KwB x  @x@A AREeaR J 8eeAR =@A=R!R@@ `@RK%WARJJ  `JRiR B@B@dR @x@A AREJaRfgAR =RB`=d (r@@ !R"!@BBBBJ B@JBz@-"@'@x@AAJE hheK=Ѐ=`=8>La` 8w  @/`8A =! =ZA`=M!Y!@?@ M @{%BJ'J  ` @ B@a B@x` =B @`E@AR =@`==R#M@C@~#M @!@BB B B@BNB @x@AAJ Eb_Q J NAJ =J3`=J  @@@ >AR JJ J B@RBB$ @x@A AREjabAR =Rm=R"\@l@0 @>JykJJ,2 J B@RB>B%@x@A AREd&RAR =RH)="R@'@@So"!R m+֯BJJ$R B@RBR@x@A ARE AR = {=R,@'@ RR @PK?ALDD B@LB@ 5@AAL @`Erb LAL =@p`=@=L& @@~!L @!BB" B@Bx@x@AAJ#EX`)AJ$ = /b@ J ,%@@U#!J&#A&J+J ' B@B d@x@A AR(EЀ AR) =RҀ=$*@<р@ TAR+J , B@RB R"@x@A AR-E":RAR. =Rm=)6 R/@Ɍ@ R" @1 >ouXAR0J(JR1 B@RB@x@A AR2EGRF3?3 =RI="4@JH@ R%CRK !0WAR5J R6 B@RB@x@A AR7ERAR8 =Rz="R9@@R ";RAR:J6JA#; B@RB@x@A AR<E!j KAR= =R="R>@\@ R   u (4?J @ B@RBR@x@A ARAEy :B =R|="RC@z@ R KPARDJCJA#E B@RB@x@A ARFE.5RARG =R8="RH@k6@K"#Q:ARIJ J B@RB@x@A ARKE ARL =R="RM@@ R  ARNJQJRO B@RBR@x@A ARPE;ARQ =R="DR@y@ R KTGaxgSJؠ%g!K  T B@RB@x@A ARUEgRARV =Rj="W@h@ R "]#KBXJ^J$Y B@RBR@x@A ARZEI# RAR[ =R%=R"\@$@ @SZ5` K |\]F ^ B@NB N@x@AAN_Eހ AN` =@& a!& h-9/(a@@ V!N @ *bBB*c B@B{@*@x@AAJdEU a TAJe =@p8 `=J ,f@@!*%BdAgJJ *h B@B*@x@A ARiÈ ARj = {Kπ=R, 'k@%΀@@S" @  xANlF̀Fm B@NB @x@AANnEq "6ANo =@E`=;#N#p@(@ V!N @ qBD Br B@B@x@AAJsE7AJt =JaJ"q" 7 ,u@A@ "AvJJ Rw B@RB @x@A >xEv  ARy =Rly=# jz@w@ ," @ KUSB fAR{J(J R| B@RB@x@A AR}E2  RAR~ =R4="R@P3@ R  RecbARJ C B@RBR@x@A ARE@Y RAR =R~="R@@  eiv!TAARJ:J$ B@RBR@x@A ARE AR =RL=R<@f@5tZ @\rALD D B@LB@ۢYB* @x@AALEdaL6AL =@p!`='@^@ " B B B@B@x@AAJEۀAJ =J $J ,@k@ "!J\ _/JחJA#Z B@RB+@x@A ARER  =AR =RS= gB@^@~ B B@B@JCI@x@AAIa%E 1I@.A =@UL i j@*T@!V |JSJ NV B@B$BV@x@A AREmaR 8//aR =@I= V0)VaR@@ $ @K #J J F B@B G@x@A AREɠ A@00aR =@̀="R@1ˀ@,JʀJR B@B@x@A AREz 11aR =R=R(@@&DD$ B@LB@x@AALEA L22aL =LB=L'[(@1@ # @ B — B@JB@x@AAJE 34aJ =J a2&@S@ = $ AJJA#R B@RBMmB@x@A AREs a 55aR =Ruv=&O!R@t@ R" @J1J B@RB%@x@A ARE/ R66aR =R1=RD@T0@@Z"!R@  1kBC K B@KB@@x@AAKE 77aK =@pq=%<@@~" <B?B< B@By@J@x@AAJE)bo89aJ =@pWe`=! @c@!%<T|J$JA#< B@B$@x@A ARE7a ::aR =R =R#R@t@ R" @<J < B@RB<@x@A ARE؀ R;;aR =Rۀ="R@ـ@ <R !JZJA#R B@RB @x@A ARED<>aR =R2="R@ @K !J R B@RBR@x@A AREƀH3??aR =Rɀ=<&]`3k@Ȁ@ R  !JxǠJ m K B@RB@x@A ARE_@@aR =R[="@@ R K !JJA# B@RB@x@A ARE=RAAaR =R@="R@!?@K !J>J$R B@RBR@x@A AREmz DBBaR =RM="R@@ R  Ga !J J$R B@RBR@x@A ARECCaR =Rӷ="R@/@ R K !JJ$R B@RBR@x@A AREzpRDDaR =Rr=RFx@q@ @S4 !FF$N B@NB !@x@AANE,NEEaN =@-=N1*@1@ !N @ gB 8* B@B|@*@x@AAJE gFGaJ =@pܥaBq;! 8 @>@ # @ @{'%A JJ * B@By1B '~ @x@A AR E^a HHaR =@րq`="@_@Z" @ B1BB B@B@"9@x@AAJEaJIJaJ =@p؀=J"\@׀@ AJ~֠J  B@B}@x@A ARE)aR KKaR =R=R"\R$@p@@SU7ANF  B@NB N@x@AANELNLLaN =@}N=N#@M@!N @ BLB B@B@x@AAJ!E7aJMNaJ" = ƀ="q! #@Iŀ@$KA$JĠJ % B@B@x@A AR&EDaR OOaR' =R@=<(@@ " @c )JJ* B@RB@x@A AR+E: RPPaR, = {=="R-@J<@ R&R .J;J$R/ B@RB@x@A AR0ER QQaR1 =RN=!"2@@  3J J4 B@RB@x@A AR5Eر!RRaR6 =R$=E7@<@& 8DD$9 B@LB*@x@AAL:E_m"LSSaL; =LRo=''<@n@ " @ =BB> B@JB@x@AAJ?E(#aJTUaJ@ =Jx=J#A@@ %=!JBJDJ oC B@RB@x@A ARDE$aR =VVaRE =Rk=!zF@I@GFF9H B@NB9@x@AANIEz[%NWWaNJ =Nf]=N"`K@\@ LB2BM B@JB[@x@AAJNE&aJXYaJO =JՀ="q" P@Ԁ@$QJoӠJ R B@RB[@x@A ARSE'aR ZZaRT =R= *U@j@ }" @?cVJ W B@RB}@x@A ARXEI(R[[aRY =RL="RZ@J@ R&R[JYJR\ B@RBC@x@A AR]E)R\\aR^ =R=!`3g_@X@ R `J Ra B@RB@x@A ARbE ]]aRc =R€=+d@ @&eDnLf B@LB@x@AALgE)|*^^aLh =L*~=L i@}@ " @ jB Jk B@JB@x@AAJlE7+aJ_`aJm =J=! n@&@$סoxJA#op B@RB@x@A >qE,aR aaaRr =RM==!zs@*@(tFF¡u B@NB,@x@AANvEDj-NbbaNw =Nl=!& /x@|k@ yB Jz B@JB@x@AAJ{E%.aJcdaJ| =Ji= }@@$~J5JA# B@RB&@x@A ARE؜/aR eeaR =R䟀=R1@>@ R" @JJ R B@RB*@x@A ARE_X0RffaR = W[="R@Y@ }JJ$R B@B@x@A ARE1RggaR =R=C"d@5@ R JJ$R B@RB@x@A AREl hhaR =Rр=+@Ѐ@ y`%aDLD$L B@LB@5@x@AALE2iiaL =@pߌ=L'@?@ " @ B B B@B@x@AAJEzF3aJS@sjlaJ =J4`=! @@ " @!JoJ Z B@RB@x@A AREy5aRA@mmaR =@{=="@xz@ " @ B ʁ B@B@@x@AAJE46aJnoA =@p=J"\@w@@S "{HJJR B@B@x@A ARE7aR ppaR =@Ү=R"\R@/@  K JJA# B@B.@x@A ARE)g8RqqaR =R)j="R@h@ R"!R u%rD;J ¥R B@RBR@x@A ARE"9RrraR =R%="R@&$@)*J#J$ B@RB@x@A ARE6 ssaR =R="R@w߀@ R )K@2@ @O@}AJaJ@Z0 B@RB"5B@x@A ARE l~~aR =@Ԁd= j?@Ġ~  B B@B@J50@x@AABba =gC`= @"f@ $VOBVJeJ  V B@NB@x@A AREDaA@,WaR =@"= @ @ $ @O?ARJ@J B@B G@x@A AREۀ RaR = `'ހ="R@܀@R uvARJ R B@B@x@A AREEaR = ="R@ݗ@ R p ^ARJ>J  GF B@>BR@x@A ARE(RFRaR =RU="R@nS@ R%K)$u ARJ  B@RB@x@A ARE GRaR =R="R@@K &q(ARJwJ$ B@RBR@x@A ARE6 KaR =R*̀="R@ʀ@ 0 1TmARJ y  RBR@x@A AREHbz RaR =R="R@@0% 8ARJyJR B@RB@x@A AREC@IaR. R$`: =RSC="R@A@KB 8| JJA# B@RB@x@A AR E@Y RaR =R="R @$@ R  FUBJJ B@RB@x@A AREQJaR =R5="R@@ R +'ARJ R B@RB@x@A ARErKRaR =Ru=&]@3t@K &ARJsJ$ B@RBK@x@A ARE^.LR# %K =Rb1="@/@ R *4ARJJ$R B@RBR@x@A ARE KaR =R="R!@1@ D K #AR"JJ# B@RB@x@A AR$ElMbz RaR% =Rp="R&@ʦ@K u% AR'J,JR( B@RB# ;B6@x@A AR)E`NaR R:#* =Rc="R+@Kb@ u%AR,JaJD- B@RB@x@A AR.EyOR g!R/ =R="R0@@ R K  AR1JIJ$R2 B@RB ~@x@A AR3E؀ aR4 =Rڀ="R5@:ـ@K%I?mAR6J 7 B@RB2! ;R@x@A AR8EPaR9 = pw="D:@ϔ@IC)lAR;J/J$K< B@B$R@x@A AR=E OQRaR> =RuQ=!z?@HP@@S-o)BAN@F A B@NBN@x@AANBE RaN 4EGC =@ǀ=N>D@K@s!N @ EBƀ BF B@B@x@AAJGESaJ*?@saJH =JY.W`=JKI@,@8!Jo~B9טAJJ'J@K B@RB@x@A ARLE@YRA@aRM =@*= R~N@@~ >O B@B I@x@APAPXbaQ =`Y`=$ oR@[_@V "wBVSJ^J VT B@NBq@x@A ARUE^ZaqaRV =R^= V!R W@@@S$ @K<gARXJJ R FY B@RBD @x@A ARZEԀ aR[ =@E؀="R\@ր@ < Wd"AR]JJ$R^ B@B5@x@A AR_Ek[b RaR` =RK="Ra@@   "\bJJG"c B@RBR@x@A ARdEK\RaRe =RN="Rf@1M@ R CYBgJLJ$Rh B@RB@x@A ARiEy]RaRj =R] ="Rk@@ %Fu|IARlJ!J$Rm B@RB5@x@A ARnEà@Y aRo =Rŀ=&]zp@<Ā@5.5 |ARqJ r B@RB5@x@A ARsE~^aRt =R="u@@!NcWARvJFJ$w B@RBK@x@A ARxE :_RaRy =R=="Rz@j;@ # v'{J | B@RBK@x@A AR}E aR~ =R="R@@ KucJ`JR B@RBR@x@A ARE`aR = _="K@@=% l@  A)BA?JJ (R B@B@x@A ARElaRaR =Ro="@m@ ! B EKJ]J$R B@RB@x@A ARE((bRaR =R*=R '@)@'@SpZ`  B&B;AOG G B@OB(@x@A AOENT =@%ca3IA4@@ W%H @ BB B@B@x@AAJEZda A!ʒ@ϙ@ 8AJ =JO Br" = F` 1+! J4@@@ @@̥pAJdJ ~ B@RB{ @x@A AREOWaRA@aR =@I[=D@Z@ c @AKAKCYC B@B@b Ko@x@AAKEKaK =@p=K'#@@ !K @ BVB4 B@B !KJ@x@AAJE\΀E8caJ =Jla&\=@k@ AJjJ  B@RB.@x@A ARE!aRA@aR =@O&=$J@$@k@S" @ }@AK:rAQI7II B@B_ ;@x@A AQE&݀ Q@==|ifj @@ƿ#@F =A;{W@`ހ@ !e~ )g @% @ AГ `AV V$A ViqU^ B@`B E @x@AA E `E % %N@D@$NB  !  vk B@B a! N@x@ABkEc+c =J`=J"BJ)8@@,NJZJ N B@RB@x@A AREAˀ@Y aR =Rπ=R%N@y΀@l@S~Z N )BÌI QN B@QBN@x@A AQEȆbfQaQ = Ӡ=H '#k#@݉@ !Q @"@BB<B B@JB@x@AAJENBaJ (@==T`=- Ex = F{)&"@_E@) @)"qAIDI ! IҦ B@`Bo@g%@x@A AQE8AQ =Q1aQ @@,JJ  B@aRBf@x@A AREta aR = {y=H $-R"@x@""ANeIwI Q B@QB@x@A AQEi0aQ Qd4=c@(A A =@ )3{#4@1@ 4D4)D4IIY4 B@`Bl! @x@A A Ez5&&@4@ B=B! A B@a B@ @x@ABEA,"^ =@pFbJ'@@,AJJA#A B@B@x@A AREbO@TA AR =Rg=R%A@.f@p@S"!0.BIeIaA B@QB` s@x@A AQEaQeA=c A =A;(#{H=f&-!@!@o@S~@! @  AI I $4 I4 B@`Bt !@x@A B E@`E%$A@$@ Y! @$5A B=Mc@A =^{ A@@ @@ [/L@ I2IA B@aB@x@A EA Eb$%A@@ ABz B! A B@a BA@x@AB^EA c =JvaJ"A @u@ b'$/!JltJ IJA" B@RB@x@A AR#EN-a aR$ =@ 2=R%A%@0@!R@~!&I/IA' B@BbA@x@A AQ(E QeA)=dr7A* ={-%-!+@@ " @5!Dw,IqI `I4- B@aBp ,@x@A EA. E$%A/@^@ `@)w@B0BB B1 B@a BA@x@AB2E\cAc3 =Jd`=J#J! 4@c@,A5JbJ RA6 B@RB@x@A AR7Eia aR8 =R% =R#9@@ "!HCMܗB:I IA; B@QB@x@A AQ<Eր QeA==~`> =ۀ{%!?@ـ@ ! @ A@I`IAA B@aB@ 5@A EAB  `E݀$%AC@K@$ADB܀ AE B@B@i &NI@x@ABFEwcA5cG =@pɣ`=J#J%AH@7Ȁ@,IJǠJ AJ B@B|DSRE@x@A ARKEa aRL =RL=R#M@ʃ@! @eANI6I O B@QB@x@A AQPE<QeAQ=N`=R =>{%!S@R=@g@ST AB'@sDwTI AU B@aBh!$4@x@A EAV E@`EXA$%AW@@@ Y&U @$5@BXBBe BY B@BA@x@ABZE c[ =JƷbJ ^\@8@ !J @!]IIQ I^ B@QB$@x@A AQ_EnaD!"aQ` =Q /`=Q$"]a@k-@,bJ,J c B@RB@x@A ARdE ##aRe =R{=R%f@@ @7CgIbI Qh B@QBY@x@A AQiEAbf@Y Q$$fj=*k ={#W!y@@"@T620@bmV n B@aB=V@x@AFo E  & &p@]@ %U @ $B@BqBB ! Br B@a B@x@ABsE\b%&ct =J`=! u@~@,NvJJA#sNw B@RB o@x@A ARxEӀ@Y ''aRy =R؀=#%Nz@ ׀@"N#B{IyրI QN| B@QB o@x@A AQ}E[bf Q((eN~= o ={ N@e@ —$- @% @ K&0 @ >VȑV $A V B@aB N@x@AEN E@`E䗀$%N@A@ @$B@BB B! BN B@B |@x@ABEJbmR A9)AJ =@ڼP@k! @<@,NJJ@N B@B@x@A AREsQ@T AR =R>m`=R%N@x@ N }>IwIQN B@QBN@x@A AQE/a QAQ = Ӡ2=H ck@ @ %"CB1J B@JB !@x@AAJE J`==d-o- A =@]{&"@@ |">I4I ¦ B@`B@x@A AEAQ =Q=H@" @[@ T!Q @6JǨJ  B@aRB@x@A AREaaRAR =R_f=R"@d@"2ß>IFI Q B@QB@x@A AQE-aQAQ =Q =Q%>)o@@@ B B B@JB k@x@AAJE؀@==Fb[ =@wۀ{$"@ـ@ ! @BڿISI I  B@`B>@x@A AQE:b0 @s+ =Q`=G@#@ƀ@ TJSJ  B@aRB1@x@A ARE:aRA@aR =@타=R"@f@ |IҁIɴ B@B@x@A AQE:aQ QaQ =Qi>=Q @=@ # @`AB%BJ B@JB*}@x@AAJEG J@==5R.-  `$- = {H@ -$@@ $- @jI  IҪ B@aB@x@A AQEαaQ[ b =Q=*Q-@ @,JvJA# B@aRB@x@A AREUmRaR =Rr=R%@p@"3ΟIoI Q B@QB\+b@x@A AQE(QaQ =Q,=Q%>*@+@ <BPB B@JB@x@AAJEb e= ={"@t@"II B@aB@x@A AEfL@T71 . "8=֙R u @u7sIg`ud uu(Nu=]U uu7cQu{ q .=nV- \`k2 =g kW7r5=\?[u7_=au7a=\-h$={t kW74=ⲑ] ۀ7ba=hnu70%A[(%KY =ƺ_ b @iĀ@,JàJ ` B@eB@x@A EdE|aRA@,aR =@p=R*@@  IXIQ B@B@x@A AQEC8QaQ =Q;=~ @Y@$B: J B@JB@x@AAJE@==uQ( -].KD-{.{.&@ @!qB\KViV  B@aB E,dr |, `x@9 A^ @EPb0 > B3 =@`=t|`@L^!5^/@@,JCJ@ B@Bd@x@A AR E1}a aR =R͟=:R% @M@+ IIQ B@QB@x@A AQEV~Q;=Gmbs A`DA =[{#$A@W@  ^~$/ @% @dAVTV % V A B@aBA@x@AA  E h]  0@\@ ! @$B@BB$B! Bd B@a B@x@ABE?biN%AJ =J?a@"N@H@,NJJ@N B@RByGB@x@ =AR @`E N&  =R[=R%N@ۼ@+N IGI QN! B@a B[ Q@x@A AQ"E.u@bf # =Qx="$@@@$%Bw B& B@JB@x@AAJ'E0AaJ (=6d-- `D-){6{&*@3@ c% w@F+V,V! Ϻp, B@aB@x@AA-E;Aa)4A. = `[!b ^&/@@,0J*J I1 B@B@x@A AR2Eؠ@Y //AR3 =R܀="4@Lۀ@+5IڀI¤6 B@QB!e@x@A AQ7Ebf Q00`=8=^Bbu9 =㙀{"A:@@ )#AKA;VVA< B@aB@x@AB= E K *Wb*>@@ ! @ $B@B?BB B@ B@a B@x@ABAE*Ob17}B =J?`=J"BJ! C@;>@,NDJ=JA#_NE B@RB@x@A ARFE 88aRG =R="NH@@+NIII Qa>aJ B@QBN@x@A AQKEbf Q99aQL =Qɵ=Q#k#M@(@$NB B$ JO B@JB@@@x@AAJPEmJ::@=Q=L `=R =@q{-&X1uS@n@ $TV;V^9! -NU B@`B@x@AA^VE~{|0W=gJc`@ X7o_0Y=zKu0Z7&)bJx;œ[ =J`u"!J&x\@/@,x]JJAxw>x^ B@b>By T@x@A B>_E &Bœ` =R>Հ=R1Ta@Ӏ@+xbI&I Qxc B@QB0@x@A AQdEbf QCŒe =Q=&xf@@~YhgBq J$ Fh B@JBh@x@AAJiEGaJ JDD@=j=I-Tk =K{%-&xl@H@ x! @%a0qTmV7V! Vxn B@aBx@x@AA^oE"aTEJMPp =^?8Hd@^!5^%q@6@,rJ J@s B@aRB@x@A ARtE@Y KKaRu =R=R%v@+@+wII¤x B@QB@x@A AQyEIbf QLL@=z=ET{ ={#$A|@ū@ A$}V&VA~ B@aB&@x@AD E F  &@@ %b @ $B@BBB! B B@a B@٣Od@x@ABkEfJbMSAJ =@pV e ! @U@ "k"JTJ@ B@BT@x@A AREy a TTaR =R*=!%N@@+NIIaN B@QB@x@A AQEȀ QUUaQ =Q̀=#kQ#@@~Blˀ  B@JB@@x@AAJE VV@==4"Kd- =@戀{ @@ 0! @%! @\0V"V ! VϪ B@`B@x@AA^E @ a W\E = `+u`=^9%@s@ aJrJ  B@B!@x@A ARE+a :]]aR =R0=:R%@*/@+I.IĄ B@QB8T@x@A AQEu Q^^@==  ={#*@@8VV A B@aBs@x@AD E ) @@ %b @ $BG.BB B B@a B@@x@ABkEc_e =@pf@! @ @ NJuJ@N B@B \@x@A AREdJa ffaR =R#O=#%N@M@+NI I QN B@QB@x@A AQEaQ QggaQ =  =Q#kQ*@@~VBZB$ 2 B@B0a@x@AAJEq Jhh =`d- =ǀ{ @}Ā@ !! @VÀV ! VqWT B@aBu@x@AAE3,i"_`w@=={KuOl =WZ`==|ccl&{6 7ib=9`=9$=  @ B.=O`=`A@``@!2@(/ <@ i c @`V <no e> @@@`$ (~ `Q@ @ 2@ @` m!  "@ @ c  !z" "1 W   " @ U<B!`otL ad!jr j@y@Y  @+! !@8`~ @ &T 7 T;`     @] !` mR!c iv   0 V@!s b ` <(`e 'hI`@ASA@E3@Q@@I/A @B@M ; 7@G@1I  @Md @i`D@i"s iYY @(Mu @z i_B@fwupd-1.2.14/plugins/unifying/data/lsusb-U0007-bootloader.txt000066400000000000000000000034761402665037500237220ustar00rootroot00000000000000Bus 001 Device 036: ID 046d:aaaa Logitech, Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xaaaa bcdDevice 1.02 iManufacturer 1 iProduct 2 iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 34 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 4 bmAttributes 0x80 (Bus Powered) MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 25 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 1 fwupd-1.2.14/plugins/unifying/data/lsusb-U0007.txt000066400000000000000000000101341402665037500215570ustar00rootroot00000000000000Bus 001 Device 049: ID 046d:c52b Logitech, Inc. Unifying Receiver Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x046d Logitech, Inc. idProduct 0xc52b Unifying Receiver bcdDevice 12.07 iManufacturer 1 Logitech iProduct 2 USB Receiver iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 84 bNumInterfaces 3 bConfigurationValue 1 iConfiguration 4 RQR12.07_B0029 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 1 Keyboard iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 59 Report Descriptor: (length is 59) Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 8 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 2 Mouse iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 148 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 2 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 93 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 2 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/unifying/data/lsusb-U0008-bootloader-old.txt000066400000000000000000000057551402665037500245010ustar00rootroot00000000000000 Bus 003 Device 036: ID 046d:aaac Logitech, Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xaaac bcdDevice 3.01 iManufacturer 1 Logitech iProduct 2 USB BootLoader iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 34 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 4 BOT03.01_B0008 bmAttributes 0x80 (Bus Powered) MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 25 Report Descriptor: (length is 25) Item(Global): Usage Page, data= [ 0xb0 0xff ] 65456 (null) Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x20 ] 32 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/unifying/data/lsusb-U0008-bootloader.txt000066400000000000000000000057551402665037500237250ustar00rootroot00000000000000 Bus 003 Device 039: ID 046d:aaac Logitech, Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xaaac bcdDevice 3.00 iManufacturer 1 Logitech iProduct 2 USB BootLoader iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 34 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 4 BOT03.00_B0006 bmAttributes 0x80 (Bus Powered) MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 25 Report Descriptor: (length is 25) Item(Global): Usage Page, data= [ 0xb0 0xff ] 65456 (null) Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x20 ] 32 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/unifying/data/lsusb-U0008-old.txt000066400000000000000000000426521402665037500223460ustar00rootroot00000000000000 Bus 003 Device 033: ID 046d:c52b Logitech, Inc. Unifying Receiver Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xc52b Unifying Receiver bcdDevice 24.01 iManufacturer 1 Logitech iProduct 2 USB Receiver iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 84 bNumInterfaces 3 bConfigurationValue 1 iConfiguration 4 RQR24.01_B0023 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 1 Keyboard iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 59 Report Descriptor: (length is 59) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x06 ] 6 Keyboard Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Usage Page, data= [ 0x07 ] 7 Keyboard Item(Local ): Usage Minimum, data= [ 0xe0 ] 224 Control Left Item(Local ): Usage Maximum, data= [ 0xe7 ] 231 GUI Right Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x08 ] 8 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): Input, data= [ 0x03 ] 3 Constant Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x05 ] 5 Item(Global): Usage Page, data= [ 0x08 ] 8 LEDs Item(Local ): Usage Minimum, data= [ 0x01 ] 1 NumLock Item(Local ): Usage Maximum, data= [ 0x05 ] 5 Kana Item(Main ): Output, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x03 ] 3 Item(Main ): Output, data= [ 0x01 ] 1 Constant Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x06 ] 6 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xa4 0x00 ] 164 Item(Global): Usage Page, data= [ 0x07 ] 7 Keyboard Item(Local ): Usage Minimum, data= [ 0x00 ] 0 No Event Item(Local ): Usage Maximum, data= [ 0xa4 0x00 ] 164 ExSel Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 8 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 2 Mouse iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 148 Report Descriptor: (length is 148) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x02 ] 2 Mouse Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x02 ] 2 Item(Local ): Usage, data= [ 0x01 ] 1 Pointer Item(Main ): Collection, data= [ 0x00 ] 0 Physical Item(Global): Usage Page, data= [ 0x09 ] 9 Buttons Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Button 1 (Primary) Item(Local ): Usage Maximum, data= [ 0x10 ] 16 (null) Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x10 ] 16 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Global): Logical Minimum, data= [ 0x01 0xf8 ] 63489 Item(Global): Logical Maximum, data= [ 0xff 0x07 ] 2047 Item(Global): Report Size, data= [ 0x0c ] 12 Item(Global): Report Count, data= [ 0x02 ] 2 Item(Local ): Usage, data= [ 0x30 ] 48 Direction-X Item(Local ): Usage, data= [ 0x31 ] 49 Direction-Y Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Logical Minimum, data= [ 0x81 ] 129 Item(Global): Logical Maximum, data= [ 0x7f ] 127 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Local ): Usage, data= [ 0x38 ] 56 Wheel Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x0c ] 12 Consumer Item(Local ): Usage, data= [ 0x38 0x02 ] 568 AC Pan Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x0c ] 12 Consumer Item(Local ): Usage, data= [ 0x01 ] 1 Consumer Control Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x03 ] 3 Item(Global): Report Size, data= [ 0x10 ] 16 Item(Global): Report Count, data= [ 0x02 ] 2 Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0x8c 0x02 ] 652 Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Consumer Control Item(Local ): Usage Maximum, data= [ 0x8c 0x02 ] 652 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x80 ] 128 System Control Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x04 ] 4 Item(Global): Report Size, data= [ 0x02 ] 2 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0x03 ] 3 Item(Local ): Usage, data= [ 0x82 ] 130 System Sleep Item(Local ): Usage, data= [ 0x81 ] 129 System Power Down Item(Local ): Usage, data= [ 0x83 ] 131 System Wake Up Item(Main ): Input, data= [ 0x60 ] 96 Data Array Absolute No_Wrap Linear No_Preferred_State Null_State Non_Volatile Bitfield Item(Global): Report Size, data= [ 0x06 ] 6 Item(Main ): Input, data= [ 0x03 ] 3 Constant Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0xbc 0xff ] 65468 (null) Item(Local ): Usage, data= [ 0x88 ] 136 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x08 ] 8 Item(Local ): Usage Minimum, data= [ 0x01 ] 1 (null) Item(Local ): Usage Maximum, data= [ 0xff ] 255 (null) Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 2 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 98 Report Descriptor: (length is 98) Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x10 ] 16 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x06 ] 6 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x11 ] 17 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x13 ] 19 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x04 ] 4 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x20 ] 32 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x0e ] 14 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x41 ] 65 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x41 ] 65 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report ID, data= [ 0x21 ] 33 Item(Global): Report Count, data= [ 0x1f ] 31 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x42 ] 66 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x42 ] 66 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 2 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/unifying/data/lsusb-U0008.txt000066400000000000000000000426511402665037500215710ustar00rootroot00000000000000Bus 003 Device 032: ID 046d:c52b Logitech, Inc. Unifying Receiver Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xc52b Unifying Receiver bcdDevice 24.05 iManufacturer 1 Logitech iProduct 2 USB Receiver iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 84 bNumInterfaces 3 bConfigurationValue 1 iConfiguration 4 RQR24.05_B0029 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 1 Keyboard iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 59 Report Descriptor: (length is 59) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x06 ] 6 Keyboard Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Usage Page, data= [ 0x07 ] 7 Keyboard Item(Local ): Usage Minimum, data= [ 0xe0 ] 224 Control Left Item(Local ): Usage Maximum, data= [ 0xe7 ] 231 GUI Right Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x08 ] 8 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): Input, data= [ 0x03 ] 3 Constant Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x05 ] 5 Item(Global): Usage Page, data= [ 0x08 ] 8 LEDs Item(Local ): Usage Minimum, data= [ 0x01 ] 1 NumLock Item(Local ): Usage Maximum, data= [ 0x05 ] 5 Kana Item(Main ): Output, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x03 ] 3 Item(Main ): Output, data= [ 0x01 ] 1 Constant Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x06 ] 6 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xa4 0x00 ] 164 Item(Global): Usage Page, data= [ 0x07 ] 7 Keyboard Item(Local ): Usage Minimum, data= [ 0x00 ] 0 No Event Item(Local ): Usage Maximum, data= [ 0xa4 0x00 ] 164 ExSel Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 8 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 2 Mouse iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 148 Report Descriptor: (length is 148) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x02 ] 2 Mouse Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x02 ] 2 Item(Local ): Usage, data= [ 0x01 ] 1 Pointer Item(Main ): Collection, data= [ 0x00 ] 0 Physical Item(Global): Usage Page, data= [ 0x09 ] 9 Buttons Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Button 1 (Primary) Item(Local ): Usage Maximum, data= [ 0x10 ] 16 (null) Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x10 ] 16 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Global): Logical Minimum, data= [ 0x01 0xf8 ] 63489 Item(Global): Logical Maximum, data= [ 0xff 0x07 ] 2047 Item(Global): Report Size, data= [ 0x0c ] 12 Item(Global): Report Count, data= [ 0x02 ] 2 Item(Local ): Usage, data= [ 0x30 ] 48 Direction-X Item(Local ): Usage, data= [ 0x31 ] 49 Direction-Y Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Logical Minimum, data= [ 0x81 ] 129 Item(Global): Logical Maximum, data= [ 0x7f ] 127 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Local ): Usage, data= [ 0x38 ] 56 Wheel Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x0c ] 12 Consumer Item(Local ): Usage, data= [ 0x38 0x02 ] 568 AC Pan Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x0c ] 12 Consumer Item(Local ): Usage, data= [ 0x01 ] 1 Consumer Control Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x03 ] 3 Item(Global): Report Size, data= [ 0x10 ] 16 Item(Global): Report Count, data= [ 0x02 ] 2 Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0x8c 0x02 ] 652 Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Consumer Control Item(Local ): Usage Maximum, data= [ 0x8c 0x02 ] 652 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x80 ] 128 System Control Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x04 ] 4 Item(Global): Report Size, data= [ 0x02 ] 2 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0x03 ] 3 Item(Local ): Usage, data= [ 0x82 ] 130 System Sleep Item(Local ): Usage, data= [ 0x81 ] 129 System Power Down Item(Local ): Usage, data= [ 0x83 ] 131 System Wake Up Item(Main ): Input, data= [ 0x60 ] 96 Data Array Absolute No_Wrap Linear No_Preferred_State Null_State Non_Volatile Bitfield Item(Global): Report Size, data= [ 0x06 ] 6 Item(Main ): Input, data= [ 0x03 ] 3 Constant Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0xbc 0xff ] 65468 (null) Item(Local ): Usage, data= [ 0x88 ] 136 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x08 ] 8 Item(Local ): Usage Minimum, data= [ 0x01 ] 1 (null) Item(Local ): Usage Maximum, data= [ 0xff ] 255 (null) Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 2 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 98 Report Descriptor: (length is 98) Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x10 ] 16 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x06 ] 6 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x11 ] 17 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x13 ] 19 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x04 ] 4 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x20 ] 32 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x0e ] 14 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x41 ] 65 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x41 ] 65 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report ID, data= [ 0x21 ] 33 Item(Global): Report Count, data= [ 0x1f ] 31 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x42 ] 66 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x42 ] 66 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 2 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/unifying/fu-plugin-unifying.c000066400000000000000000000123761402665037500221560ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-plugin-vfuncs.h" #include "fu-unifying-bootloader-nordic.h" #include "fu-unifying-bootloader-texas.h" #include "fu-unifying-common.h" #include "fu-unifying-peripheral.h" #include "fu-unifying-runtime.h" gboolean fu_plugin_update_detach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_detach (device, error); } gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_attach (device, error); } gboolean fu_plugin_update_reload (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } static gboolean fu_plugin_unifying_check_supported_device (FuPlugin *plugin, FuDevice *device) { GPtrArray *guids = fu_device_get_guids (device); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index (guids, i); if (fu_plugin_check_supported (plugin, guid)) return TRUE; } return FALSE; } gboolean fu_plugin_udev_device_added (FuPlugin *plugin, FuUdevDevice *device, GError **error) { g_autoptr(FuDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* interesting device? */ if (g_strcmp0 (fu_udev_device_get_subsystem (device), "hidraw") != 0) return TRUE; /* logitech */ if (fu_udev_device_get_vendor (device) != FU_UNIFYING_DEVICE_VID) return TRUE; /* runtime */ if (fu_device_has_custom_flag (FU_DEVICE (device), "is-receiver")) { dev = g_object_new (FU_TYPE_UNIFYING_RUNTIME, "version-format", FWUPD_VERSION_FORMAT_PLAIN, NULL); fu_device_incorporate (dev, FU_DEVICE (device)); } else { /* create device so we can run ->probe() and add UFY GUIDs */ dev = g_object_new (FU_TYPE_UNIFYING_PERIPHERAL, "version-format", FWUPD_VERSION_FORMAT_PLAIN, NULL); fu_device_incorporate (dev, FU_DEVICE (device)); if (!fu_device_probe (dev, error)) return FALSE; /* there are a lot of unifying peripherals, but not all respond * well to opening -- so limit to ones with issued updates */ if (!fu_plugin_unifying_check_supported_device (plugin, dev)) { g_autofree gchar *guids = fu_device_get_guids_as_str (FU_DEVICE (device)); g_debug ("%s has no updates, so ignoring device", guids); return TRUE; } } /* open to get the version */ locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, dev); return TRUE; } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* logitech */ if (fu_usb_device_get_vid (device) != FU_UNIFYING_DEVICE_VID) return TRUE; /* check is bootloader */ if (!fu_device_has_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug ("not in bootloader mode, ignoring"); return TRUE; } if (fu_device_has_custom_flag (FU_DEVICE (device), "is-nordic")) { dev = g_object_new (FU_TYPE_UNIFYING_BOOTLOADER_NORDIC, "version-format", FWUPD_VERSION_FORMAT_PLAIN, NULL); fu_device_incorporate (dev, FU_DEVICE (device)); } else if (fu_device_has_custom_flag (FU_DEVICE (device), "is-texas")) { dev = g_object_new (FU_TYPE_UNIFYING_BOOTLOADER_TEXAS, "version-format", FWUPD_VERSION_FORMAT_PLAIN, NULL); fu_device_incorporate (dev, FU_DEVICE (device)); g_usleep (200*1000); } /* not supported */ if (dev == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "bootloader device not supported"); return FALSE; } /* open to get the version */ locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, dev); return TRUE; } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { /* check the kernel has CONFIG_HIDRAW */ if (!g_file_test ("/sys/class/hidraw", G_FILE_TEST_IS_DIR)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no kernel support for CONFIG_HIDRAW"); return FALSE; } return TRUE; } void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.logitech.unifying"); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.logitech.unifyingsigned"); fu_plugin_add_udev_subsystem (plugin, "hidraw"); } fwupd-1.2.14/plugins/unifying/fu-unifying-bootloader-nordic.c000066400000000000000000000203311402665037500242540ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-unifying-common.h" #include "fu-unifying-bootloader-nordic.h" struct _FuUnifyingBootloaderNordic { FuUnifyingBootloader parent_instance; }; G_DEFINE_TYPE (FuUnifyingBootloaderNordic, fu_unifying_bootloader_nordic, FU_TYPE_UNIFYING_BOOTLOADER) static gchar * fu_unifying_bootloader_nordic_get_hw_platform_id (FuUnifyingBootloader *self, GError **error) { g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_GET_HW_PLATFORM_ID; if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to get HW ID: "); return NULL; } return g_strndup ((const gchar *) req->data, req->len); } static gchar * fu_unifying_bootloader_nordic_get_fw_version (FuUnifyingBootloader *self, GError **error) { guint16 micro; g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_GET_FW_VERSION; if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to get firmware version: "); return NULL; } /* RRRxx.yy_Bzzzz * 012345678901234*/ micro = (guint16) fu_unifying_buffer_read_uint8 ((const gchar *) req->data + 10) << 8; micro += fu_unifying_buffer_read_uint8 ((const gchar *) req->data + 12); return fu_unifying_format_version ("RQR", fu_unifying_buffer_read_uint8 ((const gchar *) req->data + 3), fu_unifying_buffer_read_uint8 ((const gchar *) req->data + 6), micro); } static gboolean fu_unifying_bootloader_nordic_setup (FuUnifyingBootloader *self, GError **error) { g_autofree gchar *hw_platform_id = NULL; g_autofree gchar *version_fw = NULL; g_autoptr(GError) error_local = NULL; /* get MCU */ hw_platform_id = fu_unifying_bootloader_nordic_get_hw_platform_id (self, error); if (hw_platform_id == NULL) return FALSE; g_debug ("hw-platform-id=%s", hw_platform_id); /* get firmware version, which is not fatal */ version_fw = fu_unifying_bootloader_nordic_get_fw_version (self, &error_local); if (version_fw == NULL) { g_warning ("failed to get firmware version: %s", error_local->message); fu_device_set_version (FU_DEVICE (self), "RQR12.00_B0000", FWUPD_VERSION_FORMAT_PLAIN); } else { fu_device_set_version (FU_DEVICE (self), version_fw, FWUPD_VERSION_FORMAT_PLAIN); } return TRUE; } static gboolean fu_unifying_bootloader_nordic_write_signature (FuUnifyingBootloader *self, guint16 addr, guint8 len, const guint8 *data, GError **error) { g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new(); req->cmd = 0xC0; req->addr = addr; req->len = len; memcpy (req->data, data, req->len); if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to write sig @0x%02x: ", addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER_INVALID_ADDR) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: signature is too big", addr); return FALSE; } return TRUE; } static gboolean fu_unifying_bootloader_nordic_write (FuUnifyingBootloader *self, guint16 addr, guint8 len, const guint8 *data, GError **error) { g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_WRITE; req->addr = addr; req->len = len; if (req->len > 28) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: data length too large %02x", addr, req->len); return FALSE; } memcpy (req->data, data, req->len); if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to transfer fw @0x%02x: ", addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_WRITE_INVALID_ADDR) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: invalid address", addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_WRITE_VERIFY_FAIL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: failed to verify flash content", addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_WRITE_NONZERO_START) { g_debug ("wrote %d bytes at address %04x, value %02x", req->len, req->addr, req->data[0]); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: only 1 byte write of 0xff supported", addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_WRITE_INVALID_CRC) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: invalid CRC", addr); return FALSE; } return TRUE; } static gboolean fu_unifying_bootloader_nordic_erase (FuUnifyingBootloader *self, guint16 addr, GError **error) { g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_ERASE_PAGE; req->addr = addr; req->len = 0x01; if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to erase fw @0x%02x: ", addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_ERASE_PAGE_INVALID_ADDR) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to erase @%04x: invalid page", addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_ERASE_PAGE_NONZERO_START) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to erase @%04x: byte 0x00 is not 0xff", addr); return FALSE; } return TRUE; } static gboolean fu_unifying_bootloader_nordic_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuUnifyingBootloader *self = FU_UNIFYING_BOOTLOADER (device); const FuUnifyingBootloaderRequest *payload; guint16 addr; g_autoptr(GPtrArray) reqs = NULL; /* erase firmware pages up to the bootloader */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); for (addr = fu_unifying_bootloader_get_addr_lo (self); addr < fu_unifying_bootloader_get_addr_hi (self); addr += fu_unifying_bootloader_get_blocksize (self)) { if (!fu_unifying_bootloader_nordic_erase (self, addr, error)) return FALSE; } /* transfer payload */ reqs = fu_unifying_bootloader_parse_requests (self, fw, error); if (reqs == NULL) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 1; i < reqs->len; i++) { gboolean res; payload = g_ptr_array_index (reqs, i); if (payload->cmd == FU_UNIFYING_BOOTLOADER_CMD_WRITE_SIGNATURE) { res = fu_unifying_bootloader_nordic_write_signature (self, payload->addr, payload->len, payload->data, error); } else { res = fu_unifying_bootloader_nordic_write (self, payload->addr, payload->len, payload->data, error); } if (!res) return FALSE; fu_device_set_progress_full (device, i * 32, reqs->len * 32); } /* send the first managed packet last, excluding the reset vector */ payload = g_ptr_array_index (reqs, 0); if (!fu_unifying_bootloader_nordic_write (self, payload->addr + 1, payload->len - 1, payload->data + 1, error)) return FALSE; if (!fu_unifying_bootloader_nordic_write (self, 0x0000, 0x01, payload->data, error)) return FALSE; /* mark as complete */ fu_device_set_progress_full (device, reqs->len * 32, reqs->len * 32); /* success! */ return TRUE; } static void fu_unifying_bootloader_nordic_class_init (FuUnifyingBootloaderNordicClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUnifyingBootloaderClass *klass_device_bootloader = FU_UNIFYING_BOOTLOADER_CLASS (klass); klass_device->write_firmware = fu_unifying_bootloader_nordic_write_firmware; klass_device_bootloader->setup = fu_unifying_bootloader_nordic_setup; } static void fu_unifying_bootloader_nordic_init (FuUnifyingBootloaderNordic *self) { } fwupd-1.2.14/plugins/unifying/fu-unifying-bootloader-nordic.h000066400000000000000000000006361402665037500242670ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-unifying-bootloader.h" G_BEGIN_DECLS #define FU_TYPE_UNIFYING_BOOTLOADER_NORDIC (fu_unifying_bootloader_nordic_get_type ()) G_DECLARE_FINAL_TYPE (FuUnifyingBootloaderNordic, fu_unifying_bootloader_nordic, FU, UNIFYING_BOOTLOADER_NORDIC, FuUnifyingBootloader) G_END_DECLS fwupd-1.2.14/plugins/unifying/fu-unifying-bootloader-texas.c000066400000000000000000000156241402665037500241330ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-unifying-common.h" #include "fu-unifying-bootloader-texas.h" struct _FuUnifyingBootloaderTexas { FuUnifyingBootloader parent_instance; }; G_DEFINE_TYPE (FuUnifyingBootloaderTexas, fu_unifying_bootloader_texas, FU_TYPE_UNIFYING_BOOTLOADER) static gboolean fu_unifying_bootloader_texas_erase_all (FuUnifyingBootloader *self, GError **error) { g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM; req->len = 0x01; /* magic number */ req->data[0] = 0x00; /* magic number */ if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to erase all pages: "); return FALSE; } return TRUE; } static gboolean fu_unifying_bootloader_texas_compute_and_test_crc (FuUnifyingBootloader *self, GError **error) { g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM; req->len = 0x01; /* magic number */ req->data[0] = 0x03; /* magic number */ if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to compute and test CRC: "); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_WRONG_CRC) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "CRC is incorrect"); return FALSE; } return TRUE; } static gboolean fu_unifying_bootloader_texas_flash_ram_buffer (FuUnifyingBootloader *self, guint16 addr, GError **error) { g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM; req->addr = addr; req->len = 0x01; /* magic number */ req->data[0] = 0x01; /* magic number */ if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to flash ram buffer @%04x: ", addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_INVALID_ADDR) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to flash ram buffer @%04x: invalid flash page", addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_PAGE0_INVALID) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to flash ram buffer @%04x: invalid App JMP vector", addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_INVALID_ORDER) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to flash ram buffer @%04x: page flashed before page 0", addr); return FALSE; } return TRUE; } static gboolean fu_unifying_bootloader_texas_clear_ram_buffer (FuUnifyingBootloader *self, GError **error) { g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM; req->addr = 0x0000; req->len = 0x01; /* magic number */ req->data[0] = 0x02; /* magic number */ if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to clear ram buffer @%04x: ", req->addr); return FALSE; } return TRUE; } static gboolean fu_unifying_bootloader_texas_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuUnifyingBootloader *self = FU_UNIFYING_BOOTLOADER (device); const FuUnifyingBootloaderRequest *payload; g_autoptr(GPtrArray) reqs = NULL; g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); /* transfer payload */ reqs = fu_unifying_bootloader_parse_requests (self, fw, error); if (reqs == NULL) return FALSE; /* erase all flash pages */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_unifying_bootloader_texas_erase_all (self, error)) return FALSE; /* set existing RAM buffer to 0xff's */ if (!fu_unifying_bootloader_texas_clear_ram_buffer (self, error)) return FALSE; /* write to RAM buffer */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < reqs->len; i++) { payload = g_ptr_array_index (reqs, i); /* check size */ if (payload->len != 16) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "payload size invalid @%04x: got 0x%02x", payload->addr, payload->len); return FALSE; } /* build packet */ req->cmd = payload->cmd; /* signature addresses do not need to fit inside 128 bytes */ if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_WRITE_SIGNATURE) req->addr = payload->addr; else req->addr = payload->addr % 0x80; req->len = payload->len; memcpy (req->data, payload->data, payload->len); if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to write ram buffer @0x%02x: ", req->addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER_INVALID_ADDR) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write ram buffer @%04x: invalid location", req->addr); return FALSE; } if (req->cmd == FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER_OVERFLOW) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write ram buffer @%04x: invalid size 0x%02x", req->addr, req->len); return FALSE; } /* flush RAM buffer to EEPROM */ if ((payload->addr + 0x10) % 0x80 == 0 && req->cmd != FU_UNIFYING_BOOTLOADER_CMD_WRITE_SIGNATURE) { guint16 addr_start = payload->addr - (7 * 0x10); g_debug ("addr flush @ 0x%04x for 0x%04x", payload->addr, addr_start); if (!fu_unifying_bootloader_texas_flash_ram_buffer (self, addr_start, error)) { g_prefix_error (error, "failed to flash ram buffer @0x%04x: ", addr_start); return FALSE; } } /* update progress */ fu_device_set_progress_full (device, i * 32, reqs->len * 32); } /* check CRC */ if (!fu_unifying_bootloader_texas_compute_and_test_crc (self, error)) return FALSE; /* mark as complete */ fu_device_set_progress_full (device, reqs->len * 32, reqs->len * 32); /* success! */ return TRUE; } static gboolean fu_unifying_bootloader_texas_setup (FuUnifyingBootloader *self, GError **error) { fu_device_set_version (FU_DEVICE (self), "RQR24.00_B0000", FWUPD_VERSION_FORMAT_PLAIN); return TRUE; } static void fu_unifying_bootloader_texas_class_init (FuUnifyingBootloaderTexasClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUnifyingBootloaderClass *klass_device_bootloader = FU_UNIFYING_BOOTLOADER_CLASS (klass); klass_device->write_firmware = fu_unifying_bootloader_texas_write_firmware; klass_device_bootloader->setup = fu_unifying_bootloader_texas_setup; } static void fu_unifying_bootloader_texas_init (FuUnifyingBootloaderTexas *self) { } fwupd-1.2.14/plugins/unifying/fu-unifying-bootloader-texas.h000066400000000000000000000006311402665037500241300ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-unifying-bootloader.h" G_BEGIN_DECLS #define FU_TYPE_UNIFYING_BOOTLOADER_TEXAS (fu_unifying_bootloader_texas_get_type ()) G_DECLARE_FINAL_TYPE (FuUnifyingBootloaderTexas, fu_unifying_bootloader_texas, FU, UNIFYING_BOOTLOADER_TEXAS, FuUnifyingBootloader) G_END_DECLS fwupd-1.2.14/plugins/unifying/fu-unifying-bootloader.c000066400000000000000000000307231402665037500230060ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-unifying-common.h" #include "fu-unifying-bootloader.h" #include "fu-unifying-hidpp.h" typedef struct { guint16 flash_addr_lo; guint16 flash_addr_hi; guint16 flash_blocksize; } FuUnifyingBootloaderPrivate; #define FU_UNIFYING_DEVICE_EP1 0x81 #define FU_UNIFYING_DEVICE_EP3 0x83 G_DEFINE_TYPE_WITH_PRIVATE (FuUnifyingBootloader, fu_unifying_bootloader, FU_TYPE_USB_DEVICE) #define GET_PRIVATE(o) (fu_unifying_bootloader_get_instance_private (o)) static void fu_unifying_bootloader_to_string (FuDevice *device, GString *str) { FuUnifyingBootloader *self = FU_UNIFYING_BOOTLOADER (device); FuUnifyingBootloaderPrivate *priv = GET_PRIVATE (self); g_string_append_printf (str, " FlashAddrHigh:\t0x%04x\n", priv->flash_addr_hi); g_string_append_printf (str, " FlashAddrLow:\t0x%04x\n", priv->flash_addr_lo); g_string_append_printf (str, " FlashBlockSize:\t0x%04x\n", priv->flash_blocksize); } FuUnifyingBootloaderRequest * fu_unifying_bootloader_request_new (void) { FuUnifyingBootloaderRequest *req = g_new0 (FuUnifyingBootloaderRequest, 1); return req; } GPtrArray * fu_unifying_bootloader_parse_requests (FuUnifyingBootloader *self, GBytes *fw, GError **error) { const gchar *tmp; g_auto(GStrv) lines = NULL; g_autoptr(GPtrArray) reqs = NULL; guint32 last_addr = 0; reqs = g_ptr_array_new_with_free_func (g_free); tmp = g_bytes_get_data (fw, NULL); lines = g_strsplit_set (tmp, "\n\r", -1); for (guint i = 0; lines[i] != NULL; i++) { g_autoptr(FuUnifyingBootloaderRequest) payload = NULL; guint8 rec_type = 0x00; /* skip empty lines */ tmp = lines[i]; if (strlen (tmp) < 5) continue; payload = fu_unifying_bootloader_request_new (); payload->len = fu_unifying_buffer_read_uint8 (tmp + 0x01); if (payload->len > 28) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware data invalid: too large %u bytes", payload->len); return NULL; } payload->addr = ((guint16) fu_unifying_buffer_read_uint8 (tmp + 0x03)) << 8; payload->addr |= fu_unifying_buffer_read_uint8 (tmp + 0x05); rec_type = fu_unifying_buffer_read_uint8 (tmp + 0x07); /* record type of 0xFD indicates signature data */ if (rec_type == 0xFD) { payload->cmd = FU_UNIFYING_BOOTLOADER_CMD_WRITE_SIGNATURE; } else { payload->cmd = FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER; } /* read the data, but skip the checksum byte */ for (guint j = 0; j < payload->len; j++) { const gchar *ptr = tmp + 0x09 + (j * 2); if (ptr[0] == '\0') { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware data invalid: expected %u bytes", payload->len); return NULL; } payload->data[j] = fu_unifying_buffer_read_uint8 (ptr); } /* no need to bound check signature addresses */ if (payload->cmd == FU_UNIFYING_BOOTLOADER_CMD_WRITE_SIGNATURE) { g_ptr_array_add (reqs, g_steal_pointer (&payload)); continue; } /* skip the bootloader */ if (payload->addr > fu_unifying_bootloader_get_addr_hi (self)) { g_debug ("skipping write @ %04x", payload->addr); continue; } /* skip the header */ if (payload->addr < fu_unifying_bootloader_get_addr_lo (self)) { g_debug ("skipping write @ %04x", payload->addr); continue; } /* make sure firmware addresses only go up */ if (payload->addr < last_addr) { g_debug ("skipping write @ %04x", payload->addr); continue; } last_addr = payload->addr; /* pending */ g_ptr_array_add (reqs, g_steal_pointer (&payload)); } if (reqs->len == 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware data invalid: no payloads found"); return NULL; } return g_steal_pointer (&reqs); } guint16 fu_unifying_bootloader_get_addr_lo (FuUnifyingBootloader *self) { FuUnifyingBootloaderPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UNIFYING_BOOTLOADER (self), 0x0000); return priv->flash_addr_lo; } guint16 fu_unifying_bootloader_get_addr_hi (FuUnifyingBootloader *self) { FuUnifyingBootloaderPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UNIFYING_BOOTLOADER (self), 0x0000); return priv->flash_addr_hi; } guint16 fu_unifying_bootloader_get_blocksize (FuUnifyingBootloader *self) { FuUnifyingBootloaderPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UNIFYING_BOOTLOADER (self), 0x0000); return priv->flash_blocksize; } static gboolean fu_unifying_bootloader_attach (FuDevice *device, GError **error) { FuUnifyingBootloader *self = FU_UNIFYING_BOOTLOADER (device); g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_REBOOT; if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to attach back to runtime: "); return FALSE; } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_unifying_bootloader_set_bl_version (FuUnifyingBootloader *self, GError **error) { guint16 build; g_autofree gchar *version = NULL; g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); /* call into hardware */ req->cmd = FU_UNIFYING_BOOTLOADER_CMD_GET_BL_VERSION; if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to get firmware version: "); return FALSE; } /* BOTxx.yy_Bzzzz * 012345678901234 */ build = (guint16) fu_unifying_buffer_read_uint8 ((const gchar *) req->data + 10) << 8; build += fu_unifying_buffer_read_uint8 ((const gchar *) req->data + 12); version = fu_unifying_format_version ("BOT", fu_unifying_buffer_read_uint8 ((const gchar *) req->data + 3), fu_unifying_buffer_read_uint8 ((const gchar *) req->data + 6), build); if (version == NULL) { g_prefix_error (error, "failed to format firmware version: "); return FALSE; } fu_device_set_version_bootloader (FU_DEVICE (self), version); return TRUE; } static gboolean fu_unifying_bootloader_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); const guint idx = 0x00; /* claim the only interface */ if (!g_usb_device_claim_interface (usb_device, idx, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "Failed to claim 0x%02x: ", idx); return FALSE; } /* success */ return TRUE; } static gboolean fu_unifying_bootloader_setup (FuDevice *device, GError **error) { FuUnifyingBootloaderClass *klass = FU_UNIFYING_BOOTLOADER_GET_CLASS (device); FuUnifyingBootloader *self = FU_UNIFYING_BOOTLOADER (device); FuUnifyingBootloaderPrivate *priv = GET_PRIVATE (self); g_autoptr(FuUnifyingBootloaderRequest) req = fu_unifying_bootloader_request_new (); /* get memory map */ req->cmd = FU_UNIFYING_BOOTLOADER_CMD_GET_MEMINFO; if (!fu_unifying_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to get meminfo: "); return FALSE; } if (req->len != 0x06) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get meminfo: invalid size %02x", req->len); return FALSE; } /* parse values */ priv->flash_addr_lo = fu_common_read_uint16 (req->data + 0, G_BIG_ENDIAN); priv->flash_addr_hi = fu_common_read_uint16 (req->data + 2, G_BIG_ENDIAN); priv->flash_blocksize = fu_common_read_uint16 (req->data + 4, G_BIG_ENDIAN); /* get bootloader version */ if (!fu_unifying_bootloader_set_bl_version (self, error)) return FALSE; /* subclassed further */ if (klass->setup != NULL) return klass->setup (self, error); /* success */ return TRUE; } static gboolean fu_unifying_bootloader_close (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); if (usb_device != NULL) { if (!g_usb_device_release_interface (usb_device, 0x00, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { return FALSE; } } return TRUE; } gboolean fu_unifying_bootloader_request (FuUnifyingBootloader *self, FuUnifyingBootloaderRequest *req, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_length = 0; guint8 buf_request[32]; guint8 buf_response[32]; /* build packet */ memset (buf_request, 0x00, sizeof (buf_request)); buf_request[0x00] = req->cmd; buf_request[0x01] = req->addr >> 8; buf_request[0x02] = req->addr & 0xff; buf_request[0x03] = req->len; memcpy (buf_request + 0x04, req->data, 28); /* send request */ if (g_getenv ("FWUPD_UNIFYING_VERBOSE") != NULL) { fu_common_dump_raw (G_LOG_DOMAIN, "host->device", buf_request, sizeof (buf_request)); } if (usb_device != NULL) { if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_SET, 0x0200, 0x0000, buf_request, sizeof (buf_request), &actual_length, FU_UNIFYING_DEVICE_TIMEOUT_MS, NULL, error)) { g_prefix_error (error, "failed to send data: "); return FALSE; } } /* no response required when rebooting */ if (usb_device != NULL && req->cmd == FU_UNIFYING_BOOTLOADER_CMD_REBOOT) { g_autoptr(GError) error_ignore = NULL; if (!g_usb_device_interrupt_transfer (usb_device, FU_UNIFYING_DEVICE_EP1, buf_response, sizeof (buf_response), &actual_length, FU_UNIFYING_DEVICE_TIMEOUT_MS, NULL, &error_ignore)) { g_debug ("ignoring: %s", error_ignore->message); } else { if (g_getenv ("FWUPD_UNIFYING_VERBOSE") != NULL) { fu_common_dump_raw (G_LOG_DOMAIN, "device->host", buf_response, actual_length); } } return TRUE; } /* get response */ memset (buf_response, 0x00, sizeof (buf_response)); if (usb_device != NULL) { if (!g_usb_device_interrupt_transfer (usb_device, FU_UNIFYING_DEVICE_EP1, buf_response, sizeof (buf_response), &actual_length, FU_UNIFYING_DEVICE_TIMEOUT_MS, NULL, error)) { g_prefix_error (error, "failed to get data: "); return FALSE; } } else { /* emulated */ buf_response[0] = buf_request[0]; if (buf_response[0] == FU_UNIFYING_BOOTLOADER_CMD_GET_MEMINFO) { buf_response[3] = 0x06; /* len */ buf_response[4] = 0x40; /* lo MSB */ buf_response[5] = 0x00; /* lo LSB */ buf_response[6] = 0x6b; /* hi MSB */ buf_response[7] = 0xff; /* hi LSB */ buf_response[8] = 0x00; /* bs MSB */ buf_response[9] = 0x80; /* bs LSB */ } actual_length = sizeof (buf_response); } if (g_getenv ("FWUPD_UNIFYING_VERBOSE") != NULL) { fu_common_dump_raw (G_LOG_DOMAIN, "device->host", buf_response, actual_length); } /* parse response */ if ((buf_response[0x00] & 0xf0) != req->cmd) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid command response of %02x, expected %02x", buf_response[0x00], req->cmd); return FALSE; } req->cmd = buf_response[0x00]; req->addr = ((guint16) buf_response[0x01] << 8) + buf_response[0x02]; req->len = buf_response[0x03]; if (req->len > 28) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid data size of %02x", req->len); return FALSE; } memset (req->data, 0x00, 28); if (req->len > 0) memcpy (req->data, buf_response + 0x04, req->len); return TRUE; } static void fu_unifying_bootloader_init (FuUnifyingBootloader *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_icon (FU_DEVICE (self), "preferences-desktop-keyboard"); fu_device_set_name (FU_DEVICE (self), "Unifying Receiver"); fu_device_set_summary (FU_DEVICE (self), "A miniaturised USB wireless receiver (bootloader)"); fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_unifying_bootloader_class_init (FuUnifyingBootloaderClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->to_string = fu_unifying_bootloader_to_string; klass_device->attach = fu_unifying_bootloader_attach; klass_device->setup = fu_unifying_bootloader_setup; klass_usb_device->open = fu_unifying_bootloader_open; klass_usb_device->close = fu_unifying_bootloader_close; } fwupd-1.2.14/plugins/unifying/fu-unifying-bootloader.h000066400000000000000000000056431402665037500230160ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-usb-device.h" G_BEGIN_DECLS #define FU_TYPE_UNIFYING_BOOTLOADER (fu_unifying_bootloader_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuUnifyingBootloader, fu_unifying_bootloader, FU, UNIFYING_BOOTLOADER, FuUsbDevice) struct _FuUnifyingBootloaderClass { FuUsbDeviceClass parent_class; gboolean (*setup) (FuUnifyingBootloader *self, GError **error); }; typedef enum { FU_UNIFYING_BOOTLOADER_CMD_GENERAL_ERROR = 0x01, FU_UNIFYING_BOOTLOADER_CMD_READ = 0x10, FU_UNIFYING_BOOTLOADER_CMD_WRITE = 0x20, FU_UNIFYING_BOOTLOADER_CMD_WRITE_INVALID_ADDR = 0x21, FU_UNIFYING_BOOTLOADER_CMD_WRITE_VERIFY_FAIL = 0x22, FU_UNIFYING_BOOTLOADER_CMD_WRITE_NONZERO_START = 0x23, FU_UNIFYING_BOOTLOADER_CMD_WRITE_INVALID_CRC = 0x24, FU_UNIFYING_BOOTLOADER_CMD_ERASE_PAGE = 0x30, FU_UNIFYING_BOOTLOADER_CMD_ERASE_PAGE_INVALID_ADDR = 0x31, FU_UNIFYING_BOOTLOADER_CMD_ERASE_PAGE_NONZERO_START = 0x33, FU_UNIFYING_BOOTLOADER_CMD_GET_HW_PLATFORM_ID = 0x40, FU_UNIFYING_BOOTLOADER_CMD_GET_FW_VERSION = 0x50, FU_UNIFYING_BOOTLOADER_CMD_GET_CHECKSUM = 0x60, FU_UNIFYING_BOOTLOADER_CMD_REBOOT = 0x70, FU_UNIFYING_BOOTLOADER_CMD_GET_MEMINFO = 0x80, FU_UNIFYING_BOOTLOADER_CMD_GET_BL_VERSION = 0x90, FU_UNIFYING_BOOTLOADER_CMD_GET_INIT_FW_VERSION = 0xa0, FU_UNIFYING_BOOTLOADER_CMD_READ_SIGNATURE = 0xb0, FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER = 0xc0, FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER_INVALID_ADDR= 0xc1, FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER_OVERFLOW = 0xc2, FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM = 0xd0, FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_INVALID_ADDR = 0xd1, FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_WRONG_CRC = 0xd2, FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_PAGE0_INVALID = 0xd3, FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_INVALID_ORDER = 0xd4, FU_UNIFYING_BOOTLOADER_CMD_WRITE_SIGNATURE = 0xe0, FU_UNIFYING_BOOTLOADER_CMD_LAST } FuUnifyingBootloaderCmd; /* packet to and from device */ typedef struct __attribute__((packed)) { guint8 cmd; guint16 addr; guint8 len; guint8 data[28]; } FuUnifyingBootloaderRequest; FuUnifyingBootloaderRequest *fu_unifying_bootloader_request_new (void); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUnifyingBootloaderRequest, g_free); #pragma clang diagnostic pop GPtrArray *fu_unifying_bootloader_parse_requests (FuUnifyingBootloader *self, GBytes *fw, GError **error); gboolean fu_unifying_bootloader_request (FuUnifyingBootloader *self, FuUnifyingBootloaderRequest *req, GError **error); guint16 fu_unifying_bootloader_get_addr_lo (FuUnifyingBootloader *self); guint16 fu_unifying_bootloader_get_addr_hi (FuUnifyingBootloader *self); guint16 fu_unifying_bootloader_get_blocksize (FuUnifyingBootloader *self); G_END_DECLS fwupd-1.2.14/plugins/unifying/fu-unifying-common.c000066400000000000000000000017451402665037500221460ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-unifying-common.h" guint8 fu_unifying_buffer_read_uint8 (const gchar *str) { guint64 tmp; gchar buf[3] = { 0x0, 0x0, 0x0 }; memcpy (buf, str, 2); tmp = g_ascii_strtoull (buf, NULL, 16); return tmp; } guint16 fu_unifying_buffer_read_uint16 (const gchar *str) { guint64 tmp; gchar buf[5] = { 0x0, 0x0, 0x0, 0x0, 0x0 }; memcpy (buf, str, 4); tmp = g_ascii_strtoull (buf, NULL, 16); return tmp; } gchar * fu_unifying_format_version (const gchar *name, guint8 major, guint8 minor, guint16 build) { GString *str = g_string_new (NULL); for (guint i = 0; i < 3; i++) { if (g_ascii_isspace (name[i])) continue; g_string_append_c (str, name[i]); } g_string_append_printf (str, "%02x.%02x_B%04x", major, minor, build); return g_string_free (str, FALSE); } fwupd-1.2.14/plugins/unifying/fu-unifying-common.h000066400000000000000000000015231402665037500221450ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FU_UNIFYING_DEVICE_VID 0x046d #define FU_UNIFYING_DEVICE_PID_RUNTIME 0xc52b #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_NORDIC 0xaaaa #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_NORDIC_PICO 0xaaae #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_TEXAS 0xaaac #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_TEXAS_PICO 0xaaad /* Signed firmware are very long to verify on the device */ #define FU_UNIFYING_DEVICE_TIMEOUT_MS 20000 guint8 fu_unifying_buffer_read_uint8 (const gchar *str); guint16 fu_unifying_buffer_read_uint16 (const gchar *str); gchar *fu_unifying_format_version (const gchar *name, guint8 major, guint8 minor, guint16 build); G_END_DECLS fwupd-1.2.14/plugins/unifying/fu-unifying-hidpp-msg.c000066400000000000000000000272441402665037500225500ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-unifying-hidpp.h" #include "fu-unifying-hidpp-msg.h" FuUnifyingHidppMsg * fu_unifying_hidpp_msg_new (void) { return g_new0 (FuUnifyingHidppMsg, 1); } const gchar * fu_unifying_hidpp_msg_dev_id_to_string (FuUnifyingHidppMsg *msg) { g_return_val_if_fail (msg != NULL, NULL); if (msg->device_id == HIDPP_DEVICE_ID_WIRED) return "wired"; if (msg->device_id == HIDPP_DEVICE_ID_RECEIVER) return "receiver"; if (msg->device_id == HIDPP_DEVICE_ID_UNSET) return "unset"; return NULL; } const gchar * fu_unifying_hidpp_msg_rpt_id_to_string (FuUnifyingHidppMsg *msg) { g_return_val_if_fail (msg != NULL, NULL); if (msg->report_id == HIDPP_REPORT_ID_SHORT) return "short"; if (msg->report_id == HIDPP_REPORT_ID_LONG) return "long"; if (msg->report_id == HIDPP_REPORT_ID_VERY_LONG) return "very-long"; return NULL; } gsize fu_unifying_hidpp_msg_get_payload_length (FuUnifyingHidppMsg *msg) { if (msg->report_id == HIDPP_REPORT_ID_SHORT) return 0x07; if (msg->report_id == HIDPP_REPORT_ID_LONG) return 0x14; if (msg->report_id == HIDPP_REPORT_ID_VERY_LONG) return 0x2f; if (msg->report_id == HIDPP_REPORT_NOTIFICATION) return 0x08; return 0x0; } const gchar * fu_unifying_hidpp_msg_fcn_id_to_string (FuUnifyingHidppMsg *msg) { g_return_val_if_fail (msg != NULL, NULL); switch (msg->sub_id) { case HIDPP_SUBID_SET_REGISTER: case HIDPP_SUBID_GET_REGISTER: case HIDPP_SUBID_SET_LONG_REGISTER: case HIDPP_SUBID_GET_LONG_REGISTER: case HIDPP_SUBID_SET_VERY_LONG_REGISTER: case HIDPP_SUBID_GET_VERY_LONG_REGISTER: if (msg->function_id == HIDPP_REGISTER_HIDPP_NOTIFICATIONS) return "hidpp-notifications"; if (msg->function_id == HIDPP_REGISTER_ENABLE_INDIVIDUAL_FEATURES) return "individual-features"; if (msg->function_id == HIDPP_REGISTER_BATTERY_STATUS) return "battery-status"; if (msg->function_id == HIDPP_REGISTER_BATTERY_MILEAGE) return "battery-mileage"; if (msg->function_id == HIDPP_REGISTER_PROFILE) return "profile"; if (msg->function_id == HIDPP_REGISTER_LED_STATUS) return "led-status"; if (msg->function_id == HIDPP_REGISTER_LED_INTENSITY) return "led-intensity"; if (msg->function_id == HIDPP_REGISTER_LED_COLOR) return "led-color"; if (msg->function_id == HIDPP_REGISTER_OPTICAL_SENSOR_SETTINGS) return "optical-sensor-settings"; if (msg->function_id == HIDPP_REGISTER_CURRENT_RESOLUTION) return "current-resolution"; if (msg->function_id == HIDPP_REGISTER_USB_REFRESH_RATE) return "usb-refresh-rate"; if (msg->function_id == HIDPP_REGISTER_GENERIC_MEMORY_MANAGEMENT) return "generic-memory-management"; if (msg->function_id == HIDPP_REGISTER_HOT_CONTROL) return "hot-control"; if (msg->function_id == HIDPP_REGISTER_READ_MEMORY) return "read-memory"; if (msg->function_id == HIDPP_REGISTER_DEVICE_CONNECTION_DISCONNECTION) return "device-connection-disconnection"; if (msg->function_id == HIDPP_REGISTER_PAIRING_INFORMATION) return "pairing-information"; if (msg->function_id == HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE) return "device-firmware-update-mode"; if (msg->function_id == HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION) return "device-firmware-information"; break; default: break; } return NULL; } const gchar * fu_unifying_hidpp_msg_sub_id_to_string (FuUnifyingHidppMsg *msg) { g_return_val_if_fail (msg != NULL, NULL); if (msg->sub_id == HIDPP_SUBID_VENDOR_SPECIFIC_KEYS) return "vendor-specific-keys"; if (msg->sub_id == HIDPP_SUBID_POWER_KEYS) return "power-keys"; if (msg->sub_id == HIDPP_SUBID_ROLLER) return "roller"; if (msg->sub_id == HIDPP_SUBID_MOUSE_EXTRA_BUTTONS) return "mouse-extra-buttons"; if (msg->sub_id == HIDPP_SUBID_BATTERY_CHARGING_LEVEL) return "battery-charging-level"; if (msg->sub_id == HIDPP_SUBID_USER_INTERFACE_EVENT) return "user-interface-event"; if (msg->sub_id == HIDPP_SUBID_F_LOCK_STATUS) return "f-lock-status"; if (msg->sub_id == HIDPP_SUBID_CALCULATOR_RESULT) return "calculator-result"; if (msg->sub_id == HIDPP_SUBID_MENU_NAVIGATE) return "menu-navigate"; if (msg->sub_id == HIDPP_SUBID_FN_KEY) return "fn-key"; if (msg->sub_id == HIDPP_SUBID_BATTERY_MILEAGE) return "battery-mileage"; if (msg->sub_id == HIDPP_SUBID_UART_RX) return "uart-rx"; if (msg->sub_id == HIDPP_SUBID_BACKLIGHT_DURATION_UPDATE) return "backlight-duration-update"; if (msg->sub_id == HIDPP_SUBID_DEVICE_DISCONNECTION) return "device-disconnection"; if (msg->sub_id == HIDPP_SUBID_DEVICE_CONNECTION) return "device-connection"; if (msg->sub_id == HIDPP_SUBID_DEVICE_DISCOVERY) return "device-discovery"; if (msg->sub_id == HIDPP_SUBID_PIN_CODE_REQUEST) return "pin-code-request"; if (msg->sub_id == HIDPP_SUBID_RECEIVER_WORKING_MODE) return "receiver-working-mode"; if (msg->sub_id == HIDPP_SUBID_ERROR_MESSAGE) return "error-message"; if (msg->sub_id == HIDPP_SUBID_RF_LINK_CHANGE) return "rf-link-change"; if (msg->sub_id == HIDPP_SUBID_HCI) return "hci"; if (msg->sub_id == HIDPP_SUBID_LINK_QUALITY) return "link-quality"; if (msg->sub_id == HIDPP_SUBID_DEVICE_LOCKING_CHANGED) return "device-locking-changed"; if (msg->sub_id == HIDPP_SUBID_WIRELESS_DEVICE_CHANGE) return "wireless-device-change"; if (msg->sub_id == HIDPP_SUBID_ACL) return "acl"; if (msg->sub_id == HIDPP_SUBID_VOIP_TELEPHONY_EVENT) return "voip-telephony-event"; if (msg->sub_id == HIDPP_SUBID_LED) return "led"; if (msg->sub_id == HIDPP_SUBID_GESTURE_AND_AIR) return "gesture-and-air"; if (msg->sub_id == HIDPP_SUBID_TOUCHPAD_MULTI_TOUCH) return "touchpad-multi-touch"; if (msg->sub_id == HIDPP_SUBID_TRACEABILITY) return "traceability"; if (msg->sub_id == HIDPP_SUBID_SET_REGISTER) return "set-register"; if (msg->sub_id == HIDPP_SUBID_GET_REGISTER) return "get-register"; if (msg->sub_id == HIDPP_SUBID_SET_LONG_REGISTER) return "set-long-register"; if (msg->sub_id == HIDPP_SUBID_GET_LONG_REGISTER) return "get-long-register"; if (msg->sub_id == HIDPP_SUBID_SET_VERY_LONG_REGISTER) return "set-very-long-register"; if (msg->sub_id == HIDPP_SUBID_GET_VERY_LONG_REGISTER) return "get-very-long-register"; if (msg->sub_id == HIDPP_SUBID_ERROR_MSG) return "error-msg"; if (msg->sub_id == HIDPP_SUBID_ERROR_MSG_20) return "error-msg-v2"; return NULL; } gboolean fu_unifying_hidpp_msg_is_reply (FuUnifyingHidppMsg *msg1, FuUnifyingHidppMsg *msg2) { g_return_val_if_fail (msg1 != NULL, FALSE); g_return_val_if_fail (msg2 != NULL, FALSE); if (msg1->device_id != msg2->device_id && msg1->device_id != HIDPP_DEVICE_ID_UNSET && msg2->device_id != HIDPP_DEVICE_ID_UNSET) return FALSE; if (msg1->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID || msg2->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID) return TRUE; if (msg1->sub_id != msg2->sub_id) return FALSE; if (msg1->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_FNCT_ID || msg2->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_FNCT_ID) return TRUE; if (msg1->function_id != msg2->function_id) return FALSE; return TRUE; } /* HID++ error */ gboolean fu_unifying_hidpp_msg_is_error (FuUnifyingHidppMsg *msg, GError **error) { g_return_val_if_fail (msg != NULL, FALSE); if (msg->sub_id == HIDPP_SUBID_ERROR_MSG) { switch (msg->data[1]) { case HIDPP_ERR_INVALID_SUBID: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "invalid SubID"); break; case HIDPP_ERR_INVALID_ADDRESS: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid address"); break; case HIDPP_ERR_INVALID_VALUE: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid value"); break; case HIDPP_ERR_CONNECT_FAIL: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "connection request failed"); break; case HIDPP_ERR_TOO_MANY_DEVICES: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, "too many devices connected"); break; case HIDPP_ERR_ALREADY_EXISTS: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_EXISTS, "already exists"); break; case HIDPP_ERR_BUSY: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_BUSY, "busy"); break; case HIDPP_ERR_UNKNOWN_DEVICE: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "unknown device"); break; case HIDPP_ERR_RESOURCE_ERROR: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE, "resource error"); break; case HIDPP_ERR_REQUEST_UNAVAILABLE: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_EXISTS, "request not valid in current context"); break; case HIDPP_ERR_INVALID_PARAM_VALUE: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "request parameter has unsupported value"); break; case HIDPP_ERR_WRONG_PIN_CODE: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED, "the pin code was wrong"); break; default: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic failure"); } return FALSE; } if (msg->sub_id == HIDPP_SUBID_ERROR_MSG_20) { switch (msg->data[1]) { case HIDPP_ERROR_CODE_INVALID_ARGUMENT: g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid argument 0x%02x", msg->data[2]); break; case HIDPP_ERROR_CODE_OUT_OF_RANGE: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "out of range"); break; case HIDPP_ERROR_CODE_HW_ERROR: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE, "hardware error"); break; case HIDPP_ERROR_CODE_INVALID_FEATURE_INDEX: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid feature index"); break; case HIDPP_ERROR_CODE_INVALID_FUNCTION_ID: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid function ID"); break; case HIDPP_ERROR_CODE_BUSY: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_BUSY, "busy"); break; case HIDPP_ERROR_CODE_UNSUPPORTED: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unsupported"); break; default: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic failure"); break; } return FALSE; } return TRUE; } void fu_unifying_hidpp_msg_copy (FuUnifyingHidppMsg *msg_dst, const FuUnifyingHidppMsg *msg_src) { g_return_if_fail (msg_dst != NULL); g_return_if_fail (msg_src != NULL); memset (msg_dst->data, 0x00, sizeof(msg_dst->data)); msg_dst->device_id = msg_src->device_id; msg_dst->sub_id = msg_src->sub_id; msg_dst->function_id = msg_src->function_id; memcpy (msg_dst->data, msg_src->data, sizeof(msg_dst->data)); } /* filter HID++1.0 messages */ gboolean fu_unifying_hidpp_msg_is_hidpp10_compat (FuUnifyingHidppMsg *msg) { g_return_val_if_fail (msg != NULL, FALSE); if (msg->sub_id == 0x40 || msg->sub_id == 0x41 || msg->sub_id == 0x49 || msg->sub_id == 0x4b || msg->sub_id == 0x8f) { return TRUE; } return FALSE; } gboolean fu_unifying_hidpp_msg_verify_swid (FuUnifyingHidppMsg *msg) { g_return_val_if_fail (msg != NULL, FALSE); if ((msg->function_id & 0x0f) != FU_UNIFYING_HIDPP_MSG_SW_ID) return FALSE; return TRUE; } fwupd-1.2.14/plugins/unifying/fu-unifying-hidpp-msg.h000066400000000000000000000037161402665037500225530ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS typedef enum { FU_UNIFYING_HIDPP_MSG_FLAG_NONE, FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT = 1 << 0, FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID = 1 << 1, FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_FNCT_ID = 1 << 2, FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SWID = 1 << 3, /*< private >*/ FU_UNIFYING_HIDPP_MSG_FLAG_LAST } FuUnifyingHidppMsgFlags; typedef struct __attribute__((packed)) { guint8 report_id; guint8 device_id; guint8 sub_id; guint8 function_id; /* funcId:software_id */ guint8 data[47]; /* maximum supported by Windows XP SP2 */ /* not included in the packet sent to the hardware */ guint32 flags; guint8 hidpp_version; } FuUnifyingHidppMsg; /* this is specific to fwupd */ #define FU_UNIFYING_HIDPP_MSG_SW_ID 0x07 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUnifyingHidppMsg, g_free); #pragma clang diagnostic pop FuUnifyingHidppMsg *fu_unifying_hidpp_msg_new (void); void fu_unifying_hidpp_msg_copy (FuUnifyingHidppMsg *msg_dst, const FuUnifyingHidppMsg *msg_src); gsize fu_unifying_hidpp_msg_get_payload_length (FuUnifyingHidppMsg *msg); gboolean fu_unifying_hidpp_msg_is_reply (FuUnifyingHidppMsg *msg1, FuUnifyingHidppMsg *msg2); gboolean fu_unifying_hidpp_msg_is_hidpp10_compat (FuUnifyingHidppMsg *msg); gboolean fu_unifying_hidpp_msg_is_error (FuUnifyingHidppMsg *msg, GError **error); gboolean fu_unifying_hidpp_msg_verify_swid (FuUnifyingHidppMsg *msg); const gchar *fu_unifying_hidpp_msg_dev_id_to_string (FuUnifyingHidppMsg *msg); const gchar *fu_unifying_hidpp_msg_rpt_id_to_string (FuUnifyingHidppMsg *msg); const gchar *fu_unifying_hidpp_msg_sub_id_to_string (FuUnifyingHidppMsg *msg); const gchar *fu_unifying_hidpp_msg_fcn_id_to_string (FuUnifyingHidppMsg *msg); G_END_DECLS fwupd-1.2.14/plugins/unifying/fu-unifying-hidpp.c000066400000000000000000000140061402665037500217540ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-unifying-common.h" #include "fu-unifying-hidpp.h" static gchar * fu_unifying_hidpp_msg_to_string (FuUnifyingHidppMsg *msg) { GString *str = g_string_new (NULL); const gchar *tmp; g_autoptr(GError) error = NULL; g_autoptr(GString) flags_str = g_string_new (NULL); g_return_val_if_fail (msg != NULL, NULL); if (msg->flags == FU_UNIFYING_HIDPP_MSG_FLAG_NONE) { g_string_append (flags_str, "none"); } else { if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT) g_string_append (flags_str, "longer-timeout,"); if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID) g_string_append (flags_str, "ignore-sub-id,"); if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_FNCT_ID) g_string_append (flags_str, "ignore-fnct-id,"); if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SWID) g_string_append (flags_str, "ignore-swid,"); if (str->len > 0) g_string_truncate (str, str->len - 1); } g_string_append_printf (str, "flags: %02x [%s]\n", msg->flags, flags_str->str); g_string_append_printf (str, "report-id: %02x [%s]\n", msg->report_id, fu_unifying_hidpp_msg_rpt_id_to_string (msg)); tmp = fu_unifying_hidpp_msg_dev_id_to_string (msg); g_string_append_printf (str, "device-id: %02x [%s]\n", msg->device_id, tmp ); g_string_append_printf (str, "sub-id: %02x [%s]\n", msg->sub_id, fu_unifying_hidpp_msg_sub_id_to_string (msg)); g_string_append_printf (str, "function-id: %02x [%s]\n", msg->function_id, fu_unifying_hidpp_msg_fcn_id_to_string (msg)); if (!fu_unifying_hidpp_msg_is_error (msg, &error)) { g_string_append_printf (str, "error: %s\n", error->message); } return g_string_free (str, FALSE); } gboolean fu_unifying_hidpp_send (FuIOChannel *io_channel, FuUnifyingHidppMsg *msg, guint timeout, GError **error) { gsize len = fu_unifying_hidpp_msg_get_payload_length (msg); /* only for HID++2.0 */ if (msg->hidpp_version >= 2.f) msg->function_id |= FU_UNIFYING_HIDPP_MSG_SW_ID; /* detailed debugging */ if (g_getenv ("FWUPD_UNIFYING_VERBOSE") != NULL) { g_autofree gchar *str = fu_unifying_hidpp_msg_to_string (msg); fu_common_dump_raw (G_LOG_DOMAIN, "host->device", (guint8 *) msg, len); g_print ("%s", str); } /* HID */ if (!fu_io_channel_write_raw (io_channel, (guint8 *) msg, len, timeout, FU_IO_CHANNEL_FLAG_FLUSH_INPUT | FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO, error)) { g_prefix_error (error, "failed to send: "); return FALSE; } /* success */ return TRUE; } gboolean fu_unifying_hidpp_receive (FuIOChannel *io_channel, FuUnifyingHidppMsg *msg, guint timeout, GError **error) { gsize read_size = 0; if (!fu_io_channel_read_raw (io_channel, (guint8 *) msg, sizeof(FuUnifyingHidppMsg), &read_size, timeout, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error)) { g_prefix_error (error, "failed to receive: "); return FALSE; } /* check long enough, but allow returning oversize packets */ if (g_getenv ("FWUPD_UNIFYING_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "device->host", (guint8 *) msg, read_size); if (read_size < fu_unifying_hidpp_msg_get_payload_length (msg)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "message length too small, " "got %" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT, read_size, fu_unifying_hidpp_msg_get_payload_length (msg)); return FALSE; } /* detailed debugging */ if (g_getenv ("FWUPD_UNIFYING_VERBOSE") != NULL) { g_autofree gchar *str = fu_unifying_hidpp_msg_to_string (msg); g_print ("%s", str); } /* success */ return TRUE; } gboolean fu_unifying_hidpp_transfer (FuIOChannel *io_channel, FuUnifyingHidppMsg *msg, GError **error) { guint timeout = FU_UNIFYING_DEVICE_TIMEOUT_MS; guint ignore_cnt = 0; g_autoptr(FuUnifyingHidppMsg) msg_tmp = fu_unifying_hidpp_msg_new (); /* increase timeout for some operations */ if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT) timeout *= 10; /* send request */ if (!fu_unifying_hidpp_send (io_channel, msg, timeout, error)) return FALSE; /* keep trying to receive until we get a valid reply */ while (1) { msg_tmp->hidpp_version = msg->hidpp_version; if (!fu_unifying_hidpp_receive (io_channel, msg_tmp, timeout, error)) { g_prefix_error (error, "failed to receive: "); return FALSE; } /* we don't know how to handle this report packet */ if (fu_unifying_hidpp_msg_get_payload_length (msg_tmp) == 0x0) { g_debug ("HID++1.0 report 0x%02x has unknown length, ignoring", msg_tmp->report_id); continue; } /* maybe something is also writing to the device? -- * we can't use the SwID as this is a HID++2.0 feature */ if (!fu_unifying_hidpp_msg_is_error (msg_tmp, error)) return FALSE; /* is valid reply */ if (fu_unifying_hidpp_msg_is_reply (msg, msg_tmp)) break; /* to ensure compatibility when an HID++ 2.0 device is * connected to an HID++ 1.0 receiver, any feature index * corresponding to an HID++ 1.0 sub-identifier which could be * sent by the receiver, must be assigned to a dummy feature */ if (msg->hidpp_version >= 2.f) { if (fu_unifying_hidpp_msg_is_hidpp10_compat (msg_tmp)) { g_debug ("ignoring HID++1.0 reply"); continue; } /* not us */ if ((msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SWID) == 0) { if (!fu_unifying_hidpp_msg_verify_swid (msg_tmp)) { g_debug ("ignoring reply with SwId 0x%02i, expected 0x%02i", msg_tmp->function_id & 0x0f, FU_UNIFYING_HIDPP_MSG_SW_ID); continue; } } } /* hardware not responding */ if (ignore_cnt++ > 10) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "too many messages to ignore"); return FALSE; } g_debug ("ignoring message %u", ignore_cnt); }; /* copy over data */ fu_unifying_hidpp_msg_copy (msg, msg_tmp); return TRUE; } fwupd-1.2.14/plugins/unifying/fu-unifying-hidpp.h000066400000000000000000000125621402665037500217660ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-io-channel.h" G_BEGIN_DECLS /* * Based on the HID++ documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #define HIDPP_DEVICE_ID_WIRED 0x00 #define HIDPP_DEVICE_ID_RECEIVER 0xFF #define HIDPP_DEVICE_ID_UNSET 0xFE #define HIDPP_REPORT_NOTIFICATION 0x01 #define HIDPP_REPORT_ID_SHORT 0x10 #define HIDPP_REPORT_ID_LONG 0x11 #define HIDPP_REPORT_ID_VERY_LONG 0x12 #define HIDPP_SUBID_VENDOR_SPECIFIC_KEYS 0x03 #define HIDPP_SUBID_POWER_KEYS 0x04 #define HIDPP_SUBID_ROLLER 0x05 #define HIDPP_SUBID_MOUSE_EXTRA_BUTTONS 0x06 #define HIDPP_SUBID_BATTERY_CHARGING_LEVEL 0x07 #define HIDPP_SUBID_USER_INTERFACE_EVENT 0x08 #define HIDPP_SUBID_F_LOCK_STATUS 0x09 #define HIDPP_SUBID_CALCULATOR_RESULT 0x0A #define HIDPP_SUBID_MENU_NAVIGATE 0x0B #define HIDPP_SUBID_FN_KEY 0x0C #define HIDPP_SUBID_BATTERY_MILEAGE 0x0D #define HIDPP_SUBID_UART_RX 0x0E #define HIDPP_SUBID_BACKLIGHT_DURATION_UPDATE 0x17 #define HIDPP_SUBID_DEVICE_DISCONNECTION 0x40 #define HIDPP_SUBID_DEVICE_CONNECTION 0x41 #define HIDPP_SUBID_DEVICE_DISCOVERY 0x42 #define HIDPP_SUBID_PIN_CODE_REQUEST 0x43 #define HIDPP_SUBID_RECEIVER_WORKING_MODE 0x44 #define HIDPP_SUBID_ERROR_MESSAGE 0x45 #define HIDPP_SUBID_RF_LINK_CHANGE 0x46 #define HIDPP_SUBID_HCI 0x48 #define HIDPP_SUBID_LINK_QUALITY 0x49 #define HIDPP_SUBID_DEVICE_LOCKING_CHANGED 0x4a #define HIDPP_SUBID_WIRELESS_DEVICE_CHANGE 0x4B #define HIDPP_SUBID_ACL 0x51 #define HIDPP_SUBID_VOIP_TELEPHONY_EVENT 0x5B #define HIDPP_SUBID_LED 0x60 #define HIDPP_SUBID_GESTURE_AND_AIR 0x65 #define HIDPP_SUBID_TOUCHPAD_MULTI_TOUCH 0x66 #define HIDPP_SUBID_TRACEABILITY 0x78 #define HIDPP_SUBID_SET_REGISTER 0x80 #define HIDPP_SUBID_GET_REGISTER 0x81 #define HIDPP_SUBID_SET_LONG_REGISTER 0x82 #define HIDPP_SUBID_GET_LONG_REGISTER 0x83 #define HIDPP_SUBID_SET_VERY_LONG_REGISTER 0x84 #define HIDPP_SUBID_GET_VERY_LONG_REGISTER 0x85 #define HIDPP_SUBID_ERROR_MSG 0x8F #define HIDPP_SUBID_ERROR_MSG_20 0xFF #define HIDPP_ERR_SUCCESS 0x00 #define HIDPP_ERR_INVALID_SUBID 0x01 #define HIDPP_ERR_INVALID_ADDRESS 0x02 #define HIDPP_ERR_INVALID_VALUE 0x03 #define HIDPP_ERR_CONNECT_FAIL 0x04 #define HIDPP_ERR_TOO_MANY_DEVICES 0x05 #define HIDPP_ERR_ALREADY_EXISTS 0x06 #define HIDPP_ERR_BUSY 0x07 #define HIDPP_ERR_UNKNOWN_DEVICE 0x08 #define HIDPP_ERR_RESOURCE_ERROR 0x09 #define HIDPP_ERR_REQUEST_UNAVAILABLE 0x0A #define HIDPP_ERR_INVALID_PARAM_VALUE 0x0B #define HIDPP_ERR_WRONG_PIN_CODE 0x0C /* * HID++1.0 registers */ #define HIDPP_REGISTER_HIDPP_NOTIFICATIONS 0x00 #define HIDPP_REGISTER_ENABLE_INDIVIDUAL_FEATURES 0x01 #define HIDPP_REGISTER_BATTERY_STATUS 0x07 #define HIDPP_REGISTER_BATTERY_MILEAGE 0x0D #define HIDPP_REGISTER_PROFILE 0x0F #define HIDPP_REGISTER_LED_STATUS 0x51 #define HIDPP_REGISTER_LED_INTENSITY 0x54 #define HIDPP_REGISTER_LED_COLOR 0x57 #define HIDPP_REGISTER_OPTICAL_SENSOR_SETTINGS 0x61 #define HIDPP_REGISTER_CURRENT_RESOLUTION 0x63 #define HIDPP_REGISTER_USB_REFRESH_RATE 0x64 #define HIDPP_REGISTER_GENERIC_MEMORY_MANAGEMENT 0xA0 #define HIDPP_REGISTER_HOT_CONTROL 0xA1 #define HIDPP_REGISTER_READ_MEMORY 0xA2 #define HIDPP_REGISTER_DEVICE_CONNECTION_DISCONNECTION 0xB2 #define HIDPP_REGISTER_PAIRING_INFORMATION 0xB5 #define HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE 0xF0 #define HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION 0xF1 /* * HID++2.0 error codes */ #define HIDPP_ERROR_CODE_NO_ERROR 0x00 #define HIDPP_ERROR_CODE_UNKNOWN 0x01 #define HIDPP_ERROR_CODE_INVALID_ARGUMENT 0x02 #define HIDPP_ERROR_CODE_OUT_OF_RANGE 0x03 #define HIDPP_ERROR_CODE_HW_ERROR 0x04 #define HIDPP_ERROR_CODE_LOGITECH_INTERNAL 0x05 #define HIDPP_ERROR_CODE_INVALID_FEATURE_INDEX 0x06 #define HIDPP_ERROR_CODE_INVALID_FUNCTION_ID 0x07 #define HIDPP_ERROR_CODE_BUSY 0x08 #define HIDPP_ERROR_CODE_UNSUPPORTED 0x09 /* * HID++2.0 features */ #define HIDPP_FEATURE_ROOT 0x0000 #define HIDPP_FEATURE_I_FEATURE_SET 0x0001 #define HIDPP_FEATURE_I_FIRMWARE_INFO 0x0003 #define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE 0x0005 #define HIDPP_FEATURE_DFU_CONTROL 0x00c1 #define HIDPP_FEATURE_DFU_CONTROL_SIGNED 0x00c2 #define HIDPP_FEATURE_DFU 0x00d0 #define HIDPP_FEATURE_BATTERY_LEVEL_STATUS 0x1000 #define HIDPP_FEATURE_KBD_REPROGRAMMABLE_KEYS 0x1b00 #define HIDPP_FEATURE_SPECIAL_KEYS_BUTTONS 0x1b04 #define HIDPP_FEATURE_MOUSE_POINTER_BASIC 0x2200 #define HIDPP_FEATURE_ADJUSTABLE_DPI 0x2201 #define HIDPP_FEATURE_ADJUSTABLE_REPORT_RATE 0x8060 #define HIDPP_FEATURE_COLOR_LED_EFFECTS 0x8070 #define HIDPP_FEATURE_ONBOARD_PROFILES 0x8100 #define HIDPP_FEATURE_MOUSE_BUTTON_SPY 0x8110 #include "fu-unifying-hidpp-msg.h" gboolean fu_unifying_hidpp_send (FuIOChannel *self, FuUnifyingHidppMsg *msg, guint timeout, GError **error); gboolean fu_unifying_hidpp_receive (FuIOChannel *self, FuUnifyingHidppMsg *msg, guint timeout, GError **error); gboolean fu_unifying_hidpp_transfer (FuIOChannel *self, FuUnifyingHidppMsg *msg, GError **error); G_END_DECLS fwupd-1.2.14/plugins/unifying/fu-unifying-peripheral.c000066400000000000000000000722131402665037500230070ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-unifying-common.h" #include "fu-unifying-peripheral.h" #include "fu-unifying-hidpp.h" struct _FuUnifyingPeripheral { FuUdevDevice parent_instance; guint8 battery_level; guint8 cached_fw_entity; guint8 hidpp_id; guint8 hidpp_version; gboolean is_updatable; gboolean is_active; FuIOChannel *io_channel; GPtrArray *feature_index; /* of FuUnifyingHidppMap */ }; typedef struct { guint8 idx; guint16 feature; } FuUnifyingHidppMap; G_DEFINE_TYPE (FuUnifyingPeripheral, fu_unifying_peripheral, FU_TYPE_UDEV_DEVICE) typedef enum { FU_UNIFYING_PERIPHERAL_KIND_KEYBOARD, FU_UNIFYING_PERIPHERAL_KIND_REMOTE_CONTROL, FU_UNIFYING_PERIPHERAL_KIND_NUMPAD, FU_UNIFYING_PERIPHERAL_KIND_MOUSE, FU_UNIFYING_PERIPHERAL_KIND_TOUCHPAD, FU_UNIFYING_PERIPHERAL_KIND_TRACKBALL, FU_UNIFYING_PERIPHERAL_KIND_PRESENTER, FU_UNIFYING_PERIPHERAL_KIND_RECEIVER, FU_UNIFYING_PERIPHERAL_KIND_LAST } FuUnifyingPeripheralKind; static const gchar * fu_unifying_peripheral_get_icon (FuUnifyingPeripheralKind kind) { if (kind == FU_UNIFYING_PERIPHERAL_KIND_KEYBOARD) return "input-keyboard"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_REMOTE_CONTROL) return "pda"; // ish if (kind == FU_UNIFYING_PERIPHERAL_KIND_NUMPAD) return "input-dialpad"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_MOUSE) return "input-mouse"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_TOUCHPAD) return "input-touchpad"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_TRACKBALL) return "input-mouse"; // ish if (kind == FU_UNIFYING_PERIPHERAL_KIND_PRESENTER) return "pda"; // ish if (kind == FU_UNIFYING_PERIPHERAL_KIND_RECEIVER) return "preferences-desktop-keyboard"; return NULL; } static const gchar * fu_unifying_peripheral_get_summary (FuUnifyingPeripheralKind kind) { if (kind == FU_UNIFYING_PERIPHERAL_KIND_KEYBOARD) return "Unifying Keyboard"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_REMOTE_CONTROL) return "Unifying Remote Control"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_NUMPAD) return "Unifying Number Pad"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_MOUSE) return "Unifying Mouse"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_TOUCHPAD) return "Unifying Touchpad"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_TRACKBALL) return "Unifying Trackball"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_PRESENTER) return "Unifying Presenter"; if (kind == FU_UNIFYING_PERIPHERAL_KIND_RECEIVER) return "Unifying Receiver"; return NULL; } static const gchar * fu_unifying_hidpp_feature_to_string (guint16 feature) { if (feature == HIDPP_FEATURE_ROOT) return "Root"; if (feature == HIDPP_FEATURE_I_FIRMWARE_INFO) return "IFirmwareInfo"; if (feature == HIDPP_FEATURE_GET_DEVICE_NAME_TYPE) return "GetDevicenameType"; if (feature == HIDPP_FEATURE_BATTERY_LEVEL_STATUS) return "BatteryLevelStatus"; if (feature == HIDPP_FEATURE_DFU_CONTROL) return "DfuControl"; if (feature == HIDPP_FEATURE_DFU_CONTROL_SIGNED) return "DfuControlSigned"; if (feature == HIDPP_FEATURE_DFU) return "Dfu"; return NULL; } static void fu_unifying_peripheral_refresh_updatable (FuUnifyingPeripheral *self) { /* device can only be upgraded if it is capable, and active */ if (self->is_updatable && self->is_active) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); return; } fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); } static gboolean fu_unifying_peripheral_ping (FuUnifyingPeripheral *self, GError **error) { gdouble version; g_autoptr(GError) error_local = NULL; g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); /* handle failure */ msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = self->hidpp_id; msg->sub_id = 0x00; /* rootIndex */ msg->function_id = 0x01 << 4; /* ping */ msg->data[0] = 0x00; msg->data[1] = 0x00; msg->data[2] = 0xaa; /* user-selected value */ msg->hidpp_version = self->hidpp_version; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, &error_local)) { if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { self->hidpp_version = 1; return TRUE; } if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE)) { self->is_active = FALSE; fu_unifying_peripheral_refresh_updatable (self); return TRUE; } g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to ping %s: %s", fu_device_get_name (FU_DEVICE (self)), error_local->message); return FALSE; } /* device no longer asleep */ self->is_active = TRUE; fu_unifying_peripheral_refresh_updatable (self); /* if the HID++ ID is unset, grab it from the reply */ if (self->hidpp_id == HIDPP_DEVICE_ID_UNSET) { self->hidpp_id = msg->device_id; g_debug ("HID++ ID is %02x", self->hidpp_id); } /* format version in BCD format */ version = (gdouble) msg->data[0] + ((gdouble) msg->data[1]) / 100.f; self->hidpp_version = (guint) version; /* success */ return TRUE; } static gboolean fu_unifying_peripheral_close (FuDevice *device, GError **error) { FuUnifyingPeripheral *self = FU_UNIFYING_PERIPHERAL (device); if (!fu_io_channel_shutdown (self->io_channel, error)) return FALSE; g_clear_object (&self->io_channel); return TRUE; } static gboolean fu_unifying_peripheral_poll (FuDevice *device, GError **error) { FuUnifyingPeripheral *self = FU_UNIFYING_PERIPHERAL (device); const guint timeout = 1; /* ms */ g_autoptr(GError) error_local = NULL; g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); g_autoptr(FuDeviceLocker) locker = NULL; /* open */ locker = fu_device_locker_new (self, error); if (locker == NULL) return FALSE; /* flush pending data */ msg->device_id = self->hidpp_id; msg->hidpp_version = self->hidpp_version; if (!fu_unifying_hidpp_receive (self->io_channel, msg, timeout, &error_local)) { if (!g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { g_warning ("failed to get pending read: %s", error_local->message); return TRUE; } /* no data to receive */ g_clear_error (&error_local); } /* just ping */ if (!fu_unifying_peripheral_ping (self, &error_local)) { g_warning ("failed to ping device: %s", error_local->message); return TRUE; } /* this is the first time the device has been active */ if (self->feature_index->len == 0) { fu_device_probe_invalidate (FU_DEVICE (self)); if (!fu_device_setup (FU_DEVICE (self), error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_unifying_peripheral_open (FuDevice *device, GError **error) { FuUnifyingPeripheral *self = FU_UNIFYING_PERIPHERAL (device); GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device)); const gchar *devpath = g_udev_device_get_device_file (udev_device); /* open */ self->io_channel = fu_io_channel_new_file (devpath, error); if (self->io_channel == NULL) return FALSE; return TRUE; } static void fu_unifying_peripheral_to_string (FuDevice *device, GString *str) { FuUnifyingPeripheral *self = FU_UNIFYING_PERIPHERAL (device); g_string_append_printf (str, " HidppVersion:\t\t%u\n", self->hidpp_version); if (self->hidpp_id != HIDPP_DEVICE_ID_UNSET) g_string_append_printf (str, " HidppId:\t\t0x%02x\n", (guint) self->hidpp_id); if (self->battery_level != 0) g_string_append_printf (str, " Battery-level:\t\t%u\n", self->battery_level); g_string_append_printf (str, " IsUpdatable:\t\t%i\n", self->is_updatable); g_string_append_printf (str, " IsActive:\t\t%i\n", self->is_active); for (guint i = 0; i < self->feature_index->len; i++) { FuUnifyingHidppMap *map = g_ptr_array_index (self->feature_index, i); g_string_append_printf (str, " Feature%02x:\t\t%s [0x%04x]\n", map->idx, fu_unifying_hidpp_feature_to_string (map->feature), map->feature); } } static guint8 fu_unifying_peripheral_feature_get_idx (FuUnifyingPeripheral *self, guint16 feature) { for (guint i = 0; i < self->feature_index->len; i++) { FuUnifyingHidppMap *map = g_ptr_array_index (self->feature_index, i); if (map->feature == feature) return map->idx; } return 0x00; } static gboolean fu_unifying_peripheral_fetch_firmware_info (FuUnifyingPeripheral *self, GError **error) { guint8 idx; guint8 entity_count; g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); /* get the feature index */ idx = fu_unifying_peripheral_feature_get_idx (self, HIDPP_FEATURE_I_FIRMWARE_INFO); if (idx == 0x00) return TRUE; /* get the entity count */ msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = self->hidpp_id; msg->sub_id = idx; msg->function_id = 0x00 << 4; /* getCount */ msg->hidpp_version = self->hidpp_version; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to get firmware count: "); return FALSE; } entity_count = msg->data[0]; g_debug ("firmware entity count is %u", entity_count); /* get firmware, bootloader, hardware versions */ for (guint8 i = 0; i < entity_count; i++) { guint16 build; g_autofree gchar *version = NULL; g_autofree gchar *name = NULL; msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = self->hidpp_id; msg->sub_id = idx; msg->function_id = 0x01 << 4; /* getInfo */ msg->data[0] = i; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to get firmware info: "); return FALSE; } if (msg->data[1] == 0x00 && msg->data[2] == 0x00 && msg->data[3] == 0x00 && msg->data[4] == 0x00 && msg->data[5] == 0x00 && msg->data[6] == 0x00 && msg->data[7] == 0x00) { g_debug ("no version set for entity %u", i); continue; } name = g_strdup_printf ("%c%c%c", msg->data[1], msg->data[2], msg->data[3]); build = ((guint16) msg->data[6]) << 8 | msg->data[7]; version = fu_unifying_format_version (name, msg->data[4], msg->data[5], build); g_debug ("firmware entity 0x%02x version is %s", i, version); if (msg->data[0] == 0) { fu_device_set_version (FU_DEVICE (self), version, FWUPD_VERSION_FORMAT_PLAIN); self->cached_fw_entity = i; } else if (msg->data[0] == 1) { fu_device_set_version_bootloader (FU_DEVICE (self), version); } else if (msg->data[0] == 2) { fu_device_set_metadata (FU_DEVICE (self), "version-hw", version); } } /* not an error, the device just doesn't support this */ return TRUE; } static gboolean fu_unifying_peripheral_fetch_battery_level (FuUnifyingPeripheral *self, GError **error) { /* try using HID++2.0 */ if (self->hidpp_version >= 2.f) { guint8 idx; idx = fu_unifying_peripheral_feature_get_idx (self, HIDPP_FEATURE_BATTERY_LEVEL_STATUS); if (idx != 0x00) { g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = self->hidpp_id; msg->sub_id = idx; msg->function_id = 0x00; /* GetBatteryLevelStatus */ msg->hidpp_version = self->hidpp_version; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to get battery info: "); return FALSE; } if (msg->data[0] != 0x00) self->battery_level = msg->data[0]; return TRUE; } } /* try HID++1.0 battery mileage */ if (self->hidpp_version == 1.f) { g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = self->hidpp_id; msg->sub_id = HIDPP_SUBID_GET_REGISTER; msg->function_id = HIDPP_REGISTER_BATTERY_MILEAGE; msg->hidpp_version = self->hidpp_version; if (fu_unifying_hidpp_transfer (self->io_channel, msg, NULL)) { if (msg->data[0] != 0x00) self->battery_level = msg->data[0]; return TRUE; } /* try HID++1.0 battery status instead */ msg->function_id = HIDPP_REGISTER_BATTERY_STATUS; if (fu_unifying_hidpp_transfer (self->io_channel, msg, NULL)) { switch (msg->data[0]) { case 1: /* 0 - 10 */ self->battery_level = 5; break; case 3: /* 11 - 30 */ self->battery_level = 20; break; case 5: /* 31 - 80 */ self->battery_level = 55; break; case 7: /* 81 - 100 */ self->battery_level = 90; break; default: g_warning ("unknown battery percentage: 0x%02x", msg->data[0]); break; } return TRUE; } } /* not an error, the device just doesn't support any of the methods */ return TRUE; } static gboolean fu_unifying_hidpp_feature_search (FuDevice *device, guint16 feature, GError **error) { FuUnifyingPeripheral *self = FU_UNIFYING_PERIPHERAL (device); FuUnifyingHidppMap *map; g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); /* find the idx for the feature */ msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = self->hidpp_id; msg->sub_id = 0x00; /* rootIndex */ msg->function_id = 0x00 << 4; /* getFeature */ msg->data[0] = feature >> 8; msg->data[1] = feature; msg->data[2] = 0x00; msg->hidpp_version = self->hidpp_version; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to get idx for feature %s [0x%04x]: ", fu_unifying_hidpp_feature_to_string (feature), feature); return FALSE; } /* zero index */ if (msg->data[0] == 0x00) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "feature %s [0x%04x] not found", fu_unifying_hidpp_feature_to_string (feature), feature); return FALSE; } /* add to map */ map = g_new0 (FuUnifyingHidppMap, 1); map->idx = msg->data[0]; map->feature = feature; g_ptr_array_add (self->feature_index, map); g_debug ("added feature %s [0x%04x] as idx %02x", fu_unifying_hidpp_feature_to_string (feature), feature, map->idx); return TRUE; } static gboolean fu_unifying_peripheral_probe (FuUdevDevice *device, GError **error) { g_autofree gchar *devid = NULL; /* set the physical ID */ if (!fu_udev_device_set_physical_id (device, "hid", error)) return FALSE; /* nearly... */ fu_device_set_vendor_id (FU_DEVICE (device), "USB:0x046D"); /* this is a non-standard extension */ devid = g_strdup_printf ("UFY\\VID_%04X&PID_%04X", fu_udev_device_get_vendor (device), fu_udev_device_get_model (device)); fu_device_add_instance_id (FU_DEVICE (device), devid); return TRUE; } static gboolean fu_unifying_peripheral_setup (FuDevice *device, GError **error) { FuUnifyingPeripheral *self = FU_UNIFYING_PERIPHERAL (device); guint8 idx; const guint16 map_features[] = { HIDPP_FEATURE_GET_DEVICE_NAME_TYPE, HIDPP_FEATURE_I_FIRMWARE_INFO, HIDPP_FEATURE_BATTERY_LEVEL_STATUS, HIDPP_FEATURE_DFU_CONTROL, HIDPP_FEATURE_DFU_CONTROL_SIGNED, HIDPP_FEATURE_DFU, HIDPP_FEATURE_ROOT }; /* ping device to get HID++ version */ if (!fu_unifying_peripheral_ping (self, error)) return FALSE; /* add known root for HID++2.0 */ g_ptr_array_set_size (self->feature_index, 0); if (self->hidpp_version >= 2.f) { FuUnifyingHidppMap *map = g_new0 (FuUnifyingHidppMap, 1); map->idx = 0x00; map->feature = HIDPP_FEATURE_ROOT; g_ptr_array_add (self->feature_index, map); } /* map some *optional* HID++2.0 features we might use */ for (guint i = 0; map_features[i] != HIDPP_FEATURE_ROOT; i++) { g_autoptr(GError) error_local = NULL; if (!fu_unifying_hidpp_feature_search (device, map_features[i], &error_local)) { g_debug ("%s", error_local->message); if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) || g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE)) { /* timed out, so not trying any more */ break; } } } /* get the firmware information */ if (!fu_unifying_peripheral_fetch_firmware_info (self, error)) return FALSE; /* get the battery level */ if (!fu_unifying_peripheral_fetch_battery_level (self, error)) return FALSE; /* try using HID++2.0 */ idx = fu_unifying_peripheral_feature_get_idx (self, HIDPP_FEATURE_GET_DEVICE_NAME_TYPE); if (idx != 0x00) { const gchar *tmp; g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = self->hidpp_id; msg->sub_id = idx; msg->function_id = 0x02 << 4; /* getDeviceType */ msg->hidpp_version = self->hidpp_version; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to get device type: "); return FALSE; } /* add nice-to-have data */ tmp = fu_unifying_peripheral_get_summary (msg->data[0]); if (tmp != NULL) fu_device_set_summary (FU_DEVICE (device), tmp); tmp = fu_unifying_peripheral_get_icon (msg->data[0]); if (tmp != NULL) fu_device_add_icon (FU_DEVICE (device), tmp); } idx = fu_unifying_peripheral_feature_get_idx (self, HIDPP_FEATURE_DFU_CONTROL); if (idx != 0x00) { self->is_updatable = TRUE; fu_device_remove_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } idx = fu_unifying_peripheral_feature_get_idx (self, HIDPP_FEATURE_DFU_CONTROL_SIGNED); if (idx != 0x00) { /* check the feature is available */ g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = self->hidpp_id; msg->sub_id = idx; msg->function_id = 0x00 << 4; /* getDfuStatus */ msg->hidpp_version = self->hidpp_version; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to get DFU status: "); return FALSE; } if ((msg->data[2] & 0x01) > 0) { g_warning ("DFU mode not available"); } else { self->is_updatable = TRUE; fu_device_remove_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } } idx = fu_unifying_peripheral_feature_get_idx (self, HIDPP_FEATURE_DFU); if (idx != 0x00) { self->is_updatable = TRUE; fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); if (fu_device_get_version (device) == NULL) { g_debug ("repairing device in bootloader mode"); fu_device_set_version (FU_DEVICE (device), "MPK00.00_B0000", FWUPD_VERSION_FORMAT_PLAIN); } } /* this device may have changed state */ fu_unifying_peripheral_refresh_updatable (self); /* poll for pings to track active state */ fu_device_set_poll_interval (device, 30000); return TRUE; } static gboolean fu_unifying_peripheral_detach (FuDevice *device, GError **error) { FuUnifyingPeripheral *self = FU_UNIFYING_PERIPHERAL (device); guint8 idx; g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); /* this requires user action */ idx = fu_unifying_peripheral_feature_get_idx (self, HIDPP_FEATURE_DFU_CONTROL); if (idx != 0x00) { msg->report_id = HIDPP_REPORT_ID_LONG; msg->device_id = self->hidpp_id; msg->sub_id = idx; msg->function_id = 0x01 << 4; /* setDfuControl */ msg->data[0] = 0x01; /* enterDfu */ msg->data[1] = 0x00; /* dfuControlParam */ msg->data[2] = 0x00; /* unused */ msg->data[3] = 0x00; /* unused */ msg->data[4] = 'D'; msg->data[5] = 'F'; msg->data[6] = 'U'; msg->hidpp_version = self->hidpp_version; msg->flags = FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID | FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to put device into DFU mode: "); return FALSE; } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } /* this can reboot all by itself */ idx = fu_unifying_peripheral_feature_get_idx (self, HIDPP_FEATURE_DFU_CONTROL_SIGNED); if (idx != 0x00) { msg->report_id = HIDPP_REPORT_ID_LONG; msg->device_id = self->hidpp_id; msg->sub_id = idx; msg->function_id = 0x01 << 4; /* setDfuControl */ msg->data[0] = 0x01; /* startDfu */ msg->data[1] = 0x00; /* dfuControlParam */ msg->data[2] = 0x00; /* unused */ msg->data[3] = 0x00; /* unused */ msg->data[4] = 'D'; msg->data[5] = 'F'; msg->data[6] = 'U'; msg->flags = FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to put device into DFU mode: "); return FALSE; } return fu_unifying_peripheral_setup (FU_DEVICE (self), error); } /* we don't know how */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "no method to detach"); return FALSE; } static gboolean fu_unifying_peripheral_check_status (guint8 status, GError **error) { switch (status & 0x7f) { case 0x00: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid status value 0x%02x", status); break; case 0x01: /* packet success */ case 0x02: /* DFU success */ case 0x05: /* DFU success: entity restart required */ case 0x06: /* DFU success: system restart required */ /* success */ return TRUE; break; case 0x03: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PENDING, "wait for event (command in progress)"); break; case 0x04: case 0x10: /* unknown */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic error"); break; case 0x11: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad voltage (power too low?)"); break; case 0x12: case 0x14: /* bad magic string */ case 0x21: /* bad firmware */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "unsupported firmware"); break; case 0x13: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "unsupported encryption mode"); break; case 0x15: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "erase failure"); break; case 0x16: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "DFU not started"); break; case 0x17: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad sequence number"); break; case 0x18: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "unsupported command"); break; case 0x19: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "command in progress"); break; case 0x1a: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "address out of range"); break; case 0x1b: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "unaligned address"); break; case 0x1c: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad size"); break; case 0x1d: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "missing program data"); break; case 0x1e: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "missing check data"); break; case 0x1f: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "program failed to write"); break; case 0x20: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "program failed to verify"); break; case 0x22: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "firmware check failure"); break; case 0x23: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "blocked command (restart required)"); break; default: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "unhandled status value 0x%02x", status); break; } return FALSE; } static gboolean fu_unifying_peripheral_write_firmware_pkt (FuUnifyingPeripheral *self, guint8 idx, guint8 cmd, const guint8 *data, GError **error) { guint32 packet_cnt; g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); g_autoptr(GError) error_local = NULL; /* send firmware data */ msg->report_id = HIDPP_REPORT_ID_LONG; msg->device_id = self->hidpp_id; msg->sub_id = idx; msg->function_id = cmd << 4; /* dfuStart or dfuCmdDataX */ msg->hidpp_version = self->hidpp_version; memcpy (msg->data, data, 16); if (!fu_unifying_hidpp_transfer (self->io_channel, msg, &error_local)) { g_prefix_error (error, "failed to supply program data: "); return FALSE; } /* check error */ packet_cnt = fu_common_read_uint32 (msg->data, G_BIG_ENDIAN); g_debug ("packet_cnt=0x%04x", packet_cnt); if (fu_unifying_peripheral_check_status (msg->data[4], &error_local)) return TRUE; /* fatal error */ if (!g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_PENDING)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, error_local->message); return FALSE; } /* wait for the HID++ notification */ g_debug ("ignoring: %s", error_local->message); for (guint retry = 0; retry < 10; retry++) { g_autoptr(FuUnifyingHidppMsg) msg2 = fu_unifying_hidpp_msg_new (); msg2->flags = FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_FNCT_ID; if (!fu_unifying_hidpp_receive (self->io_channel, msg2, 15000, error)) return FALSE; if (fu_unifying_hidpp_msg_is_reply (msg, msg2)) { g_autoptr(GError) error2 = NULL; if (!fu_unifying_peripheral_check_status (msg2->data[4], &error2)) { g_debug ("got %s, waiting a bit longer", error2->message); continue; } return TRUE; } else { g_debug ("got wrong packet, continue to wait..."); } } /* nothing in the queue */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get event after timeout"); return FALSE; } static gboolean fu_unifying_peripheral_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuUnifyingPeripheral *self = FU_UNIFYING_PERIPHERAL (device); gsize sz = 0; const guint8 *data; guint8 cmd = 0x04; guint8 idx; /* if we're in bootloader mode, we should be able to get this feature */ idx = fu_unifying_peripheral_feature_get_idx (self, HIDPP_FEATURE_DFU); if (idx == 0x00) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "no DFU feature available"); return FALSE; } /* flash hardware */ data = g_bytes_get_data (fw, &sz); fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (gsize i = 0; i < sz / 16; i++) { /* send packet and wait for reply */ g_debug ("send data at addr=0x%04x", (guint) i * 16); if (!fu_unifying_peripheral_write_firmware_pkt (self, idx, cmd, data + (i * 16), error)) { g_prefix_error (error, "failed to write @0x%04x: ", (guint) i * 16); return FALSE; } /* use sliding window */ cmd = (cmd + 1) % 4; /* update progress-bar */ fu_device_set_progress_full (device, i * 16, sz); } return TRUE; } static gboolean fu_unifying_peripheral_attach (FuDevice *device, GError **error) { FuUnifyingPeripheral *self = FU_UNIFYING_PERIPHERAL (device); guint8 idx; g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); /* if we're in bootloader mode, we should be able to get this feature */ idx = fu_unifying_peripheral_feature_get_idx (self, HIDPP_FEATURE_DFU); if (idx == 0x00) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "no DFU feature available"); return FALSE; } /* reboot back into firmware mode */ msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = self->hidpp_id; msg->sub_id = idx; msg->function_id = 0x05 << 4; /* restart */ msg->data[0] = self->cached_fw_entity; /* fwEntity */ msg->hidpp_version = self->hidpp_version; msg->flags = FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID | FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SWID | // inferred? FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to restart device: "); return FALSE; } /* reprobe */ if (!fu_unifying_peripheral_setup (device, error)) return FALSE; /* success */ return TRUE; } static void fu_unifying_peripheral_finalize (GObject *object) { FuUnifyingPeripheral *self = FU_UNIFYING_PERIPHERAL (object); g_ptr_array_unref (self->feature_index); G_OBJECT_CLASS (fu_unifying_peripheral_parent_class)->finalize (object); } static void fu_unifying_peripheral_class_init (FuUnifyingPeripheralClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUdevDeviceClass *klass_device_udev = FU_UDEV_DEVICE_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_unifying_peripheral_finalize; klass_device->setup = fu_unifying_peripheral_setup; klass_device->open = fu_unifying_peripheral_open; klass_device->close = fu_unifying_peripheral_close; klass_device->write_firmware = fu_unifying_peripheral_write_firmware; klass_device->attach = fu_unifying_peripheral_attach; klass_device->detach = fu_unifying_peripheral_detach; klass_device->poll = fu_unifying_peripheral_poll; klass_device->to_string = fu_unifying_peripheral_to_string; klass_device_udev->probe = fu_unifying_peripheral_probe; } static void fu_unifying_peripheral_init (FuUnifyingPeripheral *self) { self->hidpp_id = HIDPP_DEVICE_ID_UNSET; self->feature_index = g_ptr_array_new_with_free_func (g_free); fu_device_add_parent_guid (FU_DEVICE (self), "HIDRAW\\VEN_046D&DEV_C52B"); fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } fwupd-1.2.14/plugins/unifying/fu-unifying-peripheral.h000066400000000000000000000005541402665037500230130ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-udev-device.h" G_BEGIN_DECLS #define FU_TYPE_UNIFYING_PERIPHERAL (fu_unifying_peripheral_get_type ()) G_DECLARE_FINAL_TYPE (FuUnifyingPeripheral, fu_unifying_peripheral, FU, UNIFYING_PERIPHERAL, FuUdevDevice) G_END_DECLS fwupd-1.2.14/plugins/unifying/fu-unifying-runtime.c000066400000000000000000000231571402665037500223420ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-unifying-common.h" #include "fu-unifying-runtime.h" #include "fu-unifying-hidpp.h" struct _FuUnifyingRuntime { FuUdevDevice parent_instance; guint8 version_bl_major; gboolean signed_firmware; FuIOChannel *io_channel; }; G_DEFINE_TYPE (FuUnifyingRuntime, fu_unifying_runtime, FU_TYPE_UDEV_DEVICE) #ifndef HAVE_GUDEV_232 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref) #endif static void fu_unifying_runtime_to_string (FuDevice *device, GString *str) { FuUnifyingRuntime *self = FU_UNIFYING_RUNTIME (device); g_string_append_printf (str, " SignedFirmware:\t%i\n", self->signed_firmware); } static gboolean fu_unifying_runtime_enable_notifications (FuUnifyingRuntime *self, GError **error) { g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = HIDPP_DEVICE_ID_RECEIVER; msg->sub_id = HIDPP_SUBID_SET_REGISTER; msg->function_id = HIDPP_REGISTER_HIDPP_NOTIFICATIONS; msg->data[0] = 0x00; msg->data[1] = 0x05; /* Wireless + SoftwarePresent */ msg->data[2] = 0x00; msg->hidpp_version = 1; return fu_unifying_hidpp_transfer (self->io_channel, msg, error); } static gboolean fu_unifying_runtime_close (FuDevice *device, GError **error) { FuUnifyingRuntime *self = FU_UNIFYING_RUNTIME (device); if (!fu_io_channel_shutdown (self->io_channel, error)) return FALSE; g_clear_object (&self->io_channel); return TRUE; } static gboolean fu_unifying_runtime_poll (FuDevice *device, GError **error) { FuUnifyingRuntime *self = FU_UNIFYING_RUNTIME (device); const guint timeout = 1; /* ms */ g_autoptr(GError) error_local = NULL; g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); g_autoptr(FuDeviceLocker) locker = NULL; /* open */ locker = fu_device_locker_new (self, error); if (locker == NULL) return FALSE; /* is there any pending data to read */ msg->hidpp_version = 1; if (!fu_unifying_hidpp_receive (self->io_channel, msg, timeout, &error_local)) { if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { return TRUE; } g_warning ("failed to get pending read: %s", error_local->message); return TRUE; } /* HID++1.0 error */ if (!fu_unifying_hidpp_msg_is_error (msg, &error_local)) { g_warning ("failed to get pending read: %s", error_local->message); return TRUE; } /* unifying receiver notification */ if (msg->report_id == HIDPP_REPORT_ID_SHORT) { switch (msg->sub_id) { case HIDPP_SUBID_DEVICE_CONNECTION: case HIDPP_SUBID_DEVICE_DISCONNECTION: case HIDPP_SUBID_DEVICE_LOCKING_CHANGED: g_debug ("device connection event, do something"); break; case HIDPP_SUBID_LINK_QUALITY: g_debug ("ignoring link quality message"); break; case HIDPP_SUBID_ERROR_MSG: g_debug ("ignoring link quality message"); break; default: g_debug ("unknown SubID %02x", msg->sub_id); break; } } return TRUE; } static gboolean fu_unifying_runtime_open (FuDevice *device, GError **error) { FuUnifyingRuntime *self = FU_UNIFYING_RUNTIME (device); GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device)); const gchar *devpath = g_udev_device_get_device_file (udev_device); /* open, but don't block */ self->io_channel = fu_io_channel_new_file (devpath, error); if (self->io_channel == NULL) return FALSE; /* poll for notifications */ fu_device_set_poll_interval (device, 5000); /* success */ return TRUE; } static gboolean fu_unifying_runtime_probe (FuUdevDevice *device, GError **error) { FuUnifyingRuntime *self = FU_UNIFYING_RUNTIME (device); GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device)); guint16 release = 0xffff; g_autoptr(GUdevDevice) udev_parent = NULL; /* set the physical ID */ if (!fu_udev_device_set_physical_id (device, "usb", error)) return FALSE; /* generate bootloader-specific GUID */ udev_parent = g_udev_device_get_parent_with_subsystem (udev_device, "usb", "usb_device"); if (udev_parent != NULL) { const gchar *release_str; release_str = g_udev_device_get_property (udev_parent, "ID_REVISION"); if (release_str != NULL) release = g_ascii_strtoull (release_str, NULL, 16); } if (release != 0xffff) { g_autofree gchar *devid2 = NULL; switch (release &= 0xff00) { case 0x1200: /* Nordic */ devid2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X", (guint) FU_UNIFYING_DEVICE_VID, (guint) FU_UNIFYING_DEVICE_PID_BOOTLOADER_NORDIC); fu_device_add_counterpart_guid (FU_DEVICE (device), devid2); self->version_bl_major = 0x01; break; case 0x2400: /* Texas */ devid2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X", (guint) FU_UNIFYING_DEVICE_VID, (guint) FU_UNIFYING_DEVICE_PID_BOOTLOADER_TEXAS); fu_device_add_counterpart_guid (FU_DEVICE (device), devid2); self->version_bl_major = 0x03; break; default: g_warning ("bootloader release %04x invalid", release); break; } } return TRUE; } static gboolean fu_unifying_runtime_setup_internal (FuDevice *device, GError **error) { FuUnifyingRuntime *self = FU_UNIFYING_RUNTIME (device); guint8 config[10]; g_autofree gchar *version_fw = NULL; /* read all 10 bytes of the version register */ memset (config, 0x00, sizeof (config)); for (guint i = 0x01; i < 0x05; i++) { g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); /* workaround a bug in the 12.01 firmware, which fails with * INVALID_VALUE when reading MCU1_HW_VERSION */ if (i == 0x03) continue; msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = HIDPP_DEVICE_ID_RECEIVER; msg->sub_id = HIDPP_SUBID_GET_REGISTER; msg->function_id = HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION; msg->data[0] = i; msg->hidpp_version = 1; if (!fu_unifying_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to read device config: "); return FALSE; } memcpy (config + (i * 2), msg->data + 1, 2); } /* get firmware version */ version_fw = fu_unifying_format_version ("RQR", config[2], config[3], (guint16) config[4] << 8 | config[5]); fu_device_set_version (device, version_fw, FWUPD_VERSION_FORMAT_PLAIN); /* get bootloader version */ if (self->version_bl_major > 0) { g_autofree gchar *version_bl = NULL; version_bl = fu_unifying_format_version ("BOT", self->version_bl_major, config[8], config[9]); fu_device_set_version_bootloader (FU_DEVICE (device), version_bl); /* is the dongle expecting signed firmware */ if ((self->version_bl_major == 0x01 && config[8] >= 0x04) || (self->version_bl_major == 0x03 && config[8] >= 0x02)) { self->signed_firmware = TRUE; } } /* enable HID++ notifications */ if (!fu_unifying_runtime_enable_notifications (self, error)) { g_prefix_error (error, "failed to enable notifications: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_unifying_runtime_setup (FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; for (guint i = 0; i < 5; i++) { /* HID++1.0 devices have to sleep to allow Solaar to talk to * the device first -- we can't use the SwID as this is a * HID++2.0 feature */ g_usleep (200*1000); if (fu_unifying_runtime_setup_internal (device, &error_local)) return TRUE; if (!g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_DATA)) { g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } g_clear_error (&error_local); } g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } static gboolean fu_unifying_runtime_detach (FuDevice *device, GError **error) { FuUnifyingRuntime *self = FU_UNIFYING_RUNTIME (device); g_autoptr(FuUnifyingHidppMsg) msg = fu_unifying_hidpp_msg_new (); msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = HIDPP_DEVICE_ID_RECEIVER; msg->sub_id = HIDPP_SUBID_SET_REGISTER; msg->function_id = HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE; msg->data[0] = 'I'; msg->data[1] = 'C'; msg->data[2] = 'P'; msg->hidpp_version = 1; msg->flags = FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT; if (!fu_unifying_hidpp_send (self->io_channel, msg, FU_UNIFYING_DEVICE_TIMEOUT_MS, error)) { g_prefix_error (error, "failed to detach to bootloader: "); return FALSE; } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_unifying_runtime_finalize (GObject *object) { G_OBJECT_CLASS (fu_unifying_runtime_parent_class)->finalize (object); } static void fu_unifying_runtime_class_init (FuUnifyingRuntimeClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUdevDeviceClass *klass_device_udev = FU_UDEV_DEVICE_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_unifying_runtime_finalize; klass_device->open = fu_unifying_runtime_open; klass_device_udev->probe = fu_unifying_runtime_probe; klass_device->setup = fu_unifying_runtime_setup; klass_device->close = fu_unifying_runtime_close; klass_device->detach = fu_unifying_runtime_detach; klass_device->poll = fu_unifying_runtime_poll; klass_device->to_string = fu_unifying_runtime_to_string; } static void fu_unifying_runtime_init (FuUnifyingRuntime *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_icon (FU_DEVICE (self), "preferences-desktop-keyboard"); fu_device_set_name (FU_DEVICE (self), "Unifying Receiver"); fu_device_set_summary (FU_DEVICE (self), "A miniaturised USB wireless receiver"); fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } fwupd-1.2.14/plugins/unifying/fu-unifying-runtime.h000066400000000000000000000005351402665037500223420ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-udev-device.h" G_BEGIN_DECLS #define FU_TYPE_UNIFYING_RUNTIME (fu_unifying_runtime_get_type ()) G_DECLARE_FINAL_TYPE (FuUnifyingRuntime, fu_unifying_runtime, FU, UNIFYING_RUNTIME, FuUdevDevice) G_END_DECLS fwupd-1.2.14/plugins/unifying/fu-unifying-self-test.c000066400000000000000000000015531402665037500225610ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-unifying-common.h" static void fu_unifying_common (void) { guint8 u8; guint16 u16; g_autofree gchar *ver1 = NULL; u8 = fu_unifying_buffer_read_uint8 ("12"); g_assert_cmpint (u8, ==, 0x12); u16 = fu_unifying_buffer_read_uint16 ("1234"); g_assert_cmpint (u16, ==, 0x1234); ver1 = fu_unifying_format_version (" A ", 0x87, 0x65, 0x4321); g_assert_cmpstr (ver1, ==, "A87.65_B4321"); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func ("/unifying/common", fu_unifying_common); return g_test_run (); } fwupd-1.2.14/plugins/unifying/meson.build000066400000000000000000000023171402665037500204120ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginUnifying"'] install_data([ 'unifying.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_unifying', fu_hash, sources : [ 'fu-plugin-unifying.c', 'fu-unifying-bootloader.c', 'fu-unifying-bootloader-nordic.c', 'fu-unifying-bootloader-texas.c', 'fu-unifying-common.c', 'fu-unifying-hidpp.c', 'fu-unifying-hidpp-msg.c', 'fu-unifying-peripheral.c', 'fu-unifying-runtime.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) if get_option('tests') e = executable( 'unifying-self-test', fu_hash, sources : [ 'fu-unifying-self-test.c', 'fu-unifying-common.c', ], include_directories : [ include_directories('../..'), include_directories('../../libfwupd'), ], dependencies : [ plugin_deps, ], link_with : [ libfwupdprivate, ], c_args : cargs, ) test('unifying-self-test', e) endif fwupd-1.2.14/plugins/unifying/unifying.quirk000066400000000000000000000016101402665037500211500ustar00rootroot00000000000000# Unifying Receiver [DeviceInstanceId=HIDRAW\VEN_046D&DEV_C52B] Plugin = unifying Flags = is-receiver VendorId=USB:0x046D InstallDuration = 7 # Nordic [DeviceInstanceId=USB\VID_046D&PID_AAAA] Plugin = unifying Flags = is-bootloader,is-nordic FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 7 # Nordic Pico [DeviceInstanceId=USB\VID_046D&PID_AAAE] Plugin = unifying Flags = is-bootloader,is-nordic FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 7 # Texas [DeviceInstanceId=USB\VID_046D&PID_AAAC] Plugin = unifying Flags = is-bootloader,is-texas FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 7 # Texas Pico [DeviceInstanceId=USB\VID_046D&PID_AAAD] Plugin = unifying Flags = is-bootloader,is-texas FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 7 fwupd-1.2.14/plugins/upower/000077500000000000000000000000001402665037500157365ustar00rootroot00000000000000fwupd-1.2.14/plugins/upower/README.md000066400000000000000000000002111402665037500172070ustar00rootroot00000000000000UPower Support ============== Introduction ------------ This plugin is used to ensure that some updates are not done on battery power. fwupd-1.2.14/plugins/upower/fu-plugin-upower.c000066400000000000000000000103771402665037500213370ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #define MINIMUM_BATTERY_PERCENTAGE 10 struct FuPluginData { GDBusProxy *upower_proxy; GDBusProxy *display_proxy; }; void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); if (data->upower_proxy != NULL) g_object_unref (data->upower_proxy); if (data->display_proxy != NULL) g_object_unref (data->display_proxy); } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autofree gchar *name_owner = NULL; data->upower_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, "org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", NULL, error); if (data->upower_proxy == NULL) { g_prefix_error (error, "failed to connect to upower: "); return FALSE; } name_owner = g_dbus_proxy_get_name_owner (data->upower_proxy); if (name_owner == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no owner for %s", g_dbus_proxy_get_name (data->upower_proxy)); return FALSE; } data->display_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, "org.freedesktop.UPower", "/org/freedesktop/UPower/devices/DisplayDevice", "org.freedesktop.UPower.Device", NULL, error); if (data->display_proxy == NULL) { g_prefix_error (error, "failed to connect to upower: "); return FALSE; } return TRUE; } static gboolean fu_plugin_upower_check_percentage_level (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); gdouble level; guint power_type; g_autoptr(GVariant) percentage_val = NULL; g_autoptr(GVariant) type_val = NULL; /* check that we "have" a battery */ type_val = g_dbus_proxy_get_cached_property (data->display_proxy, "Type"); if (type_val == NULL) { g_warning ("Failed to query power type, assume AC power"); return TRUE; } power_type = g_variant_get_uint32 (type_val); if (power_type != 2) { g_debug ("Not running on battery (Type: %u)", power_type); return TRUE; } /* check percentage high enough */ percentage_val = g_dbus_proxy_get_cached_property (data->display_proxy, "Percentage"); if (percentage_val == NULL) { g_warning ("Failed to query power percentage level, assume enough charge"); return TRUE; } level = g_variant_get_double (percentage_val); g_debug ("System power source is %.1f%%", level); return level >= MINIMUM_BATTERY_PERCENTAGE; } static gboolean fu_plugin_upower_check_on_battery (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); g_autoptr(GVariant) value = NULL; value = g_dbus_proxy_get_cached_property (data->upower_proxy, "OnBattery"); if (value == NULL) { g_warning ("failed to get OnBattery value, assume on AC power"); return FALSE; } return g_variant_get_boolean (value); } gboolean fu_plugin_update_prepare (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *device, GError **error) { /* not all devices need this */ if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REQUIRE_AC)) return TRUE; /* determine if operating on AC or battery */ if (fu_plugin_upower_check_on_battery (plugin) && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_AC_POWER_REQUIRED, "Cannot install update " "when not on AC power unless forced"); return FALSE; } /* deteremine if battery high enough */ if (!fu_plugin_upower_check_percentage_level (plugin) && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW, "Cannot install update when battery " "is not at least %d%% unless forced", MINIMUM_BATTERY_PERCENTAGE); return FALSE; } return TRUE; } fwupd-1.2.14/plugins/upower/meson.build000066400000000000000000000006551402665037500201060ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginUpower"'] shared_module('fu_plugin_upower', fu_hash, sources : [ 'fu-plugin-upower.c', ], include_directories : [ include_directories('../..'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, link_with : [ libfwupdprivate, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.2.14/plugins/wacom-raw/000077500000000000000000000000001402665037500163125ustar00rootroot00000000000000fwupd-1.2.14/plugins/wacom-raw/README.md000066400000000000000000000017651402665037500176020ustar00rootroot00000000000000Wacom RAW Support ================= Introduction ------------ This plugin updates integrated Wacom AES and EMR devices. They are typically connected using I²C and not USB. GUID Generation --------------- The HID DeviceInstanceId values are used, e.g. `HIDRAW\VEN_056A&DEV_4875`. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in Intel HEX file format. This plugin supports the following protocol ID: * com.wacom.raw Quirk use --------- This plugin uses the following plugin-specific quirks: | Quirk | Description | Minimum fwupd version | |-------------------------|-------------------------------------|-----------------------| | `WacomI2cFlashBlockSize`| Block size to transfer firmware | 1.2.4 | | `WacomI2cFlashBaseAddr` | Base address for firmware | 1.2.4 | | `WacomI2cFlashSize` | Maximum size of the firmware zone | 1.2.4 | fwupd-1.2.14/plugins/wacom-raw/data/000077500000000000000000000000001402665037500172235ustar00rootroot00000000000000fwupd-1.2.14/plugins/wacom-raw/data/hid-recorder.txt000066400000000000000000001054561402665037500223460ustar00rootroot00000000000000# WCOM4875:00 056A:4875 # 0x05, 0x0d, // Usage Page (Digitizers) 0 # 0x09, 0x04, // Usage (Touch Screen) 2 # 0xa1, 0x01, // Collection (Application) 4 # 0x85, 0x0c, // Report ID (12) 6 # 0x95, 0x01, // Report Count (1) 8 # 0x75, 0x08, // Report Size (8) 10 # 0x26, 0xff, 0x00, // Logical Maximum (255) 12 # 0x15, 0x00, // Logical Minimum (0) 15 # 0x81, 0x03, // Input (Cnst,Var,Abs) 17 # 0x09, 0x54, // Usage (Contact Count) 19 # 0x81, 0x02, // Input (Data,Var,Abs) 21 # 0x05, 0x0d, // Usage Page (Digitizers) 23 # 0x09, 0x22, // Usage (Finger) 25 # 0xa1, 0x02, // Collection (Logical) 27 # 0x09, 0x42, // Usage (Tip Switch) 29 # 0x15, 0x00, // Logical Minimum (0) 31 # 0x25, 0x01, // Logical Maximum (1) 33 # 0x75, 0x01, // Report Size (1) 35 # 0x95, 0x01, // Report Count (1) 37 # 0x81, 0x02, // Input (Data,Var,Abs) 39 # 0x81, 0x03, // Input (Cnst,Var,Abs) 41 # 0x09, 0x47, // Usage (Confidence) 43 # 0x81, 0x02, // Input (Data,Var,Abs) 45 # 0x95, 0x05, // Report Count (5) 47 # 0x81, 0x03, // Input (Cnst,Var,Abs) 49 # 0x75, 0x10, // Report Size (16) 51 # 0x09, 0x51, // Usage (Contact Id) 53 # 0x95, 0x01, // Report Count (1) 55 # 0x81, 0x02, // Input (Data,Var,Abs) 57 # 0x05, 0x01, // Usage Page (Generic Desktop) 59 # 0x75, 0x10, // Report Size (16) 61 # 0x95, 0x01, // Report Count (1) 63 # 0x55, 0x0e, // Unit Exponent (-2) 65 # 0x65, 0x11, // Unit (Centimeter,SILinear) 67 # 0x09, 0x30, // Usage (X) 69 # 0x26, 0xc8, 0x35, // Logical Maximum (13768) 71 # 0x35, 0x00, // Physical Minimum (0) 74 # 0x46, 0x72, 0x0d, // Physical Maximum (3442) 76 # 0x81, 0x02, // Input (Data,Var,Abs) 79 # 0x46, 0x90, 0x07, // Physical Maximum (1936) 81 # 0x09, 0x31, // Usage (Y) 84 # 0x26, 0x40, 0x1e, // Logical Maximum (7744) 86 # 0x81, 0x02, // Input (Data,Var,Abs) 89 # 0xc0, // End Collection 91 # 0x05, 0x0d, // Usage Page (Digitizers) 92 # 0x09, 0x22, // Usage (Finger) 94 # 0xa1, 0x02, // Collection (Logical) 96 # 0x09, 0x42, // Usage (Tip Switch) 98 # 0x15, 0x00, // Logical Minimum (0) 100 # 0x25, 0x01, // Logical Maximum (1) 102 # 0x75, 0x01, // Report Size (1) 104 # 0x95, 0x01, // Report Count (1) 106 # 0x81, 0x02, // Input (Data,Var,Abs) 108 # 0x81, 0x03, // Input (Cnst,Var,Abs) 110 # 0x09, 0x47, // Usage (Confidence) 112 # 0x81, 0x02, // Input (Data,Var,Abs) 114 # 0x95, 0x05, // Report Count (5) 116 # 0x81, 0x03, // Input (Cnst,Var,Abs) 118 # 0x75, 0x10, // Report Size (16) 120 # 0x09, 0x51, // Usage (Contact Id) 122 # 0x95, 0x01, // Report Count (1) 124 # 0x81, 0x02, // Input (Data,Var,Abs) 126 # 0x05, 0x01, // Usage Page (Generic Desktop) 128 # 0x75, 0x10, // Report Size (16) 130 # 0x95, 0x01, // Report Count (1) 132 # 0x55, 0x0e, // Unit Exponent (-2) 134 # 0x65, 0x11, // Unit (Centimeter,SILinear) 136 # 0x09, 0x30, // Usage (X) 138 # 0x26, 0xc8, 0x35, // Logical Maximum (13768) 140 # 0x35, 0x00, // Physical Minimum (0) 143 # 0x46, 0x72, 0x0d, // Physical Maximum (3442) 145 # 0x81, 0x02, // Input (Data,Var,Abs) 148 # 0x46, 0x90, 0x07, // Physical Maximum (1936) 150 # 0x09, 0x31, // Usage (Y) 153 # 0x26, 0x40, 0x1e, // Logical Maximum (7744) 155 # 0x81, 0x02, // Input (Data,Var,Abs) 158 # 0xc0, // End Collection 160 # 0x05, 0x0d, // Usage Page (Digitizers) 161 # 0x09, 0x22, // Usage (Finger) 163 # 0xa1, 0x02, // Collection (Logical) 165 # 0x09, 0x42, // Usage (Tip Switch) 167 # 0x15, 0x00, // Logical Minimum (0) 169 # 0x25, 0x01, // Logical Maximum (1) 171 # 0x75, 0x01, // Report Size (1) 173 # 0x95, 0x01, // Report Count (1) 175 # 0x81, 0x02, // Input (Data,Var,Abs) 177 # 0x81, 0x03, // Input (Cnst,Var,Abs) 179 # 0x09, 0x47, // Usage (Confidence) 181 # 0x81, 0x02, // Input (Data,Var,Abs) 183 # 0x95, 0x05, // Report Count (5) 185 # 0x81, 0x03, // Input (Cnst,Var,Abs) 187 # 0x75, 0x10, // Report Size (16) 189 # 0x09, 0x51, // Usage (Contact Id) 191 # 0x95, 0x01, // Report Count (1) 193 # 0x81, 0x02, // Input (Data,Var,Abs) 195 # 0x05, 0x01, // Usage Page (Generic Desktop) 197 # 0x75, 0x10, // Report Size (16) 199 # 0x95, 0x01, // Report Count (1) 201 # 0x55, 0x0e, // Unit Exponent (-2) 203 # 0x65, 0x11, // Unit (Centimeter,SILinear) 205 # 0x09, 0x30, // Usage (X) 207 # 0x26, 0xc8, 0x35, // Logical Maximum (13768) 209 # 0x35, 0x00, // Physical Minimum (0) 212 # 0x46, 0x72, 0x0d, // Physical Maximum (3442) 214 # 0x81, 0x02, // Input (Data,Var,Abs) 217 # 0x46, 0x90, 0x07, // Physical Maximum (1936) 219 # 0x09, 0x31, // Usage (Y) 222 # 0x26, 0x40, 0x1e, // Logical Maximum (7744) 224 # 0x81, 0x02, // Input (Data,Var,Abs) 227 # 0xc0, // End Collection 229 # 0x05, 0x0d, // Usage Page (Digitizers) 230 # 0x09, 0x22, // Usage (Finger) 232 # 0xa1, 0x02, // Collection (Logical) 234 # 0x09, 0x42, // Usage (Tip Switch) 236 # 0x15, 0x00, // Logical Minimum (0) 238 # 0x25, 0x01, // Logical Maximum (1) 240 # 0x75, 0x01, // Report Size (1) 242 # 0x95, 0x01, // Report Count (1) 244 # 0x81, 0x02, // Input (Data,Var,Abs) 246 # 0x81, 0x03, // Input (Cnst,Var,Abs) 248 # 0x09, 0x47, // Usage (Confidence) 250 # 0x81, 0x02, // Input (Data,Var,Abs) 252 # 0x95, 0x05, // Report Count (5) 254 # 0x81, 0x03, // Input (Cnst,Var,Abs) 256 # 0x75, 0x10, // Report Size (16) 258 # 0x09, 0x51, // Usage (Contact Id) 260 # 0x95, 0x01, // Report Count (1) 262 # 0x81, 0x02, // Input (Data,Var,Abs) 264 # 0x05, 0x01, // Usage Page (Generic Desktop) 266 # 0x75, 0x10, // Report Size (16) 268 # 0x95, 0x01, // Report Count (1) 270 # 0x55, 0x0e, // Unit Exponent (-2) 272 # 0x65, 0x11, // Unit (Centimeter,SILinear) 274 # 0x09, 0x30, // Usage (X) 276 # 0x26, 0xc8, 0x35, // Logical Maximum (13768) 278 # 0x35, 0x00, // Physical Minimum (0) 281 # 0x46, 0x72, 0x0d, // Physical Maximum (3442) 283 # 0x81, 0x02, // Input (Data,Var,Abs) 286 # 0x46, 0x90, 0x07, // Physical Maximum (1936) 288 # 0x09, 0x31, // Usage (Y) 291 # 0x26, 0x40, 0x1e, // Logical Maximum (7744) 293 # 0x81, 0x02, // Input (Data,Var,Abs) 296 # 0xc0, // End Collection 298 # 0x05, 0x0d, // Usage Page (Digitizers) 299 # 0x09, 0x22, // Usage (Finger) 301 # 0xa1, 0x02, // Collection (Logical) 303 # 0x09, 0x42, // Usage (Tip Switch) 305 # 0x15, 0x00, // Logical Minimum (0) 307 # 0x25, 0x01, // Logical Maximum (1) 309 # 0x75, 0x01, // Report Size (1) 311 # 0x95, 0x01, // Report Count (1) 313 # 0x81, 0x02, // Input (Data,Var,Abs) 315 # 0x81, 0x03, // Input (Cnst,Var,Abs) 317 # 0x09, 0x47, // Usage (Confidence) 319 # 0x81, 0x02, // Input (Data,Var,Abs) 321 # 0x95, 0x05, // Report Count (5) 323 # 0x81, 0x03, // Input (Cnst,Var,Abs) 325 # 0x75, 0x10, // Report Size (16) 327 # 0x09, 0x51, // Usage (Contact Id) 329 # 0x95, 0x01, // Report Count (1) 331 # 0x81, 0x02, // Input (Data,Var,Abs) 333 # 0x05, 0x01, // Usage Page (Generic Desktop) 335 # 0x75, 0x10, // Report Size (16) 337 # 0x95, 0x01, // Report Count (1) 339 # 0x55, 0x0e, // Unit Exponent (-2) 341 # 0x65, 0x11, // Unit (Centimeter,SILinear) 343 # 0x09, 0x30, // Usage (X) 345 # 0x26, 0xc8, 0x35, // Logical Maximum (13768) 347 # 0x35, 0x00, // Physical Minimum (0) 350 # 0x46, 0x72, 0x0d, // Physical Maximum (3442) 352 # 0x81, 0x02, // Input (Data,Var,Abs) 355 # 0x46, 0x90, 0x07, // Physical Maximum (1936) 357 # 0x09, 0x31, // Usage (Y) 360 # 0x26, 0x40, 0x1e, // Logical Maximum (7744) 362 # 0x81, 0x02, // Input (Data,Var,Abs) 365 # 0xc0, // End Collection 367 # 0x05, 0x0d, // Usage Page (Digitizers) 368 # 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) 370 # 0x75, 0x10, // Report Size (16) 375 # 0x95, 0x01, // Report Count (1) 377 # 0x09, 0x56, // Usage (Scan Time) 379 # 0x81, 0x02, // Input (Data,Var,Abs) 381 # 0x85, 0x0c, // Report ID (12) 383 # 0x09, 0x55, // Usage (Contact Max) 385 # 0x75, 0x08, // Report Size (8) 387 # 0x95, 0x01, // Report Count (1) 389 # 0x26, 0xff, 0x00, // Logical Maximum (255) 391 # 0xb1, 0x02, // Feature (Data,Var,Abs) 394 # 0x85, 0x0a, // Report ID (10) 396 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 398 # 0x09, 0xc5, // Usage (Vendor Usage 0xc5) 401 # 0x96, 0x00, 0x01, // Report Count (256) 403 # 0xb1, 0x02, // Feature (Data,Var,Abs) 406 # 0xc0, // End Collection 408 # 0x06, 0x11, 0xff, // Usage Page (Vendor Usage Page 0xff11) 409 # 0x09, 0x11, // Usage (Vendor Usage 0x11) 412 # 0xa1, 0x01, // Collection (Application) 414 # 0x85, 0x03, // Report ID (3) 416 # 0xa1, 0x02, // Collection (Logical) 418 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 420 # 0x75, 0x08, // Report Size (8) 422 # 0x15, 0x00, // Logical Minimum (0) 424 # 0x26, 0xff, 0x00, // Logical Maximum (255) 426 # 0x95, 0x27, // Report Count (39) 429 # 0x81, 0x02, // Input (Data,Var,Abs) 431 # 0xc0, // End Collection 433 # 0x85, 0x02, // Report ID (2) 434 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 436 # 0x95, 0x01, // Report Count (1) 438 # 0xb1, 0x02, // Feature (Data,Var,Abs) 440 # 0x85, 0x03, // Report ID (3) 442 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 444 # 0x95, 0x3f, // Report Count (63) 446 # 0xb1, 0x02, // Feature (Data,Var,Abs) 448 # 0x85, 0x04, // Report ID (4) 450 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 452 # 0x95, 0x0f, // Report Count (15) 454 # 0xb1, 0x02, // Feature (Data,Var,Abs) 456 # 0x85, 0x07, // Report ID (7) 458 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 460 # 0x96, 0x00, 0x01, // Report Count (256) 462 # 0xb1, 0x02, // Feature (Data,Var,Abs) 465 # 0x85, 0x08, // Report ID (8) 467 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 469 # 0x96, 0x87, 0x00, // Report Count (135) 471 # 0xb1, 0x02, // Feature (Data,Var,Abs) 474 # 0x85, 0x09, // Report ID (9) 476 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 478 # 0x96, 0x3f, 0x00, // Report Count (63) 480 # 0xb1, 0x02, // Feature (Data,Var,Abs) 483 # 0x85, 0x0d, // Report ID (13) 485 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 487 # 0x95, 0x07, // Report Count (7) 489 # 0xb1, 0x02, // Feature (Data,Var,Abs) 491 # 0xc0, // End Collection 493 # 0x05, 0x0d, // Usage Page (Digitizers) 494 # 0x09, 0x0e, // Usage (Device Configuration) 496 # 0xa1, 0x01, // Collection (Application) 498 # 0x85, 0x0e, // Report ID (14) 500 # 0x09, 0x23, // Usage (Device Settings) 502 # 0xa1, 0x02, // Collection (Logical) 504 # 0x09, 0x52, // Usage (Inputmode) 506 # 0x09, 0x53, // Usage (Device Index) 508 # 0x15, 0x00, // Logical Minimum (0) 510 # 0x25, 0x0a, // Logical Maximum (10) 512 # 0x75, 0x08, // Report Size (8) 514 # 0x95, 0x02, // Report Count (2) 516 # 0xb1, 0x02, // Feature (Data,Var,Abs) 518 # 0xc0, // End Collection 520 # 0xc0, // End Collection 521 # 0x05, 0x0d, // Usage Page (Digitizers) 522 # 0x09, 0x02, // Usage (Pen) 524 # 0xa1, 0x01, // Collection (Application) 526 # 0x85, 0x06, // Report ID (6) 528 # 0xa4, // Push 530 # 0x09, 0x20, // Usage (Stylus) 531 # 0xa1, 0x00, // Collection (Physical) 533 # 0x09, 0x42, // Usage (Tip Switch) 535 # 0x09, 0x44, // Usage (Barrel Switch) 537 # 0x09, 0x45, // Usage (Eraser) 539 # 0x09, 0x3c, // Usage (Invert) 541 # 0x09, 0x5a, // Usage (Secondary Barrel Switch) 543 # 0x09, 0x32, // Usage (In Range) 545 # 0x15, 0x00, // Logical Minimum (0) 547 # 0x25, 0x01, // Logical Maximum (1) 549 # 0x75, 0x01, // Report Size (1) 551 # 0x95, 0x06, // Report Count (6) 553 # 0x81, 0x02, // Input (Data,Var,Abs) 555 # 0x95, 0x02, // Report Count (2) 557 # 0x81, 0x03, // Input (Cnst,Var,Abs) 559 # 0x05, 0x01, // Usage Page (Generic Desktop) 561 # 0x09, 0x30, // Usage (X) 563 # 0x27, 0x70, 0x86, 0x00, 0x00, // Logical Maximum (34416) 565 # 0x47, 0x70, 0x86, 0x00, 0x00, // Physical Maximum (34416) 570 # 0x65, 0x11, // Unit (Centimeter,SILinear) 575 # 0x55, 0x0d, // Unit Exponent (-3) 577 # 0x75, 0x10, // Report Size (16) 579 # 0x95, 0x01, // Report Count (1) 581 # 0x81, 0x02, // Input (Data,Var,Abs) 583 # 0x09, 0x31, // Usage (Y) 585 # 0x27, 0x9f, 0x4b, 0x00, 0x00, // Logical Maximum (19359) 587 # 0x47, 0x9f, 0x4b, 0x00, 0x00, // Physical Maximum (19359) 592 # 0x81, 0x02, // Input (Data,Var,Abs) 597 # 0x45, 0x00, // Physical Maximum (0) 599 # 0x65, 0x00, // Unit (None) 601 # 0x55, 0x00, // Unit Exponent (0) 603 # 0x05, 0x0d, // Usage Page (Digitizers) 605 # 0x09, 0x30, // Usage (Tip Pressure) 607 # 0x26, 0xff, 0x0f, // Logical Maximum (4095) 609 # 0x75, 0x10, // Report Size (16) 612 # 0x81, 0x02, // Input (Data,Var,Abs) 614 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 616 # 0x09, 0x5b, // Usage (Vendor Usage 0x5b) 619 # 0x16, 0x00, 0x80, // Logical Minimum (-32768) 621 # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 624 # 0x75, 0x10, // Report Size (16) 627 # 0x81, 0x02, // Input (Data,Var,Abs) 629 # 0x05, 0x0d, // Usage Page (Digitizers) 631 # 0x09, 0x5b, // Usage (Transducer Serial Number) 633 # 0x17, 0x00, 0x00, 0x00, 0x80, // Logical Minimum (-2147483648) 635 # 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 640 # 0x75, 0x20, // Report Size (32) 645 # 0x81, 0x02, // Input (Data,Var,Abs) 647 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 649 # 0x09, 0x00, // Usage (Undefined) 652 # 0x75, 0x08, // Report Size (8) 654 # 0x26, 0xff, 0x00, // Logical Maximum (255) 656 # 0x15, 0x00, // Logical Minimum (0) 659 # 0x81, 0x02, // Input (Data,Var,Abs) 661 # 0x05, 0x0d, // Usage Page (Digitizers) 663 # 0x09, 0x3b, // Usage (Battery Strength) 665 # 0x81, 0x02, // Input (Data,Var,Abs) 667 # 0x65, 0x14, // Unit (Degrees,EngRotation) 669 # 0x55, 0x00, // Unit Exponent (0) 671 # 0x16, 0xa6, 0xff, // Logical Minimum (-90) 673 # 0x26, 0x5a, 0x00, // Logical Maximum (90) 676 # 0x36, 0xa6, 0xff, // Physical Minimum (-90) 679 # 0x46, 0x5a, 0x00, // Physical Maximum (90) 682 # 0x75, 0x08, // Report Size (8) 685 # 0x09, 0x3d, // Usage (X Tilt) 687 # 0x81, 0x02, // Input (Data,Var,Abs) 689 # 0x09, 0x3e, // Usage (Y Tilt) 691 # 0x81, 0x02, // Input (Data,Var,Abs) 693 # 0xc0, // End Collection 695 # 0xb4, // Pop 696 # 0x85, 0x13, // Report ID (19) 697 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 699 # 0x09, 0xc5, // Usage (Vendor Usage 0xc5) 702 # 0x96, 0x00, 0x01, // Report Count (256) 704 # 0xb1, 0x02, // Feature (Data,Var,Abs) 707 # 0xc0, // End Collection 709 # 0x06, 0x11, 0xff, // Usage Page (Vendor Usage Page 0xff11) 710 # 0x09, 0x02, // Usage (Vendor Usage 0x02) 713 # 0xa1, 0x01, // Collection (Application) 715 # 0x85, 0x0b, // Report ID (11) 717 # 0xa4, // Push 719 # 0x09, 0x20, // Usage (Vendor Usage 0x20) 720 # 0xa1, 0x00, // Collection (Physical) 722 # 0x09, 0x42, // Usage (Vendor Usage 0x42) 724 # 0x09, 0x44, // Usage (Vendor Usage 0x44) 726 # 0x09, 0x45, // Usage (Vendor Usage 0x45) 728 # 0x09, 0x3c, // Usage (Vendor Usage 0x3c) 730 # 0x09, 0x5a, // Usage (Vendor Usage 0x5a) 732 # 0x09, 0x32, // Usage (Vendor Usage 0x32) 734 # 0x15, 0x00, // Logical Minimum (0) 736 # 0x25, 0x01, // Logical Maximum (1) 738 # 0x75, 0x01, // Report Size (1) 740 # 0x95, 0x06, // Report Count (6) 742 # 0x81, 0x02, // Input (Data,Var,Abs) 744 # 0x95, 0x02, // Report Count (2) 746 # 0x81, 0x03, // Input (Cnst,Var,Abs) 748 # 0x05, 0x01, // Usage Page (Generic Desktop) 750 # 0x09, 0x30, // Usage (X) 752 # 0x27, 0x70, 0x86, 0x00, 0x00, // Logical Maximum (34416) 754 # 0x47, 0x70, 0x86, 0x00, 0x00, // Physical Maximum (34416) 759 # 0x65, 0x11, // Unit (Centimeter,SILinear) 764 # 0x55, 0x0d, // Unit Exponent (-3) 766 # 0x75, 0x10, // Report Size (16) 768 # 0x95, 0x01, // Report Count (1) 770 # 0x81, 0x02, // Input (Data,Var,Abs) 772 # 0x09, 0x31, // Usage (Y) 774 # 0x27, 0x9f, 0x4b, 0x00, 0x00, // Logical Maximum (19359) 776 # 0x47, 0x9f, 0x4b, 0x00, 0x00, // Physical Maximum (19359) 781 # 0x81, 0x02, // Input (Data,Var,Abs) 786 # 0x45, 0x00, // Physical Maximum (0) 788 # 0x65, 0x00, // Unit (None) 790 # 0x55, 0x00, // Unit Exponent (0) 792 # 0x05, 0x0d, // Usage Page (Digitizers) 794 # 0x09, 0x30, // Usage (Tip Pressure) 796 # 0x26, 0xff, 0x0f, // Logical Maximum (4095) 798 # 0x75, 0x10, // Report Size (16) 801 # 0x81, 0x02, // Input (Data,Var,Abs) 803 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 805 # 0x09, 0x5b, // Usage (Vendor Usage 0x5b) 808 # 0x16, 0x00, 0x80, // Logical Minimum (-32768) 810 # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 813 # 0x75, 0x10, // Report Size (16) 816 # 0x81, 0x02, // Input (Data,Var,Abs) 818 # 0x05, 0x0d, // Usage Page (Digitizers) 820 # 0x09, 0x5b, // Usage (Transducer Serial Number) 822 # 0x17, 0x00, 0x00, 0x00, 0x80, // Logical Minimum (-2147483648) 824 # 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 829 # 0x75, 0x20, // Report Size (32) 834 # 0x81, 0x02, // Input (Data,Var,Abs) 836 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 838 # 0x09, 0x00, // Usage (Undefined) 841 # 0x75, 0x08, // Report Size (8) 843 # 0x26, 0xff, 0x00, // Logical Maximum (255) 845 # 0x15, 0x00, // Logical Minimum (0) 848 # 0x81, 0x02, // Input (Data,Var,Abs) 850 # 0x05, 0x0d, // Usage Page (Digitizers) 852 # 0x09, 0x3b, // Usage (Battery Strength) 854 # 0x81, 0x02, // Input (Data,Var,Abs) 856 # 0x65, 0x14, // Unit (Degrees,EngRotation) 858 # 0x55, 0x00, // Unit Exponent (0) 860 # 0x16, 0xa6, 0xff, // Logical Minimum (-90) 862 # 0x26, 0x5a, 0x00, // Logical Maximum (90) 865 # 0x36, 0xa6, 0xff, // Physical Minimum (-90) 868 # 0x46, 0x5a, 0x00, // Physical Maximum (90) 871 # 0x75, 0x08, // Report Size (8) 874 # 0x09, 0x3d, // Usage (X Tilt) 876 # 0x81, 0x02, // Input (Data,Var,Abs) 878 # 0x09, 0x3e, // Usage (Y Tilt) 880 # 0x81, 0x02, // Input (Data,Var,Abs) 882 # 0xc0, // End Collection 884 # 0xb4, // Pop 885 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 886 # 0x75, 0x08, // Report Size (8) 889 # 0x15, 0x00, // Logical Minimum (0) 891 # 0x26, 0xff, 0x00, // Logical Maximum (255) 893 # 0x85, 0x05, // Report ID (5) 896 # 0x09, 0x00, // Usage (Undefined) 898 # 0x95, 0x3a, // Report Count (58) 900 # 0x81, 0x02, // Input (Data,Var,Abs) 902 # 0x85, 0x10, // Report ID (16) 904 # 0x09, 0x00, // Usage (Undefined) 906 # 0x95, 0x14, // Report Count (20) 908 # 0x81, 0x02, // Input (Data,Var,Abs) 910 # 0x85, 0x0f, // Report ID (15) 912 # 0x09, 0x00, // Usage (Undefined) 914 # 0x95, 0x28, // Report Count (40) 916 # 0x81, 0x02, // Input (Data,Var,Abs) 918 # 0x85, 0x0f, // Report ID (15) 920 # 0x09, 0x00, // Usage (Undefined) 922 # 0x95, 0x07, // Report Count (7) 924 # 0xb1, 0x02, // Feature (Data,Var,Abs) 926 # 0x85, 0x11, // Report ID (17) 928 # 0x09, 0x00, // Usage (Undefined) 930 # 0x95, 0x09, // Report Count (9) 932 # 0xb1, 0x02, // Feature (Data,Var,Abs) 934 # 0x85, 0x05, // Report ID (5) 936 # 0x09, 0x00, // Usage (Undefined) 938 # 0x95, 0x08, // Report Count (8) 940 # 0xb1, 0x02, // Feature (Data,Var,Abs) 942 # 0x85, 0x10, // Report ID (16) 944 # 0x09, 0x00, // Usage (Undefined) 946 # 0x96, 0x3f, 0x00, // Report Count (63) 948 # 0xb1, 0x02, // Feature (Data,Var,Abs) 951 # 0x85, 0x0b, // Report ID (11) 953 # 0x09, 0x00, // Usage (Undefined) 955 # 0x96, 0x3f, 0x00, // Report Count (63) 957 # 0xb1, 0x02, // Feature (Data,Var,Abs) 960 # 0xc0, // End Collection 962 # 0x05, 0x01, // Usage Page (Generic Desktop) 963 # 0x09, 0x02, // Usage (Mouse) 965 # 0xa1, 0x01, // Collection (Application) 967 # 0x85, 0x01, // Report ID (1) 969 # 0x09, 0x01, // Usage (Pointer) 971 # 0xa1, 0x00, // Collection (Physical) 973 # 0x05, 0x09, // Usage Page (Button) 975 # 0x19, 0x01, // Usage Minimum (1) 977 # 0x29, 0x02, // Usage Maximum (2) 979 # 0x15, 0x00, // Logical Minimum (0) 981 # 0x25, 0x01, // Logical Maximum (1) 983 # 0x95, 0x02, // Report Count (2) 985 # 0x75, 0x01, // Report Size (1) 987 # 0x81, 0x02, // Input (Data,Var,Abs) 989 # 0x95, 0x01, // Report Count (1) 991 # 0x75, 0x06, // Report Size (6) 993 # 0x81, 0x03, // Input (Cnst,Var,Abs) 995 # 0x05, 0x01, // Usage Page (Generic Desktop) 997 # 0x09, 0x30, // Usage (X) 999 # 0x09, 0x31, // Usage (Y) 1001 # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 1003 # 0x75, 0x10, // Report Size (16) 1006 # 0x95, 0x02, // Report Count (2) 1008 # 0x81, 0x02, // Input (Data,Var,Abs) 1010 # 0xc0, // End Collection 1012 # 0xc0, // End Collection 1013 fwupd-1.2.14/plugins/wacom-raw/fu-plugin-wacom-raw.c000066400000000000000000000045211402665037500222610ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-wacom-aes-device.h" #include "fu-wacom-emr-device.h" #include "fu-wacom-common.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.wacom.raw"); fu_plugin_add_udev_subsystem (plugin, "hidraw"); } gboolean fu_plugin_update_detach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_detach (device, error); } gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_attach (device, error); } gboolean fu_plugin_udev_device_added (FuPlugin *plugin, FuUdevDevice *device, GError **error) { /* interesting device? */ if (g_strcmp0 (fu_udev_device_get_subsystem (device), "hidraw") != 0) return TRUE; /* no actual device to open */ if (g_udev_device_get_device_file (fu_udev_device_get_dev (device)) == NULL) return TRUE; /* EMR */ if (fu_device_has_instance_id (FU_DEVICE (device), "WacomEMR")) { g_autoptr(FuWacomEmrDevice) dev = fu_wacom_emr_device_new (device); g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, FU_DEVICE (dev)); } /* AES */ if (fu_device_has_instance_id (FU_DEVICE (device), "WacomAES")) { g_autoptr(FuWacomAesDevice) dev = fu_wacom_aes_device_new (device); g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, FU_DEVICE (dev)); } return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } fwupd-1.2.14/plugins/wacom-raw/fu-wacom-aes-device.c000066400000000000000000000153101402665037500221770ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-chunk.h" #include "fu-wacom-common.h" #include "fu-wacom-aes-device.h" typedef struct __attribute__((packed)) { guint8 report_id; guint8 cmd; guint8 echo; guint32 addr; guint8 size8; guint8 data[128]; } FuWacomRawVerifyResponse; struct _FuWacomAesDevice { FuWacomDevice parent_instance; }; G_DEFINE_TYPE (FuWacomAesDevice, fu_wacom_aes_device, FU_TYPE_WACOM_DEVICE) static gboolean fu_wacom_aes_add_recovery_hwid (FuDevice *device, GError **error) { FuWacomRawRequest cmd = { .report_id = FU_WACOM_RAW_BL_REPORT_ID_SET, .cmd = FU_WACOM_RAW_BL_CMD_VERIFY_FLASH, .echo = 0x01, .addr = FU_WACOM_RAW_BL_START_ADDR, .size8 = FU_WACOM_RAW_BL_BYTES_CHECK/8, }; FuWacomRawVerifyResponse rsp = { .report_id = FU_WACOM_RAW_BL_REPORT_ID_GET, .size8 = 0x00, .data = { 0x00 } }; g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; guint16 pid; if (!fu_wacom_device_set_feature (FU_WACOM_DEVICE (device), (guint8*) &cmd, sizeof(cmd), error)) { g_prefix_error (error, "failed to send: "); return FALSE; } if (!fu_wacom_device_get_feature (FU_WACOM_DEVICE (device), (guint8*) &rsp, sizeof(rsp), error)) { g_prefix_error (error, "failed to receive: "); return FALSE; } if (rsp.size8 != cmd.size8) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "firmware does not support this feature"); return FALSE; } pid = (rsp.data[7] << 8) + (rsp.data[6]); if( (pid == 0xFFFF) || (pid == 0x0000) ) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "invalid recovery product ID %04x", pid); return FALSE; } devid1 = g_strdup_printf ("HIDRAW\\VEN_2D1F&DEV_%04X", pid); devid2 = g_strdup_printf ("HIDRAW\\VEN_056A&DEV_%04X", pid); fu_device_add_instance_id (device, devid1); fu_device_add_instance_id (device, devid2); return TRUE; } static gboolean fu_wacom_aes_query_operation_mode (FuWacomAesDevice *self, GError **error) { guint8 buf[FU_WACOM_RAW_FW_REPORT_SZ] = { FU_WACOM_RAW_FW_REPORT_ID, FU_WACOM_RAW_FW_CMD_QUERY_MODE, }; /* 0x00=runtime, 0x02=bootloader */ if (!fu_wacom_device_get_feature (FU_WACOM_DEVICE (self), buf, sizeof(buf), error)) return FALSE; if (buf[1] == 0x00) { fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } if (buf[1] == 0x02) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } /* unsupported */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to query operation mode, got 0x%x", buf[1]); return FALSE; } static gboolean fu_wacom_aes_device_setup (FuDevice *device, GError **error) { FuWacomAesDevice *self = FU_WACOM_AES_DEVICE (device); g_autoptr(GError) error_local = NULL; /* find out if in bootloader mode already */ if (!fu_wacom_aes_query_operation_mode (self, error)) return FALSE; /* get firmware version */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { fu_device_set_version (device, "0.0", FWUPD_VERSION_FORMAT_PAIR); /* get the recovery PID if supported */ if (!fu_wacom_aes_add_recovery_hwid (device, &error_local)) g_debug ("failed to get HwID: %s", error_local->message); } else { guint32 fw_ver; guint8 data[FU_WACOM_RAW_STATUS_REPORT_SZ] = { FU_WACOM_RAW_STATUS_REPORT_ID, 0x0 }; g_autofree gchar *version = NULL; if (!fu_wacom_device_get_feature (FU_WACOM_DEVICE (self), data, sizeof(data), error)) return FALSE; fw_ver = fu_common_read_uint16 (data + 11, G_LITTLE_ENDIAN); version = g_strdup_printf ("%04x.%02x", fw_ver, data[13]); fu_device_set_version (device, version, FWUPD_VERSION_FORMAT_PAIR); } /* success */ return TRUE; } static gboolean fu_wacom_aes_device_erase_all (FuWacomAesDevice *self, GError **error) { FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_ALL_ERASE, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00 }; FuWacomRawResponse rsp = { 0x00 }; if (!fu_wacom_device_cmd (FU_WACOM_DEVICE (self), &req, &rsp, 2000 * 1000, /* this takes a long time */ FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING, error)) { g_prefix_error (error, "failed to send eraseall command: "); return FALSE; } g_usleep (2 * G_USEC_PER_SEC); return TRUE; } static gboolean fu_wacom_aes_device_write_block (FuWacomAesDevice *self, guint32 idx, guint32 address, const guint8 *data, guint16 datasz, GError **error) { guint blocksz = fu_wacom_device_get_block_sz (FU_WACOM_DEVICE (self)); FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_WRITE_FLASH, .echo = (guint8) idx + 1, .addr = GUINT32_TO_LE(address), .size8 = datasz / 8, .data = { 0x00 }, }; FuWacomRawResponse rsp = { 0x00 }; /* check size */ if (datasz != blocksz) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "block size 0x%x != 0x%x untested", datasz, (guint) blocksz); return FALSE; } memcpy (&req.data, data, datasz); /* write */ if (!fu_wacom_device_cmd (FU_WACOM_DEVICE (self), &req, &rsp, 1000, FU_WACOM_DEVICE_CMD_FLAG_NONE, error)) { g_prefix_error (error, "failed to write block %u: ", idx); return FALSE; } return TRUE; } static gboolean fu_wacom_aes_device_write_firmware (FuDevice *device, GPtrArray *chunks, GError **error) { FuWacomAesDevice *self = FU_WACOM_AES_DEVICE (device); /* erase */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_wacom_aes_device_erase_all (self, error)) return FALSE; /* write */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); if (!fu_wacom_aes_device_write_block (self, chk->idx, chk->address, chk->data, chk->data_sz, error)) return FALSE; fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len); } return TRUE; } static void fu_wacom_aes_device_init (FuWacomAesDevice *self) { fu_device_set_name (FU_DEVICE (self), "Embedded Wacom AES Device"); } static void fu_wacom_aes_device_class_init (FuWacomAesDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuWacomDeviceClass *klass_wac_device = FU_WACOM_DEVICE_CLASS (klass); klass_device->setup = fu_wacom_aes_device_setup; klass_wac_device->write_firmware = fu_wacom_aes_device_write_firmware; } FuWacomAesDevice * fu_wacom_aes_device_new (FuUdevDevice *device) { FuWacomAesDevice *self = g_object_new (FU_TYPE_WACOM_AES_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } fwupd-1.2.14/plugins/wacom-raw/fu-wacom-aes-device.h000066400000000000000000000006411402665037500222050ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wacom-device.h" G_BEGIN_DECLS #define FU_TYPE_WACOM_AES_DEVICE (fu_wacom_aes_device_get_type ()) G_DECLARE_FINAL_TYPE (FuWacomAesDevice, fu_wacom_aes_device, FU, WACOM_AES_DEVICE, FuWacomDevice) FuWacomAesDevice *fu_wacom_aes_device_new (FuUdevDevice *device); G_END_DECLS fwupd-1.2.14/plugins/wacom-raw/fu-wacom-common.c000066400000000000000000000046501402665037500214670ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wacom-common.h" gboolean fu_wacom_common_check_reply (const FuWacomRawRequest *req, const FuWacomRawResponse *rsp, GError **error) { if (rsp->report_id != FU_WACOM_RAW_BL_REPORT_ID_GET) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "report ID failed, expected 0x%02x, got 0x%02x", (guint) FU_WACOM_RAW_BL_REPORT_ID_GET, req->report_id); return FALSE; } if (req->cmd != rsp->cmd) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cmd failed, expected 0x%02x, got 0x%02x", req->cmd, rsp->cmd); return FALSE; } if (req->echo != rsp->echo) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "echo failed, expected 0x%02x, got 0x%02x", req->echo, rsp->echo); return FALSE; } return TRUE; } gboolean fu_wacom_common_rc_set_error (const FuWacomRawResponse *rsp, GError **error) { if (rsp->resp == FU_WACOM_RAW_RC_OK) return TRUE; if (rsp->resp == FU_WACOM_RAW_RC_BUSY) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_BUSY, "device is busy"); return FALSE; } if (rsp->resp == FU_WACOM_RAW_RC_MCUTYPE) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "MCU type does not match"); return FALSE; } if (rsp->resp == FU_WACOM_RAW_RC_PID) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "PID does not match"); return FALSE; } if (rsp->resp == FU_WACOM_RAW_RC_CHECKSUM1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum1 does not match"); return FALSE; } if (rsp->resp == FU_WACOM_RAW_RC_CHECKSUM2) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum2 does not match"); return FALSE; } if (rsp->resp == FU_WACOM_RAW_RC_TIMEOUT) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "command timed out"); return FALSE; } g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "unknown error 0x%02x", rsp->resp); return FALSE; } gboolean fu_wacom_common_block_is_empty (const guint8 *data, guint16 datasz) { for (guint16 i = 0; i < datasz; i++) { if (data[i] != 0xff) return FALSE; } return TRUE; } fwupd-1.2.14/plugins/wacom-raw/fu-wacom-common.h000066400000000000000000000040461402665037500214730ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FU_WACOM_RAW_CMD_RETRIES 1000 #define FU_WACOM_RAW_STATUS_REPORT_ID 0x04 #define FU_WACOM_RAW_STATUS_REPORT_SZ 16 #define FU_WACOM_RAW_FW_REPORT_ID 0x02 #define FU_WACOM_RAW_FW_CMD_QUERY_MODE 0x00 #define FU_WACOM_RAW_FW_CMD_DETACH 0x02 #define FU_WACOM_RAW_FW_REPORT_SZ 2 #define FU_WACOM_RAW_BL_START_ADDR (0x11FF8) #define FU_WACOM_RAW_BL_BYTES_CHECK 8 #define FU_WACOM_RAW_BL_REPORT_ID_SET 0x07 #define FU_WACOM_RAW_BL_REPORT_ID_GET 0x08 #define FU_WACOM_RAW_BL_CMD_ERASE_FLASH 0x00 #define FU_WACOM_RAW_BL_CMD_WRITE_FLASH 0x01 #define FU_WACOM_RAW_BL_CMD_VERIFY_FLASH 0x02 #define FU_WACOM_RAW_BL_CMD_ATTACH 0x03 #define FU_WACOM_RAW_BL_CMD_GET_BLVER 0x04 #define FU_WACOM_RAW_BL_CMD_GET_MPUTYPE 0x05 #define FU_WACOM_RAW_BL_CMD_CHECK_MODE 0x07 #define FU_WACOM_RAW_BL_CMD_ERASE_DATAMEM 0x0e #define FU_WACOM_RAW_BL_CMD_ALL_ERASE 0x90 #define FU_WACOM_RAW_RC_OK 0x00 #define FU_WACOM_RAW_RC_BUSY 0x80 #define FU_WACOM_RAW_RC_MCUTYPE 0x0c #define FU_WACOM_RAW_RC_PID 0x0d #define FU_WACOM_RAW_RC_CHECKSUM1 0x81 #define FU_WACOM_RAW_RC_CHECKSUM2 0x82 #define FU_WACOM_RAW_RC_TIMEOUT 0x87 #define FU_WACOM_RAW_RC_IN_PROGRESS 0xff #define FU_WACOM_RAW_ECHO_DEFAULT g_random_int_range(0xa0,0xfe) typedef struct __attribute__((packed)) { guint8 report_id; guint8 cmd; guint8 echo; guint32 addr; guint8 size8; guint8 data[128]; guint8 data_unused[121]; } FuWacomRawRequest; typedef struct __attribute__((packed)) { guint8 report_id; guint8 cmd; guint8 echo; guint8 resp; guint8 data_unused[132]; } FuWacomRawResponse; gboolean fu_wacom_common_rc_set_error (const FuWacomRawResponse *rsp, GError **error); gboolean fu_wacom_common_check_reply (const FuWacomRawRequest *req, const FuWacomRawResponse *rsp, GError **error); gboolean fu_wacom_common_block_is_empty (const guint8 *data, guint16 datasz); G_END_DECLS fwupd-1.2.14/plugins/wacom-raw/fu-wacom-device.c000066400000000000000000000267711402665037500214460ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "fu-chunk.h" #include "fu-wacom-common.h" #include "fu-wacom-device.h" #include "dfu-firmware.h" typedef struct { gint fd; guint flash_block_size; guint32 flash_base_addr; guint32 flash_size; } FuWacomDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuWacomDevice, fu_wacom_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_wacom_device_get_instance_private (o)) static void fu_wacom_device_to_string (FuDevice *device, GString *str) { FuWacomDevice *self = FU_WACOM_DEVICE (device); FuWacomDevicePrivate *priv = GET_PRIVATE (self); g_string_append (str, " FuWacomDevice:\n"); g_string_append_printf (str, " fd:\t\t\t%i\n", priv->fd); g_string_append_printf (str, " flash-block-size:\t0x%04x\n", priv->flash_block_size); g_string_append_printf (str, " flash-base-addr:\t0x%04x\n", priv->flash_base_addr); g_string_append_printf (str, " flash-size:\t\t0x%04x\n", priv->flash_size); } guint fu_wacom_device_get_block_sz (FuWacomDevice *self) { FuWacomDevicePrivate *priv = GET_PRIVATE (self); return priv->flash_block_size; } guint fu_wacom_device_get_base_addr (FuWacomDevice *self) { FuWacomDevicePrivate *priv = GET_PRIVATE (self); return priv->flash_base_addr; } gboolean fu_wacom_device_check_mpu (FuWacomDevice *self, GError **error) { FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_GET_MPUTYPE, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00 }; FuWacomRawResponse rsp = { 0x00 }; if (!fu_wacom_device_cmd (FU_WACOM_DEVICE (self), &req, &rsp, 0, FU_WACOM_DEVICE_CMD_FLAG_NO_ERROR_CHECK, error)) { g_prefix_error (error, "failed to get MPU type: "); return FALSE; } /* W9013 */ if (rsp.resp == 0x2e) { fu_device_add_instance_id (FU_DEVICE (self), "WacomEMR_W9013"); return TRUE; } /* W9021 */ if (rsp.resp == 0x45) { fu_device_add_instance_id (FU_DEVICE (self), "WacomEMR_W9021"); return TRUE; } /* unsupported */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "MPU is not W9013 or W9021: 0x%x", rsp.resp); return FALSE; } static gboolean fu_wacom_device_open (FuDevice *device, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE (device); FuWacomDevicePrivate *priv = GET_PRIVATE (self); GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device)); /* open device */ priv->fd = g_open (g_udev_device_get_device_file (udev_device), O_RDWR); if (priv->fd < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to open %s", g_udev_device_get_device_file (udev_device)); return FALSE; } /* success */ return TRUE; } static gboolean fu_wacom_device_close (FuDevice *device, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE (device); FuWacomDevicePrivate *priv = GET_PRIVATE (self); if (!g_close (priv->fd, error)) return FALSE; priv->fd = 0; return TRUE; } static gboolean fu_wacom_device_probe (FuUdevDevice *device, GError **error) { /* set the physical ID */ if (!fu_udev_device_set_physical_id (device, "hid", error)) return FALSE; return TRUE; } static gboolean fu_wacom_device_detach (FuDevice *device, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE (device); guint8 buf[FU_WACOM_RAW_FW_REPORT_SZ] = { FU_WACOM_RAW_FW_REPORT_ID, FU_WACOM_RAW_FW_CMD_DETACH, }; if (!fu_wacom_device_set_feature (self, buf, sizeof(buf), error)) { g_prefix_error (error, "failed to switch to bootloader mode: "); return FALSE; } g_usleep (300 * 1000); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_wacom_device_attach (FuDevice *device, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE (device); FuWacomRawRequest req = { .report_id = FU_WACOM_RAW_BL_REPORT_ID_SET, .cmd = FU_WACOM_RAW_BL_CMD_ATTACH, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00 }; if (!fu_wacom_device_set_feature (self, (const guint8 *) &req, sizeof(req), error)) { g_prefix_error (error, "failed to switch to runtime mode: "); return FALSE; } /* only required on AES, but harmless for EMR */ g_usleep (300 * 1000); fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_wacom_device_check_mode (FuWacomDevice *self, GError **error) { FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_CHECK_MODE, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00 }; FuWacomRawResponse rsp = { 0x00 }; if (!fu_wacom_device_cmd (self, &req, &rsp, 0, FU_WACOM_DEVICE_CMD_FLAG_NO_ERROR_CHECK, error)) { g_prefix_error (error, "failed to check mode: "); return FALSE; } if (rsp.resp != 0x06) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "check mode failed, mode=0x%02x", rsp.resp); return FALSE; } return TRUE; } static gboolean fu_wacom_device_set_version_bootloader (FuWacomDevice *self, GError **error) { FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_GET_BLVER, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00 }; FuWacomRawResponse rsp = { 0x00 }; g_autofree gchar *version = NULL; if (!fu_wacom_device_cmd (self, &req, &rsp, 0, FU_WACOM_DEVICE_CMD_FLAG_NO_ERROR_CHECK, error)) { g_prefix_error (error, "failed to get bootloader version: "); return FALSE; } version = g_strdup_printf ("%u", rsp.resp); fu_device_set_version_bootloader (FU_DEVICE (self), version); return TRUE; } static gboolean fu_wacom_device_write_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE (device); FuWacomDevicePrivate *priv = GET_PRIVATE (self); FuWacomDeviceClass *klass = FU_WACOM_DEVICE_GET_CLASS (device); DfuElement *element; DfuImage *image; GBytes *fw_new; g_autoptr(DfuFirmware) firmware = dfu_firmware_new (); g_autoptr(GPtrArray) chunks = NULL; /* parse hex file */ if (!dfu_firmware_parse_data (firmware, fw, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) return FALSE; if (dfu_firmware_get_format (firmware) != DFU_FIRMWARE_FORMAT_INTEL_HEX) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "expected firmware format is 'ihex', got '%s'", dfu_firmware_format_to_string (dfu_firmware_get_format (firmware))); return FALSE; } /* use the correct image from the firmware */ image = dfu_firmware_get_image_default (firmware); if (image == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no firmware image"); return FALSE; } element = dfu_image_get_element_default (image); if (element == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no element in image"); return FALSE; } g_debug ("using element at addr 0x%0x", (guint) dfu_element_get_address (element)); /* check start address and size */ if (dfu_element_get_address (element) != priv->flash_base_addr) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "base addr invalid: 0x%05x", (guint) dfu_element_get_address (element)); return FALSE; } fw_new = dfu_element_get_contents (element); if (g_bytes_get_size (fw_new) > priv->flash_size) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "size is invalid: 0x%05x", (guint) g_bytes_get_size (fw_new)); return FALSE; } /* we're in bootloader mode now */ if (!fu_wacom_device_check_mode (self, error)) return FALSE; if (!fu_wacom_device_set_version_bootloader (self, error)) return FALSE; /* flash chunks */ chunks = fu_chunk_array_new_from_bytes (fw_new, priv->flash_base_addr, 0x00, /* page_sz */ priv->flash_block_size); return klass->write_firmware (device, chunks, error); } gboolean fu_wacom_device_set_feature (FuWacomDevice *self, const guint8 *data, guint datasz, GError **error) { FuWacomDevicePrivate *priv = GET_PRIVATE (self); /* Set Feature */ fu_common_dump_raw (G_LOG_DOMAIN, "SetFeature", data, datasz); if (ioctl (priv->fd, HIDIOCSFEATURE(datasz), data) < 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to SetFeature"); return FALSE; } return TRUE; } gboolean fu_wacom_device_get_feature (FuWacomDevice *self, guint8 *data, guint datasz, GError **error) { FuWacomDevicePrivate *priv = GET_PRIVATE (self); if (ioctl (priv->fd, HIDIOCGFEATURE(datasz), data) < 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to GetFeature"); return FALSE; } fu_common_dump_raw (G_LOG_DOMAIN, "GetFeature", data, datasz); return TRUE; } gboolean fu_wacom_device_cmd (FuWacomDevice *self, FuWacomRawRequest *req, FuWacomRawResponse *rsp, gulong delay_us, FuWacomDeviceCmdFlags flags, GError **error) { req->report_id = FU_WACOM_RAW_BL_REPORT_ID_SET; if (!fu_wacom_device_set_feature (self, (const guint8 *)req, sizeof(*req), error)) { g_prefix_error (error, "failed to send: "); return FALSE; } if (delay_us > 0) g_usleep (delay_us); rsp->report_id = FU_WACOM_RAW_BL_REPORT_ID_GET; if (!fu_wacom_device_get_feature (self, (guint8 *)rsp, sizeof(*rsp), error)) { g_prefix_error (error, "failed to receive: "); return FALSE; } if (flags & FU_WACOM_DEVICE_CMD_FLAG_NO_ERROR_CHECK) return TRUE; if (!fu_wacom_common_check_reply (req, rsp, error)) return FALSE; /* wait for the command to complete */ if (flags & FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING && rsp->resp != FU_WACOM_RAW_RC_OK) { for (guint i = 0; i < FU_WACOM_RAW_CMD_RETRIES; i++) { if (delay_us > 0) g_usleep (delay_us); if (!fu_wacom_device_get_feature (self, (guint8 *)rsp, sizeof(*rsp), error)) return FALSE; if (!fu_wacom_common_check_reply (req, rsp, error)) return FALSE; if (rsp->resp != FU_WACOM_RAW_RC_IN_PROGRESS && rsp->resp != FU_WACOM_RAW_RC_BUSY) break; } } return fu_wacom_common_rc_set_error (rsp, error); } static gboolean fu_wacom_device_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE (device); FuWacomDevicePrivate *priv = GET_PRIVATE (self); if (g_strcmp0 (key, "WacomI2cFlashBlockSize") == 0) { priv->flash_block_size = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "WacomI2cFlashBaseAddr") == 0) { priv->flash_base_addr = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "WacomI2cFlashSize") == 0) { priv->flash_size = fu_common_strtoull (value); return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_wacom_device_init (FuWacomDevice *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); } static void fu_wacom_device_class_init (FuWacomDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUdevDeviceClass *klass_device_udev = FU_UDEV_DEVICE_CLASS (klass); klass_device->to_string = fu_wacom_device_to_string; klass_device->open = fu_wacom_device_open; klass_device->close = fu_wacom_device_close; klass_device->write_firmware = fu_wacom_device_write_firmware; klass_device->attach = fu_wacom_device_attach; klass_device->detach = fu_wacom_device_detach; klass_device->set_quirk_kv = fu_wacom_device_set_quirk_kv; klass_device_udev->probe = fu_wacom_device_probe; } fwupd-1.2.14/plugins/wacom-raw/fu-wacom-device.h000066400000000000000000000027241402665037500214430ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wacom-common.h" #include "fu-udev-device.h" #include "dfu-element.h" G_BEGIN_DECLS #define FU_TYPE_WACOM_DEVICE (fu_wacom_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuWacomDevice, fu_wacom_device, FU, WACOM_DEVICE, FuUdevDevice) struct _FuWacomDeviceClass { FuUdevDeviceClass parent_class; gboolean (*write_firmware) (FuDevice *self, GPtrArray *chunks, GError **error); }; typedef enum { FU_WACOM_DEVICE_CMD_FLAG_NONE = 0, FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING = 1 << 0, FU_WACOM_DEVICE_CMD_FLAG_NO_ERROR_CHECK = 1 << 1, } FuWacomDeviceCmdFlags; gboolean fu_wacom_device_set_feature (FuWacomDevice *self, const guint8 *data, guint datasz, GError **error); gboolean fu_wacom_device_get_feature (FuWacomDevice *self, guint8 *data, guint datasz, GError **error); gboolean fu_wacom_device_cmd (FuWacomDevice *self, FuWacomRawRequest *req, FuWacomRawResponse *rsp, gulong delay_us, FuWacomDeviceCmdFlags flags, GError **error); gboolean fu_wacom_device_erase_all (FuWacomDevice *self, GError **error); gboolean fu_wacom_device_check_mpu (FuWacomDevice *self, GError **error); guint fu_wacom_device_get_block_sz (FuWacomDevice *self); guint fu_wacom_device_get_base_addr (FuWacomDevice *self); G_END_DECLS fwupd-1.2.14/plugins/wacom-raw/fu-wacom-emr-device.c000066400000000000000000000152631402665037500222210ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-chunk.h" #include "fu-wacom-common.h" #include "fu-wacom-emr-device.h" struct _FuWacomEmrDevice { FuWacomDevice parent_instance; }; G_DEFINE_TYPE (FuWacomEmrDevice, fu_wacom_emr_device, FU_TYPE_WACOM_DEVICE) static gboolean fu_wacom_emr_device_setup (FuDevice *device, GError **error) { FuWacomEmrDevice *self = FU_WACOM_EMR_DEVICE (device); /* check MPU type */ if (!fu_wacom_device_check_mpu (FU_WACOM_DEVICE (self), error)) return FALSE; /* get firmware version */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { fu_device_set_version (device, "0.0", FWUPD_VERSION_FORMAT_PAIR); } else { guint16 fw_ver; guint8 data[19] = { 0x03, 0x0 }; /* 0x03 is an unknown ReportID */ g_autofree gchar *version = NULL; if (!fu_wacom_device_get_feature (FU_WACOM_DEVICE (self), data, sizeof(data), error)) return FALSE; fw_ver = fu_common_read_uint16 (data + 11, G_LITTLE_ENDIAN); fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); version = fu_common_version_from_uint16 (fw_ver, FWUPD_VERSION_FORMAT_PAIR); fu_device_set_version (device, version, FWUPD_VERSION_FORMAT_PAIR); } /* success */ return TRUE; } static guint8 fu_wacom_emr_device_calc_checksum (guint8 init1, const guint8 *buf, guint8 bufsz) { guint8 sum = 0; sum += init1; for (guint i = 0; i < bufsz; i++) sum += buf[i]; return ~sum + 1; } static gboolean fu_wacom_emr_device_w9013_erase_data (FuWacomEmrDevice *self, GError **error) { FuWacomRawResponse rsp = { 0x00 }; FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_ERASE_DATAMEM, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00 }; guint8 *buf = (guint8 *) &req.addr; buf[0] = 0x00; /* erased block */ buf[1] = fu_wacom_emr_device_calc_checksum (0x05 + 0x00 + 0x07 + 0x00, (const guint8 *) &req, 4); if (!fu_wacom_device_cmd (FU_WACOM_DEVICE (self), &req, &rsp, 50, FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING, error)) { g_prefix_error (error, "failed to erase datamem: "); return FALSE; } g_usleep (50); return TRUE; } static gboolean fu_wacom_emr_device_w9013_erase_code (FuWacomEmrDevice *self, guint8 idx, guint8 block_nr, GError **error) { FuWacomRawResponse rsp = { 0x00 }; FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_ERASE_FLASH, .echo = idx, 0x00 }; guint8 *buf = (guint8 *) &req.addr; buf[0] = block_nr; buf[1] = fu_wacom_emr_device_calc_checksum (0x05 + 0x00 + 0x07 + 0x00, (const guint8 *) &req, 4); if (!fu_wacom_device_cmd (FU_WACOM_DEVICE (self), &req, &rsp, 50, FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING, error)) { g_prefix_error (error, "failed to erase codemem: "); return FALSE; } g_usleep (50); return TRUE; } static gboolean fu_wacom_device_w9021_erase_all (FuWacomEmrDevice *self, GError **error) { FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_ALL_ERASE, .echo = 0x01, .addr = 0x00, }; FuWacomRawResponse rsp = { 0x00 }; if (!fu_wacom_device_cmd (FU_WACOM_DEVICE (self), &req, &rsp, 2000 * 1000, /* this takes a long time */ FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING, error)) { g_prefix_error (error, "failed to send eraseall command: "); return FALSE; } if (!fu_wacom_common_rc_set_error (&rsp, error)) { g_prefix_error (error, "failed to erase"); return FALSE; } g_usleep (50); return TRUE; } static gboolean fu_wacom_emr_device_write_block (FuWacomEmrDevice *self, guint32 idx, guint32 address, const guint8 *data, guint16 datasz, GError **error) { guint blocksz = fu_wacom_device_get_block_sz (FU_WACOM_DEVICE (self)); FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_WRITE_FLASH, .echo = (guint8) idx + 1, .addr = GUINT32_TO_LE(address), .size8 = datasz / 8, .data = { 0x00 }, }; FuWacomRawResponse rsp = { 0x00 }; /* check size */ if (datasz > sizeof(req.data)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "data size 0x%x too large for packet", datasz); return FALSE; } if (datasz != blocksz) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "block size 0x%x != 0x%x untested", datasz, (guint) blocksz); return FALSE; } /* data */ memcpy (&req.data, data, datasz); /* cmd and data checksums */ req.data[blocksz + 0] = fu_wacom_emr_device_calc_checksum (0x05 + 0x00 + 0x4c + 0x00, (const guint8 *) &req, 8); req.data[blocksz + 1] = fu_wacom_emr_device_calc_checksum (0x00, data, datasz); if (!fu_wacom_device_cmd (FU_WACOM_DEVICE (self), &req, &rsp, 50, FU_WACOM_DEVICE_CMD_FLAG_NONE, error)) { g_prefix_error (error, "failed to write at 0x%x: ", address); return FALSE; } return TRUE; } static gboolean fu_wacom_emr_device_write_firmware (FuDevice *device, GPtrArray *chunks, GError **error) { FuWacomEmrDevice *self = FU_WACOM_EMR_DEVICE (device); guint8 idx = 0; /* erase W9013 */ if (fu_device_has_instance_id (device, "WacomEMR_W9013")) { fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_wacom_emr_device_w9013_erase_data (self, error)) return FALSE; for (guint i = 127; i >= 8; i--) { if (!fu_wacom_emr_device_w9013_erase_code (self, idx++, i, error)) return FALSE; } } /* erase W9021 */ if (fu_device_has_instance_id (device, "WacomEMR_W9021")) { if (!fu_wacom_device_w9021_erase_all (self, error)) return FALSE; } /* write */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); if (fu_wacom_common_block_is_empty (chk->data, chk->data_sz)) continue; if (!fu_wacom_emr_device_write_block (self, chk->idx, chk->address, chk->data, chk->data_sz, error)) return FALSE; fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len); } fu_device_set_progress (device, 100); return TRUE; } static void fu_wacom_emr_device_init (FuWacomEmrDevice *self) { fu_device_set_name (FU_DEVICE (self), "Embedded Wacom EMR Device"); } static void fu_wacom_emr_device_class_init (FuWacomEmrDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuWacomDeviceClass *klass_wac_device = FU_WACOM_DEVICE_CLASS (klass); klass_device->setup = fu_wacom_emr_device_setup; klass_wac_device->write_firmware = fu_wacom_emr_device_write_firmware; } FuWacomEmrDevice * fu_wacom_emr_device_new (FuUdevDevice *device) { FuWacomEmrDevice *self = g_object_new (FU_TYPE_WACOM_EMR_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } fwupd-1.2.14/plugins/wacom-raw/fu-wacom-emr-device.h000066400000000000000000000006401402665037500222170ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wacom-device.h" G_BEGIN_DECLS #define FU_TYPE_WACOM_EMR_DEVICE (fu_wacom_emr_device_get_type ()) G_DECLARE_FINAL_TYPE (FuWacomEmrDevice, fu_wacom_emr_device, FU, WACOM_EMR_DEVICE, FuUdevDevice) FuWacomEmrDevice *fu_wacom_emr_device_new (FuUdevDevice *device); G_END_DECLS fwupd-1.2.14/plugins/wacom-raw/meson.build000066400000000000000000000012251402665037500204540ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginWacomRaw"'] install_data(['wacom-raw.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_wacom_raw', fu_hash, sources : [ 'fu-plugin-wacom-raw.c', 'fu-wacom-common.c', 'fu-wacom-device.c', 'fu-wacom-aes-device.c', 'fu-wacom-emr-device.c', ], include_directories : [ include_directories('../..'), include_directories('../dfu'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ dfu, ], ) fwupd-1.2.14/plugins/wacom-raw/wacom-raw.quirk000066400000000000000000000043061402665037500212670ustar00rootroot00000000000000# Devices that do "replug" and thus don't change VID:PID to the bootloader # need to have an extra GUID of WacomAES or WacomEMR added so that the flash # constants are set correctly. # Dell Chromebook Enterprise 5300 [DeviceInstanceId=HIDRAW\VEN_2D1F&DEV_4946] Plugin = wacom_raw Guid = WacomAES # Moffet 14-LGD-TPK [DeviceInstanceId=HIDRAW\VEN_2D1F&DEV_4970] Plugin = wacom_raw Guid = WacomAES Flags = self-recovery # Moffet 14-Sharp-HH [DeviceInstanceId=HIDRAW\VEN_2D1F&DEV_4971] Plugin = wacom_raw Guid = WacomAES Flags = self-recovery # Moffet 14-Sharp-VIA [DeviceInstanceId=HIDRAW\VEN_2D1F&DEV_4972] Plugin = wacom_raw Guid = WacomAES Flags = self-recovery # Dell Latitude 5175 [DeviceInstanceId=HIDRAW\VEN_056A&DEV_4807] Plugin = wacom_raw Guid = WacomAES # Dell XPS 12 9250 [DeviceInstanceId=HIDRAW\VEN_056A&DEV_4822] Plugin = wacom_raw Guid = WacomAES # Dell Venue 8 Pro 5855 [DeviceInstanceId=HIDRAW\VEN_056A&DEV_4824] Plugin = wacom_raw Guid = WacomAES # Dell XPS 13 9365 [DeviceInstanceId=HIDRAW\VEN_056A&DEV_4831] Plugin = wacom_raw Guid = WacomAES # Dell Latitude 5285 [DeviceInstanceId=HIDRAW\VEN_056A&DEV_484C] Plugin = wacom_raw Guid = WacomAES # Dell Latitude 7390 2-in-1 [DeviceInstanceId=HIDRAW\VEN_056A&DEV_4841] Plugin = wacom_raw Guid = WacomAES # Dell XPS-15 9575 [DeviceInstanceId=HIDRAW\VEN_056A&DEV_4875] Plugin = wacom_raw Guid = WacomAES # Dell Latitude 7400 2-in-1 [DeviceInstanceId=HIDRAW\VEN_056A&DEV_48C9] Plugin = wacom_raw Guid = WacomAES # Dell XPS-15 9570 [DeviceInstanceId=HIDRAW\VEN_056A&DEV_488F] Plugin = wacom_raw Guid = WacomAES # Dell XPS 13 7390 2-in-1 [DeviceInstanceId=HIDRAW\VEN_056A&DEV_48ED] Plugin = wacom_raw Guid = WacomAES # AES bootloader mode [DeviceInstanceId=HIDRAW\VEN_056A&DEV_0094] Plugin = wacom_raw Guid = WacomAES Flags = is-bootloader # EMR bootloader mode [DeviceInstanceId=HIDRAW\VEN_056A&DEV_012B] Plugin = wacom_raw Guid = WacomEMR Flags = is-bootloader [Guid=WacomEMR_W9013] WacomI2cFlashBlockSize=64 WacomI2cFlashBaseAddr=0x2000 WacomI2cFlashSize=0x1e000 [Guid=WacomEMR_W9021] WacomI2cFlashBlockSize=256 WacomI2cFlashBaseAddr=0x3000 WacomI2cFlashSize=0x3c000 [Guid=WacomAES] WacomI2cFlashBlockSize=128 WacomI2cFlashBaseAddr=0x8000 WacomI2cFlashSize=0x24000 fwupd-1.2.14/plugins/wacom-usb/000077500000000000000000000000001402665037500163125ustar00rootroot00000000000000fwupd-1.2.14/plugins/wacom-usb/README.md000066400000000000000000000024431402665037500175740ustar00rootroot00000000000000Wacom USB Support ================= Introduction ------------ Wacom provides interactive pen displays, pen tablets, and styluses to equip and inspire everyone make the world a more creative place. From 2016 Wacom has been using a HID-based proprietary flashing algorithm which has been documented by support team at Wacom and provided under NDA under the understanding it would be used to build a plugin under a LGPLv2+ licence. Wacom devices are actually composite devices, with the main ARM CPU being programmed using a more complicated erase, write, verify algorithm based on a historical update protocol. The "sub-module" devices use a newer protocol, again based on HID, but are handled differently depending on their type. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in the following formats: * Touch module: Intel HEX file format * Bluetooth module: Unknown airoflash file format * EMR module: Plain SREC file format * Main module: SREC file format, with a custom `WACOM` vendor header This plugin supports the following protocol ID: * com.wacom.usb GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_056A&PID_0378&REV_0001` * `USB\VID_056A&PID_0378` * `USB\VID_056A` fwupd-1.2.14/plugins/wacom-usb/data/000077500000000000000000000000001402665037500172235ustar00rootroot00000000000000fwupd-1.2.14/plugins/wacom-usb/data/lsusb.txt000066400000000000000000000037021402665037500211160ustar00rootroot00000000000000Bus 001 Device 023: ID 056a:0378 Wacom Co., Ltd CTL-6100WL [Intuos BT (M)] Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x056a Wacom Co., Ltd idProduct 0x0378 CTL-6100WL [Intuos BT (M)] bcdDevice 1.66 iManufacturer 1 Wacom Co.,Ltd. iProduct 2 Intuos BT M iSerial 3 8BH00U2012294 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0022 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 759 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.2.14/plugins/wacom-usb/fu-plugin-wacom-usb.c000066400000000000000000000036641402665037500222700ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-wac-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.wacom.usb"); } gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error) { g_autoptr(FuWacDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; dev = fu_wac_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent (device); g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (parent != NULL ? parent : device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware (device, blob_fw, flags, error); } static FuDevice * fu_plugin_wacom_usb_get_device (GPtrArray *devices) { for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index (devices, i); if (FU_IS_WAC_DEVICE (dev)) return dev; } return NULL; } gboolean fu_plugin_composite_cleanup (FuPlugin *plugin, GPtrArray *devices, GError **error) { FuDevice *device = fu_plugin_wacom_usb_get_device (devices); g_autoptr(FuDeviceLocker) locker = NULL; /* not us */ if (device == NULL) return TRUE; /* reboot, which switches the boot index of the firmware */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); return fu_wac_device_update_reset (FU_WAC_DEVICE (device), error); } fwupd-1.2.14/plugins/wacom-usb/fu-self-test.c000066400000000000000000000034761402665037500210060ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-common.h" #include "fu-test.h" #include "fu-wac-common.h" #include "fu-wac-firmware.h" #include "fwupd-error.h" static void fu_wac_firmware_parse_func (void) { DfuElement *element; DfuImage *image; gboolean ret; g_autofree gchar *fn = NULL; g_autoptr(DfuFirmware) firmware = dfu_firmware_new (); g_autoptr(GBytes) blob_block = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GError) error = NULL; /* parse the test file */ fn = fu_test_get_filename (TESTDATADIR, "test.wac"); if (fn == NULL) { g_test_skip ("no data file found"); return; } bytes = fu_common_get_contents_bytes (fn, &error); g_assert_no_error (error); g_assert_nonnull (bytes); ret = fu_wac_firmware_parse_data (firmware, bytes, DFU_FIRMWARE_PARSE_FLAG_NONE, &error); g_assert_no_error (error); g_assert_true (ret); /* get image data */ image = dfu_firmware_get_image (firmware, 0); g_assert_nonnull (image); element = dfu_image_get_element_default (image); g_assert_nonnull (element); /* get block */ blob_block = dfu_element_get_contents_chunk (element, 0x8008000, 1024, &error); g_assert_no_error (error); g_assert_nonnull (blob_block); fu_wac_buffer_dump ("IMG", FU_WAC_REPORT_ID_MODULE, g_bytes_get_data (blob_block, NULL), g_bytes_get_size (blob_block)); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* log everything */ g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); /* tests go here */ g_test_add_func ("/wac/firmware{parse}", fu_wac_firmware_parse_func); return g_test_run (); } fwupd-1.2.14/plugins/wacom-usb/fu-wac-common.c000066400000000000000000000047561402665037500211420ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "fu-wac-common.h" guint32 fu_wac_calculate_checksum32le (const guint8 *data, gsize len) { guint32 csum = 0x0; g_return_val_if_fail (len % 4 == 0, 0xff); for (guint i = 0; i < len; i += 4) { guint32 tmp; memcpy (&tmp, &data[i], sizeof(guint32)); csum += GUINT32_FROM_LE (tmp); } return GUINT32_TO_LE (csum); } guint32 fu_wac_calculate_checksum32le_bytes (GBytes *blob) { gsize len = 0; const guint8 *data = g_bytes_get_data (blob, &len); return fu_wac_calculate_checksum32le (data, len); } const gchar * fu_wac_report_id_to_string (guint8 report_id) { if (report_id == FU_WAC_REPORT_ID_FW_DESCRIPTOR) return "FwDescriptor"; if (report_id == FU_WAC_REPORT_ID_SWITCH_TO_FLASH_LOADER) return "SwitchToFlashLoader"; if (report_id == FU_WAC_REPORT_ID_QUIT_AND_RESET) return "QuitAndReset"; if (report_id == FU_WAC_REPORT_ID_READ_BLOCK_DATA) return "ReadBlockData"; if (report_id == FU_WAC_REPORT_ID_WRITE_BLOCK) return "WriteBlock"; if (report_id == FU_WAC_REPORT_ID_ERASE_BLOCK) return "EraseBlock"; if (report_id == FU_WAC_REPORT_ID_SET_READ_ADDRESS) return "SetReadAddress"; if (report_id == FU_WAC_REPORT_ID_GET_STATUS) return "GetStatus"; if (report_id == FU_WAC_REPORT_ID_UPDATE_RESET) return "UpdateReset"; if (report_id == FU_WAC_REPORT_ID_WRITE_WORD) return "WriteWord"; if (report_id == FU_WAC_REPORT_ID_GET_PARAMETERS) return "GetParameters"; if (report_id == FU_WAC_REPORT_ID_GET_FLASH_DESCRIPTOR) return "GetFlashDescriptor"; if (report_id == FU_WAC_REPORT_ID_GET_CHECKSUMS) return "GetChecksums"; if (report_id == FU_WAC_REPORT_ID_SET_CHECKSUM_FOR_BLOCK) return "SetChecksumForBlock"; if (report_id == FU_WAC_REPORT_ID_CALCULATE_CHECKSUM_FOR_BLOCK) return "CalculateChecksumForBlock"; if (report_id == FU_WAC_REPORT_ID_WRITE_CHECKSUM_TABLE) return "WriteChecksumTable"; if (report_id == FU_WAC_REPORT_ID_GET_CURRENT_FIRMWARE_IDX) return "GetCurrentFirmwareIdx"; if (report_id == FU_WAC_REPORT_ID_MODULE) return "Module"; return NULL; } void fu_wac_buffer_dump (const gchar *title, guint8 cmd, const guint8 *buf, gsize sz) { g_autofree gchar *tmp = NULL; if (g_getenv ("FWUPD_WACOM_USB_VERBOSE") == NULL) return; tmp = g_strdup_printf ("%s %s (%" G_GSIZE_FORMAT ")", title, fu_wac_report_id_to_string (cmd), sz); fu_common_dump_raw (G_LOG_DOMAIN, tmp, buf, sz); } fwupd-1.2.14/plugins/wacom-usb/fu-wac-common.h000066400000000000000000000037301402665037500211360ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FU_WAC_PACKET_LEN 512 #define FU_WAC_REPORT_ID_COMMAND 0x01 #define FU_WAC_REPORT_ID_STATUS 0x02 #define FU_WAC_REPORT_ID_CONTROL 0x03 #define FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_MAIN 0x07 #define FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_TOUCH 0x07 #define FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_BLUETOOTH 0x16 #define FU_WAC_REPORT_ID_FW_DESCRIPTOR 0xcb /* GET_FEATURE */ #define FU_WAC_REPORT_ID_SWITCH_TO_FLASH_LOADER 0xcc /* SET_FEATURE */ #define FU_WAC_REPORT_ID_QUIT_AND_RESET 0xcd /* SET_FEATURE */ #define FU_WAC_REPORT_ID_READ_BLOCK_DATA 0xd1 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_WRITE_BLOCK 0xd2 /* SET_FEATURE */ #define FU_WAC_REPORT_ID_ERASE_BLOCK 0xd3 /* SET_FEATURE */ #define FU_WAC_REPORT_ID_SET_READ_ADDRESS 0xd4 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_GET_STATUS 0xd5 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_UPDATE_RESET 0xd6 /* SET_FEATURE */ #define FU_WAC_REPORT_ID_WRITE_WORD 0xd7 /* SET_FEATURE */ #define FU_WAC_REPORT_ID_GET_PARAMETERS 0xd8 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_GET_FLASH_DESCRIPTOR 0xd9 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_GET_CHECKSUMS 0xda /* GET_FEATURE */ #define FU_WAC_REPORT_ID_SET_CHECKSUM_FOR_BLOCK 0xdb /* SET_FEATURE */ #define FU_WAC_REPORT_ID_CALCULATE_CHECKSUM_FOR_BLOCK 0xdc /* SET_FEATURE */ #define FU_WAC_REPORT_ID_WRITE_CHECKSUM_TABLE 0xde /* SET_FEATURE */ #define FU_WAC_REPORT_ID_GET_CURRENT_FIRMWARE_IDX 0xe2 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_MODULE 0xe4 guint32 fu_wac_calculate_checksum32le (const guint8 *data, gsize len); guint32 fu_wac_calculate_checksum32le_bytes (GBytes *blob); const gchar *fu_wac_report_id_to_string (guint8 report_id); void fu_wac_buffer_dump (const gchar *title, guint8 cmd, const guint8 *buf, gsize sz); G_END_DECLS fwupd-1.2.14/plugins/wacom-usb/fu-wac-device.c000066400000000000000000000655031402665037500211060ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-chunk.h" #include "fu-wac-device.h" #include "fu-wac-common.h" #include "fu-wac-firmware.h" #include "fu-wac-module-bluetooth.h" #include "fu-wac-module-touch.h" #include "dfu-common.h" #include "dfu-firmware.h" typedef struct __attribute__((packed)) { guint32 start_addr; guint32 block_sz; guint16 write_sz; /* bit 15 is write protection flag */ } FuWacFlashDescriptor; typedef enum { FU_WAC_STATUS_UNKNOWN = 0, FU_WAC_STATUS_WRITING = 1 << 0, FU_WAC_STATUS_ERASING = 1 << 1, FU_WAC_STATUS_ERROR_WRITE = 1 << 2, FU_WAC_STATUS_ERROR_ERASE = 1 << 3, FU_WAC_STATUS_WRITE_PROTECTED = 1 << 4, FU_WAC_STATUS_LAST } FuWacStatus; #define FU_WAC_DEVICE_TIMEOUT 5000 /* ms */ struct _FuWacDevice { FuUsbDevice parent_instance; GPtrArray *flash_descriptors; GArray *checksums; guint32 status_word; guint16 firmware_index; guint16 loader_ver; guint16 read_data_sz; guint16 write_word_sz; guint16 write_block_sz; /* usb transfer size */ guint16 nr_flash_blocks; guint16 configuration; }; G_DEFINE_TYPE (FuWacDevice, fu_wac_device, FU_TYPE_USB_DEVICE) static GString * fu_wac_device_status_to_string (guint32 status_word) { GString *str = g_string_new (NULL); if (status_word & FU_WAC_STATUS_WRITING) g_string_append (str, "writing,"); if (status_word & FU_WAC_STATUS_ERASING) g_string_append (str, "erasing,"); if (status_word & FU_WAC_STATUS_ERROR_WRITE) g_string_append (str, "error-write,"); if (status_word & FU_WAC_STATUS_ERROR_ERASE) g_string_append (str, "error-erase,"); if (status_word & FU_WAC_STATUS_WRITE_PROTECTED) g_string_append (str, "write-protected,"); if (str->len == 0) { g_string_append (str, "none"); return str; } g_string_truncate (str, str->len - 1); return str; } static gboolean fu_wav_device_flash_descriptor_is_wp (const FuWacFlashDescriptor *fd) { return fd->write_sz & 0x8000; } static void fu_wac_device_to_string (FuDevice *device, GString *str) { GPtrArray *children; FuWacDevice *self = FU_WAC_DEVICE (device); g_autoptr(GString) status_str = NULL; g_string_append (str, " FuWacDevice:\n"); if (self->firmware_index != 0xffff) { g_string_append_printf (str, " fw-index: 0x%04x\n", self->firmware_index); } if (self->loader_ver > 0) { g_string_append_printf (str, " loader-ver: 0x%04x\n", (guint) self->loader_ver); } if (self->read_data_sz > 0) { g_string_append_printf (str, " read-data-sz: 0x%04x\n", (guint) self->read_data_sz); } if (self->write_word_sz > 0) { g_string_append_printf (str, " write-word-sz: 0x%04x\n", (guint) self->write_word_sz); } if (self->write_block_sz > 0) { g_string_append_printf (str, " write-block-sz: 0x%04x\n", (guint) self->write_block_sz); } if (self->nr_flash_blocks > 0) { g_string_append_printf (str, " nr-flash-blocks: 0x%04x\n", (guint) self->nr_flash_blocks); } if (self->configuration != 0xffff) { g_string_append_printf (str, " configuration: 0x%04x\n", (guint) self->configuration); } for (guint i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index (self->flash_descriptors, i); g_string_append_printf (str, " flash-descriptor-%02u:\n", i); g_string_append_printf (str, " start-addr:\t0x%08x\n", (guint) fd->start_addr); g_string_append_printf (str, " block-sz:\t0x%08x\n", (guint) fd->block_sz); g_string_append_printf (str, " write-sz:\t0x%04x\n", (guint) fd->write_sz & ~0x8000); g_string_append_printf (str, " protected:\t%s\n", fu_wav_device_flash_descriptor_is_wp (fd) ? "yes" : "no"); } status_str = fu_wac_device_status_to_string (self->status_word); g_string_append_printf (str, " status:\t\t%s\n", status_str->str); /* print children also */ children = fu_device_get_children (device); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index (children, i); g_autofree gchar *tmp = fu_device_to_string (FU_DEVICE (child)); g_string_append (str, " FuWacDeviceChild:\n"); g_string_append (str, tmp); } } gboolean fu_wac_device_get_feature_report (FuWacDevice *self, guint8 *buf, gsize bufsz, FuWacDeviceFeatureFlags flags, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize sz = 0; guint8 cmd = buf[0]; /* hit hardware */ if ((flags & FU_WAC_DEVICE_FEATURE_FLAG_NO_DEBUG) == 0) fu_wac_buffer_dump ("GET", cmd, buf, bufsz); if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_GET, /* bRequest */ HID_FEATURE | cmd, /* wValue */ 0x0000, /* wIndex */ buf, bufsz, &sz, FU_WAC_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "Failed to get feature report: "); return FALSE; } if ((flags & FU_WAC_DEVICE_FEATURE_FLAG_NO_DEBUG) == 0) fu_wac_buffer_dump ("GE2", cmd, buf, sz); /* check packet */ if ((flags & FU_WAC_DEVICE_FEATURE_FLAG_ALLOW_TRUNC) == 0 && sz != bufsz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "packet get bytes %" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT, sz, bufsz); return FALSE; } if (buf[0] != cmd) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "command response was %i expected %i", buf[0], cmd); return FALSE; } return TRUE; } gboolean fu_wac_device_set_feature_report (FuWacDevice *self, guint8 *buf, gsize bufsz, FuWacDeviceFeatureFlags flags, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize sz = 0; guint8 cmd = buf[0]; /* hit hardware */ fu_wac_buffer_dump ("SET", cmd, buf, bufsz); if (g_getenv ("FWUPD_WAC_EMULATE") != NULL) return TRUE; if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, HID_REPORT_SET, /* bRequest */ HID_FEATURE | cmd, /* wValue */ 0x0000, /* wIndex */ buf, bufsz, &sz, FU_WAC_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "Failed to set feature report: "); return FALSE; } /* check packet */ if ((flags & FU_WAC_DEVICE_FEATURE_FLAG_ALLOW_TRUNC) == 0 && sz != bufsz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "packet sent bytes %" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT, sz, bufsz); return FALSE; } return TRUE; } static gboolean fu_wac_device_ensure_flash_descriptors (FuWacDevice *self, GError **error) { gsize sz = (self->nr_flash_blocks * 10) + 1; g_autofree guint8 *buf = NULL; /* already done */ if (self->flash_descriptors->len > 0) return TRUE; /* hit hardware */ buf = g_malloc (sz); memset (buf, 0xff, sz); buf[0] = FU_WAC_REPORT_ID_GET_FLASH_DESCRIPTOR; if (!fu_wac_device_get_feature_report (self, buf, sz, FU_WAC_DEVICE_FEATURE_FLAG_NONE, error)) return FALSE; /* parse */ for (guint i = 0; i < self->nr_flash_blocks; i++) { FuWacFlashDescriptor *fd = g_new0 (FuWacFlashDescriptor, 1); const guint blksz = sizeof(FuWacFlashDescriptor); fd->start_addr = fu_common_read_uint32 (buf + (i * blksz) + 1, G_LITTLE_ENDIAN); fd->block_sz = fu_common_read_uint32 (buf + (i * blksz) + 5, G_LITTLE_ENDIAN); fd->write_sz = fu_common_read_uint16 (buf + (i * blksz) + 9, G_LITTLE_ENDIAN); g_ptr_array_add (self->flash_descriptors, fd); } g_debug ("added %u flash descriptors", self->flash_descriptors->len); return TRUE; } static gboolean fu_wac_device_ensure_status (FuWacDevice *self, GError **error) { g_autoptr(GString) str = NULL; guint8 buf[] = { [0] = FU_WAC_REPORT_ID_GET_STATUS, [1 ... 4] = 0xff }; /* hit hardware */ buf[0] = FU_WAC_REPORT_ID_GET_STATUS; if (!fu_wac_device_get_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error)) return FALSE; /* parse */ self->status_word = fu_common_read_uint32 (buf + 1, G_LITTLE_ENDIAN); str = fu_wac_device_status_to_string (self->status_word); g_debug ("status now: %s", str->str); return TRUE; } static gboolean fu_wac_device_ensure_checksums (FuWacDevice *self, GError **error) { gsize sz = (self->nr_flash_blocks * 4) + 5; guint32 updater_version; g_autofree guint8 *buf = g_malloc (sz); /* hit hardware */ memset (buf, 0xff, sz); buf[0] = FU_WAC_REPORT_ID_GET_CHECKSUMS; if (!fu_wac_device_get_feature_report (self, buf, sz, FU_WAC_DEVICE_FEATURE_FLAG_NONE, error)) return FALSE; /* parse */ updater_version = fu_common_read_uint32 (buf + 1, G_LITTLE_ENDIAN); g_debug ("updater-version: %" G_GUINT32_FORMAT, updater_version); /* get block checksums */ g_array_set_size (self->checksums, 0); for (guint i = 0; i < self->nr_flash_blocks; i++) { guint32 csum = fu_common_read_uint32 (buf + 5 + (i * 4), G_LITTLE_ENDIAN); g_debug ("checksum block %02u: 0x%08x", i, (guint) csum); g_array_append_val (self->checksums, csum); } g_debug ("added %u checksums", self->flash_descriptors->len); return TRUE; } static gboolean fu_wac_device_ensure_firmware_index (FuWacDevice *self, GError **error) { guint8 buf[] = { [0] = FU_WAC_REPORT_ID_GET_CURRENT_FIRMWARE_IDX, [1 ... 2] = 0xff }; /* hit hardware */ if (!fu_wac_device_get_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error)) return FALSE; /* parse */ self->firmware_index = fu_common_read_uint16 (buf + 1, G_LITTLE_ENDIAN); return TRUE; } static gboolean fu_wac_device_ensure_parameters (FuWacDevice *self, GError **error) { guint8 buf[] = { [0] = FU_WAC_REPORT_ID_GET_PARAMETERS, [1 ... 12] = 0xff }; /* hit hardware */ if (!fu_wac_device_get_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error)) return FALSE; /* parse */ self->loader_ver = fu_common_read_uint16 (buf + 1, G_LITTLE_ENDIAN); self->read_data_sz = fu_common_read_uint16 (buf + 3, G_LITTLE_ENDIAN); self->write_word_sz = fu_common_read_uint16 (buf + 5, G_LITTLE_ENDIAN); self->write_block_sz = fu_common_read_uint16 (buf + 7, G_LITTLE_ENDIAN); self->nr_flash_blocks = fu_common_read_uint16 (buf + 9, G_LITTLE_ENDIAN); self->configuration = fu_common_read_uint16 (buf + 11, G_LITTLE_ENDIAN); return TRUE; } static gboolean fu_wac_device_write_block (FuWacDevice *self, guint32 addr, GBytes *blob, GError **error) { const guint8 *tmp; gsize bufsz = self->write_block_sz + 5; gsize sz = 0; g_autofree guint8 *buf = NULL; /* check size */ tmp = g_bytes_get_data (blob, &sz); if (sz > self->write_block_sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "packet was too large at %" G_GSIZE_FORMAT " bytes", sz); return FALSE; } /* build packet */ buf = g_malloc (bufsz); memset (buf, 0xff, bufsz); buf[0] = FU_WAC_REPORT_ID_WRITE_BLOCK; fu_common_write_uint32 (buf + 1, addr, G_LITTLE_ENDIAN); if (sz > 0) memcpy (buf + 5, tmp, sz); /* hit hardware */ return fu_wac_device_set_feature_report (self, buf, bufsz, FU_WAC_DEVICE_FEATURE_FLAG_NONE, error); } static gboolean fu_wac_device_erase_block (FuWacDevice *self, guint32 addr, GError **error) { guint8 buf[] = { [0] = FU_WAC_REPORT_ID_ERASE_BLOCK, [1 ... 4] = 0xff }; /* build packet */ fu_common_write_uint32 (buf + 1, addr, G_LITTLE_ENDIAN); /* hit hardware */ return fu_wac_device_set_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error); } gboolean fu_wac_device_update_reset (FuWacDevice *self, GError **error) { guint8 buf[] = { [0] = FU_WAC_REPORT_ID_UPDATE_RESET, [1 ... 4] = 0xff }; /* hit hardware */ return fu_wac_device_set_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error); } static gboolean fu_wac_device_set_checksum_of_block (FuWacDevice *self, guint16 block_nr, guint32 checksum, GError **error) { guint8 buf[] = { [0] = FU_WAC_REPORT_ID_SET_CHECKSUM_FOR_BLOCK, [1 ... 6] = 0xff }; /* build packet */ fu_common_write_uint16 (buf + 1, block_nr, G_LITTLE_ENDIAN); fu_common_write_uint32 (buf + 3, checksum, G_LITTLE_ENDIAN); /* hit hardware */ return fu_wac_device_set_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error); } static gboolean fu_wac_device_calculate_checksum_of_block (FuWacDevice *self, guint16 block_nr, GError **error) { guint8 buf[] = { [0] = FU_WAC_REPORT_ID_CALCULATE_CHECKSUM_FOR_BLOCK, [1 ... 2] = 0xff }; /* build packet */ fu_common_write_uint16 (buf + 1, block_nr, G_LITTLE_ENDIAN); /* hit hardware */ return fu_wac_device_set_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error); } static gboolean fu_wac_device_write_checksum_table (FuWacDevice *self, GError **error) { guint8 buf[] = { [0] = FU_WAC_REPORT_ID_WRITE_CHECKSUM_TABLE, [1 ... 4] = 0xff }; /* hit hardware */ return fu_wac_device_set_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error); } static gboolean fu_wac_device_switch_to_flash_loader (FuWacDevice *self, GError **error) { guint8 buf[] = { [0] = FU_WAC_REPORT_ID_SWITCH_TO_FLASH_LOADER, [1] = 0x05, [2] = 0x6a }; /* hit hardware */ return fu_wac_device_set_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error); } static gboolean fu_wac_device_write_firmware (FuDevice *device, GBytes *blob, FwupdInstallFlags flags, GError **error) { DfuElement *element; DfuImage *image; FuWacDevice *self = FU_WAC_DEVICE (device); gsize blocks_done = 0; gsize blocks_total = 0; g_autoptr(DfuFirmware) firmware = dfu_firmware_new (); g_autoptr(GHashTable) fd_blobs = NULL; g_autofree guint32 *csum_local = NULL; /* load .wac file, including metadata */ if (!fu_wac_firmware_parse_data (firmware, blob, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) return FALSE; if (dfu_firmware_get_format (firmware) != DFU_FIRMWARE_FORMAT_SREC) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "expected firmware format is 'srec', got '%s'", dfu_firmware_format_to_string (dfu_firmware_get_format (firmware))); return FALSE; } /* enter flash mode */ if (!fu_wac_device_switch_to_flash_loader (self, error)) return FALSE; /* get current selected device */ if (!fu_wac_device_ensure_firmware_index (self, error)) return FALSE; /* use the correct image from the firmware */ image = dfu_firmware_get_image (firmware, self->firmware_index == 1 ? 1 : 0); if (image == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no firmware image for index %" G_GUINT16_FORMAT, self->firmware_index); return FALSE; } element = dfu_image_get_element_default (image); if (element == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no element in image %" G_GUINT16_FORMAT, self->firmware_index); return FALSE; } g_debug ("using element at addr 0x%0x", (guint) dfu_element_get_address (element)); /* get firmware parameters (page sz and transfer sz) */ if (!fu_wac_device_ensure_parameters (self, error)) return FALSE; /* get the current flash descriptors */ if (!fu_wac_device_ensure_flash_descriptors (self, error)) return FALSE; /* get the updater protocol version */ if (!fu_wac_device_ensure_checksums (self, error)) return FALSE; /* clear all checksums of pages */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); for (guint16 i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index (self->flash_descriptors, i); if (fu_wav_device_flash_descriptor_is_wp (fd)) continue; if (!fu_wac_device_set_checksum_of_block (self, i, 0x0, error)) return FALSE; } /* get the blobs for each chunk */ fd_blobs = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_bytes_unref); for (guint16 i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index (self->flash_descriptors, i); GBytes *blob_block; g_autoptr(GBytes) blob_tmp = NULL; if (fu_wav_device_flash_descriptor_is_wp (fd)) continue; blob_tmp = dfu_element_get_contents_chunk (element, fd->start_addr, fd->block_sz, NULL); if (blob_tmp == NULL) break; blob_block = dfu_utils_bytes_pad (blob_tmp, fd->block_sz); g_hash_table_insert (fd_blobs, fd, blob_block); } /* checksum actions post-write */ blocks_total = g_hash_table_size (fd_blobs) + 2; /* write the data into the flash page */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); csum_local = g_new0 (guint32, self->flash_descriptors->len); for (guint16 i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index (self->flash_descriptors, i); GBytes *blob_block; g_autoptr(GPtrArray) chunks = NULL; /* if page is protected */ if (fu_wav_device_flash_descriptor_is_wp (fd)) continue; /* get data for page */ blob_block = g_hash_table_lookup (fd_blobs, fd); if (blob_block == NULL) break; /* ignore empty blocks */ if (fu_common_bytes_is_empty (blob_block)) { g_debug ("empty block, ignoring"); fu_device_set_progress_full (device, blocks_done++, blocks_total); continue; } /* erase entire block */ if (!fu_wac_device_erase_block (self, i, error)) return FALSE; /* write block in chunks */ chunks = fu_chunk_array_new_from_bytes (blob_block, fd->start_addr, 0, /* page_sz */ self->write_block_sz); for (guint j = 0; j < chunks->len; j++) { FuChunk *chk = g_ptr_array_index (chunks, j); g_autoptr(GBytes) blob_chunk = g_bytes_new (chk->data, chk->data_sz); if (!fu_wac_device_write_block (self, chk->address, blob_chunk, error)) return FALSE; } /* calculate expected checksum and save to device RAM */ csum_local[i] = fu_wac_calculate_checksum32le_bytes (blob_block); g_debug ("block checksum %02u: 0x%08x", i, csum_local[i]); if (!fu_wac_device_set_checksum_of_block (self, i, csum_local[i], error)) return FALSE; /* update device progress */ fu_device_set_progress_full (device, blocks_done++, blocks_total); } /* calculate CRC inside device */ for (guint16 i = 0; i < self->flash_descriptors->len; i++) { if (!fu_wac_device_calculate_checksum_of_block (self, i, error)) return FALSE; } /* update device progress */ fu_device_set_progress_full (device, blocks_done++, blocks_total); /* read all CRC of all pages and verify with local CRC */ if (!fu_wac_device_ensure_checksums (self, error)) return FALSE; for (guint16 i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index (self->flash_descriptors, i); GBytes *blob_block; guint32 csum_rom; /* if page is protected */ if (fu_wav_device_flash_descriptor_is_wp (fd)) continue; /* no more written pages */ blob_block = g_hash_table_lookup (fd_blobs, fd); if (blob_block == NULL) continue; if (fu_common_bytes_is_empty (blob_block)) continue; /* check checksum matches */ csum_rom = g_array_index (self->checksums, guint32, i); if (csum_rom != csum_local[i]) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed local checksum at block %u, " "got 0x%08x expected 0x%08x", i, (guint) csum_rom, (guint) csum_local[i]); return FALSE; } g_debug ("matched checksum at block %u of 0x%08x", i, csum_rom); } /* update device progress */ fu_device_set_progress_full (device, blocks_done++, blocks_total); /* store host CRC into flash */ if (!fu_wac_device_write_checksum_table (self, error)) return FALSE; /* update progress */ fu_device_set_progress_full (device, blocks_total, blocks_total); return TRUE; } static gboolean fu_wac_device_add_modules_bluetooth (FuWacDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); g_autofree gchar *name = NULL; g_autofree gchar *version = NULL; g_autoptr(FuWacModule) module = NULL; guint8 buf[] = { [0] = FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_BLUETOOTH, [1 ... 14] = 0xff }; buf[0] = FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_BLUETOOTH; if (!fu_wac_device_get_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error)) { g_prefix_error (error, "Failed to get GetFirmwareVersionBluetooth: "); return FALSE; } /* success */ name = g_strdup_printf ("%s [Legacy Bluetooth Module]", fu_device_get_name (FU_DEVICE (self))); version = g_strdup_printf ("%x.%x", (guint) buf[2], (guint) buf[1]); module = fu_wac_module_bluetooth_new (usb_device); fu_device_add_child (FU_DEVICE (self), FU_DEVICE (module)); fu_device_set_name (FU_DEVICE (module), name); fu_device_set_version (FU_DEVICE (module), version, FWUPD_VERSION_FORMAT_PAIR); return TRUE; } static gboolean fu_wac_device_add_modules_legacy (FuWacDevice *self, GError **error) { g_autoptr(GError) error_bt = NULL; /* optional bluetooth */ if (!fu_wac_device_add_modules_bluetooth (self, &error_bt)) g_debug ("no bluetooth hardware: %s", error_bt->message); return TRUE; } static gboolean fu_wac_device_add_modules (FuWacDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); g_autofree gchar *version_bootloader = NULL; guint8 buf[] = { [0] = FU_WAC_REPORT_ID_FW_DESCRIPTOR, [1 ... 31] = 0xff }; if (!fu_wac_device_get_feature_report (self, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_NONE, error)) { g_prefix_error (error, "Failed to get DeviceFirmwareDescriptor: "); return FALSE; } /* verify bootloader is compatible */ if (buf[1] != 0x01) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "bootloader major version not compatible"); return FALSE; } /* verify the number of submodules is possible */ if (buf[3] > (512 - 4) / 4) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "number of submodules is impossible"); return FALSE; } /* bootloader version */ version_bootloader = g_strdup_printf ("%u.%u", buf[1], buf[2]); fu_device_set_version_bootloader (FU_DEVICE (self), version_bootloader); /* get versions of each submodule */ for (guint8 i = 0; i < buf[3]; i++) { guint8 fw_type = buf[(i * 4) + 4] & ~0x80; g_autofree gchar *name = NULL; g_autofree gchar *version = NULL; g_autoptr(FuWacModule) module = NULL; /* version number is decimal */ version = g_strdup_printf ("%u.%u", buf[(i * 4) + 5], buf[(i * 4) + 6]); switch (fw_type) { case FU_WAC_MODULE_FW_TYPE_TOUCH: module = fu_wac_module_touch_new (usb_device); name = g_strdup_printf ("%s [Touch Module]", fu_device_get_name (FU_DEVICE (self))); fu_device_add_child (FU_DEVICE (self), FU_DEVICE (module)); fu_device_set_name (FU_DEVICE (module), name); fu_device_set_version (FU_DEVICE (module), version, FWUPD_VERSION_FORMAT_PAIR); break; case FU_WAC_MODULE_FW_TYPE_BLUETOOTH: module = fu_wac_module_bluetooth_new (usb_device); name = g_strdup_printf ("%s [Bluetooth Module]", fu_device_get_name (FU_DEVICE (self))); fu_device_add_child (FU_DEVICE (self), FU_DEVICE (module)); fu_device_set_name (FU_DEVICE (module), name); fu_device_set_version (FU_DEVICE (module), version, FWUPD_VERSION_FORMAT_PAIR); break; case FU_WAC_MODULE_FW_TYPE_MAIN: fu_device_set_version (FU_DEVICE (self), version, FWUPD_VERSION_FORMAT_PAIR); break; default: g_warning ("unknown submodule type 0x%0x", fw_type); break; } } return TRUE; } static gboolean fu_wac_device_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* open device */ if (!g_usb_device_claim_interface (usb_device, 0x00, /* HID */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to claim HID interface: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_wac_device_setup (FuDevice *device, GError **error) { FuWacDevice *self = FU_WAC_DEVICE (device); /* get current status */ if (!fu_wac_device_ensure_status (self, error)) return FALSE; /* get version of each sub-module */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION)) { if (!fu_wac_device_add_modules_legacy (self, error)) return FALSE; } else { if (!fu_wac_device_add_modules (self, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_wac_device_close (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* reattach wacom.ko */ if (!g_usb_device_release_interface (usb_device, 0x00, /* HID */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to re-attach interface: "); return FALSE; } /* The hidcore subsystem uses a generic power_supply that has a deferred * work item that will lock the device. When removing the power_supply, * we take the lock, then cancel the work item which needs to take the * lock too. This needs to be fixed in the kernel, but for the moment * this should let the kernel unstick itself. */ g_usleep (20 * 1000); /* success */ return TRUE; } static void fu_wac_device_init (FuWacDevice *self) { self->flash_descriptors = g_ptr_array_new_with_free_func (g_free); self->checksums = g_array_new (FALSE, FALSE, sizeof(guint32)); self->configuration = 0xffff; self->firmware_index = 0xffff; fu_device_add_icon (FU_DEVICE (self), "input-tablet"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_install_duration (FU_DEVICE (self), 10); } static void fu_wac_device_finalize (GObject *object) { FuWacDevice *self = FU_WAC_DEVICE (object); g_ptr_array_unref (self->flash_descriptors); g_array_unref (self->checksums); G_OBJECT_CLASS (fu_wac_device_parent_class)->finalize (object); } static void fu_wac_device_class_init (FuWacDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); object_class->finalize = fu_wac_device_finalize; klass_device->write_firmware = fu_wac_device_write_firmware; klass_device->to_string = fu_wac_device_to_string; klass_device->setup = fu_wac_device_setup; klass_usb_device->open = fu_wac_device_open; klass_usb_device->close = fu_wac_device_close; } FuWacDevice * fu_wac_device_new (FuUsbDevice *device) { FuWacDevice *self = g_object_new (FU_TYPE_WAC_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } fwupd-1.2.14/plugins/wacom-usb/fu-wac-device.h000066400000000000000000000017621402665037500211100ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_WAC_DEVICE (fu_wac_device_get_type ()) G_DECLARE_FINAL_TYPE (FuWacDevice, fu_wac_device, FU, WAC_DEVICE, FuUsbDevice) typedef enum { FU_WAC_DEVICE_FEATURE_FLAG_NONE = 0, FU_WAC_DEVICE_FEATURE_FLAG_ALLOW_TRUNC = 1 << 0, FU_WAC_DEVICE_FEATURE_FLAG_NO_DEBUG = 1 << 1, FU_WAC_DEVICE_FEATURE_FLAG_LAST } FuWacDeviceFeatureFlags; FuWacDevice *fu_wac_device_new (FuUsbDevice *device); gboolean fu_wac_device_update_reset (FuWacDevice *self, GError **error); gboolean fu_wac_device_get_feature_report (FuWacDevice *self, guint8 *buf, gsize bufsz, FuWacDeviceFeatureFlags flags, GError **error); gboolean fu_wac_device_set_feature_report (FuWacDevice *self, guint8 *buf, gsize bufsz, FuWacDeviceFeatureFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/plugins/wacom-usb/fu-wac-firmware.c000066400000000000000000000140031402665037500214500ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "dfu-element.h" #include "dfu-format-srec.h" #include "dfu-image.h" #include "fu-wac-firmware.h" #include "fwupd-error.h" typedef struct { guint32 addr; guint32 sz; guint32 prog_start_addr; } DfuFirmwareWacHeaderRecord; /** * fu_wac_firmware_parse_data: * @firmware: a #DfuFirmware * @bytes: data to parse * @flags: some #DfuFirmwareParseFlags * @error: a #GError, or %NULL * * Unpacks into a firmware object from DfuSe data. * * Returns: %TRUE for success **/ gboolean fu_wac_firmware_parse_data (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error) { gsize len; guint8 *data; g_auto(GStrv) lines = NULL; g_autoptr(GString) image_buffer = NULL; g_autofree gchar *data_str = NULL; guint8 images_cnt = 0; g_autoptr(GPtrArray) header_infos = g_ptr_array_new_with_free_func (g_free); /* check the prefix (BE) */ data = (guint8 *) g_bytes_get_data (bytes, &len); if (memcmp (data, "WACOM", 5) != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid .wac prefix"); return FALSE; } /* parse each line */ data_str = g_strndup ((const gchar *) data, len); lines = g_strsplit (data_str, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { g_autofree gchar *cmd = g_strndup (lines[i], 2); /* remove windows line endings */ g_strdelimit (lines[i], "\r", '\0'); /* Wacom-specific metadata */ if (g_strcmp0 (cmd, "WA") == 0) { guint cmdlen = strlen (lines[i]); /* header info record */ if (memcmp (lines[i] + 2, "COM", 3) == 0) { guint8 header_image_cnt = 0; if (cmdlen != 40) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid header, got %u bytes", cmdlen); return FALSE; } header_image_cnt = dfu_utils_buffer_parse_uint4 (lines[i] + 5); for (guint j = 0; j < header_image_cnt; j++) { DfuFirmwareWacHeaderRecord *hdr = g_new0 (DfuFirmwareWacHeaderRecord, 1); hdr->addr = dfu_utils_buffer_parse_uint32 (lines[i] + (j * 16) + 6); hdr->sz = dfu_utils_buffer_parse_uint32 (lines[i] + (j * 16) + 14); g_ptr_array_add (header_infos, hdr); g_debug ("header_fw%u_addr: 0x%x", j, hdr->addr); g_debug ("header_fw%u_sz: 0x%x", j, hdr->sz); } continue; } /* firmware headline record */ if (cmdlen == 13) { DfuFirmwareWacHeaderRecord *hdr; guint8 idx = dfu_utils_buffer_parse_uint4 (lines[i] + 2); if (idx == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "headline %u invalid", idx); return FALSE; } if (idx > header_infos->len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "headline %u exceeds header count %u", idx, header_infos->len); return FALSE; } hdr = g_ptr_array_index (header_infos, idx - 1); hdr->prog_start_addr = dfu_utils_buffer_parse_uint32 (lines[i] + 3); if (hdr->prog_start_addr != hdr->addr) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "programming address 0x%x != " "base address 0x%0x for idx %u", hdr->prog_start_addr, hdr->addr, idx); return FALSE; } g_debug ("programing-start-address: 0x%x", hdr->prog_start_addr); continue; } g_debug ("unknown Wacom-specific metadata"); continue; } /* start */ if (g_strcmp0 (cmd, "S0") == 0) { if (image_buffer != NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "duplicate S0 without S7"); return FALSE; } image_buffer = g_string_new (NULL); } /* these are things we want to include in the image */ if (g_strcmp0 (cmd, "S0") == 0 || g_strcmp0 (cmd, "S1") == 0 || g_strcmp0 (cmd, "S2") == 0 || g_strcmp0 (cmd, "S3") == 0 || g_strcmp0 (cmd, "S5") == 0 || g_strcmp0 (cmd, "S7") == 0 || g_strcmp0 (cmd, "S8") == 0 || g_strcmp0 (cmd, "S9") == 0) { if (image_buffer == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s without S0", cmd); return FALSE; } g_string_append_printf (image_buffer, "%s\n", lines[i]); } /* end */ if (g_strcmp0 (cmd, "S7") == 0) { g_autoptr(GBytes) blob = NULL; g_autoptr(DfuImage) image = dfu_image_new (); DfuFirmwareWacHeaderRecord *hdr; /* get the correct relocated start address */ if (images_cnt >= header_infos->len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s without header", cmd); return FALSE; } hdr = g_ptr_array_index (header_infos, images_cnt); if (image_buffer == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s with missing image buffer", cmd); return FALSE; } /* parse SREC file and add as image */ blob = g_bytes_new (image_buffer->str, image_buffer->len); if (!dfu_image_from_srec (image, blob, hdr->addr, flags, error)) return FALSE; /* the alt-setting is used for the firmware index */ dfu_image_set_alt_setting (image, images_cnt); dfu_firmware_add_image (firmware, image); images_cnt++; /* clear the image buffer */ g_string_free (image_buffer, TRUE); image_buffer = NULL; } } /* verify data is complete */ if (image_buffer != NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "truncated data: no S7"); return FALSE; } /* ensure this matched the header */ if (header_infos->len != images_cnt) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "not enough images %u for header count %u", images_cnt, header_infos->len); return FALSE; } /* success */ dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_SREC); return TRUE; } fwupd-1.2.14/plugins/wacom-usb/fu-wac-firmware.h000066400000000000000000000005301402665037500214550ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "dfu-firmware.h" G_BEGIN_DECLS gboolean fu_wac_firmware_parse_data (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/plugins/wacom-usb/fu-wac-module-bluetooth.c000066400000000000000000000125731402665037500231360ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-common.h" #include "fu-wac-device.h" #include "fu-wac-module-bluetooth.h" struct _FuWacModuleBluetooth { FuWacModule parent_instance; }; G_DEFINE_TYPE (FuWacModuleBluetooth, fu_wac_module_bluetooth, FU_TYPE_WAC_MODULE) #define FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ 256 #define FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_START 0x3000 #define FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_STOP 0x8000 typedef struct { guint8 preamble[7]; guint8 addr[3]; guint8 crc; guint8 cdata[FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ]; } FuWacModuleBluetoothBlockData; static void fu_wac_module_bluetooth_calculate_crc_byte (guint8 *crc, guint8 data) { guint8 c[8]; guint8 m[8]; guint8 r[8]; /* find out what bits are set */ for (guint i = 0; i < 8; i++) { c[i] = (*crc & (1 << i)) != 0; m[i] = (data & (1 << i)) != 0; } /* do CRC on byte */ r[7] = (c[7] ^ m[4] ^ c[3] ^ m[3] ^ c[4] ^ m[6] ^ c[1] ^ m[0]); r[6] = (c[6] ^ m[5] ^ c[2] ^ m[4] ^ c[3] ^ m[7] ^ c[0] ^ m[1]); r[5] = (c[5] ^ m[6] ^ c[1] ^ m[5] ^ c[2] ^ m[2]); r[4] = (c[4] ^ m[7] ^ c[0] ^ m[6] ^ c[1] ^ m[3]); r[3] = (m[7] ^ m[0] ^ c[7] ^ c[0] ^ m[3] ^ c[4] ^ m[6] ^ c[1]); r[2] = (m[1] ^ c[6] ^ m[0] ^ c[7] ^ m[3] ^ c[4] ^ m[7] ^ c[0] ^ m[6] ^ c[1]); r[1] = (m[2] ^ c[5] ^ m[1] ^ c[6] ^ m[4] ^ c[3] ^ m[7] ^ c[0]); r[0] = (m[3] ^ c[4] ^ m[2] ^ c[5] ^ m[5] ^ c[2]); /* copy back into CRC */ *crc = 0; for (guint i = 0; i < 8; i++) { if (r[i] == 0) continue; *crc |= (1 << i); } } static guint8 fu_wac_module_bluetooth_calculate_crc (const guint8 *data, gsize sz) { guint8 crc = 0; for (gsize i = 0; i < sz; i++) fu_wac_module_bluetooth_calculate_crc_byte (&crc, data[i]); return crc; } static GPtrArray * fu_wac_module_bluetooth_parse_blocks (const guint8 *data, gsize sz, gboolean skip_user_data) { const guint8 preamble[] = {0x02, 0x00, 0x0f, 0x06, 0x01, 0x08, 0x01}; GPtrArray *blocks = g_ptr_array_new_with_free_func (g_free); for (guint addr = 0x0; addr < sz; addr += FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ) { FuWacModuleBluetoothBlockData *bd; gsize cdata_sz = FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ; /* user data area */ if (skip_user_data && addr >= FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_START && addr < FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_STOP) continue; bd = g_new0 (FuWacModuleBluetoothBlockData, 1); memcpy (bd->preamble, preamble, sizeof (preamble)); bd->addr[0] = (addr >> 16) & 0xff; bd->addr[1] = (addr >> 8) & 0xff; bd->addr[2] = addr & 0xff; memset (bd->cdata, 0xff, FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ); /* if file is not in multiples of payload size */ if (addr + FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ >= sz) cdata_sz = sz - addr; memcpy (bd->cdata, data + addr, cdata_sz); bd->crc = fu_wac_module_bluetooth_calculate_crc (bd->cdata, FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ); g_ptr_array_add (blocks, bd); } return blocks; } static gboolean fu_wac_module_bluetooth_write_firmware (FuDevice *device, GBytes *blob, FwupdInstallFlags flags, GError **error) { FuWacModule *self = FU_WAC_MODULE (device); const guint8 *data; gsize len = 0; gsize blocks_total = 0; const guint8 buf_start[] = { 0x00 }; g_autoptr(GPtrArray) blocks = NULL; g_autoptr(GBytes) blob_start = g_bytes_new_static (buf_start, 1); /* build each data packet */ data = g_bytes_get_data (blob, &len); blocks = fu_wac_module_bluetooth_parse_blocks (data, len, TRUE); blocks_total = blocks->len + 2; /* start, which will erase the module */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_START, blob_start, error)) return FALSE; /* update progress */ fu_device_set_progress_full (device, 1, blocks_total); /* data */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < blocks->len; i++) { FuWacModuleBluetoothBlockData *bd = g_ptr_array_index (blocks, i); guint8 buf[256+11]; g_autoptr(GBytes) blob_chunk = NULL; /* build data packet */ memset (buf, 0xff, sizeof(buf)); memcpy(&buf[0], bd->preamble, 7); memcpy(&buf[7], bd->addr, 3); buf[10] = bd->crc; memcpy (&buf[11], bd->cdata, sizeof(bd->cdata)); blob_chunk = g_bytes_new (buf, sizeof(buf)); if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_DATA, blob_chunk, error)) return FALSE; /* update progress */ fu_device_set_progress_full (device, i + 1, blocks_total); } /* end */ if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_END, NULL, error)) return FALSE; /* update progress */ fu_device_set_progress_full (device, blocks_total, blocks_total); return TRUE; } static void fu_wac_module_bluetooth_init (FuWacModuleBluetooth *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_install_duration (FU_DEVICE (self), 30); } static void fu_wac_module_bluetooth_class_init (FuWacModuleBluetoothClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->write_firmware = fu_wac_module_bluetooth_write_firmware; } FuWacModule * fu_wac_module_bluetooth_new (GUsbDevice *usb_device) { FuWacModule *module = NULL; module = g_object_new (FU_TYPE_WAC_MODULE_BLUETOOTH, "usb-device", usb_device, "fw-type", FU_WAC_MODULE_FW_TYPE_BLUETOOTH, NULL); return module; } fwupd-1.2.14/plugins/wacom-usb/fu-wac-module-bluetooth.h000066400000000000000000000006551402665037500231410ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wac-module.h" G_BEGIN_DECLS #define FU_TYPE_WAC_MODULE_BLUETOOTH (fu_wac_module_bluetooth_get_type ()) G_DECLARE_FINAL_TYPE (FuWacModuleBluetooth, fu_wac_module_bluetooth, FU, WAC_MODULE_BLUETOOTH, FuWacModule) FuWacModule *fu_wac_module_bluetooth_new (GUsbDevice *usb_device); G_END_DECLS fwupd-1.2.14/plugins/wacom-usb/fu-wac-module-touch.c000066400000000000000000000076011402665037500222470ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-device.h" #include "fu-wac-module-touch.h" #include "fu-chunk.h" #include "dfu-firmware.h" struct _FuWacModuleTouch { FuWacModule parent_instance; }; G_DEFINE_TYPE (FuWacModuleTouch, fu_wac_module_touch, FU_TYPE_WAC_MODULE) static gboolean fu_wac_module_touch_write_firmware (FuDevice *device, GBytes *blob, FwupdInstallFlags flags, GError **error) { DfuElement *element; DfuImage *image; FuWacModule *self = FU_WAC_MODULE (device); gsize blocks_total = 0; g_autoptr(GPtrArray) chunks = NULL; g_autoptr(DfuFirmware) firmware = dfu_firmware_new (); /* load .hex file */ if (!dfu_firmware_parse_data (firmware, blob, DFU_FIRMWARE_PARSE_FLAG_NONE, error)) return FALSE; /* check type */ if (dfu_firmware_get_format (firmware) != DFU_FIRMWARE_FORMAT_INTEL_HEX) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "expected firmware format is 'ihex', got '%s'", dfu_firmware_format_to_string (dfu_firmware_get_format (firmware))); return FALSE; } /* use the correct image from the firmware */ image = dfu_firmware_get_image (firmware, 0); if (image == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no firmware image"); return FALSE; } element = dfu_image_get_element_default (image); if (element == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no firmware element"); return FALSE; } g_debug ("using element at addr 0x%0x", (guint) dfu_element_get_address (element)); blob = dfu_element_get_contents (element); /* build each data packet */ chunks = fu_chunk_array_new_from_bytes (blob, dfu_element_get_address (element), 0x0, /* page_sz */ 128); /* packet_sz */ blocks_total = chunks->len + 2; /* start, which will erase the module */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_START, NULL, error)) return FALSE; /* update progress */ fu_device_set_progress_full (device, 1, blocks_total); /* data */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); guint8 buf[128+7] = { 0xff }; g_autoptr(GBytes) blob_chunk = NULL; /* build G11T data packet */ memset (buf, 0xff, sizeof(buf)); buf[0] = 0x01; /* writing */ buf[1] = chk->idx + 1; fu_common_write_uint32 (&buf[2], chk->address, G_LITTLE_ENDIAN); buf[6] = 0x10; /* no idea! */ memcpy (&buf[7], chk->data, chk->data_sz); blob_chunk = g_bytes_new (buf, sizeof(buf)); if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_DATA, blob_chunk, error)) { g_prefix_error (error, "failed to write block %u: ", chk->idx); return FALSE; } /* update progress */ fu_device_set_progress_full (device, i + 1, blocks_total); } /* end */ if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_END, NULL, error)) return FALSE; /* update progress */ fu_device_set_progress_full (device, blocks_total, blocks_total); return TRUE; } static void fu_wac_module_touch_init (FuWacModuleTouch *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_install_duration (FU_DEVICE (self), 30); } static void fu_wac_module_touch_class_init (FuWacModuleTouchClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->write_firmware = fu_wac_module_touch_write_firmware; } FuWacModule * fu_wac_module_touch_new (GUsbDevice *usb_device) { FuWacModule *module = NULL; module = g_object_new (FU_TYPE_WAC_MODULE_TOUCH, "usb-device", usb_device, "fw-type", FU_WAC_MODULE_FW_TYPE_TOUCH, NULL); return module; } fwupd-1.2.14/plugins/wacom-usb/fu-wac-module-touch.h000066400000000000000000000006251402665037500222530ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wac-module.h" G_BEGIN_DECLS #define FU_TYPE_WAC_MODULE_TOUCH (fu_wac_module_touch_get_type ()) G_DECLARE_FINAL_TYPE (FuWacModuleTouch, fu_wac_module_touch, FU, WAC_MODULE_TOUCH, FuWacModule) FuWacModule *fu_wac_module_touch_new (GUsbDevice *usb_device); G_END_DECLS fwupd-1.2.14/plugins/wacom-usb/fu-wac-module.c000066400000000000000000000246461402665037500211370ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-module.h" #include "fu-wac-common.h" #include "fu-wac-device.h" #include "dfu-common.h" #include "dfu-firmware.h" #define FU_WAC_MODULE_STATUS_OK 0 #define FU_WAC_MODULE_STATUS_BUSY 1 #define FU_WAC_MODULE_STATUS_ERR_CRC 2 #define FU_WAC_MODULE_STATUS_ERR_CMD 3 #define FU_WAC_MODULE_STATUS_ERR_HW_ACCESS_FAIL 4 #define FU_WAC_MODULE_STATUS_ERR_FLASH_NO_SUPPORT 5 #define FU_WAC_MODULE_STATUS_ERR_MODE_WRONG 6 #define FU_WAC_MODULE_STATUS_ERR_MPU_NO_SUPPORT 7 #define FU_WAC_MODULE_STATUS_ERR_VERSION_NO_SUPPORT 8 #define FU_WAC_MODULE_STATUS_ERR_ERASE 9 #define FU_WAC_MODULE_STATUS_ERR_WRITE 10 #define FU_WAC_MODULE_STATUS_ERR_EXIT 11 #define FU_WAC_MODULE_STATUS_ERR 12 #define FU_WAC_MODULE_STATUS_ERR_INVALID_OP 13 #define FU_WAC_MODULE_STATUS_ERR_WRONG_IMAGE 14 typedef struct { GUsbDevice *usb_device; guint8 fw_type; guint8 command; guint8 status; } FuWacModulePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuWacModule, fu_wac_module, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_wac_module_get_instance_private (o)) enum { PROP_0, PROP_FW_TYPE, PROP_USB_DEVICE, PROP_LAST }; static const gchar * fu_wac_module_fw_type_to_string (guint8 fw_type) { if (fw_type == FU_WAC_MODULE_FW_TYPE_TOUCH) return "touch"; if (fw_type == FU_WAC_MODULE_FW_TYPE_BLUETOOTH) return "bluetooth"; if (fw_type == FU_WAC_MODULE_FW_TYPE_EMR_CORRECTION) return "emr-correction"; if (fw_type == FU_WAC_MODULE_FW_TYPE_BLUETOOTH_HID) return "bluetooth-hid"; return NULL; } static const gchar * fu_wac_module_command_to_string (guint8 command) { if (command == FU_WAC_MODULE_COMMAND_START) return "start"; if (command == FU_WAC_MODULE_COMMAND_DATA) return "data"; if (command == FU_WAC_MODULE_COMMAND_END) return "end"; return NULL; } static const gchar * fu_wac_module_status_to_string (guint8 status) { if (status == FU_WAC_MODULE_STATUS_OK) return "ok"; if (status == FU_WAC_MODULE_STATUS_BUSY) return "busy"; if (status == FU_WAC_MODULE_STATUS_ERR_CRC) return "err-crc"; if (status == FU_WAC_MODULE_STATUS_ERR_CMD) return "err-cmd"; if (status == FU_WAC_MODULE_STATUS_ERR_HW_ACCESS_FAIL) return "err-hw-access-fail"; if (status == FU_WAC_MODULE_STATUS_ERR_FLASH_NO_SUPPORT) return "err-flash-no-support"; if (status == FU_WAC_MODULE_STATUS_ERR_MODE_WRONG) return "err-mode-wrong"; if (status == FU_WAC_MODULE_STATUS_ERR_MPU_NO_SUPPORT) return "err-mpu-no-support"; if (status == FU_WAC_MODULE_STATUS_ERR_VERSION_NO_SUPPORT) return "erro-version-no-support"; if (status == FU_WAC_MODULE_STATUS_ERR_ERASE) return "err-erase"; if (status == FU_WAC_MODULE_STATUS_ERR_WRITE) return "err-write"; if (status == FU_WAC_MODULE_STATUS_ERR_EXIT) return "err-exit"; if (status == FU_WAC_MODULE_STATUS_ERR) return "err-err"; if (status == FU_WAC_MODULE_STATUS_ERR_INVALID_OP) return "err-invalid-op"; if (status == FU_WAC_MODULE_STATUS_ERR_WRONG_IMAGE) return "err-wrong-image"; return NULL; } static void fu_wac_module_to_string (FuDevice *device, GString *str) { FuWacModule *self = FU_WAC_MODULE (device); FuWacModulePrivate *priv = GET_PRIVATE (self); g_string_append (str, " FuWacSubModule:\n"); g_string_append_printf (str, " fw-type:\t\t%s\n", fu_wac_module_fw_type_to_string (priv->fw_type)); g_string_append_printf (str, " status:\t\t%s\n", fu_wac_module_status_to_string (priv->status)); g_string_append_printf (str, " command:\t\t%s\n", fu_wac_module_command_to_string (priv->command)); } static gboolean fu_wac_module_refresh (FuWacModule *self, GError **error) { FuWacDevice *parent_device = FU_WAC_DEVICE (fu_device_get_parent (FU_DEVICE (self))); FuWacModulePrivate *priv = GET_PRIVATE (self); guint8 buf[] = { [0] = FU_WAC_REPORT_ID_MODULE, [1 ... FU_WAC_PACKET_LEN - 1] = 0xff }; /* get from hardware */ if (!fu_wac_device_get_feature_report (parent_device, buf, sizeof(buf), FU_WAC_DEVICE_FEATURE_FLAG_ALLOW_TRUNC | FU_WAC_DEVICE_FEATURE_FLAG_NO_DEBUG, error)) { g_prefix_error (error, "failed to refresh status: "); return FALSE; } /* check fw type */ if (priv->fw_type != buf[1]) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Submodule GetFeature fw_Type invalid " "got 0x%02x expected 0x%02x", (guint) buf[1], (guint) priv->fw_type); return FALSE; } /* current phase and status */ if (priv->command != buf[2] || priv->status != buf[3]) { priv->command = buf[2]; priv->status = buf[3]; g_debug ("command: %s, status: %s", fu_wac_module_command_to_string (priv->command), fu_wac_module_status_to_string (priv->status)); } /* success */ return TRUE; } gboolean fu_wac_module_set_feature (FuWacModule *self, guint8 command, GBytes *blob, /* optional */ GError **error) { FuWacDevice *parent_device = FU_WAC_DEVICE (fu_device_get_parent (FU_DEVICE (self))); FuWacModulePrivate *priv = GET_PRIVATE (self); const guint8 *data; gsize len = 0; guint busy_poll_loops = 100; /* 1s */ guint8 buf[] = { [0] = FU_WAC_REPORT_ID_MODULE, [1] = priv->fw_type, [2] = command, [3 ... FU_WAC_PACKET_LEN - 1] = 0xff }; /* verify the size of the blob */ if (blob != NULL) { data = g_bytes_get_data (blob, &len); if (len > 509) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Submodule SetFeature blob larger than " "buffer %" G_GSIZE_FORMAT, len); return FALSE; } } /* build packet */ if (len > 0) memcpy (&buf[3], data, len); /* tell the daemon the current status */ switch (command) { case FU_WAC_MODULE_COMMAND_START: fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); break; case FU_WAC_MODULE_COMMAND_DATA: fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); break; case FU_WAC_MODULE_COMMAND_END: fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY); break; default: break; } /* send to hardware */ if (!fu_wac_device_set_feature_report (parent_device, buf, len + 3, FU_WAC_DEVICE_FEATURE_FLAG_ALLOW_TRUNC, error)) { g_prefix_error (error, "failed to set module feature: "); return FALSE; } /* special case StartProgram, as it can take much longer as it is * erasing the blocks (15s) */ if (command == FU_WAC_MODULE_COMMAND_START) busy_poll_loops *= 15; /* wait for hardware */ for (guint i = 0; i < busy_poll_loops; i++) { if (!fu_wac_module_refresh (self, error)) return FALSE; if (priv->status == FU_WAC_MODULE_STATUS_BUSY) { g_usleep (10000); /* 10ms */ continue; } if (priv->status == FU_WAC_MODULE_STATUS_OK) break; g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to SetFeature: %s", fu_wac_module_status_to_string (priv->status)); return FALSE; } /* too many retries */ if (priv->status != FU_WAC_MODULE_STATUS_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Timed out after %u loops with status %s", busy_poll_loops, fu_wac_module_status_to_string (priv->status)); return FALSE; } /* success */ return TRUE; } static void fu_wac_module_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuWacModule *self = FU_WAC_MODULE (object); FuWacModulePrivate *priv = GET_PRIVATE (self); switch (prop_id) { case PROP_FW_TYPE: g_value_set_uint (value, priv->fw_type); break; case PROP_USB_DEVICE: g_value_set_object (value, priv->usb_device); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_wac_module_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuWacModule *self = FU_WAC_MODULE (object); FuWacModulePrivate *priv = GET_PRIVATE (self); switch (prop_id) { case PROP_FW_TYPE: priv->fw_type = g_value_get_uint (value); break; case PROP_USB_DEVICE: g_set_object (&priv->usb_device, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_wac_module_init (FuWacModule *self) { } static void fu_wac_module_constructed (GObject *object) { FuWacModule *self = FU_WAC_MODULE (object); FuWacModulePrivate *priv = GET_PRIVATE (self); g_autofree gchar *devid = NULL; g_autofree gchar *vendor_id = NULL; /* set vendor ID */ vendor_id = g_strdup_printf ("USB:0x%04X", g_usb_device_get_vid (priv->usb_device)); fu_device_set_vendor_id (FU_DEVICE (self), vendor_id); /* set USB physical and logical IDs */ fu_device_set_physical_id (FU_DEVICE (self), g_usb_device_get_platform_id (priv->usb_device)); fu_device_set_logical_id (FU_DEVICE (self), fu_wac_module_fw_type_to_string (priv->fw_type)); /* append the firmware kind to the generated GUID */ devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X-%s", g_usb_device_get_vid (priv->usb_device), g_usb_device_get_pid (priv->usb_device), fu_wac_module_fw_type_to_string (priv->fw_type)); fu_device_add_instance_id (FU_DEVICE (self), devid); G_OBJECT_CLASS (fu_wac_module_parent_class)->constructed (object); } static void fu_wac_module_finalize (GObject *object) { FuWacModule *self = FU_WAC_MODULE (object); FuWacModulePrivate *priv = GET_PRIVATE (self); if (priv->usb_device != NULL) g_object_unref (priv->usb_device); G_OBJECT_CLASS (fu_wac_module_parent_class)->finalize (object); } static void fu_wac_module_class_init (FuWacModuleClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); /* properties */ object_class->get_property = fu_wac_module_get_property; object_class->set_property = fu_wac_module_set_property; pspec = g_param_spec_object ("usb-device", NULL, NULL, G_USB_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_USB_DEVICE, pspec); pspec = g_param_spec_uint ("fw-type", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_FW_TYPE, pspec); object_class->constructed = fu_wac_module_constructed; object_class->finalize = fu_wac_module_finalize; klass_device->to_string = fu_wac_module_to_string; } fwupd-1.2.14/plugins/wacom-usb/fu-wac-module.h000066400000000000000000000015411402665037500211310ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_WAC_MODULE (fu_wac_module_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuWacModule, fu_wac_module, FU, WAC_MODULE, FuDevice) struct _FuWacModuleClass { FuDeviceClass parent_class; }; #define FU_WAC_MODULE_FW_TYPE_TOUCH 0x00 #define FU_WAC_MODULE_FW_TYPE_BLUETOOTH 0x01 #define FU_WAC_MODULE_FW_TYPE_EMR_CORRECTION 0x02 #define FU_WAC_MODULE_FW_TYPE_BLUETOOTH_HID 0x03 #define FU_WAC_MODULE_FW_TYPE_MAIN 0x3f #define FU_WAC_MODULE_COMMAND_START 0x01 #define FU_WAC_MODULE_COMMAND_DATA 0x02 #define FU_WAC_MODULE_COMMAND_END 0x03 gboolean fu_wac_module_set_feature (FuWacModule *self, guint8 command, GBytes *blob, GError **error); G_END_DECLS fwupd-1.2.14/plugins/wacom-usb/meson.build000066400000000000000000000026661402665037500204660ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginWacomUsb"'] install_data(['wacom-usb.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_wacom_usb', fu_hash, sources : [ 'fu-wac-common.c', 'fu-wac-device.c', 'fu-wac-firmware.c', 'fu-wac-module.c', 'fu-wac-module-bluetooth.c', 'fu-wac-module-touch.c', 'fu-plugin-wacom-usb.c', ], include_directories : [ include_directories('../..'), include_directories('../dfu'), include_directories('../../src'), include_directories('../../libfwupd'), ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ libfwupdprivate, dfu, ], ) if get_option('tests') testdatadir = join_paths(meson.current_source_dir(), 'tests') cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'wacom-usb-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-wac-common.c', 'fu-wac-firmware.c', ], include_directories : [ include_directories('..'), include_directories('../dfu'), include_directories('../..'), include_directories('../../libfwupd'), include_directories('../../src'), ], dependencies : [ libxmlb, gio, gusb, gudev, libm, ], link_with : [ dfu, libfwupdprivate, ], c_args : cargs ) test('wacom-usb-self-test', e) endif fwupd-1.2.14/plugins/wacom-usb/wacom-usb.quirk000066400000000000000000000015511402665037500212660ustar00rootroot00000000000000 # Intuos Pro medium (2nd-gen USB) [PTH-660] [DeviceInstanceId=USB\VID_056A&PID_0357] Plugin = wacom_usb Flags = use-runtime-version # Intuos Pro large (2nd-gen USB) [PTH-860] [DeviceInstanceId=USB\VID_056A&PID_0358] Plugin = wacom_usb Flags = use-runtime-version # Intuos S 3rd-gen (USB) [CTL-4100] [DeviceInstanceId=USB\VID_056A&PID_0374] Plugin = wacom_usb Flags = use-runtime-version # Intuos M 3rd-gen (USB) [NA] [DeviceInstanceId=USB\VID_056A&PID_0375] Plugin = wacom_usb Flags = use-runtime-version # Intuos BT S 3rd-gen (USB) [CTL-4100WL] [DeviceInstanceId=USB\VID_056A&PID_0376] Plugin = wacom_usb Flags = use-runtime-version # Intuos BT M 3rd-gen (USB) [CTL-6100WL] [DeviceInstanceId=USB\VID_056A&PID_0378] Plugin = wacom_usb Flags = use-runtime-version # Intuos Pro Small (2nd-gen USB) [PTH-460] [DeviceInstanceId=USB\VID_056A&PID_0392] Plugin = wacom_usb fwupd-1.2.14/po/000077500000000000000000000000001402665037500133525ustar00rootroot00000000000000fwupd-1.2.14/po/.gitignore000066400000000000000000000000061402665037500153360ustar00rootroot00000000000000*.pot fwupd-1.2.14/po/LINGUAS000066400000000000000000000001641402665037500144000ustar00rootroot00000000000000af ast ca cs da de en_GB eo eu fi fr fur he hi hr hu id it kk ko ky lt nl oc pl pt_BR ru sk sr sv tr uk zh_CN zh_TW fwupd-1.2.14/po/POTFILES.in000066400000000000000000000005251402665037500151310ustar00rootroot00000000000000data/remotes.d/lvfs.metainfo.xml data/remotes.d/lvfs-testing.metainfo.xml policy/org.freedesktop.fwupd.policy.in plugins/dfu/dfu-tool.c plugins/uefi/fu-plugin-uefi.c plugins/uefi/fu-uefi-tool.c src/fu-agent.c src/fu-config.c src/fu-debug.c src/fu-main.c src/fu-offline.c src/fu-tool.c src/fu-progressbar.c src/fu-util.c src/fu-util-common.c fwupd-1.2.14/po/POTFILES.skip000066400000000000000000000000501402665037500154620ustar00rootroot00000000000000data/org.freedesktop.fwupd.metainfo.xml fwupd-1.2.14/po/af.po000066400000000000000000000236451402665037500143120ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # F Wolff , 2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Afrikaans (http://www.transifex.com/freedesktop/fwupd/language/af/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: af\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minuut oor" msgstr[1] "%.0f minute oor" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dag" msgstr[1] "%u dae" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u uur" msgstr[1] "%u ure" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuut" msgstr[1] "%u minute" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekonde" msgstr[1] "%u sekondes" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Bygevoeg" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Ouderdom" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "’n Bywerking vereis dat die stelsel herbegin om te voltooi." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Antwoord ja op alle vrae" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Attribute" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Kanselleer" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Gekanselleer" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Verander" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolesom" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "Vlokkie-ID" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Kies 'n toestel:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Kies 'n vrystelling:" #. TRANSLATORS: error message msgid "Command not found" msgstr "Opdrag nie gevind nie" msgid "DFU" msgstr "DFU" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Pak tans uit…" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Beskrywing" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Toestel bygevoeg:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Toestel verander:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Toestel verwyder:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Toestelle wat suksesvol bygewerk is:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Toestelle wat nie korrek bygewerk is nie:" #. success msgid "Done!" msgstr "Klaar!" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Gradeer tans %s af vanaf %s na %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Gradeer tans %s af…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Laai tans af…" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Geaktiveer" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Vee tans uit…" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "Kon nie aflaai nie a.g.v. bedienerlimiet" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Kry tans lêer" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Kry tans metadata" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Kry tans handtekening" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Lêernaam" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Lêernaamhandtekening" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Gevind" msgid "GUID" msgstr "GUID" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Ledig…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installeer tans op %s…" msgid "Keyring" msgstr "Sleutelring" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Minder as een minuut oor" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Laai tans…" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadata-URI" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Metadata-URI-handtekening" msgid "Mode" msgstr "Modus" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Naam" msgid "OK" msgstr "Goed" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Wagwoord" msgid "Permission denied" msgstr "Toestemming gewyer" msgid "Print the version number" msgstr "Druk die weergawenommer" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioriteit" msgid "Proceed with upload?" msgstr "Gaan voort met oplaai?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokol" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Lees tans…" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Streek" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Herinstalleer tans %s met %s… " #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Verwyder" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Internetverbinding is nodig" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Herbegin nou?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Herbegin tans toestel…" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Skeduleer die installasie vir die volgende herbegin indien moontlik" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Skeduleer tans…" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Reeksnommer" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Wys toestelle wat nie bygewerk kan word nie" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Toestand" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Status" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Opsomming" msgid "Target" msgstr "Teiken" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Titel" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Oordraggrootte" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipe" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Onbekend" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Bywerking se kontrolesom" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Bywerking se beskrywing" #. TRANSLATORS: section header for the amount #. * of time it takes to install the update msgid "Update Duration" msgstr "Bywerking se duur" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Bywerking se ligging" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Bywerking se naam" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Bywerking se opsomming" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Bywerking se weergawe" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Werk nou by?" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Werk tans %s by vanaf %s na %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Werk tans %s by…" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Oplaaiboodskap:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Laai verslag nou op?" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Gebruikernaam" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifieer tans…" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Weergawe" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Wag tans…" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Skryf tans…" fwupd-1.2.14/po/ast.po000066400000000000000000000036711402665037500145100ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # enolp , 2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Asturian (http://www.transifex.com/freedesktop/fwupd/language/ast/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ast\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Amestóse" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Encaboxóse" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Camudóse" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Cifráu" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Alcontróse" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" msgid "Mode" msgstr "Mou" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Nome" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protocolu" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Rexón" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Desanicióse" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Serial" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Estáu" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Estáu" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Tamañu de tresferencia" fwupd-1.2.14/po/ca.po000066400000000000000000001221441402665037500143010ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Antoni Bella Pérez , 2017-2019 # Robert Antoni Buj Gelonch , 2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Catalan (http://www.transifex.com/freedesktop/fwupd/language/ca/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ca\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Manca %.0f minut" msgstr[1] "Manquen %.0f minuts" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Actualització ME del consumidor %s" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Actualització del controlador %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Actualització ME corporativa de %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Actualizació del dispositiu %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Actualització del controlador incorporat %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Actualització ME de %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Actualizació del sistema %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Actualització de %s" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s té actualitzacions de microprogramari:" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dia" msgstr[1] "%u dies" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hora" msgstr[1] "%u hores" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minut" msgstr[1] "%u minuts" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u segon" msgstr[1] "%u segons" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Activa els dispositius" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Activa els dispositius pendents" msgid "Activate the new firmware on the device" msgstr "Activa el microprogramari nou al dispositiu" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Activació de l'actualització del microprogramari" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Activa l’actualització del microprogramari per a" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "S'ha afegit" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Antiguitat" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Accepteu i habiliteu el remot?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Àlies per a %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Permet tornar a la versió anterior del microprogramari" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Permet tornar a instal·lar les versions de microprogramari existents" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Una actualització requereix un reinici per a completar-se." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "Una actualització requereix que s'aturi el sistema per a finalitzar." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Respon sí a totes les preguntes" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Aplica un pedaç binari" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Aplica les actualizacions de microprogramari" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Microprogramari aprovat:" msgstr[1] "Microprogramari aprovat:" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "Connecta un dispositiu amb capacitat DFU en temps real" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Ajunta al mode microprogramari" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Atributs" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "S'està autenticant..." #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Es requereix autenticació per a desactualitzar el microprogramari en un dispositiu extraible" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Es requereix autenticació per a desactualitzar el microprogramari en aquesta màquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Es necessita l'autenticació per a modificar un remot configurat emprat per a les actualitzacions del microprogramari" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Es requereix autenticació per a modificar la configuració del dimoni" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Es requereix autenticació per a establir la llista de microprogramari aprovat" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Es requereix autenticació per a signar les dades emprant el certificat del client" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Es requereix autenticació per a canviar a la nova versió del micropogramari" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Es requereix autenticació per a desbloquejar un dispositiu" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Es requereix autenticació per actualitzar el microprogramari en un dispositiu extraible" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Es requereix autenticació per actualitzar el microprogramari en aquesta màquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Es requereix autenticació per actualitzar les sumes de verificació emmagatzemades pels dispositius" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Construeix el microprogramari usant un entorn de proves" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancel·la" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "S'ha cancel·lat" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "S'ha canviat" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Suma de comprovació" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "Id. xip" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Trieu un dispositiu:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Trieu un alliberament:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Xifra" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Neteja qualsevol actualització programyada per a ser actualitzada sense connexió" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Esborra els resultats de l'última actualització" #. TRANSLATORS: error message msgid "Command not found" msgstr "No s'ha trobat cap ordre" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Converteix el microprogramari al format DFU" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Crea un pedaç binari emprant dos fitxers" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Utilitat DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opcions per a la depuració" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "S'està descomprimint..." #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Desencripta les dades del microprogramari" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Descripció" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Desconecta el dispositiu amb capacitat DFU actualment connectat" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Separa del mode carregador d'arrencada" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID del dispositiu" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "S'ha afegit el dispositiu:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "S'ha canviat el dispositiu:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "S'ha eliminat el dispositiu:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Els dispositius que s'han actualitzat correctament:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Els dispositius que no s'han actualitzat correctament:" msgid "Disabled fwupdate debugging" msgstr "La depuració del «fwupdate» està inhabilitada" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Inhabilita un remot indicat" #. TRANSLATORS: command line option msgid "Display version" msgstr "Mostra la versió" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "No comprovar si hi ha metadades antigues" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "No realitzis el reinici després de l'actualització" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "No comprovar si hi ha un historial sense informar" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "No escriure a la base de dades de l'historial" #. success msgid "Done!" msgstr "Fet!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Desactualitza el microprogramari en un dispositiu" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "S'està desactualitzant %s des de %s a %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "S'està desactualitzant %s…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "S'està descarregant..." #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Bolca les dades al SMBIOS des d'un fitxer" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Bolca els detalls sobre un fitxer de microprogramari" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Bolca la informació sobre un pedaç binari a la pantalla" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "L'ESP especificat no era vàlid" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Habilita el suport per a l'actualització del microprogramari sobre sistemes compatibles" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Habilito aquest remot?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Habilitat" msgid "Enabled fwupdate debugging" msgstr "La depuració del «fwupdate» està habilitada" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Habilita un remot indicat" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Si habiliteu aquesta funcionalitat, ho fareu sota el vostre propi risc, el qual significa que haureu de posar-vos en contacte amb el fabricant original de l'equip quant a qualsevol problema causat per aquestes actualitzacions. A $OS_RELEASE:BUG_REPORT_URL$, només s'han de presentar els problemes amb el procés d'actualització." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Si habiliteu aquest remot, ho fareu sota el vostre propi risc." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Encripta les dades del microprogramari" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Esborra tot l'historial de les actualitzacions de microprogramari" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "S'està esborrant..." #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Surt després d'un petit retard" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Sur una vegada s'hagi carregat el motor" #. TRANSLATORS: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "Ha fallat en connectar amb el dimoni" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "La baixada ha fallat a causa del límit del servidor" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Ha fallat en obtenir els dispositius pendents" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Ha fallat en instal·lar l’actualització del microprogramari" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "No s'han pogut carregar les peculiaritats" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Ha fallat en analitzar els arguments" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Ha fallat en tornar a arrencar" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Ha fallat en establir el mode de presentació" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "S'està obtenint el fitxer" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "S'està obtenint el microprogramari" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "S'estan obtenint les metadades" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "S'està obtenint la signatura" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Nom del fitxer" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Signatura del nom del fitxer" #. TRANSLATORS: program name msgid "Firmware Agent" msgstr "Agent de microprogramari" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI base del microprogramari" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Servei de D-Bus per a l'actualització de microprogramari" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Dimoni per a l'actualització de microprogramari" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Utilitat per al microprogramari" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Les metadades del microprogramari no s'han actualitzat durant %u dia i podria ser que no estiguin actualitzades." msgstr[1] "Les metadades del microprogramari no s'han actualitzat durant %u dies i podria ser que no estiguin actualitzades." msgid "Firmware updates are not supported on this machine." msgstr "Les actualitzacions de microprogramari no estan admeses en aquesta màquina." msgid "Firmware updates are supported on this machine." msgstr "Les actualitzacions de microprogramari estan admeses en aquesta màquina." #. TRANSLATORS: section header for firmware flags msgid "Flags" msgstr "Etiquetes" msgid "Force the action ignoring all warnings" msgstr "Força l'acció ignorant tots els avisos" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "S'ha trobat" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "Obtén tots els dispositius segons la topologia del sistema" #. TRANSLATORS: command description msgid "Get all devices and possible releases" msgstr "Obtén tots els dispositius i possibles llançaments" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Obté tots els dispositius que admeten actualitzacions de microprogramari" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Obtén tots els connectors habilitats registrats amb el sistema" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Obté la informació sobre un fitxer de microprogramari" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Obtén els remots configurats" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Obté el resultat d'aplicar la funció criptogràfica de resum sobre el microprogramari bolcat" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware." msgstr "Obtén la llista del microprogramari aprovat." #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Obté la llista d'actualitzacions per al maquinari connectat" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Obté els alliberaments per a un dispositiu" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Obté els resultats de l'última actualització" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Està ociós..." #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Instal·la un blob de microprogramari en un dispositiu" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Instal·la un fitxer de microprogramari en aquest maquinari" msgid "Install old version of system firmware" msgstr "Instal·la la versió antiga del microprogramari per al sistema" msgid "Install signed device firmware" msgstr "Instal·la microprogramari signat per al dispositiu" msgid "Install signed system firmware" msgstr "Instal·la microprogramari signat per al sistema" msgid "Install unsigned device firmware" msgstr "Instal·la microprogramari sense signar per al dispositiu" msgid "Install unsigned system firmware" msgstr "Instal·la microprogramari sense signar per al sistema" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Instal·lació del microprogramari..." #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "S'està instal·lant l'actualització de microprogramari..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "S'està intal·lant a %s…" msgid "Keyring" msgstr " Anell de claus" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Manca menys d'un minut" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Servei de microprogramari del proveïdor Linux (microprogramari estable)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Servei de microprogramari del proveïdor Linux (microprogramari en proves)" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "Llista els dispositius amb capacitat DFU actualment connectats" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Llista les actualitzacions de microprogramari compatibles" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "S'està carregant..." #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Connectors especíificats manualment a la llista blanca" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Fusiona múltiples fitxers de microprogramari en un de sol" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI de les metadades" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Signatura de l'URI de les metadades" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Les metadades es poden obtenir des del servei de microprogramari del proveïdor Linux." #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "El dimoni i el client no coincideixin, useu %s en el seu lloc" msgid "Mode" msgstr "Mode" #. TRANSLATORS: sets something in daemon.conf msgid "Modifies a daemon configuration value." msgstr "Modifica un valor de la configuració del dimoni." #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifica un remot indicat" msgid "Modify a configured remote" msgstr "Modifica un remot configurat" msgid "Modify daemon configuration" msgstr "Modifica la configuració del dimoni" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitora el dimoni pels esdeveniments" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Nom" msgid "No action specified!" msgstr "No s'ha especificat cap acció!" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "No s'ha detectat cap maquinari amb capacitat per a l'actualització del microprogramari" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "No s'ha trobat cap connector" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Actualment, no hi ha cap remot habilitat, de manera que no hi ha metadades disponibles." #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "No s’han aplicat les actualitzacions" msgid "OK" msgstr "D'acord" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Passa per alt els avisos del connector" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Preferència sobre el camí ESP predeterminat" #. TRANSLATORS: command line option msgid "Override warnings and force the action" msgstr "Anul·la els avisos i força l'acció" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Contrasenya" msgid "Payload" msgstr "Carrega útil" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentatge completat" msgid "Permission denied" msgstr "Permís denegat" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Introduïu un número del 0 al %u: " msgid "Print the version number" msgstr "Imprimeix el número de versió" msgid "Print verbose debug statements" msgstr "Imprimeix les sentències detallades de la depuració" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritat" msgid "Proceed with upload?" msgstr "Continuo amb la pujada?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protocol" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Consulta el suport per a l'actualització del microprogramari" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Peculiaritats" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Llegeix el microprogramari des del dispositiu a un fitxer" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Llegeix el microprogramari des d'una partició a un fitxer" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "S'està llegint..." #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "S'està tornant a arencar..." #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Refresca les metadades des del servidor remot" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Regió" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "S'està reinstal·lant %s amb %s... " #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Remot" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID remot" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "S'ha eliminat" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Substitueix les dades en un fitxer de microprogramari existent" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI de l'informe" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Requereix connexió a Internet" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "Restableix un dispositiu DFU" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Reinicio ara?" #. TRANSLATORS: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Reinicio el dimoni per a fer efectiu el canvi?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "S'està reiniciant el dispositiu..." #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Retorna tots els ID del maquinari de la màquina" #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Executa la rutina de neteja de la composició del connector en usar install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Executa la rutina de preparació de la composició del connector en usar install-blob" msgid "Runtime" msgstr "Temps d'execució" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "Desa l'estat del dispositiu en un fitxer JSON entre les execucions" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Planifica la instal·lació per al següent reinici quan sigui posible" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Planificació..." #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Sèrie" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Estableix el nom alternatiu al fitxer del microprogramari" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Estableix el número alternatiu al fitxer del microprogramari" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Estableix l'adreça de l'element al fitxer del microprogramari" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Estableix l'ID del producte al fitxer del microprogramari" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Estableix la versió de llançament al fitxer del microprogramari" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Estableix l'indicador de depuració durant l'actualització" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Estableix la mida del microprogramari per a l'objectiu" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Estableix l'ID del proveïdor al fitxer del microprogramari" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Estableix les metadades en un fitxer de microprogramari" msgid "Sets the list of approved firmware" msgstr "Estableix la llista de microprogramari aprovat" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware." msgstr "Estableix la llista del microprogramari aprovat." #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Comparteix l'historial de microprogramari amb els desenvolupadors" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Mostra les versions del client i el dimoni" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Mostra informació detallada del dimoni per a un domini concret" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Mostra la informació de depuració per a tots els dominis" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostra les opcions per a la depuració" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Mostra els dispositius que no són actualitzables" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mostra la informació de depuració addicional" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Mostra l'historial de les actualitzacions de microprogramari" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Mostra la informació detallada del connector" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Mostra el registre de depuració del darrer intent d'actualització" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Mostra la informació sobre l'estat de l'actualització del microprogramari" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Aturar-lo ara?" msgid "Sign data using the client certificate" msgstr "Signa les dades emprant el certificat del client" msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Signa les dades emprant el certificat del client" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Signa les dades pujades amb el certificat del client" msgid "Signature" msgstr "Signatura" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Especifiqueu el proveïdor/ID del producte del dispositiu DFU" msgid "Specify the number of bytes per USB transfer" msgstr "Especifiqueu el nombre de bytes per a la transferència USB" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Estat" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Estat" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Resum" msgid "Target" msgstr "Objectiu" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "El LVFS és un servei gratuït que funciona com una entitat legal independent i no té cap vincle amb la $OS_RELEASE:NAME$. És possible que el vostre distribuïdor no hagi verificat cap de les actualitzacions del microprogramari per a la compatibilitat amb el vostre sistema o els dispositius connectats. Tot el microprogramari només és proporcionat pel fabricant original dels equips." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "No hi ha cap microprogramari aprovat." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Aquest programa només pot funcionar correctament com a «root»" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Aquest remot conté el microprogramari que no està embargat, però encara l'ha provat el proveïdor del maquinari. Haureu d'assegurar-vos que teniu una manera de baixar manualment el microprogramari si l'actualització del microprogramari no funciona." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Aquesta eina només pot ser usada per l'usuari «root»." #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Títol" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Mida a transferir" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipus" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilitat per al microprogramari UEFI" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Desconegut" msgid "Unlock the device to allow access" msgstr "Desbloqueja el dispositiu per a permetre l'accés" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Desbloqueja el dispositiu per accedir al microprogramari" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "No estableixis l'indicador de depuració durant l'actualització" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Versió %s no admesa del dimoni, la versió del client és %s" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Suma de verificació de l'actualització" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Descripció de l'actualització" #. TRANSLATORS: section header for the amount #. * of time it takes to install the update msgid "Update Duration" msgstr "Durada de l'actualització" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Ubicació de l'actualització" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Actualitza el nom" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "Actualitza l'ID remot:" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Actualitza el resum" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Versió de l'actualització" #. TRANSLATORS: command description msgid "Update all devices that match local metadata" msgstr "Actualitza tots els dispositius que coincideixin amb les metadades locals" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Que falli en actualitzar és un problema conegut, visiteu aquest URL per obtenir més informació:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Actualitzo ara?" msgid "Update the stored device verification information" msgstr "Actualitza la informació de verificació dels dispositius emmagatzemats" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Actualitza les metadades emmagatzemades amb el contingut de la ROM actual" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Actualitza les metadades emmagatzemades amb el contingut actual" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Actualitza tot el microprogramari a les versions més recents" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "S'està actualitzant %s des de %s a %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "S'està actualitzant %s…" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Missatge de la pujada:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Pujo l'informe ara?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "L'enviament dels informes de microprogramari ajudarà als proveïdors de maquinari a identificar amb rapidesa les actualitzacions fallides i satisfactòries sobre els dispositius reals." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nom d'usuari" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "S'està verificant..." #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Versió:" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "S'està esperant…" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "Vigila els dispositius DFU que han estat connectats en calent" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Mira per a canvis al maquinari" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Escriu el microprogramari des d'un fitxer a dins del dispositiu" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Escriu el microprogramari des d'un fitxer a dins d'una partició" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "S'està escrivint..." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "És possible que el vostre distribuïdor no hagi verificat cap de les actualitzacions del microprogramari per a la compatibilitat amb el vostre sistema o els dispositius connectats." fwupd-1.2.14/po/cs.po000066400000000000000000000737261402665037500143360ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Ascii Wolf , 2017 # Ascii Wolf , 2017 # Marek Černocký , 2016,2018 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Czech (http://www.transifex.com/freedesktop/fwupd/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: cs\n" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Zbývá %.0f minuta" msgstr[1] "Zbývají %.0f minuty" msgstr[2] "Zbývá %.0f minut" msgstr[3] "Zbývá %.0f minuty" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s má aktualizace firmwaru:" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Přidáno" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Stáří" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Odsouhlasit a povolit vzdálený zdroj?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias pro %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Povolit přechod na nižší verze firmwaru" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Povolit reinstalaci stávající verze firmwaru" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Některá z aktualizací vyžaduje pro dokončení restart." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Na všechny dotazy odpovědět ano" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Použít binární záplatu" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Použít aktualizace firmwaru" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "Napojit zařízení podporující DFU zpět do provozního režimu" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Napojit do režimu firmwaru" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Vlastnosti" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Autentizuje se…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "K přechodu na nižší verzi firmwaru na výměnném zařízení je vyžadováno ověření" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "K přechodu na nižší verzi firmwaru na tomto počítači je vyžadováno ověření" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Ke změně nastaveného vzdáleného zdroje, který se používá pro aktualizace firmwaru, je vyžadováno ověření" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Pro odemknutí zařízení je požadováno ověření" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "K aktualizaci firmwaru na výměnném zařízení je vyžadováno ověření" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "K aktualizaci firmwaru na tomto počítači je vyžadováno ověření" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "K aktualizaci uložených kontrolních součtů zařízení je vyžadováno ověření" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Sestavit firmware za použití izolovaného prostředí" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Zrušit" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Zrušeno" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Změněno" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolní součet" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "ID čipu" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Vyberte zařízení:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Vyberte verzi:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Šifra" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Smazat vše naplánované pro aktualizaci při odpojení" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Smazat výsledky z poslední aktualizace" #. TRANSLATORS: error message msgid "Command not found" msgstr "Příkaz nebyl nalezen" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Převést firmware do formátu DFU" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Vytvořit binární záplatu za použití dvou souborů" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Nástroj pro práci s DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Volby ladění" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Rozbaluje se…" #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Dešifrovat data firmwaru" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Popis" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Odpojit aktuálně napojené zařízení podporující DFU" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Odpojit do režimu zavaděče" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Přidáno zařízení:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Změněno zařízení:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Odebráno zařízení:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Zařízení, která byla úspěšně aktualizována:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Zařízení, která nebyla úspěšně aktualizována:" msgid "Disabled fwupdate debugging" msgstr "Vypnout ladění fwupdate" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Zakázat zadaný vzdálený zdroj" #. TRANSLATORS: command line option msgid "Display version" msgstr "Zobrazit verzi" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Nekontrolovat stáří metadat" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "Nekontrolovat restart po aktualizaci" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Nekontrolovat nenahlášení historie" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Nezapisovat do databáze historie" #. success msgid "Done!" msgstr "Hotovo!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Přejít na nižší verzi firmwaru na zařízení" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Převádí se %s z verze %s na %s…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Stahuje se…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Vypsat data SMBIOS ze souboru" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Vypsat podrobnosti o souboru s firmwarem" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Vypsat na obrazovku informace o binární záplatě" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Určený ESP není platný" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Povolit podporu aktualizace firmwaru na podporovaných systémech" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Povolit tento vzdálený zdroj?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Povoleno" msgid "Enabled fwupdate debugging" msgstr "Zapnout ladění fwupdate" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Povolit zadaný vzdálený zdroj" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Zapnutí této funkcionality je na vaše vlastní riziko, což znamená, že v případě problémů způsobených aktualizací musíte kontaktovat přímo výrobce zařízení. Pouze problémy s aktualizačním procesem jako takovým by měly být hlášeny na $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Povolení tohoto zdroje je na vaše vlastní riziko." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Zašifrovat data firmwaru" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Smazat veškerou historii aktualizací" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Maže se…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Skončit po krátké prodlevě" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Skončit po načtení výkonné části" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "Nezdařilo se stáhnout kvůli omezením serveru" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Selhalo načtení zvláštních požadavků" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Selhalo zpracování argumentů" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Stahuje se soubor" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "Stahuje se firmware" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Stahují se metadata" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Stahuje se podpis" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Název souboru" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Podpis názvu souboru" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Základní URI firmwaru" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Služba D-Bus pro aktualizaci firmwaru" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Démon pro aktualizaci firmwaru" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Nástroj pro práci s firmwarem" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Metadata firmwaru nebyla akualizována již celý den a nelze je aktualizovat." msgstr[1] "Metadata firmwaru nebyla akualizována již %u dny a nelze je aktualizovat." msgstr[2] "Metadata firmwaru nebyla akualizována již %u dní a nelze je aktualizovat." msgstr[3] "Metadata firmwaru nebyla akualizována již %u dne a nelze je aktualizovat." msgid "Firmware updates are not supported on this machine." msgstr "Aktualizace firmwaru nejsou na tomto počítači podporované." msgid "Firmware updates are supported on this machine." msgstr "Aktualizace firmwaru jsou na tomto počítači podporované." #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Nalezeno" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "Zjistit všechna zařízení podle topologie systému" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Zjistit všechna zařízení podporující aktualizaci firmwaru" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Zjistit všechny povolené zásuvné moduly registrované v systému" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Zjistit podrobnosti o souboru s firmwarem" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Vypsat nastavené vzdálené zdroje" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Vypsat kryptografický otisk vypsaného firmwaru" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Vypsat seznam aktualizací pro připojený hardware" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Vypsat vydání pro zařízení" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Vypsat výsledky z poslední aktualizace" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Nečinný…" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Nainstalovat na zařízení binární soubor s firmwarem" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Nainstalovat soubor s firmwarem na tento hardware" msgid "Install old version of system firmware" msgstr "Instalace starší verze systémového firmwaru" msgid "Install signed device firmware" msgstr "Instalace podepsaného firmwaru zařízení" msgid "Install signed system firmware" msgstr "Instalace podepsaného systémového firmwaru" msgid "Install unsigned device firmware" msgstr "Instalace nepodepsaného firmwaru zařízení" msgid "Install unsigned system firmware" msgstr "Instalace nepodepsaného systémového firmwaru" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Instaluje se aktualizace firmwaru…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Instaluje se na zařízení %s…" msgid "Keyring" msgstr "Klíčenka" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Zbývá méně než jedna minuta" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stabilní firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (testovací firmware)" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "Vypsat právě napojená zařízení podporující DFU" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Vypsat podporované aktualizace firmwarů" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Načítá se…" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Ručně povolit konkrétní zásuvné moduly" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Sloučit více firmwarů do jednoho" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI metadat" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Podpis URI metadat" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadata lze získat ze služby Linux Vendor Firmware." msgid "Mode" msgstr "Režim" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Změnit zadaný vzdálený zdroj" msgid "Modify a configured remote" msgstr "Upravit nastavený vzdálený zdroj" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Sledovat události démona" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Název" msgid "No action specified!" msgstr "Není určena žádné akce!" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nebylo nalezeno žádné zařízení schopné aktualizace firmwaru" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nebyl nalezen žádný zásuvný modul" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Zrovna nejsou povolené žádné vzdálené zdroje, takže nejsou k dispozici žádná metadata." msgid "OK" msgstr "V pořádku" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Potlačit varování zásuvného modulu" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Přepsat výchozí cestu ESP" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Heslo" msgid "Payload" msgstr "Obsah" msgid "Permission denied" msgstr "Byl odepřen přístup" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Zadejte prosím číslo od 0 do %u:" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Priorita" msgid "Proceed with upload?" msgstr "Pokračovat v nahrávání?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokol" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Dotázat se na podporu aktualizace firmwaru" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Zvláštní požadavky" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Přečíst firmware ze zařízení do souboru" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Přečíst firmware z jednoho oddílu do souboru" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Čte se…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Aktualizovat metadata ze vzdáleného serveru" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Oblast" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Přeinstalovává se %s na %s…" #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Vzdálený zdroj" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID vzdáleného zdroje" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Odebráno" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Nahradit data ve stávajícím souboru s firmwarem" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI hlášení" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Vyžaduje připojení k Internetu" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "Resetovat zařízení podporující DFU" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Restartovat nyní?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Zařízení se restartuje…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Vrátit všechna ID hardwaru počítače" msgid "Runtime" msgstr "provozní" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Pokud je to možné, naplánovat instalaci na příští restart" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Plánuje se…" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Sériové číslo" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Nastavit alternativní název v souboru s firmwarem" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Nastavit alternativní číslo v souboru s firmwarem" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Nastavit adresu prvku na firmwaru zařízení" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Nastavit ID produktu v souboru s firmwarem" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Nastavit verzi vydání v souboru s firmwarem" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Během aktualizace nastavit příznak ladění" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Nastavit velikost firmwaru pro cíl" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Nastavit ID výrobce v souboru s firmwarem" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Nastavit metadata v souboru s firmwarem" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Sdílet historii firmwaru s vývojáři" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Zobrazit verzi klienta a démona" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Zobrazit volby ladění" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Zobrazit zařízení, která nelze aktualizovat" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Zobrazovat doplňující informace pro ladění" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Zobrazit historii aktualizací firmwaru" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Zobrazit podrobné informace o zásuvném modulu" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Zobrazit ladicí záznam z posledního pokusu o aktualizaci" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Zobrazit informace o stavu aktualizace firmwaru" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Stav" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Stav" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Souhrn" msgid "Target" msgstr "Cíl" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS je svobodná služba, které funguje jako nezávislý právní subjekt a nemá žádné vazby na systém $OS_RELEASE:NAME$. Váš distributor nemusí některé z aktualizací firmwaru schválit kvůli kompatibilitě s vaším systémem nebo připojenými zařízeními. Veškerý firmware je poskytován pouze přímo výrobci daných zařízení." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Tento program může správně fungovat jen pod uživatelem root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Tento zdroj obsahuje firmware, který není zakázaný, ale zatím je u výrobce stále ve stádiu testování. Měli byste se ujistit, že znáte způsob, jak se vrátit k předchozí verzi firmwaru, kdyby došlo k selhání." #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Název" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Přenášená velikost" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Typ" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Nástroj pro práci s firmwarem UEFI" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Neznámý" msgid "Unlock the device to allow access" msgstr "Odemknutí zařízení pro umožnění přístupu" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Odemknout zařízení pro přístup k firmwaru" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Během aktualizace zrušit příznak ladění" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Kontrolní součet aktualizace" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Popis aktualizace" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Umístění aktualizace" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Název aktualizace" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "ID vzdáleného zdroje aktualizace" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Souhrn aktualizace" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Verze aktualizace" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "O selhání aktualizace se ví, více informací najdete na této adrese:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Aktualizovat nyní?" msgid "Update the stored device verification information" msgstr "Aktualizace uložené informace o ověření zařízení" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Aktualizovat uložená metadata pomocí aktuálního obsahu ROM" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Aktualizovat všechen firmware na nejnovější dostupné verze" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Aktualizuje se %s z verze %s na %s…" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Nahraná zpráva:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Nahrát hlášení nyní?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Když nahrajete hlášení o firmwaru, pomůžete tím výrobcům hardwaru rychle rozpoznat nezdařené a úspěšné aktualizace na reálných zařízeních." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Uživatelské jméno" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Ověřuje se…" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Verze" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Čeká se…" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "Sledovat připojení zařízení podporujících DFU" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Sledovat změny hardwaru" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Zapsat firmware ze souboru do zařízení" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Zapsat firmware ze souboru do jednoho oddílu" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Zapisuje se…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Váš distributor nemusí schválit některé aktualizace firmwaru kvůli kompatibilitě s vaším systémem nebo připojenými zařízeními." fwupd-1.2.14/po/da.po000066400000000000000000001143741402665037500143100ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # scootergrisen, 2019 # scootergrisen, 2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Danish (http://www.transifex.com/freedesktop/fwupd/language/da/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: da\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minut tilbage" msgstr[1] "%.0f minutter tilbage" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s ME-opdatering for forbruger" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Kontrolleropdatering for %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s ME-opdatering for virksomhed" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Enhedsopdatering for %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Controlleropdatering for indlejret %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "ME-opdatering for %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Systemopdatering for %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Opdatering for %s" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s har firmwareopdateringer:" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dag" msgstr[1] "%u dage" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u time" msgstr[1] "%u timer" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minut" msgstr[1] "%u minutter" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekund" msgstr[1] "%u sekunder" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktivér enheder" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktiverer afventende enheder" msgid "Activate the new firmware on the device" msgstr "Aktivér den nye firmware på enheden" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Aktiverer firmwareopdatering" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Aktiverer firmwareopdatering for" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Tilføjet" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Alder" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Accepter og aktivér fjernen?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias til %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Tillad nedgradering af firmwareversioner" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Tillad geninstallering af eksisterende firmwareversioner" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "For at fuldføre en opdatering skal systemet genstartes." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "For at fuldføre en opdatering skal systemet lukkes ned." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Svar ja til alle spørgsmål" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Anvend en binær patch" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Anvend firmwareopdateringer" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Godkendt firmware:" msgstr[1] "Godkendt firmware:" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "Tilkobl enheder som formår DFU tilbage til runtime" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Tilkobl til firmwaretilstand" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Attributter" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Autentificerer …" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Der kræves autentifikation for at nedgradere firmwaren på en flytbar enhed" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Der kræves autentifikation for at nedgradere firmwaren på maskinen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Der kræves autentifikation for at redigere en konfigureret fjern som bruges til firmwareopdateringer" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Der kræves autentifikation for at redigere dæmonkonfiguration" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Der kræves autentifikation for at indstille listen over godkendt firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Der kræves autentifikation for at underskrive data med klientcertifikatet" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Der kræves autentifikation for at skifte til den nye firmwareversion" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Der kræves autentifikation for at låse enhed op" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Der kræves autentifikation for at opdatere firmwaren på en flytbar enhed" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Der kræves autentifikation for at opdatere firmwaren på maskinen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Der kræves autentifikation for at opdatere de gemte checksumme for enheden" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Byg firmware med en sandkasse" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Annuller" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Annulleret" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Ændret" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "Chip-id" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Vælg en enhed:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Vælg en udgivelse:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Krypteringsalgoritme" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Rydder opdateringer som er planlagt til at blive opdateret offline" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Rydder resultaterne fra den sidste opdatering" #. TRANSLATORS: error message msgid "Command not found" msgstr "Kommandoen blev ikke fundet" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Konverter firmware til DFU-format" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Opret en binær patch ved brug af to filer" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU-redskab" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Fejlsøgningsindstillinger" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Udpakker …" #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Afkryptér firmwaredata" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Beskrivelse" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Frakobl tilkoblede enheder som formår DFU" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Frakobl til opstartsindlæsertilstand" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Enheds-id" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Enhed tilføjet:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Enhed ændret:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Enhed fjernet:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Enheder som det lykkedes at opdatere:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Enheder som ikke blev opdateret korrekt:" msgid "Disabled fwupdate debugging" msgstr "Deaktivér fejlsøgning af fwupdate" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Deaktiverer en angivet fjern" #. TRANSLATORS: command line option msgid "Display version" msgstr "Vis version" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Tjek ikke efter gammel metadata" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "Tjek ikke om der er genstartet, efter opdatering" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Tjek ikke efter historik som ikke er blevet rapporteret" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Skriv ikke til historikdatabasen" #. success msgid "Done!" msgstr "Færdig!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Nedgraderer firmwaren på en enhed" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Nedgraderer %s fra %s til %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Nedgraderer %s …" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Downloader …" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Dump SMBIOS-data fra en fil" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Dump detaljer om en firmwarefil" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Dump information om en binær patch på skærmen" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Angivet ESP var ikke gyldig" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Aktivér understøttelse af firmwareopdateringer på systemer som understøtter det" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Aktivér fjernen?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Aktiveret" msgid "Enabled fwupdate debugging" msgstr "Aktivér fejlsøgning af fwupdate" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Aktiverer en angivet fjern" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Aktivering af funktionen sker på egen risiko. Det betydet at du skal kontakte din oprindelige udstyrsproducent vedrørende eventuelle problemer forårsaget af opdateringerne. Det er kun problemer med selv opdateringsprocessen som skal indsende på $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Aktivering af fjernen sker på egen risiko." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Kryptér firmwaredata" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Slet al historik over firmwareopdateringer" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Sletter …" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Afslut efter en lille forsinkelse" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Afslut efter motoren er indlæst" #. TRANSLATORS: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "Kunne ikke oprette forbindelse til dæmonen" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "Kunne ikke downloade pga. begrænsning på server" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Kunne ikke hente afventende enheder" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Kunne ikke installere firmwareopdateringen" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Kunne ikke indlæse quirks" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Kunne ikke fortolke argumenter" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Kunne ikke genstarte" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Kunne ikke indstille splash-tilstand" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Henter fil" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "Henter firmware" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Henter metadata" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Henter underskrift" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Filnavn" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Filnavnets underskrift" #. TRANSLATORS: program name msgid "Firmware Agent" msgstr "Firmwareagent" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Grund-URI for firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "D-Bus-tjeneste for firmwareopdatering" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Firmwareopdateringsdæmon" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmwareredskab" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Firmwaremetadata er ikke blevet opdateret i %u dag og kan være forældet." msgstr[1] "Firmwaremetadata er ikke blevet opdateret i %u dage og kan være forældet." msgid "Firmware updates are not supported on this machine." msgstr "Maskinen understøtter ikke firmwareopdateringer." msgid "Firmware updates are supported on this machine." msgstr "Maskinen understøtter firmwareopdateringer." #. TRANSLATORS: section header for firmware flags msgid "Flags" msgstr "Flag" msgid "Force the action ignoring all warnings" msgstr "Tving handlingen og ignorer alle advarsler" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Fundet" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "Hent alle enheder i henhold til systemets topologi" #. TRANSLATORS: command description msgid "Get all devices and possible releases" msgstr "Hent alle enheder og mulige udgivelser" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Hent alle enheder som understøtter firmwareopdateringer" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Hent alle aktiverede plugins som er registreret med systemet" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Hent detaljer om en firmwarefil" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Henter de konfigurerede fjerne" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Henter den kryptografiske hash fra den dumpede firmware" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware." msgstr "Henter listen over godkendt firmware." #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Henter listen over opdateringer for tilsluttet hardware" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Henter resultaterne fra en enhed" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Henter resultaterne fra den sidste opdatering" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "Id" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Inaktiv …" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Installer en firmwareblob på en enhed" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Installer en firmwarefil på hardwaren" msgid "Install old version of system firmware" msgstr "Installer gammel version af systemfirmware" msgid "Install signed device firmware" msgstr "Installer enhedsfirmware der er underskrevet" msgid "Install signed system firmware" msgstr "Installer systemfirmware der er underskrevet" msgid "Install unsigned device firmware" msgstr "Installer enhedsfirmware der ikke er underskrevet" msgid "Install unsigned system firmware" msgstr "Installer systemfirmware der ikke er underskrevet" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Installerer firmware …" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Installerer firmwareopdateringer …" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installerer på %s …" msgid "Keyring" msgstr "Nøglering" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Mindre end et minut tilbage" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stabilt firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (testning firmware)" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "Vis tilkoblede enheder som formår DFU" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Vis understøttede firmwareopdateringer" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Indlæser …" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Manuel hvidlistning af bestemte plugins" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Sammenlæg flere firmwarefiler i én" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadata-URI" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Underskrift for metadata-URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadata kan hentes fra Linux Vendor Firmware Service." #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Dæmon og klient passer ikke sammen, brug %s i stedet" msgid "Mode" msgstr "Tilstand" #. TRANSLATORS: sets something in daemon.conf msgid "Modifies a daemon configuration value." msgstr "Rediger en værdi i dæmonkonfiguration." #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Redigerer en angivet fjern" msgid "Modify a configured remote" msgstr "Rediger en konfigureret fjern" msgid "Modify daemon configuration" msgstr "Rediger dæmonkonfiguration" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Overvåg dæmonen for hændelser" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Navn" msgid "No action specified!" msgstr "Der er ikke angivet nogen handling!" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Der blev ikke fundet nogen hardware med firmware som kan opdateres" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Der blev ikke fundet nogen plugins" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Der er ikke nogen metadata da der ikke er aktiveret nogen fjerne." #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Der blev ikke anvendt nogen opdateringer" msgid "OK" msgstr "OK" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Tilsidesæt advarsel for plugin" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Tilsidesæt standard-ESP-stien" #. TRANSLATORS: command line option msgid "Override warnings and force the action" msgstr "Tilsidesæt advarsler og gennemtving handlingen" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Adgangskode" msgid "Payload" msgstr "Nyttelast" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Procent fuldført" msgid "Permission denied" msgstr "Tilladelse nægtet" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Indtast venligst et tal nummer 0 og %u: " msgid "Print the version number" msgstr "Udskriv versionsnummer" msgid "Print verbose debug statements" msgstr "Udskriv uddybende fejlsøgningsudsagn" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritet" msgid "Proceed with upload?" msgstr "Fortsæt med upload?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokol" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Forespørg om understøttelse af firmwareopdatering" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Quirks" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Læs firmware fra enhed ind i en fil" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Læs firmware fra en partition ind i en fil" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Læser …" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Genstarter …" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Genopfrisk metadata fra fjernserver" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Område" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Geninstallerer %s med %s... " #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Fjern" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Fjern-id" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Fjernet" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Erstat data i en eksisterende firmwarefil" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Rapport-URI" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Kræver internetforbindelse" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "Nulstil en DFU-enhed" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Genstart nu?" #. TRANSLATORS: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Genstart dæmonen så ændringerne kan træde i kraft?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Genstarter enhed …" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Returner alle maskinens hardware-id'er" #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Kør oprydningsrutinen for pluginkomposition når install-blob bruges" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Kør forberedelsesrutinen for pluginkomposition når install-blob bruges" msgid "Runtime" msgstr "Runtime" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "Gem enhedens tilstand i en JSON-fil mellem udførsler" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Planlægning af installation ved næste genstart, når det er muligt" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Planlægger …" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Serienummer" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Indstil alternativt navn på firmwarefil" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Indstil alternativt tal på firmwarefil" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Indstil elementadresse på firmwarefil" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Indstil producent-id på firmwarefil" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Indstil udgivelsesversion på firmwarefil" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Indstil fejlsøgningsflaget under opdatering" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Indstil firmwarestørrelse på målet" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Indstil producent-id på firmwarefil" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Indstiller metadata på en firmwarefil" msgid "Sets the list of approved firmware" msgstr "Indstiller listen over godkendt firmware" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware." msgstr "Indstiller listen over godkendt firmware." #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Del historik over firmware med udviklerne" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Vis klient- og dæmonversioner" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Vis uddybende information for dæmon for et bestemt domæne" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Vis fejlsøgningsinformation for alle domæner" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Vis fejlsøgningsindstillinger" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Vis enheder som ikke kan opdateres" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Vis ekstra fejlsøgningsinformation" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Vis historik over firmwareopdateringer" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Vis uddybende information om plugin" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Vis fejlsøgningsloggen fra den opdatering der blev forsøgt sidst" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Vis informationen om firmwareopdateringsstatus" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Luk ned nu?" msgid "Sign data using the client certificate" msgstr "Underskriv data med klientcertifikat" msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Underskriv data med klientcertifikat" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Underskriv den uploadede data med klientcertifikatet" msgid "Signature" msgstr "Underskrift" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Angiv producent-/produkt-id('er) for DFU-enhed" msgid "Specify the number of bytes per USB transfer" msgstr "Angiv antal bytes pr. USB-overførsel" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Tilstand" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Status" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Opsummering" msgid "Target" msgstr "Mål" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS'en er en gratis tjeneste der opererer som en selvstændig juridisk enhed og har ingen forbindelse med $OS_RELEASE:NAME$. Din distributør har måske ikke bekræftet nogen af firmwareopdateringerne for kompatibilitet med dit system eller tilsluttede enheder. Al firmware leveres kun af den oprindelige udstyrsproducent." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Der er ikke nogen godkendt firmware." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Programmet virker måske kun korrekt som root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Fjernen indeholder firmware som der ikke er embargo på, men som stadigvæk testes af hardwareproducenten. Du skal sikre dig at du har en manuel måde til at nedgradere firmwaren på hvis firmwareopdateringen mislykkedes." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Værktøjet kan kun bruges af root-brugeren" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Titel" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Overførselsstørrelse" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Type" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI-firmwareredskab" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Ukendt" msgid "Unlock the device to allow access" msgstr "Lås enheden op for at tillade adgang" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Låser op for enheden for at få adgang til firmwaren" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Fjern fejlsøgningsflaget under opdatering" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Uunderstøttet dæmonversion %s, klientversionen er %s" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Opdateringens checksum" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Opdateringens beskrivelse" #. TRANSLATORS: section header for the amount #. * of time it takes to install the update msgid "Update Duration" msgstr "Opdateringens varighed" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Opdateringens placering" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Opdateringens navn" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "Opdateringens fjern-id" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Opdateringens opsummering" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Opdateringens version" #. TRANSLATORS: command description msgid "Update all devices that match local metadata" msgstr "Opdater alle enheder som matcher lokale metadata" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Opdateringer som mislykkes er et velkendt problem. Besøg URL'en for mere information:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Opdater nu?" msgid "Update the stored device verification information" msgstr "Opdaterer de gemte informationer om enhedsverifikation" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Opdater de gemte metadata med indholdet fra den nuværende ROM" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Opdater det gemte metadata med det nuværende indhold" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Opdaterer alle firmware til de seneste versioner" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Opdaterer %s fra %s til %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Opdaterer %s …" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Uploadmeddelelse:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Upload rapport nu?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Upload af firmwarerapporter hjælper hardwareproducenter til hurtigt at identificere opdateringer som fejlede og lykkedes på rigtigt hardware." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Brugernavn" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verificerer …" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Version" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Venter …" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "Hold øje med DFU-enheder som hotplugges" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Hold øje med hardwareændringer" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Skriv firmware fra fil ind i enhed" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Skriv firmware fra fil ind i en partition" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Skriver …" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Din distributør har måske ikke verificeret kompatibiliteten af firmwareopdateringerne med dit system eller tilsluttede enheder." fwupd-1.2.14/po/de.po000066400000000000000000000671171402665037500143160ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Ettore Atalan , 2018 # Marco Tedaldi , 2015 # Wolfgang Stöggl , 2015 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: German (http://www.transifex.com/freedesktop/fwupd/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f Minute verbleibt" msgstr[1] "%.0f Minuten verbleiben" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "Firmware-Aktualisierungen für %s verfügbar:" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Hinzugefügt" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Alter" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Verweis auf %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Herabstufung von Firmware-Versionen zulassen" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Erneute Installation vorhandener Firmware-Versionen erlauben" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Ein Neustart ist erforderlich, um eine Aktualisierung abzuschließen." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Alle Fragen mit Ja beantworten" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Binären Patch anwenden" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Firmware-Aktualisierungen anwenden" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "DFU-fähiges Gerät wieder zurück in Laufzeit einhängen" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Attribute" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Authentifizierung …" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Für eine Herabstufung der Firmware auf einem entfernbaren Gerät ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Für eine Herabstufung der Firmware auf diesem System ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Legitimation ist zum Entsperren eines Geräts erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Für die Aktualisierung der Firmware auf einem entfernbaren Gerät ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Für die Aktualisierung der Firmware auf diesem System ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Eine Authentifizierung ist erforderlich, um die gespeicherten Prüfsummen für das Gerät zu aktualisieren" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Firmware mit Hilfe einer Sandbox erstellen" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Abbrechen" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Abgebrochen" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Geändert" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Prüfsumme" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "Chip-Kennung" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Wählen Sie ein Gerät aus:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Wählen Sie eine Version aus:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Verschlüsselungsverfahren" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Bereinigt die Ergebnisse der letzten Aktualisierung" #. TRANSLATORS: error message msgid "Command not found" msgstr "Befehl nicht gefunden" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Firmware in das DFU-Format konvertieren" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Binären Patch aus zwei Dateien erstellen" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU-Dienstprogramm" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Debug Optionen" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Entpacken …" #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Firmware-Daten entschlüsseln" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Beschreibung" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Derzeit eingehängtes DFU-fähiges Gerät entfernen" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Gerät hinzugefügt:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Gerät geändert:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Gerät entfernt:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Erfolgreich aktualisierte Geräte:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Nicht korrekt aktualisierte Geräte:" msgid "Disabled fwupdate debugging" msgstr "fwupdate-Defektlokalisierung deaktivieren" #. TRANSLATORS: command line option msgid "Display version" msgstr "Version anzeigen" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Nicht auf alte Metadaten prüfen" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "Nach der Aktualisierung nicht auf einen Neustart prüfen" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Nicht auf nicht erfassten Verlauf prüfen" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Nicht in die Verlaufsdatenbank schreiben" #. success msgid "Done!" msgstr "Fertig." #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Stuft die Firmware auf einem Gerät herab" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Herabstufung für %s von %s auf %s wird eingespielt…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "%s wird herabgestuft …" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Herunterladen …" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "SMBIOS-Daten aus einer Datei ausgeben" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Details zu einer Firmware-Datei ausgeben" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Informationen über einen binären Patch auf dem Bildschirm ausgeben" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Das angegebene ESP war nicht gültig" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Firmware-Aktualisierungsunterstützung auf unterstützten Systemen aktivieren" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Aktiviert" msgid "Enabled fwupdate debugging" msgstr "fwupdate-Defektlokalisierung aktivieren" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Die Aktivierung dieser Funktionalität erfolgt auf eigene Gefahr, d.h. Sie müssen sich bei Problemen, die durch diese Aktualisierungen verursacht werden, an Ihren Erstausrüster wenden. Nur Probleme mit dem Aktualisierungsprozess selbst sollten unter $OS_RELEASE:BUG_REPORT_URL$ eingereicht werden." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Firmware-Daten verschlüsseln" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Gesamten Firmware-Aktualisierungsverlauf löschen" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Löschen …" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Verlassen nach einer kurzen Verzögerung" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Nach dem Laden der Engine beenden" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Verarbeitung der Argumente schlug fehl" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Datei wird abgerufen" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "Firmware wird abgerufen" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Metadaten werden abgerufen" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Signatur wird abgerufen" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Dateiname" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Signatur des Dateinamens" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Firmware Basis-URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "D-Bus-Dienst für Firmware-Aktualisierung" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Hintergrundprogramm für Firmware-Aktualisierung" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmware-Dienstprogramm" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Firmware-Metadaten wurden seit %u Tag nicht aktualisiert und sind möglicherweise nicht auf dem neuesten Stand." msgstr[1] "Firmware-Metadaten wurden seit %u Tagen nicht aktualisiert und sind möglicherweise nicht auf dem neuesten Stand." msgid "Firmware updates are not supported on this machine." msgstr "Firmware-Aktualisierungen werden auf diesem System nicht unterstützt." msgid "Firmware updates are supported on this machine." msgstr "Firmware-Aktualisierungen werden auf diesem System unterstützt." msgid "Force the action ignoring all warnings" msgstr "Aktion erzwingen, bei der alle Warnungen ignoriert werden" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Gefunden" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "Ermittelt alle Geräte gemäß der Systemtopologie" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Alle Geräte ermitteln, die Firmware-Aktualisierungen unterstützen" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Alle aktivierten und im System registrierten Plugins ermitteln" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Ermittelt Details über eine Firmware-Datei" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Ermittelt den kryptographischen Hash-Wert der abgelegten Firmware" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Ermittelt die Liste der Aktualisierungen für angeschlossene Hardware" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Ermittelt die Versionen für ein Gerät" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Ermittelt die Ergebnisse der letzten Aktualisierung" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "Kennung" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Bereit …" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Firmware-Blob auf einem Gerät installieren" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Eine Firmware-Datei auf dieser Hardware installieren" msgid "Install old version of system firmware" msgstr "Alte Version der System-Firmware installieren" msgid "Install signed device firmware" msgstr "Signierte Geräte-Firmware installieren" msgid "Install signed system firmware" msgstr "Signierte System-Firmware installieren" msgid "Install unsigned device firmware" msgstr "Nicht-signierte Geräte-Firmware installieren" msgid "Install unsigned system firmware" msgstr "Nicht-signierte System-Firmware installieren" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Firmware-Aktualisierung wird installiert …" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Wird auf %s installiert …" msgid "Keyring" msgstr "Schlüsselbund" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Weniger als eine Minute verbleiben" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stabile Firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (Test-Firmware)" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "Derzeit angeschlossene DFU-fähige Geräte auflisten" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Unterstützte Firmware-Aktualisierungen auflisten" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Laden …" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Mehrere Firmware-Dateien in eine zusammenführen" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadaten-URI" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Metadaten URI-Signatur" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadaten können über den Linux Vendor Firmware Service bezogen werden." msgid "Mode" msgstr "Modus" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Hintergrundprogramm auf Ereignisse überwachen" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Name" msgid "No action specified!" msgstr "Keine Aktion angegeben!" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Es wurde keine Hardware erkannt, deren Firmware aktualisiert werden kann" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Keine Plugins gefunden" msgid "OK" msgstr "Ok" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Plugin-Warnung überschreiben" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Standard-ESP-Pfad überschreiben" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Passwort" msgid "Payload" msgstr "Nutzdaten" msgid "Permission denied" msgstr "Berechtigung verweigert" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Bitte geben Sie eine Zahl von 0 bis %u ein: " msgid "Print the version number" msgstr "Versionsnummer ausgeben" msgid "Print verbose debug statements" msgstr "Ausführliche Debug-Anweisungen ausgeben" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Priorität" msgid "Proceed with upload?" msgstr "Mit dem Hochladen fortfahren?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokoll" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Abfrage der Unterstützung für Firmware-Aktualisierungen" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Macken" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Firmware von Gerät in Datei schreiben" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Firmware von einzelner Partition in Datei lesen" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Lesen …" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Metadaten von entferntem Server aktualisieren" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Bereich" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Erneute Installation von %s mit %s …" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Entfernt" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Daten in einer bestehenden Firmware-Datei ersetzen" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Bericht-URI" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Erfordert Internetverbindung" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "DFU-Gerät zurücksetzen" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Jetzt neu starten?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Gerät wird neu gestartet …" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Alle Hardware-Kennungen für das System zurückgeben" msgid "Runtime" msgstr "Laufzeit" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Installation für den nächsten Neustart planen, falls möglich" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Einplanen …" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Seriell" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Alternativen Namen einer Firmware-Datei festlegen" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Alternative Nummer einer Firmware-Datei festlegen" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Die Elementadresse in Firmware-Datei festlegen" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Produkt-Kennung einer Firmware-Datei festlegen" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Veröffentlichungsversion einer Firmware-Datei festlegen" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Firmware-Größe für das Ziel festlegen" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Hersteller-Kennung einer Firmware-Datei festlegen" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Metadaten einer Firmware-Datei festlegen" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Firmware-Verlauf mit den Entwicklern teilen" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Client- und Hintergrundprogramm-Versionen anzeigen" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Debug Optionen anzeigen" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Nicht aktualisierbare Geräte anzeigen" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Zusätzliche Informationen zur Fehlerdiagnose anzeigen" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Verlauf von Firmware-Aktualisierungen anzeigen" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Ausführliche Informationen zum Plugin anzeigen" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Fehlerprotokoll der letzten versuchten Aktualisierung anzeigen" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Informationen über den Firmware-Aktualisierungsstatus anzeigen" msgid "Specify the number of bytes per USB transfer" msgstr "Geben Sie die Anzahl der Bytes pro USB-Übertragung an" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Zustand" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Status" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Zusammenfassung" msgid "Target" msgstr "Ziel" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "Der LVFS ist ein kostenloser Dienst, der als unabhängige juristische Person arbeitet und keine Verbindung zu $OS_RELEASE:NAME$ hat. Möglicherweise hat Ihr Lieferant eine der Firmware-Aktualisierungen nicht auf Kompatibilität mit Ihrem System oder angeschlossenen Geräten überprüft. Die gesamte Firmware wird nur vom Originalhersteller zur Verfügung gestellt." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Dieses Programm funktioniert möglicherweise nur als root korrekt" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Titel" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Übertragungsgröße" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Typ" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI-Firmware-Dienstprogramm" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Unbekannt" msgid "Unlock the device to allow access" msgstr "Das Gerät entsperren, um Zugriff zu ermöglichen" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Entsperrt das Gerät für Zugriff auf die Firmware" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Prüfsumme aktualisieren" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Beschreibung aktualisieren" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Ort aktualisieren" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Aktualisierungsname" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Aktualisierungszusammenfassung" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Version aktualisieren" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Der Aktualisierungsfehler ist ein bekanntes Problem, besuchen Sie diese URL für weitere Informationen:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Jetzt aktualisieren?" msgid "Update the stored device verification information" msgstr "Gespeicherte Geräteverifizierungsinformationen aktualisieren" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Gespeicherte Metadaten mit dem aktuellen ROM-Inhalt aktualisieren" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Alle Firmware auf die neueste verfügbare Version aktualisieren" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Aktualisieren von %s von %s nach %s …" #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s wird aktualisiert …" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Nachricht beim Hochladen:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Bericht jetzt hochladen?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Das Hochladen von Firmware-Berichten hilft Hardwareherstellern, fehlerhafte und erfolgreiche Aktualisierungen auf realen Geräten schnell zu erkennen." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Benutzername" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Überprüfung …" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Version" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Warten …" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "Geräteanschluss von DFU-Geräten überwachen" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Auf Hardware-Änderungen achten" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Firmware von Datei auf Gerät schreiben" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Firmware aus Datei in einzelne Partition schreiben" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Schreiben …" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Möglicherweise hat Ihr Lieferant eine der Firmware-Aktualisierungen nicht auf Kompatibilität mit Ihrem System oder angeschlossenen Geräten überprüft." fwupd-1.2.14/po/en_GB.po000066400000000000000000000741531402665037500146760ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Richard Hughes , 2015,2017-2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: English (United Kingdom) (http://www.transifex.com/freedesktop/fwupd/language/en_GB/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: en_GB\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minute remaining" msgstr[1] "%.0f minutes remaining" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s has firmware updates:" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u day" msgstr[1] "%u days" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hour" msgstr[1] "%u hours" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minute" msgstr[1] "%u minutes" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u second" msgstr[1] "%u seconds" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Added" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Age" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Agree and enable the remote?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias to %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Allow downgrading firmware versions" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Allow re-installing existing firmware versions" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "An update requires a reboot to complete." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "An update requires the system to shutdown to complete." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Answer yes to all questions" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Apply a binary patch" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Apply firmware updates" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "Attach DFU capable device back to runtime" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Attach to firmware mode" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Attributes" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Authenticating…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Authentication is required to downgrade the firmware on a removable device" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Authentication is required to downgrade the firmware on this machine" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Authentication is required to modify a configured remote used for firmware updates" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Authentication is required to unlock a device" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Authentication is required to update the firmware on a removable device" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Authentication is required to update the firmware on this machine" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Authentication is required to update the stored checksums for the device" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Build firmware using a sandbox" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancel" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Cancelled" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Changed" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "Chip ID" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Choose a device:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Choose a release:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Cipher" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Clears any updates scheduled to be updated offline" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Clears the results from the last update" #. TRANSLATORS: error message msgid "Command not found" msgstr "Command not found" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Convert firmware to DFU format" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Create a binary patch using two files" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU Utility" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Debugging Options" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Decompressing…" #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Decrypt firmware data" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Description" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Detach currently attached DFU capable device" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Detach to bootloader mode" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Device added:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Device changed:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Device removed:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Devices that have been updated successfully:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Devices that were not updated correctly:" msgid "Disabled fwupdate debugging" msgstr "Disabled fwupdate debugging" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Disables a given remote" #. TRANSLATORS: command line option msgid "Display version" msgstr "Display version" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Do not check for old metadata" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "Do not check for reboot after update" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Do not check for unreported history" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Do not write to the history database" #. success msgid "Done!" msgstr "Done!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Downgrades the firmware on a device" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Downgrading %s from %s to %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Downgrading %s…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Downloading…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Dump SMBIOS data from a file" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Dump details about a firmware file" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Dump information about a binary patch to the screen" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "ESP specified was not valid" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Enable firmware update support on supported systems" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Enable this remote?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Enabled" msgid "Enabled fwupdate debugging" msgstr "Enabled fwupdate debugging" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Enables a given remote" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Enabling this remote is done at your own risk." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Encrypt firmware data" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Erase all firmware update history" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Erasing…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Exit after a small delay" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Exit after the engine has loaded" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "Failed to download due to server limit" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Failed to load quirks" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Failed to parse arguments" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Fetching file" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "Fetching firmware" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Fetching metadata" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Fetching signature" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Filename" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Filename Signature" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Firmware Base URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Firmware Update D-Bus Service" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Firmware Update Daemon" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmware Utility" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Firmware metadata has not been updated for %u day and may not be up to date." msgstr[1] "Firmware metadata has not been updated for %u days and may not be up to date." msgid "Firmware updates are not supported on this machine." msgstr "Firmware updates are not supported on this machine." msgid "Firmware updates are supported on this machine." msgstr "Firmware updates are supported on this machine." msgid "Force the action ignoring all warnings" msgstr "Force the action ignoring all warnings" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Found" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "Get all devices according to the system topology" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Get all devices that support firmware updates" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Get all enabled plugins registered with the system" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Gets details about a firmware file" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Gets the configured remotes" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Gets the cryptographic hash of the dumped firmware" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Gets the list of updates for connected hardware" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Gets the releases for a device" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Gets the results from the last update" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Idle…" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Install a firmware blob on a device" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Install a firmware file on this hardware" msgid "Install old version of system firmware" msgstr "Install old version of system firmware" msgid "Install signed device firmware" msgstr "Install signed device firmware" msgid "Install signed system firmware" msgstr "Install signed system firmware" msgid "Install unsigned device firmware" msgstr "Install unsigned device firmware" msgid "Install unsigned system firmware" msgstr "Install unsigned system firmware" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Installing firmware update…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installing on %s…" msgid "Keyring" msgstr "Keyring" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Less than one minute remaining" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stable firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (testing firmware)" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "List currently attached DFU capable devices" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "List supported firmware updates" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Loading…" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Manually whitelist specific plugins" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Merge multiple firmware files into one" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadata URI" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Metadata URI Signature" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadata can be obtained from the Linux Vendor Firmware Service." msgid "Mode" msgstr "Mode" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifies a given remote" msgid "Modify a configured remote" msgstr "Modify a configured remote" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitor the daemon for events" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Name" msgid "No action specified!" msgstr "No action specified!" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "No hardware detected with firmware update capability" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "No plugins found" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "No remotes are currently enabled so no metadata is available." msgid "OK" msgstr "OK" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Override plugin warning" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Override the default ESP path" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Password" msgid "Payload" msgstr "Payload" msgid "Permission denied" msgstr "Permission denied" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Please enter a number from 0 to %u: " msgid "Print the version number" msgstr "Print the version number" msgid "Print verbose debug statements" msgstr "Print verbose debug statements" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Priority" msgid "Proceed with upload?" msgstr "Proceed with upload?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protocol" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Query for firmware update support" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Quirks" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Read firmware from device into a file" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Read firmware from one partition into a file" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Reading…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Refresh metadata from remote server" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Region" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Reinstalling %s with %s... " #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Remote" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Remote ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Removed" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Replace data in an existing firmware file" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Report URI" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Requires internet connection" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "Reset a DFU device" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Restart now?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Restarting device…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Return all the hardware IDs for the machine" #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Run the plugin composite cleanup routine when using install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Run the plugin composite prepare routine when using install-blob" msgid "Runtime" msgstr "Runtime" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Schedule installation for next reboot when possible" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Scheduling…" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Serial" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Set alternative name on firmware file" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Set alternative number on firmware file" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Set element address on firmware file" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Set product ID on firmware file" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Set release version on firmware file" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Set the debugging flag during update" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Set the firmware size for the target" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Set vendor ID on firmware file" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Sets metadata on a firmware file" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Share firmware history with the developers" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Show client and daemon versions" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Show debugging options" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Show devices that are not updatable" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Show extra debugging information" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Show history of firmware updates" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Show plugin verbose information" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Show the debug log from the last attempted update" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Show the information of firmware update status" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Shutdown now?" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Specify Vendor/Product ID(s) of DFU device" msgid "Specify the number of bytes per USB transfer" msgstr "Specify the number of bytes per USB transfer" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "State" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Status" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Summary" msgid "Target" msgstr "Target" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "This program may only work correctly as root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Title" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Transfer Size" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Type" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI Firmware Utility" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Unknown" msgid "Unlock the device to allow access" msgstr "Unlock the device to allow access" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Unlocks the device for firmware access" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Unset the debugging flag during update" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Update Checksum" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Update Description" #. TRANSLATORS: section header for the amount #. * of time it takes to install the update msgid "Update Duration" msgstr "Update Duration" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Update Location" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Update Name" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "Update Remote ID" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Update Summary" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Update Version" #. TRANSLATORS: command description msgid "Update all devices that match local metadata" msgstr "Update all devices that match local metadata" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Update failure is a known issue, visit this URL for more information:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Update now?" msgid "Update the stored device verification information" msgstr "Update the stored device verification information" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Update the stored metadata with current ROM contents" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Updates all firmware to latest versions available" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Updating %s from %s to %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Updating %s…" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Upload message:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Upload report now?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Username" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifying…" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Version" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Waiting…" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "Watch DFU devices being hotplugged" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Watch for hardware changes" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Write firmware from file into device" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Write firmware from file into one partition" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Writing…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." fwupd-1.2.14/po/eo.po000066400000000000000000000041601402665037500143160ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # kristjan , 2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Esperanto (http://www.transifex.com/freedesktop/fwupd/language/eo/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: eo\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekundo" msgstr[1] "%u sekundoj" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Aldonita" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Nuligi" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Nuligita" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Ŝanĝita" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Elektu aparaton:" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Priskribo" #. success msgid "Done!" msgstr "Farita!" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Trovita" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" msgid "Mode" msgstr "Reĝimo" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Nomo" msgid "OK" msgstr "Bone" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokolo" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Regiono" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Forigita" msgid "Target" msgstr "Celo" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Nekonata" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Versio" fwupd-1.2.14/po/eu.po000066400000000000000000000030701402665037500143230ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # assar , 2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Basque (http://www.transifex.com/freedesktop/fwupd/language/eu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: eu\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Gehitua" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Aldatua" #. TRANSLATORS: error message msgid "Command not found" msgstr "Ez da komandoa aurkitu" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Aurkitua" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "IDa" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Izena" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokoloa" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Eskualdea" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Kendua" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Egoera" fwupd-1.2.14/po/fi.po000066400000000000000000001155101402665037500143130ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Jiri Grönroos , 2017-2018 # Kimmo Kujansuu , 2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Finnish (http://www.transifex.com/freedesktop/fwupd/language/fi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minuuttia jäljellä" msgstr[1] "%.0f minuuttia jäljellä" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%sKuluttajan ME-päivitys" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%sOhjaimen päivitys" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%sYrityksen ME-päivitys" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%sLaitteen päivitys" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%sSulautetun ohjaimen päivitys" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%sME päivitys" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%sJärjestelmän päivitys" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%sPäivitys" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s on laiteohjelmistopäivityksiä:" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%upäivää" msgstr[1] "%upäivää" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u tuntia" msgstr[1] "%utuntia" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuuttia" msgstr[1] "%u minuuttia" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u " msgstr[1] "%u sekunttia" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktivoi laitteet" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktivoi odottavat laitteet" msgid "Activate the new firmware on the device" msgstr "Aktivoi laitteessa oleva uusi laiteohjelmisto 'firmware'" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Firmware-päivityksen aktivointi" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Käynnistä laiteohjelmiston päivitys" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Lisätty" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Ikä" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Hyväksy ja ota etäyhteys käyttöön?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Peitenimi %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Salli firmware-versioiden alentaminen" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Salli laiteohjelmiston versioiden asentaminen uudelleen" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Päivitys vaatii uudelleenkäynnistyksen." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "Päivitys edellyttää järjestelmän sammuttamista." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Vastaa kaikkiin kysymyksiin kyllä" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Käytä binääristä korjaustiedostoa" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Käytä firmware-päivityksiä" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Hyväksytty laiteohjelmisto:" msgstr[1] "Hyväksytty laiteohjelmisto:" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "Kiinnitä DFU-kykyinen laite takaisin ajoon" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Kiinnitä laiteohjelmistotilaan" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Määritteet" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Tunnistaudutaan…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Todennus on tarpeen, jotta firmware voidaan alentaa siirrettävällä laitteelta" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Todennus on tarpeen tämän laitteen firmwaren alentamiseksi" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Todennusta tarvitaan firmware-päivityksiin käytettävän konfiguroidun etä-ohjaimen muokkaamisessa" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Todennusta tarvitaan taustaprosessin asetusten muokkaamiseen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Vahvistus on tarpeen hyväksyttyjen laiteohjelmien listan määrittämiseksi" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Autentikointi edellyttää tietojen allekirjoittamista asiakaan sertifikaatin avulla" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Uuden firmware-version käyttöönottoon tarvitaan todennus" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Laitteen lukituksen avaaminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Erillisen laitteen firmwaren päivittäminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Tämän laitteen firmwaren päivittäminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Todennus edellyttää laitteen tallennettujen tarkistussummien päivittämistä" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Rakenna laiteohjelmisto hiekkalaatikon avulla" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Peru" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Peruttu" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Muutettu" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Tarkistussumma" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "Chip-tunnus" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Valitse laite:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Valitse julkaisu:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Salaus" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Tyhjentää päivitykset, jotka on tarkoitus päivittää offline-tilassa" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Tyhjennä viimeisimmän päivityksen tulokset" #. TRANSLATORS: error message msgid "Command not found" msgstr "Komentoa ei löytynyt" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Muunna firmware DFU-muotoon" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Luo binäärinen korjaustiedosto käyttäen kahta tiedostoa" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU-apuohjelma" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Vianjäljitysvalinnat" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Puretaan…" #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Pura firmwaren data" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Kuvaus" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Irrota tällä hetkellä liitetty DFU-laite" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Irrota käynnistyslataimen tilaan" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Laitteen tunnus" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Laite lisätty:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Laite muutettu:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Laite poistettu:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Laitteet, jotka on päivitetty onnistuneesti:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Laitteet, joita ei päivitetty oikein:" msgid "Disabled fwupdate debugging" msgstr "fw-päivityksen virheenkorjaus poistettu" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Poista annettu etäyhteys" #. TRANSLATORS: command line option msgid "Display version" msgstr "Näytä versio" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Älä tarkista vanhoja metatietoja" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "Älä tarkista käynnistystä uudelleen päivityksen jälkeen" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Älä tarkista ilmoittamattomasta historiasta" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Älä kirjoita historiatietokantaan" #. success msgid "Done!" msgstr "Valmis!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Alentaa laitteen laiteohjelmistoa" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Alenee %s -sta %s tulos %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Alentaa %s…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Ladataan…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Pura SMBIOS-tiedot tiedostosta" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Tyhjennä laiteohjelmiston tiedot" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Tulosta tiedot binäärisestä korjaustiedostosta näytölle" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Määritetty ESP ei ollut voimassa" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Ota firmware-päivitystuki käyttöön tuetuissa järjestelmissä" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Ota etäyhteys käyttöön?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Käytössä" msgid "Enabled fwupdate debugging" msgstr "fw-päivityksen virheenkorjaus käytössä" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Ota käyttöön annettu etäyhteys" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Tämän toiminnon käyttöönotto tapahtuu omalla vastuullasi, joten sinun on otettava yhteyttä alkuperäiseen laitevalmistajaan näiden päivitysten aiheuttamista ongelmista. Vain päivitysprosessiin liittyvät ongelmat pitäisi jättää osoitteeseen $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Tämän etä-ohjaimen käyttöönotto tapahtuu omalla vastuullasi." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Salaa firmwaren data" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Poista kaikki laiteohjelmiston päivityshistoria" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Poistetaan…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Poistu pienen viiveen jälkeen" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Poistu kun moottori on ladattu" #. TRANSLATORS: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "Yhteys taustaprosessiin epäonnistui" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "Lataaminen epäonnistui palvelimen rajoituksen vuoksi" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Vireillä olevien laitteiden saaminen epäonnistui" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Laiteohjelmiston firmware päivityksen asennus epäonnistui" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Quirksin lataaminen epäonnistui" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Argumenttien jäsentäminen epäonnistui" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Käynnistys epäonnistui" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Splash-tilan asettaminen epäonnistui" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Noudetaan tiedosto" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "Noudetaan firmware" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Noudetaan metatietoja" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Noudetaan allekirjoitus" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Tiedostonimi" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Tiedoston allekirjoitus" #. TRANSLATORS: program name msgid "Firmware Agent" msgstr "Firmware-agentti" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Firmware pohja URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Laiteohjelmiston päivitys D-Bus-palveluun" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Firmware-päivityksen taustaprosessi" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmware-työkalu" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Laiteohjelmiston metatietoja ei ole päivitetty %upäivään ja ne eivät välttämättä ole ajan tasalla." msgstr[1] "Laiteohjelmiston metatietoja ei ole päivitetty %upäivään ja ne eivät välttämättä ole ajan tasalla." msgid "Firmware updates are not supported on this machine." msgstr "Ohjelmistopäivitys 'firmware' ei tue tätä laitetta" msgid "Firmware updates are supported on this machine." msgstr "Ohjelmistopäivitys 'firmware' tukee tätä laitetta" #. TRANSLATORS: section header for firmware flags msgid "Flags" msgstr "Liput" msgid "Force the action ignoring all warnings" msgstr "Pakota toimenpide huomioimatta kaikki varoitukset" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Löydetty" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "Hae kaikki laitteet järjestelmän topologian mukaisesti" #. TRANSLATORS: command description msgid "Get all devices and possible releases" msgstr "Hanki kaikki laitteet ja mahdolliset julkaisut" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Hae kaikki laiteohjelmistopäivityksiä tukevat laitteet" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Hanki kaikki järjestelmään rekisteröidyt laajennukset" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Hae tietoja firmware-tiedostosta" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Määrittää konfiguroidut etäyhteydet" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Hae salakirjoituksen kohteena oleva laiteohjelmisto." #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware." msgstr "Hae hyväksytty laiteohjelmiston luettelo." #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Antaa luettelon liitettyjen laitteiden päivityksistä" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Antaa laitteen julkaisut" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Saat viimeisimmän päivityksen tulokset" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "Henkilöllisyys" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Jouten…" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Asenna laiteohjelmiston merkintä laitteeseen" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Asenna laiteohjelmisto tähän laitteistoon" msgid "Install old version of system firmware" msgstr "Asenna vanha firmware versio" msgid "Install signed device firmware" msgstr "Asenna allekirjoitettu laitteen firmware" msgid "Install signed system firmware" msgstr "Asenna allekirjoitettu järjestelmän firmware" msgid "Install unsigned device firmware" msgstr "Asenna allekirjoittamattoman laitteen firmware" msgid "Install unsigned system firmware" msgstr "Asenna allekirjoittamaton järjestelmän firmware" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Asentaa firmwarea…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Asennetaan firmware-päivitystä…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Asentaa %s…" msgid "Keyring" msgstr "Avaimet" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Jäljellä on alle minuutti" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux-toimittajan laiteohjelmisto (vakaa firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux-toimittajan laiteohjelmisto (firmware testaus)" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "Luettelo tällä hetkellä liitetyistä DFU-laitteista" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Lista tuetuista firmware-päivityksistä" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Ladataan…" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Luetteloi tiettyjä lisäosia manuaalisesti" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Yhdistä useita firmware-tiedostoja yhteen" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metatietojen URI" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Metatietojen URI-allekirjoitus" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metatiedot voidaan hankkia Linux Vendor Firmware -palvelusta." #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Virheellinen taustaprosessi ja asiakas, käytä sen sijaan %s" msgid "Mode" msgstr "Malli" #. TRANSLATORS: sets something in daemon.conf msgid "Modifies a daemon configuration value." msgstr "Muuta taustaprosessin määritysarvoja." #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Muuta annettua etäyhteyttä" msgid "Modify a configured remote" msgstr "Muokkaa määritettyä etänä" msgid "Modify daemon configuration" msgstr "Muokkaa taustaprosessin kokoonpanoa" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Seuraa tapahtumien taustaprosessia" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Nimi" msgid "No action specified!" msgstr "Toimintoa ei ole määritetty!" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Ei havaittu sopivaa laitteistoa firmware päivitykselle" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Ei laajennuksia" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Mitään etäyhteyksiä ei ole tällä hetkellä käytössä, joten metatietoja ei ole saatavilla." #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Ei soveltuvia päivityksiä" msgid "OK" msgstr "OK" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Ohita plugin-varoitus" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Ohita oletusarvoinen ESP-polku" #. TRANSLATORS: command line option msgid "Override warnings and force the action" msgstr "Ohita varoitukset ja pakota toiminto" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Salasana" msgid "Payload" msgstr "Tietosisältö" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Prosenttiosuus valmis" msgid "Permission denied" msgstr "Lupa kielletty" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Anna numero 0 -%u" msgid "Print the version number" msgstr "Tulosta versionumero" msgid "Print verbose debug statements" msgstr "Tulosta virheelliset virheilmoitukset" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioriteetti" msgid "Proceed with upload?" msgstr "Jatka lähettämistä?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokolla" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Kysely firmware-päivityksen tuesta" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Merkintä" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Lue firmware laitteesta tiedostoon" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Lue firmware osiolta tiedostoon" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Luetaan…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Käynnistää..." #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Päivitä etäpalvelimen metatiedot" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Alue" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Uudelleenasennus %s kera %s... " #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Etä" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Etätunnus" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Poistettu" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Vaihda tiedot olemassa olevaan firmware-tiedostoon" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Ilmoita URI" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Vaatii Internet-yhteyden" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "Nollaa DFU-laite" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Käynnistä nyt?" #. TRANSLATORS: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Käynnistä taustaprosessi uudelleen, jotta muutos tulee voimaan" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Käynnistetään laite uudelleen…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Palauta kaikki laitteen laitteistotunnukset" #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Suorita plugin-puhdistustoiminto, kun käytät asennus-blobia" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Asennus-blobia ajamalla käytät yhdistelmän valmiita laajennus rutiineja " msgid "Runtime" msgstr "Käyttöaika" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "Tallenna laitteen tila JSON-tiedostoon suoritusten välillä" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Ajasta asennus uudelleenkäynnistykselle mahdollisuuksien mukaan" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Ajoitetaan…" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Sarjanumero" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Aseta vaihtoehtoinen nimi firmware-tiedostolle" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Aseta vaihtoehtoinen numero firmware-tiedostolle" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Aseta elementin osoite firmware tiedostoon" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Aseta tuotetunnus firmware tiedostoon" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Aseta julkaisuversio firmware-tiedostosta" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Aseta virheenkorjauksen lippu päivityksen aikana" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Määritä firmwaren kohteen koko" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Aseta toimittajan tunnus firmware tiedostoon" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Asetta metatiedot firmware -tiedostoon" msgid "Sets the list of approved firmware" msgstr "Asettaa listan hyväksytyjä 'firmware' laiteohjelmistoja " #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware." msgstr "Asettaa hyväksytyn laiteohjelmiston luettelon." #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Jaa laiteohjelmiston historia kehittäjien kanssa" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Näytä asiakas- ja taustaprosessin versiot" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Näytä taustaprosessin täydelliset tiedot tietylle verkkotunnukselle" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Näytä kaikkien verkkotunnusten virheenkorjaustiedot" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Näytä vianjäljitysvalinnat" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Näytä laitteet, joita ei voi päivittää" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Näytä ylimääräiset virheenkorjaustiedot" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Näytä laiteohjelmiston päivitysten historia" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Näytä plugin tiedot" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Näytä virheenkorjausloki viimeisestä päivitysyrityksestä" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Näytä tiedot laiteohjelmiston 'firmware' päivitystilasta" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Sammuta nyt?" msgid "Sign data using the client certificate" msgstr "Allekirjoita tietoja käyttämällä asiakkaan sertifikaattia" msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Allekirjoita tietoja käyttämällä asiakkaan sertifikaattia" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Kirjoita ladatut tiedot asiakaan sertifikaatilla" msgid "Signature" msgstr "Allekirjoitus" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Määritä DFU laitteen toimittaja/tuotetunnus(s)" msgid "Specify the number of bytes per USB transfer" msgstr "Määritä tavujen määrä USB-siirtoa kohti" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Osavaltio" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Tila" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Yhteenveto" msgid "Target" msgstr "Kohde" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS on ilmainen palvelu, joka toimii itsenäisenä oikeushenkilönä eikä sillä ole yhteyttä $OS_RELEASE:NAME$. Jakelijasi ei ehkä ole tarkistanut mitään laiteohjelmistopäivityksiä, jotka ovat yhteensopivia järjestelmän tai liitettyjen laitteiden kanssa. Kaikki laiteohjelmistot on tarkoitettu vain alkuperäisen laitteen valmistajalle." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Hyväksyttyä laiteohjelmistoa ei ole." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Tämä ohjelma voi toimia vain juuressa 'root'" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Tämä sisältää firmwaren, jota ei ole vientikiellossa, mutta jota laitteistotoimittaja testaa edelleen. Varmista, että voit päivittää firmwaren manuaalisesti, jos päivitys epäonnistuu." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Tätä työkalua voi käyttää vain root-käyttäjä" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Otsikko" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Siirron koko" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tyyppi" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI firmware -apuohjelma" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Tuntematon" msgid "Unlock the device to allow access" msgstr "Sallia pääsy laitteeseen" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Avaa pääsy laitteen laiteohjelmistoon" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Poista virheenkorjauksen lippu päivityksen aikana" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Ei tuettu taustaprosessin versio %s, asiakasversio on %s" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Päivitä tarkistussumma" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Päivitä kuvaus" #. TRANSLATORS: section header for the amount #. * of time it takes to install the update msgid "Update Duration" msgstr "Päivitä kesto" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Päivitä sijainti" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Päivitä nimi" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "Päivitä etätunnus" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Päivitä yhteenveto" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Päivitä versio" #. TRANSLATORS: command description msgid "Update all devices that match local metadata" msgstr "Päivitä kaikki paikallisia metatietoja vastaavat laitteet" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Päivityksen epäonnistuminen on tunnettu ongelma. Saat lisätietoja tästä URL-osoitteesta:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Päivitä nyt?" msgid "Update the stored device verification information" msgstr "Päivitä laitteen tallennetut vahvistustiedot" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Päivitä tallennetut metatiedot nykyisen ROM-sisällön kanssa" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Päivitä tallennetut metatiedot nykyiseen sisältöön" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Päivittää kaikki laiteohjaimet uusimpiin saatavilla oleviin versioihin" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Korotus %s -sta %s tulos %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Päivittää %s…" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Lähetä viesti:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Lataa raportti nyt?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Firmware-raporttien lataaminen auttaa laitteistotoimittajia tunnistamaan nopeasti virheelliset ja onnistuneet päivitykset todellisissa laitteissa." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Käyttäjätunnus" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Vahvistetaan…" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Versio" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Odotetaan…" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "Katso, että DFU-laitteet on kytketty" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Katso laitteiston muutoksia" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Kirjoita firmware tiedostosta laitteeseen" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Kirjoita firmware tiedostosta osioon" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Kirjoitetaan…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Jakelijasi ei ehkä ole tarkistanut mitään laiteohjelmistopäivityksiä, jotka ovat yhteensopivia järjestelmän tai liitettyjen laitteiden kanssa." fwupd-1.2.14/po/fr.po000066400000000000000000000062461402665037500143310ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Franck , 2015 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: French (http://www.transifex.com/freedesktop/fwupd/language/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias de %s" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Une authentification est nécessaire pour mettre à jour le micrologiciel sur cette machine" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Options de débogage" #. success msgid "Done!" msgstr "Terminé !" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Rétrogradation de %s de %s en %s" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Quitter après un bref délai" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Quitter après le chargement du moteur" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Echec de l'analyse des paramètres" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Service D-Bus de mise à jour des micrologiciels" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Obtenir la liste des périphériques supportant les mises à jour de micrologiciel" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Obtenir les détails d'un fichier de micrologiciel" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Installer un fichier de micrologiciel sur ce matériel" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Aucun matériel ayant des capacités de mise à jour du micrologiciel n'a été détecté" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Réinstallation de %s en %s" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Montrer les options de débogage" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Montre des informations de débogage complémentaires" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Mise à jour de %s de %s en %s" fwupd-1.2.14/po/fur.po000066400000000000000000000474251402665037500145220ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Fabio Tomat , 2017-2018 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Friulian (http://www.transifex.com/freedesktop/fwupd/language/fur/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fur\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s al à inzornaments dal firmware:" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Zontât" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Etât" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias a %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Permet di tornâ indaûr aes versions di firmware precedentis" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Permet di tornâ a instalâ lis versions dai firmware esistentis" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Un inzornament al à bisugne che si torni a inviâ il computer par finî." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Rispuint di sì a dutis lis domandis" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Apliche une patch binarie" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Atribûts" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Daûr a autenticâ…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "La autenticazion e je necessarie par tornâ indaûr ae version precedente dal firmware suntun dispositîf estraibil " #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "La autenticazion e je necessarie par tornâ indaûr ae version precedente dal firmware su cheste machine" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "La autenticazion e je necessarie par modificâ un rimot configurât, doprât pai inzornaments dal firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "La autenticazion e je necessarie par sblocâ un dispositîf" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "La autenticazion e je necessarie par inzornâ il firmware suntun dispositîf estraibil" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "La autenticazion e je necessarie par inzornâ il firmware su cheste machine" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "La autenticazion e je necessarie par inzornâ i checksum archiviâts pal dispositîf" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Costruìs il firmware doprant une ambient isolât - sandbox" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Anule" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Anulât" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Modificât" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "ID chip" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Sielç un dispositîf:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Sielç une publicazion:" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Al nete ducj i inzornaments programâts di fâ fûr rêt" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Al nete i risultâts dal ultin inzornament" #. TRANSLATORS: error message msgid "Command not found" msgstr "Comant no cjatât" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Convertìs il firmware tal formât DFU" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Cree une patch binarie doprant doi file" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opzions di debug" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Daûr a decomprimi…" #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Decifre i dâts dal firmware" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Descrizion" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Dispositîf zontât:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Dispositîf modificât:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositîf gjavât:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Dispositîfs che a son stâts inzornâts cun sucès:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Dispositîfs che no son stâts inzornâts ben:" #. success msgid "Done!" msgstr "Fat!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Al torne indaûr ae version precedente dal firmware suntun dispositîf" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Daûr a tornâ indaûr ae version precedente di %s de %s ae %s... " #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Daûr a discjariâ…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Scrîf jù i dâts SMBIOS di un file" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Scrîf jù i detais in merit a un file firmware" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Scrîf jù lis informazions sul schermi in merit a une patch binarie " #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Abilitât" #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Cifre i dâts dal firmware" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Scancele dute la cronologjie dai inzornaments dal firmware" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Daûr a scancelâ…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Jes dopo un piçul ritart" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Jes dopo che il motôr al à cjariât" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "No si è rivâts a analizâ i argoments" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Non file" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Firme non file" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI de base dal firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Servizi D-Bus inzornament firmware" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Demoni di inzornament firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Utilitât firmware" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "I metadâts dal firmware no son stâts inzornâts par %u zornade e a podaressin jessi vielis." msgstr[1] "I metadâts dal firmware no son stâts inzornâts par %u dîs e a podaressin jessi vielis." #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Cjatât" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Oten ducj i dispositîfs che a supuartin i inzornaments dal firmware" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Al oten detais su un file di firmware" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Al oten i rimots configurâts" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Al oten il hash di critografie dal firmware tirât jù" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Al oten la liste di inzornaments pal hardware tacât" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Al oten lis publicazions par un dispositîf" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Al oten i risultâts dal ultin inzornament" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "In polse…" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Instale un file firmware su chest hardware" msgid "Install old version of system firmware" msgstr "Instale une version vecje dal firmware di sisteme" msgid "Install signed device firmware" msgstr "Instasle firmware di dispositîf firmât" msgid "Install signed system firmware" msgstr "Instale firmware di sisteme firmât" msgid "Install unsigned device firmware" msgstr "Instale firmware di dispositîf cence firme" msgid "Install unsigned system firmware" msgstr "Instale firmware di sisteme cence firme" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Daûr a instalâ l'inzornament dal firmware…" msgid "Keyring" msgstr "Puarteclâfs" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Daûr a cjariâ…" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Unìs plui file firmware intun" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI metadata" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Firme dal URI dal metadata" msgid "Mode" msgstr "Modalitât" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Al modifiche un rimot furnît" msgid "Modify a configured remote" msgstr "Modifiche un rimot configurât" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitore il demoni pai events" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Non" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nissun hardware rilevât un funzionalitâts di inzornament dal firmware" msgid "OK" msgstr "Va ben" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Ignore i avertiments dal plugin" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Password" msgid "Permission denied" msgstr "Permès dineât" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Inserìs un numar di 0 a %u: " #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritât" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protocol" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Lei il firmware dal dispositîf intun file" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Lei il firmware di une partizion intun file" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Daûr a lei…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Inzorne i metadâts dal servidôr rimot" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Regjon" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Daûr a tornâ a instalâ %s cun %s... " #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Rimot" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID rimot" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Gjavât" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Sostituìs i dâts intun file di firmware esistent" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI segnalazion" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Al à bisugne de conession a internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Tornâ a inviâ cumò?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Daûr a tornâ a inviâ il dispositîf…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Torne ducj i ID dal hardware pe machine" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Planifiche la instalazion pe volte sucessive che si torne a inviâ cuant che al è pussibil" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Daûr a planificâ…" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Seriâl" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Met il non alternatîf sul file dal firmware" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Met il numar alternatîf sul file dal firmware" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Met la direzion dal element sul file dal firmware" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Met il ID dal prodot sul file dal firmware" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Met la version di publicazion sul file dal firmware" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Met la direzion dal firmware pe destinazion" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Met il ID dal produtôr sul file dal firmware" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Al stabilìs i metadâts suntun file firmware" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Condivît la conologjie dai firmware cui svilupadôrs" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Mostre versions di client e demoni" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostre opzions di debug" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mostre informazions di debug adizionâls" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Mostre la cronologjie dai inzoronaments dal firmware" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Mostre lis informazions prolissis dai plugin" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Stât" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Stât" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Sintesi" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Titul" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Gjenar" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "No cognossût" msgid "Unlock the device to allow access" msgstr "Sbloche il dispositîf par permeti l'acès" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Al sbloche il dispositîf pal acès al firmware" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Inzorne some di control - checksum" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Inzorne descrizion" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Inzorne posizion" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Inzorne non" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "Inzorne ID rimot" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Inzorne sintesi" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Inzorne version" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Il faliment dal inzornament al è un probleme cognossût, visite chest URL par vê plui informazions:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Inzornâ cumò?" msgid "Update the stored device verification information" msgstr "Inzorne lis informazions di verifiche dal dispositîf archiviadis" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Inzorne il metadât archiviât cui contignûts ROM atuâi" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Al inzorne ducj i firmware ae ultime version disponibile" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Daûr a inzornâ %s di %s a %s... " #. TRANSLATORS: remote filename base msgid "Username" msgstr "Non utent" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Daûr a verificâ…" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Version" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "In spiete…" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Scrîf il firmware dal file intal dispositîf" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Scrîf il firmware dal file intune partizion" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Daûr a scrivi…" fwupd-1.2.14/po/he.po000066400000000000000000000100011402665037500142760ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # dhead666 , 2015 # GenghisKhan , 2015 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Hebrew (http://www.transifex.com/freedesktop/fwupd/language/he/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: he\n" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "ישנם עדכוני קושחה עבור %s:" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "כינוי עבור %s" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "אימות משתמש נדרש לעדכון קושחה מערכת זו" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "סכום ביקורת" #. TRANSLATORS: error message msgid "Command not found" msgstr "פקודה לא נמצאה" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "אפשרויות ניפוי שגיאות" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "תיאור" #. success msgid "Done!" msgstr "הסתיים!" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "משנמך גרסת %s מ־%s ל־%s..." #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "יציאה לאחר השהייה קצרה" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "יציאה לאחר טעינת מנוע התכנה" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "נכשל בפענוח הארגומנטים" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "שירות D-Bus עדכון קושחה" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "שדון עדכון קושחה" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "מציג כל המכשירים התומכים בעדכוני קושחה" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "מציג פרטים אודות קובץ קושחה" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "מתקין קובץ קושחה בחומרה זו" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "לא אותרה חומרה בעלת יכולת עדכון קושחה" msgid "OK" msgstr "אישור" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "מתקין מחדש %s עם %s..." #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "הצג אפשרויות ניפוי שגיאות" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "הצג מידע ניפוי שגיאות מורחב" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "מעדכן %s מ־%s ל־%s..." #. TRANSLATORS: section header for release version number msgid "Version" msgstr "גרסא" fwupd-1.2.14/po/hi.po000066400000000000000000000075461402665037500143260ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Prashant Gupta , 2015 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Hindi (http://www.transifex.com/freedesktop/fwupd/language/hi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "%s का उपनाम " #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "फर्मवेयर अपडेट के लिए प्रमाणीकरण चाहिए " #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "डिबगिंग के विकल्प " #. success msgid "Done!" msgstr "हो गया !" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "%s की %s से %s तक अधोगति हो रही है " #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "थोड़ी देरी के बाद बहार जाएँ " #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "इंजन के लोड हो जाने पर बहार जाएँ " #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "आर्गुमेंट पार्स करने में असफल " #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "फर्मवेयर अपडेट डी-बस सेवा " #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "फर्मवेयर अपडेट का समर्थन करने वाली सभी युक्तियाँ प्राप्त करें " #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "फर्मवेयर फाइल की अधिक जानकारी प्राप्त करें " #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "फर्मवेयर फाइल को इस हार्डवेयर पर स्थापित करें " #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "अपडेट की क्षमता वाला हार्डवेयर उपलब्ध नहीं " #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "%s को %s से दोबारा स्थापित करा जा रहा है " #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "डिबगिंग के विकल्प दिखाए " #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "डिबगिंग की अतिरिक्त जानकारी दिखाएँ " #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "%s को %s से %s तक अपडेट करा जा रहा है " fwupd-1.2.14/po/hr.po000066400000000000000000001156221402665037500143320ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # FIRST AUTHOR , 2016 # gogo , 2016 # gogo , 2016-2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Croatian (http://www.transifex.com/freedesktop/fwupd/language/hr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hr\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minuta preostala" msgstr[1] "%.0f minute preostale" msgstr[2] "%.0f minuta preostalo" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s Potrošački pogon upravljanja (ME) nadopunjen" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s nadopuna upravljača" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s Korporativni pogon upravljanja (ME) nadopunjen" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s uređaj nadopunjen" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s ugrađeni upravljač nadopunjen" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s Pogon upravljanja (ME) nadopunjen" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s sustav nadopunjen" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s nadopuna" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s ima nadopune frimvera:" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dan" msgstr[1] "%u dana" msgstr[2] "%u dana" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u sat" msgstr[1] "%u sata" msgstr[2] "%u sati" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuta" msgstr[1] "%u minute" msgstr[2] "%u minuta" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekunda" msgstr[1] "%u sekunde" msgstr[2] "%u sekundi" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktiviraj uređaj" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktiviraj uređaje na čekanju" msgid "Activate the new firmware on the device" msgstr "Aktiviraj novi frimver na uređaju" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Aktivacija nadopune frimvera" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Aktivacija nadopune frimvera za" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Dodano" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Dob" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Slažete se s omogućavanjem udaljene lokacije?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Zamjena za %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Dopusti vraćanje starije inačice frimvera" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Dopusti ponovnu instalaciju frimvera postojeće inačice" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Nadopuna zahtijeva ponovno pokretanje za završetak." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "Nadopuna zahtijeva potpuno isključivanje sustava." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Odgovori 'da' na sva pitanja" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Primijeni binarnu zakrpu" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Primijeni nadopune frimvera" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Odobreni frimver:" msgstr[1] "Odobreni frimveri:" msgstr[2] "Odobreni frimveri:" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "Poveži DFU sposoban uređaj natrag u vremenu izvršavanja" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Prebaci u način frimvera" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Značajke" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Ovjeravanje..." #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Potrebna je ovjera za vraćanje starije inačicu frimvera na uklonjivom uređaju" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Potrebna je ovjera za vraćanje starije inačicu frimvera na ovom računalu" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Potrebna je ovjera za promjenu udaljene lokacije koja se koristi za nadopunu frimvera" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Potrebna je ovjera za promjenu podešavanja pozadinskog programa" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Potrebna je ovjera za postavljanje popisa odobrenih frimvera" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Potrebna je ovjera za potpisivanje podataka vjerodajnicom klijenta" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Potrebna je ovjera za prebacivanje na novu inačicu frimvera" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Potrebna je ovjera za otključavanje uređaja" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Potrebna je ovjera za nadopunu frimvera na uklonjivom uređaju" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Potrebna je ovjera za nadopunu frimvera na ovom računalu" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Potrebna je ovjera za nadopunu spremljenog kontrolnog zbroja uređaja" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Izgradi frimver u osiguranom okruženju" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Odustani" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Prekinuto" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Promijenjeno" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolni zbroj" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "ID čipa" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Odaberite uređaj:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Odaberi izdanje:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Cipher" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Uklanja sve nadopune zakazne za izvanmrežno nadopunjivanje" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Uklanja rezultate posljednje nadopune" #. TRANSLATORS: error message msgid "Command not found" msgstr "Naredba nije pronađena" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Pretvori firmver u DFU format" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Stvori binarnu zakrpu koristeći dvije datoteke" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU pomagalo" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Mogućnosti otklanjanja greške" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Raspakiravanje..." #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Dešifriraj podatke frimvera" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Opis" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Odspoji trenutno povezane DFU sposobne uređaje" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Prebaci u način učitača pokretanja" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID uređaja" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Uređaj dodan:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Uređaj promijenjen:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Uređaj uklonjen:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Uređaji koji su ispravno nadopunjeni:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Uređaji koji nisu ispravno nadopunjeni:" msgid "Disabled fwupdate debugging" msgstr "Onemogući fwupdate otklanjanje grešaka" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Onemogućuje zadane udaljene lokacije" #. TRANSLATORS: command line option msgid "Display version" msgstr "Prikaži inačicu" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Ne provjeravaj stare metapodatke" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "Ne provjeravaj za ponovno pokretanje nakon nadopune" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Ne provjeravaj neprijavljenu povijest" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Ne zapisuj bazu podataka povijesti" #. success msgid "Done!" msgstr "Završeno!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Vraća stariju inačicu frimvera na uređaju" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Vraćanje %s s inačice %s na inačicu %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Vraćanje %s na stariju inačicu…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Preuzimanje..." #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Ispiši opširnije podatke SMBIOS-a iz datoteke" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Ispiši opširnije pojedinosti o frimveru u datoteku" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Ispiši informacije o binarnoj zakrpi na zaslonu" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Određeni ESP nije ispravan" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Omogući podršku nadopune frimvera na podržanim sustavima" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Omogući ovu udaljenu lokaciju?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Omogućeno" msgid "Enabled fwupdate debugging" msgstr "Omogući fwupdate otklanjanje grešaka" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Omogućuje zadane udaljene lokacije" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Omogućujete ovu funkcionalnost na vlastiti rizik, što znači da morate kontaktirati svog izvornog proizvođača opreme u vezi problema uzrokovanih tim nadopunama. Samo probleme sa samim postupkom nadopune treba prijaviti na $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Ovu udaljenu lokaciju omogućavate na vlastiti rizik." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Šifriraj podatke frimvera" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Obriši svu povijest nadopune frimvera" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Brisanje..." #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Izađi nakon kratke odgode" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Izađi nakon učitavanja pogona" #. TRANSLATORS: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "Neuspjelo povezivanje s pozadinskim programom" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "Neuspjelo preuzimanje zbog ograničenja poslužitelja" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Neuspjeli prikaz uređaja na čekanju" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Neuspjela instalacija nadopune frimvera" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Neuspjelo učitavanje okolnosti uređaja" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Neuspjela obrada argumenata" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Neuspjelo ponovno pokretanje" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Dohvaćanje datoteke" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "Dohvaćanje frimvera" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Dohvaćanje metapodataka" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Dohvaćanje potpisa" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Naziv datoteke" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Potpis naziva datoteke" #. TRANSLATORS: program name msgid "Firmware Agent" msgstr "Firmver agent" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Osnovni URI frimvera" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Firmver nadopuna D-Bus usluge" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Pozadinski program nadopune frimvera" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmver pomagalo" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Metapodaci firmvera nisu nadopunjeni %u dan i možda nisu najnoviji." msgstr[1] "Metapodaci firmvera nisu nadopunjeni %u dana i možda nisu najnoviji." msgstr[2] "Metapodaci firmvera nisu nadopunjeni %u dana i možda nisu najnoviji." msgid "Firmware updates are not supported on this machine." msgstr "Nadopuna frimvera nije podržana na ovom računalu." msgid "Firmware updates are supported on this machine." msgstr "Nadopuna frimvera je podržana na ovom računalu." #. TRANSLATORS: section header for firmware flags msgid "Flags" msgstr "Oznake" msgid "Force the action ignoring all warnings" msgstr "Prisili radnju zanemarivanja svih upozorenja" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Pronađen" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "Prikaži sve uređaje prema topologiji sustava" #. TRANSLATORS: command description msgid "Get all devices and possible releases" msgstr "Prikaži sve uređaje i moguća izdanja" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Prikaži sve uređaje koji podržavaju nadopunu frimvera" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Prikaži sve omogućene priključke registrirane sa sustavom" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Prikaži pojedinosti datoteke frimvera" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Prikazuje udaljena podešavanja" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Prikaži kriptografsku jedinstvenu vrijednost opširnijih informacija frimvera" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware." msgstr "Prikazuje popis odobrenih frimvera." #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Prikaži popis nadopuna za povezani hardver" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Prikazuje izdanja za uređaj" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Prikaži rezultate posljednje nadopune" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Mirovanje..." #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Instaliraj blob frimvera na uređaj" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Instaliraj datoteku frimvera na ovaj uređaj" msgid "Install old version of system firmware" msgstr "Instaliraj stariju inačicu frimvera sustava" msgid "Install signed device firmware" msgstr "Instaliraj frimver potpisan uređajem" msgid "Install signed system firmware" msgstr "Instaliraj frimver potpisan sustavom" msgid "Install unsigned device firmware" msgstr "Instaliraj frimver nepotpisan uređajem" msgid "Install unsigned system firmware" msgstr "Instaliraj frimver nepotpisan sustavom" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Instalacija frimvera…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Instalacija nadopune frimvera..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Instaliram na %s…" msgid "Keyring" msgstr "Skup ključeva" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Preostalo je manje od minute" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Frimver Usluga Linux Proizvođača (LVFS) (stabilni frimver)." msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Frimver Usluga Linux Proizvođača (LVFS) (testni frimver)." #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "Prikaži trenutno povezane DFU sposobne uređaje" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Prikaži nadopune podržanih frimvera" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Učitavanje..." #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Ručno dopusti određene priključke" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Spoji više frimver datoteka u jednu" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI metapodataka" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Potpis URI metapodataka" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metapodaci se mogu dobiti od Frimver Usluge Linux Proizvođača (LVFS)." #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Pozadinski program i klijent se ne podudaraju, umjesto koristite %s" msgid "Mode" msgstr "Način" #. TRANSLATORS: sets something in daemon.conf msgid "Modifies a daemon configuration value." msgstr "Prilagođava vrijednost podešavanja pozadinskog programa." #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Promjena zadane udaljene lokacije" msgid "Modify a configured remote" msgstr "Promijeni zadanu udaljenu lokaciju" msgid "Modify daemon configuration" msgstr "Prilagodi podešavanje pozadinskog programa" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Nadgledaj događaje pozadinskim programom" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Naziv" msgid "No action specified!" msgstr "Nema zadane radnje!" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nema otkrivenog hardvera s mogućnosti nadopune frimvera" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nema pronađenih priključaka" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Trenutno nema omogućenih udaljenih lokacija stoga nema dostupnih metapodataka." #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nema primijenjenih nadopuna" msgid "OK" msgstr "U redu" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Zaobiđi upozorenja priključka" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Zaobiđi zadanu ESP putanju" #. TRANSLATORS: command line option msgid "Override warnings and force the action" msgstr "Zaobiđi upozorenja i prisili radnju" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Lozinka" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Postotak završetka" msgid "Permission denied" msgstr "Dozvola odbijena" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Odaberite broj od 0 do %u: " msgid "Print the version number" msgstr "Prikaži broj inačice" msgid "Print verbose debug statements" msgstr "Zapisuj opširniji izvještaj otklanjanja grešaka" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritet" msgid "Proceed with upload?" msgstr "Nastavi sa slanjem?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokol" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Zatraži podršku nadopune frimvera" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Okolnosti uređaja" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Očitaj frimver iz uređaja u datoteku" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Očitaj frimver iz jedne particije u datoteku" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Čitanje..." #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Ponovno pokretanje…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Osvježi metapodatke s udaljenog poslužitelja" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Regija" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Ponovna instalacija %s inačice %s... " #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Udaljena lokacija" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Udaljeni ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Uklonjeno" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Zamijeni podatke u postojećoj datoteci frimvera" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI izvještaja" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Potreban je pristup internetu" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "Vrati na izvorno DFU uređaj" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Ponovno pokreni odmah?" #. TRANSLATORS: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Ponovno pokreni pozadinski program kako bi se promjene primijenile?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Ponovno pokretanje uređaja..." #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Vrati sve ID-ove hardvera za uređaj" #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Pokreni rutinu čišćenja sastavljanja priključka kada se koristi install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Pokreni rutinu pripreme sastavljanja priključka kada se koristi install-blob" msgid "Runtime" msgstr "Vrijeme trajanja" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "Spremi stanje uređaja u JSON datoteku između izvršavanja" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Zakaži instalaciju pri sljedećem pokretanju kada je moguće" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Zakazivanje..." #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Serijski broj" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Postavi zamjenski naziv u datoteku firmvera" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Postavi zamjenski broj u datoteku firmvera" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Postavi adresu elementa na datoteku frimvera" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Postavi ID proizvoda u datoteku firmvera" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Postavi inačicu izdanja u datoteku firmvera" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Postavi oznaku otklanjanja grešaka tijekom nadopune" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Postavi veličinu frimvera za metu" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Postavi ID proizvođača u datoteku firmvera" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Postavlja metapodatke u datoteku frimvera" msgid "Sets the list of approved firmware" msgstr "Postavlja popis odobrenih frimvera" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware." msgstr "Postavlja popis odobrenih frimvera" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Podijeli povijest frimvera sa razvijateljima" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Prikaži inačicu klijenta i pozadinskog programa" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Prikaži dodatne informacije pozadinskog programa za određenu domenu" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Prikaži informacije otklanjanja greške za sve domene" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Prikaži mogućnosti otklanjanja greške" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Prikaži uređaje koji se ne mogu nadopuniti" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Prikaži dodatne informacije otklanjanja grešaka" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Prikaži povijest nadopune metapodataka" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Prikaži dodatne informacije priključka" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Prikaži zapis otklanjanja grešaka posljednjeg pokušaja nadopune" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Prikaži informacije stanja nadopune frimvera" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Odmah isključi?" msgid "Sign data using the client certificate" msgstr "Potpiši podatke koristeći vjerodajnicu klijenta" msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Potpiši podatke koristeći vjerodajnicu klijenta" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Potpiši poslane podatke s vjerodajnicom klijenta" msgid "Signature" msgstr "Potpis" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Odredi ID-ove Proizvođača/Proizvoda DFU uređaja" msgid "Specify the number of bytes per USB transfer" msgstr "Odredi broj bajtova po USB prijenosu" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Stanje" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Stanje" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Sažetak" msgid "Target" msgstr "Odredište" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS (Frimver Usluga Linux Proizvođača) je besplatna usluga koja djeluje kao neovisna pravna osoba i nema veze sa $OS_RELEASE:NAME$. Vaš distributer možda nije provjerio nadopune frimvera za kompatibilnost s vašim sustavom ili priključenim uređajima. Svi frimveri su pružani od strane izvornih proizvođača opreme." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Ne postoji odobreni frimver." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Ovaj program možda radi ispravno samo ako je pokrenut kao korijenski korisnik" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Ova udaljena lokacija sadrži frimver koji nije zabranjen, ali se još uvijek testira od strane proizvođača hardvera. Provjerite da imate način na ručno vraćanje starije inačice frimvera ako nadopuna frimvera ne uspije." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Ovaj alat može koristiti samo korijenski korisnik" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Naziv" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Veličina prijenosa" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Vrsta" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI firmver pomagalo" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Nepoznat" msgid "Unlock the device to allow access" msgstr "Otključaj uređaj za dopuštenje pristupa" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Otključava uređaj za pristup frimvera" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Ukloni oznaku otklanjanja grešaka tijekom nadopune" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Nepodržana inačica pozadinskog programa %s, inačica klijenta je %s" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Kontrolni zbroj nadopune" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Opis nadopune" #. TRANSLATORS: section header for the amount #. * of time it takes to install the update msgid "Update Duration" msgstr "Trajanje nadopune" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Lokacija nadopune" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Naziv nadopune" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "Udaljeni ID nadopune" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Sažetak nadopune" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Inačica nadopune" #. TRANSLATORS: command description msgid "Update all devices that match local metadata" msgstr "Nadopuni sve uređaje koji se podudaraju s lokalnim metapodacima" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Neuspješna nadopuna je poznat problem, posjetite ovaj URL za više informacija:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Nadopuni odmah?" msgid "Update the stored device verification information" msgstr "Nadopuni spremljenu informaciju provjere uređaja" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Nadopuni pohranjene metapodatke s trenutnim sadržajem ROM-a" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Nadopuni pohranjene metapodatke s trenutnim sadržajem" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Nadopuni sav frimver na najnovije dostupne inačice" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Nadopuna %s s inačice %s na inačicu %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Nadopunjujem %s…" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Pošalji poruku:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Pošalji izvještaj odmah?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Slanje izvještaja frimvera pomaže proizvođačima hardvera brzo otkrivanje nedostatka i brzu nadopunu na stvarnim uređajima." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Korisničko ime" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Provjeravanje..." #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Inačica" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Čekanje…" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "Nadgledaj odspajanje DFU uređaja" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Nadgledaj promjene hardvera" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Zapiši frimver iz datoteke u uređaj" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Zapiši frimver iz datoteke u jednu particiju uređaja" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Zapisivanje..." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Vaš distributer možda nije provjerio nadopune frimvera za kompatibilnost s vašim sustavom ili priključenim uređajima." fwupd-1.2.14/po/hu.po000066400000000000000000001134341402665037500143340ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Balázs Meskó , 2017-2019 # Balázs Úr, 2015-2018 # Gabor Kelemen , 2016 # kelemeng , 2016 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Hungarian (http://www.transifex.com/freedesktop/fwupd/language/hu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hu\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f perc van hátra" msgstr[1] "%.0f perc van hátra" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s vezérlőfrissítés" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s eszközfrissítés" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s beágyazottvezérlő-frissítés" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s ME frissítés" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s rendszerfrissítés" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s frissítés" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s firmware frissítésekkel rendelkezik:" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u nap" msgstr[1] "%u nap" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u óra" msgstr[1] "%u óra" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u perc" msgstr[1] "%u perc" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u másodperc" msgstr[1] "%u másodperc" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Eszközök aktiválása" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Függőben lévő eszközök aktiválása" msgid "Activate the new firmware on the device" msgstr "Az új firmware aktiválása az eszközön" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Firmware frissítés aktiválása" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Firmware frissítés aktiválása ennél:" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Hozzáadva" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Kor" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Beleegyezik és engedélyezi a távoli tárolót?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Álnév ehhez: %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Firmware verziók visszafejlesztésének engedélyezése" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Meglévő firmware verziók újratelepítésének engedélyezése" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Egy frissítés újraindítást igényel a befejezéshez." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "Egy frissítés a rendszer újraindítását igényel a befejezéshez." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Igen az összes kérdésre" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Bináris folt alkalmazása" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Firmware-frissítések alkalmazása" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Jóváhagyott firmware:" msgstr[1] "Jóváhagyott firmwarek:" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "DFU-képes eszköz visszacsatolása a futtatókörnyezethez" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Csatlakoztatás a firmware módhoz" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Attribútumok" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Hitelesítés…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Hitelesítés szükséges a firmware visszafejlesztéséhez egy cserélhető eszközön" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Hitelesítés szükséges a firmware visszafejlesztéséhez ezen a gépen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Hitelesítés szükséges a firmware frissítéshez beállított távoli tároló módosításához." #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Hitelesítés szükséges a jóváhagyott firmwarek listájának beállításához" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Hitelesítés szükséges az adatok ügyféltanúsítvánnyal történő aláírásához" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Hitelesítés szükséges az új firmware verzióra váltáshoz" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Hitelesítés szükséges az eszköz feloldásához" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Hitelesítés szükséges a firmware frissítéséhez egy cserélhető eszközön" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Hitelesítés szükséges a firmware frissítéséhez ezen a gépen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Hitelesítés szükséges az eszköz tárolt ellenőrzőösszegeinek frissítéséhez" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Firmware összeállítása egy homokozóban" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Mégse" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Megszakítva" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Módosítva" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Ellenőrzőösszeg" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "Chipazonosító" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Válasszon eszközt:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Válasszon kiadást:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Titkosító" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Törli a beütemezett offline frissítéseket" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Törli a legutóbbi frissítésből származó eredményeket" #. TRANSLATORS: error message msgid "Command not found" msgstr "A parancs nem található" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Firmware átalakítása DFU formátumra" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Bináris folt készítése két fájl használatával" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU segédprogram" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Hibakeresési beállítások" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Kibontás…" #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Firmware adatok visszafejtése" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Leírás" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Jelenleg csatlakoztatott DFU-képes eszközök leválasztása" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Leválasztás a indítóbetöltő módhoz" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Eszközazonosító" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Eszköz hozzáadva:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Eszköz módosítva:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Eszköz eltávolítva:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Eszközök, melyek sikeresen frissítve lettek:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Eszközök, melyek nem lettek helyesen frissítve:" msgid "Disabled fwupdate debugging" msgstr "Fwupdate hibakeresés letiltva" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Letiltja az adott távoli tárolót" #. TRANSLATORS: command line option msgid "Display version" msgstr "Verzió megjelenítése" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Ne ellenőrizze a régi metaadatokat" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "Ne ellenőrizze az újraindítást a frissítés után" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Ne ellenőrizze a nem jelentett előzményeket" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Ne írjon az előzmények adatbázisába" #. success msgid "Done!" msgstr "Kész!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "A firmware visszafejlesztése az eszközön" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "%s visszafejlesztése: %s -> %s…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "%s visszaállítása…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Letöltés…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "SMBIOS adatok kiírása egy fájlból" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Részletek kiírása egy firmware fájlról" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Információk kiírása a képernyőre a bináris foltról" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "A megadott ESP érvénytelen" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Firmware frissítési támogatás engedélyezése a támogatott rendszereken" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Engedélyezi ezt a távoli tárolót?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Engedélyezve" msgid "Enabled fwupdate debugging" msgstr "Fwupdate hibakeresés engedélyezve" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Engedélyezi az adott távoli tárolót" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Csak a saját felelősségére engedélyezze ezt a funkciót, amely azt jelenti, hogy az eredeti termék gyártójával kell kapcsolatba lépnie, ha problémát okoz a frissítés. Csak a frissítési folyamattal kapcsolatos problémákat jelentse itt be: $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "A saját felelősségére engedélyezze ezt a távoli tárolót." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Firmware adatok titkosítása" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Firmware frissítési előzmények törlése" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Törlés…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Kilépés egy kis késleltetés után" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Kilépés a motor betöltődése után" #. TRANSLATORS: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "A démonhoz kapcsolódás sikertelen" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "A letöltés kiszolgálókorlát miatt meghiúsult" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "A függőben lévő eszközök lekérése sikertelen" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "A firmware frissítés telepítése sikertelen" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "A trükkök betöltése sikertelen" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Nem sikerült feldolgozni az argumentumokat" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Újraindítás sikertelen" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Az indítóképernyő módjának beállítása sikertelen" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Fájl lekérése" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "Firmware lekérése" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Metaadatok lekérése" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Aláírás lekérése" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Fájlnév" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Fájlnév aláírása" #. TRANSLATORS: program name msgid "Firmware Agent" msgstr "Firmware ügynök" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Firmware kiindulópont URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Firmware frissítés D-Bus szolgáltatás" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Firmware frissítő démon" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmware segédprogram" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "A firmware metaadatok %u napja nem lettek frissítve, és lehet hogy elavultak." msgstr[1] "A firmware metaadatok %u napja nem lettek frissítve, és lehet hogy elavultak." msgid "Firmware updates are not supported on this machine." msgstr "A firmware frissítések nem támogatottak ezen a gépen." msgid "Firmware updates are supported on this machine." msgstr "A firmware frissítések támogatottak ezen a gépen." #. TRANSLATORS: section header for firmware flags msgid "Flags" msgstr "Jelzők" msgid "Force the action ignoring all warnings" msgstr "A művelet erőltetése, az összes figyelmeztetés mellőzése" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Megtalálva" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "Az összes eszköz lekérdezése a rendszer topológiájának megfelelően" #. TRANSLATORS: command description msgid "Get all devices and possible releases" msgstr "Összes eszköz és a lehetséges kiadások lekérése" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Minden eszköz lekérése, amelyek támogatják a firmware frissítéseket" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Az összes rendszeren regisztrált és engedélyezett bővítmény lekérdezése" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Részleteket kér le egy firmware fájlról" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Lekéri a beállított távoli tárolókat" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Lekéri a kiírt firmware kriptográfiai hash-ét" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware." msgstr "Lekéri a jóváhagyott firmwarek listáját." #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "A frissítések listáját kéri le a csatlakoztatott hardverhez" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Lekéri az eszközhöz tartozó kiadásokat" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "A legutóbbi frissítésből származó eredményeket kéri le" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "Azonosító" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Üresjárat…" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Firmware blob telepítése egy eszközre" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Egy firmware fájl telepítése ezen a hardveren" msgid "Install old version of system firmware" msgstr "A rendszer firmware régi verziójának telepítése" msgid "Install signed device firmware" msgstr "Aláírt eszköz firmware telepítése" msgid "Install signed system firmware" msgstr "Aláírt rendszer firmware telepítése" msgid "Install unsigned device firmware" msgstr "Nem aláírt eszköz firmware telepítése" msgid "Install unsigned system firmware" msgstr "Nem aláírt rendszer firmware telepítése" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Firmware telepítése…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Firmware frissítés telepítése…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "%s telepítése…" msgid "Keyring" msgstr "Kulcstartó" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Kevesebb mint egy perc van hátra" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux gyártói firmware szolgáltatás (stabil firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux gyártói firmware szolgáltatás (teszt firmware)" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "Jelenleg csatlakoztatott DFU-képes eszközök felsorolása" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "A támogatott firmware-frissítések listázása" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Betöltés…" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Egyes bővítmények kézi fehérlistára tétele" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Több firmware fájl egyesítése" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metaadat URI" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Metaadat URI aláírása" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "A metaadatok nem szerezhetőek be a Linux gyártói firmware szolgáltatásból." msgid "Mode" msgstr "Mód" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "A megadott távoli tároló módosítása" msgid "Modify a configured remote" msgstr "A beállított távoli tároló módosítása" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "A démon eseményeinek figyelése" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Név" msgid "No action specified!" msgstr "Nincs művelet megadva!" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nem észlelhető firmware frissítési képességgel rendelkező hardver" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nem található bővítmény" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Jelenleg nincsenek engedélyezett távoli tárolók, így nem érhetőek el metaadatok." #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nem lett frissítés alkalmazva" msgid "OK" msgstr "OK" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Bővítmény figyelmeztetés felülbírálása" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Az alapértelmezett ESP útvonal felülbírálása" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Jelszó" msgid "Payload" msgstr "Tartalom" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Százalék kész" msgid "Permission denied" msgstr "Hozzáférés megtagadva" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Adjon meg egy számot 0 és %u között:" msgid "Print the version number" msgstr "A verziószám kiírása." msgid "Print verbose debug statements" msgstr "Részletes hibakeresési utasítások kiírása" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritás" msgid "Proceed with upload?" msgstr "Folytatja a feltöltést?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokoll" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Firmware frissítési támogatás lekérdezése" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Kompatibilitási trükkök" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Firmware beolvasása eszközről egy fájlba" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Firmware beolvasása egy partícióról fájlba" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Olvasás…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Újraindítás…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Metaadatok frissítése a távoli kiszolgálóról" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Régió" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "%s újratelepítése ezzel: %s…" #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Távoli tároló" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Távoli azonosító" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Eltávolítva" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Adatok cseréje egy meglévő firmware fájlban" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Jelentési URI" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Internetkapcsolat szükséges" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "DFU eszköz visszaállítása" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Újraindítja most?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Eszköz újraindítása…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "A géphez tartozó összes hardverazonosító visszaadása" msgid "Runtime" msgstr "Futtatókörnyezet" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "Eszköz állapotának mentése JSON fájlba a végrehajtások között" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Telepítés ütemezése a következő újraindításkor, ha lehetséges" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Ütemezés…" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Sorozat" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Alternatív név beállítása a firmware fájlon" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Alternatív szám beállítása a firmware fájlon" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Elem címének beállítása a firmware fájlban" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Termékazonosító beállítása a firmware fájlon" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Kiadási verzió beállítása a firmware fájlon" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "A hibakeresési jelző beállítása frissítéskor" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "A firmware méretének beállítása a célhoz" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Gyártóazonosító beállítása a firmware fájlon" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Metaadatok beállítása egy firmware fájlon" msgid "Sets the list of approved firmware" msgstr "Beállítja a jóváhagyott firmwarek listáját" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware." msgstr "Beállítja a jóváhagyott firmwarek listáját." #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Firmware frissítése előzmények megosztása a fejlesztőkkel" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Ügyfél és démon verziók megjelenítése" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Hibakeresési beállítások megjelenítése" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Eszközök, melyek nem frissíthetőek" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "További hibakeresési információk megjelenítése" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Firmware frissítési előzmények megtekintése" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Bővítmény bőbeszédű információinak megjelenítése" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "A legutóbb megpróbált frissítés hibakeresési naplójának megjelenítése" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "A firmware frissítési állapot információinak megjelenítése" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Leállítja most?" msgid "Sign data using the client certificate" msgstr "Adatok aláírása az ügyféltanúsítvánnyal" msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Adatok aláírása az ügyféltanúsítvánnyal" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Feltöltött adatok aláírása az ügyféltanúsítvánnyal" msgid "Signature" msgstr "Aláírás" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Adja meg a DFU eszköz gyártó-/termékazonosítóját" msgid "Specify the number of bytes per USB transfer" msgstr "Adja meg az USB átvitelek bájtjainak számát" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Állapot" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Állapot" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Összegzés" msgid "Target" msgstr "Cél" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "Az LVFS egy ingyenes szolgáltatás, amely független jogi entitásként működik, és nincs kapcsolata a $OS_RELEASE:NAME$ operációs rendszerrel. A disztribúció szállítója nem biztos, hogy ellenőrízte kompatibilitási szempontból a firmware frissítést. Mindent firmware-t csak az eredeti termék gyártója biztosít." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Nincs jóváhagyott firmware." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Ez a program jelenleg lehet, hogy csak rendszergazdaként működik" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Ez a távoli tároló olyan firmware-t tartalmaz, amelyre nem vonatkozik embargó, de még teszteli a harvergyártó. Érdemes biztosítani a firmware kézi visszaállításáról, ha a firmware frissítése meghiúsul." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Ezt az eszközt csak a root felhasználó használhatja" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Cím" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Átviteli méret" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Típus" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI firmware segédprogram" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Ismeretlen" msgid "Unlock the device to allow access" msgstr "Eszköz feloldása hozzáférés engedélyezéséhez" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Eszköz feloldása a firmware eléréséhez" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "A hibakeresési jelző kikapcsolása frissítéskor" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Frissítés ellenőrzőösszege" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Frissítés leírása" #. TRANSLATORS: section header for the amount #. * of time it takes to install the update msgid "Update Duration" msgstr "Frissítés hossza" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Frissítés helye" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Frissítés neve" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "Távoli azonosító frissítése" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Frissítés összegzése" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Frissítés verziója" #. TRANSLATORS: command description msgid "Update all devices that match local metadata" msgstr "Az összes olyan eszköz frissítése, amely illeszkedik a helyi metaadatokra" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "A feltöltési hiba ismert probléma, további információkért látogassa meg ezt az URL-t:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Frissíti most?" msgid "Update the stored device verification information" msgstr "A tárolt eszközellenőrzési információk frissítése" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "A tárolt metaadatok frissítése a jelenlegi ROM tartalmával" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "A tárolt metaadatok frissítése a jelenlegi tartalommal" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Minden firmware-t az elérhető legfrissebb verziókra frissít" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "%s frissítése: %s -> %s…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s frissítése…" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Feltöltési üzenet:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Feltölti most a jelentést?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "A firmware jelentések segítenek a hardvergyártóknak, hogy gyorsan azonosítsák a hibás és sikeres frissítéseket valós eszközökön." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Felhasználónév" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Ellenőrzés…" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Verzió" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Várakozás…" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "DFU-eszközök menet közbeni csatlakoztatásának figyelése" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Hardverváltozások figyelése" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Firmware írása fájlból egy eszközre" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Firmware írása fájlból egy partícióra" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Írás…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "A disztribúció szállítója nem biztos, hogy ellenőrízte a firmware frissítés kompatibilitását a rendszerével és a kapcsolódó eszközeivel." fwupd-1.2.14/po/id.po000066400000000000000000000525331402665037500143160ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Andika Triwidada , 2017-2018 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Indonesian (http://www.transifex.com/freedesktop/fwupd/language/id/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: id\n" "Plural-Forms: nplurals=1; plural=0;\n" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s memiliki pemutakhiran firmware:" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Ditambahkan" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Usia" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias ke %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Izinkan penuruntingkatan versi firmware" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Izinkan pemasangan ulang versi firmware yang telah ada" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Suatu pembaruan memerlukan boot ulang agar lengkap." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Jawab ya untuk semua pertanyaan" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Terapkan sebuah patch biner" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "Cantolkan kembali perangkat mampu-DFU ke runtime" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Atribut" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Mengotentikasi..." #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Otentikasi diperlukan untuk menuruntingkatkan firmware pada perangkat lepas pasang" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Otentikasi diperlukan untuk menuruntingkatkan firmware pada mesin ini" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Otentikasi diperlukan untuk mengubah sebuah remote yang ditata yang dipakai untuk pembaruan firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Otentikasi diperlukan untuk membuka kunci suatu perangkat" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Otentikasi diperlukan untuk memutakhirkan firmware pada perangkat lepas pasang" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Otentikasi diperlukan untuk memutakhirkan firmware pada mesin ini" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Otentikasi diperlukan untuk memutakhirkan checksum tersimpan bagi perangkat" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Bangun firmware memakai suatu sandbox" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Dibatalkan" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Diubah" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "ID Chip" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Pilih suatu peranti:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Pilih sebuah rilis:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Cipher" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Bersihkan semua pembaruan yang dijadwalkan untuk diperbarui luring" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Bersihkan hasil dari pemutakhiran terakhir" #. TRANSLATORS: error message msgid "Command not found" msgstr "Perintah tidak ditemukan" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Konversikan firmware ke format DFU" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Buat suatu patch biner mamakai dua berkas" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Utilitas DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opsi Pengawakutuan" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Mendekompresi..." #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Dekripsikan data firmware" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Deskripsi" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Lepas perangkat mampu-DFU yang kini tercantol" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Perangkat ditambahkan:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Perangkat diubah:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Perangkat dilepas:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Peranti yang sukses diperbarui:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Peranti yang tidak diperbarui dengan benar:" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Jangan periksa untuk metadata lama" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "Jangan periksa untuk boot ulang setelah pembaruan" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Jangan periksa untuk riwayat yang tak dilaporkan" #. success msgid "Done!" msgstr "Selesai!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Turuntingkatkan firmware pada suatu peranti" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Menuruntingkatkan %s dari %s ke %s..." #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Sedang mengunduh..." #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Curahkan data SMBIOS dari suatu berkas" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Curahkan rincian tentang suatu berkas firmware" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Curahkan informasi tentang suatu patch biner ke layar" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Difungsikan" #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Enkripsikan data firmware" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Hapus semua riwayat pembaruan firmware" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Menghapus..." #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Keluar setelah tundaan sejenak" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Keluar setelah mesin telah dimuat" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Gagal memuat quirk" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Gagal mengurai argumen" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Sedang mengambil berkas" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "Sedang mengambil firmware" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Sedang mengambil metadata" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Sedang mengambil tanda tangan" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Nama Berkas" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Tanda Tangan Nama Berkas" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI Basis Firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Layanan D-Bus Pemutakhiran Firmware" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Daemon Pemutakhiran Firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Utilitas Firmware" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Metadata firmware belum diperbarui selama %uhari dan mungkin tidak mutakhir." #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Ditemukan" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Dapatkan semua perangkat yang mendukung pemutakhiran firmware" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Dapatkan rincian tentang suatu berkas firmware" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Dapatkan remote-remote yang terkonfigurasi" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Dapatkan hash kriptografis dari firmware yang dicurahkan" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Dapatkan daftar pemutakhiran bagi perangkat keras yang tersambung" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Dapatkan rilis-rilis bagi sebuah peranti" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Dapatkan hasil dari pemutakhiran terakhir" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Menganggur..." #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Pasang suatu berkas firmware pada perangkat keras ini" msgid "Install old version of system firmware" msgstr "Pasang versi lama dari firmware sistem" msgid "Install signed device firmware" msgstr "Pasang firmware perangkat yang ditandatangani" msgid "Install signed system firmware" msgstr "Pasang firmware sistem yang ditandatangani" msgid "Install unsigned device firmware" msgstr "Pasang firmware perangkat yang tak ditandatangani" msgid "Install unsigned system firmware" msgstr "Pasang firmware sistem yang tak ditandatangani" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Sedang memasang pembaruan firmware..." msgid "Keyring" msgstr "Keyring" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "Tampilkan daftar perangkat mampu-DFU yang kini tercantol" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Memuat..." #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Gabungkan beberapa berkas firmware menjadi satu" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI Metadata" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Tanda Tangan URI Metadata" msgid "Mode" msgstr "Mode" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Mengubah suatu remote yang diberikan" msgid "Modify a configured remote" msgstr "Ubah suatu remote yang ditata" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Pantau daemon untuk kejadian" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Nama" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Tidak terdeteksi perangkat keras dengan kapabilitas pemutakhiran firmware" msgid "OK" msgstr "OK" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Timpa peringatan plugin" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Kata Sandi" msgid "Payload" msgstr "Payload" msgid "Permission denied" msgstr "Izin ditolak" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritas" msgid "Proceed with upload?" msgstr "Lanjutkan mengunggah?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokol" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Keanehan" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Baca firmware dari perangkat ke dalam suatu berkas" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Baca firmware dari satu partisi ke dalam suatu berkas" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Membaca..." #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Segarkan metadata dari server remote" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Wilayah" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Memasang ulang %s dengan %s..." #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Remote" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID Remote" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Dihapus" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Gantikan data dalam suatu berkas firmware yang telah ada" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI Lapor" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Memerlukan koneksi internet" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "Reset suatu peranti DFU" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Mulai ulang sekarang?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Memulai ulang perangkat..." #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Kembalikan semua ID perangkat keras bagi mesin" msgid "Runtime" msgstr "Runtime" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Jadwalkan instalasi untuk boot ulang selanjutnya bila mungkin" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Menjadwalkan..." #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Serial" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Atur nama alternatif pada berkas firmware" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Atur nomor alternatif pada berkas firmware" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Atur alamat elemen pada berkas firmware" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Atur ID produk pada berkas firmware" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Atur versi rilis pada berkas firmware" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Atur ukuran firmware bagi target" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Atur ID vendor pada berkas firmware" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Atur metadata pada suatu berkas firmware" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Bagikan riwayat firmware dengan para pengembang" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Tampilkan versi daemon dan klien" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Tampilkan opsi pengawakutuan" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Tampilkan informasi pengawakutuan ekstra" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Tampilkan riwayat pembaruan firmware" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Tampilkan informasi rinci pengaya" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Keadaan" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Status" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Ringkasan" msgid "Target" msgstr "Target" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Judul" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Ukuran Transfer" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipe" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Tidak diketahui" msgid "Unlock the device to allow access" msgstr "Buka kunci perangkat untuk mengizinkan akses" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Buka kunci perangkat bagi akses firmware" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Mutakhirkan Checksum" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Mutakhirkan Keterangan" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Mutakhirkan Lokasi" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Nama Pembaruan" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "Mutakhirkan ID Remote" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Ringkasan Pembaruan" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Mutakhirkan Versi" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Kegagalan pembaruan adalah masalah yang telah diketahui, kunjungi URL ini untuk informasi lebih lanjut:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Perbarui sekarang?" msgid "Update the stored device verification information" msgstr "Mutakhirkan informasi verifikasi perangkat yang tersimpan" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Mutakhirkan metadata tersimpan dengan isi ROM saat ini" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Mutakhirkan semua firmware ke versi terbaru yang tersedia" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Memutakhirkan %s dari %s ke %s..." #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Pesan unggah:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Unggah laporan sekarang?" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nama Pengguna" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifikasi..." #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Versi" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Menunggu..." #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "Amati perangkat DFU yang sedang di-hotplug" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Tulis firmware dari berkas ke dalam perangkat" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Tulis firmware dari berkas ke dalam suatu partisi" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Menulis..." fwupd-1.2.14/po/it.po000066400000000000000000001163661402665037500143430ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Gianvito Cavasoli , 2016 # Milo Casagrande , 2017-2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Italian (http://www.transifex.com/freedesktop/fwupd/language/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Manca %.0f minuto" msgstr[1] "Mancano %.0f minuti" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Aggiornamento Consumer ME di %s" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Aggiornamento unità di controllo %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Aggiornamento Coporate ME di %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Aggiornamento dispositivo %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Aggiornamento unità di controllo integrata di %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Aggiornamento ME di %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Aggiornamento sistema %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Aggiornamento di %s" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s ha degli aggiornamenti del firmware:" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u giorno" msgstr[1] "%u giorni" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u ora" msgstr[1] "%u ore" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuto" msgstr[1] "%u minuti" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u secondo" msgstr[1] "%u secondi" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Attiva dispositivi" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Attiva i dispositivi in attesa" msgid "Activate the new firmware on the device" msgstr "Attiva il nuovo firmware sul dispositivo" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Attivazione aggiornamento firmware" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Attivazione aggiornamento firmware per" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Aggiunto" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Età" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Accettare e abilitare il remoto?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias di %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Consente di tornare alle precedenti versioni del firmware" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Consente di reinstallare versioni del firmware esistenti" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Per essere completato, un aggiornamento richiede un riavvio." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "Per essere completato, un aggiornamento richiede lo spegnimento del sistema." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Risponde affermativamente a tutte le domande" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Applica una patch binaria" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Applica aggiornamenti firmware" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Firmware approvato:" msgstr[1] "Firmware approvati:" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "Collega dispositivo DFU al runtime" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Collega in modalità firmware" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Attributi" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Autenticazione…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "È richiesto autenticarsi per tornare al precedente firmware su un dispositivo rimovibile" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "È richiesto autenticarsi tornare al precedente firmware su questa macchina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "È richiesto autenticarsi per modificare un remoto configurato utilizzato per aggiornamenti firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "È richiesto autenticarsi per modificare la configurazione del demone" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "È richiesto autenticarsi per impostare l'elenco dei firmware approvati" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "È richiesto autenticarsi per firmare i dati utilizzando il certificato del client" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "È richiesto autenticarsi per passare alla nuova versione del firmware " #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "È richiesto autenticarsi per sbloccare un dispositivo" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "È richiesto autenticarsi per aggiornare il firmware su un dispositivo rimovibile" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "È richiesto autenticarsi per aggiornare il firmware su questa macchina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "È richiesto autenticarsi per aggiornare il codice di controllo del dispositivo salvato" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Compila il firmware utilizzando una sandbox" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Annulla" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Annullato" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Modificato" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Codice di controllo" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "ID processore" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Scegliere un dispositivo:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Scegliere un rilascio:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Cifratura" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Annulla gli aggiornamenti pianificati per essere eseguiti offline" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Pulisce i risultati dell'ultimo aggiornamento" #. TRANSLATORS: error message msgid "Command not found" msgstr "Comando non trovato" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Converte il firmware nel formato DFU" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Crea una patch binaria utilizzando due file" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Strumento DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opzioni di debug" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Estrazione…" #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Decifra i dati del firmware" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Descrizione" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Rimuove l'attuale dispositivo collegato con supporto DFU" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Scollega in modalità bootloader" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID dispositivo" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Dispositivo aggiunto:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Dispositivo modificato:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositivo rimosso:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Dispositivi aggiornati con successo:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Dispositivi non aggiornati correttamente:" msgid "Disabled fwupdate debugging" msgstr "Debug fwupdate disabilitato" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Disabilita un remoto dato" #. TRANSLATORS: command line option msgid "Display version" msgstr "Visualizza la versione" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Non controlla i metadati vecchi" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "Non controlla se è necessario riavviare dopo un aggiornamento" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Non controlla la cronologia non segnalata " #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Non scrive la cronologia" #. success msgid "Done!" msgstr "Fatto." #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Ripristina una vecchia versione del firmare su un dispositivo" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Arretramento di %s da %s a %s..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Arretramento di %s…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Scaricamento…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Scarica i dati SMBIOS da un file" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Scarta informazioni su un file di firmware" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Stampa le informazioni riguardo a una patch binaria su schermo" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "ESP specificata non era valida" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Abilita il supporto all'aggiornamento firmware sui sistemi compatibili" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Abilitare questo remoto?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Abilitato" msgid "Enabled fwupdate debugging" msgstr "Debug fwupdate abilitato" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Abilita un remoto dato" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Abilitare questa funzionalità a proprio rischio: in caso di problemi con gli aggiornamenti sarà necessario contattare l'OEM. Solamente i problemi legati al processo di aggiornamento possono essere inviati a $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Abilitare questo remoto a proprio rischio." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Cifra i dati del firmware" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Elimina tutta la cronologia degli aggiornamenti firmware" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Eliminazione…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Esce dopo una breve attesa" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Esce dopo che il motore è stato caricato" #. TRANSLATORS: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "Connessione al demone non riuscita" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "Scaricamento non riuscito a causa di un limite sul server" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Recupero dei dispositivi in attesa non riuscito" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Installazione aggiornamento firmware non riuscita" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Caricamento stranezze non riuscito" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Analisi degli argomenti non riuscita" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Riavvio non riuscito" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Impostazione della modalità splash non riuscita" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Recupero file" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "Recupero firmware" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Recupero metadati" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Recupero firma" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Nome file" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Firma nome file" #. TRANSLATORS: program name msgid "Firmware Agent" msgstr "Strumento firmware" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI di base del firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Servizio D-Bus di aggiornamento firmware" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Demone di aggiornamento firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Strumento gestione firmware" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "I metadati del firmware non sono stati controllati per %u giorno e potrebbero non essere aggiornati." msgstr[1] "I metadati del firmware non sono stati controllati per %u giorni e potrebbero non essere aggiornati." msgid "Firmware updates are not supported on this machine." msgstr "Gli aggiornamenti firmware non sono supportati su questo dispositivo." msgid "Firmware updates are supported on this machine." msgstr "Gli aggiornamenti firmware sono supportati su questo dispositivo." #. TRANSLATORS: section header for firmware flags msgid "Flags" msgstr "Flag" msgid "Force the action ignoring all warnings" msgstr "Forza l'azione ignorando gli avvisi" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Trovato" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "Recupera tutti i dispositivi in base alla topologia di sistema" #. TRANSLATORS: command description msgid "Get all devices and possible releases" msgstr "Ottiene tutti i dispositivi e i possibili rilasci" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Ottiene tutti i dispositivi che supportano gli aggiornamenti del firmware" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Recupera tutti i plugin registrati nel sistema" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Ottiene le informazioni su un file di firmware" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Ottiene i remoti configurati" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Ottiene l'hash crittografico del firmware scartato" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware." msgstr "Recupera l'elenco dei firmware approvati." #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Ottiene l'elenco degli aggiornamenti per l'hardware connesso" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Ottiene i rilasci di un dispositivo" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Ottiene i risultati dell'ultimo aggiornamento" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "Identificativo" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Inattivo…" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Installa blob firmware su un dispositivo" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Installa un file di firmware su questo hardware" msgid "Install old version of system firmware" msgstr "Installa una vecchia versione del firmware di sistema" msgid "Install signed device firmware" msgstr "Installa firmware firmato del dispositivo" msgid "Install signed system firmware" msgstr "Installa firmware firmato di sistema" msgid "Install unsigned device firmware" msgstr "Installa firmware non firmato del dispositivo" msgid "Install unsigned system firmware" msgstr "Installa firmware non firmato di sistema" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Installazione firmware…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Installazione aggiornamento firmware…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installazione su %s…" msgid "Keyring" msgstr "Portachiavi" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Manca meno di un minuto" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (firmware stabile)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (firmware in prova)" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "Elenca gli attuali dispositivi collegati con supporto DFU" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Elenca gli aggiornamenti firmware supportati" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Caricamento…" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Abilita manualmente plugin specifici" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Unisce più file di firmware in uno" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI metadati" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Firma URI dei metadati" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadati possono essere scaricati da Linux Vendor Firmware Service." #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Versioni di demone e client non corrispondenti, usare %s" msgid "Mode" msgstr "Modalità" #. TRANSLATORS: sets something in daemon.conf msgid "Modifies a daemon configuration value." msgstr "Modifica il valore della configurazione del demone" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifica un remoto" msgid "Modify a configured remote" msgstr "Modifica un remoto configurato" msgid "Modify daemon configuration" msgstr "Modifica la configurazione del demone" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Controlla il demone per gli eventi" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Nome" msgid "No action specified!" msgstr "Nessuna azione specificata." #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Non è stato rilevato nessun hardware con capacità di aggiornamento del firmware" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Non è stato trovato alcun plugin" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Non è abilitato alcun remoto e non sono disponibili metadati." #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Non è stato applicato alcun aggiornamento" msgid "OK" msgstr "Fatto" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Scavalca l'avviso sul plugin" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Sovrascrive il percorso ESP predefinito" #. TRANSLATORS: command line option msgid "Override warnings and force the action" msgstr "Scavalca gli avvisi e forza l'azione" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Password" msgid "Payload" msgstr "Carico" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentuale completamento" msgid "Permission denied" msgstr "Permesso negato" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Inserire un numero tra 0 e %u:" msgid "Print the version number" msgstr "Stampa il numero di versione" msgid "Print verbose debug statements" msgstr "Stampa messaggi di debug prolissi" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Priorità" msgid "Proceed with upload?" msgstr "Procedere con il caricamento?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protocollo" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Interroga il supporto per gli aggiornamenti firmware" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Stranezze" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Legge il firmware dal dispositivo in un file" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Legge il firmware da una partizione in un file" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Lettura…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Riavvio…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Ricarica i metadati dal server remoto" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Regione" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Reinstallazione di %s con %s..." #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Remoto" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID remoto" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Rimosso" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Sostituisce i dati su un file di firmware esistente" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI del rapporto" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Richiede una connessione a Internet" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "Ripristina un dispositivo DFU" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Riavviare ora?" #. TRANSLATORS: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Riavviare il demone per applicare le modifiche?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Riavvio del dispositivo…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Fornisce tutti gli ID hardware per un dispositivo" #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Esegue la procedura di pulizia del plugin quando si utilizza install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Esegue la procedura di preparazione del plugin quando si utilizza install-blob" msgid "Runtime" msgstr "Runtime" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "Salva lo stato del dispositivo tra le esecuzioni su un file JSON" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Pianifica l'installazione al prossimo riavvio quando è possibile" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Pianificazione…" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Seriale" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Imposta il nome alternativo sul file del firmware" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Imposta il numero alternativo sul file del firmware" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Imposta l'indirizzo dell'elemento sul file del firmware" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Imposta l'identificativo del prodotto sul file del firmware" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Imposta la versione di rilascio sul file del firmware" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Imposta il flag di debug durante l'aggiornamento" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Imposta la dimensione del firmware per la destinazione" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Imposta l'identificativo del produttore sul file del firmware" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Imposta i metadati su un file di firmware" msgid "Sets the list of approved firmware" msgstr "Imposta l'elenco dei firmware approvati" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware." msgstr "Imposta l'elenco dei firmware approvati." #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Condivide la cronologia del firmware con gli sviluppatori" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Mostra la versione del client e del demone" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Mostra informazioni prolisse del demone per un dominio" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Mostra informazioni di debug per tutti i domini" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostra le opzioni di debug" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Mostra dispositivi non aggiornabili" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mostra maggiori informazioni di debug" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Mostra la cronologia degli aggiornamenti firmware" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Mostra informazioni dettagliate del plugin" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Mostra debug dell'ultimo tentativo di aggiornamento" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Mostra informazioni sullo stato degli aggiornamenti firmware" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Spegnere ora?" msgid "Sign data using the client certificate" msgstr "Firma i dati col certificato del client" msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Firma i dati col certificato del client" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Firma i dati caricati col certificato del client" msgid "Signature" msgstr "Firma" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Specifica vendor/ID prodotto di un dispositivo DFU" msgid "Specify the number of bytes per USB transfer" msgstr "Specifica il numero di byte per trasferimento USB" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Stato" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Stato" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Riepilogo" msgid "Target" msgstr "Obiettivo" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS è un servizio gratuito che opera come entità legale indipendente e non ha alcun legame con $OS_RELEASE:NAME$. Il distributore potrebbe non aver verificato la compatibilità degli aggiornamenti firmware col proprio sistema o con i propri dispositivi collegati. Il firmware viene fornito solamente dall'OEM." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Non ci sono firmware approvati." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Questo programma può funzionare correttamente solo come utente root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Questo remoto contiene firmware che non è bloccato, ma è ancora in fase di verifica dal produttore hardware. Assicurarsi di poter ripristinare, manualmente o con altre procedure, il vecchio firmware nel caso in cui l'aggiornamento non riuscisse." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Questo strumento può essere usato solamente dall'utente root" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Titolo" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Dimensione trasferimento" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipo" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Strumento firmware UEFI" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Sconosciuto" msgid "Unlock the device to allow access" msgstr "Sblocco del dispositivo per consentire l'accesso" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Sblocca il dispositivo per accedere al firmware" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Ripristina il flag di debug durante l'aggiornamento" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Demone versione %s non supportato, la versione del client è %s" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Codice di controllo aggiornamento" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Descrizione aggiornamento" #. TRANSLATORS: section header for the amount #. * of time it takes to install the update msgid "Update Duration" msgstr "Durata aggiornamento" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Posizione aggiornamento" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Nome aggiornamento" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "ID remoto aggiornamento" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Riepilogo aggiornameto" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Versione aggiornamento" #. TRANSLATORS: command description msgid "Update all devices that match local metadata" msgstr "Aggiorna tutti i dispositivi corrispondenti ai metadati locali" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Questo è un problema noto, consultare il seguente URL per maggiori informazioni:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Aggiornare ora?" msgid "Update the stored device verification information" msgstr "Aggiornamento delle informazioni di verifica del dispositivo salvate" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Aggiorna i metadati salvati con gli attuali contenuti della ROM" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Aggiorna i metadati salvati con il contenuto attuale" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Aggiorna tutti i firmware all'ultima versione disponibile" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Aggiornamento di %s da %s a %s..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Aggiornamento di %s…" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Messaggio di caricamento:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Caricare il rapporto ora?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Inviare resoconti sul firmware aiuta gli sviluppatori a identificare velocemente aggiornamenti eseguiti con successo o non riusciti su dispositivi reali." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nome utente" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifica…" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Versione" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Attesa…" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "Controlla i dispositivi DFU che sono collegati a caldo" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Controlla le modifiche hardware" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Scrive il firmware dal file nel dispositivo" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Scrive il firmware dal file in una partizione" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Scrittura…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "La propria distribuzione potrebbe non aver verificato la compatibilità degli aggiornamenti firmware col proprio sistema o col dispositivo collegato." fwupd-1.2.14/po/its/000077500000000000000000000000001402665037500141515ustar00rootroot00000000000000fwupd-1.2.14/po/its/appdata.its000066400000000000000000000013501402665037500163030ustar00rootroot00000000000000 fwupd-1.2.14/po/its/appdata.loc000066400000000000000000000005121402665037500162600ustar00rootroot00000000000000 fwupd-1.2.14/po/kk.po000066400000000000000000000024751402665037500143270ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Baurzhan Muftakhidinov , 2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Kazakh (http://www.transifex.com/freedesktop/fwupd/language/kk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: kk\n" "Plural-Forms: nplurals=1; plural=0;\n" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Қосылған" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Бас тартылған" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Өзгертілген" #. TRANSLATORS: read from device to host msgid "Erasing" msgstr "Өшірілуде" #. TRANSLATORS: read from device to host msgid "Reading" msgstr "Оқылуда" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Өшірілген" #. TRANSLATORS: read from device to host msgid "Verifying" msgstr "Тексерілуде" #. TRANSLATORS: write from host to device msgid "Writing" msgstr "Жазылуда" fwupd-1.2.14/po/ko.po000066400000000000000000001170201402665037500143240ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Seong-ho Cho , 2017,2019 # Shinjo Park , 2018-2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Korean (http://www.transifex.com/freedesktop/fwupd/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ko\n" "Plural-Forms: nplurals=1; plural=0;\n" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f분 남음" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s 소비자용 ME 업데이트" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s 컨트롤러 업데이트" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s 기업용 ME 업데이트" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s 장치 업데이트" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s 임베디드 컨트롤러 업데이트" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s ME 업데이트" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s 시스템 업데이트" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s 업데이트" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s의 최신 펌웨어가 있습니다:" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u일" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u시간" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u분" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u초" #. TRANSLATORS: command description msgid "Activate devices" msgstr "장치 활성화" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "대기 중인 장치 활성화" msgid "Activate the new firmware on the device" msgstr "장치에 새 펌웨어 활성화" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "펌웨어 업데이트 활성화 중" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "다음 장치의 펌웨어 업데이트 활성화 중:" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "추가함" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "경과 기간" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "동의하며 원격 설정을 활성화하시겠습니까?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "%s의 별칭" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "펌웨어 다운그레이드를 허용합니다" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "기존 펌웨어 버전 재설치를 허용합니다" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "업데이트를 완료하려면 다시 시작해야 합니다." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "업데이트를 완료하려면 시스템을 종료해야 합니다." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "모든 질문에 예로 답하기" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "바이너리 패치 적용" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "펌웨어 업데이트 적용" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "허용 펌웨어:" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "DFU 기능 장치를 런타임에 연결" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "펌웨어 모드에 연결" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "속성" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "인증 중…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "이동식 장치의 펌웨어를 이전 버전으로 되돌리려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "이 머신에 이전 버전의 펌웨어를 설치하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "펌웨어 업데이트에 사용할 원격 설정을 수정하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "데몬 설정을 수정하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "허용된 펌웨어 목록을 설정하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "클라이언트 인증서로 데이터를 서명하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "새 펌웨어 버전으로 전환하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "장치 잠금을 해제하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "이동식 장치의 펌웨어를 업데이트하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "이 머신의 펌웨어를 업데이트하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "저장된 체크섬을 업데이트하려면 인증해야 합니다" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "샌드박스에서 펌웨어를 빌드합니다" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "취소" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "취소함" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "바뀜" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "체크섬" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "칩 ID" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "장치를 선택하십시오:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "출시 버전을 선택하십시오:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "암호화 방식" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "오프라인으로 업데이트할 업데이트를 지웁니다" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "최근 업데이트 결과를 지웁니다" #. TRANSLATORS: error message msgid "Command not found" msgstr "명령을 찾을 수 없습니다" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "펌웨어를 DFU 형식으로 변환" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "파일 두 개를 사용하여 바이너리 패치 만들기" msgid "DFU" msgstr "DFU" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU 유틸리티" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "디버깅 옵션" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "압축 해제 중…" #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "펌웨어 데이터 복호화" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "설명" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "현재 연결한 DFU 기능 장치 분리" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "부트로더 모드로 전환" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "장치 ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "장치 추가됨:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "장치 상태 바뀜:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "장치 제거됨:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "성공적으로 업데이트된 장치:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "올바르게 업데이트되지 않은 장치:" msgid "Disabled fwupdate debugging" msgstr "fwupdate 디버깅 비활성화" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "지정한 원격 정보를 비활성화합니다" #. TRANSLATORS: command line option msgid "Display version" msgstr "버전 표시" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "오래된 메타데이터 검사하지 않기" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "업데이트 후 다시 시작 검사하지 않기" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "보고되지 않은 과거 기록 검사하지 않기" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "과거 기록 데이터베이스에 기록하지 않기" #. success msgid "Done!" msgstr "완료!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "장치 펌웨어 버전을 이전으로 되돌립니다" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "%2$s에서 %3$s(으)로 %1$s 다운그레이드 중... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "%s 다운그레이드 중..." #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "다운로드 중…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "파일에 저장된 SMBIOS 데이터 덤프를 출력합니다" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "펌웨어 파일 세부 정보 출력" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "바이너리 패치 정보를 화면으로 출력" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "지정한 ESP가 올바르지 않습니다" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "지원하는 시스템의 펌웨어 업데이트 지원 활성화" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "이 원격 설정을 활성화하시겠습니까?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "활성화 여부" msgid "Enabled fwupdate debugging" msgstr "fwupdate 디버깅 활성화" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "지정한 원격 정보를 활성화합니다" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "이 기능의 사용은 본인의 책임이며, 업데이트로 인해서 생긴 문제는 원 장치 제조사에 직접 보고해야 합니다. 업데이트 진행 과정 자체의 문제는 $OS_RELEASE:BUG_REPORT_URL$(으)로 보고해 주십시오." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "이 원격 장치를 설정하는 것은 본인의 책임입니다." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "펌웨어 데이터 암호화" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "모든 펌웨어 업데이트 기록 지우기" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "지우는 중…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "짧은 대기 시간 경과 후 나가기" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "엔진을 불러온 후 나가기" #. TRANSLATORS: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "데몬에 연결할 수 없음" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "서버 제한으로 파일을 다운로드할 수 없음" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "대기 중인 장치를 가져올 수 없음" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "펌웨어 업데이트를 설치할 수 없음" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "특이 사항을 불러올 수 없음" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "인자 해석에 실패했습니다" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "다시 시작할 수 없음" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "스플래시 모드를 설정할 수 없음" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "파일 가져오는 중" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "펌웨어 가져오는 중" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "메타데이터 가져오는 중" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "서명 가져오는 중" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "파일 이름" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "파일 이름 서명" #. TRANSLATORS: program name msgid "Firmware Agent" msgstr "펌웨어 에이전트" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "펌웨어 기본 URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "펌웨어 업데이트 D-Bus 서비스" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "펌웨어 업데이트 데몬" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "펌웨어 유틸리티" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "펌웨어 메타데이터가 %u일 동안 업데이트되지 않았으므로 최신 정보가 누락되었을 수도 있습니다." msgid "Firmware updates are not supported on this machine." msgstr "이 머신에서 펌웨어 업데이트를 지원하지 않습니다." msgid "Firmware updates are supported on this machine." msgstr "이 머신에서 펌웨어 업데이트를 지원합니다." #. TRANSLATORS: section header for firmware flags msgid "Flags" msgstr "플래그" msgid "Force the action ignoring all warnings" msgstr "모든 경고를 무시하고 작업 강제 진행" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "감지 장치" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "시스템에 연결된 순서대로 모든 장치 목록 가져오기" #. TRANSLATORS: command description msgid "Get all devices and possible releases" msgstr "모든 장치와 릴리스 가져오기" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "펌웨어 업데이트를 지원하는 모든 장치 정보를 가져옵니다" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "시스템에 등록된 모든 활성 플러그인 가져오기" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "펌웨어 파일 세부 정보를 가져옵니다" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "원격 설정 정보를 가져옵니다" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "덤프한 펌웨어의 암호화 해시 정보를 가져옵니다" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware." msgstr "허용된 펌웨어 목록을 가져옵니다." #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "연결한 하드웨어의 업데이트 목록을 가져옵니다" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "장치 펌웨어 릴리스를 가져옵니다" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "최근 업데이트 결과를 가져옵니다" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "대기 중…" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "장치에 펌웨어 파일 설치" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "이 하드웨어에 펌웨어 파일을 설치합니다" msgid "Install old version of system firmware" msgstr "이전 버전 시스템 펌웨어 설치" msgid "Install signed device firmware" msgstr "서명된 장치 펌웨어 설치" msgid "Install signed system firmware" msgstr "서명된 시스템 펌웨어 설치" msgid "Install unsigned device firmware" msgstr "서명되지 않은 장치 펌웨어 설치" msgid "Install unsigned system firmware" msgstr "서명되지 않은 시스템 펌웨어 설치" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "펌웨어 설치 중..." #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "펌웨어 업데이트 설치 중…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "%s에 설치 중..." msgid "Keyring" msgstr "키 모음" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "1분 미만 남음" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "리눅스 제조사 펌웨어 서비스(안정 버전 펌웨어)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "리눅스 제조사 펌웨어 서비스(테스트 버전 펌웨어)" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "현재 연결한 DFU 기능 장치 목록 보기" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "지원하는 펌웨어 업데이트 목록 표시" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "불러오는 중…" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "지정한 플러그인을 수동으로 허용 목록에 추가" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "다중 펌웨어 파일을 하나로 병합" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "메타데이터 URI" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "메타데이터 URI 서명" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "리눅스 제조사 펌웨어 서비스에서 메타데이터를 가져올 수 있습니다." #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "데몬과 클라이언트가 일치하지 않음, %s을(를) 대신 사용함" msgid "Mode" msgstr "모드" #. TRANSLATORS: sets something in daemon.conf msgid "Modifies a daemon configuration value." msgstr "데몬 설정값을 변경합니다." #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "지정한 원격 정보를 수정합니다" msgid "Modify a configured remote" msgstr "원격 설정 수정" msgid "Modify daemon configuration" msgstr "데몬 설정 수정" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "데몬 이벤트를 감시합니다" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "이름" msgid "No action specified!" msgstr "동작을 지정하지 않았습니다!" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "펌웨어를 업데이트할 수 있는 하드웨어가 없음" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "플러그인을 찾을 수 없음" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "원격 자원이 설정되지 않았으므로 메타데이터를 사용할 수 없습니다." #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "적용된 업데이트 없음" msgid "OK" msgstr "확인" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "플러그인 경고 무시" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "기본 ESP 경로 재정의" #. TRANSLATORS: command line option msgid "Override warnings and force the action" msgstr "경고를 무시하고 강제로 작업 진행" #. TRANSLATORS: remote filename base msgid "Password" msgstr "암호" msgid "Payload" msgstr "페이로드" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "진행률" msgid "Permission denied" msgstr "권한 거부" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "0에서 %u까지의 숫자 중 하나를 입력하십시오:" msgid "Print the version number" msgstr "버전 번호 표시" msgid "Print verbose debug statements" msgstr "자세한 디버그 정보 표시" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "우선 순위" msgid "Proceed with upload?" msgstr "업로드를 진행하시겠습니까?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "프로토콜" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "펌웨어 업데이트 지원 조회" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "특이 사항" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "장치 펌웨어를 읽어 파일에 기록" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "파티션에서 펌웨어를 읽어 파일에 기록" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "읽는 중…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "다시 시작하는 중..." #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "원격 서버의 메타데이터를 새로 고칩니다" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "메모리 영역" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "%2$s(으)로 %1$s 다시 설치하는 중... " #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "원격" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "원격 ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "제거함" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "기존 펌웨어 파일의 데이터 교체" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "보고서 URI" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "인터넷 연결 필요" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "DFU 장치 초기화" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "지금 다시 시작하시겠습니까?" #. TRANSLATORS: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "데몬을 다시 시작하여 변경 사항을 적용하시겠습니까?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "장치 다시 시작하는 중…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "머신의 모든 하드웨어 ID를 반환합니다" #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "install-blob 사용 시 플러그인 정리 루틴 실행" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "install-blob 사용 시 플러그인 준비 루틴 실행" msgid "Runtime" msgstr "런타임" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "실행하는 동안의 장치 상태를 JSON 파일로 저장" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "가능하다면 다음에 다시 시작할 때 설치 예약" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "작업 계획 중…" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "시리얼" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "펌웨어 파일의 대체 명칭 설정" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "펌웨어 파일의 대체 번호 설정" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "펌웨어 파일의 구성요소 주소 설정" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "펌웨어 파일의 제품 ID 설정" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "펌웨어 파일의 릴리스 버전 설정" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "업데이트 중 디버깅 플래그 설정" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "대상 펌웨어 크기 설정" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "펌웨어 파일의 제조사 ID 설정" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "펌웨어 파일의 메타데이터 설정" msgid "Sets the list of approved firmware" msgstr "허용된 펌웨어 목록 설정" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware." msgstr "허용된 펌웨어 목록을 설정합니다." #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "개발사와 펌웨어 업데이트 기록 공유하기" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "클라이언트와 데몬 버전 표시" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "지정한 도메인의 자세한 데몬 정보 표시" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "모든 도메인의 디버깅 정보 표시" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "디버깅 옵션을 표시합니다" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "업데이트할 수 없는 장치 표시" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "추가 디버깅 정보 표시" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "펌웨어 업데이트 기록 보기" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "자세한 플러그인 정보 표시" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "마지막으로 시도한 업데이트의 디버그 정보 표시" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "펌웨어 업데이트 상태 표시" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "지금 종료하시겠습니까?" msgid "Sign data using the client certificate" msgstr "클라이언트 인증서로 데이터 서명" msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "클라이언트 인증서로 데이터 서명" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "클라이언트 인증서로 업로드된 데이터 서명" msgid "Signature" msgstr "서명" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "DFU 장치의 제조사/제품 ID 지정" msgid "Specify the number of bytes per USB transfer" msgstr "USB 전송 당 바이트 수 지정" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "상태" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "장치 상태" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "요약" msgid "Target" msgstr "대상" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS는 $OS_RELEASE:NAME$와(과) 별개로 운영되는 독립된 법적 단체에서 운영하는 무료 서비스입니다. 배포판 개발사에서 펌웨어 업데이트와 시스템 및 연결한 장치의 호환성을 검증한다는 보장이 없습니다. 모든 펌웨어는 장치 제조사(OEM)에서 직접 제공합니다." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "허용된 펌웨어가 없습니다." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "이 프로그램은 루트 권한으로만 올바르게 작동할 수도 있습니다" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "이 원격 저장소에서는 하드웨어 제조사에서 검증 단계를 진행 중인 펌웨어를 배포합니다. 펌웨어 업데이트 도중 및 이후 문제가 발생했을 경우를 대비하여 직접 펌웨어를 다운그레이드할 방편을 확보해두시는게 좋습니다." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "이 도구는 루트로만 사용할 수 있습니다" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "제목" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "전송 용량" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "형식" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI 펌웨어 유틸리티" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "알 수 없음" msgid "Unlock the device to allow access" msgstr "접근을 허용하려면 장치 잠금을 해제하십시오" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "펌웨어에 접근할 수 있도록 장치 잠금을 해제합니다" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "업데이트 중 디버깅 플래그 설정 해제" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "지원하지 않는 데몬 버전 %s, 클라이언트 버전 %s" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "업데이트 체크섬" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "업데이트 설명" #. TRANSLATORS: section header for the amount #. * of time it takes to install the update msgid "Update Duration" msgstr "업데이트 예상 시간" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "업데이트 위치" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "업데이트 이름" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "업데이트 원격 ID" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "업데이트 요약" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "업데이트 버전" #. TRANSLATORS: command description msgid "Update all devices that match local metadata" msgstr "로컬 메타데이터와 일치하는 모든 장치 업데이트" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "알 수 없는 이유로 업데이트가 실패했습니다. 더 많은 정보를 보려면 다음 URL을 참조가힙시오:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "지금 업데이트하시겠습니까?" msgid "Update the stored device verification information" msgstr "저장된 장치 검증 정보 업데이트" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "저장된 메타데이터를 현재 ROM 내용으로 업데이트합니다" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "저장된 메타데이터를 현재 내용으로 업데이트" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "모든 펌웨어를 사용할 수 있는 최신 버전으로 업데이트합니다" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "%2$s에서 %3$s(으)로 %1$s 업데이트 중... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s 업데이트 중..." #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "업로드 메시지:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "지금 보고서를 업로드하시겠습니까?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "펌웨어 보고서를 업로드하면 하드웨어 제작사에서 실제 장치에 업데이트를 적용했을 때 성공 및 실패 여부를 빠르게 알 수 있습니다." #. TRANSLATORS: remote filename base msgid "Username" msgstr "사용자 이름" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "검증 중…" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "버전" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "기다리는 중…" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "DFU 핫 플러그 장치 보기" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "하드웨어 변경 사항 감시" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "파일의 펌웨어를 장치에 기록" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "파일의 펌웨어를 파티션 하나에 기록" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "쓰는 중…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "배포판 개발사에서 펌웨어 업데이트와 시스템 및 연결된 장치간의 호환성을 검증한다는 보장이 없습니다." fwupd-1.2.14/po/ky.po000066400000000000000000000032341402665037500143370ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Ilyas Bakirov , 2018 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Kyrgyz (http://www.transifex.com/freedesktop/fwupd/language/ky/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ky\n" "Plural-Forms: nplurals=1; plural=0;\n" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Кошулду" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "Чиптин ID'си" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Табылды" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. show message in progressbar #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing %s" msgstr "Орнотулууда: %s" msgid "Mode" msgstr "Режими" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Протокол" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Окуулууда..." #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Абалы" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Статус" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Белгисиз" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Жазылууда..." msgid "fwupd" msgstr "fwupd" fwupd-1.2.14/po/lt.po000066400000000000000000001111421402665037500143310ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Moo, 2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Lithuanian (http://www.transifex.com/freedesktop/fwupd/language/lt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: lt\n" "Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);\n" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s valdiklio atnaujinimas" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s įrenginio atnaujinimas" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s įtaisytojo valdiklio atnaujinimas" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s ME atnaujinimas" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s sisteminis atnaujinimas" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s atnaujinimas" #. TRANSLATORS: first replacement is device name #, c-format msgid "%s has firmware updates:" msgstr "%s turi programinės aparatinės įrangos atnaujinimų:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktyvuoti įrenginius" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktyvuoti laukiančius įrenginius" msgid "Activate the new firmware on the device" msgstr "Aktyvuoti naują programinę aparatinę įrangą įrenginyje" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Aktyvuojamas programinės aparatinės įrangos atnaujinimas" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Aktyvuojama programinė aparatinė įranga, skirta" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Pridėtas" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Amžius" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Sutikti ir įjungti šią nuotolinę saugyklą?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alternatyvusis %s pavadinimas" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Leisti sendinti programinės aparatinės įrangos versijas" #. TRANSLATORS: command line option msgid "Allow re-installing existing firmware versions" msgstr "Leisti iš naujo įdiegti esamas programinės aparatinės įrangos versijas" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Atnaujinimo užbaigimui, reikia paleisti sistemą iš naujo." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "Atnaujinimo užbaigimui, reikia išjungti sistemą." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Atsakyti taip į visus klausimus" #. TRANSLATORS: command description msgid "Apply a binary patch" msgstr "Taikyti dvejetainę pataisą" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Taikyti programinės aparatinės įrangos atnaujinimus" #. TRANSLATORS: command description msgid "Attach DFU capable device back to runtime" msgstr "Prijungti ĮPAĮA įgalintą įrenginį atgal prie vykdymo aplinkos" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Pridėti į programinės aparatinės įrangos veikseną" #. TRANSLATORS: device attributes, i.e. things that #. * the device can do msgid "Attributes" msgstr "Požymiai" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Nustatoma tapatybė…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Norint keičiamajame įrenginyje sendinti programinę aparatinę įrangą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Norint šiame kompiuteryje sendinti programinę aparatinę įrangą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Norint modifikuoti programinės aparatinės įrangos atnaujinimams naudojamą sukonfigūruotą saugyklą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Norint nustatyti patvirtintos programinės aparatinės įrangos sąrašą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Norint pasirašyti duomenis naudojant kliento liudijimą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Norint perjungti į naują programinės aparatinės įrangos versiją, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Norint atrakinti įrenginį, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Norint keičiamajame įrenginyje atnaujinti programinę aparatinę įrangą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Norint šiame kompiuteryje atnaujinti programinę aparatinę įrangą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Norint atnaujinti įrenginiui saugomas kontrolines sumas, reikalingas tapatybės nustatymas" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Sukurti programinę aparatinę įrangą, naudojant smėliadėžę" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Atsisakyti" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Atsisakyta" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Pakeistas" #. TRANSLATORS: section header for firmware checksum #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolinė suma" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "Lusto ID" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Pasirinkite įrenginį:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Pasirinkite laidą:" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Šifras" #. TRANSLATORS: command description msgid "Clears any updates scheduled to be updated offline" msgstr "Išvalo visus autonominiam atnaujinimui suplanuotus atnaujinimus" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Išvalo rezultatus iš paskutinio atnaujinimo" #. TRANSLATORS: error message msgid "Command not found" msgstr "Komanda nerasta" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Konvertuoti programinę aparatinė įrangą į ĮPAĮA formatą" #. TRANSLATORS: command description msgid "Create a binary patch using two files" msgstr "Sukurti dvejetainę pataisą, naudojant du failus" msgid "DFU" msgstr "ĮPAĮA" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "ĮPAĮA paslaugų programa" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Derinimo parametrai" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Išskleidžiama…" #. TRANSLATORS: command description msgid "Decrypt firmware data" msgstr "Iššifruoti programinės aparatinės įrangos duomenis" #. TRANSLATORS: section header for firmware description msgid "Description" msgstr "Aprašas" #. TRANSLATORS: command description msgid "Detach currently attached DFU capable device" msgstr "Atjungti šiuo metu prijungtą ĮPAĮA įgalintą įrenginį" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Atskirti į pradinio įkėliklio veikseną" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Įrenginio ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Pridėtas įrenginys:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Pakeistas įrenginys:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Pašalintas įrenginys:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Įrenginiai, kurie buvo sėkmingai atnaujinti:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Įrenginiai, kurie nebuvo teisingai atnaujinti:" msgid "Disabled fwupdate debugging" msgstr "Išjungtas fwupdate derinimas" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Išjungia nurodytą nuotolinę saugyklą" #. TRANSLATORS: command line option msgid "Display version" msgstr "Rodyti versiją" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Netikrinti ar yra senų metaduomenų" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "Po atnaujinimo netikrinti ar buvo paleidimas iš naujo" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Netikrinti ar yra istorijos apie kurią nepranešta" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Nerašyti į istorijos duomenų bazę" #. success msgid "Done!" msgstr "Atlikta!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Sendina programinę aparatinę įrangą įrenginyje" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Sendinama %s iš %s į %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Sendinamas %s…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Atsisiunčiama…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Iškloti SMBIOS duomenis iš failo" #. TRANSLATORS: command description msgid "Dump details about a firmware file" msgstr "Iškloti išsamesnę informaciją apie programinės aparatinės įrangos failą" #. TRANSLATORS: command description msgid "Dump information about a binary patch to the screen" msgstr "Iškloti į ekraną informaciją apie dvejetainę pataisą" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Nurodytas ESS (angl. ESP) nebuvo teisingas" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Įjungti programinės aparatinės įrangos atnaujinimo palaikymą palaikomose sistemose" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Įjungti šią nuotolinę saugyklą?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Įjungta" msgid "Enabled fwupdate debugging" msgstr "Įjungtas fwupdate derinimas" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Įjungia nurodytą nuotolinę saugyklą" #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Šios nuotolinės saugyklos įjungimas yra jūsų pačių rizika." #. TRANSLATORS: command description msgid "Encrypt firmware data" msgstr "Šifruoti programinės aparatinės įrangos duomenis" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Ištrinti visą programinės aparatinės įrangos atnaujinimų istoriją" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Ištrinama…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Išeiti po nedidelės delsos" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Išeiti, įkėlus modulį" #. TRANSLATORS: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "Nepavyko prisijungti prie tarnybos" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "Nepavyko atsisiųsti dėl serverio apribojimo" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Nepavyko gauti laukiančių įrenginių" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Nepavyko įdiegti programinės aparatinės įrangos atnaujinimo" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Nepavyko įkelti gudrybių" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Nepavyko išanalizuoti argumentų" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Nepavyko paleisti iš naujo" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Nepavyko nustatyti prisistatymo lango veikseną" #. TRANSLATORS: downloading unknown file msgid "Fetching file" msgstr "Gaunamas failas" #. TRANSLATORS: downloading new firmware file msgid "Fetching firmware" msgstr "Gaunama programinė aparatinė įranga" #. TRANSLATORS: downloading new metadata file msgid "Fetching metadata" msgstr "Gaunami metaduomenys" #. TRANSLATORS: downloading new signing file msgid "Fetching signature" msgstr "Gaunamas parašas" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Failo pavadinimas" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Failo pavadinimo parašas" #. TRANSLATORS: program name msgid "Firmware Agent" msgstr "Programinės aparatinės įrangos agentas" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Pagrindinis programinės aparatinės įrangos URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Programinės aparatinės įrangos atnaujinimo D-Bus tarnyba" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Programinės aparatinės įrangos atnaujinimo tarnyba" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Programinės aparatinės įrangos paslaugų programa" msgid "Firmware updates are not supported on this machine." msgstr "Šiame kompiuteryje programinės aparatinės įrangos atnaujinimai yra neprieinami." msgid "Firmware updates are supported on this machine." msgstr "Šiame kompiuteryje yra prieinami programinės aparatinės įrangos atnaujinimai." #. TRANSLATORS: section header for firmware flags msgid "Flags" msgstr "Vėliavėlės" msgid "Force the action ignoring all warnings" msgstr "Priverstinai atlikti veiksmą nepaisant visų įspėjimų" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Rastas" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices according to the system topology" msgstr "Gauti visus įrenginius pagal sistemos topologiją" #. TRANSLATORS: command description msgid "Get all devices and possible releases" msgstr "Gauti visus įrenginius ir galimas laidas" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Gauti visus įrenginius, kurie palaiko programinės aparatinės įrangos atnaujinimus" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Gauti visus įjungtus sistemoje registruotus įskiepius" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Gauna išsamesnę informaciją apie programinės aparatinės įrangos failą" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Gauna sukonfigūruotas nuotolines saugyklas" #. TRANSLATORS: command description msgid "Gets the cryptographic hash of the dumped firmware" msgstr "Gauna išklotos programinės aparatinės įrangos šifravimo maišą" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware." msgstr "Gauna patvirtintos programinės aparatinės įrangos sąrašą." #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Gauna atnaujinimų sąrašą prijungtai aparatinei įrangai" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Gauna laidas įrenginiui" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Gauna rezultatus iš paskutinio atnaujinimo" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Neveiklus…" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Įdiegti įrenginyje programinės aparatinės įrangos dvejetainį išplėstinį objektą" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Įdiegti šioje aparatinėje įrangoje programinės aparatinės įrangos failą" msgid "Install old version of system firmware" msgstr "Įdiegti senąją sistemos programinės aparatinės įrangos versiją" msgid "Install signed device firmware" msgstr "Įdiegti pasirašytą įrenginio programinę aparatinę įrangą" msgid "Install signed system firmware" msgstr "Įdiegti pasirašytą sistemos programinę aparatinę įrangą" msgid "Install unsigned device firmware" msgstr "Įdiegti nepasirašytą įrenginio programinę aparatinę įrangą" msgid "Install unsigned system firmware" msgstr "Įdiegti nepasirašytą sistemos programinę aparatinę įrangą" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Įdiegiama programinė aparatinė įranga…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Įdiegiamas programinės aparatinės įrangos atnaujinimas…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Įdiegiama ties %s…" msgid "Keyring" msgstr "Raktinė" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Liko mažiau kaip viena minutė" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux tiekėjų programinės aparatinės įrangos paslauga (stabili programinė aparatinė įranga)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux tiekėjų programinės aparatinės įrangos paslauga (testuojama programinė aparatinė įranga)" #. TRANSLATORS: command description msgid "List currently attached DFU capable devices" msgstr "Išvardyti šiuo metu prijungtus ĮPAĮA įgalintus įrenginius" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Išvardyti prieinamus programinės aparatinės įrangos atnaujinimus" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Įkeliama…" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Rankiniu būdu įtraukti tam tikrus įskiepius į baltąjį sąrašą" #. TRANSLATORS: command description msgid "Merge multiple firmware files into one" msgstr "Sujungti kelis programinės aparatinės įrangos failus į vieną" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metaduomenų URI" #. TRANSLATORS: remote URI msgid "Metadata URI Signature" msgstr "Metaduomenų URI parašas" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metaduomenys gali būti gauti iš Linux tiekėjų programinės aparatinės įrangos paslaugos." msgid "Mode" msgstr "Veiksena" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifikuoja nurodytą nuotolinę saugyklą" msgid "Modify a configured remote" msgstr "Modifikuoti sukonfigūruotą nuotolinę saugyklą" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Stebėti tarnybą, ar yra įvykių" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Pavadinimas" msgid "No action specified!" msgstr "Nenurodytas joks veiksmas!" #. TRANSLATORS: nothing attached #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Neaptikta jokios aparatinės įrangos su programinės aparatinės įrangos atnaujinimo galimybėmis" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nerasta jokių įskiepių" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Šiuo metu nėra įjungtos jokios nuotolinės saugyklos, taigi, nėra prieinami jokie metaduomenys." #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nebuvo pritaikyti jokie atnaujinimai" msgid "OK" msgstr "Gerai" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Nustelbti įskiepio įspėjimą" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Nustelbti numatytąjį ESS (angl. ESP) kelią" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Slaptažodis" msgid "Payload" msgstr "Naudingoji apkrova" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Užbaigta procentinė dalis" msgid "Permission denied" msgstr "Leidimas atmestas" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Įveskite skaičių nuo 0 iki %u: " msgid "Print the version number" msgstr "Parodyti versijos numerį" msgid "Print verbose debug statements" msgstr "Parodyti išsamius derinimo teiginius" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Pirmenybė" msgid "Proceed with upload?" msgstr "Tęsti išsiuntimą?" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokolas" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Užklausti programinės aparatinės įrangos atnaujinimų palaikymo" #. TRANSLATORS: device quirks, i.e. things that #. * it does that we have to work around msgid "Quirks" msgstr "Gudrybės" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Skaityti programinę aparatinę įrangą iš įrenginio į failą" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Skaityti programinę aparatinę įrangą iš vieno skaidinio į failą" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Skaitoma…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Paleidžiama iš naujo…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Iš naujo įkelti metaduomenis iš nuotolinio serverio" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Sritis" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Iš naujo įdiegiama %s su %s... " #. TRANSLATORS: section header for the remote the file is coming from msgid "Remote" msgstr "Nuotolinė saugykla" #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Nuotolinės saugyklos ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Pašalintas" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Pakeisti duomenis esamame programinės aparatinės įrangos faile" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Ataskaitų URI" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Reikalauja interneto ryšio" #. TRANSLATORS: command description msgid "Reset a DFU device" msgstr "Atstatyti ĮPAĮA įrenginį" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Paleisti iš naujo dabar?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Įrenginys paleidžiamas iš naujo…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Grąžinti visus kompiuterio aparatinės įrangos ID" #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Paleisti sudėtinę įskiepio išvalymo programą, naudojant install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Paleisti sudėtinę įskiepio paruošimo programą, naudojant install-blob" msgid "Runtime" msgstr "Vykdymo aplinka" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "Įrašyti įrenginio būseną tarp paleidimų į JSON failą" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Kai įmanoma, suplanuoti įdiegimą kitam paleidimui iš naujo" #. TRANSLATORS: scheduing an update to be done on the next boot msgid "Scheduling…" msgstr "Suplanuojama…" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Serijos numeris" #. TRANSLATORS: command description msgid "Set alternative name on firmware file" msgstr "Nustatyti programinės aparatinės įrangos faile alternatyvų pavadinimą" #. TRANSLATORS: command description msgid "Set alternative number on firmware file" msgstr "Nustatyti programinės aparatinės įrangos faile alternatyvų skaičių" #. TRANSLATORS: command description msgid "Set element address on firmware file" msgstr "Nustatyti elemento adresą programinės aparatinės įrangos faile" #. TRANSLATORS: command description msgid "Set product ID on firmware file" msgstr "Nustatyti programinės aparatinės įrangos faile produkto ID" #. TRANSLATORS: command description msgid "Set release version on firmware file" msgstr "Nustatyti programinės aparatinės įrangos faile laidos versiją" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Atnaujinimo metu nustatyti derinimo vėliavėlę" #. TRANSLATORS: command description msgid "Set the firmware size for the target" msgstr "Nustatyti programinės aparatinės įrangos dydį, skirtą paskirties objektui" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Nustatyti programinės aparatinės įrangos faile tiekėjo ID" #. TRANSLATORS: command description msgid "Sets metadata on a firmware file" msgstr "Nustato metaduomenis programinės aparatinės įrangos faile" msgid "Sets the list of approved firmware" msgstr "Nustato patvirtintą programinės aparatinės įrangos sąrašą" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware." msgstr "Nustato patvirtintos programinės aparatinės įrangos sąrašą." #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Bendrinti programinės aparatinės įrangos istoriją su plėtotojais" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Rodyti kliento ir tarnybos versijas" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Rodyti derinimo parametrus" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Rodyti negalimus atnaujinti įrenginius" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Rodyti papildomą derinimo informaciją" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Rodyti programinės aparatinės įrangos atnaujinimų istoriją" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Rodyti išsamią įskiepio informaciją" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Rodyti derinimo žurnalą iš paskutinio bandyto atnaujinimo" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Rodyti programinės aparatinės įrangos atnaujinimo būseną" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Išjungti dabar?" msgid "Sign data using the client certificate" msgstr "Pasirašyti duomenis, naudojant kliento liudijimą" msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Pasirašyti duomenis, naudojant kliento liudijimą" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Pasirašyti išsiunčiamus duomenis naudojant kliento liudijimą" msgid "Signature" msgstr "Parašas" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Nurodyti ĮPAĮA įrenginio tiekėją/produkto ID" msgid "Specify the number of bytes per USB transfer" msgstr "Nurodyti baitų skaičių tenkantį vienam USB persiuntimui" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Būsena" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Būsena" #. TRANSLATORS: section header for the release one line summary msgid "Summary" msgstr "Santrauka" msgid "Target" msgstr "Paskirtis" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Nėra jokios patvirtintos programinės aparatinės įrangos." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Ši programa gali tinkamai veikti tik pagrindinio naudotojo teisėmis" #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Šį įrankį gali naudoti tik pagrindinis naudotojas" #. TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" msgid "Title" msgstr "Pavadinimas" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Persiuntimo dydis" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipas" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI programinės aparatinės įrangos paslaugų programa" #. TRANSLATORS: section header for firmware URI msgid "URI" msgstr "URI" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Nežinoma" msgid "Unlock the device to allow access" msgstr "Atrakinti įrenginį, kad būtų leista prieiga" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Atrakina įrenginį programinės aparatinės įrangos prieigai" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Atnaujinimo metu panaikinti derinimo vėliavėlės nustatymą" #. TRANSLATORS: section header for firmware checksum msgid "Update Checksum" msgstr "Atnaujinimo kontrolinė suma" #. TRANSLATORS: section header for long firmware desc msgid "Update Description" msgstr "Atnaujinimo aprašas" #. TRANSLATORS: section header for the amount #. * of time it takes to install the update msgid "Update Duration" msgstr "Atnaujinimo trukmė" #. TRANSLATORS: section header for firmware remote http:// msgid "Update Location" msgstr "Atnaujinimo vieta" #. TRANSLATORS: section header for the release name msgid "Update Name" msgstr "Atnaujinimo pavadinimas" #. TRANSLATORS: section header for remote ID, e.g. lvfs-testing msgid "Update Remote ID" msgstr "Atnaujinimų nuotolinės saugyklos ID" #. TRANSLATORS: section header for the release one line summary msgid "Update Summary" msgstr "Atnaujinimo santrauka" #. TRANSLATORS: section header for firmware version msgid "Update Version" msgstr "Atnaujinimo versija" #. TRANSLATORS: command description msgid "Update all devices that match local metadata" msgstr "Atnaujinti visus įrenginius, kurie atitinka vietinius metaduomenis" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Atnaujinimo nesėkmė yra žinoma problema, išsamesnei informacijai apsilankykite šiame URL:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Atnaujinti dabar?" msgid "Update the stored device verification information" msgstr "Atnaujinti saugomo įrenginio patikrinimo informaciją" #. TRANSLATORS: command description msgid "Update the stored metadata with current ROM contents" msgstr "Atnaujinti saugomus metaduomenis esamu ROM turiniu" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Atnaujinti saugomus metaduomenis esamu turiniu" #. TRANSLATORS: command description msgid "Updates all firmware to latest versions available" msgstr "Atnaujina visą programinę aparatinę įrangą iki naujausios prieinamos versijos" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Atnaujinama %s iš %s į %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Atnaujinama %s…" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Išsiuntimo žinutė:" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Išsiųsti ataskaitą dabar?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Programinės aparatinės įrangos ataskaitų išsiuntimas padeda aparatinės įrangos tiekėjams greitai atpažinti nesėkmingus bei sėkmingus atnaujinimus tikruose įrenginiuose." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Naudotojo vardas" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Patikrinama…" #. TRANSLATORS: section header for release version number msgid "Version" msgstr "Versija" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Laukiama…" #. TRANSLATORS: command description msgid "Watch DFU devices being hotplugged" msgstr "Stebėti ĮPAĮA įrenginius, kurie atnaujinami nepaleidžiant iš naujo" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Stebėti aparatinės įrangos pakeitimus" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Rašyti programinę aparatinę įrangą iš failo į įrenginį" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Rašyti programinę aparatinę įrangą iš failo į vieną skaidinį" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Rašoma…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Gali būti, kad jūsų platintojas, suderinamumui su jūsų sistema ar prijungtais įrenginiais, nėra patvirtinęs jokių programinės aparatinės įrangos atnaujinimų." fwupd-1.2.14/po/make-images000077500000000000000000000144671402665037500154740ustar00rootroot00000000000000#!/usr/bin/python3 """ This thing rasterizes text for use later """ # pylint: disable=wrong-import-position,too-many-locals,unused-argument # pylint: disable=invalid-name,too-many-instance-attributes """ SPDX-License-Identifier: LGPL-2.1+ """ import os import sys import gettext import math import cairo import gi gi.require_version('Pango', '1.0') gi.require_version('PangoCairo', '1.0') from gi.repository import Pango, PangoCairo from PIL import Image def usage(return_code): """ print usage and exit with the supplied return code """ if return_code == 0: out = sys.stdout else: out = sys.stderr out.write("usage: make-images

%s

", /* TRANSLATORS: show the user a warning */ _("Your distributor may not have verified any of " "the firmware updates for compatibility with your " "system or connected devices.")); g_string_append_printf (str, "

%s

", /* TRANSLATORS: show the user a warning */ _("Enabling this remote is done at your own risk.")); return str; } static GString * fu_config_get_remote_agreement_for_app (FwupdRemote *self, XbNode *component, GError **error) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(XbNode) n = NULL; /* manually find the first agreement section */ n = xb_node_query_first (component, "agreement/agreement_section/description/*", &error_local); if (n == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No agreement description found: %s", error_local->message); return NULL; } tmp = xb_node_export (n, XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS, error); if (tmp == NULL) return NULL; return g_string_new (tmp); } static gchar * fu_config_build_remote_component_id (FwupdRemote *remote) { return g_strdup_printf ("org.freedesktop.fwupd.remotes.%s", fwupd_remote_get_id (remote)); } static gboolean fu_config_add_remotes_for_path (FuConfig *self, const gchar *path, GError **error) { const gchar *tmp; g_autofree gchar *path_remotes = NULL; g_autoptr(GDir) dir = NULL; path_remotes = g_build_filename (path, "remotes.d", NULL); if (!g_file_test (path_remotes, G_FILE_TEST_EXISTS)) { g_debug ("path %s does not exist", path_remotes); return TRUE; } if (!fu_config_add_inotify (self, path_remotes, error)) return FALSE; dir = g_dir_open (path_remotes, 0, error); if (dir == NULL) return FALSE; while ((tmp = g_dir_read_name (dir)) != NULL) { g_autofree gchar *filename = g_build_filename (path_remotes, tmp, NULL); g_autoptr(FwupdRemote) remote = fwupd_remote_new (); /* skip invalid files */ if (!g_str_has_suffix (tmp, ".conf")) { g_debug ("skipping invalid file %s", filename); continue; } /* load from keyfile */ g_debug ("loading config from %s", filename); if (!fwupd_remote_load_from_filename (remote, filename, NULL, error)) { g_prefix_error (error, "failed to load %s: ", filename); return FALSE; } /* watch the config file and the XML file itself */ if (!fu_config_add_inotify (self, filename, error)) return FALSE; if (!fu_config_add_inotify (self, fwupd_remote_get_filename_cache (remote), error)) return FALSE; /* try to find a custom agreement, falling back to a generic warning */ if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_DOWNLOAD) { g_autoptr(GString) agreement_markup = NULL; g_autofree gchar *component_id = fu_config_build_remote_component_id (remote); g_autoptr(XbNode) component = NULL; g_autofree gchar *xpath = NULL; xpath = g_strdup_printf ("component/id[text()='%s']/..", component_id); component = xb_silo_query_first (self->silo, xpath, NULL); if (component != NULL) { agreement_markup = fu_config_get_remote_agreement_for_app (remote, component, error); } else { agreement_markup = fu_config_get_remote_agreement_default (remote, error); } if (agreement_markup == NULL) return FALSE; /* replace any dynamic values from os-release */ tmp = g_hash_table_lookup (self->os_release, "NAME"); if (tmp == NULL) tmp = "this distribution"; fu_common_string_replace (agreement_markup, "$OS_RELEASE:NAME$", tmp); tmp = g_hash_table_lookup (self->os_release, "BUG_REPORT_URL"); if (tmp == NULL) tmp = "https://github.com/hughsie/fwupd/issues"; fu_common_string_replace (agreement_markup, "$OS_RELEASE:BUG_REPORT_URL$", tmp); fwupd_remote_set_agreement (remote, agreement_markup->str); } /* set mtime */ fwupd_remote_set_mtime (remote, fu_config_get_remote_mtime (self, remote)); g_ptr_array_add (self->remotes, g_steal_pointer (&remote)); } return TRUE; } static gint fu_config_remote_sort_cb (gconstpointer a, gconstpointer b) { FwupdRemote *remote_a = *((FwupdRemote **) a); FwupdRemote *remote_b = *((FwupdRemote **) b); /* use priority first */ if (fwupd_remote_get_priority (remote_a) < fwupd_remote_get_priority (remote_b)) return 1; if (fwupd_remote_get_priority (remote_a) > fwupd_remote_get_priority (remote_b)) return -1; /* fall back to name */ return g_strcmp0 (fwupd_remote_get_id (remote_a), fwupd_remote_get_id (remote_b)); } static FwupdRemote * fu_config_get_remote_by_id_noref (GPtrArray *remotes, const gchar *remote_id) { for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); if (g_strcmp0 (remote_id, fwupd_remote_get_id (remote)) == 0) return remote; } return NULL; } static guint fu_config_remotes_depsolve_with_direction (FuConfig *self, gint inc) { guint cnt = 0; for (guint i = 0; i < self->remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (self->remotes, i); gchar **order = inc < 0 ? fwupd_remote_get_order_after (remote) : fwupd_remote_get_order_before (remote); if (order == NULL) continue; for (guint j = 0; order[j] != NULL; j++) { FwupdRemote *remote2; if (g_strcmp0 (order[j], fwupd_remote_get_id (remote)) == 0) { g_debug ("ignoring self-dep remote %s", order[j]); continue; } remote2 = fu_config_get_remote_by_id_noref (self->remotes, order[j]); if (remote2 == NULL) { g_debug ("ignoring unfound remote %s", order[j]); continue; } if (fwupd_remote_get_priority (remote) > fwupd_remote_get_priority (remote2)) continue; g_debug ("ordering %s=%s+%i", fwupd_remote_get_id (remote), fwupd_remote_get_id (remote2), inc); fwupd_remote_set_priority (remote, fwupd_remote_get_priority (remote2) + inc); /* increment changes counter */ cnt++; } } return cnt; } static gboolean fu_config_load_remotes (FuConfig *self, GError **error) { guint depsolve_check; g_autoptr(GPtrArray) paths = NULL; /* get a list of all config paths */ paths = fu_config_get_config_paths (); if (paths->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No search paths found"); return FALSE; } /* look for all remotes */ for (guint i = 0; i < paths->len; i++) { const gchar *path = g_ptr_array_index (paths, i); g_debug ("using config path of %s", path); if (!fu_config_add_remotes_for_path (self, path, error)) return FALSE; } /* depsolve */ for (depsolve_check = 0; depsolve_check < 100; depsolve_check++) { guint cnt = 0; cnt += fu_config_remotes_depsolve_with_direction (self, 1); cnt += fu_config_remotes_depsolve_with_direction (self, -1); if (cnt == 0) break; } if (depsolve_check == 100) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot depsolve remotes ordering"); return FALSE; } /* order these by priority, then name */ g_ptr_array_sort (self->remotes, fu_config_remote_sort_cb); /* success */ return TRUE; } gboolean fu_config_modify_and_save (FuConfig *self, const gchar *key, const gchar *value, GError **error) { g_key_file_set_string (self->keyfile, "fwupd", key, value); return g_key_file_save_to_file (self->keyfile, self->config_file, error); } static gboolean fu_config_load_from_file (FuConfig *self, const gchar *config_file, GError **error) { GFileMonitor *monitor; guint64 archive_size_max; guint idle_timeout; g_auto(GStrv) approved_firmware = NULL; g_auto(GStrv) devices = NULL; g_auto(GStrv) plugins = NULL; g_autoptr(GFile) file = NULL; g_autofree gchar *domains = NULL; /* ensure empty in case we're called from a monitor change */ g_ptr_array_set_size (self->blacklist_devices, 0); g_ptr_array_set_size (self->blacklist_plugins, 0); g_ptr_array_set_size (self->approved_firmware, 0); g_ptr_array_set_size (self->monitors, 0); g_ptr_array_set_size (self->remotes, 0); g_debug ("loading config values from %s", config_file); if (!g_key_file_load_from_file (self->keyfile, config_file, G_KEY_FILE_KEEP_COMMENTS, error)) return FALSE; /* set up a notify watch */ file = g_file_new_for_path (config_file); monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, error); if (monitor == NULL) return FALSE; g_signal_connect (monitor, "changed", G_CALLBACK (fu_config_monitor_changed_cb), self); g_ptr_array_add (self->monitors, monitor); /* get blacklisted devices */ devices = g_key_file_get_string_list (self->keyfile, "fwupd", "BlacklistDevices", NULL, /* length */ NULL); if (devices != NULL) { for (guint i = 0; devices[i] != NULL; i++) { g_ptr_array_add (self->blacklist_devices, g_strdup (devices[i])); } } /* get blacklisted plugins */ plugins = g_key_file_get_string_list (self->keyfile, "fwupd", "BlacklistPlugins", NULL, /* length */ NULL); if (plugins != NULL) { for (guint i = 0; plugins[i] != NULL; i++) { g_ptr_array_add (self->blacklist_plugins, g_strdup (plugins[i])); } } /* get approved firmware */ approved_firmware = g_key_file_get_string_list (self->keyfile, "fwupd", "ApprovedFirmware", NULL, /* length */ NULL); if (approved_firmware != NULL) { for (guint i = 0; approved_firmware[i] != NULL; i++) { g_ptr_array_add (self->approved_firmware, g_strdup (approved_firmware[i])); } } /* get maximum archive size, defaulting to something sane */ archive_size_max = g_key_file_get_uint64 (self->keyfile, "fwupd", "ArchiveSizeMax", NULL); if (archive_size_max > 0) self->archive_size_max = archive_size_max *= 0x100000; /* get idle timeout */ idle_timeout = g_key_file_get_uint64 (self->keyfile, "fwupd", "IdleTimeout", NULL); if (idle_timeout > 0) self->idle_timeout = idle_timeout; /* get the domains to run in verbose */ domains = g_key_file_get_string (self->keyfile, "fwupd", "VerboseDomains", NULL); if (domains != NULL && domains[0] != '\0') g_setenv ("FWUPD_VERBOSE", domains, TRUE); return TRUE; } static gboolean fu_config_load_metainfos (XbBuilder *builder, GError **error) { const gchar *fn; g_autofree gchar *datadir = NULL; g_autofree gchar *metainfo_path = NULL; g_autoptr(GDir) dir = NULL; /* pkg metainfo dir */ datadir = fu_common_get_path (FU_PATH_KIND_DATADIR_PKG); metainfo_path = g_build_filename (datadir, "metainfo", NULL); if (!g_file_test (metainfo_path, G_FILE_TEST_EXISTS)) return TRUE; g_debug ("loading %s", metainfo_path); dir = g_dir_open (metainfo_path, 0, error); if (dir == NULL) return FALSE; while ((fn = g_dir_read_name (dir)) != NULL) { if (g_str_has_suffix (fn, ".metainfo.xml")) { g_autofree gchar *filename = g_build_filename (metainfo_path, fn, NULL); g_autoptr(GFile) file = g_file_new_for_path (filename); g_autoptr(XbBuilderSource) source = xb_builder_source_new (); if (!xb_builder_source_load_file (source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, error)) return FALSE; xb_builder_import_source (builder, source); } } return TRUE; } gboolean fu_config_load (FuConfig *self, FuConfigLoadFlags flags, GError **error) { const gchar *const *locales = g_get_language_names (); g_autofree gchar *configdir = NULL; g_autofree gchar *cachedirpkg = NULL; g_autofree gchar *xmlbfn = NULL; g_autoptr(GFile) xmlb = NULL; g_autoptr(XbBuilder) builder = xb_builder_new (); XbBuilderCompileFlags compile_flags = XB_BUILDER_COMPILE_FLAG_SINGLE_LANG | XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID; g_return_val_if_fail (FU_IS_CONFIG (self), FALSE); /* load the main daemon config file */ configdir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR_PKG); self->config_file = g_build_filename (configdir, "daemon.conf", NULL); if (g_file_test (self->config_file, G_FILE_TEST_EXISTS)) { if (!fu_config_load_from_file (self, self->config_file, error)) return FALSE; } else { g_warning ("Daemon configuration %s not found", self->config_file); } /* load AppStream about the remotes */ self->os_release = fwupd_get_os_release (error); if (self->os_release == NULL) return FALSE; if (!fu_config_load_metainfos (builder, error)) return FALSE; /* add the locales, which is really only going to be 'C' or 'en' */ for (guint i = 0; locales[i] != NULL; i++) xb_builder_add_locale (builder, locales[i]); #if LIBXMLB_CHECK_VERSION(0,1,7) /* on a read-only filesystem don't care about the cache GUID */ if (flags & FU_CONFIG_LOAD_FLAG_READONLY_FS) compile_flags |= XB_BUILDER_COMPILE_FLAG_IGNORE_GUID; #endif /* build the metainfo silo */ cachedirpkg = fu_common_get_path (FU_PATH_KIND_CACHEDIR_PKG); xmlbfn = g_build_filename (cachedirpkg, "metainfo.xmlb", NULL); xmlb = g_file_new_for_path (xmlbfn); self->silo = xb_builder_ensure (builder, xmlb, compile_flags, NULL, error); if (self->silo == NULL) return FALSE; /* load remotes */ if (!fu_config_load_remotes (self, error)) return FALSE; /* success */ return TRUE; } GPtrArray * fu_config_get_remotes (FuConfig *self) { g_return_val_if_fail (FU_IS_CONFIG (self), NULL); return self->remotes; } guint fu_config_get_idle_timeout (FuConfig *self) { g_return_val_if_fail (FU_IS_CONFIG (self), 0); return self->idle_timeout; } FwupdRemote * fu_config_get_remote_by_id (FuConfig *self, const gchar *remote_id) { g_return_val_if_fail (FU_IS_CONFIG (self), NULL); return fu_config_get_remote_by_id_noref (self->remotes, remote_id); } GPtrArray * fu_config_get_blacklist_devices (FuConfig *self) { g_return_val_if_fail (FU_IS_CONFIG (self), NULL); return self->blacklist_devices; } guint64 fu_config_get_archive_size_max (FuConfig *self) { g_return_val_if_fail (FU_IS_CONFIG (self), 0); return self->archive_size_max; } GPtrArray * fu_config_get_blacklist_plugins (FuConfig *self) { g_return_val_if_fail (FU_IS_CONFIG (self), NULL); return self->blacklist_plugins; } GPtrArray * fu_config_get_approved_firmware (FuConfig *self) { g_return_val_if_fail (FU_IS_CONFIG (self), NULL); return self->approved_firmware; } static void fu_config_class_init (FuConfigClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_config_finalize; signals[SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void fu_config_init (FuConfig *self) { self->archive_size_max = 512 * 0x100000; self->keyfile = g_key_file_new (); self->blacklist_devices = g_ptr_array_new_with_free_func (g_free); self->blacklist_plugins = g_ptr_array_new_with_free_func (g_free); self->approved_firmware = g_ptr_array_new_with_free_func (g_free); self->remotes = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); self->monitors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); } static void fu_config_finalize (GObject *obj) { FuConfig *self = FU_CONFIG (obj); if (self->os_release != NULL) g_hash_table_unref (self->os_release); if (self->silo != NULL) g_object_unref (self->silo); g_key_file_unref (self->keyfile); g_ptr_array_unref (self->blacklist_devices); g_ptr_array_unref (self->blacklist_plugins); g_ptr_array_unref (self->approved_firmware); g_ptr_array_unref (self->remotes); g_ptr_array_unref (self->monitors); g_free (self->config_file); G_OBJECT_CLASS (fu_config_parent_class)->finalize (obj); } FuConfig * fu_config_new (void) { FuConfig *self; self = g_object_new (FU_TYPE_CONFIG, NULL); return FU_CONFIG (self); } fwupd-1.2.14/src/fu-config.h000066400000000000000000000025561402665037500155610ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-remote.h" G_BEGIN_DECLS #define FU_TYPE_CONFIG (fu_config_get_type ()) G_DECLARE_FINAL_TYPE (FuConfig, fu_config, FU, CONFIG, GObject) /** * FuConfigLoadFlags: * @FU_CONFIG_LOAD_FLAG_NONE: No flags set * @FU_CONFIG_LOAD_FLAG_READONLY_FS: Ignore readonly filesystem errors * * The flags to use when loading a configuration file. **/ typedef enum { FU_CONFIG_LOAD_FLAG_NONE = 0, FU_CONFIG_LOAD_FLAG_READONLY_FS = 1 << 0, /*< private >*/ FU_CONFIG_LOAD_FLAG_LAST } FuConfigLoadFlags; FuConfig *fu_config_new (void); gboolean fu_config_load (FuConfig *self, FuConfigLoadFlags flags, GError **error); gboolean fu_config_modify_and_save (FuConfig *self, const gchar *key, const gchar *value, GError **error); guint64 fu_config_get_archive_size_max (FuConfig *self); guint fu_config_get_idle_timeout (FuConfig *self); GPtrArray *fu_config_get_blacklist_devices (FuConfig *self); GPtrArray *fu_config_get_blacklist_plugins (FuConfig *self); GPtrArray *fu_config_get_approved_firmware (FuConfig *self); GPtrArray *fu_config_get_remotes (FuConfig *self); FwupdRemote *fu_config_get_remote_by_id (FuConfig *self, const gchar *remote_id); G_END_DECLS fwupd-1.2.14/src/fu-debug.c000066400000000000000000000124761402665037500153770ustar00rootroot00000000000000/* * Copyright (C) 2010-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDebug" #include #include #include #include #include typedef struct { GOptionGroup *group; gboolean verbose; gboolean console; gchar **plugin_verbose; gchar **daemon_verbose; } FuDebug; static void fu_debug_free (FuDebug *self) { g_option_group_set_parse_hooks (self->group, NULL, NULL); g_option_group_unref (self->group); g_strfreev (self->plugin_verbose); g_strfreev (self->daemon_verbose); g_free (self); } static gboolean fu_debug_filter_cb (FuDebug *self, const gchar *log_domain, GLogLevelFlags log_level) { const gchar *domains = g_getenv ("FWUPD_VERBOSE"); g_auto(GStrv) domains_str = NULL; /* include important things by default only */ if (domains == NULL) { if (log_level == G_LOG_LEVEL_INFO || log_level == G_LOG_LEVEL_CRITICAL || log_level == G_LOG_LEVEL_WARNING || log_level == G_LOG_LEVEL_ERROR) { return TRUE; } return FALSE; } /* everything */ if (g_strcmp0 (domains, "*") == 0) return TRUE; /* filter on domain */ domains_str = g_strsplit (domains, ",", -1); return g_strv_contains ((const gchar * const *) domains_str, log_domain); } static void fu_debug_handler_cb (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { FuDebug *self = (FuDebug *) user_data; g_autofree gchar *tmp = NULL; g_autoptr(GDateTime) dt = g_date_time_new_now_utc (); g_autoptr(GString) domain = NULL; /* should ignore */ if (!fu_debug_filter_cb (self, log_domain, log_level)) return; /* time header */ tmp = g_strdup_printf ("%02i:%02i:%02i:%04i", g_date_time_get_hour (dt), g_date_time_get_minute (dt), g_date_time_get_second (dt), g_date_time_get_microsecond (dt) / 1000); /* each file should have set this */ if (log_domain == NULL) log_domain = "FIXME"; /* pad out domain */ domain = g_string_new (log_domain); for (gsize i = domain->len; i < 20; i++) g_string_append (domain, " "); /* to file */ if (!self->console) { if (tmp != NULL) g_printerr ("%s ", tmp); g_printerr ("%s ", domain->str); g_printerr ("%s\n", message); return; } /* to screen */ switch (log_level) { case G_LOG_LEVEL_ERROR: case G_LOG_LEVEL_CRITICAL: case G_LOG_LEVEL_WARNING: /* critical in red */ if (tmp != NULL) g_printerr ("%c[%dm%s ", 0x1B, 32, tmp); g_printerr ("%s ", domain->str); g_printerr ("%c[%dm%s\n%c[%dm", 0x1B, 31, message, 0x1B, 0); break; default: /* debug in blue */ if (tmp != NULL) g_printerr ("%c[%dm%s ", 0x1B, 32, tmp); g_printerr ("%s ", domain->str); g_printerr ("%c[%dm%s\n%c[%dm", 0x1B, 34, message, 0x1B, 0); break; } } static gboolean fu_debug_pre_parse_hook (GOptionContext *context, GOptionGroup *group, gpointer data, GError **error) { FuDebug *self = (FuDebug *) data; const GOptionEntry main_entries[] = { { "verbose", 'v', 0, G_OPTION_ARG_NONE, &self->verbose, /* TRANSLATORS: turn on all debugging */ N_("Show debugging information for all domains"), NULL }, { "plugin-verbose", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &self->plugin_verbose, /* TRANSLATORS: this is for plugin development */ N_("Show plugin verbose information"), "PLUGIN-NAME" }, { "daemon-verbose", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &self->daemon_verbose, /* TRANSLATORS: this is for daemon development */ N_("Show daemon verbose information for a particular domain"), "DOMAIN" }, { NULL} }; /* add main entry */ g_option_context_add_main_entries (context, main_entries, NULL); return TRUE; } static gboolean fu_debug_post_parse_hook (GOptionContext *context, GOptionGroup *group, gpointer data, GError **error) { FuDebug *self = (FuDebug *) data; /* verbose? */ if (self->verbose) { g_setenv ("FWUPD_VERBOSE", "*", TRUE); } else if (self->daemon_verbose != NULL) { g_autofree gchar *str = g_strjoinv (",", self->daemon_verbose); g_setenv ("FWUPD_VERBOSE", str, TRUE); } /* redirect all domains to be able to change FWUPD_VERBOSE at runtime */ g_log_set_default_handler (fu_debug_handler_cb, self); /* are we on an actual TTY? */ self->console = (isatty (fileno (stderr)) == 1); g_debug ("Verbose debugging %s (on console %i)", self->verbose ? "enabled" : "disabled", self->console); /* allow each plugin to be extra verbose */ if (self->plugin_verbose != NULL) { for (guint i = 0; self->plugin_verbose[i] != NULL; i++) { g_autofree gchar *name_caps = NULL; g_autofree gchar *varname = NULL; name_caps = g_ascii_strup (self->plugin_verbose[i], -1); varname = g_strdup_printf ("FWUPD_%s_VERBOSE", name_caps); g_debug ("setting %s=1", varname); g_setenv (varname, "1", TRUE); } } return TRUE; } /*(transfer): full */ GOptionGroup * fu_debug_get_option_group (void) { FuDebug *self = g_new0 (FuDebug, 1); self->group = g_option_group_new ("debug", /* TRANSLATORS: for the --verbose arg */ _("Debugging Options"), /* TRANSLATORS: for the --verbose arg */ _("Show debugging options"), self, (GDestroyNotify) fu_debug_free); g_option_group_set_parse_hooks (self->group, fu_debug_pre_parse_hook, fu_debug_post_parse_hook); return g_option_group_ref (self->group); } fwupd-1.2.14/src/fu-debug.h000066400000000000000000000003361402665037500153740ustar00rootroot00000000000000/* * Copyright (C) 2010-2011 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS GOptionGroup *fu_debug_get_option_group (void); G_END_DECLS fwupd-1.2.14/src/fu-device-list.c000066400000000000000000000610741402665037500165170ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDeviceList" #include "config.h" #include #include #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-mutex.h" #include "fwupd-error.h" /** * SECTION:fu-device-list * @short_description: a list of devices * * This list of devices provides a way to find a device using either the * device-id or a GUID. * * The device list will emit ::added and ::removed signals when the device list * has been changed. If the #FuDevice has changed during a device replug then * the ::changed signal will be emitted instead of ::added and then ::removed. * * See also: #FuDevice */ static void fu_device_list_finalize (GObject *obj); struct _FuDeviceList { GObject parent_instance; GPtrArray *devices; /* of FuDeviceItem */ GRWLock devices_mutex; }; enum { SIGNAL_ADDED, SIGNAL_REMOVED, SIGNAL_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = { 0 }; typedef struct { FuDevice *device; FuDevice *device_old; FuDeviceList *self; /* no ref */ GMainLoop *replug_loop; /* block waiting for replug */ guint replug_id; /* timeout the loop */ guint remove_id; } FuDeviceItem; G_DEFINE_TYPE (FuDeviceList, fu_device_list, G_TYPE_OBJECT) static void fu_device_list_emit_device_added (FuDeviceList *self, FuDevice *device) { g_debug ("::added %s", fu_device_get_id (device)); g_signal_emit (self, signals[SIGNAL_ADDED], 0, device); } static void fu_device_list_emit_device_removed (FuDeviceList *self, FuDevice *device) { g_debug ("::removed %s", fu_device_get_id (device)); g_signal_emit (self, signals[SIGNAL_REMOVED], 0, device); } static void fu_device_list_emit_device_changed (FuDeviceList *self, FuDevice *device) { g_debug ("::changed %s", fu_device_get_id (device)); g_signal_emit (self, signals[SIGNAL_CHANGED], 0, device); } /** * fu_device_list_get_all: * @self: A #FuDeviceList * * Returns all the devices that have been added to the device list. * This includes devices that are no longer active, for instance where a * different plugin has taken over responsibility of the #FuDevice. * * Returns: (transfer container) (element-type FuDevice): the devices * * Since: 1.0.2 **/ GPtrArray * fu_device_list_get_all (FuDeviceList *self) { GPtrArray *devices; g_return_val_if_fail (FU_IS_DEVICE_LIST (self), NULL); devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_rw_lock_reader_lock (&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); g_ptr_array_add (devices, g_object_ref (item->device)); } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device_old == NULL) continue; g_ptr_array_add (devices, g_object_ref (item->device_old)); } g_rw_lock_reader_unlock (&self->devices_mutex); return devices; } /** * fu_device_list_get_active: * @self: A #FuDeviceList * * Returns all the active devices that have been added to the device list. * An active device is defined as a device that is currently connected and has * is owned by a plugin. * * Returns: (transfer container) (element-type FuDevice): the devices * * Since: 1.0.2 **/ GPtrArray * fu_device_list_get_active (FuDeviceList *self) { GPtrArray *devices; g_return_val_if_fail (FU_IS_DEVICE_LIST (self), NULL); devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_rw_lock_reader_lock (&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); g_ptr_array_add (devices, g_object_ref (item->device)); } g_rw_lock_reader_unlock (&self->devices_mutex); return devices; } static FuDeviceItem * fu_device_list_find_by_device (FuDeviceList *self, FuDevice *device) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&self->devices_mutex); g_return_val_if_fail (locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device == device) return item; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device_old == device) return item; } return NULL; } static FuDeviceItem * fu_device_list_find_by_guid (FuDeviceList *self, const gchar *guid) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&self->devices_mutex); g_return_val_if_fail (locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (fu_device_has_guid (item->device, guid)) return item; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device_old == NULL) continue; if (fu_device_has_guid (item->device_old, guid)) return item; } return NULL; } static FuDeviceItem * fu_device_list_find_by_connection (FuDeviceList *self, const gchar *physical_id, const gchar *logical_id) { g_autoptr(GRWLockReaderLocker) locker = NULL; if (physical_id == NULL) return NULL; locker = g_rw_lock_reader_locker_new (&self->devices_mutex); g_return_val_if_fail (locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index (self->devices, i); FuDevice *device = item_tmp->device; if (device != NULL && g_strcmp0 (fu_device_get_physical_id (device), physical_id) == 0 && g_strcmp0 (fu_device_get_logical_id (device), logical_id) == 0) return item_tmp; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index (self->devices, i); FuDevice *device = item_tmp->device_old; if (device != NULL && g_strcmp0 (fu_device_get_physical_id (device), physical_id) == 0 && g_strcmp0 (fu_device_get_logical_id (device), logical_id) == 0) return item_tmp; } return NULL; } static FuDeviceItem * fu_device_list_find_by_id (FuDeviceList *self, const gchar *device_id, gboolean *multiple_matches) { FuDeviceItem *item = NULL; gsize device_id_len; /* sanity check */ if (device_id == NULL) { g_critical ("device ID was NULL"); return NULL; } /* support abbreviated hashes */ device_id_len = strlen (device_id); g_rw_lock_reader_lock (&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index (self->devices, i); const gchar *ids[] = { fu_device_get_id (item_tmp->device), fu_device_get_equivalent_id (item_tmp->device), NULL }; for (guint j = 0; ids[j] != NULL; j++) { if (strncmp (ids[j], device_id, device_id_len) == 0) { if (item != NULL && multiple_matches != NULL) *multiple_matches = TRUE; item = item_tmp; } } } g_rw_lock_reader_unlock (&self->devices_mutex); if (item != NULL) return item; /* only search old devices if we didn't find the active device */ g_rw_lock_reader_lock (&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index (self->devices, i); const gchar *ids[3] = { NULL }; if (item_tmp->device_old == NULL) continue; ids[0] = fu_device_get_id (item_tmp->device_old); ids[1] = fu_device_get_equivalent_id (item_tmp->device_old); for (guint j = 0; ids[j] != NULL; j++) { if (strncmp (ids[j], device_id, device_id_len) == 0) { if (item != NULL && multiple_matches != NULL) *multiple_matches = TRUE; item = item_tmp; } } } g_rw_lock_reader_unlock (&self->devices_mutex); return item; } /** * fu_device_list_get_old: * @self: A #FuDeviceList * @device: A #FuDevice * * Returns the old device associated with the currently active device. * * Returns: (transfer full): the device, or %NULL if not found * * Since: 1.0.3 **/ FuDevice * fu_device_list_get_old (FuDeviceList *self, FuDevice *device) { FuDeviceItem *item = fu_device_list_find_by_device (self, device); if (item == NULL) return NULL; if (item->device_old == NULL) return NULL; return g_object_ref (item->device_old); } static FuDeviceItem * fu_device_list_get_by_guids (FuDeviceList *self, GPtrArray *guids) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&self->devices_mutex); g_return_val_if_fail (locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index (guids, j); if (fu_device_has_guid (item->device, guid)) return item; } } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device_old == NULL) continue; for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index (guids, j); if (fu_device_has_guid (item->device_old, guid)) return item; } } return NULL; } static gboolean fu_device_list_device_delayed_remove_cb (gpointer user_data) { FuDeviceItem *item = (FuDeviceItem *) user_data; FuDeviceList *self = FU_DEVICE_LIST (item->self); /* no longer valid */ item->remove_id = 0; /* just remove now */ g_debug ("doing delayed removal"); fu_device_list_emit_device_removed (self, item->device); g_rw_lock_writer_lock (&self->devices_mutex); g_ptr_array_remove (self->devices, item); g_rw_lock_writer_unlock (&self->devices_mutex); return G_SOURCE_REMOVE; } static void fu_device_list_remove_with_delay (FuDeviceItem *item) { /* we can't do anything with an unconnected device */ fu_device_remove_flag (item->device, FWUPD_DEVICE_FLAG_UPDATABLE); /* give the hardware time to re-enumerate or the user time to * re-insert the device with a magic button pressed */ g_debug ("waiting %ums for %s device removal", fu_device_get_remove_delay (item->device), fu_device_get_name (item->device)); item->remove_id = g_timeout_add (fu_device_get_remove_delay (item->device), fu_device_list_device_delayed_remove_cb, item); } /** * fu_device_list_remove: * @self: A #FuDeviceList * @device: A #FuDevice * * Removes a specific device from the list if it exists. * * If the @device has a remove-delay set then a timeout will be started. If * the exact same #FuDevice is added to the list with fu_device_list_add() * within the timeout then only a ::changed signal will be emitted. * * If there is no remove-delay set, the ::removed signal will be emitted * straight away. * * Since: 1.0.2 **/ void fu_device_list_remove (FuDeviceList *self, FuDevice *device) { FuDeviceItem *item; GPtrArray *children; g_return_if_fail (FU_IS_DEVICE_LIST (self)); g_return_if_fail (FU_IS_DEVICE (device)); /* check the device already exists */ item = fu_device_list_find_by_id (self, fu_device_get_id (device), NULL); if (item == NULL) { g_debug ("device %s not found", fu_device_get_id (device)); return; } /* ensure never fired if the remove delay is changed */ if (item->remove_id > 0) { g_source_remove (item->remove_id); item->remove_id = 0; } /* remove any children associated with device */ children = fu_device_get_children (device); for (guint j = 0; j < children->len; j++) { FuDevice *child = g_ptr_array_index (children, j); FuDeviceItem *child_item = fu_device_list_find_by_id (self, fu_device_get_id (child), NULL); if (child_item == NULL) { g_debug ("device %s not found", fu_device_get_id (child)); continue; } if (fu_device_get_remove_delay (child_item->device) > 0) { fu_device_list_remove_with_delay (child_item); continue; } fu_device_list_emit_device_removed (self, child); g_rw_lock_writer_lock (&self->devices_mutex); g_ptr_array_remove (self->devices, child_item); g_rw_lock_writer_unlock (&self->devices_mutex); } /* delay the removal and check for replug */ if (fu_device_get_remove_delay (item->device) > 0) { fu_device_list_remove_with_delay (item); return; } /* remove right now */ fu_device_list_emit_device_removed (self, item->device); g_rw_lock_writer_lock (&self->devices_mutex); g_ptr_array_remove (self->devices, item); g_rw_lock_writer_unlock (&self->devices_mutex); } static void fu_device_list_add_missing_guids (FuDevice *device_new, FuDevice *device_old) { GPtrArray *guids_old = fu_device_get_guids (device_old); for (guint i = 0; i < guids_old->len; i++) { const gchar *guid_tmp = g_ptr_array_index (guids_old, i); if (!fu_device_has_guid (device_new, guid_tmp)) { g_debug ("adding GUID %s to device", guid_tmp); fu_device_add_counterpart_guid (device_new, guid_tmp); } } } static void fu_device_list_replace (FuDeviceList *self, FuDeviceItem *item, FuDevice *device) { /* clear timeout if scheduled */ if (item->remove_id != 0) { g_source_remove (item->remove_id); item->remove_id = 0; } /* copy over any GUIDs that used to exist */ fu_device_list_add_missing_guids (device, item->device); /* enforce the vendor ID if specified */ if (fu_device_get_vendor_id (item->device) != NULL && fu_device_get_vendor_id (device) == NULL) { const gchar *vendor_id = fu_device_get_vendor_id (item->device); g_debug ("copying old vendor ID %s to new device", vendor_id); fu_device_set_vendor_id (device, vendor_id); } /* copy over the version strings if not set */ if (fu_device_get_version (item->device) != NULL && fu_device_get_version (device) == NULL) { const gchar *version = fu_device_get_version (item->device); g_debug ("copying old version %s to new device", version); fu_device_set_version (device, version, fu_device_get_version_format (item->device)); } /* always use the runtime version */ if (fu_device_has_flag (item->device, FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION) && fu_device_has_flag (item->device, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER)) { const gchar *version = fu_device_get_version (item->device); g_debug ("forcing runtime version %s to new device", version); fu_device_set_version (device, version, fu_device_get_version_format (item->device)); } /* allow another plugin to handle the write too */ if (fu_device_has_flag (item->device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED)) { g_debug ("copying another-write-required to new device"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); } /* copy the parent if not already set */ if (fu_device_get_parent (item->device) != NULL && fu_device_get_parent (device) == NULL) { FuDevice *parent = fu_device_get_parent (item->device); g_debug ("copying parent %s to new device", fu_device_get_id (parent)); fu_device_set_parent (device, parent); } /* assign the new device */ g_set_object (&item->device_old, item->device); g_set_object (&item->device, device); fu_device_list_emit_device_changed (self, device); /* we were waiting for this... */ if (g_main_loop_is_running (item->replug_loop)) { g_debug ("quitting replug loop"); g_main_loop_quit (item->replug_loop); } } /** * fu_device_list_add: * @self: A #FuDeviceList * @device: A #FuDevice * * Adds a specific device to the device list if not already present. * * If the @device (or a compatible @device) has been previously removed within * the remove-timeout then only the ::changed signal will be emitted on calling * this function. Otherwise the ::added signal will be emitted straight away. * * Compatible devices are defined as #FuDevice objects that share at least one * device GUID. If a compatible device is matched then the vendor ID and * version will be copied to the new object if they are not already set. * * Any GUIDs present on the old device and not on the new device will be * inherited and do not have to be copied over by plugins manually. * * Returns: (transfer none): a device, or %NULL if not found * * Since: 1.0.2 **/ void fu_device_list_add (FuDeviceList *self, FuDevice *device) { FuDeviceItem *item; g_return_if_fail (FU_IS_DEVICE_LIST (self)); g_return_if_fail (FU_IS_DEVICE (device)); /* is the device waiting to be replugged? */ item = fu_device_list_find_by_id (self, fu_device_get_id (device), NULL); if (item != NULL && item->remove_id != 0) { g_debug ("found existing device %s, reusing item", fu_device_get_id (item->device)); if (item->remove_id != 0) { g_source_remove (item->remove_id); item->remove_id = 0; } fu_device_list_replace (self, item, device); return; } /* verify the device does not already exist */ if (item != NULL) { g_debug ("device %s already exists, ignoring", fu_device_get_id (item->device)); return; } /* verify a compatible device does not already exist */ item = fu_device_list_get_by_guids (self, fu_device_get_guids (device)); if (item == NULL) { item = fu_device_list_find_by_connection (self, fu_device_get_physical_id (device), fu_device_get_logical_id (device)); } if (item != NULL && item->remove_id != 0) { g_debug ("found compatible device %s recently removed, reusing " "item from plugin %s for plugin %s", fu_device_get_id (item->device), fu_device_get_plugin (item->device), fu_device_get_plugin (device)); fu_device_list_replace (self, item, device); return; } /* added the same device from a different plugin */ if (item != NULL && g_strcmp0 (fu_device_get_plugin (item->device), fu_device_get_plugin (device)) != 0) { if (fu_device_get_priority (device) < fu_device_get_priority (item->device)) { g_debug ("ignoring device %s [%s] as better device %s [%s] already exists", fu_device_get_id (device), fu_device_get_plugin (device), fu_device_get_id (item->device), fu_device_get_plugin (item->device)); return; } if (fu_device_get_priority (device) == fu_device_get_priority (item->device)) { g_warning ("ignoring device %s [%s] existing device %s [%s] already exists", fu_device_get_id (device), fu_device_get_plugin (device), fu_device_get_id (item->device), fu_device_get_plugin (item->device)); return; } g_debug ("removing device %s [%s] as better device %s [%s] added", fu_device_get_id (item->device), fu_device_get_plugin (item->device), fu_device_get_id (device), fu_device_get_plugin (device)); fu_device_list_remove (self, item->device); } /* add helper */ item = g_new0 (FuDeviceItem, 1); item->self = self; /* no ref */ item->device = g_object_ref (device); item->replug_loop = g_main_loop_new (NULL, FALSE); g_rw_lock_writer_lock (&self->devices_mutex); g_ptr_array_add (self->devices, item); g_rw_lock_writer_unlock (&self->devices_mutex); fu_device_list_emit_device_added (self, device); } /** * fu_device_list_get_by_guid: * @self: A #FuDeviceList * @guid: A device GUID * @error: A #GError, or %NULL * * Finds a specific device that has the matching GUID. * * Returns: (transfer full): a device, or %NULL if not found * * Since: 1.0.2 **/ FuDevice * fu_device_list_get_by_guid (FuDeviceList *self, const gchar *guid, GError **error) { FuDeviceItem *item; g_return_val_if_fail (FU_IS_DEVICE_LIST (self), NULL); g_return_val_if_fail (guid != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); item = fu_device_list_find_by_guid (self, guid); if (item != NULL) return g_object_ref (item->device); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "GUID %s was not found", guid); return NULL; } static gboolean fu_device_list_replug_cb (gpointer user_data) { FuDeviceItem *item = (FuDeviceItem *) user_data; /* no longer valid */ item->replug_id = 0; /* quit loop */ g_debug ("device did not replug"); g_main_loop_quit (item->replug_loop); return FALSE; } /** * fu_device_list_wait_for_replug: * @self: A #FuDeviceList * @device: A #FuDevice * @error: A #GError, or %NULL * * Waits for a specific device to replug if %FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG * is set. * * If the device does not exist this function returns without an error. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_list_wait_for_replug (FuDeviceList *self, FuDevice *device, GError **error) { FuDeviceItem *item; guint remove_delay; g_return_val_if_fail (FU_IS_DEVICE_LIST (self), FALSE); g_return_val_if_fail (FU_IS_DEVICE (device), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* not found */ item = fu_device_list_find_by_device (self, device); if (item == NULL) return TRUE; /* not required, or possibly literally just happened */ if (!fu_device_has_flag (item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_debug ("no replug or re-enumerate required"); return TRUE; } /* plugin did not specify */ remove_delay = fu_device_get_remove_delay (device); if (remove_delay == 0) { remove_delay = FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE; g_warning ("plugin %s did not specify a remove delay for %s, " "so guessing we should wait %ums for replug", fu_device_get_plugin (device), fu_device_get_id (device), remove_delay); } else { g_debug ("waiting %ums for replug", remove_delay); } /* time to unplug and then re-plug */ item->replug_id = g_timeout_add (remove_delay, fu_device_list_replug_cb, item); g_main_loop_run (item->replug_loop); /* the loop was quit without the timer */ if (item->replug_id != 0) { g_debug ("waited for replug"); g_source_remove (item->replug_id); fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); item->replug_id = 0; return TRUE; } /* device was not added back to the device list */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device %s did not come back", fu_device_get_id (device)); return FALSE; } /** * fu_device_list_get_by_id: * @self: A #FuDeviceList * @device_id: A device ID, typically a SHA1 hash * @error: A #GError, or %NULL * * Finds a specific device using the ID string. This function also supports * using abbreviated hashes. * * Returns: (transfer full): a device, or %NULL if not found * * Since: 1.0.2 **/ FuDevice * fu_device_list_get_by_id (FuDeviceList *self, const gchar *device_id, GError **error) { FuDeviceItem *item; gboolean multiple_matches = FALSE; g_return_val_if_fail (FU_IS_DEVICE_LIST (self), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* multiple things matched */ item = fu_device_list_find_by_id (self, device_id, &multiple_matches); if (multiple_matches) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device ID %s was not unique", device_id); return NULL; } /* nothing at all matched */ if (item == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device ID %s was not found", device_id); return NULL; } /* something found */ return g_object_ref (item->device); } static void fu_device_list_item_free (FuDeviceItem *item) { if (item->remove_id != 0) g_source_remove (item->remove_id); if (item->replug_id != 0) g_source_remove (item->replug_id); if (item->device_old != NULL) g_object_unref (item->device_old); g_main_loop_unref (item->replug_loop); g_object_unref (item->device); g_free (item); } static void fu_device_list_class_init (FuDeviceListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_device_list_finalize; signals[SIGNAL_ADDED] = g_signal_new ("added", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); signals[SIGNAL_REMOVED] = g_signal_new ("removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); signals[SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); } static void fu_device_list_init (FuDeviceList *self) { self->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_device_list_item_free); g_rw_lock_init (&self->devices_mutex); } static void fu_device_list_finalize (GObject *obj) { FuDeviceList *self = FU_DEVICE_LIST (obj); g_ptr_array_unref (self->devices); g_rw_lock_clear (&self->devices_mutex); G_OBJECT_CLASS (fu_device_list_parent_class)->finalize (obj); } /** * fu_device_list_new: * * Creates a new device list. * * Returns: (transfer full): a #FuDeviceList * * Since: 1.0.2 **/ FuDeviceList * fu_device_list_new (void) { FuDeviceList *self; self = g_object_new (FU_TYPE_DEVICE_LIST, NULL); return FU_DEVICE_LIST (self); } fwupd-1.2.14/src/fu-device-list.h000066400000000000000000000020651402665037500165170ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-device.h" G_BEGIN_DECLS #define FU_TYPE_DEVICE_LIST (fu_device_list_get_type ()) G_DECLARE_FINAL_TYPE (FuDeviceList, fu_device_list, FU, DEVICE_LIST, GObject) FuDeviceList *fu_device_list_new (void); void fu_device_list_add (FuDeviceList *self, FuDevice *device); void fu_device_list_remove (FuDeviceList *self, FuDevice *device); GPtrArray *fu_device_list_get_all (FuDeviceList *self); GPtrArray *fu_device_list_get_active (FuDeviceList *self); FuDevice *fu_device_list_get_old (FuDeviceList *self, FuDevice *device); FuDevice *fu_device_list_get_by_id (FuDeviceList *self, const gchar *device_id, GError **error); FuDevice *fu_device_list_get_by_guid (FuDeviceList *self, const gchar *guid, GError **error); gboolean fu_device_list_wait_for_replug (FuDeviceList *self, FuDevice *device, GError **error); G_END_DECLS fwupd-1.2.14/src/fu-device-locker.c000066400000000000000000000103351402665037500170150ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDeviceLocker" #include "config.h" #include #include #include "fu-device-locker.h" #include "fu-usb-device.h" /** * SECTION:fu-device-locker * @title: FuDeviceLocker * @short_description: a device helper object * * An object that makes it easy to close a device when an object goes out of * scope. * * See also: #FuDevice */ struct _FuDeviceLocker { GObject parent_instance; GObject *device; gboolean device_open; FuDeviceLockerFunc open_func; FuDeviceLockerFunc close_func; }; G_DEFINE_TYPE (FuDeviceLocker, fu_device_locker, G_TYPE_OBJECT) static void fu_device_locker_finalize (GObject *obj) { FuDeviceLocker *self = FU_DEVICE_LOCKER (obj); /* close device */ if (self->device_open) { g_autoptr(GError) error = NULL; if (!self->close_func (self->device, &error)) g_warning ("failed to close device: %s", error->message); } g_object_unref (self->device); G_OBJECT_CLASS (fu_device_locker_parent_class)->finalize (obj); } static void fu_device_locker_class_init (FuDeviceLockerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_device_locker_finalize; } static void fu_device_locker_init (FuDeviceLocker *self) { } /** * fu_device_locker_new: * @device: A #GObject * @error: A #GError, or %NULL * * Opens the device for use. When the #FuDeviceLocker is deallocated the device * will be closed and any error will just be directed to the console. * This object is typically called using g_autoptr() but the device can also be * manually closed using g_clear_object(). * * The functions used for opening and closing the device are set automatically. * If the @device is not a type or supertype of #GUsbDevice or #FuDevice then * this function will not work. * * For custom objects please use fu_device_locker_new_full(). * * NOTE: If the @open_func failed then the @close_func will not be called. * * Think of this object as the device ownership. * * Returns: a #FuDeviceLocker, or %NULL if the @open_func failed. **/ FuDeviceLocker * fu_device_locker_new (gpointer device, GError **error) { g_return_val_if_fail (device != NULL, NULL); g_return_val_if_fail (error != NULL, NULL); /* GUsbDevice */ if (G_USB_IS_DEVICE (device)) { return fu_device_locker_new_full (device, (FuDeviceLockerFunc) g_usb_device_open, (FuDeviceLockerFunc) g_usb_device_close, error); } /* FuDevice */ if (FU_IS_DEVICE (device)) { return fu_device_locker_new_full (device, (FuDeviceLockerFunc) fu_device_open, (FuDeviceLockerFunc) fu_device_close, error); } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "device object type not supported"); return NULL; } /** * fu_device_locker_new_full: * @device: A #GObject * @open_func: (scope async): A function to open the device * @close_func: (scope async): A function to close the device * @error: A #GError, or %NULL * * Opens the device for use. When the #FuDeviceLocker is deallocated the device * will be closed and any error will just be directed to the console. * This object is typically called using g_autoptr() but the device can also be * manually closed using g_clear_object(). * * NOTE: If the @open_func failed then the @close_func will not be called. * * Think of this object as the device ownership. * * Returns: a #FuDeviceLocker, or %NULL if the @open_func failed. **/ FuDeviceLocker * fu_device_locker_new_full (gpointer device, FuDeviceLockerFunc open_func, FuDeviceLockerFunc close_func, GError **error) { g_autoptr(FuDeviceLocker) self = NULL; g_return_val_if_fail (device != NULL, NULL); g_return_val_if_fail (open_func != NULL, NULL); g_return_val_if_fail (close_func != NULL, NULL); g_return_val_if_fail (error != NULL, NULL); /* create object */ self = g_object_new (FU_TYPE_DEVICE_LOCKER, NULL); self->device = g_object_ref (device); self->open_func = open_func; self->close_func = close_func; /* open device */ if (!self->open_func (device, error)) return NULL; /* success */ self->device_open = TRUE; return g_steal_pointer (&self); } fwupd-1.2.14/src/fu-device-locker.h000066400000000000000000000012221402665037500170150ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FU_TYPE_DEVICE_LOCKER (fu_device_locker_get_type ()) G_DECLARE_FINAL_TYPE (FuDeviceLocker, fu_device_locker, FU, DEVICE_LOCKER, GObject) typedef gboolean (*FuDeviceLockerFunc) (GObject *device, GError **error); FuDeviceLocker *fu_device_locker_new (gpointer device, GError **error); FuDeviceLocker *fu_device_locker_new_full (gpointer device, FuDeviceLockerFunc open_func, FuDeviceLockerFunc close_func, GError **error); G_END_DECLS fwupd-1.2.14/src/fu-device-metadata.h000066400000000000000000000033641402665037500173270ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS /** * SECTION:fu-device-metadata * @short_description: a device helper object * * An object that makes it easy to close a device when an object goes out of * scope. * * See also: #FuDevice */ /** * FU_DEVICE_METADATA_TBT_CAN_FORCE_POWER: * * If the system can force-enable the Thunderbolt controller. * Consumed by the thunderbolt plugin. */ #define FU_DEVICE_METADATA_TBT_CAN_FORCE_POWER "Thunderbolt::CanForcePower" /** * FU_DEVICE_METADATA_TBT_IS_SAFE_MODE: * * If the Thunderbolt hardware is stuck in safe mode. * Consumed by the thunderbolt plugin. */ #define FU_DEVICE_METADATA_TBT_IS_SAFE_MODE "Thunderbolt::IsSafeMode" /** * FU_DEVICE_METADATA_UEFI_DEVICE_KIND: * * The type of UEFI device, e.g. "system-firmware" or "device-firmware" * Consumed by the uefi plugin when other devices register fake devices that * need to be handled as a capsule update. */ #define FU_DEVICE_METADATA_UEFI_DEVICE_KIND "UefiDeviceKind" /** * FU_DEVICE_METADATA_UEFI_FW_VERSION: * * The firmware version of the UEFI device specified as a 32 bit unsigned * integer. * Consumed by the uefi plugin when other devices register fake devices that * need to be handled as a capsule update. */ #define FU_DEVICE_METADATA_UEFI_FW_VERSION "UefiFwVersion" /** * FU_DEVICE_METADATA_UEFI_CAPSULE_FLAGS: * * The capsule flags for the UEFI device, e.g. %EFI_CAPSULE_HEADER_FLAGS_PERSIST_ACROSS_RESET * Consumed by the uefi plugin when other devices register fake devices that * need to be handled as a capsule update. */ #define FU_DEVICE_METADATA_UEFI_CAPSULE_FLAGS "UefiCapsuleFlags" G_END_DECLS fwupd-1.2.14/src/fu-device-private.h000066400000000000000000000027071402665037500172210ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include G_BEGIN_DECLS /** * FuDeviceInstanceFlags: * @FU_DEVICE_INSTANCE_FLAG_NONE: No flags set * @FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS: Only use instance ID for quirk matching * * The flags to use when interacting with a device instance **/ typedef enum { FU_DEVICE_INSTANCE_FLAG_NONE = 0, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS = 1 << 0, /*< private >*/ FU_DEVICE_INSTANCE_FLAG_LAST } FuDeviceInstanceFlags; GPtrArray *fu_device_get_parent_guids (FuDevice *self); gboolean fu_device_has_parent_guid (FuDevice *self, const gchar *guid); void fu_device_set_parent (FuDevice *self, FuDevice *parent); guint fu_device_get_order (FuDevice *self); void fu_device_set_order (FuDevice *self, guint order); guint fu_device_get_priority (FuDevice *self); void fu_device_set_priority (FuDevice *self, guint priority); void fu_device_set_alternate (FuDevice *self, FuDevice *alternate); gboolean fu_device_ensure_id (FuDevice *self, GError **error); void fu_device_incorporate_from_component (FuDevice *device, XbNode *component); void fu_device_convert_instance_ids (FuDevice *self); void fu_device_add_instance_id_full (FuDevice *self, const gchar *instance_id, FuDeviceInstanceFlags flags); G_END_DECLS fwupd-1.2.14/src/fu-device.c000066400000000000000000001714351402665037500155510ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDevice" #include "config.h" #include #include #include #include "fu-common.h" #include "fu-common-version.h" #include "fu-device-private.h" #include "fu-mutex.h" #include "fwupd-common.h" #include "fwupd-device-private.h" /** * SECTION:fu-device * @short_description: a physical or logical device * * An object that represents a physical or logical device. * * See also: #FuDeviceLocker */ static void fu_device_finalize (GObject *object); typedef struct { gchar *alternate_id; gchar *equivalent_id; FuDevice *alternate; FuDevice *parent; /* noref */ FuQuirks *quirks; GHashTable *metadata; GRWLock metadata_mutex; GPtrArray *parent_guids; GRWLock parent_guids_mutex; GPtrArray *children; guint remove_delay; /* ms */ FwupdStatus status; guint progress; guint order; guint priority; guint poll_id; gboolean done_probe; gboolean done_setup; guint64 size_min; guint64 size_max; gint open_refcount; /* atomic */ } FuDevicePrivate; enum { PROP_0, PROP_STATUS, PROP_PROGRESS, PROP_PHYSICAL_ID, PROP_LOGICAL_ID, PROP_QUIRKS, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE (FuDevice, fu_device, FWUPD_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_device_get_instance_private (o)) static void fu_device_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuDevice *self = FU_DEVICE (object); FuDevicePrivate *priv = GET_PRIVATE (self); switch (prop_id) { case PROP_STATUS: g_value_set_uint (value, priv->status); break; case PROP_PROGRESS: g_value_set_uint (value, priv->progress); break; case PROP_PHYSICAL_ID: g_value_set_string (value, fu_device_get_physical_id (self)); break; case PROP_LOGICAL_ID: g_value_set_string (value, fu_device_get_logical_id (self)); break; case PROP_QUIRKS: g_value_set_object (value, priv->quirks); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_device_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuDevice *self = FU_DEVICE (object); switch (prop_id) { case PROP_STATUS: fu_device_set_status (self, g_value_get_uint (value)); break; case PROP_PROGRESS: fu_device_set_progress (self, g_value_get_uint (value)); break; case PROP_PHYSICAL_ID: fu_device_set_physical_id (self, g_value_get_string (value)); break; case PROP_LOGICAL_ID: fu_device_set_logical_id (self, g_value_get_string (value)); break; case PROP_QUIRKS: fu_device_set_quirks (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * fu_device_poll: * @self: A #FuDevice * @error: A #GError, or %NULL * * Polls a device, typically querying the hardware for status. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_poll (FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* subclassed */ if (klass->poll != NULL) { if (!klass->poll (self, error)) return FALSE; } return TRUE; } static gboolean fu_device_poll_cb (gpointer user_data) { FuDevice *self = FU_DEVICE (user_data); FuDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GError) error_local = NULL; if (!fu_device_poll (self, &error_local)) { g_warning ("disabling polling: %s", error_local->message); priv->poll_id = 0; return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } /** * fu_device_set_poll_interval: * @self: a #FuPlugin * @interval: duration in ms, or 0 to disable * * Polls the hardware every interval period. If the subclassed `->poll()` method * returns %FALSE then a warning is printed to the console and the poll is * disabled until the next call to fu_device_set_poll_interval(). * * Since: 1.1.2 **/ void fu_device_set_poll_interval (FuDevice *self, guint interval) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); if (priv->poll_id != 0) { g_source_remove (priv->poll_id); priv->poll_id = 0; } if (interval == 0) return; if (interval % 1000 == 0) { priv->poll_id = g_timeout_add_seconds (interval / 1000, fu_device_poll_cb, self); } else { priv->poll_id = g_timeout_add (interval, fu_device_poll_cb, self); } } /** * fu_device_get_order: * @self: a #FuPlugin * * Gets the device order, where higher numbers are installed after lower * numbers. * * Returns: the integer value * * Since: 1.0.8 **/ guint fu_device_get_order (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), 0); return priv->order; } /** * fu_device_set_order: * @self: a #FuDevice * @order: a integer value * * Sets the device order, where higher numbers are installed after lower * numbers. * * Since: 1.0.8 **/ void fu_device_set_order (FuDevice *self, guint order) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); priv->order = order; } /** * fu_device_get_priority: * @self: a #FuPlugin * * Gets the device priority, where higher numbers are better. * * Returns: the integer value * * Since: 1.1.1 **/ guint fu_device_get_priority (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), 0); return priv->priority; } /** * fu_device_set_priority: * @self: a #FuDevice * @priority: a integer value * * Sets the device priority, where higher numbers are better. * * Since: 1.1.1 **/ void fu_device_set_priority (FuDevice *self, guint priority) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); priv->priority = priority; } const gchar * fu_device_get_equivalent_id (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return priv->equivalent_id; } void fu_device_set_equivalent_id (FuDevice *self, const gchar *equivalent_id) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); g_free (priv->equivalent_id); priv->equivalent_id = g_strdup (equivalent_id); } /** * fu_device_get_alternate: * @self: A #FuDevice * * Gets any alternate device ID. An alternate device may be linked to the primary * device in some way. * * Returns: (transfer none): a #FuDevice or %NULL * * Since: 1.1.0 **/ const gchar * fu_device_get_alternate_id (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return priv->alternate_id; } /** * fu_device_set_alternate: * @self: A #FuDevice * @alternate: Another #FuDevice * * Sets any alternate device ID. An alternate device may be linked to the primary * device in some way. * * Since: 1.1.0 **/ void fu_device_set_alternate_id (FuDevice *self, const gchar *alternate_id) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); g_free (priv->alternate_id); priv->alternate_id = g_strdup (alternate_id); } /** * fu_device_get_alternate: * @self: A #FuDevice * * Gets any alternate device. An alternate device may be linked to the primary * device in some way. * * The alternate object will be matched from the ID set in fu_device_set_alternate_id() * and will be assigned by the daemon. This means if the ID is not found as an * added device, then this function will return %NULL. * * Returns: (transfer none): a #FuDevice or %NULL * * Since: 0.7.2 **/ FuDevice * fu_device_get_alternate (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return priv->alternate; } /** * fu_device_set_alternate: * @self: A #FuDevice * @alternate: Another #FuDevice * * Sets any alternate device. An alternate device may be linked to the primary * device in some way. * * This function is only usable by the daemon, not directly from plugins. * * Since: 0.7.2 **/ void fu_device_set_alternate (FuDevice *self, FuDevice *alternate) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); g_set_object (&priv->alternate, alternate); } /** * fu_device_get_parent: * @self: A #FuDevice * * Gets any parent device. An parent device is logically "above" the current * device and this may be reflected in client tools. * * This information also allows the plugin to optionally verify the parent * device, for instance checking the parent device firmware version. * * The parent object is not refcounted and if destroyed this function will then * return %NULL. * * Returns: (transfer none): a #FuDevice or %NULL * * Since: 1.0.8 **/ FuDevice * fu_device_get_parent (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return priv->parent; } void fu_device_set_parent (FuDevice *self, FuDevice *parent) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); g_object_add_weak_pointer (G_OBJECT (parent), (gpointer *) &priv->parent); priv->parent = parent; /* this is what goes over D-Bus */ fwupd_device_set_parent_id (FWUPD_DEVICE (self), parent != NULL ? fu_device_get_id (parent) : NULL); } /** * fu_device_get_children: * @self: A #FuDevice * * Gets any child devices. A child device is logically "below" the current * device and this may be reflected in client tools. * * Returns: (transfer none) (element-type FuDevice): child devices * * Since: 1.0.8 **/ GPtrArray * fu_device_get_children (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return priv->children; } /** * fu_device_add_child: * @self: A #FuDevice * @child: Another #FuDevice * * Sets any child device. An child device is logically linked to the primary * device in some way. * * Since: 1.0.8 **/ void fu_device_add_child (FuDevice *self, FuDevice *child) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (FU_IS_DEVICE (child)); /* add if the child does not already exist */ for (guint i = 0; i < priv->children->len; i++) { FuDevice *devtmp = g_ptr_array_index (priv->children, i); if (devtmp == child) return; } g_ptr_array_add (priv->children, g_object_ref (child)); /* copy from main device if unset */ if (fu_device_get_physical_id (child) == NULL && fu_device_get_physical_id (self) != NULL) fu_device_set_physical_id (child, fu_device_get_physical_id (self)); if (fu_device_get_vendor (child) == NULL) fu_device_set_vendor (child, fu_device_get_vendor (self)); if (fu_device_get_vendor_id (child) == NULL) fu_device_set_vendor_id (child, fu_device_get_vendor_id (self)); if (fu_device_get_icons(child)->len == 0) { GPtrArray *icons = fu_device_get_icons (self); for (guint i = 0; i < icons->len; i++) { const gchar *icon_name = g_ptr_array_index (icons, i); fu_device_add_icon (child, icon_name); } } /* ensure the parent is also set on the child */ fu_device_set_parent (child, self); /* order devices so they are updated in the correct sequence */ if (fu_device_has_flag (child, FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST)) { if (priv->order >= fu_device_get_order (child)) fu_device_set_order (child, priv->order + 1); } else { if (priv->order <= fu_device_get_order (child)) priv->order = fu_device_get_order (child) + 1; } } /** * fu_device_get_parent_guids: * @self: A #FuDevice * * Gets any parent device GUIDs. If a device is added to the daemon that matches * any GUIDs added from fu_device_add_parent_guid() then this device is marked the parent of @self. * * Returns: (transfer none) (element-type utf8): a list of GUIDs * * Since: 1.0.8 **/ GPtrArray * fu_device_get_parent_guids (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&priv->parent_guids_mutex); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); g_return_val_if_fail (locker != NULL, NULL); return priv->parent_guids; } /** * fu_device_has_parent_guid: * @self: A #FuDevice * @guid: a GUID * * Searches the list of parent GUIDs for a string match. * * Returns: %TRUE if the parent GUID exists * * Since: 1.0.8 **/ gboolean fu_device_has_parent_guid (FuDevice *self, const gchar *guid) { FuDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&priv->parent_guids_mutex); g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (locker != NULL, FALSE); for (guint i = 0; i < priv->parent_guids->len; i++) { const gchar *guid_tmp = g_ptr_array_index (priv->parent_guids, i); if (g_strcmp0 (guid_tmp, guid) == 0) return TRUE; } return FALSE; } /** * fu_device_add_parent_guid: * @self: A #FuDevice * @guid: a GUID * * Sets any parent device using a GUID. An parent device is logically linked to * the primary device in some way and can be added before or after @self. * * The GUIDs are searched in order, and so the order of adding GUIDs may be * important if more than one parent device might match. * * If the parent device is removed, any children logically linked to it will * also be removed. * * Since: 1.0.8 **/ void fu_device_add_parent_guid (FuDevice *self, const gchar *guid) { FuDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (guid != NULL); /* make valid */ if (!fwupd_guid_is_valid (guid)) { g_autofree gchar *tmp = fwupd_guid_hash_string (guid); if (fu_device_has_parent_guid (self, tmp)) return; g_debug ("using %s for %s", tmp, guid); g_ptr_array_add (priv->parent_guids, g_steal_pointer (&tmp)); return; } /* already valid */ if (fu_device_has_parent_guid (self, guid)) return; locker = g_rw_lock_writer_locker_new (&priv->parent_guids_mutex); g_return_if_fail (locker != NULL); g_ptr_array_add (priv->parent_guids, g_strdup (guid)); } static gboolean fu_device_add_child_by_type_guid (FuDevice *self, GType type, const gchar *guid, GError **error) { FuDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(FuDevice) child = NULL; child = g_object_new (type, "quirks", priv->quirks, "logical-id", guid, NULL); fu_device_add_guid (child, guid); if (fu_device_get_physical_id (self) != NULL) fu_device_set_physical_id (child, fu_device_get_physical_id (self)); if (!fu_device_ensure_id (self, error)) return FALSE; if (!fu_device_probe (child, error)) return FALSE; fu_device_convert_instance_ids (child); fu_device_add_child (self, child); return TRUE; } static gboolean fu_device_add_child_by_kv (FuDevice *self, const gchar *str, GError **error) { g_auto(GStrv) split = g_strsplit (str, "|", -1); /* type same as parent */ if (g_strv_length (split) == 1) { return fu_device_add_child_by_type_guid (self, G_OBJECT_TYPE (self), split[1], error); } /* type specified */ if (g_strv_length (split) == 2) { GType devtype = g_type_from_name (split[0]); if (devtype == 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no GType registered"); return FALSE; } return fu_device_add_child_by_type_guid (self, devtype, split[1], error); } /* more than one '|' */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "unable to add parse child section"); return FALSE; } static gboolean fu_device_set_quirk_kv (FuDevice *self, const gchar *key, const gchar *value, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); if (g_strcmp0 (key, FU_QUIRKS_PLUGIN) == 0) { fu_device_set_plugin (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_FLAGS) == 0) { fu_device_set_custom_flags (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_NAME) == 0) { fu_device_set_name (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_SUMMARY) == 0) { fu_device_set_summary (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_VENDOR) == 0) { fu_device_set_vendor (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_VENDOR_ID) == 0) { fu_device_set_vendor_id (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_VERSION) == 0) { fu_device_set_version (self, value, fu_device_get_version_format (self)); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_ICON) == 0) { fu_device_add_icon (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_GUID) == 0) { fu_device_add_guid (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_COUNTERPART_GUID) == 0) { fu_device_add_counterpart_guid (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_PARENT_GUID) == 0) { fu_device_add_parent_guid (self, value); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_FIRMWARE_SIZE_MIN) == 0) { fu_device_set_firmware_size_min (self, fu_common_strtoull (value)); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_FIRMWARE_SIZE_MAX) == 0) { fu_device_set_firmware_size_max (self, fu_common_strtoull (value)); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_FIRMWARE_SIZE) == 0) { fu_device_set_firmware_size (self, fu_common_strtoull (value)); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_INSTALL_DURATION) == 0) { fu_device_set_install_duration (self, fu_common_strtoull (value)); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_VERSION_FORMAT) == 0) { fu_device_set_version_format (self, fwupd_version_format_from_string (value)); return TRUE; } if (g_strcmp0 (key, FU_QUIRKS_CHILDREN) == 0) { g_auto(GStrv) sections = g_strsplit (value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) { if (!fu_device_add_child_by_kv (self, sections[i], error)) return FALSE; } return TRUE; } /* optional device-specific method */ if (klass->set_quirk_kv != NULL) return klass->set_quirk_kv (self, key, value, error); /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_device_add_guid_quirks (FuDevice *self, const gchar *guid) { FuDevicePrivate *priv = GET_PRIVATE (self); const gchar *key; const gchar *value; GHashTableIter iter; /* not set */ if (priv->quirks == NULL) return; if (!fu_quirks_get_kvs_for_guid (priv->quirks, guid, &iter)) return; while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) { g_autoptr(GError) error = NULL; if (!fu_device_set_quirk_kv (self, key, value, &error)) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { g_warning ("failed to set quirk key %s=%s: %s", key, value, error->message); } } } } /** * fu_device_set_firmware_size: * @self: A #FuDevice * @size: Size in bytes * * Sets the exact allowed size of the firmware blob. * * Since: 1.2.6 **/ void fu_device_set_firmware_size (FuDevice *self, guint64 size) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); priv->size_min = size; priv->size_max = size; } /** * fu_device_set_firmware_size_min: * @self: A #FuDevice * @size_min: Size in bytes * * Sets the minimum allowed size of the firmware blob. * * Since: 1.1.2 **/ void fu_device_set_firmware_size_min (FuDevice *self, guint64 size_min) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); priv->size_min = size_min; } /** * fu_device_set_firmware_size_max: * @self: A #FuDevice * @size_max: Size in bytes * * Sets the maximum allowed size of the firmware blob. * * Since: 1.1.2 **/ void fu_device_set_firmware_size_max (FuDevice *self, guint64 size_max) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); priv->size_max = size_max; } /** * fu_device_get_firmware_size_min: * @self: A #FuDevice * * Gets the minimum size of the firmware blob. * * Returns: Size in bytes, or 0 if unset * * Since: 1.2.6 **/ guint64 fu_device_get_firmware_size_min (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), 0); return priv->size_min; } /** * fu_device_get_firmware_size_max: * @self: A #FuDevice * * Gets the maximum size of the firmware blob. * * Returns: Size in bytes, or 0 if unset * * Since: 1.2.6 **/ guint64 fu_device_get_firmware_size_max (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), 0); return priv->size_max; } static void fu_device_add_guid_safe (FuDevice *self, const gchar *guid) { /* add the device GUID before adding additional GUIDs from quirks * to ensure the bootloader GUID is listed after the runtime GUID */ fwupd_device_add_guid (FWUPD_DEVICE (self), guid); fu_device_add_guid_quirks (self, guid); } /** * fu_device_has_guid: * @self: A #FuDevice * @guid: A GUID, e.g. `WacomAES` * * Finds out if the device has a specific GUID. * * Returns: %TRUE if the GUID is found * * Since: 1.2.2 **/ gboolean fu_device_has_guid (FuDevice *self, const gchar *guid) { g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (guid != NULL, FALSE); /* make valid */ if (!fwupd_guid_is_valid (guid)) { g_autofree gchar *tmp = fwupd_guid_hash_string (guid); return fwupd_device_has_guid (FWUPD_DEVICE (self), tmp); } /* already valid */ return fwupd_device_has_guid (FWUPD_DEVICE (self), guid); } /* private */ void fu_device_add_instance_id_full (FuDevice *self, const gchar *instance_id, FuDeviceInstanceFlags flags) { g_autofree gchar *guid = NULL; if (fwupd_guid_is_valid (instance_id)) { g_warning ("use fu_device_add_guid(\"%s\") instead!", instance_id); fu_device_add_guid_safe (self, instance_id); return; } /* it seems odd adding the instance ID and the GUID quirks and not just * calling fu_device_add_guid_safe() -- but we want the quirks to match * so the plugin is set, but not the LVFS metadata to match firmware * until we're sure the device isn't using _NO_AUTO_INSTANCE_IDS */ guid = fwupd_guid_hash_string (instance_id); fu_device_add_guid_quirks (self, guid); if ((flags & FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS) == 0) fwupd_device_add_instance_id (FWUPD_DEVICE (self), instance_id); } /** * fu_device_add_instance_id: * @self: A #FuDevice * @instance_id: the InstanceID, e.g. `PCI\VEN_10EC&DEV_525A` * * Adds an instance ID to the device. If the @instance_id argument is already a * valid GUID then fu_device_add_guid() should be used instead. * * Since: 1.2.5 **/ void fu_device_add_instance_id (FuDevice *self, const gchar *instance_id) { g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (instance_id != NULL); fu_device_add_instance_id_full (self, instance_id, FU_DEVICE_INSTANCE_FLAG_NONE); } /** * fu_device_add_guid: * @self: A #FuDevice * @guid: A GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad` * * Adds a GUID to the device. If the @guid argument is not a valid GUID then it * is converted to a GUID using fwupd_guid_hash_string(). * * Since: 0.7.2 **/ void fu_device_add_guid (FuDevice *self, const gchar *guid) { g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (guid != NULL); if (!fwupd_guid_is_valid (guid)) { fu_device_add_instance_id (self, guid); return; } fu_device_add_guid_safe (self, guid); } /** * fu_device_add_counterpart_guid: * @self: A #FuDevice * @guid: A GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad` * * Adds a GUID to the device. If the @guid argument is not a valid GUID then it * is converted to a GUID using fwupd_guid_hash_string(). * * A counterpart GUID is typically the GUID of the same device in bootloader * or runtime mode, if they have a different device PCI or USB ID. Adding this * type of GUID does not cause a "cascade" by matching using the quirk database. * * Since: 1.1.2 **/ void fu_device_add_counterpart_guid (FuDevice *self, const gchar *guid) { g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (guid != NULL); /* make valid */ if (!fwupd_guid_is_valid (guid)) { g_autofree gchar *tmp = fwupd_guid_hash_string (guid); fwupd_device_add_guid (FWUPD_DEVICE (self), tmp); return; } /* already valid */ fwupd_device_add_guid (FWUPD_DEVICE (self), guid); } /** * fu_device_get_guids_as_str: * @self: A #FuDevice * * Gets the device GUIDs as a joined string, which may be useful for error * messages. * * Returns: a string, which may be empty length but not %NULL * * Since: 1.0.8 **/ gchar * fu_device_get_guids_as_str (FuDevice *self) { GPtrArray *guids; g_autofree gchar **tmp = NULL; g_return_val_if_fail (FU_IS_DEVICE (self), NULL); guids = fu_device_get_guids (self); tmp = g_new0 (gchar *, guids->len + 1); for (guint i = 0; i < guids->len; i++) tmp[i] = g_ptr_array_index (guids, i); return g_strjoinv (",", tmp); } /** * fu_device_get_metadata: * @self: A #FuDevice * @key: the key * * Gets an item of metadata from the device. * * Returns: a string value, or %NULL for unfound. * * Since: 0.1.0 **/ const gchar * fu_device_get_metadata (FuDevice *self, const gchar *key) { FuDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&priv->metadata_mutex); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); g_return_val_if_fail (key != NULL, NULL); g_return_val_if_fail (locker != NULL, NULL); return g_hash_table_lookup (priv->metadata, key); } /** * fu_device_get_metadata_boolean: * @self: A #FuDevice * @key: the key * * Gets an item of metadata from the device. * * Returns: a boolean value, or %FALSE for unfound or failure to parse. * * Since: 0.9.7 **/ gboolean fu_device_get_metadata_boolean (FuDevice *self, const gchar *key) { FuDevicePrivate *priv = GET_PRIVATE (self); const gchar *tmp; g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&priv->metadata_mutex); g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (key != NULL, FALSE); g_return_val_if_fail (locker != NULL, FALSE); tmp = g_hash_table_lookup (priv->metadata, key); if (tmp == NULL) return FALSE; return g_strcmp0 (tmp, "true") == 0; } /** * fu_device_get_metadata_integer: * @self: A #FuDevice * @key: the key * * Gets an item of metadata from the device. * * Returns: a string value, or %G_MAXUINT for unfound or failure to parse. * * Since: 0.9.7 **/ guint fu_device_get_metadata_integer (FuDevice *self, const gchar *key) { FuDevicePrivate *priv = GET_PRIVATE (self); const gchar *tmp; gchar *endptr = NULL; guint64 val; g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&priv->metadata_mutex); g_return_val_if_fail (FU_IS_DEVICE (self), G_MAXUINT); g_return_val_if_fail (key != NULL, G_MAXUINT); g_return_val_if_fail (locker != NULL, G_MAXUINT); tmp = g_hash_table_lookup (priv->metadata, key); if (tmp == NULL) return G_MAXUINT; val = g_ascii_strtoull (tmp, &endptr, 10); if (endptr != NULL && endptr[0] != '\0') return G_MAXUINT; if (val > G_MAXUINT) return G_MAXUINT; return (guint) val; } /** * fu_device_set_metadata: * @self: A #FuDevice * @key: the key * @value: the string value * * Sets an item of metadata on the device. * * Since: 0.1.0 **/ void fu_device_set_metadata (FuDevice *self, const gchar *key, const gchar *value) { FuDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_writer_locker_new (&priv->metadata_mutex); g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (key != NULL); g_return_if_fail (value != NULL); g_return_if_fail (locker != NULL); g_hash_table_insert (priv->metadata, g_strdup (key), g_strdup (value)); } /** * fu_device_set_metadata_boolean: * @self: A #FuDevice * @key: the key * @value: the boolean value * * Sets an item of metadata on the device. When @value is set to %TRUE * the actual stored value is "true". * * Since: 0.9.7 **/ void fu_device_set_metadata_boolean (FuDevice *self, const gchar *key, gboolean value) { g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (key != NULL); fu_device_set_metadata (self, key, value ? "true" : "false"); } /** * fu_device_set_metadata_integer: * @self: A #FuDevice * @key: the key * @value: the unsigned integer value * * Sets an item of metadata on the device. The integer is stored as a * base-10 string internally. * * Since: 0.9.7 **/ void fu_device_set_metadata_integer (FuDevice *self, const gchar *key, guint value) { g_autofree gchar *tmp = g_strdup_printf ("%u", value); g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (key != NULL); fu_device_set_metadata (self, key, tmp); } /** * fu_device_set_name: * @self: A #FuDevice * @value: a device name * * Sets the name on the device. Any invalid parts will be converted or removed. * * Since: 0.7.1 **/ void fu_device_set_name (FuDevice *self, const gchar *value) { g_autoptr(GString) new = g_string_new (value); g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (value != NULL); /* overwriting? */ if (g_strcmp0 (value, fu_device_get_name (self)) == 0) { const gchar *id = fu_device_get_id (self); g_debug ("%s device overwriting same name value: %s", id != NULL ? id : "unknown", value); return; } /* changing */ if (fu_device_get_name (self) != NULL) { const gchar *id = fu_device_get_id (self); g_debug ("%s device overwriting name value: %s->%s", id != NULL ? id : "unknown", fu_device_get_name (self), value); } g_strdelimit (new->str, "_", ' '); fu_common_string_replace (new, "(TM)", "™"); fwupd_device_set_name (FWUPD_DEVICE (self), new->str); } static gboolean fu_device_id_is_valid (const gchar *device_id) { if (device_id == NULL) return FALSE; if (strlen (device_id) != 40) return FALSE; for (guint i = 0; device_id[i] != '\0'; i++) { gchar tmp = device_id[i]; /* isalnum isn't case specific */ if ((tmp < 'a' || tmp > 'f') && (tmp < '0' || tmp > '9')) return FALSE; } return TRUE; } /** * fu_device_set_id: * @self: A #FuDevice * @id: a string, e.g. `tbt-port1` * * Sets the ID on the device. The ID should represent the *connection* of the * device, so that any similar device plugged into a different slot will * have a different @id string. * * The @id will be converted to a SHA1 hash if required before the device is * added to the daemon, and plugins should not assume that the ID that is set * here is the same as what is returned by fu_device_get_id(). * * Since: 0.7.1 **/ void fu_device_set_id (FuDevice *self, const gchar *id) { FuDevicePrivate *priv = GET_PRIVATE (self); g_autofree gchar *id_hash = NULL; g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (id != NULL); /* allow sane device-id to be set directly */ if (fu_device_id_is_valid (id)) { id_hash = g_strdup (id); } else { id_hash = g_compute_checksum_for_string (G_CHECKSUM_SHA1, id, -1); g_debug ("using %s for %s", id_hash, id); } fwupd_device_set_id (FWUPD_DEVICE (self), id_hash); /* ensure the parent ID is set */ for (guint i = 0; i < priv->children->len; i++) { FuDevice *devtmp = g_ptr_array_index (priv->children, i); fwupd_device_set_parent_id (FWUPD_DEVICE (devtmp), id_hash); } } /** * fu_device_set_version: * @self: A #FuDevice * @version: (allow-none): a string, e.g. `1.2.3` * @fmt: a #FwupdVersionFormat, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Sets the device version, sanitizing the string if required. * * Since: 1.2.9 **/ void fu_device_set_version (FuDevice *self, const gchar *version, FwupdVersionFormat fmt) { g_autofree gchar *version_safe = NULL; g_autoptr(GError) error = NULL; g_return_if_fail (FU_IS_DEVICE (self)); /* sanitize if required */ if (fu_device_has_flag (self, FWUPD_DEVICE_FLAG_ENSURE_SEMVER)) { version_safe = fu_common_version_ensure_semver (version); if (g_strcmp0 (version, version_safe) != 0) g_debug ("converted '%s' to '%s'", version, version_safe); } else { version_safe = g_strdup (version); } /* print a console warning for an invalid version, if semver */ if (!fu_common_version_verify_format (version_safe, fmt, &error)) g_warning ("%s", error->message); fu_device_set_version_format (self, fmt); fwupd_device_set_version (FWUPD_DEVICE (self), version_safe); } /** * fu_device_ensure_id: * @self: A #FuDevice * @error: A #GError * * If not already set, generates a device ID with the optional physical and * logical IDs. * * Returns: %TRUE on success * * Since: 1.1.2 **/ gboolean fu_device_ensure_id (FuDevice *self, GError **error) { g_autofree gchar *device_id = NULL; g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* already set */ if (fu_device_get_id (self) != NULL) return TRUE; /* nothing we can do! */ if (fu_device_get_physical_id (self) == NULL) { g_autofree gchar *tmp = fu_device_to_string (self); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot ensure ID: %s", tmp); return FALSE; } /* logical may be NULL */ device_id = g_strjoin (":", fu_device_get_physical_id (self), fu_device_get_logical_id (self), NULL); fu_device_set_id (self, device_id); return TRUE; } /** * fu_device_get_logical_id: * @self: A #FuDevice * * Gets the logical ID set for the device, which disambiguates devices with the * same physical ID. * * Returns: a string value, or %NULL if never set. * * Since: 1.1.2 **/ const gchar * fu_device_get_logical_id (FuDevice *self) { g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return fu_device_get_metadata (self, "logical-id"); } /** * fu_device_set_logical_id: * @self: A #FuDevice * @logical_id: a string, e.g. `dev2` * * Sets the logical ID on the device. This is designed to disambiguate devices * with the same physical ID. * * Since: 1.1.2 **/ void fu_device_set_logical_id (FuDevice *self, const gchar *logical_id) { g_return_if_fail (FU_IS_DEVICE (self)); fu_device_set_metadata (self, "logical-id", logical_id); } /** * fu_device_set_physical_id: * @self: A #FuDevice * @physical_id: a string that identifies the physical device connection * * Sets the physical ID on the device which represents the electrical connection * of the device to the system. Multiple #FuDevices can share a physical ID. * * The physical ID is used to remove logical devices when a physical device has * been removed from the system. * * A sysfs or devpath is not a physical ID, but could be something like * `PCI_SLOT_NAME=0000:3e:00.0`. * * Since: 1.1.2 **/ void fu_device_set_physical_id (FuDevice *self, const gchar *physical_id) { g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (physical_id != NULL); fu_device_set_metadata (self, "physical-id", physical_id); } /** * fu_device_get_physical_id: * @self: A #FuDevice * * Gets the physical ID set for the device, which represents the electrical * connection used to compare devices. * * Multiple #FuDevices can share a single physical ID. * * Returns: a string value, or %NULL if never set. * * Since: 1.1.2 **/ const gchar * fu_device_get_physical_id (FuDevice *self) { g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return fu_device_get_metadata (self, "physical-id"); } static void fu_device_set_custom_flag (FuDevice *self, const gchar *hint) { FwupdDeviceFlags flag; /* is this a known device flag */ flag = fwupd_device_flag_from_string (hint); if (flag == FWUPD_DEVICE_FLAG_UNKNOWN) return; /* being both a bootloader and requiring a bootloader is invalid */ if (flag == FWUPD_DEVICE_FLAG_NONE || flag == FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER) { fu_device_remove_flag (self, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } if (flag == FWUPD_DEVICE_FLAG_NONE || flag == FWUPD_DEVICE_FLAG_IS_BOOTLOADER) { fu_device_remove_flag (self, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); } /* none is not used as an "exported" flag */ if (flag != FWUPD_DEVICE_FLAG_NONE) fu_device_add_flag (self, flag); } /** * fu_device_set_custom_flags: * @self: A #FuDevice * @custom_flags: a string * * Sets the custom flags from the quirk system that can be used to * affect device matching. The actual string format is defined by the plugin. * * Since: 1.1.0 **/ void fu_device_set_custom_flags (FuDevice *self, const gchar *custom_flags) { g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (custom_flags != NULL); /* display what was set when converting to a string */ fu_device_set_metadata (self, "CustomFlags", custom_flags); /* look for any standard FwupdDeviceFlags */ if (custom_flags != NULL) { g_auto(GStrv) hints = g_strsplit (custom_flags, ",", -1); for (guint i = 0; hints[i] != NULL; i++) fu_device_set_custom_flag (self, hints[i]); } } /** * fu_device_get_custom_flags: * @self: A #FuDevice * * Gets the custom flags for the device from the quirk system. * * Returns: a string value, or %NULL if never set. * * Since: 1.1.0 **/ const gchar * fu_device_get_custom_flags (FuDevice *self) { g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return fu_device_get_metadata (self, "CustomFlags"); } /** * fu_device_has_custom_flag: * @self: A #FuDevice * @hint: A string, e.g. "bootloader" * * Checks if the custom flag exists for the device from the quirk system. * * It may be more efficient to call fu_device_get_custom_flags() and split the * string locally if checking for lots of different flags. * * Returns: %TRUE if the hint exists * * Since: 1.1.0 **/ gboolean fu_device_has_custom_flag (FuDevice *self, const gchar *hint) { const gchar *hint_str; g_auto(GStrv) hints = NULL; g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (hint != NULL, FALSE); /* no hint is perfectly valid */ hint_str = fu_device_get_custom_flags (self); if (hint_str == NULL) return FALSE; hints = g_strsplit (hint_str, ",", -1); return g_strv_contains ((const gchar * const *) hints, hint); } static void fwupd_pad_kv_str (GString *str, const gchar *key, const gchar *value) { /* ignore */ if (key == NULL || value == NULL) return; g_string_append_printf (str, " %s: ", key); for (gsize i = strlen (key); i < 20; i++) g_string_append (str, " "); g_string_append_printf (str, "%s\n", value); } /** * fu_device_get_remove_delay: * @self: A #FuDevice * * Returns the maximum delay expected when replugging the device going into * bootloader mode. * * Returns: time in milliseconds * * Since: 1.0.2 **/ guint fu_device_get_remove_delay (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), 0); return priv->remove_delay; } /** * fu_device_set_remove_delay: * @self: A #FuDevice * @remove_delay: the remove_delay value * * Sets the amount of time a device is allowed to return in bootloader mode. * * NOTE: this should be less than 3000ms for devices that just have to reset * and automatically re-enumerate, but significantly longer if it involves a * user removing a cable, pressing several buttons and removing a cable. * A suggested value for this would be 10,000ms. * * Since: 1.0.2 **/ void fu_device_set_remove_delay (FuDevice *self, guint remove_delay) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); priv->remove_delay = remove_delay; } /** * fu_device_get_status: * @self: A #FuDevice * * Returns what the device is currently doing. * * Returns: the status value, e.g. %FWUPD_STATUS_DEVICE_WRITE * * Since: 1.0.3 **/ FwupdStatus fu_device_get_status (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), 0); return priv->status; } /** * fu_device_set_status: * @self: A #FuDevice * @status: the status value, e.g. %FWUPD_STATUS_DEVICE_WRITE * * Sets what the device is currently doing. * * Since: 1.0.3 **/ void fu_device_set_status (FuDevice *self, FwupdStatus status) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); if (priv->status == status) return; priv->status = status; g_object_notify (G_OBJECT (self), "status"); } /** * fu_device_get_progress: * @self: A #FuDevice * * Returns the progress completion. * * Returns: value in percent * * Since: 1.0.3 **/ guint fu_device_get_progress (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), 0); return priv->progress; } /** * fu_device_set_progress: * @self: A #FuDevice * @progress: the progress percentage value * * Sets the progress completion. * * Since: 1.0.3 **/ void fu_device_set_progress (FuDevice *self, guint progress) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); if (priv->progress == progress) return; priv->progress = progress; g_object_notify (G_OBJECT (self), "progress"); } /** * fu_device_set_progress_full: * @self: A #FuDevice * @progress_done: the bytes already done * @progress_total: the total number of bytes * * Sets the progress completion using the raw progress values. * * Since: 1.0.3 **/ void fu_device_set_progress_full (FuDevice *self, gsize progress_done, gsize progress_total) { gdouble percentage = 0.f; g_return_if_fail (FU_IS_DEVICE (self)); if (progress_total > 0) percentage = (100.f * (gdouble) progress_done) / (gdouble) progress_total; fu_device_set_progress (self, (guint) percentage); } /** * fu_device_to_string: * @self: A #FuDevice * * This allows us to easily print the FwupdDevice, the FwupdRelease and the * daemon-specific metadata. * * Returns: a string value, or %NULL for invalid. * * Since: 0.9.8 **/ gchar * fu_device_to_string (FuDevice *self) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); FuDevicePrivate *priv = GET_PRIVATE (self); GString *str = g_string_new (""); g_autofree gchar *tmp = NULL; g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&priv->metadata_mutex); g_autoptr(GList) keys = NULL; g_return_val_if_fail (FU_IS_DEVICE (self), NULL); g_return_val_if_fail (locker != NULL, NULL); tmp = fwupd_device_to_string (FWUPD_DEVICE (self)); if (tmp != NULL && tmp[0] != '\0') g_string_append (str, tmp); if (priv->alternate_id != NULL) fwupd_pad_kv_str (str, "AlternateId", priv->alternate_id); if (priv->equivalent_id != NULL) fwupd_pad_kv_str (str, "EquivalentId", priv->equivalent_id); if (priv->size_min > 0) { g_autofree gchar *sz = g_strdup_printf ("%" G_GUINT64_FORMAT, priv->size_min); fwupd_pad_kv_str (str, "FirmwareSizeMin", sz); } if (priv->size_max > 0) { g_autofree gchar *sz = g_strdup_printf ("%" G_GUINT64_FORMAT, priv->size_max); fwupd_pad_kv_str (str, "FirmwareSizeMax", sz); } keys = g_hash_table_get_keys (priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup (priv->metadata, key); fwupd_pad_kv_str (str, key, value); } /* subclassed */ if (klass->to_string != NULL) klass->to_string (self, str); return g_string_free (str, FALSE); } /** * fu_device_set_quirks: * @self: A #FuDevice * @quirks: A #FuQuirks, or %NULL * * Sets the optional quirk information which may be useful to this device. * This is typically set after the #FuDevice has been created, but before * the device has been opened or probed. * * Since: 1.0.3 **/ void fu_device_set_quirks (FuDevice *self, FuQuirks *quirks) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); if (g_set_object (&priv->quirks, quirks)) g_object_notify (G_OBJECT (self), "quirks"); } /** * fu_device_get_quirks: * @self: A #FuDevice * * Gets the quirk information which may be useful to this device. * * Returns: (transfer none): the #FuQuirks object, or %NULL * * Since: 1.0.3 **/ FuQuirks * fu_device_get_quirks (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return priv->quirks; } /** * fu_device_get_release_default: * @self: A #FuDevice * * Gets the default release for the device, creating one if not found. * * Returns: (transfer none): the #FwupdRelease object * * Since: 1.0.5 **/ FwupdRelease * fu_device_get_release_default (FuDevice *self) { g_autoptr(FwupdRelease) rel = NULL; g_return_val_if_fail (FU_IS_DEVICE (self), NULL); if (fwupd_device_get_release_default (FWUPD_DEVICE (self)) != NULL) return fwupd_device_get_release_default (FWUPD_DEVICE (self)); rel = fwupd_release_new (); fwupd_device_add_release (FWUPD_DEVICE (self), rel); return rel; } /** * fu_device_write_firmware: * @self: A #FuDevice * @fw: A #GBytes * @flags: #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: A #GError * * Writes firmware to the device by calling a plugin-specific vfunc. * * Returns: %TRUE on success * * Since: 1.0.8 **/ gboolean fu_device_write_firmware (FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); g_autoptr(GBytes) fw_new = NULL; g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* no plugin-specific method */ if (klass->write_firmware == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return FALSE; } /* prepare (e.g. decompress) firmware */ fw_new = fu_device_prepare_firmware (self, fw, flags, error); if (fw_new == NULL) return FALSE; /* call vfunc */ return klass->write_firmware (self, fw_new, flags, error); } /** * fu_device_prepare_firmware: * @self: A #FuDevice * @fw: A #GBytes * @flags: #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: A #GError * * Prepares the firmware by calling an optional device-specific vfunc for the * device, which can do things like decompressing or parsing of the firmware * data. * * For all firmware, this checks the size of the firmware if limits have been * set using fu_device_set_firmware_size_min(), fu_device_set_firmware_size_max() * or using a quirk entry. * * Returns: (transfer full): A new #GBytes, or %NULL for error * * Since: 1.1.2 **/ GBytes * fu_device_prepare_firmware (FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); FuDevicePrivate *priv = GET_PRIVATE (self); guint64 fw_sz; g_autoptr(GBytes) fw_new = NULL; g_return_val_if_fail (FU_IS_DEVICE (self), NULL); g_return_val_if_fail (fw != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* optionally subclassed */ if (klass->prepare_firmware != NULL) { fw_new = klass->prepare_firmware (self, fw, flags, error); if (fw_new == NULL) return NULL; } else { fw_new = g_bytes_ref (fw); } /* check size */ fw_sz = (guint64) g_bytes_get_size (fw_new); if (priv->size_max > 0 && fw_sz > priv->size_max) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware is %04x bytes larger than the allowed " "maximum size of %04x bytes", (guint) (fw_sz - priv->size_max), (guint) priv->size_max); return NULL; } if (priv->size_min > 0 && fw_sz < priv->size_min) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware is %04x bytes smaller than the allowed " "minimum size of %04x bytes", (guint) (priv->size_min - fw_sz), (guint) priv->size_max); return NULL; } return g_steal_pointer (&fw_new); } /** * fu_device_read_firmware: * @self: A #FuDevice * @error: A #GError * * Reads firmware from the device by calling a plugin-specific vfunc. * * Returns: (transfer full): A #GBytes, or %NULL for error * * Since: 1.0.8 **/ GBytes * fu_device_read_firmware (FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* no plugin-specific method */ if (klass->read_firmware == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return NULL; } /* call vfunc */ return klass->read_firmware (self, error); } /** * fu_device_detach: * @self: A #FuDevice * @error: A #GError * * Detaches a device from the application into bootloader mode. * * Returns: %TRUE on success * * Since: 1.0.8 **/ gboolean fu_device_detach (FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* no plugin-specific method */ if (klass->detach == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return FALSE; } /* call vfunc */ return klass->detach (self, error); } /** * fu_device_attach: * @self: A #FuDevice * @error: A #GError * * Attaches a device from the bootloader into application mode. * * Returns: %TRUE on success * * Since: 1.0.8 **/ gboolean fu_device_attach (FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* no plugin-specific method */ if (klass->attach == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return FALSE; } /* call vfunc */ return klass->attach (self, error); } /** * fu_device_open: * @self: A #FuDevice * @error: A #GError, or %NULL * * Opens a device, optionally running a object-specific vfunc. * * Plugins can call fu_device_open() multiple times without calling * fu_device_close(), but only the first call will actually invoke the vfunc. * * It is expected that plugins issue the same number of fu_device_open() and * fu_device_close() methods when using a specific @self. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_open (FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* already open */ g_atomic_int_inc (&priv->open_refcount); if (priv->open_refcount > 1) return TRUE; /* probe */ if (!fu_device_probe (self, error)) return FALSE; /* ensure the device ID is already setup */ if (!fu_device_ensure_id (self, error)) return FALSE; /* subclassed */ if (klass->open != NULL) { if (!klass->open (self, error)) return FALSE; } /* setup */ if (!fu_device_setup (self, error)) return FALSE; /* success */ return TRUE; } /** * fu_device_close: * @self: A #FuDevice * @error: A #GError, or %NULL * * Closes a device, optionally running a object-specific vfunc. * * Plugins can call fu_device_close() multiple times without calling * fu_device_open(), but only the last call will actually invoke the vfunc. * * It is expected that plugins issue the same number of fu_device_open() and * fu_device_close() methods when using a specific @self. * * An error is returned if this method is called without having used the * fu_device_open() method beforehand. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_close (FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* not yet open */ if (priv->open_refcount == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot close device, refcount already zero"); return FALSE; } if (!g_atomic_int_dec_and_test (&priv->open_refcount)) return TRUE; /* subclassed */ if (klass->close != NULL) { if (!klass->close (self, error)) return FALSE; } /* success */ return TRUE; } /** * fu_device_probe: * @self: A #FuDevice * @error: A #GError, or %NULL * * Probes a device, setting parameters on the object that does not need * the device open or the interface claimed. * If the device is not compatible then an error should be returned. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_probe (FuDevice *self, GError **error) { FuDevicePrivate *priv = GET_PRIVATE (self); FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* already done */ if (priv->done_probe) return TRUE; /* subclassed */ if (klass->probe != NULL) { if (!klass->probe (self, error)) return FALSE; } priv->done_probe = TRUE; return TRUE; } /** * fu_device_convert_instance_ids: * @self: A #FuDevice * * Converts all the Device Instance IDs added using fu_device_add_instance_id() * into actual GUIDs, **unless** %FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS has * been set. * * Plugins will only need to need to call this manually when adding child * devices, as fu_device_setup() automatically calls this after the * fu_device_probe() and fu_device_setup() virtual functions have been run. * * Since: 1.2.5 **/ void fu_device_convert_instance_ids (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); GPtrArray *instance_ids = fwupd_device_get_instance_ids (FWUPD_DEVICE (self)); /* OEM specific hardware */ if (fu_device_has_flag (self, FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS)) return; for (guint i = 0; i < instance_ids->len; i++) { const gchar *instance_id = g_ptr_array_index (instance_ids, i); g_autofree gchar *guid = fwupd_guid_hash_string (instance_id); fwupd_device_add_guid (FWUPD_DEVICE (self), guid); } /* convert all children too */ for (guint i = 0; i < priv->children->len; i++) { FuDevice *devtmp = g_ptr_array_index (priv->children, i); fu_device_convert_instance_ids (devtmp); } } /** * fu_device_setup: * @self: A #FuDevice * @error: A #GError, or %NULL * * Sets up a device, setting parameters on the object that requires * the device to be open and have the interface claimed. * If the device is not compatible then an error should be returned. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_setup (FuDevice *self, GError **error) { FuDevicePrivate *priv = GET_PRIVATE (self); FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* already done */ if (priv->done_setup) return TRUE; /* subclassed */ if (klass->setup != NULL) { if (!klass->setup (self, error)) return FALSE; } /* convert the instance IDs to GUIDs */ fu_device_convert_instance_ids (self); priv->done_setup = TRUE; return TRUE; } /** * fu_device_activate: * @self: A #FuDevice * @error: A #GError, or %NULL * * Activates up a device, which normally means the device switches to a new * firmware version. This should only be called when data loss cannot occur. * * Returns: %TRUE for success * * Since: 1.2.6 **/ gboolean fu_device_activate (FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); g_return_val_if_fail (FU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* subclassed */ if (klass->activate != NULL) { if (!klass->activate (self, error)) return FALSE; } return TRUE; } /** * fu_device_probe_invalidate: * @self: A #FuDevice * * Normally when calling fu_device_probe() multiple times it is only done once. * Calling this method causes the next requests to fu_device_probe() and * fu_device_setup() actually probe the hardware. * * This should be done in case the backing device has changed, for instance if * a USB device has been replugged. * * Returns: %TRUE for success * * Since: 1.1.2 **/ void fu_device_probe_invalidate (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); priv->done_probe = FALSE; priv->done_setup = FALSE; } /** * fu_device_incorporate: * @self: A #FuDevice * @donor: Another #FuDevice * * Copy all properties from the donor object if they have not already been set. * * Since: 1.1.0 **/ void fu_device_incorporate (FuDevice *self, FuDevice *donor) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); FuDevicePrivate *priv = GET_PRIVATE (self); FuDevicePrivate *priv_donor = GET_PRIVATE (donor); GPtrArray *parent_guids = fu_device_get_parent_guids (donor); g_autoptr(GList) metadata_keys = NULL; g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (FU_IS_DEVICE (donor)); /* copy from donor FuDevice if has not already been set */ if (priv->alternate_id == NULL) fu_device_set_alternate_id (self, fu_device_get_alternate_id (donor)); if (priv->equivalent_id == NULL) fu_device_set_equivalent_id (self, fu_device_get_equivalent_id (donor)); if (priv->quirks == NULL) fu_device_set_quirks (self, fu_device_get_quirks (donor)); g_rw_lock_reader_lock (&priv_donor->parent_guids_mutex); for (guint i = 0; i < parent_guids->len; i++) fu_device_add_parent_guid (self, g_ptr_array_index (parent_guids, i)); g_rw_lock_reader_unlock (&priv_donor->parent_guids_mutex); g_rw_lock_reader_lock (&priv_donor->metadata_mutex); metadata_keys = g_hash_table_get_keys (priv_donor->metadata); for (GList *l = metadata_keys; l != NULL; l = l->next) { const gchar *key = l->data; if (g_hash_table_lookup (priv->metadata, key) == NULL) { const gchar *value = g_hash_table_lookup (priv_donor->metadata, key); fu_device_set_metadata (self, key, value); } } g_rw_lock_reader_unlock (&priv_donor->metadata_mutex); /* now the base class, where all the interesting bits are */ fwupd_device_incorporate (FWUPD_DEVICE (self), FWUPD_DEVICE (donor)); /* optional subclass */ if (klass->incorporate != NULL) klass->incorporate (self, donor); } /** * fu_device_incorporate_from_component: * @device: A #FuDevice * @component: A #XbNode * * Copy all properties from the donor AppStream component. * * Since: 1.2.4 **/ void fu_device_incorporate_from_component (FuDevice *self, XbNode *component) { const gchar *tmp; g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (XB_IS_NODE (component)); tmp = xb_node_query_text (component, "custom/value[@key='LVFS::UpdateMessage']", NULL); if (tmp != NULL) fwupd_device_set_update_message (FWUPD_DEVICE (self), tmp); } static void fu_device_class_init (FuDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; object_class->finalize = fu_device_finalize; object_class->get_property = fu_device_get_property; object_class->set_property = fu_device_set_property; pspec = g_param_spec_uint ("status", NULL, NULL, FWUPD_STATUS_UNKNOWN, FWUPD_STATUS_LAST, FWUPD_STATUS_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_STATUS, pspec); pspec = g_param_spec_string ("physical-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_PHYSICAL_ID, pspec); pspec = g_param_spec_string ("logical-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_LOGICAL_ID, pspec); pspec = g_param_spec_uint ("progress", NULL, NULL, 0, 100, 0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_PROGRESS, pspec); pspec = g_param_spec_object ("quirks", NULL, NULL, FU_TYPE_QUIRKS, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_QUIRKS, pspec); } static void fu_device_init (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); priv->status = FWUPD_STATUS_IDLE; priv->children = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->parent_guids = g_ptr_array_new_with_free_func (g_free); g_rw_lock_init (&priv->parent_guids_mutex); priv->metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); g_rw_lock_init (&priv->metadata_mutex); } static void fu_device_finalize (GObject *object) { FuDevice *self = FU_DEVICE (object); FuDevicePrivate *priv = GET_PRIVATE (self); if (priv->alternate != NULL) g_object_unref (priv->alternate); if (priv->parent != NULL) g_object_remove_weak_pointer (G_OBJECT (priv->parent), (gpointer *) &priv->parent); if (priv->quirks != NULL) g_object_unref (priv->quirks); if (priv->poll_id != 0) g_source_remove (priv->poll_id); g_rw_lock_clear (&priv->metadata_mutex); g_rw_lock_clear (&priv->parent_guids_mutex); g_hash_table_unref (priv->metadata); g_ptr_array_unref (priv->children); g_ptr_array_unref (priv->parent_guids); g_free (priv->alternate_id); g_free (priv->equivalent_id); G_OBJECT_CLASS (fu_device_parent_class)->finalize (object); } FuDevice * fu_device_new (void) { FuDevice *self = g_object_new (FU_TYPE_DEVICE, NULL); return FU_DEVICE (self); } fwupd-1.2.14/src/fu-device.h000066400000000000000000000244461402665037500155550ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-quirks.h" #include "fu-common-version.h" G_BEGIN_DECLS #define FU_TYPE_DEVICE (fu_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuDevice, fu_device, FU, DEVICE, FwupdDevice) struct _FuDeviceClass { FwupdDeviceClass parent_class; void (*to_string) (FuDevice *self, GString *str); gboolean (*write_firmware) (FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error); GBytes *(*read_firmware) (FuDevice *self, GError **error); gboolean (*detach) (FuDevice *self, GError **error); gboolean (*attach) (FuDevice *self, GError **error); gboolean (*open) (FuDevice *self, GError **error); gboolean (*close) (FuDevice *self, GError **error); gboolean (*probe) (FuDevice *self, GError **error); GBytes *(*prepare_firmware) (FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error); gboolean (*set_quirk_kv) (FuDevice *self, const gchar *key, const gchar *value, GError **error); gboolean (*setup) (FuDevice *self, GError **error); void (*incorporate) (FuDevice *self, FuDevice *donor); gboolean (*poll) (FuDevice *self, GError **error); gboolean (*activate) (FuDevice *self, GError **error); /*< private >*/ gpointer padding[19]; }; /** * FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE: * * The default removal delay for device re-enumeration taking into account a * chain of slow USB hubs. This should be used when the device is able to * reset itself between bootloader->runtime->bootloader. */ #define FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE 10000 /** * FU_DEVICE_REMOVE_DELAY_USER_REPLUG: * * The default removal delay for device re-plug taking into account humans * being slow and clumsy. This should be used when the user has to do something, * e.g. unplug, press a magic button and then replug. */ #define FU_DEVICE_REMOVE_DELAY_USER_REPLUG 40000 FuDevice *fu_device_new (void); /* helpful casting macros */ #define fu_device_add_flag(d,v) fwupd_device_add_flag(FWUPD_DEVICE(d),v) #define fu_device_remove_flag(d,v) fwupd_device_remove_flag(FWUPD_DEVICE(d),v) #define fu_device_has_flag(d,v) fwupd_device_has_flag(FWUPD_DEVICE(d),v) #define fu_device_has_instance_id(d,v) fwupd_device_has_instance_id(FWUPD_DEVICE(d),v) #define fu_device_add_checksum(d,v) fwupd_device_add_checksum(FWUPD_DEVICE(d),v) #define fu_device_add_release(d,v) fwupd_device_add_release(FWUPD_DEVICE(d),v) #define fu_device_add_icon(d,v) fwupd_device_add_icon(FWUPD_DEVICE(d),v) #define fu_device_set_created(d,v) fwupd_device_set_created(FWUPD_DEVICE(d),v) #define fu_device_set_description(d,v) fwupd_device_set_description(FWUPD_DEVICE(d),v) #define fu_device_set_flags(d,v) fwupd_device_set_flags(FWUPD_DEVICE(d),v) #define fu_device_set_modified(d,v) fwupd_device_set_modified(FWUPD_DEVICE(d),v) #define fu_device_set_plugin(d,v) fwupd_device_set_plugin(FWUPD_DEVICE(d),v) #define fu_device_set_serial(d,v) fwupd_device_set_serial(FWUPD_DEVICE(d),v) #define fu_device_set_summary(d,v) fwupd_device_set_summary(FWUPD_DEVICE(d),v) #define fu_device_set_update_error(d,v) fwupd_device_set_update_error(FWUPD_DEVICE(d),v) #define fu_device_set_update_state(d,v) fwupd_device_set_update_state(FWUPD_DEVICE(d),v) #define fu_device_set_vendor(d,v) fwupd_device_set_vendor(FWUPD_DEVICE(d),v) #define fu_device_set_vendor_id(d,v) fwupd_device_set_vendor_id(FWUPD_DEVICE(d),v) #define fu_device_set_version_lowest(d,v) fwupd_device_set_version_lowest(FWUPD_DEVICE(d),v) #define fu_device_set_version_bootloader(d,v) fwupd_device_set_version_bootloader(FWUPD_DEVICE(d),v) #define fu_device_set_version_format(d,v) fwupd_device_set_version_format(FWUPD_DEVICE(d),v) #define fu_device_set_flashes_left(d,v) fwupd_device_set_flashes_left(FWUPD_DEVICE(d),v) #define fu_device_set_install_duration(d,v) fwupd_device_set_install_duration(FWUPD_DEVICE(d),v) #define fu_device_get_checksums(d) fwupd_device_get_checksums(FWUPD_DEVICE(d)) #define fu_device_get_flags(d) fwupd_device_get_flags(FWUPD_DEVICE(d)) #define fu_device_get_created(d) fwupd_device_get_created(FWUPD_DEVICE(d)) #define fu_device_get_modified(d) fwupd_device_get_modified(FWUPD_DEVICE(d)) #define fu_device_get_guids(d) fwupd_device_get_guids(FWUPD_DEVICE(d)) #define fu_device_get_guid_default(d) fwupd_device_get_guid_default(FWUPD_DEVICE(d)) #define fu_device_get_icons(d) fwupd_device_get_icons(FWUPD_DEVICE(d)) #define fu_device_get_name(d) fwupd_device_get_name(FWUPD_DEVICE(d)) #define fu_device_get_serial(d) fwupd_device_get_serial(FWUPD_DEVICE(d)) #define fu_device_get_summary(d) fwupd_device_get_summary(FWUPD_DEVICE(d)) #define fu_device_get_id(d) fwupd_device_get_id(FWUPD_DEVICE(d)) #define fu_device_get_plugin(d) fwupd_device_get_plugin(FWUPD_DEVICE(d)) #define fu_device_get_update_error(d) fwupd_device_get_update_error(FWUPD_DEVICE(d)) #define fu_device_get_update_state(d) fwupd_device_get_update_state(FWUPD_DEVICE(d)) #define fu_device_get_vendor(d) fwupd_device_get_vendor(FWUPD_DEVICE(d)) #define fu_device_get_version(d) fwupd_device_get_version(FWUPD_DEVICE(d)) #define fu_device_get_version_lowest(d) fwupd_device_get_version_lowest(FWUPD_DEVICE(d)) #define fu_device_get_version_bootloader(d) fwupd_device_get_version_bootloader(FWUPD_DEVICE(d)) #define fu_device_get_version_format(d) fwupd_device_get_version_format(FWUPD_DEVICE(d)) #define fu_device_get_vendor_id(d) fwupd_device_get_vendor_id(FWUPD_DEVICE(d)) #define fu_device_get_flashes_left(d) fwupd_device_get_flashes_left(FWUPD_DEVICE(d)) #define fu_device_get_install_duration(d) fwupd_device_get_install_duration(FWUPD_DEVICE(d)) /* accessors */ gchar *fu_device_to_string (FuDevice *self); const gchar *fu_device_get_alternate_id (FuDevice *self); void fu_device_set_alternate_id (FuDevice *self, const gchar *alternate_id); const gchar *fu_device_get_equivalent_id (FuDevice *self); void fu_device_set_equivalent_id (FuDevice *self, const gchar *equivalent_id); void fu_device_add_guid (FuDevice *self, const gchar *guid); gboolean fu_device_has_guid (FuDevice *self, const gchar *guid); void fu_device_add_instance_id (FuDevice *self, const gchar *instance_id); gchar *fu_device_get_guids_as_str (FuDevice *self); FuDevice *fu_device_get_alternate (FuDevice *self); FuDevice *fu_device_get_parent (FuDevice *self); GPtrArray *fu_device_get_children (FuDevice *self); void fu_device_add_child (FuDevice *self, FuDevice *child); void fu_device_add_parent_guid (FuDevice *self, const gchar *guid); void fu_device_add_counterpart_guid (FuDevice *self, const gchar *guid); const gchar *fu_device_get_metadata (FuDevice *self, const gchar *key); gboolean fu_device_get_metadata_boolean (FuDevice *self, const gchar *key); guint fu_device_get_metadata_integer (FuDevice *self, const gchar *key); void fu_device_set_metadata (FuDevice *self, const gchar *key, const gchar *value); void fu_device_set_metadata_boolean (FuDevice *self, const gchar *key, gboolean value); void fu_device_set_metadata_integer (FuDevice *self, const gchar *key, guint value); void fu_device_set_id (FuDevice *self, const gchar *id); void fu_device_set_version (FuDevice *self, const gchar *version, FwupdVersionFormat fmt); const gchar *fu_device_get_physical_id (FuDevice *self); void fu_device_set_physical_id (FuDevice *self, const gchar *physical_id); const gchar *fu_device_get_logical_id (FuDevice *self); void fu_device_set_logical_id (FuDevice *self, const gchar *logical_id); const gchar *fu_device_get_custom_flags (FuDevice *self); gboolean fu_device_has_custom_flag (FuDevice *self, const gchar *hint); void fu_device_set_custom_flags (FuDevice *self, const gchar *custom_flags); void fu_device_set_name (FuDevice *self, const gchar *value); guint fu_device_get_remove_delay (FuDevice *self); void fu_device_set_remove_delay (FuDevice *self, guint remove_delay); FwupdStatus fu_device_get_status (FuDevice *self); void fu_device_set_status (FuDevice *self, FwupdStatus status); void fu_device_set_firmware_size (FuDevice *self, guint64 size); void fu_device_set_firmware_size_min (FuDevice *self, guint64 size_min); void fu_device_set_firmware_size_max (FuDevice *self, guint64 size_max); guint64 fu_device_get_firmware_size_min (FuDevice *self); guint64 fu_device_get_firmware_size_max (FuDevice *self); guint fu_device_get_progress (FuDevice *self); void fu_device_set_progress (FuDevice *self, guint progress); void fu_device_set_progress_full (FuDevice *self, gsize progress_done, gsize progress_total); void fu_device_set_quirks (FuDevice *self, FuQuirks *quirks); FuQuirks *fu_device_get_quirks (FuDevice *self); FwupdRelease *fu_device_get_release_default (FuDevice *self); gboolean fu_device_write_firmware (FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error); GBytes *fu_device_prepare_firmware (FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error); GBytes *fu_device_read_firmware (FuDevice *self, GError **error); gboolean fu_device_attach (FuDevice *self, GError **error); gboolean fu_device_detach (FuDevice *self, GError **error); void fu_device_incorporate (FuDevice *self, FuDevice *donor); gboolean fu_device_open (FuDevice *self, GError **error); gboolean fu_device_close (FuDevice *self, GError **error); gboolean fu_device_probe (FuDevice *self, GError **error); gboolean fu_device_setup (FuDevice *self, GError **error); gboolean fu_device_activate (FuDevice *self, GError **error); void fu_device_probe_invalidate (FuDevice *self); gboolean fu_device_poll (FuDevice *self, GError **error); void fu_device_set_poll_interval (FuDevice *self, guint interval); G_END_DECLS fwupd-1.2.14/src/fu-engine.c000066400000000000000000004331531402665037500155550ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEngine" #include "config.h" #include #include #include #include #include #include #include #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" #include "fwupd-resources.h" #include "fu-common-cab.h" #include "fu-common.h" #include "fu-config.h" #include "fu-debug.h" #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-engine.h" #include "fu-hwids.h" #include "fu-idle.h" #include "fu-keyring-utils.h" #include "fu-hash.h" #include "fu-history.h" #include "fu-mutex.h" #include "fu-plugin.h" #include "fu-plugin-list.h" #include "fu-plugin-private.h" #include "fu-quirks.h" #include "fu-smbios.h" #include "fu-udev-device-private.h" #include "fu-usb-device-private.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #endif static void fu_engine_finalize (GObject *obj); struct _FuEngine { GObject parent_instance; FuAppFlags app_flags; GUsbContext *usb_ctx; GUdevClient *gudev_client; FuConfig *config; FuDeviceList *device_list; FwupdStatus status; gboolean tainted; guint percentage; FuHistory *history; FuIdle *idle; XbSilo *silo; gboolean coldplug_running; guint coldplug_id; guint coldplug_delay; FuPluginList *plugin_list; GPtrArray *plugin_filter; GPtrArray *udev_subsystems; FuSmbios *smbios; FuHwids *hwids; FuQuirks *quirks; GHashTable *runtime_versions; GHashTable *compile_versions; GHashTable *approved_firmware; gboolean loaded; }; enum { SIGNAL_CHANGED, SIGNAL_DEVICE_ADDED, SIGNAL_DEVICE_REMOVED, SIGNAL_DEVICE_CHANGED, SIGNAL_STATUS_CHANGED, SIGNAL_PERCENTAGE_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE (FuEngine, fu_engine, G_TYPE_OBJECT) static void fu_engine_emit_changed (FuEngine *self) { g_signal_emit (self, signals[SIGNAL_CHANGED], 0); fu_engine_idle_reset (self); } static void fu_engine_emit_device_changed (FuEngine *self, FuDevice *device) { g_signal_emit (self, signals[SIGNAL_DEVICE_CHANGED], 0, device); } /** * fu_engine_get_status: * @self: A #FuEngine * * Gets the current engine status. * * Returns: a #FwupdStatus, e.g. %FWUPD_STATUS_DECOMPRESSING **/ FwupdStatus fu_engine_get_status (FuEngine *self) { g_return_val_if_fail (FU_IS_ENGINE (self), 0); return self->status; } static void fu_engine_set_status (FuEngine *self, FwupdStatus status) { if (self->status == status) return; self->status = status; /* emit changed */ g_debug ("Emitting PropertyChanged('Status'='%s')", fwupd_status_to_string (status)); g_signal_emit (self, signals[SIGNAL_STATUS_CHANGED], 0, status); } static void fu_engine_set_percentage (FuEngine *self, guint percentage) { if (self->percentage == percentage) return; self->percentage = percentage; /* emit changed */ g_signal_emit (self, signals[SIGNAL_PERCENTAGE_CHANGED], 0, percentage); } static void fu_engine_progress_notify_cb (FuDevice *device, GParamSpec *pspec, FuEngine *self) { if (fu_device_get_status (device) == FWUPD_STATUS_UNKNOWN) return; fu_engine_set_percentage (self, fu_device_get_progress (device)); fu_engine_emit_device_changed (self, device); } static void fu_engine_status_notify_cb (FuDevice *device, GParamSpec *pspec, FuEngine *self) { fu_engine_set_status (self, fu_device_get_status (device)); fu_engine_emit_device_changed (self, device); } static void fu_engine_watch_device (FuEngine *self, FuDevice *device) { g_autoptr(FuDevice) device_old = fu_device_list_get_old (self->device_list, device); if (device_old != NULL) { g_signal_handlers_disconnect_by_func (device_old, fu_engine_progress_notify_cb, self); g_signal_handlers_disconnect_by_func (device_old, fu_engine_status_notify_cb, self); } g_signal_connect (device, "notify::progress", G_CALLBACK (fu_engine_progress_notify_cb), self); g_signal_connect (device, "notify::status", G_CALLBACK (fu_engine_status_notify_cb), self); } static void fu_engine_device_added_cb (FuDeviceList *device_list, FuDevice *device, FuEngine *self) { fu_engine_watch_device (self, device); g_signal_emit (self, signals[SIGNAL_DEVICE_ADDED], 0, device); } static void fu_engine_device_runner_device_removed (FuEngine *self, FuDevice *device) { GPtrArray *plugins = fu_plugin_list_get_all (self->plugin_list); for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j); fu_plugin_runner_device_removed (plugin_tmp, device); } } static void fu_engine_device_removed_cb (FuDeviceList *device_list, FuDevice *device, FuEngine *self) { fu_engine_device_runner_device_removed (self, device); g_signal_handlers_disconnect_by_data (device, self); g_signal_emit (self, signals[SIGNAL_DEVICE_REMOVED], 0, device); } static void fu_engine_device_changed_cb (FuDeviceList *device_list, FuDevice *device, FuEngine *self) { fu_engine_watch_device (self, device); fu_engine_emit_device_changed (self, device); } static gboolean fu_engine_set_device_version_format (FuEngine *self, FuDevice *device, XbNode *component, GError **error) { FwupdVersionFormat fmt; const gchar *developer_name; const gchar *version_format; /* specified in metadata */ version_format = xb_node_query_text (component, "custom/value[@key='LVFS::VersionFormat']", NULL); if (version_format != NULL) { fmt = fwupd_version_format_from_string (version_format); if (fmt == FWUPD_VERSION_FORMAT_UNKNOWN) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "version format from metadata %s unsupported", version_format); return FALSE; } g_debug ("using VersionFormat %s from metadata", version_format); fu_device_set_version_format (device, fmt); return TRUE; } /* fall back to the SmbiosManufacturer quirk */ developer_name = xb_node_query_text (component, "developer_name", NULL); if (developer_name != NULL && fu_device_has_flag (device, FWUPD_DEVICE_FLAG_INTERNAL)) { g_autofree gchar *group = NULL; group = g_strdup_printf ("SmbiosManufacturer=%s", developer_name); version_format = fu_quirks_lookup_by_id (self->quirks, group, FU_QUIRKS_UEFI_VERSION_FORMAT); if (version_format != NULL) { fmt = fwupd_version_format_from_string (version_format); if (fmt == FWUPD_VERSION_FORMAT_UNKNOWN) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "version format %s from quirk %s unsupported", version_format, developer_name); return FALSE; } g_debug ("using VersionFormat %s from SmbiosManufacturer %s", version_format, developer_name); fu_device_set_version_format (device, fmt); return TRUE; } } /* nothing found, which is probably fine */ return TRUE; } /* convert hex and decimal versions to dotted style */ static gchar * fu_engine_get_release_version (FuEngine *self, FuDevice *dev, XbNode *rel, GError **error) { FwupdVersionFormat fmt = FWUPD_VERSION_FORMAT_TRIPLET; const gchar *version; guint64 ver_uint32; /* get version */ version = xb_node_get_attr (rel, "version"); if (version == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "version unset"); return NULL; } /* already dotted notation */ if (g_strstr_len (version, -1, ".") != NULL) return g_strdup (version); /* specified in metadata or from a quirk */ fmt = fu_device_get_version_format (dev); if (fmt == FWUPD_VERSION_FORMAT_UNKNOWN) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "version format unset and version %s ambiguous", version); return NULL; } /* don't touch my version! */ if (fmt == FWUPD_VERSION_FORMAT_PLAIN) return g_strdup (version); /* parse as integer */ ver_uint32 = fu_common_strtoull (version); if (ver_uint32 == 0 || ver_uint32 > G_MAXUINT32) return g_strdup (version); /* convert to dotted decimal */ return fu_common_version_from_uint32 ((guint32) ver_uint32, fmt); } static gboolean fu_engine_set_release_from_appstream (FuEngine *self, FuDevice *dev, FwupdRelease *rel, XbNode *component, XbNode *release, GError **error) { FwupdRemote *remote = NULL; const gchar *tmp; const gchar *remote_id; guint64 tmp64; g_autofree gchar *version_rel = NULL; g_autoptr(GPtrArray) cats = NULL; g_autoptr(XbNode) description = NULL; /* set from the component */ tmp = xb_node_query_text (component, "id", NULL); if (tmp != NULL) fwupd_release_set_appstream_id (rel, tmp); tmp = xb_node_query_text (component, "url[@type='homepage']", NULL); if (tmp != NULL) fwupd_release_set_homepage (rel, tmp); tmp = xb_node_query_text (component, "project_license", NULL); if (tmp != NULL) fwupd_release_set_license (rel, tmp); tmp = xb_node_query_text (component, "name", NULL); if (tmp != NULL) fwupd_release_set_name (rel, tmp); tmp = xb_node_query_text (component, "summary", NULL); if (tmp != NULL) fwupd_release_set_summary (rel, tmp); tmp = xb_node_query_text (component, "developer_name", NULL); if (tmp != NULL) fwupd_release_set_vendor (rel, tmp); /* the version is fixed up at runtime */ version_rel = fu_engine_get_release_version (self, dev, release, error); if (version_rel == NULL) return FALSE; fwupd_release_set_version (rel, version_rel); /* find the remote */ remote_id = xb_node_query_text (component, "../custom/value[@key='fwupd::RemoteId']", NULL); if (remote_id != NULL) { fwupd_release_set_remote_id (rel, remote_id); remote = fu_config_get_remote_by_id (self->config, remote_id); if (remote == NULL) g_warning ("no remote found for release %s", version_rel); } description = xb_node_query_first (release, "description", NULL); if (description != NULL) { g_autofree gchar *xml = NULL; xml = xb_node_export (description, XB_NODE_EXPORT_FLAG_ONLY_CHILDREN, NULL); if (xml != NULL) fwupd_release_set_description (rel, xml); } tmp = xb_node_query_text (release, "location", NULL); if (tmp != NULL) { g_autofree gchar *uri = NULL; if (remote != NULL) uri = fwupd_remote_build_firmware_uri (remote, tmp, NULL); if (uri == NULL) uri = g_strdup (tmp); fwupd_release_set_uri (rel, uri); } else if (remote != NULL && fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_DIRECTORY) { g_autofree gchar *uri = NULL; tmp = xb_node_query_text (component, "../custom/value[@key='fwupd::FilenameCache']", NULL); if (tmp != NULL) { uri = g_strdup_printf ("file://%s", tmp); fwupd_release_set_uri (rel, uri); } } tmp = xb_node_query_text (release, "checksum[@target='content']", NULL); if (tmp != NULL) fwupd_release_set_filename (rel, tmp); tmp = xb_node_query_text (release, "url[@type='details']", NULL); if (tmp != NULL) fwupd_release_set_details_url (rel, tmp); tmp = xb_node_query_text (release, "url[@type='source']", NULL); if (tmp != NULL) fwupd_release_set_source_url (rel, tmp); tmp = xb_node_query_text (release, "checksum[@target='container']", NULL); if (tmp != NULL) fwupd_release_add_checksum (rel, tmp); tmp64 = xb_node_query_text_as_uint (release, "size[@type='installed']", NULL); if (tmp64 != G_MAXUINT64) { fwupd_release_set_size (rel, tmp64); } else { GBytes *sz = xb_node_get_data (release, "fwupd::ReleaseSize"); if (sz != NULL) { const guint64 *sizeptr = g_bytes_get_data (sz, NULL); fwupd_release_set_size (rel, *sizeptr); } } tmp64 = xb_node_get_attr_as_uint (release, "install_duration"); if (tmp64 != G_MAXUINT64) fwupd_release_set_install_duration (rel, tmp64); cats = xb_node_query (component, "categories/category", 0, NULL); if (cats != NULL) { for (guint i = 0; i < cats->len; i++) { XbNode *n = g_ptr_array_index (cats, i); fwupd_release_add_category (rel, xb_node_get_text (n)); } } tmp = xb_node_query_text (component, "custom/value[@key='LVFS::UpdateProtocol']", NULL); if (tmp != NULL) fwupd_release_set_protocol (rel, tmp); tmp = xb_node_query_text (component, "custom/value[@key='LVFS::UpdateMessage']", NULL); if (tmp != NULL) fwupd_release_set_update_message (rel, tmp); return TRUE; } /* finds the remote-id for the first firmware in the silo that matches this * container checksum */ static const gchar * fu_engine_get_remote_id_for_checksum (FuEngine *self, const gchar *csum) { g_autofree gchar *xpath = NULL; g_autoptr(XbNode) key = NULL; xpath = g_strdup_printf ("components/component/releases/release/" "checksum[@target='container'][text()='%s']/../../" "../../custom/value[@key='fwupd::RemoteId']", csum); key = xb_silo_query_first (self->silo, xpath, NULL); if (key == NULL) return NULL; return xb_node_get_text (key); } /** * fu_engine_unlock: * @self: A #FuEngine * @device_id: A device ID * @error: A #GError, or %NULL * * Unlocks a device. * * Returns: %TRUE for success **/ gboolean fu_engine_unlock (FuEngine *self, const gchar *device_id, GError **error) { FuPlugin *plugin; g_autoptr(FuDevice) device = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* check the device exists */ device = fu_device_list_get_by_id (self->device_list, device_id, error); if (device == NULL) return FALSE; /* get the plugin */ plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), error); if (plugin == NULL) return FALSE; /* run the correct plugin that added this */ if (!fu_plugin_runner_unlock (plugin, device, error)) return FALSE; /* make the UI update */ fu_engine_emit_device_changed (self, device); fu_engine_emit_changed (self); return TRUE; } gboolean fu_engine_modify_config (FuEngine *self, const gchar *key, const gchar *value, GError **error) { const gchar *keys[] = { "ArchiveSizeMax", "BlacklistDevices", "BlacklistPlugins", "IdleTimeout", "VerboseDomains", NULL }; g_return_val_if_fail (FU_IS_ENGINE (self), FALSE); g_return_val_if_fail (key != NULL, FALSE); g_return_val_if_fail (value != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* check keys are valid */ if (!g_strv_contains (keys, key)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "key %s not supported", key); return FALSE; } /* modify, effective next reboot */ return fu_config_modify_and_save (self->config, key, value, error); } /** * fu_engine_modify_remote: * @self: A #FuEngine * @remote_id: A remote ID * @key: the key, e.g. `Enabled` * @value: the key, e.g. `true` * @error: A #GError, or %NULL * * Updates the verification silo entry for a specific device. * * Returns: %TRUE for success **/ gboolean fu_engine_modify_remote (FuEngine *self, const gchar *remote_id, const gchar *key, const gchar *value, GError **error) { FwupdRemote *remote; const gchar *filename; const gchar *keys[] = { "Enabled", "MetadataURI", "FirmwareBaseURI", NULL }; g_autoptr(GKeyFile) keyfile = g_key_file_new (); /* check remote is valid */ remote = fu_config_get_remote_by_id (self->config, remote_id); if (remote == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "remote %s not found", remote_id); return FALSE; } /* check keys are valid */ if (!g_strv_contains (keys, key)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "key %s not supported", key); return FALSE; } /* modify the remote filename */ filename = fwupd_remote_get_filename_source (remote); if (!g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_KEEP_COMMENTS, error)) { g_prefix_error (error, "failed to load %s: ", filename); return FALSE; } g_key_file_set_string (keyfile, "fwupd Remote", key, value); return g_key_file_save_to_file (keyfile, filename, error); } /** * fu_engine_modify_device: * @self: A #FuEngine * @device_id: A device ID * @key: the key, e.g. `Flags` * @value: the key, e.g. `reported` * @error: A #GError, or %NULL * * Sets the reported flag for a specific device. This ensures that other * front-end clients for fwupd do not report the same event. * * Returns: %TRUE for success **/ gboolean fu_engine_modify_device (FuEngine *self, const gchar *device_id, const gchar *key, const gchar *value, GError **error) { g_autoptr(FuDevice) device = NULL; /* find the correct device */ device = fu_history_get_device_by_id (self->history, device_id, error); if (device == NULL) return FALSE; /* support adding a subset of the device flags */ if (g_strcmp0 (key, "Flags") == 0) { FwupdDeviceFlags flag = fwupd_device_flag_from_string (value); if (flag == FWUPD_DEVICE_FLAG_UNKNOWN) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "key %s not a valid flag", key); return FALSE; } if (flag != FWUPD_DEVICE_FLAG_REPORTED && flag != FWUPD_DEVICE_FLAG_NOTIFIED) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flag %s cannot be set from client", key); return FALSE; } fu_device_add_flag (device, flag); return fu_history_modify_device (self->history, device, FU_HISTORY_FLAGS_MATCH_OLD_VERSION | FU_HISTORY_FLAGS_MATCH_NEW_VERSION, error); } /* others invalid */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "key %s not supported", key); return FALSE; } static const gchar * fu_engine_checksum_type_to_string (GChecksumType checksum_type) { if (checksum_type == G_CHECKSUM_SHA1) return "sha1"; if (checksum_type == G_CHECKSUM_SHA256) return "sha256"; if (checksum_type == G_CHECKSUM_SHA512) return "sha512"; return "sha1"; } /** * fu_engine_verify_update: * @self: A #FuEngine * @device_id: A device ID * @error: A #GError, or %NULL * * Updates the verification silo entry for a specific device. * * Returns: %TRUE for success **/ gboolean fu_engine_verify_update (FuEngine *self, const gchar *device_id, GError **error) { FuPlugin *plugin; GPtrArray *checksums; GPtrArray *guids; g_autofree gchar *fn = NULL; g_autofree gchar *localstatedir = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new (); g_autoptr(XbBuilderNode) component = NULL; g_autoptr(XbBuilderNode) provides = NULL; g_autoptr(XbBuilderNode) release = NULL; g_autoptr(XbBuilderNode) releases = NULL; g_autoptr(XbSilo) silo = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* check the devices still exists */ device = fu_device_list_get_by_id (self->device_list, device_id, error); if (device == NULL) return FALSE; /* get the plugin */ plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), error); if (plugin == NULL) return FALSE; /* get the checksum */ checksums = fu_device_get_checksums (device); if (checksums->len == 0) { if (!fu_plugin_runner_verify (plugin, device, FU_PLUGIN_VERIFY_FLAG_NONE, error)) return FALSE; fu_engine_emit_device_changed (self, device); } /* we got nothing */ if (checksums->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device verification not supported"); return FALSE; } /* build XML */ component = xb_builder_node_insert (NULL, "component", "type", "firmware", NULL); provides = xb_builder_node_insert (component, "provides", NULL); guids = fu_device_get_guids (device); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index (guids, i); g_autoptr(XbBuilderNode) provide = NULL; provide = xb_builder_node_insert (provides, "firmware", "type", "flashed", NULL); xb_builder_node_set_text (provide, guid, -1); } releases = xb_builder_node_insert (component, "releases", NULL); release = xb_builder_node_insert (releases, "release", "version", fu_device_get_version (device), NULL); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index (checksums, i); GChecksumType kind = fwupd_checksum_guess_kind (checksum); g_autoptr(XbBuilderNode) csum = NULL; csum = xb_builder_node_insert (release, "checksum", "type", fu_engine_checksum_type_to_string (kind), "target", "content", NULL); xb_builder_node_set_text (csum, checksum, -1); } xb_builder_import_node (builder, component); /* save silo */ localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); fn = g_strdup_printf ("%s/verify/%s.xml", localstatedir, device_id); if (!fu_common_mkdir_parent (fn, error)) return FALSE; file = g_file_new_for_path (fn); silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; if (!xb_silo_export_file (silo, file, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE, NULL, error)) return FALSE; /* success */ return TRUE; } XbNode * fu_engine_get_component_by_guids (FuEngine *self, FuDevice *device) { GPtrArray *guids = fu_device_get_guids (device); g_autoptr(GString) xpath = g_string_new (NULL); g_autoptr(XbNode) component = NULL; for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index (guids, i); xb_string_append_union (xpath, "components/component/" "provides/firmware[@type='flashed'][text()='%s']/" "../..", guid); } component = xb_silo_query_first (self->silo, xpath->str, NULL); if (component != NULL) return g_steal_pointer (&component); return NULL; } /** * fu_engine_verify: * @self: A #FuEngine * @device_id: A device ID * @error: A #GError, or %NULL * * Verifies a device firmware checksum using the verification silo entry. * * Returns: %TRUE for success **/ gboolean fu_engine_verify (FuEngine *self, const gchar *device_id, GError **error) { FuPlugin *plugin; GPtrArray *checksums; const gchar *version; g_autofree gchar *fn = NULL; g_autofree gchar *localstatedir = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GString) xpath_csum = g_string_new (NULL); g_autoptr(XbNode) csum = NULL; g_autoptr(XbNode) release = NULL; g_autoptr(XbSilo) silo = xb_silo_new (); g_return_val_if_fail (FU_IS_ENGINE (self), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* check the id exists */ device = fu_device_list_get_by_id (self->device_list, device_id, error); if (device == NULL) return FALSE; /* get the plugin */ plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), error); if (plugin == NULL) return FALSE; /* set the device firmware hash */ if (!fu_plugin_runner_verify (plugin, device, FU_PLUGIN_VERIFY_FLAG_NONE, error)) return FALSE; /* find component in metadata */ version = fu_device_get_version (device); localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); fn = g_strdup_printf ("%s/verify/%s.xml", localstatedir, device_id); file = g_file_new_for_path (fn); if (g_file_query_exists (file, NULL)) { g_autofree gchar *xpath = NULL; g_autoptr(XbBuilder) builder = xb_builder_new (); g_autoptr(XbBuilderSource) source = xb_builder_source_new (); if (!xb_builder_source_load_file (source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, error)) return FALSE; xb_builder_import_source (builder, source); silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; xpath = g_strdup_printf ("component/releases/release[@version='%s']", version); release = xb_silo_query_first (silo, xpath, NULL); } /* try again with the system metadata */ if (release == NULL) { GPtrArray *guids = fu_device_get_guids (device); FwupdVersionFormat fmt = fu_device_get_version_format (device); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index (guids, i); g_autofree gchar *xpath2 = NULL; g_autoptr(GPtrArray) releases = NULL; xpath2 = g_strdup_printf ("components/component/" "provides/firmware[@type='flashed'][text()='%s']/" "../../releases/release", guid); releases = xb_silo_query (self->silo, xpath2, 0, error); if (releases == NULL) return FALSE; for (guint j = 0; j < releases->len; j++) { XbNode *rel = g_ptr_array_index (releases, j); const gchar *rel_ver = xb_node_get_attr (rel, "version"); g_autofree gchar *tmp_ver = fu_common_version_parse_from_format (rel_ver, fmt); if (fu_common_vercmp (tmp_ver, version) == 0) { release = g_object_ref (rel); break; } } if (release != NULL) break; } } if (release == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No version %s", version); return FALSE; } /* get the matching checksum */ checksums = fu_device_get_checksums (device); if (checksums->len == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No device checksums for %s", version); return FALSE; } /* do any of the checksums in the release match any in the device */ for (guint j = 0; j < checksums->len; j++) { const gchar *hash_tmp = g_ptr_array_index (checksums, j); xb_string_append_union (xpath_csum, "checksum[@target='device'][text()='%s']", hash_tmp); xb_string_append_union (xpath_csum, "checksum[@target='content'][text()='%s']", hash_tmp); } csum = xb_node_query_first (release, xpath_csum->str, NULL); if (csum == NULL) { g_autoptr(GString) checksums_device = g_string_new (NULL); g_autoptr(GString) checksums_metadata = g_string_new (NULL); g_autoptr(GPtrArray) csums = NULL; g_autoptr(GString) xpath = g_string_new (NULL); /* get all checksums to display a useful error */ xb_string_append_union (xpath, "checksum[@target='device']"); xb_string_append_union (xpath, "checksum[@target='content']"); csums = xb_node_query (release, xpath->str, 0, NULL); if (csums == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No device or content checksum for %s", version); return FALSE; } for (guint i = 0; i < csums->len; i++) { XbNode *csum_tmp = g_ptr_array_index (csums, i); xb_string_append_union (checksums_metadata, "%s", xb_node_get_text (csum_tmp)); } for (guint i = 0; i < checksums->len; i++) { const gchar *hash_tmp = g_ptr_array_index (checksums, i); xb_string_append_union (checksums_device, "%s", hash_tmp); } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "For v%s expected %s, got %s", version, checksums_metadata->str, checksums_device->str); return FALSE; } /* success */ return TRUE; } static gboolean fu_engine_require_vercmp (XbNode *req, const gchar *version, GError **error) { gboolean ret = FALSE; gint rc = 0; const gchar *tmp = xb_node_get_attr (req, "compare"); const gchar *version_req = xb_node_get_attr (req, "version"); if (g_strcmp0 (tmp, "eq") == 0) { rc = fu_common_vercmp (version, version_req); ret = rc == 0; } else if (g_strcmp0 (tmp, "ne") == 0) { rc = fu_common_vercmp (version, version_req); ret = rc != 0; } else if (g_strcmp0 (tmp, "lt") == 0) { rc = fu_common_vercmp (version, version_req); ret = rc < 0; } else if (g_strcmp0 (tmp, "gt") == 0) { rc = fu_common_vercmp (version, version_req); ret = rc > 0; } else if (g_strcmp0 (tmp, "le") == 0) { rc = fu_common_vercmp (version, version_req); ret = rc <= 0; } else if (g_strcmp0 (tmp, "ge") == 0) { rc = fu_common_vercmp (version, version_req); ret = rc >= 0; } else if (g_strcmp0 (tmp, "glob") == 0) { ret = fnmatch (version_req, version, 0) == 0; } else if (g_strcmp0 (tmp, "regex") == 0) { ret = g_regex_match_simple (version_req, version, 0, 0); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to compare [%s] and [%s]", version_req, version); return FALSE; } /* set error */ if (!ret) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed predicate [%s %s %s]", version_req, tmp, version); } return ret; } static gboolean fu_engine_check_requirement_not_child (FuEngine *self, XbNode *req, FuDevice *device, GError **error) { GPtrArray *children = fu_device_get_children (device); /* only supported */ if (g_strcmp0 (xb_node_get_element (req), "firmware") != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot handle not-child %s requirement", xb_node_get_element (req)); return FALSE; } /* check each child */ for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index (children, i); const gchar *version = fu_device_get_version (child); if (fu_engine_require_vercmp (req, version, NULL)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not compatible with child device version %s", version); return FALSE; } } return TRUE; } static gboolean fu_engine_check_requirement_firmware (FuEngine *self, XbNode *req, FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; /* old firmware version */ if (xb_node_get_text (req) == NULL) { const gchar *version = fu_device_get_version (device); if (!fu_engine_require_vercmp (req, version, &error_local)) { if (g_strcmp0 (xb_node_get_attr (req, "compare"), "ge") == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with firmware version %s, requires >= %s", version, xb_node_get_attr (req, "version")); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with firmware version: %s", error_local->message); } return FALSE; } return TRUE; } /* bootloader version */ if (g_strcmp0 (xb_node_get_text (req), "bootloader") == 0) { const gchar *version = fu_device_get_version_bootloader (device); if (!fu_engine_require_vercmp (req, version, &error_local)) { if (g_strcmp0 (xb_node_get_attr (req, "compare"), "ge") == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with bootloader version %s, requires >= %s", version, xb_node_get_attr (req, "version")); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with bootloader version: %s", error_local->message); } return FALSE; } return TRUE; } /* vendor ID */ if (g_strcmp0 (xb_node_get_text (req), "vendor-id") == 0 && fu_device_get_vendor_id (device) != NULL) { const gchar *version = fu_device_get_vendor_id (device); if (!fu_engine_require_vercmp (req, version, &error_local)) { if (g_strcmp0 (xb_node_get_attr (req, "compare"), "ge") == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with vendor %s, requires >= %s", version, xb_node_get_attr (req, "version")); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with vendor: %s", error_local->message); } return FALSE; } return TRUE; } /* child version */ if (g_strcmp0 (xb_node_get_text (req), "not-child") == 0) return fu_engine_check_requirement_not_child (self, req, device, error); /* another device */ if (fwupd_guid_is_valid (xb_node_get_text (req))) { const gchar *guid = xb_node_get_text (req); const gchar *version; g_autoptr(FuDevice) device2 = NULL; /* find if the other device exists */ device2 = fu_device_list_get_by_guid (self->device_list, guid, error); if (device2 == NULL) return FALSE; /* get the version of the other device */ version = fu_device_get_version (device2); if (version != NULL && !fu_engine_require_vercmp (req, version, &error_local)) { if (g_strcmp0 (xb_node_get_attr (req, "compare"), "ge") == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with %s version %s, requires >= %s", fu_device_get_name (device2), version, xb_node_get_attr (req, "version")); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with %s: %s", fu_device_get_name (device2), error_local->message); } return FALSE; } return TRUE; } /* not supported */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot handle firmware requirement '%s'", xb_node_get_text (req)); return FALSE; } static gboolean fu_engine_check_requirement_id (FuEngine *self, XbNode *req, GError **error) { g_autoptr(GError) error_local = NULL; const gchar *version = g_hash_table_lookup (self->runtime_versions, xb_node_get_text (req)); if (version == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no version available for %s", xb_node_get_text (req)); return FALSE; } if (!fu_engine_require_vercmp (req, version, &error_local)) { if (g_strcmp0 (xb_node_get_attr (req, "compare"), "ge") == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with %s version %s, requires >= %s", xb_node_get_text (req), version, xb_node_get_attr (req, "version")); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with %s version: %s", xb_node_get_text (req), error_local->message); } return FALSE; } g_debug ("requirement %s %s %s on %s passed", xb_node_get_attr (req, "version"), xb_node_get_attr (req, "compare"), version, xb_node_get_text (req)); return TRUE; } static gboolean fu_engine_check_requirement_hardware (FuEngine *self, XbNode *req, GError **error) { g_auto(GStrv) hwid_split = NULL; /* split and treat as OR */ hwid_split = g_strsplit (xb_node_get_text (req), "|", -1); for (guint i = 0; hwid_split[i] != NULL; i++) { if (fu_hwids_has_guid (self->hwids, hwid_split[i])) { g_debug ("HWID provided %s", hwid_split[i]); return TRUE; } } /* nothing matched */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no HWIDs matched %s", xb_node_get_text (req)); return FALSE; } static gboolean fu_engine_check_requirement (FuEngine *self, XbNode *req, FuDevice *device, GError **error) { /* ensure component requirement */ if (g_strcmp0 (xb_node_get_element (req), "id") == 0) return fu_engine_check_requirement_id (self, req, error); /* ensure firmware requirement */ if (g_strcmp0 (xb_node_get_element (req), "firmware") == 0) { if (device == NULL) return TRUE; return fu_engine_check_requirement_firmware (self, req, device, error); } /* ensure hardware requirement */ if (g_strcmp0 (xb_node_get_element (req), "hardware") == 0) return fu_engine_check_requirement_hardware (self, req, error); /* not supported */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot handle requirement type %s", xb_node_get_element (req)); return FALSE; } gboolean fu_engine_check_requirements (FuEngine *self, FuInstallTask *task, FwupdInstallFlags flags, GError **error) { FuDevice *device = fu_install_task_get_device (task); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) reqs = NULL; /* all install task checks require a device */ if (device != NULL) { if (!fu_install_task_check_requirements (task, flags, error)) return FALSE; } /* do engine checks */ reqs = xb_node_query (fu_install_task_get_component (task), "requires/*", 0, &error_local); if (reqs == NULL) { if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return TRUE; if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return TRUE; g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } for (guint i = 0; i < reqs->len; i++) { XbNode *req = g_ptr_array_index (reqs, i); if (!fu_engine_check_requirement (self, req, device, error)) return FALSE; } return TRUE; } void fu_engine_idle_reset (FuEngine *self) { fu_idle_reset (self->idle); } static gchar * fu_engine_get_boot_time (void) { g_autofree gchar *buf = NULL; g_auto(GStrv) lines = NULL; if (!g_file_get_contents ("/proc/stat", &buf, NULL, NULL)) return NULL; lines = g_strsplit (buf, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { if (g_str_has_prefix (lines[i], "btime ")) return g_strdup (lines[i] + 6); } return NULL; } static GHashTable * fu_engine_get_report_metadata (FuEngine *self) { GHashTable *hash; gchar *btime; struct utsname name_tmp; g_autoptr(GList) compile_keys = g_hash_table_get_keys (self->compile_versions); g_autoptr(GList) runtime_keys = g_hash_table_get_keys (self->runtime_versions); /* convert all the runtime and compile-time versions */ hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); for (GList *l = compile_keys; l != NULL; l = l->next) { const gchar *id = l->data; const gchar *version = g_hash_table_lookup (self->compile_versions, id); g_hash_table_insert (hash, g_strdup_printf ("CompileVersion(%s)", id), g_strdup (version)); } for (GList *l = runtime_keys; l != NULL; l = l->next) { const gchar *id = l->data; const gchar *version = g_hash_table_lookup (self->runtime_versions, id); g_hash_table_insert (hash, g_strdup_printf ("RuntimeVersion(%s)", id), g_strdup (version)); } /* kernel version is often important for debugging failures */ memset (&name_tmp, 0, sizeof (struct utsname)); if (uname (&name_tmp) >= 0) { g_hash_table_insert (hash, g_strdup ("CpuArchitecture"), g_strdup (name_tmp.machine)); g_hash_table_insert (hash, g_strdup ("KernelVersion"), g_strdup (name_tmp.release)); } /* add the kernel boot time so we can detect a reboot */ btime = fu_engine_get_boot_time (); if (btime != NULL) g_hash_table_insert (hash, g_strdup ("BootTime"), btime); return hash; } /** * fu_engine_composite_prepare: * @self: A #FuEngine * @devices: (element-type #FuDevice): devices that will be updated * @error: A #GError, or %NULL * * Calls into the plugin loader, informing each plugin of the pending upgrade(s). * * Any failure in any plugin will abort all of the actions before they are started. * * Returns: %TRUE for success **/ gboolean fu_engine_composite_prepare (FuEngine *self, GPtrArray *devices, GError **error) { GPtrArray *plugins = fu_plugin_list_get_all (self->plugin_list); for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j); if (!fu_plugin_runner_composite_prepare (plugin_tmp, devices, error)) return FALSE; } return TRUE; } /** * fu_engine_composite_cleanup: * @self: A #FuEngine * @devices: (element-type #FuDevice): devices that will be updated * @error: A #GError, or %NULL * * Calls into the plugin loader, informing each plugin of the pending upgrade(s). * * Returns: %TRUE for success **/ gboolean fu_engine_composite_cleanup (FuEngine *self, GPtrArray *devices, GError **error) { GPtrArray *plugins = fu_plugin_list_get_all (self->plugin_list); for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j); if (!fu_plugin_runner_composite_cleanup (plugin_tmp, devices, error)) return FALSE; } return TRUE; } /** * fu_engine_install_tasks: * @self: A #FuEngine * @install_tasks: (element-type FuInstallTask): A #FuDevice * @blob_cab: The #GBytes of the .cab file * @flags: The #FwupdInstallFlags, e.g. %FWUPD_DEVICE_FLAG_UPDATABLE * @error: A #GError, or %NULL * * Installs a specific firmware file on one or more install tasks. * * By this point all the requirements and tests should have been done in * fu_engine_check_requirements() so this should not fail before running * the plugin loader. * * Returns: %TRUE for success **/ gboolean fu_engine_install_tasks (FuEngine *self, GPtrArray *install_tasks, GBytes *blob_cab, FwupdInstallFlags flags, GError **error) { g_autoptr(FuIdleLocker) locker = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_new = NULL; /* do not allow auto-shutdown during this time */ locker = fu_idle_locker_new (self->idle, "performing update"); g_assert (locker != NULL); /* notify the plugins about the composite action */ devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < install_tasks->len; i++) { FuInstallTask *task = g_ptr_array_index (install_tasks, i); g_ptr_array_add (devices, g_object_ref (fu_install_task_get_device (task))); } if (!fu_engine_composite_prepare (self, devices, error)) { g_prefix_error (error, "failed to prepare composite action: "); return FALSE; } /* all authenticated, so install all the things */ for (guint i = 0; i < install_tasks->len; i++) { FuInstallTask *task = g_ptr_array_index (install_tasks, i); if (!fu_engine_install (self, task, blob_cab, flags, error)) { g_autoptr(GError) error_local = NULL; if (!fu_engine_composite_cleanup (self, devices, &error_local)) { g_warning ("failed to cleanup failed composite action: %s", error_local->message); } return FALSE; } } /* set all the device statuses back to unknown */ for (guint i = 0; i < install_tasks->len; i++) { FuInstallTask *task = g_ptr_array_index (install_tasks, i); FuDevice *device = fu_install_task_get_device (task); fu_device_set_status (device, FWUPD_STATUS_UNKNOWN); } /* get a new list of devices in case they replugged */ devices_new = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < devices->len; i++) { FuDevice *device; g_autoptr(FuDevice) device_new = NULL; g_autoptr(GError) error_local = NULL; device = g_ptr_array_index (devices, i); device_new = fu_device_list_get_by_id (self->device_list, fu_device_get_id (device), &error_local); if (device_new == NULL) { g_debug ("failed to find new device: %s", error_local->message); continue; } g_ptr_array_add (devices_new, g_steal_pointer (&device_new)); } /* notify the plugins about the composite action */ if (!fu_engine_composite_cleanup (self, devices_new, error)) { g_prefix_error (error, "failed to cleanup composite action: "); return FALSE; } /* success */ return TRUE; } static FwupdRelease * fu_engine_create_release_metadata (FuEngine *self, FuPlugin *plugin, GError **error) { const gchar *tmp; g_autoptr(FwupdRelease) release = fwupd_release_new (); g_autoptr(GHashTable) metadata_hash = NULL; g_autoptr(GHashTable) os_release = NULL; /* add release data from os-release */ os_release = fwupd_get_os_release (error); if (os_release == NULL) return NULL; /* build the version metadata */ metadata_hash = fu_engine_get_report_metadata (self); fwupd_release_add_metadata (release, metadata_hash); fwupd_release_add_metadata (release, fu_plugin_get_report_metadata (plugin)); /* add details from os-release as metadata */ tmp = g_hash_table_lookup (os_release, "ID"); if (tmp != NULL) fwupd_release_add_metadata_item (release, "DistroId", tmp); tmp = g_hash_table_lookup (os_release, "VERSION_ID"); if (tmp != NULL) fwupd_release_add_metadata_item (release, "DistroVersion", tmp); tmp = g_hash_table_lookup (os_release, "VARIANT_ID"); if (tmp != NULL) fwupd_release_add_metadata_item (release, "DistroVariant", tmp); return g_steal_pointer (&release); } static gboolean fu_engine_is_running_offline (FuEngine *self) { #ifdef HAVE_SYSTEMD g_autofree gchar *default_target = NULL; g_autoptr(GError) error = NULL; default_target = fu_systemd_get_default_target (&error); if (default_target == NULL) { g_warning ("failed to get default.target: %s", error->message); return FALSE; } return g_strcmp0 (default_target, "system-update.target") == 0; #else return FALSE; #endif } /** * fu_engine_install: * @self: A #FuEngine * @task: A #FuInstallTask * @blob_cab: The #GBytes of the .cab file * @flags: The #FwupdInstallFlags, e.g. %FWUPD_DEVICE_FLAG_UPDATABLE * @error: A #GError, or %NULL * * Installs a specific firmware file on a device. * * By this point all the requirements and tests should have been done in * fu_engine_check_requirements() so this should not fail before running * the plugin loader. * * Returns: %TRUE for success **/ gboolean fu_engine_install (FuEngine *self, FuInstallTask *task, GBytes *blob_cab, FwupdInstallFlags flags, GError **error) { XbNode *component = fu_install_task_get_component (task); FuPlugin *plugin; GBytes *blob_fw; const gchar *tmp = NULL; g_autofree gchar *release_key = NULL; g_autofree gchar *version_orig = NULL; g_autofree gchar *version_rel = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(FuDevice) device_tmp = NULL; g_autoptr(GBytes) blob_fw2 = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(XbNode) rel = NULL; #if LIBXMLB_CHECK_VERSION(0,2,0) g_autoptr(XbQuery) query = NULL; #endif g_return_val_if_fail (FU_IS_ENGINE (self), FALSE); g_return_val_if_fail (XB_IS_NODE (component), FALSE); g_return_val_if_fail (blob_cab != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* not in bootloader mode */ device = g_object_ref (fu_install_task_get_device (task)); if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER)) { const gchar *caption = NULL; caption = xb_node_query_text (component, "screenshots/screenshot/caption", NULL); if (caption != NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Device %s needs to manually be put in update mode: %s", fu_device_get_name (device), caption); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Device %s needs to manually be put in update mode", fu_device_get_name (device)); } return FALSE; } /* parse the DriverVer */ #if LIBXMLB_CHECK_VERSION(0,2,0) query = xb_query_new_full (xb_node_get_silo (component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, error); if (query == NULL) return FALSE; rel = xb_node_query_first_full (component, query, &error_local); #else rel = xb_node_query_first (component, "releases/release", &error_local); #endif if (rel == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "No releases in the firmware component: %s", error_local->message); return FALSE; } /* get the blob */ tmp = xb_node_query_attr (rel, "checksum[@target='content']", "filename", NULL); if (tmp == NULL) tmp = "firmware.bin"; /* not all devices have to use the same blob */ release_key = g_strdup_printf ("fwupd::ReleaseBlob(%s)", tmp); blob_fw = xb_node_get_data (rel, release_key); if (blob_fw == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "Failed to get firmware blob using %s", tmp); return FALSE; } /* use a bubblewrap helper script to build the firmware */ tmp = g_object_get_data (G_OBJECT (component), "fwupd::BuilderScript"); if (tmp != NULL) { const gchar *tmp2 = g_object_get_data (G_OBJECT (component), "fwupd::BuilderOutput"); if (tmp2 == NULL) tmp2 = "firmware.bin"; blob_fw2 = fu_common_firmware_builder (blob_fw, tmp, tmp2, error); if (blob_fw2 == NULL) return FALSE; } else { blob_fw2 = g_bytes_ref (blob_fw); } /* get the plugin */ plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), error); if (plugin == NULL) return FALSE; /* schedule this for the next reboot if not in system-update.target, * but first check if allowed on battery power */ version_rel = fu_engine_get_release_version (self, device, rel, error); if (version_rel == NULL) { g_prefix_error (error, "failed to get release version: "); return FALSE; } if ((flags & FWUPD_INSTALL_FLAG_OFFLINE) > 0 && !fu_engine_is_running_offline (self)) { g_autoptr(FwupdRelease) release_tmp = NULL; plugin = fu_plugin_list_find_by_name (self->plugin_list, "upower", NULL); if (plugin != NULL) { if (!fu_plugin_runner_update_prepare (plugin, flags, device, error)) return FALSE; } release_tmp = fu_engine_create_release_metadata (self, plugin, error); if (release_tmp == NULL) return FALSE; fwupd_release_set_version (release_tmp, version_rel); return fu_plugin_runner_schedule_update (plugin, device, release_tmp, blob_cab, flags, error); } /* add device to database */ if ((flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0) { g_autoptr(FwupdRelease) release_tmp = NULL; release_tmp = fu_engine_create_release_metadata (self, plugin, error); if (release_tmp == NULL) return FALSE; tmp = xb_node_query_text (component, "releases/release/checksum[@target='container']", NULL); if (tmp != NULL) fwupd_release_add_checksum (release_tmp, tmp); fwupd_release_set_version (release_tmp, version_rel); fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED); if (!fu_history_add_device (self->history, device, release_tmp, error)) return FALSE; } /* install firmware blob */ version_orig = g_strdup (fu_device_get_version (device)); if (!fu_engine_install_blob (self, device, blob_fw2, flags, &error_local)) { fu_device_set_status (device, FWUPD_STATUS_IDLE); if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_AC_POWER_REQUIRED) || g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW) || g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_BROKEN_SYSTEM)) { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED_TRANSIENT); } else { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED); } fu_device_set_update_error (device, error_local->message); if ((flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0 && !fu_history_modify_device (self->history, device, FU_HISTORY_FLAGS_MATCH_OLD_VERSION | FU_HISTORY_FLAGS_MATCH_NEW_VERSION, error)) { return FALSE; } g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } /* the device may have changed */ device_tmp = fu_device_list_get_by_id (self->device_list, fu_device_get_id (device), error); if (device_tmp == NULL) { g_prefix_error (error, "failed to get device after install: "); return FALSE; } g_set_object (&device, device_tmp); /* update database */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT) || fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_NEEDS_REBOOT); if ((flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0 && !fu_history_modify_device (self->history, device, FU_HISTORY_FLAGS_MATCH_OLD_VERSION | FU_HISTORY_FLAGS_MATCH_NEW_VERSION, error)) return FALSE; /* success */ return TRUE; } /* for online updates, verify the version changed if not a re-install */ if (version_rel != NULL && fu_common_vercmp (version_orig, version_rel) != 0 && fu_common_vercmp (version_orig, fu_device_get_version (device)) == 0) { g_autofree gchar *str = NULL; fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED); str = g_strdup_printf ("device version not updated on success, %s != %s", version_rel, fu_device_get_version (device)); fu_device_set_update_error (device, str); if ((flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0 && !fu_history_modify_device (self->history, device, FU_HISTORY_FLAGS_MATCH_OLD_VERSION, error)) return FALSE; /* success */ return TRUE; } /* ensure the new version matched what we expected */ if (version_rel != NULL && g_strcmp0 (fu_device_get_version (device), version_rel) != 0) { g_warning ("new device version '%s' was is not '%s', fixing up", fu_device_get_version (device), version_rel); fu_device_set_version (device, version_rel, fu_device_get_version_format (device)); } /* success */ fu_device_set_update_state (device, FWUPD_UPDATE_STATE_SUCCESS); if ((flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0 && !fu_history_modify_device (self->history, device, FU_HISTORY_FLAGS_MATCH_NEW_VERSION, error)) return FALSE; return TRUE; } /** * fu_engine_get_plugins: * @self: A #FuPluginList * * Gets all the plugins that have been added. * * Returns: (transfer none) (element-type FuPlugin): the plugins * * Since: 1.0.8 **/ GPtrArray * fu_engine_get_plugins (FuEngine *self) { g_return_val_if_fail (FU_IS_ENGINE (self), NULL); return fu_plugin_list_get_all (self->plugin_list); } static FuDevice * fu_engine_get_device_by_id (FuEngine *self, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device1 = NULL; g_autoptr(FuDevice) device2 = NULL; /* find device */ device1 = fu_device_list_get_by_id (self->device_list, device_id, error); if (device1 == NULL) return NULL; /* no replug required */ if (!fu_device_has_flag (device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) return g_steal_pointer (&device1); /* wait for device to disconnect and reconnect */ if (!fu_device_list_wait_for_replug (self->device_list, device1, error)) { g_prefix_error (error, "failed to wait for detach replug: "); return NULL; } /* get the new device */ device2 = fu_device_list_get_by_id (self->device_list, device_id, error); if (device2 == NULL) { g_prefix_error (error, "failed to get device after replug: "); return NULL; } /* success */ return g_steal_pointer (&device2); } static gboolean fu_engine_update_prepare (FuEngine *self, FwupdInstallFlags flags, const gchar *device_id, GError **error) { GPtrArray *plugins = fu_plugin_list_get_all (self->plugin_list); g_autoptr(FuDevice) device = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device_by_id (self, device_id, error); if (device == NULL) return FALSE; for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j); if (!fu_plugin_runner_update_prepare (plugin_tmp, flags, device, error)) return FALSE; } return TRUE; } static gboolean fu_engine_update_cleanup (FuEngine *self, FwupdInstallFlags flags, const gchar *device_id, GError **error) { GPtrArray *plugins = fu_plugin_list_get_all (self->plugin_list); g_autoptr(FuDevice) device = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device_by_id (self, device_id, error); if (device == NULL) return FALSE; for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j); if (!fu_plugin_runner_update_cleanup (plugin_tmp, flags, device, error)) return FALSE; } return TRUE; } static gboolean fu_engine_update_detach (FuEngine *self, const gchar *device_id, GError **error) { FuPlugin *plugin; g_autoptr(FuDevice) device = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device_by_id (self, device_id, error); if (device == NULL) return FALSE; plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), error); if (plugin == NULL) return FALSE; if (!fu_plugin_runner_update_detach (plugin, device, error)) return FALSE; return TRUE; } static gboolean fu_engine_update_attach (FuEngine *self, const gchar *device_id, GError **error) { FuPlugin *plugin; g_autoptr(FuDevice) device = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device_by_id (self, device_id, error); if (device == NULL) { g_prefix_error (error, "failed to get device after update: "); return FALSE; } plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), error); if (plugin == NULL) return FALSE; if (!fu_plugin_runner_update_attach (plugin, device, error)) return FALSE; return TRUE; } gboolean fu_engine_activate (FuEngine *self, const gchar *device_id, GError **error) { FuPlugin *plugin; g_autoptr(FuDevice) device = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* check the device exists */ device = fu_device_list_get_by_id (self->device_list, device_id, error); if (device == NULL) return FALSE; plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), error); if (plugin == NULL) return FALSE; g_debug ("Activating %s", fu_device_get_name (device)); if (!fu_plugin_runner_activate (plugin, device, error)) return FALSE; fu_engine_emit_device_changed (self, device); fu_engine_emit_changed (self); return TRUE; } static gboolean fu_engine_update_reload (FuEngine *self, const gchar *device_id, GError **error) { FuPlugin *plugin; g_autoptr(FuDevice) device = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device_by_id (self, device_id, error); if (device == NULL) { g_prefix_error (error, "failed to get device after update: "); return FALSE; } plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), error); if (plugin == NULL) return FALSE; if (!fu_plugin_runner_update_reload (plugin, device, error)) { g_prefix_error (error, "failed to reload device: "); return FALSE; } return TRUE; } static gboolean fu_engine_update (FuEngine *self, const gchar *device_id, GBytes *blob_fw2, FwupdInstallFlags flags, GError **error) { FuPlugin *plugin; g_autoptr(FuDevice) device = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device_by_id (self, device_id, error); if (device == NULL) { g_prefix_error (error, "failed to get device after detach: "); return FALSE; } plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), error); if (plugin == NULL) return FALSE; if (!fu_plugin_runner_update (plugin, device, blob_fw2, flags, error)) { g_autoptr(GError) error_attach = NULL; g_autoptr(GError) error_cleanup = NULL; /* attack back into runtime then cleanup */ if (!fu_plugin_runner_update_attach (plugin, device, &error_attach)) { g_warning ("failed to attach device after failed update: %s", error_attach->message); } if (!fu_engine_update_cleanup (self, flags, device_id, &error_cleanup)) { g_warning ("failed to update-cleanup after failed update: %s", error_cleanup->message); } return FALSE; } return TRUE; } gboolean fu_engine_install_blob (FuEngine *self, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { guint retries = 0; g_autofree gchar *device_id = NULL; g_autoptr(GTimer) timer = g_timer_new (); /* test the firmware is not an empty blob */ if (g_bytes_get_size (blob_fw) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Firmware is invalid as has zero size"); return FALSE; } /* mark this as modified even if we actually fail to do the update */ fu_device_set_modified (device, (guint64) g_get_real_time () / G_USEC_PER_SEC); /* plugins can set FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED to run again, but they * must return TRUE rather than an error */ device_id = g_strdup (fu_device_get_id (device)); do { /* check for a loop */ if (++retries > 5) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "aborting device write loop, limit 5"); return FALSE; } /* don't rely on a plugin clearing this */ fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); /* signal to all the plugins the update is about to happen */ if (!fu_engine_update_prepare (self, flags, device_id, error)) return FALSE; /* detach to bootloader mode */ if (!fu_engine_update_detach (self, device_id, error)) return FALSE; /* install */ if (!fu_engine_update (self, device_id, blob_fw, flags, error)) return FALSE; /* attach into runtime mode */ if (!fu_engine_update_attach (self, device_id, error)) return FALSE; } while (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED)); /* get the new version number */ if (!fu_engine_update_reload (self, device_id, error)) return FALSE; /* signal to all the plugins the update has happened */ if (!fu_engine_update_cleanup (self, flags, device_id, error)) return FALSE; /* make the UI update */ fu_engine_set_status (self, FWUPD_STATUS_IDLE); fu_engine_emit_changed (self); g_debug ("Updating %s took %f seconds", fu_device_get_name (device), g_timer_elapsed (timer, NULL)); return TRUE; } static FuDevice * fu_engine_get_item_by_id_fallback_history (FuEngine *self, const gchar *id, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* not a wildcard */ if (g_strcmp0 (id, FWUPD_DEVICE_ID_ANY) != 0) { g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* get this one device */ dev = fu_history_get_device_by_id (self->history, id, &error_local); if (dev == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Failed to find %s in history database: %s", id, error_local->message); return NULL; } /* only useful */ if (fu_device_get_update_state (dev) == FWUPD_UPDATE_STATE_SUCCESS || fu_device_get_update_state (dev) == FWUPD_UPDATE_STATE_FAILED_TRANSIENT || fu_device_get_update_state (dev) == FWUPD_UPDATE_STATE_FAILED) { return g_steal_pointer (&dev); } /* nothing in database */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Device %s has no results to report", fu_device_get_id (dev)); return NULL; } /* allow '*' for any */ devices = fu_history_get_devices (self->history, error); if (devices == NULL) return NULL; for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index (devices, i); if (fu_device_get_update_state (dev) == FWUPD_UPDATE_STATE_SUCCESS || fu_device_get_update_state (dev) == FWUPD_UPDATE_STATE_FAILED_TRANSIENT || fu_device_get_update_state (dev) == FWUPD_UPDATE_STATE_FAILED) return g_object_ref (dev); } g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Failed to find any useful results to report"); return NULL; } /* for the self tests */ void fu_engine_set_silo (FuEngine *self, XbSilo *silo) { g_return_if_fail (FU_IS_ENGINE (self)); g_return_if_fail (XB_IS_SILO (silo)); g_set_object (&self->silo, silo); } static gboolean fu_engine_is_device_supported (FuEngine *self, FuDevice *device) { g_autoptr(XbNode) component = NULL; /* sanity check */ if (self->silo == NULL) { g_critical ("FuEngine silo not set up"); return FALSE; } /* no device version */ if (fu_device_get_version (device) == NULL) return FALSE; /* match the GUIDs in the XML */ component = fu_engine_get_component_by_guids (self, device); if (component == NULL) return FALSE; /* success */ return TRUE; } static gboolean fu_engine_appstream_upgrade_cb (XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { if (g_strcmp0 (xb_builder_node_get_element (bn), "metadata") == 0) xb_builder_node_set_element (bn, "custom"); return TRUE; } static XbBuilderSource * fu_engine_create_metadata_builder_source (FuEngine *self, const gchar *fn, GError **error) { g_autoptr(GBytes) blob = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new (); g_autofree gchar *xml = NULL; g_debug ("building metadata for %s", fn); blob = fu_common_get_contents_bytes (fn, error); if (blob == NULL) return NULL; /* convert the silo for the CAB into a XbBuilderSource */ silo = fu_engine_get_silo_from_blob (self, blob, error); if (silo == NULL) return NULL; xml = xb_silo_export (silo, XB_NODE_EXPORT_FLAG_NONE, error); if (xml == NULL) return NULL; if (!xb_builder_source_load_xml (source, xml, XB_BUILDER_SOURCE_FLAG_NONE, error)) return NULL; return g_steal_pointer (&source); } static gboolean fu_engine_create_metadata (FuEngine *self, XbBuilder *builder, FwupdRemote *remote, GError **error) { g_autoptr(GPtrArray) files = NULL; const gchar *path; /* find all files in directory */ path = fwupd_remote_get_filename_cache (remote); files = fu_common_get_files_recursive (path, error); if (files == NULL) return FALSE; /* add each source */ for (guint i = 0; i < files->len; i++) { g_autoptr(XbBuilderNode) custom = NULL; g_autoptr(XbBuilderSource) source = NULL; g_autoptr(GError) error_local = NULL; const gchar *fn = g_ptr_array_index (files, i); g_autofree gchar *fn_lowercase = g_ascii_strdown (fn, -1); /* check is cab file */ if (!g_str_has_suffix (fn_lowercase, ".cab")) { g_debug ("ignoring: %s", fn); continue; } /* build source for file */ source = fu_engine_create_metadata_builder_source (self, fn, &error_local); if (source == NULL) { g_warning ("%s", error_local->message); continue; } /* add metadata */ custom = xb_builder_node_new ("custom"); xb_builder_node_insert_text (custom, "value", fn, "key", "fwupd::FilenameCache", NULL); xb_builder_node_insert_text (custom, "value", fwupd_remote_get_id (remote), "key", "fwupd::RemoteId", NULL); xb_builder_source_set_info (source, custom); xb_builder_import_source (builder, source); } return TRUE; } static gboolean fu_engine_load_metadata_store (FuEngine *self, FuEngineLoadFlags flags, GError **error) { GPtrArray *remotes; XbBuilderCompileFlags compile_flags = XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID; g_autofree gchar *cachedirpkg = NULL; g_autofree gchar *xmlbfn = NULL; g_autoptr(GFile) xmlb = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(XbBuilder) builder = xb_builder_new (); /* clear existing silo */ g_clear_object (&self->silo); /* verbose profiling */ if (g_getenv ("FWUPD_VERBOSE") != NULL) { xb_builder_set_profile_flags (builder, XB_SILO_PROFILE_FLAG_XPATH | XB_SILO_PROFILE_FLAG_DEBUG); } /* load each enabled metadata file */ remotes = fu_config_get_remotes (self->config); for (guint i = 0; i < remotes->len; i++) { const gchar *path = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilderFixup) fixup = NULL; g_autoptr(XbBuilderNode) custom = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new (); FwupdRemote *remote = g_ptr_array_index (remotes, i); if (!fwupd_remote_get_enabled (remote)) { g_debug ("remote %s not enabled, so skipping", fwupd_remote_get_id (remote)); continue; } path = fwupd_remote_get_filename_cache (remote); if (!g_file_test (path, G_FILE_TEST_EXISTS)) { g_debug ("no %s, so skipping", path); continue; } /* generate all metadata on demand */ if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_DIRECTORY) { g_debug ("building metadata for remote '%s'", fwupd_remote_get_id (remote)); if (!fu_engine_create_metadata (self, builder, remote, &error_local)) { g_warning ("failed to generate remote %s: %s", fwupd_remote_get_id (remote), error_local->message); } continue; } /* save the remote-id in the custom metadata space */ file = g_file_new_for_path (path); if (!xb_builder_source_load_file (source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error_local)) { g_warning ("failed to load remote %s: %s", fwupd_remote_get_id (remote), error_local->message); continue; } /* fix up any legacy installed files */ fixup = xb_builder_fixup_new ("AppStreamUpgrade", fu_engine_appstream_upgrade_cb, self, NULL); xb_builder_fixup_set_max_depth (fixup, 3); xb_builder_source_add_fixup (source, fixup); /* add metadata */ custom = xb_builder_node_new ("custom"); xb_builder_node_insert_text (custom, "value", path, "key", "fwupd::FilenameCache", NULL); xb_builder_node_insert_text (custom, "value", fwupd_remote_get_id (remote), "key", "fwupd::RemoteId", NULL); xb_builder_source_set_info (source, custom); /* we need to watch for changes? */ xb_builder_import_source (builder, source); } #if LIBXMLB_CHECK_VERSION(0,1,7) /* on a read-only filesystem don't care about the cache GUID */ if (flags & FU_ENGINE_LOAD_FLAG_READONLY_FS) compile_flags |= XB_BUILDER_COMPILE_FLAG_IGNORE_GUID; #endif /* ensure silo is up to date */ cachedirpkg = fu_common_get_path (FU_PATH_KIND_CACHEDIR_PKG); xmlbfn = g_build_filename (cachedirpkg, "metadata.xmlb", NULL); xmlb = g_file_new_for_path (xmlbfn); self->silo = xb_builder_ensure (builder, xmlb, compile_flags, NULL, error); if (self->silo == NULL) return FALSE; /* print what we've got */ components = xb_silo_query (self->silo, "components/component", 0, NULL); if (components != NULL) g_debug ("%u components now in silo", components->len); /* build the index */ if (!xb_silo_query_build_index (self->silo, "components/component/provides/firmware", "type", error)) return FALSE; if (!xb_silo_query_build_index (self->silo, "components/component/provides/firmware", NULL, error)) return FALSE; /* did any devices SUPPORTED state change? */ devices = fu_device_list_get_all (self->device_list); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_SUPPORTED)) { if (!fu_engine_is_device_supported (self, device)) { /* was supported, now unsupported */ fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_SUPPORTED); fu_engine_emit_device_changed (self, device); } } else { /* was unsupported, now supported */ if (fu_engine_is_device_supported (self, device)) { fu_device_add_flag (device, FWUPD_DEVICE_FLAG_SUPPORTED); fu_engine_emit_device_changed (self, device); } } } return TRUE; } static void fu_engine_config_changed_cb (FuConfig *config, FuEngine *self) { g_autoptr(GError) error_local = NULL; if (!fu_engine_load_metadata_store (self, FU_ENGINE_LOAD_FLAG_NONE, &error_local)) g_warning ("Failed to reload metadata store: %s", error_local->message); } static FuKeyringResult * fu_engine_get_existing_keyring_result (FuEngine *self, FuKeyring *kr, FwupdRemote *remote, GError **error) { g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) blob_sig = NULL; blob = fu_common_get_contents_bytes (fwupd_remote_get_filename_cache (remote), error); if (blob == NULL) return NULL; blob_sig = fu_common_get_contents_bytes (fwupd_remote_get_filename_cache_sig (remote), error); if (blob_sig == NULL) return NULL; return fu_keyring_verify_data (kr, blob, blob_sig, FU_KEYRING_VERIFY_FLAG_NONE, error); } /** * fu_engine_update_metadata: * @self: A #FuEngine * @remote_id: A remote ID, e.g. `lvfs` * @fd: file descriptor of the metadata * @fd_sig: file descriptor of the metadata signature * @error: A #GError, or %NULL * * Updates the metadata for a specific remote. * * Note: this will close the fds when done * * Returns: %TRUE for success **/ gboolean fu_engine_update_metadata (FuEngine *self, const gchar *remote_id, gint fd, gint fd_sig, GError **error) { FwupdKeyringKind keyring_kind; FwupdRemote *remote; g_autoptr(GBytes) bytes_raw = NULL; g_autoptr(GBytes) bytes_sig = NULL; g_autoptr(GInputStream) stream_fd = NULL; g_autoptr(GInputStream) stream_sig = NULL; g_autofree gchar *pki_dir = NULL; g_autofree gchar *sysconfdir = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), FALSE); g_return_val_if_fail (remote_id != NULL, FALSE); g_return_val_if_fail (fd > 0, FALSE); g_return_val_if_fail (fd_sig > 0, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* ensures the fd's are closed on error */ stream_fd = g_unix_input_stream_new (fd, TRUE); stream_sig = g_unix_input_stream_new (fd_sig, TRUE); /* check remote is valid */ remote = fu_config_get_remote_by_id (self->config, remote_id); if (remote == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "remote %s not found", remote_id); return FALSE; } if (!fwupd_remote_get_enabled (remote)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "remote %s not enabled", remote_id); return FALSE; } /* read the entire file into memory */ bytes_raw = g_input_stream_read_bytes (stream_fd, 0x100000, NULL, error); if (bytes_raw == NULL) return FALSE; /* read signature */ bytes_sig = g_input_stream_read_bytes (stream_sig, 0x100000, NULL, error); if (bytes_sig == NULL) return FALSE; /* verify file */ keyring_kind = fwupd_remote_get_keyring_kind (remote); if (keyring_kind != FWUPD_KEYRING_KIND_NONE) { g_autoptr(FuKeyring) kr = NULL; g_autoptr(FuKeyringResult) kr_result = NULL; g_autoptr(FuKeyringResult) kr_result_old = NULL; g_autoptr(GError) error_local = NULL; kr = fu_keyring_create_for_kind (keyring_kind, error); if (kr == NULL) return FALSE; if (!fu_keyring_setup (kr, error)) return FALSE; sysconfdir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR); pki_dir = g_build_filename (sysconfdir, "pki", "fwupd-metadata", NULL); if (!fu_keyring_add_public_keys (kr, pki_dir, error)) return FALSE; kr_result = fu_keyring_verify_data (kr, bytes_raw, bytes_sig, FU_KEYRING_VERIFY_FLAG_NONE, error); if (kr_result == NULL) return FALSE; /* verify the metadata was signed later than the existing * metadata for this remote to mitigate a rollback attack */ kr_result_old = fu_engine_get_existing_keyring_result (self, kr, remote, &error_local); if (kr_result_old == NULL) { if (g_error_matches (error_local, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { g_debug ("no existing valid keyrings: %s", error_local->message); } else { g_warning ("could not get existing keyring result: %s", error_local->message); } } else { gint64 delta = 0; if (fu_keyring_result_get_timestamp (kr_result) > 0 && fu_keyring_result_get_timestamp (kr_result_old) > 0) { delta = fu_keyring_result_get_timestamp (kr_result) - fu_keyring_result_get_timestamp (kr_result_old); } if (delta < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "new signing timestamp was %" G_GINT64_FORMAT " seconds older", -delta); return FALSE; } else if (delta > 0) { g_debug ("timestamp increased, so no rollback"); } } } /* save XML and signature to remotes.d */ if (!fu_common_set_contents_bytes (fwupd_remote_get_filename_cache (remote), bytes_raw, error)) return FALSE; if (keyring_kind != FWUPD_KEYRING_KIND_NONE) { if (!fu_common_set_contents_bytes (fwupd_remote_get_filename_cache_sig (remote), bytes_sig, error)) return FALSE; } return fu_engine_load_metadata_store (self, FU_ENGINE_LOAD_FLAG_NONE, error); } /** * fu_engine_get_silo_from_blob: * @self: A #FuEngine * @blob_cab: A #GBytes * @error: A #GError, or %NULL * * Creates a silo from a .cab file blob. * * Returns: (transfer container): a #XbSilo, or %NULL **/ XbSilo * fu_engine_get_silo_from_blob (FuEngine *self, GBytes *blob_cab, GError **error) { g_autoptr(XbSilo) silo = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), NULL); g_return_val_if_fail (blob_cab != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* load file */ fu_engine_set_status (self, FWUPD_STATUS_DECOMPRESSING); silo = fu_common_cab_build_silo (blob_cab, fu_engine_get_archive_size_max (self), error); if (silo == NULL) return NULL; fu_engine_set_status (self, FWUPD_STATUS_IDLE); return g_steal_pointer (&silo); } static FuDevice * fu_engine_get_result_from_component (FuEngine *self, XbNode *component, GError **error) { FwupdReleaseFlags release_flags = FWUPD_RELEASE_FLAG_NONE; g_autoptr(FuInstallTask) task = NULL; g_autoptr(FuDevice) dev = NULL; g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) provides = NULL; g_autoptr(XbNode) description = NULL; g_autoptr(XbNode) release = NULL; #if LIBXMLB_CHECK_VERSION(0,2,0) g_autoptr(XbQuery) query = NULL; #endif dev = fu_device_new (); provides = xb_node_query (component, "provides/firmware[@type=$'flashed']", 0, &error_local); if (provides == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get release: %s", error_local->message); return NULL; } for (guint i = 0; i < provides->len; i++) { XbNode *prov = XB_NODE (g_ptr_array_index (provides, i)); const gchar *guid; g_autoptr(FuDevice) device = NULL; /* is a online or offline update appropriate */ guid = xb_node_get_text (prov); if (guid == NULL) continue; device = fu_device_list_get_by_guid (self->device_list, guid, NULL); if (device != NULL) { fu_device_set_name (dev, fu_device_get_name (device)); fu_device_set_flags (dev, fu_device_get_flags (device)); fu_device_set_id (dev, fu_device_get_id (device)); } /* add GUID */ fu_device_add_guid (dev, guid); } if (fu_device_get_guids(dev)->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "component has no GUIDs"); return NULL; } /* get (or guess) the component version format */ if (!fu_engine_set_device_version_format (self, dev, component, error)) return NULL; /* check we can install it */ task = fu_install_task_new (NULL, component); if (!fu_engine_check_requirements (self, task, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; /* verify trust */ #if LIBXMLB_CHECK_VERSION(0,2,0) query = xb_query_new_full (xb_node_get_silo (component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, error); if (query == NULL) return FALSE; release = xb_node_query_first_full (component, query, &error_local); #else release = xb_node_query_first (component, "releases/release", &error_local); #endif if (release == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get release: %s", error_local->message); return NULL; } if (!fu_keyring_get_release_flags (release, &release_flags, &error_local)) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_warning ("Ignoring verification: %s", error_local->message); } else { g_propagate_error (error, g_steal_pointer (&error_local)); return NULL; } } /* create a result with all the metadata in */ description = xb_node_query_first (component, "description", NULL); if (description != NULL) { g_autofree gchar *xml = NULL; xml = xb_node_export (description, XB_NODE_EXPORT_FLAG_ONLY_CHILDREN, NULL); if (xml != NULL) fu_device_set_description (dev, xml); } rel = fwupd_release_new (); fwupd_release_set_flags (rel, release_flags); if (!fu_engine_set_release_from_appstream (self, dev, rel, component, release, error)) return NULL; fu_device_add_release (dev, rel); return g_steal_pointer (&dev); } /** * fu_engine_get_details: * @self: A #FuEngine * @fd: A file descriptor * @error: A #GError, or %NULL * * Gets the details about a local file. * * Note: this will close the fd when done * * Returns: (transfer container) (element-type FuDevice): results **/ GPtrArray * fu_engine_get_details (FuEngine *self, gint fd, GError **error) { const gchar *remote_id; g_autofree gchar *csum = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) details = NULL; g_autoptr(XbSilo) silo = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), NULL); g_return_val_if_fail (fd > 0, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* get all components */ blob = fu_common_get_contents_fd (fd, fu_engine_get_archive_size_max (self), error); if (blob == NULL) return NULL; silo = fu_engine_get_silo_from_blob (self, blob, error); if (silo == NULL) return NULL; components = xb_silo_query (silo, "components/component", 0, &error_local); if (components == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no components: %s", error_local->message); return NULL; } /* build the index */ if (!xb_silo_query_build_index (silo, "components/component/provides/firmware", "type", error)) return NULL; if (!xb_silo_query_build_index (silo, "components/component/provides/firmware", NULL, error)) return NULL; /* does this exist in any enabled remote */ csum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, blob); remote_id = fu_engine_get_remote_id_for_checksum (self, csum); /* create results with all the metadata in */ details = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < components->len; i++) { XbNode *component = g_ptr_array_index (components, i); FuDevice *dev; dev = fu_engine_get_result_from_component (self, component, error); if (dev == NULL) return NULL; if (remote_id != NULL) { FwupdRelease *rel = fu_device_get_release_default (dev); fwupd_release_set_remote_id (rel, remote_id); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED); } g_ptr_array_add (details, dev); } return g_steal_pointer (&details); } static gint fu_engine_sort_devices_by_priority (gconstpointer a, gconstpointer b) { FuDevice *dev_a = *((FuDevice **) a); FuDevice *dev_b = *((FuDevice **) b); gint prio_a = fu_device_get_priority (dev_a); gint prio_b = fu_device_get_priority (dev_b); if (prio_a > prio_b) return -1; if (prio_a < prio_b) return 1; return 0; } /** * fu_engine_get_devices: * @self: A #FuEngine * @error: A #GError, or %NULL * * Gets the list of devices. * * Returns: (transfer container) (element-type FwupdDevice): results **/ GPtrArray * fu_engine_get_devices (FuEngine *self, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); devices = fu_device_list_get_active (self->device_list); if (devices->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No detected devices"); return NULL; } g_ptr_array_sort (devices, fu_engine_sort_devices_by_priority); return g_steal_pointer (&devices); } /** * fu_engine_get_device: * @self: A #FuEngine * @device_id: A device ID * @error: A #GError, or %NULL * * Gets a specific device. * * Returns: (transfer full): a device, or %NULL if not found **/ FuDevice * fu_engine_get_device (FuEngine *self, const gchar *device_id, GError **error) { FuDevice *device; device = fu_device_list_get_by_id (self->device_list, device_id, error); if (device == NULL) return NULL; return device; } /** * fu_engine_get_history: * @self: A #FuEngine * @error: A #GError, or %NULL * * Gets the list of history. * * Returns: (transfer container) (element-type FwupdDevice): results **/ GPtrArray * fu_engine_get_history (FuEngine *self, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); devices = fu_history_get_devices (self->history, error); if (devices == NULL) return NULL; if (devices->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No history"); return NULL; } /* try to set the remote ID for each device */ for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index (devices, i); FwupdRelease *rel; GPtrArray *csums; /* get the checksums */ rel = fu_device_get_release_default (dev); if (rel == NULL) continue; /* find the checksum that matches */ csums = fwupd_release_get_checksums (rel); for (guint j = 0; j < csums->len; j++) { const gchar *csum = g_ptr_array_index (csums, j); const gchar *remote_id = fu_engine_get_remote_id_for_checksum (self, csum); if (remote_id != NULL) { fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED); fwupd_release_set_remote_id (rel, remote_id); break; } } } return g_steal_pointer (&devices); } /** * fu_engine_get_remotes: * @self: A #FuEngine * @error: A #GError, or %NULL * * Gets the list of remotes in use by the engine. * * Returns: (transfer container) (element-type FwupdRemote): results **/ GPtrArray * fu_engine_get_remotes (FuEngine *self, GError **error) { GPtrArray *remotes; g_return_val_if_fail (FU_IS_ENGINE (self), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); remotes = fu_config_get_remotes (self->config); if (remotes->len == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "No remotes configured"); return NULL; } return g_ptr_array_ref (remotes); } /** * fu_engine_get_remote_by_id: * @self: A #FuEngine * @remote_id: A string representation of a remote * @error: A #GError, or %NULL * * Gets the FwupdRemote object. * * Returns: FwupdRemote **/ FwupdRemote * fu_engine_get_remote_by_id (FuEngine *self, const gchar *remote_id, GError **error) { g_autoptr(GPtrArray) remotes = NULL; remotes = fu_engine_get_remotes (self, error); if (remotes == NULL) return NULL; for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); if (g_strcmp0 (remote_id, fwupd_remote_get_id (remote)) == 0) return remote; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Couldn't find remote %s", remote_id); return NULL; } static gint fu_engine_sort_releases_cb (gconstpointer a, gconstpointer b) { FwupdRelease *rel_a = FWUPD_RELEASE (*((FwupdRelease **) a)); FwupdRelease *rel_b = FWUPD_RELEASE (*((FwupdRelease **) b)); return fu_common_vercmp (fwupd_release_get_version (rel_b), fwupd_release_get_version (rel_a)); } static gboolean fu_engine_check_release_is_approved (FuEngine *self, FwupdRelease *rel) { GPtrArray *csums = fwupd_release_get_checksums (rel); for (guint i = 0; i < csums->len; i++) { const gchar *csum = g_ptr_array_index (csums, i); g_debug ("checking %s against approved list", csum); if (g_hash_table_lookup (self->approved_firmware, csum) != NULL) return TRUE; } return FALSE; } static gboolean fu_engine_add_releases_for_device_component (FuEngine *self, FuDevice *device, XbNode *component, GPtrArray *releases, GError **error) { g_autoptr(GError) error_local = NULL; g_autoptr(FuInstallTask) task = fu_install_task_new (device, component); g_autoptr(GPtrArray) releases_tmp = NULL; if (!fu_engine_check_requirements (self, task, FWUPD_INSTALL_FLAG_OFFLINE | FWUPD_INSTALL_FLAG_ALLOW_REINSTALL | FWUPD_INSTALL_FLAG_ALLOW_OLDER, error)) return FALSE; /* get all releases */ releases_tmp = xb_node_query (component, "releases/release", 0, &error_local); if (releases_tmp == NULL) { if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return TRUE; if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return TRUE; g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } for (guint i = 0; i < releases_tmp->len; i++) { XbNode *release = g_ptr_array_index (releases_tmp, i); const gchar *remote_id; const gchar *update_message; gint vercmp; GPtrArray *checksums; g_autoptr(FwupdRelease) rel = fwupd_release_new (); g_autoptr(GError) error_loop = NULL; /* create new FwupdRelease for the XbNode */ if (!fu_engine_set_release_from_appstream (self, device, rel, component, release, &error_loop)) { g_warning ("failed to set release for component: %s", error_loop->message); continue; } /* fall back to quirk-provided value */ if (fwupd_release_get_install_duration (rel) == 0) fwupd_release_set_install_duration (rel, fu_device_get_install_duration (device)); /* invalid */ if (fwupd_release_get_uri (rel) == NULL) continue; checksums = fwupd_release_get_checksums (rel); if (checksums->len == 0) continue; /* test for upgrade or downgrade */ vercmp = fu_common_vercmp (fwupd_release_get_version (rel), fu_device_get_version (device)); if (vercmp > 0) fwupd_release_add_flag (rel, FWUPD_RELEASE_FLAG_IS_UPGRADE); else if (vercmp < 0) fwupd_release_add_flag (rel, FWUPD_RELEASE_FLAG_IS_DOWNGRADE); /* lower than allowed to downgrade to */ if (fu_device_get_version_lowest (device) != NULL && fu_common_vercmp (fwupd_release_get_version (rel), fu_device_get_version_lowest (device)) < 0) { fwupd_release_add_flag (rel, FWUPD_RELEASE_FLAG_BLOCKED_VERSION); } /* check if remote is whitelisting firmware */ remote_id = fwupd_release_get_remote_id (rel); if (remote_id != NULL) { FwupdRemote *remote = fu_engine_get_remote_by_id (self, remote_id, NULL); if (remote != NULL && fwupd_remote_get_approval_required (remote) && !fu_engine_check_release_is_approved (self, rel)) { fwupd_release_add_flag (rel, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL); } } /* add update message if exists but device doesn't already have one */ update_message = fwupd_release_get_update_message (rel); if (fwupd_device_get_update_message (FWUPD_DEVICE (device)) == NULL && update_message != NULL) { fwupd_device_set_update_message (FWUPD_DEVICE (device), update_message); } /* success */ g_ptr_array_add (releases, g_steal_pointer (&rel)); } /* success */ return TRUE; } static GPtrArray * fu_engine_get_releases_for_device (FuEngine *self, FuDevice *device, GError **error) { GPtrArray *device_guids; GPtrArray *releases; const gchar *version; g_autoptr(GError) error_all = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(GString) xpath = g_string_new (NULL); /* get device version */ version = fu_device_get_version (device); if (version == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no version set"); return NULL; } /* only show devices that can be updated */ if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ignoring %s [%s] as not updatable", fu_device_get_name (device), fu_device_get_id (device)); return NULL; } /* get all the components that provide any of these GUIDs */ device_guids = fu_device_get_guids (device); for (guint i = 0; i < device_guids->len; i++) { const gchar *guid = g_ptr_array_index (device_guids, i); xb_string_append_union (xpath, "components/component/" "provides/firmware[@type=$'flashed'][text()=$'%s']/" "../..", guid); } components = xb_silo_query (self->silo, xpath->str, 0, &error_local); if (components == NULL) { if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No releases for %s", fu_device_get_name (device)); return NULL; } g_propagate_error (error, g_steal_pointer (&error_local)); return NULL; } /* find all the releases that pass all the requirements */ releases = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < components->len; i++) { XbNode *component = XB_NODE (g_ptr_array_index (components, i)); g_autoptr(GError) error_tmp = NULL; if (!fu_engine_add_releases_for_device_component (self, device, component, releases, &error_tmp)) { if (error_all == NULL) { error_all = g_steal_pointer (&error_tmp); continue; } /* assume the domain and code is the same */ g_prefix_error (&error_all, "%s, ", error_tmp->message); } } /* return the compound error */ if (releases->len == 0) { if (error_all != NULL) { g_propagate_prefixed_error (error, g_steal_pointer (&error_all), "No releases found for device: "); return NULL; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No releases found for device"); return NULL; } return releases; } /** * fu_engine_get_releases: * @self: A #FuEngine * @device_id: A device ID * @error: A #GError, or %NULL * * Gets the releases available for a specific device. * * Returns: (transfer container) (element-type FwupdDevice): results **/ GPtrArray * fu_engine_get_releases (FuEngine *self, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GPtrArray) releases = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* find the device */ device = fu_device_list_get_by_id (self->device_list, device_id, error); if (device == NULL) return NULL; /* get all the releases for the device */ releases = fu_engine_get_releases_for_device (self, device, error); if (releases == NULL) return NULL; if (releases->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No releases for device"); return NULL; } g_ptr_array_sort (releases, fu_engine_sort_releases_cb); return g_steal_pointer (&releases); } /** * fu_engine_get_downgrades: * @self: A #FuEngine * @device_id: A device ID * @error: A #GError, or %NULL * * Gets the downgrades available for a specific device. * * Returns: (transfer container) (element-type FwupdDevice): results **/ GPtrArray * fu_engine_get_downgrades (FuEngine *self, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(GPtrArray) releases_tmp = NULL; g_autoptr(GString) error_str = g_string_new (NULL); g_return_val_if_fail (FU_IS_ENGINE (self), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* find the device */ device = fu_device_list_get_by_id (self->device_list, device_id, error); if (device == NULL) return NULL; /* get all the releases for the device */ releases_tmp = fu_engine_get_releases_for_device (self, device, error); if (releases_tmp == NULL) return NULL; releases = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < releases_tmp->len; i++) { FwupdRelease *rel_tmp = g_ptr_array_index (releases_tmp, i); /* same as installed */ if (!fwupd_release_has_flag (rel_tmp, FWUPD_RELEASE_FLAG_IS_UPGRADE) && !fwupd_release_has_flag (rel_tmp, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { g_string_append_printf (error_str, "%s=same, ", fwupd_release_get_version (rel_tmp)); g_debug ("ignoring %s as the same as %s", fwupd_release_get_version (rel_tmp), fu_device_get_version (device)); continue; } /* newer than current */ if (fwupd_release_has_flag (rel_tmp, FWUPD_RELEASE_FLAG_IS_UPGRADE)) { g_string_append_printf (error_str, "%s=newer, ", fwupd_release_get_version (rel_tmp)); g_debug ("ignoring %s as newer than %s", fwupd_release_get_version (rel_tmp), fu_device_get_version (device)); continue; } /* don't show releases we are not allowed to downgrade to */ if (fwupd_release_has_flag (rel_tmp, FWUPD_RELEASE_FLAG_BLOCKED_VERSION)) { g_string_append_printf (error_str, "%s=lowest, ", fwupd_release_get_version (rel_tmp)); g_debug ("ignoring %s as older than lowest %s", fwupd_release_get_version (rel_tmp), fu_device_get_version_lowest (device)); continue; } g_ptr_array_add (releases, g_object_ref (rel_tmp)); } if (error_str->len > 2) g_string_truncate (error_str, error_str->len - 2); if (releases->len == 0) { if (error_str->len > 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No downgrades for device, current is %s: %s", fu_device_get_version (device), error_str->str); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No downgrades for device, current is %s", fu_device_get_version (device)); } return NULL; } g_ptr_array_sort (releases, fu_engine_sort_releases_cb); return g_steal_pointer (&releases); } GPtrArray * fu_engine_get_approved_firmware (FuEngine *self) { GPtrArray *checksums = g_ptr_array_new_with_free_func (g_free); g_autoptr(GList) keys = g_hash_table_get_keys (self->approved_firmware); for (GList *l = keys; l != NULL; l = l->next) { const gchar *csum = l->data; g_ptr_array_add (checksums, g_strdup (csum)); } return checksums; } void fu_engine_add_approved_firmware (FuEngine *self, const gchar *checksum) { g_hash_table_add (self->approved_firmware, g_strdup (checksum)); } gchar * fu_engine_self_sign (FuEngine *self, const gchar *value, FuKeyringSignFlags flags, GError **error) { g_autoptr(FuKeyring) kr = NULL; g_autoptr(FuKeyringResult) kr_result = NULL; g_autoptr(GBytes) payload = NULL; g_autoptr(GBytes) signature = NULL; /* create detached signature and verify */ kr = fu_keyring_create_for_kind (FWUPD_KEYRING_KIND_PKCS7, error); if (kr == NULL) return NULL; if (!fu_keyring_setup (kr, error)) return NULL; payload = g_bytes_new (value, strlen (value)); signature = fu_keyring_sign_data (kr, payload, flags, error); if (signature == NULL) return NULL; kr_result = fu_keyring_verify_data (kr, payload, signature, FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT, error); if (kr_result == NULL) return NULL; return g_strndup (g_bytes_get_data (signature, NULL), g_bytes_get_size (signature)); } /** * fu_engine_get_upgrades: * @self: A #FuEngine * @device_id: A device ID * @error: A #GError, or %NULL * * Gets the upgrades available for a specific device. * * Returns: (transfer container) (element-type FwupdDevice): results **/ GPtrArray * fu_engine_get_upgrades (FuEngine *self, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(GPtrArray) releases_tmp = NULL; g_autoptr(GString) error_str = g_string_new (NULL); g_return_val_if_fail (FU_IS_ENGINE (self), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* find the device */ device = fu_device_list_get_by_id (self->device_list, device_id, error); if (device == NULL) return NULL; /* don't show upgrades again until we reboot */ if (fu_device_get_update_state (device) == FWUPD_UPDATE_STATE_NEEDS_REBOOT) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No upgrades for %s: A reboot is pending", fu_device_get_name (device)); return NULL; } /* get all the releases for the device */ releases_tmp = fu_engine_get_releases_for_device (self, device, error); if (releases_tmp == NULL) return NULL; releases = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < releases_tmp->len; i++) { FwupdRelease *rel_tmp = g_ptr_array_index (releases_tmp, i); /* same as installed */ if (!fwupd_release_has_flag (rel_tmp, FWUPD_RELEASE_FLAG_IS_UPGRADE) && !fwupd_release_has_flag (rel_tmp, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { g_string_append_printf (error_str, "%s=same, ", fwupd_release_get_version (rel_tmp)); g_debug ("ignoring %s as the same as %s", fwupd_release_get_version (rel_tmp), fu_device_get_version (device)); continue; } /* older than current */ if (fwupd_release_has_flag (rel_tmp, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { g_string_append_printf (error_str, "%s=older, ", fwupd_release_get_version (rel_tmp)); g_debug ("ignoring %s as older than %s", fwupd_release_get_version (rel_tmp), fu_device_get_version (device)); continue; } /* not approved */ if (fwupd_release_has_flag (rel_tmp, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL)) { g_string_append_printf (error_str, "%s=not-approved, ", fwupd_release_get_version (rel_tmp)); g_debug ("ignoring %s as not approved as required by %s", fwupd_release_get_version (rel_tmp), fwupd_release_get_remote_id (rel_tmp)); continue; } g_ptr_array_add (releases, g_object_ref (rel_tmp)); } if (error_str->len > 2) g_string_truncate (error_str, error_str->len - 2); if (releases->len == 0) { if (error_str->len > 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No upgrades for %s, current is %s: %s", fu_device_get_name (device), fu_device_get_version (device), error_str->str); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No upgrades for %s, current is %s", fu_device_get_name (device), fu_device_get_version (device)); } return NULL; } g_ptr_array_sort (releases, fu_engine_sort_releases_cb); return g_steal_pointer (&releases); } /** * fu_engine_clear_results: * @self: A #FuEngine * @device_id: A device ID * @error: A #GError, or %NULL * * Clear the historical state of a specific device operation. * * Returns: %TRUE for success **/ gboolean fu_engine_clear_results (FuEngine *self, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device = NULL; FuPlugin *plugin; g_return_val_if_fail (FU_IS_ENGINE (self), FALSE); g_return_val_if_fail (device_id != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* find the device */ device = fu_engine_get_item_by_id_fallback_history (self, device_id, error); if (device == NULL) return FALSE; /* already set on the database */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NOTIFIED)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device already has notified flag"); return FALSE; } /* call into the plugin if it still exists */ plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), error); if (plugin != NULL) { if (!fu_plugin_runner_clear_results (plugin, device, error)) return FALSE; } /* override */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NOTIFIED); return fu_history_modify_device (self->history, device, FU_HISTORY_FLAGS_MATCH_OLD_VERSION | FU_HISTORY_FLAGS_MATCH_NEW_VERSION, error); } /** * fu_engine_get_results: * @self: A #FuEngine * @device_id: A device ID * @error: A #GError, or %NULL * * Gets the historical state of a specific device operation. * * Returns: (transfer container): a #FwupdDevice, or %NULL **/ FwupdDevice * fu_engine_get_results (FuEngine *self, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* find the device */ device = fu_engine_get_item_by_id_fallback_history (self, device_id, error); if (device == NULL) return NULL; /* the notification has already been shown to the user */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NOTIFIED)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "User has already been notified about %s [%s]", fu_device_get_name (device), fu_device_get_id (device)); return NULL; } /* success */ return g_object_ref (FWUPD_DEVICE (device)); } static void fu_engine_plugins_setup (FuEngine *self) { GPtrArray *plugins = fu_plugin_list_get_all (self->plugin_list); for (guint i = 0; i < plugins->len; i++) { g_autoptr(GError) error = NULL; FuPlugin *plugin = g_ptr_array_index (plugins, i); if (!fu_plugin_runner_startup (plugin, &error)) { fu_plugin_set_enabled (plugin, FALSE); g_message ("disabling plugin because: %s", error->message); } } } static void fu_engine_plugins_coldplug (FuEngine *self, gboolean is_recoldplug) { GPtrArray *plugins; g_autoptr(GString) str = g_string_new (NULL); /* don't allow coldplug to be scheduled when in coldplug */ self->coldplug_running = TRUE; /* prepare */ plugins = fu_plugin_list_get_all (self->plugin_list); for (guint i = 0; i < plugins->len; i++) { g_autoptr(GError) error = NULL; FuPlugin *plugin = g_ptr_array_index (plugins, i); if (!fu_plugin_runner_coldplug_prepare (plugin, &error)) g_warning ("failed to prepare coldplug: %s", error->message); } /* do this in one place */ if (self->coldplug_delay > 0) { g_debug ("sleeping for %ums", self->coldplug_delay); g_usleep (self->coldplug_delay * 1000); } /* exec */ for (guint i = 0; i < plugins->len; i++) { g_autoptr(GError) error = NULL; FuPlugin *plugin = g_ptr_array_index (plugins, i); if (is_recoldplug) { if (!fu_plugin_runner_recoldplug (plugin, &error)) g_message ("failed recoldplug: %s", error->message); } else { if (!fu_plugin_runner_coldplug (plugin, &error)) { fu_plugin_set_enabled (plugin, FALSE); g_message ("disabling plugin because: %s", error->message); } } } /* cleanup */ for (guint i = 0; i < plugins->len; i++) { g_autoptr(GError) error = NULL; FuPlugin *plugin = g_ptr_array_index (plugins, i); if (!fu_plugin_runner_coldplug_cleanup (plugin, &error)) g_warning ("failed to cleanup coldplug: %s", error->message); } /* print what we do have */ for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index (plugins, i); if (!fu_plugin_get_enabled (plugin)) continue; g_string_append_printf (str, "%s, ", fu_plugin_get_name (plugin)); } if (str->len > 2) { g_string_truncate (str, str->len - 2); g_debug ("using plugins: %s", str->str); } /* we can recoldplug from this point on */ self->coldplug_running = FALSE; } static void fu_engine_plugin_device_register (FuEngine *self, FuDevice *device) { GPtrArray *plugins; if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REGISTERED)) { g_warning ("already registered %s, ignoring", fu_device_get_id (device)); return; } plugins = fu_plugin_list_get_all (self->plugin_list); for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index (plugins, i); fu_plugin_runner_device_register (plugin, device); } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_REGISTERED); } static void fu_engine_plugin_device_register_cb (FuPlugin *plugin, FuDevice *device, gpointer user_data) { FuEngine *self = FU_ENGINE (user_data); fu_engine_plugin_device_register (self, device); } static void fu_engine_plugin_device_added_cb (FuPlugin *plugin, FuDevice *device, gpointer user_data) { FuEngine *self = (FuEngine *) user_data; gint priority = fu_plugin_get_priority (plugin); GPtrArray *children = fu_device_get_children (device); /* set the priority to 1 greater than biggest child */ for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index (children, i); gint child_priority = fu_device_get_priority (child); if (child_priority >= priority) priority = child_priority + 1; } fu_device_set_priority (device, priority); fu_engine_add_device (self, device); } static void fu_engine_adopt_children (FuEngine *self, FuDevice *device) { GPtrArray *guids; g_autoptr(GPtrArray) devices = fu_device_list_get_active (self->device_list); /* find the parent GUID in any existing device */ guids = fu_device_get_parent_guids (device); for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index (guids, j); for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index (devices, i); if (fu_device_get_parent (device) != NULL) continue; if (fu_device_has_guid (device_tmp, guid)) { g_debug ("setting parent of %s [%s] to be %s [%s]", fu_device_get_name (device), fu_device_get_id (device), fu_device_get_name (device_tmp), fu_device_get_id (device_tmp)); fu_device_add_child (device_tmp, device); break; } } } /* the new device is the parent to an existing child */ guids = fu_device_get_guids (device); for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index (guids, j); for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index (devices, i); if (fu_device_get_parent (device_tmp) != NULL) continue; if (fu_device_has_parent_guid (device_tmp, guid)) { g_debug ("setting parent of %s [%s] to be %s [%s]", fu_device_get_name (device_tmp), fu_device_get_id (device_tmp), fu_device_get_name (device), fu_device_get_id (device)); fu_device_add_child (device, device_tmp); } } } } static void fu_engine_device_inherit_history (FuEngine *self, FuDevice *device) { g_autoptr(FuDevice) device_history = NULL; /* any success or failed update? */ device_history = fu_history_get_device_by_id (self->history, fu_device_get_id (device), NULL); if (device_history == NULL) return; /* the device is still running the old firmware version and so if it * required activation before, it still requires it now -- note: * we can't just check for version_new=version to allow for re-installs */ if (fu_device_has_flag (device_history, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { FwupdRelease *release = fu_device_get_release_default (device_history); if (fu_common_vercmp (fu_device_get_version (device), fwupd_release_get_version (release)) != 0) { g_debug ("inheriting needs-activation for %s as version %s != %s", fu_device_get_name (device), fu_device_get_version (device), fwupd_release_get_version (release)); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); } } } void fu_engine_add_device (FuEngine *self, FuDevice *device) { GPtrArray *blacklisted_devices; GPtrArray *device_guids; /* device has no GUIDs set! */ device_guids = fu_device_get_guids (device); if (device_guids->len == 0) { g_warning ("no GUIDs for device %s [%s]", fu_device_get_name (device), fu_device_get_id (device)); return; } /* is this GUID blacklisted */ blacklisted_devices = fu_config_get_blacklist_devices (self->config); for (guint i = 0; i < blacklisted_devices->len; i++) { const gchar *blacklisted_guid = g_ptr_array_index (blacklisted_devices, i); for (guint j = 0; j < device_guids->len; j++) { const gchar *device_guid = g_ptr_array_index (device_guids, j); if (g_strcmp0 (blacklisted_guid, device_guid) == 0) { g_debug ("%s [%s] is blacklisted [%s], ignoring from %s", fu_device_get_name (device), fu_device_get_id (device), device_guid, fu_device_get_plugin (device)); return; } } } /* if this device is locked get some metadata from AppStream */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_LOCKED)) { g_autoptr(XbNode) component = fu_engine_get_component_by_guids (self, device); if (component != NULL) { g_autoptr(XbNode) release = NULL; release = xb_node_query_first (component, "releases/release", NULL); if (release != NULL) { g_autoptr(FwupdRelease) rel = fwupd_release_new (); g_autoptr(GError) error_local = NULL; if (!fu_engine_set_release_from_appstream (self, device, rel, component, release, &error_local)) { g_warning ("failed to set AppStream release: %s", error_local->message); } else { fu_device_add_release (device, rel); } } } } /* adopt any required children, which may or may not already exist */ fu_engine_adopt_children (self, device); /* set any alternate objects on the device from the ID */ if (fu_device_get_alternate_id (device) != NULL) { g_autoptr(FuDevice) device_alt = NULL; device_alt = fu_device_list_get_by_id (self->device_list, fu_device_get_alternate_id (device), NULL); if (device_alt != NULL) fu_device_set_alternate (device, device_alt); } if (fu_device_get_version_format (device) == FWUPD_VERSION_FORMAT_UNKNOWN && fu_common_version_guess_format (fu_device_get_version (device)) == FWUPD_VERSION_FORMAT_NUMBER) { fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_update_error (device, "VersionFormat is ambiguous for this device"); } /* notify all plugins about this new device */ if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REGISTERED)) fu_engine_plugin_device_register (self, device); /* create new device */ fu_device_list_add (self->device_list, device); /* match the metadata at this point so clients can tell if the * device is worthy */ if (fu_engine_is_device_supported (self, device)) fu_device_add_flag (device, FWUPD_DEVICE_FLAG_SUPPORTED); /* sometimes inherit flags from recent history */ fu_engine_device_inherit_history (self, device); } static void fu_engine_plugin_rules_changed_cb (FuPlugin *plugin, gpointer user_data) { FuEngine *self = FU_ENGINE (user_data); GPtrArray *rules = fu_plugin_get_rules (plugin, FU_PLUGIN_RULE_INHIBITS_IDLE); for (guint j = 0; j < rules->len; j++) { const gchar *tmp = g_ptr_array_index (rules, j); fu_idle_inhibit (self->idle, tmp); } } static void fu_engine_plugin_device_removed_cb (FuPlugin *plugin, FuDevice *device, gpointer user_data) { FuEngine *self = (FuEngine *) user_data; FuPlugin *plugin_old; g_autoptr(FuDevice) device_tmp = NULL; g_autoptr(GError) error = NULL; device_tmp = fu_device_list_get_by_id (self->device_list, fu_device_get_id (device), &error); if (device_tmp == NULL) { g_debug ("%s", error->message); return; } /* get the plugin */ plugin_old = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (device), &error); if (plugin_old == NULL) { g_debug ("%s", error->message); return; } /* check this came from the same plugin */ if (g_strcmp0 (fu_plugin_get_name (plugin), fu_plugin_get_name (plugin_old)) != 0) { g_debug ("ignoring duplicate removal from %s", fu_plugin_get_name (plugin)); return; } /* make the UI update */ fu_device_list_remove (self->device_list, device); fu_engine_emit_changed (self); } static gboolean fu_engine_recoldplug_delay_cb (gpointer user_data) { FuEngine *self = (FuEngine *) user_data; g_debug ("performing a recoldplug"); fu_engine_plugins_coldplug (self, TRUE); self->coldplug_id = 0; return FALSE; } static void fu_engine_udev_device_add (FuEngine *self, GUdevDevice *udev_device) { GPtrArray *plugins = fu_plugin_list_get_all (self->plugin_list); const gchar *plugin_name; g_autoptr(FuUdevDevice) device = fu_udev_device_new (udev_device); g_autoptr(GError) error_local = NULL; /* add any extra quirks */ fu_device_set_quirks (FU_DEVICE (device), self->quirks); if (!fu_device_probe (FU_DEVICE (device), &error_local)) { g_warning ("failed to probe device %s: %s", g_udev_device_get_sysfs_path (udev_device), error_local->message); return; } /* can be specified using a quirk */ plugin_name = fu_device_get_plugin (FU_DEVICE (device)); if (plugin_name != NULL) { g_autoptr(GError) error = NULL; FuPlugin *plugin = fu_plugin_list_find_by_name (self->plugin_list, plugin_name, &error); if (plugin == NULL) { g_warning ("failed to find specified plugin %s: %s", plugin_name, error->message); return; } if (!fu_plugin_runner_udev_device_added (plugin, device, &error)) { g_warning ("failed to add udev device %s: %s", g_udev_device_get_sysfs_path (udev_device), error->message); } return; } /* call into each plugin */ g_debug ("no plugin specified for udev device %s", g_udev_device_get_sysfs_path (udev_device)); for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j); g_autoptr(GError) error = NULL; /* skipping plugin as requires quirk */ if (fu_plugin_has_rule (plugin_tmp, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN)) { continue; } /* run all plugins */ if (!fu_plugin_runner_udev_device_added (plugin_tmp, device, &error)) { if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug ("%s ignoring: %s", fu_plugin_get_name (plugin_tmp), error->message); continue; } g_warning ("%s failed to add udev device %s: %s", fu_plugin_get_name (plugin_tmp), g_udev_device_get_sysfs_path (udev_device), error->message); } } } static void fu_engine_udev_device_remove (FuEngine *self, GUdevDevice *udev_device) { g_autoptr(GPtrArray) devices = NULL; /* go through each device and remove any that match */ devices = fu_device_list_get_all (self->device_list); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); if (!FU_IS_UDEV_DEVICE (device)) continue; if (g_strcmp0 (fu_udev_device_get_sysfs_path (FU_UDEV_DEVICE (device)), g_udev_device_get_sysfs_path (udev_device)) == 0) { g_debug ("auto-removing GUdevDevice"); fu_device_list_remove (self->device_list, device); } } } static void fu_engine_udev_device_changed (FuEngine *self, GUdevDevice *udev_device) { g_autoptr(GPtrArray) devices = NULL; /* emit changed on any that match */ devices = fu_device_list_get_all (self->device_list); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); if (!FU_IS_UDEV_DEVICE (device)) continue; if (g_strcmp0 (fu_udev_device_get_sysfs_path (FU_UDEV_DEVICE (device)), g_udev_device_get_sysfs_path (udev_device)) == 0) { fu_udev_device_emit_changed (FU_UDEV_DEVICE (device)); } } } static void fu_engine_enumerate_udev (FuEngine *self) { /* get all devices of class */ for (guint i = 0; i < self->udev_subsystems->len; i++) { const gchar *subsystem = g_ptr_array_index (self->udev_subsystems, i); GList *devices = g_udev_client_query_by_subsystem (self->gudev_client, subsystem); g_debug ("%u devices with subsystem %s", g_list_length (devices), subsystem); for (GList *l = devices; l != NULL; l = l->next) { GUdevDevice *udev_device = l->data; fu_engine_udev_device_add (self, udev_device); } g_list_foreach (devices, (GFunc) g_object_unref, NULL); g_list_free (devices); } } static void fu_engine_plugin_recoldplug_cb (FuPlugin *plugin, FuEngine *self) { if (self->coldplug_running) { g_warning ("coldplug already running, cannot recoldplug"); return; } if (self->app_flags & FU_APP_FLAGS_NO_IDLE_SOURCES) { g_debug ("doing direct recoldplug"); fu_engine_plugins_coldplug (self, TRUE); fu_engine_enumerate_udev (self); return; } g_debug ("scheduling a recoldplug"); if (self->coldplug_id != 0) g_source_remove (self->coldplug_id); self->coldplug_id = g_timeout_add (1500, fu_engine_recoldplug_delay_cb, self); } static void fu_engine_plugin_set_coldplug_delay_cb (FuPlugin *plugin, guint duration, FuEngine *self) { self->coldplug_delay = MAX (self->coldplug_delay, duration); g_debug ("got coldplug delay of %ums, global maximum is now %ums", duration, self->coldplug_delay); } /* this is called by the self tests as well */ void fu_engine_add_plugin (FuEngine *self, FuPlugin *plugin) { /* plugin does not match built version */ if (fu_plugin_get_build_hash (plugin) == NULL) { const gchar *name = fu_plugin_get_name (plugin); g_warning ("%s should call fu_plugin_set_build_hash()", name); self->tainted = TRUE; } else if (g_strcmp0 (fu_plugin_get_build_hash (plugin), FU_BUILD_HASH) != 0) { const gchar *name = fu_plugin_get_name (plugin); g_warning ("%s has incorrect built version %s", name, fu_plugin_get_build_hash (plugin)); self->tainted = TRUE; } fu_plugin_list_add (self->plugin_list, plugin); } static gboolean fu_engine_is_plugin_name_blacklisted (FuEngine *self, const gchar *name) { GPtrArray *blacklist = fu_config_get_blacklist_plugins (self->config); for (guint i = 0; i < blacklist->len; i++) { const gchar *name_tmp = g_ptr_array_index (blacklist, i); if (g_strcmp0 (name_tmp, name) == 0) return TRUE; } return FALSE; } static gboolean fu_engine_is_plugin_name_whitelisted (FuEngine *self, const gchar *name) { if (self->plugin_filter->len == 0) return TRUE; for (guint i = 0; i < self->plugin_filter->len; i++) { const gchar *name_tmp = g_ptr_array_index (self->plugin_filter, i); if (fnmatch (name_tmp, name, 0) == 0) return TRUE; } return FALSE; } void fu_engine_add_plugin_filter (FuEngine *self, const gchar *plugin_glob) { g_return_if_fail (FU_IS_ENGINE (self)); g_return_if_fail (plugin_glob != NULL); g_ptr_array_add (self->plugin_filter, g_strdup (plugin_glob)); } static gboolean fu_engine_plugin_check_supported_cb (FuPlugin *plugin, const gchar *guid, FuEngine *self) { g_autoptr(XbNode) n = NULL; g_autofree gchar *xpath = NULL; xpath = g_strdup_printf ("components/component/" "provides/firmware[@type='flashed'][text()='%s']", guid); n = xb_silo_query_first (self->silo, xpath, NULL); return n != NULL; } gboolean fu_engine_get_tainted (FuEngine *self) { return self->tainted; } gboolean fu_engine_load_plugins (FuEngine *self, GError **error) { const gchar *fn; g_autoptr(GDir) dir = NULL; g_autofree gchar *plugin_path = NULL; /* search */ plugin_path = fu_common_get_path (FU_PATH_KIND_PLUGINDIR_PKG); dir = g_dir_open (plugin_path, 0, error); if (dir == NULL) return FALSE; while ((fn = g_dir_read_name (dir)) != NULL) { g_autofree gchar *filename = NULL; g_autofree gchar *name = NULL; g_autoptr(FuPlugin) plugin = NULL; g_autoptr(GError) error_local = NULL; /* ignore non-plugins */ if (!g_str_has_suffix (fn, ".so")) continue; /* is blacklisted */ name = fu_plugin_guess_name_from_fn (fn); if (name == NULL) continue; if (fu_engine_is_plugin_name_blacklisted (self, name)) { g_debug ("plugin %s is blacklisted", name); continue; } if (!fu_engine_is_plugin_name_whitelisted (self, name)) { g_debug ("plugin %s is not whitelisted", name); continue; } /* open module */ filename = g_build_filename (plugin_path, fn, NULL); plugin = fu_plugin_new (); fu_plugin_set_name (plugin, name); fu_plugin_set_usb_context (plugin, self->usb_ctx); fu_plugin_set_hwids (plugin, self->hwids); fu_plugin_set_smbios (plugin, self->smbios); fu_plugin_set_udev_subsystems (plugin, self->udev_subsystems); fu_plugin_set_quirks (plugin, self->quirks); fu_plugin_set_runtime_versions (plugin, self->runtime_versions); fu_plugin_set_compile_versions (plugin, self->compile_versions); g_debug ("adding plugin %s", filename); /* if loaded from fu_engine_load() open the plugin */ if (self->usb_ctx != NULL) { if (!fu_plugin_open (plugin, filename, &error_local)) { g_warning ("failed to open plugin %s: %s", filename, error_local->message); continue; } } /* self disabled */ if (!fu_plugin_get_enabled (plugin)) { g_debug ("%s self disabled", fu_plugin_get_name (plugin)); continue; } /* watch for changes */ g_signal_connect (plugin, "device-added", G_CALLBACK (fu_engine_plugin_device_added_cb), self); g_signal_connect (plugin, "device-removed", G_CALLBACK (fu_engine_plugin_device_removed_cb), self); g_signal_connect (plugin, "device-register", G_CALLBACK (fu_engine_plugin_device_register_cb), self); g_signal_connect (plugin, "recoldplug", G_CALLBACK (fu_engine_plugin_recoldplug_cb), self); g_signal_connect (plugin, "set-coldplug-delay", G_CALLBACK (fu_engine_plugin_set_coldplug_delay_cb), self); g_signal_connect (plugin, "check-supported", G_CALLBACK (fu_engine_plugin_check_supported_cb), self); g_signal_connect (plugin, "rules-changed", G_CALLBACK (fu_engine_plugin_rules_changed_cb), self); /* add */ fu_engine_add_plugin (self, plugin); } /* depsolve into the correct order */ if (!fu_plugin_list_depsolve (self->plugin_list, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_engine_cleanup_state (GError **error) { const gchar *filenames[] = { "/var/cache/app-info/xmls/fwupd-verify.xml", "/var/cache/app-info/xmls/fwupd.xml", NULL }; for (guint i = 0; filenames[i] != NULL; i++) { g_autoptr(GFile) file = g_file_new_for_path (filenames[i]); if (g_file_query_exists (file, NULL)) { if (!g_file_delete (file, NULL, error)) return FALSE; } } return TRUE; } guint64 fu_engine_get_archive_size_max (FuEngine *self) { return fu_config_get_archive_size_max (self->config); } static void fu_engine_usb_device_removed_cb (GUsbContext *ctx, GUsbDevice *usb_device, FuEngine *self) { g_autoptr(GPtrArray) devices = NULL; /* go through each device and remove any that match */ devices = fu_device_list_get_all (self->device_list); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); if (!FU_IS_USB_DEVICE (device)) continue; if (g_strcmp0 (fu_usb_device_get_platform_id (FU_USB_DEVICE (device)), g_usb_device_get_platform_id (usb_device)) == 0) { g_debug ("auto-removing GUsbDevice"); fu_device_list_remove (self->device_list, device); } } } static void fu_engine_usb_device_added_cb (GUsbContext *ctx, GUsbDevice *usb_device, FuEngine *self) { GPtrArray *plugins = fu_plugin_list_get_all (self->plugin_list); const gchar *plugin_name; g_autoptr(FuUsbDevice) device = fu_usb_device_new (usb_device); g_autoptr(GError) error_local = NULL; /* add any extra quirks */ fu_device_set_quirks (FU_DEVICE (device), self->quirks); if (!fu_device_probe (FU_DEVICE (device), &error_local)) { g_warning ("failed to probe device %s: %s", fu_device_get_physical_id (FU_DEVICE (device)), error_local->message); return; } /* can be specified using a quirk */ plugin_name = fu_device_get_plugin (device); if (plugin_name != NULL) { g_autoptr(GError) error = NULL; FuPlugin *plugin = fu_plugin_list_find_by_name (self->plugin_list, plugin_name, &error); if (plugin == NULL) { g_warning ("failed to find specified plugin %s: %s", plugin_name, error->message); return; } if (!fu_plugin_runner_usb_device_added (plugin, device, &error)) { g_warning ("failed to add USB device %04x:%04x: %s", g_usb_device_get_vid (usb_device), g_usb_device_get_pid (usb_device), error->message); } return; } /* call into each plugin */ g_debug ("no plugin specified for USB device %04x:%04x", g_usb_device_get_vid (usb_device), g_usb_device_get_pid (usb_device)); for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j); g_autoptr(GError) error = NULL; /* skipping plugin as requires quirk */ if (fu_plugin_has_rule (plugin_tmp, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_QUIRKS_PLUGIN)) { continue; } /* create a device, then probe */ if (!fu_plugin_runner_usb_device_added (plugin_tmp, device, &error)) { if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug ("%s ignoring: %s", fu_plugin_get_name (plugin_tmp), error->message); continue; } g_warning ("%s failed to add USB device %04x:%04x: %s", fu_plugin_get_name (plugin_tmp), g_usb_device_get_vid (usb_device), g_usb_device_get_pid (usb_device), error->message); } } } static void fu_engine_load_quirks (FuEngine *self) { g_autoptr(GError) error = NULL; if (!fu_quirks_load (self->quirks, &error)) g_warning ("Failed to load quirks: %s", error->message); } static void fu_engine_load_smbios (FuEngine *self) { g_autoptr(GError) error = NULL; if (!fu_smbios_setup (self->smbios, &error)) g_warning ("Failed to load SMBIOS: %s", error->message); } static void fu_engine_load_hwids (FuEngine *self) { g_autoptr(GError) error = NULL; if (!fu_hwids_setup (self->hwids, self->smbios, &error)) g_warning ("Failed to load HWIDs: %s", error->message); } static gboolean fu_engine_update_history_device (FuEngine *self, FuDevice *dev_history, GError **error) { FuPlugin *plugin; FwupdRelease *rel_history; g_autofree gchar *btime = NULL; g_autoptr(FuDevice) dev = NULL; /* is in the device list */ dev = fu_device_list_get_by_id (self->device_list, fu_device_get_id (dev_history), error); if (dev == NULL) return FALSE; /* does the installed version match what we tried to install * before fwupd was restarted */ rel_history = fu_device_get_release_default (dev_history); if (rel_history == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no release for history FuDevice"); return FALSE; } /* is this the same boot time as when we scheduled the update, * i.e. has fwupd been restarted before we rebooted */ btime = fu_engine_get_boot_time (); if (g_strcmp0 (fwupd_release_get_metadata_item (rel_history, "BootTime"), btime) == 0) { g_debug ("service restarted, but no reboot has taken place"); return TRUE; } /* the system is running with the new firmware version */ if (fu_common_vercmp (fu_device_get_version (dev), fwupd_release_get_version (rel_history)) == 0) { GPtrArray *checksums; g_debug ("installed version %s matching history %s", fu_device_get_version (dev), fwupd_release_get_version (rel_history)); /* copy over runtime checksums if set from probe() */ checksums = fu_device_get_checksums (dev); for (guint i = 0; i < checksums->len; i++) { const gchar *csum = g_ptr_array_index (checksums, i); fu_device_add_checksum (dev_history, csum); } fu_device_set_version (dev_history, fu_device_get_version (dev), fu_device_get_version_format (dev)); fu_device_remove_flag (dev_history, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); fu_device_set_update_state (dev_history, FWUPD_UPDATE_STATE_SUCCESS); return fu_history_modify_device (self->history, dev_history, FU_HISTORY_FLAGS_MATCH_NEW_VERSION, error); } /* does the plugin know the update failure */ plugin = fu_plugin_list_find_by_name (self->plugin_list, fu_device_get_plugin (dev), error); if (plugin == NULL) return FALSE; if (!fu_plugin_runner_get_results (plugin, dev, error)) return FALSE; /* the plugin either can't tell us the error, or doesn't know itself */ if (fu_device_get_update_state (dev) != FWUPD_UPDATE_STATE_FAILED && fu_device_get_update_state (dev) != FWUPD_UPDATE_STATE_FAILED_TRANSIENT) { g_debug ("falling back to generic failure"); fu_device_set_update_error (dev_history, "failed to run update on reboot"); } /* update the state in the database */ fu_device_set_update_error (dev_history, fu_device_get_update_error (dev)); return fu_history_modify_device (self->history, dev_history, FU_HISTORY_FLAGS_MATCH_OLD_VERSION, error); } static gboolean fu_engine_update_history_database (FuEngine *self, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* get any devices */ devices = fu_history_get_devices (self->history, error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index (devices, i); g_autoptr(GError) error_local = NULL; /* not in the required state */ if (fu_device_get_update_state (dev) != FWUPD_UPDATE_STATE_NEEDS_REBOOT) continue; /* try to save the new update-state, but ignoring any error */ if (!fu_engine_update_history_device (self, dev, &error_local)) g_warning ("%s", error_local->message); } return TRUE; } static void fu_engine_udev_uevent_cb (GUdevClient *gudev_client, const gchar *action, GUdevDevice *udev_device, FuEngine *self) { if (g_strcmp0 (action, "add") == 0) { fu_engine_udev_device_add (self, udev_device); return; } if (g_strcmp0 (action, "remove") == 0) { fu_engine_udev_device_remove (self, udev_device); return; } if (g_strcmp0 (action, "change") == 0) { fu_engine_udev_device_changed (self, udev_device); return; } } static void fu_engine_ensure_client_certificate (FuEngine *self) { g_autoptr(FuKeyring) kr = NULL; g_autoptr(GBytes) blob = g_bytes_new_static ("test\0", 5); g_autoptr(GBytes) sig = NULL; g_autoptr(GError) error = NULL; /* create keyring and sign dummy data to ensure certificate exists */ kr = fu_keyring_create_for_kind (FWUPD_KEYRING_KIND_PKCS7, &error); if (kr == NULL) { g_message ("failed to create keyring: %s", error->message); return; } if (!fu_keyring_setup (kr, &error)) { g_message ("failed to setup keyring: %s", error->message); return; } sig = fu_keyring_sign_data (kr, blob, FU_KEYRING_SIGN_FLAG_NONE, &error); if (sig == NULL) { g_message ("failed to sign using keyring: %s", error->message); return; } g_debug ("client certificate exists and working"); } /** * fu_engine_load: * @self: A #FuEngine * @flags: #FuEngineLoadFlags, e.g. %FU_ENGINE_LOAD_FLAG_READONLY_FS * @error: A #GError, or %NULL * * Load the firmware update engine so it is ready for use. * * Returns: %TRUE for success **/ gboolean fu_engine_load (FuEngine *self, FuEngineLoadFlags flags, GError **error) { FuConfigLoadFlags config_flags = FU_CONFIG_LOAD_FLAG_NONE; g_autoptr(GPtrArray) checksums = NULL; g_return_val_if_fail (FU_IS_ENGINE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* avoid re-loading a second time if fu-tool or fu-util request to */ if (self->loaded) return TRUE; /* read config file */ if (flags & FU_ENGINE_LOAD_FLAG_READONLY_FS) config_flags |= FU_CONFIG_LOAD_FLAG_READONLY_FS; if (!fu_config_load (self->config, config_flags, error)) { g_prefix_error (error, "Failed to load config: "); return FALSE; } /* create client certificate */ fu_engine_ensure_client_certificate (self); /* get hardcoded approved firmware */ checksums = fu_config_get_approved_firmware (self->config); for (guint i = 0; i < checksums->len; i++) { const gchar *csum = g_ptr_array_index (checksums, i); fu_engine_add_approved_firmware (self, csum); } /* get extra firmware saved to the database */ checksums = fu_history_get_approved_firmware (self->history, error); if (checksums == NULL) return FALSE; for (guint i = 0; i < checksums->len; i++) { const gchar *csum = g_ptr_array_index (checksums, i); fu_engine_add_approved_firmware (self, csum); } /* set up idle exit */ if ((self->app_flags & FU_APP_FLAGS_NO_IDLE_SOURCES) == 0) fu_idle_set_timeout (self->idle, fu_config_get_idle_timeout (self->config)); /* load quirks, SMBIOS and the hwids */ fu_engine_load_smbios (self); fu_engine_load_hwids (self); fu_engine_load_quirks (self); /* load AppStream metadata */ if (!fu_engine_load_metadata_store (self, flags, error)) { g_prefix_error (error, "Failed to load AppStream data: "); return FALSE; } /* set shared USB context */ self->usb_ctx = g_usb_context_new (error); if (self->usb_ctx == NULL) { g_prefix_error (error, "Failed to get USB context: "); return FALSE; } /* delete old data files */ if (!fu_engine_cleanup_state (error)) { g_prefix_error (error, "Failed to clean up: "); return FALSE; } /* load plugin */ if (!fu_engine_load_plugins (self, error)) { g_prefix_error (error, "Failed to load plugins: "); return FALSE; } /* watch the device list for updates and proxy */ g_signal_connect (self->device_list, "added", G_CALLBACK (fu_engine_device_added_cb), self); g_signal_connect (self->device_list, "removed", G_CALLBACK (fu_engine_device_removed_cb), self); g_signal_connect (self->device_list, "changed", G_CALLBACK (fu_engine_device_changed_cb), self); /* udev watches can only be set up in _init() so set up client now */ if (self->udev_subsystems->len > 0) { g_auto(GStrv) udev_subsystems = g_new0 (gchar *, self->udev_subsystems->len + 1); for (guint i = 0; i < self->udev_subsystems->len; i++) { const gchar *subsystem = g_ptr_array_index (self->udev_subsystems, i); udev_subsystems[i] = g_strdup (subsystem); } self->gudev_client = g_udev_client_new ((const gchar * const *) udev_subsystems); g_signal_connect (self->gudev_client, "uevent", G_CALLBACK (fu_engine_udev_uevent_cb), self); } fu_engine_set_status (self, FWUPD_STATUS_LOADING); /* add devices */ fu_engine_plugins_setup (self); fu_engine_plugins_coldplug (self, FALSE); /* coldplug USB devices */ g_signal_connect (self->usb_ctx, "device-added", G_CALLBACK (fu_engine_usb_device_added_cb), self); g_signal_connect (self->usb_ctx, "device-removed", G_CALLBACK (fu_engine_usb_device_removed_cb), self); g_usb_context_enumerate (self->usb_ctx); /* coldplug udev devices */ fu_engine_enumerate_udev (self); /* update the db for devices that were updated during the reboot */ if (!fu_engine_update_history_database (self, error)) return FALSE; fu_engine_set_status (self, FWUPD_STATUS_IDLE); self->loaded = TRUE; /* success */ return TRUE; } static void fu_engine_class_init (FuEngineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_engine_finalize; signals[SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[SIGNAL_DEVICE_ADDED] = g_signal_new ("device-added", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); signals[SIGNAL_DEVICE_REMOVED] = g_signal_new ("device-removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); signals[SIGNAL_DEVICE_CHANGED] = g_signal_new ("device-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); signals[SIGNAL_STATUS_CHANGED] = g_signal_new ("status-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); signals[SIGNAL_PERCENTAGE_CHANGED] = g_signal_new ("percentage-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); } void fu_engine_add_runtime_version (FuEngine *self, const gchar *component_id, const gchar *version) { g_hash_table_insert (self->runtime_versions, g_strdup (component_id), g_strdup (version)); } void fu_engine_add_app_flag (FuEngine *self, FuAppFlags app_flags) { g_return_if_fail (FU_IS_ENGINE (self)); self->app_flags |= app_flags; } static void fu_engine_idle_status_notify_cb (FuIdle *idle, GParamSpec *pspec, FuEngine *self) { FwupdStatus status = fu_idle_get_status (idle); if (status == FWUPD_STATUS_SHUTDOWN) fu_engine_set_status (self, status); } static void fu_engine_init (FuEngine *self) { self->percentage = 0; self->status = FWUPD_STATUS_IDLE; self->config = fu_config_new (); self->device_list = fu_device_list_new (); self->smbios = fu_smbios_new (); self->hwids = fu_hwids_new (); self->idle = fu_idle_new (); self->quirks = fu_quirks_new (); self->history = fu_history_new (); self->plugin_list = fu_plugin_list_new (); self->plugin_filter = g_ptr_array_new_with_free_func (g_free); self->udev_subsystems = g_ptr_array_new_with_free_func (g_free); self->runtime_versions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); self->compile_versions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); self->approved_firmware = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_signal_connect (self->config, "changed", G_CALLBACK (fu_engine_config_changed_cb), self); g_signal_connect (self->idle, "notify::status", G_CALLBACK (fu_engine_idle_status_notify_cb), self); /* add some runtime versions of things the daemon depends on */ fu_engine_add_runtime_version (self, "org.freedesktop.fwupd", VERSION); fu_engine_add_runtime_version (self, "com.redhat.fwupdate", "12"); fu_engine_add_runtime_version (self, "org.freedesktop.appstream-glib", "0.7.14"); #if G_USB_CHECK_VERSION(0,3,1) fu_engine_add_runtime_version (self, "org.freedesktop.gusb", g_usb_version_string ()); #endif g_hash_table_insert (self->compile_versions, g_strdup ("com.redhat.fwupdate"), g_strdup ("12")); g_hash_table_insert (self->compile_versions, g_strdup ("org.freedesktop.fwupd"), g_strdup (VERSION)); g_hash_table_insert (self->compile_versions, g_strdup ("org.freedesktop.gusb"), g_strdup_printf ("%i.%i.%i", G_USB_MAJOR_VERSION, G_USB_MINOR_VERSION, G_USB_MICRO_VERSION)); } static void fu_engine_finalize (GObject *obj) { FuEngine *self = FU_ENGINE (obj); if (self->usb_ctx != NULL) g_object_unref (self->usb_ctx); if (self->silo != NULL) g_object_unref (self->silo); if (self->gudev_client != NULL) g_object_unref (self->gudev_client); if (self->coldplug_id != 0) g_source_remove (self->coldplug_id); g_object_unref (self->idle); g_object_unref (self->config); g_object_unref (self->smbios); g_object_unref (self->quirks); g_object_unref (self->hwids); g_object_unref (self->history); g_object_unref (self->device_list); g_ptr_array_unref (self->plugin_filter); g_ptr_array_unref (self->udev_subsystems); g_hash_table_unref (self->runtime_versions); g_hash_table_unref (self->compile_versions); g_hash_table_unref (self->approved_firmware); g_object_unref (self->plugin_list); G_OBJECT_CLASS (fu_engine_parent_class)->finalize (obj); } FuEngine * fu_engine_new (FuAppFlags app_flags) { FuEngine *self; self = g_object_new (FU_TYPE_ENGINE, NULL); self->app_flags = app_flags; return FU_ENGINE (self); } fwupd-1.2.14/src/fu-engine.h000066400000000000000000000123251402665037500155540ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fwupd-device.h" #include "fwupd-enums.h" #include "fu-common.h" #include "fu-keyring.h" #include "fu-install-task.h" #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_ENGINE (fu_engine_get_type ()) G_DECLARE_FINAL_TYPE (FuEngine, fu_engine, FU, ENGINE, GObject) /** * FuEngineLoadFlags: * @FU_ENGINE_LOAD_FLAG_NONE: No flags set * @FU_ENGINE_LOAD_FLAG_READONLY_FS: Ignore readonly filesystem errors * * The flags to use when loading the engine. **/ typedef enum { FU_ENGINE_LOAD_FLAG_NONE = 0, FU_ENGINE_LOAD_FLAG_READONLY_FS = 1 << 0, /*< private >*/ FU_ENGINE_LOAD_FLAG_LAST } FuEngineLoadFlags; FuEngine *fu_engine_new (FuAppFlags app_flags); void fu_engine_add_app_flag (FuEngine *self, FuAppFlags app_flags); void fu_engine_add_plugin_filter (FuEngine *self, const gchar *plugin_glob); void fu_engine_idle_reset (FuEngine *self); gboolean fu_engine_load (FuEngine *self, FuEngineLoadFlags flags, GError **error); gboolean fu_engine_load_plugins (FuEngine *self, GError **error); gboolean fu_engine_get_tainted (FuEngine *self); FwupdStatus fu_engine_get_status (FuEngine *self); XbSilo *fu_engine_get_silo_from_blob (FuEngine *self, GBytes *blob_cab, GError **error); guint64 fu_engine_get_archive_size_max (FuEngine *self); GPtrArray *fu_engine_get_plugins (FuEngine *self); GPtrArray *fu_engine_get_devices (FuEngine *self, GError **error); FuDevice *fu_engine_get_device (FuEngine *self, const gchar *device_id, GError **error); GPtrArray *fu_engine_get_history (FuEngine *self, GError **error); FwupdRemote *fu_engine_get_remote_by_id (FuEngine *self, const gchar *remote_id, GError **error); GPtrArray *fu_engine_get_remotes (FuEngine *self, GError **error); GPtrArray *fu_engine_get_releases (FuEngine *self, const gchar *device_id, GError **error); GPtrArray *fu_engine_get_downgrades (FuEngine *self, const gchar *device_id, GError **error); GPtrArray *fu_engine_get_upgrades (FuEngine *self, const gchar *device_id, GError **error); FwupdDevice *fu_engine_get_results (FuEngine *self, const gchar *device_id, GError **error); gboolean fu_engine_clear_results (FuEngine *self, const gchar *device_id, GError **error); gboolean fu_engine_update_metadata (FuEngine *self, const gchar *remote_id, gint fd, gint fd_sig, GError **error); gboolean fu_engine_unlock (FuEngine *self, const gchar *device_id, GError **error); gboolean fu_engine_verify (FuEngine *self, const gchar *device_id, GError **error); gboolean fu_engine_verify_update (FuEngine *self, const gchar *device_id, GError **error); gboolean fu_engine_modify_remote (FuEngine *self, const gchar *remote_id, const gchar *key, const gchar *value, GError **error); gboolean fu_engine_modify_device (FuEngine *self, const gchar *device_id, const gchar *key, const gchar *value, GError **error); gboolean fu_engine_composite_prepare (FuEngine *self, GPtrArray *devices, GError **error); gboolean fu_engine_composite_cleanup (FuEngine *self, GPtrArray *devices, GError **error); gboolean fu_engine_install (FuEngine *self, FuInstallTask *task, GBytes *blob_cab, FwupdInstallFlags flags, GError **error); gboolean fu_engine_install_blob (FuEngine *self, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error); gboolean fu_engine_install_tasks (FuEngine *self, GPtrArray *install_tasks, GBytes *blob_cab, FwupdInstallFlags flags, GError **error); GPtrArray *fu_engine_get_details (FuEngine *self, gint fd, GError **error); gboolean fu_engine_activate (FuEngine *self, const gchar *device_id, GError **error); GPtrArray *fu_engine_get_approved_firmware (FuEngine *self); void fu_engine_add_approved_firmware (FuEngine *self, const gchar *checksum); gchar *fu_engine_self_sign (FuEngine *self, const gchar *value, FuKeyringSignFlags flags, GError **error); gboolean fu_engine_modify_config (FuEngine *self, const gchar *key, const gchar *value, GError **error); /* for the self tests */ void fu_engine_add_device (FuEngine *self, FuDevice *device); void fu_engine_add_plugin (FuEngine *self, FuPlugin *plugin); void fu_engine_add_runtime_version (FuEngine *self, const gchar *component_id, const gchar *version); gboolean fu_engine_check_requirements (FuEngine *self, FuInstallTask *task, FwupdInstallFlags flags, GError **error); void fu_engine_set_silo (FuEngine *self, XbSilo *silo); XbNode *fu_engine_get_component_by_guids (FuEngine *self, FuDevice *device); G_END_DECLS fwupd-1.2.14/src/fu-hash.py000066400000000000000000000015561402665037500154370ustar00rootroot00000000000000#!/usr/bin/python3 """ Builds a header for the plugins to include """ # pylint: disable=invalid-name,wrong-import-position,pointless-string-statement """ SPDX-License-Identifier: LGPL-2.1+ """ import sys import hashlib def usage(return_code): """ print usage and exit with the supplied return code """ if return_code == 0: out = sys.stdout else: out = sys.stderr out.write("usage: fu-hash.py
") sys.exit(return_code) if __name__ == '__main__': if {'-?', '--help', '--usage'}.intersection(set(sys.argv)): usage(0) if len(sys.argv) != 3: usage(1) with open(sys.argv[1], 'rb') as f: buf = f.read() csum = hashlib.sha256(buf).hexdigest() with open(sys.argv[2], 'w') as f2: f2.write('#pragma once\n') f2.write('#define FU_BUILD_HASH "%s"\n' % csum) fwupd-1.2.14/src/fu-history.c000066400000000000000000000675641402665037500160220ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHistory" #include "config.h" #include #include #include #include #include #include #include "fu-common.h" #include "fu-device-private.h" #include "fu-history.h" #include "fu-mutex.h" #define FU_HISTORY_CURRENT_SCHEMA_VERSION 5 static void fu_history_finalize (GObject *object); struct _FuHistory { GObject parent_instance; sqlite3 *db; GRWLock db_mutex; }; G_DEFINE_TYPE (FuHistory, fu_history, G_TYPE_OBJECT) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(sqlite3_stmt, sqlite3_finalize); #pragma clang diagnostic pop static FuDevice * fu_history_device_from_stmt (sqlite3_stmt *stmt) { const gchar *tmp; FuDevice *device; FwupdRelease *release; /* create new result */ device = fu_device_new (); release = fu_device_get_release_default (device); /* device_id */ tmp = (const gchar *) sqlite3_column_text (stmt, 0); if (tmp != NULL) fwupd_device_set_id (FWUPD_DEVICE (device), tmp); /* checksum */ tmp = (const gchar *) sqlite3_column_text (stmt, 1); if (tmp != NULL) fwupd_release_add_checksum (release, tmp); /* plugin */ tmp = (const gchar *) sqlite3_column_text (stmt, 2); if (tmp != NULL) fu_device_set_plugin (device, tmp); /* device_created */ fu_device_set_created (device, sqlite3_column_int64 (stmt, 3)); /* device_modified */ fu_device_set_modified (device, sqlite3_column_int64 (stmt, 4)); /* display_name */ tmp = (const gchar *) sqlite3_column_text (stmt, 5); if (tmp != NULL) fu_device_set_name (device, tmp); /* filename */ tmp = (const gchar *) sqlite3_column_text (stmt, 6); if (tmp != NULL) fwupd_release_set_filename (release, tmp); /* flags */ fu_device_set_flags (device, sqlite3_column_int64 (stmt, 7)); /* metadata */ tmp = (const gchar *) sqlite3_column_text (stmt, 8); if (tmp != NULL) { g_auto(GStrv) split = g_strsplit (tmp, ";", -1); for (guint i = 0; split[i] != NULL; i++) { g_auto(GStrv) kv = g_strsplit (split[i], "=", 2); if (g_strv_length (kv) != 2) continue; fwupd_release_add_metadata_item (release, kv[0], kv[1]); } } /* guid_default */ tmp = (const gchar *) sqlite3_column_text (stmt, 9); if (tmp != NULL) fu_device_add_guid (device, tmp); /* update_state */ fu_device_set_update_state (device, sqlite3_column_int (stmt, 10)); /* update_error */ tmp = (const gchar *) sqlite3_column_text (stmt, 11); fu_device_set_update_error (device, tmp); /* version_new */ tmp = (const gchar *) sqlite3_column_text (stmt, 12); if (tmp != NULL) fwupd_release_set_version (release, tmp); /* version_old */ tmp = (const gchar *) sqlite3_column_text (stmt, 13); if (tmp != NULL) fu_device_set_version (device, tmp, FWUPD_VERSION_FORMAT_UNKNOWN); /* checksum_device */ tmp = (const gchar *) sqlite3_column_text (stmt, 14); if (tmp != NULL) fu_device_add_checksum (device, tmp); /* protocol */ tmp = (const gchar *) sqlite3_column_text (stmt, 15); if (tmp != NULL) fwupd_release_set_protocol (release, tmp); return device; } static gboolean fu_history_stmt_exec (FuHistory *self, sqlite3_stmt *stmt, GPtrArray *array, GError **error) { gint rc; if (array == NULL) { rc = sqlite3_step (stmt); } else { while ((rc = sqlite3_step (stmt)) == SQLITE_ROW) { FuDevice *device = fu_history_device_from_stmt (stmt); g_ptr_array_add (array, device); } } if (rc != SQLITE_DONE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to execute prepared statement: %s", sqlite3_errmsg (self->db)); return FALSE; } return TRUE; } static gboolean fu_history_create_database (FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec (self->db, "BEGIN TRANSACTION;" "CREATE TABLE IF NOT EXISTS schema (" "created timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP," "version INTEGER DEFAULT 0);" "INSERT INTO schema (version) VALUES (0);" "CREATE TABLE IF NOT EXISTS history (" "device_id TEXT," "update_state INTEGER DEFAULT 0," "update_error TEXT," "filename TEXT," "display_name TEXT," "plugin TEXT," "device_created INTEGER DEFAULT 0," "device_modified INTEGER DEFAULT 0," "checksum TEXT DEFAULT NULL," "flags INTEGER DEFAULT 0," "metadata TEXT DEFAULT NULL," "guid_default TEXT DEFAULT NULL," "version_old TEXT," "version_new TEXT," "checksum_device TEXT DEFAULT NULL," "protocol TEXT DEFAULT NULL);" "CREATE TABLE IF NOT EXISTS approved_firmware (" "checksum TEXT);" "COMMIT;", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL for creating tables: %s", sqlite3_errmsg (self->db)); return FALSE; } return TRUE; } static gboolean fu_history_migrate_database_v1 (FuHistory *self, GError **error) { gint rc; /* rename the table to something out the way */ rc = sqlite3_exec (self->db, "ALTER TABLE history RENAME TO history_old;", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_debug ("cannot rename v0 table: %s", sqlite3_errmsg (self->db)); return TRUE; } /* create new table */ if (!fu_history_create_database (self, error)) return FALSE; /* migrate the old entries to the new table */ rc = sqlite3_exec (self->db, "INSERT INTO history SELECT " "device_id, update_state, update_error, filename, " "display_name, plugin, device_created, device_modified, " "checksum, flags, metadata, guid_default, version_old, " "version_new, NULL, NULL FROM history_old;" "DROP TABLE history_old;", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_debug ("no history to migrate: %s", sqlite3_errmsg (self->db)); return TRUE; } return TRUE; } static gboolean fu_history_migrate_database_v2 (FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec (self->db, "ALTER TABLE history ADD COLUMN checksum_device TEXT DEFAULT NULL;", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to alter database: %s", sqlite3_errmsg (self->db)); return FALSE; } return TRUE; } static gboolean fu_history_migrate_database_v3 (FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec (self->db, "ALTER TABLE history ADD COLUMN protocol TEXT DEFAULT NULL;", NULL, NULL, NULL); if (rc != SQLITE_OK) g_debug ("ignoring database error: %s", sqlite3_errmsg (self->db)); return TRUE; } static gboolean fu_history_migrate_database_v4 (FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec (self->db, "CREATE TABLE IF NOT EXISTS approved_firmware (checksum TEXT);", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to create table: %s", sqlite3_errmsg (self->db)); return FALSE; } return TRUE; } /* returns 0 if database is not initialised */ static guint fu_history_get_schema_version (FuHistory *self) { gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; rc = sqlite3_prepare_v2 (self->db, "SELECT version FROM schema LIMIT 1;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_debug ("no schema version: %s", sqlite3_errmsg (self->db)); return 0; } rc = sqlite3_step (stmt); if (rc != SQLITE_ROW) { g_warning ("failed prepare to get schema version: %s", sqlite3_errmsg (self->db)); return 0; } return sqlite3_column_int (stmt, 0); } static gboolean fu_history_create_or_migrate (FuHistory *self, guint schema_ver, GError **error) { gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; /* create initial up-to-date database or migrate */ if (schema_ver == 0) { g_debug ("building initial database"); if (!fu_history_create_database (self, error)) return FALSE; } else if (schema_ver == 1) { g_debug ("migrating v%u database by recreating table", schema_ver); if (!fu_history_migrate_database_v1 (self, error)) return FALSE; } else if (schema_ver == 2) { g_debug ("migrating v%u database by altering", schema_ver); if (!fu_history_migrate_database_v2 (self, error)) return FALSE; if (!fu_history_migrate_database_v3 (self, error)) return FALSE; if (!fu_history_migrate_database_v4 (self, error)) return FALSE; } else if (schema_ver == 3) { g_debug ("migrating v%u database by altering", schema_ver); if (!fu_history_migrate_database_v3 (self, error)) return FALSE; if (!fu_history_migrate_database_v4 (self, error)) return FALSE; } else if (schema_ver == 4) { g_debug ("migrating v%u database by altering", schema_ver); if (!fu_history_migrate_database_v4 (self, error)) return FALSE; } else { /* this is probably okay, but return an error if we ever delete * or rename columns */ g_warning ("schema version %u is unknown", schema_ver); return TRUE; } /* set new schema version */ rc = sqlite3_prepare_v2 (self->db, "UPDATE schema SET version=?1;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL for updating schema: %s", sqlite3_errmsg (self->db)); return FALSE; } sqlite3_bind_int (stmt, 1, FU_HISTORY_CURRENT_SCHEMA_VERSION); return fu_history_stmt_exec (self, stmt, NULL, error); } static gboolean fu_history_open (FuHistory *self, const gchar *filename, GError **error) { gint rc; g_debug ("trying to open database '%s'", filename); rc = sqlite3_open (filename, &self->db); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "Can't open %s: %s", filename, sqlite3_errmsg (self->db)); return FALSE; } return TRUE; } static gboolean fu_history_load (FuHistory *self, GError **error) { gint rc; guint schema_ver; g_autofree gchar *dirname = NULL; g_autofree gchar *filename = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_writer_locker_new (&self->db_mutex); /* already done */ if (self->db != NULL) return TRUE; g_return_val_if_fail (FU_IS_HISTORY (self), FALSE); g_return_val_if_fail (self->db == NULL, FALSE); g_return_val_if_fail (locker != NULL, FALSE); /* create directory */ dirname = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); file = g_file_new_for_path (dirname); if (!g_file_query_exists (file, NULL)) { if (!g_file_make_directory_with_parents (file, NULL, error)) return FALSE; } /* open */ filename = g_build_filename (dirname, "pending.db", NULL); if (!fu_history_open (self, filename, error)) return FALSE; /* check database */ schema_ver = fu_history_get_schema_version (self); if (schema_ver == 0) { g_autoptr(sqlite3_stmt) stmt_tmp = NULL; rc = sqlite3_prepare_v2 (self->db, "SELECT * FROM history LIMIT 0;", -1, &stmt_tmp, NULL); if (rc == SQLITE_OK) schema_ver = 1; } /* create initial up-to-date database, or migrate */ g_debug ("got schema version of %u", schema_ver); if (schema_ver != FU_HISTORY_CURRENT_SCHEMA_VERSION) { g_autoptr(GError) error_migrate = NULL; if (!fu_history_create_or_migrate (self, schema_ver, &error_migrate)) { /* this is fatal to the daemon, so delete the database * and try again with something empty */ g_warning ("failed to migrate %s database: %s", filename, error_migrate->message); sqlite3_close (self->db); if (g_unlink (filename) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Can't delete %s", filename); return FALSE; } if (!fu_history_open (self, filename, error)) return FALSE; return fu_history_create_database (self, error); } } /* success */ return TRUE; } static gchar * _convert_hash_to_string (GHashTable *hash) { GString *str = g_string_new (NULL); g_autoptr(GList) keys = g_hash_table_get_keys (hash); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup (hash, key); if (str->len > 0) g_string_append (str, ";"); g_string_append_printf (str, "%s=%s", key, value); } return g_string_free (str, FALSE); } /* unset some flags we don't want to store */ static FwupdDeviceFlags fu_history_get_device_flags_filtered (FuDevice *device) { FwupdDeviceFlags flags = fu_device_get_flags (device); flags &= ~FWUPD_DEVICE_FLAG_REGISTERED; flags &= ~FWUPD_DEVICE_FLAG_SUPPORTED; return flags; } gboolean fu_history_modify_device (FuHistory *self, FuDevice *device, FuHistoryFlags flags, GError **error) { gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail (FU_IS_HISTORY (self), FALSE); g_return_val_if_fail (FU_IS_DEVICE (device), FALSE); /* lazy load */ if (!fu_history_load (self, error)) return FALSE; /* overwrite entry if it exists */ locker = g_rw_lock_writer_locker_new (&self->db_mutex); g_return_val_if_fail (locker != NULL, FALSE); if ((flags & FU_HISTORY_FLAGS_MATCH_OLD_VERSION) && (flags & FU_HISTORY_FLAGS_MATCH_NEW_VERSION)) { g_debug ("modifying device %s [%s], version not important", fu_device_get_name (device), fu_device_get_id (device)); rc = sqlite3_prepare_v2 (self->db, "UPDATE history SET " "update_state = ?1, " "update_error = ?2, " "checksum_device = ?6, " "device_modified = ?7, " "flags = ?3 " "WHERE device_id = ?4;", -1, &stmt, NULL); } else if (flags & FU_HISTORY_FLAGS_MATCH_OLD_VERSION) { g_debug ("modifying device %s [%s], only version old %s", fu_device_get_name (device), fu_device_get_id (device), fu_device_get_version (device)); rc = sqlite3_prepare_v2 (self->db, "UPDATE history SET " "update_state = ?1, " "update_error = ?2, " "checksum_device = ?6, " "device_modified = ?7, " "flags = ?3 " "WHERE device_id = ?4 AND version_old = ?5;", -1, &stmt, NULL); } else if (flags & FU_HISTORY_FLAGS_MATCH_NEW_VERSION) { g_debug ("modifying device %s [%s], only version new %s", fu_device_get_name (device), fu_device_get_id (device), fu_device_get_version (device)); rc = sqlite3_prepare_v2 (self->db, "UPDATE history SET " "update_state = ?1, " "update_error = ?2, " "checksum_device = ?6, " "device_modified = ?7, " "flags = ?3 " "WHERE device_id = ?4 AND version_new = ?5;", -1, &stmt, NULL); } else { g_assert_not_reached (); } if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to update history: %s", sqlite3_errmsg (self->db)); return FALSE; } sqlite3_bind_int (stmt, 1, fu_device_get_update_state (device)); sqlite3_bind_text (stmt, 2, fu_device_get_update_error (device), -1, SQLITE_STATIC); sqlite3_bind_int64 (stmt, 3, fu_history_get_device_flags_filtered (device)); sqlite3_bind_text (stmt, 4, fu_device_get_id (device), -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 5, fu_device_get_version (device), -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 6, fwupd_checksum_get_by_kind (fu_device_get_checksums (device), G_CHECKSUM_SHA1), -1, SQLITE_STATIC); sqlite3_bind_int64 (stmt, 7, fu_device_get_modified (device)); return fu_history_stmt_exec (self, stmt, NULL, error); } gboolean fu_history_add_device (FuHistory *self, FuDevice *device, FwupdRelease *release, GError **error) { const gchar *checksum_device; const gchar *checksum = NULL; gint rc; g_autofree gchar *metadata = NULL; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail (FU_IS_HISTORY (self), FALSE); g_return_val_if_fail (FU_IS_DEVICE (device), FALSE); g_return_val_if_fail (FWUPD_IS_RELEASE (release), FALSE); /* lazy load */ if (!fu_history_load (self, error)) return FALSE; /* ensure device with this old-version -> new-version does not exist */ if (!fu_history_remove_device (self, device, release, error)) return FALSE; g_debug ("add device %s [%s]", fu_device_get_name (device), fu_device_get_id (device)); if (release != NULL) { GPtrArray *checksums = fwupd_release_get_checksums (release); checksum = fwupd_checksum_get_by_kind (checksums, G_CHECKSUM_SHA1); } checksum_device = fwupd_checksum_get_by_kind (fu_device_get_checksums (device), G_CHECKSUM_SHA1); /* metadata is stored as a simple string */ metadata = _convert_hash_to_string (fwupd_release_get_metadata (release)); /* add */ locker = g_rw_lock_writer_locker_new (&self->db_mutex); g_return_val_if_fail (locker != NULL, FALSE); rc = sqlite3_prepare_v2 (self->db, "INSERT INTO history (device_id," "update_state," "update_error," "flags," "filename," "checksum," "display_name," "plugin," "guid_default," "metadata," "device_created," "device_modified," "version_old," "version_new," "checksum_device," "protocol) " "VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10," "?11,?12,?13,?14,?15,?16)", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to insert history: %s", sqlite3_errmsg (self->db)); return FALSE; } sqlite3_bind_text (stmt, 1, fu_device_get_id (device), -1, SQLITE_STATIC); sqlite3_bind_int (stmt, 2, fu_device_get_update_state (device)); sqlite3_bind_text (stmt, 3, fu_device_get_update_error (device), -1, SQLITE_STATIC); sqlite3_bind_int64 (stmt, 4, fu_history_get_device_flags_filtered (device)); sqlite3_bind_text (stmt, 5, fwupd_release_get_filename (release), -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 6, checksum, -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 7, fu_device_get_name (device), -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 8, fu_device_get_plugin (device), -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 9, fu_device_get_guid_default (device), -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 10, metadata, -1, SQLITE_STATIC); sqlite3_bind_int64 (stmt, 11, fu_device_get_created (device)); sqlite3_bind_int64 (stmt, 12, fu_device_get_modified (device)); sqlite3_bind_text (stmt, 13, fu_device_get_version (device), -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 14, fwupd_release_get_version (release), -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 15, checksum_device, -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 16, fwupd_release_get_protocol (release), -1, SQLITE_STATIC); return fu_history_stmt_exec (self, stmt, NULL, error); } gboolean fu_history_remove_all_with_state (FuHistory *self, FwupdUpdateState update_state, GError **error) { gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail (FU_IS_HISTORY (self), FALSE); /* lazy load */ if (!fu_history_load (self, error)) return FALSE; /* remove entries */ locker = g_rw_lock_writer_locker_new (&self->db_mutex); g_return_val_if_fail (locker != NULL, FALSE); g_debug ("removing all devices with update_state %s", fwupd_update_state_to_string (update_state)); rc = sqlite3_prepare_v2 (self->db, "DELETE FROM history WHERE update_state = ?1", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to delete history: %s", sqlite3_errmsg (self->db)); return FALSE; } sqlite3_bind_int (stmt, 1, update_state); return fu_history_stmt_exec (self, stmt, NULL, error); } gboolean fu_history_remove_all (FuHistory *self, GError **error) { gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail (FU_IS_HISTORY (self), FALSE); /* lazy load */ if (!fu_history_load (self, error)) return FALSE; /* remove entries */ locker = g_rw_lock_writer_locker_new (&self->db_mutex); g_return_val_if_fail (locker != NULL, FALSE); g_debug ("removing all devices"); rc = sqlite3_prepare_v2 (self->db, "DELETE FROM history;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to delete history: %s", sqlite3_errmsg (self->db)); return FALSE; } return fu_history_stmt_exec (self, stmt, NULL, error); } gboolean fu_history_remove_device (FuHistory *self, FuDevice *device, FwupdRelease *release, GError **error) { gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail (FU_IS_HISTORY (self), FALSE); g_return_val_if_fail (FU_IS_DEVICE (device), FALSE); g_return_val_if_fail (FWUPD_IS_RELEASE (release), FALSE); /* lazy load */ if (!fu_history_load (self, error)) return FALSE; locker = g_rw_lock_writer_locker_new (&self->db_mutex); g_return_val_if_fail (locker != NULL, FALSE); g_debug ("remove device %s [%s]", fu_device_get_name (device), fu_device_get_id (device)); rc = sqlite3_prepare_v2 (self->db, "DELETE FROM history WHERE device_id = ?1 " "AND version_old = ?2 " "AND version_new = ?3;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to delete history: %s", sqlite3_errmsg (self->db)); return FALSE; } sqlite3_bind_text (stmt, 1, fu_device_get_id (device), -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 2, fu_device_get_version (device), -1, SQLITE_STATIC); sqlite3_bind_text (stmt, 3, fwupd_release_get_version (release), -1, SQLITE_STATIC); return fu_history_stmt_exec (self, stmt, NULL, error); } FuDevice * fu_history_get_device_by_id (FuHistory *self, const gchar *device_id, GError **error) { gint rc; g_autoptr(GPtrArray) array_tmp = NULL; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail (FU_IS_HISTORY (self), NULL); g_return_val_if_fail (device_id != NULL, NULL); /* lazy load */ if (!fu_history_load (self, error)) return NULL; /* get all the devices */ locker = g_rw_lock_reader_locker_new (&self->db_mutex); g_return_val_if_fail (locker != NULL, NULL); g_debug ("get device"); rc = sqlite3_prepare_v2 (self->db, "SELECT device_id, " "checksum, " "plugin, " "device_created, " "device_modified, " "display_name, " "filename, " "flags, " "metadata, " "guid_default, " "update_state, " "update_error, " "version_new, " "version_old, " "checksum_device, " "protocol FROM history WHERE " "device_id = ?1 ORDER BY device_created DESC " "LIMIT 1", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to get history: %s", sqlite3_errmsg (self->db)); return NULL; } sqlite3_bind_text (stmt, 1, device_id, -1, SQLITE_STATIC); array_tmp = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); if (!fu_history_stmt_exec (self, stmt, array_tmp, error)) return NULL; if (array_tmp->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No devices found"); return NULL; } return g_object_ref (g_ptr_array_index (array_tmp, 0)); } GPtrArray * fu_history_get_devices (FuHistory *self, GError **error) { GPtrArray *array = NULL; g_autoptr(sqlite3_stmt) stmt = NULL; gint rc; g_autoptr(GPtrArray) array_tmp = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail (FU_IS_HISTORY (self), NULL); /* lazy load */ if (self->db == NULL) { if (!fu_history_load (self, error)) return NULL; } /* get all the devices */ locker = g_rw_lock_reader_locker_new (&self->db_mutex); g_return_val_if_fail (locker != NULL, NULL); rc = sqlite3_prepare_v2 (self->db, "SELECT device_id, " "checksum, " "plugin, " "device_created, " "device_modified, " "display_name, " "filename, " "flags, " "metadata, " "guid_default, " "update_state, " "update_error, " "version_new, " "version_old, " "checksum_device, " "protocol FROM history " "ORDER BY device_modified ASC;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to get history: %s", sqlite3_errmsg (self->db)); return NULL; } array_tmp = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); if (!fu_history_stmt_exec (self, stmt, array_tmp, error)) return NULL; array = g_ptr_array_ref (array_tmp); return array; } GPtrArray * fu_history_get_approved_firmware (FuHistory *self, GError **error) { gint rc; g_autoptr(GRWLockReaderLocker) locker = NULL; g_autoptr(GPtrArray) array = NULL; g_autoptr(sqlite3_stmt) stmt = NULL; g_return_val_if_fail (FU_IS_HISTORY (self), NULL); /* lazy load */ if (self->db == NULL) { if (!fu_history_load (self, error)) return NULL; } /* get all the approved firmware */ locker = g_rw_lock_reader_locker_new (&self->db_mutex); g_return_val_if_fail (locker != NULL, NULL); rc = sqlite3_prepare_v2 (self->db, "SELECT checksum FROM approved_firmware;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to get checksum: %s", sqlite3_errmsg (self->db)); return NULL; } array = g_ptr_array_new_with_free_func (g_free); while ((rc = sqlite3_step (stmt)) == SQLITE_ROW) { const gchar *tmp = (const gchar *) sqlite3_column_text (stmt, 0); g_ptr_array_add (array, g_strdup (tmp)); } if (rc != SQLITE_DONE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to execute prepared statement: %s", sqlite3_errmsg (self->db)); return NULL; } return g_steal_pointer (&array); } gboolean fu_history_clear_approved_firmware (FuHistory *self, GError **error) { gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail (FU_IS_HISTORY (self), FALSE); /* lazy load */ if (!fu_history_load (self, error)) return FALSE; /* remove entries */ locker = g_rw_lock_writer_locker_new (&self->db_mutex); g_return_val_if_fail (locker != NULL, FALSE); rc = sqlite3_prepare_v2 (self->db, "DELETE FROM approved_firmware;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to delete approved firmware: %s", sqlite3_errmsg (self->db)); return FALSE; } return fu_history_stmt_exec (self, stmt, NULL, error); } gboolean fu_history_add_approved_firmware (FuHistory *self, const gchar *checksum, GError **error) { gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail (FU_IS_HISTORY (self), FALSE); g_return_val_if_fail (checksum != NULL, FALSE); /* lazy load */ if (!fu_history_load (self, error)) return FALSE; /* add */ locker = g_rw_lock_writer_locker_new (&self->db_mutex); g_return_val_if_fail (locker != NULL, FALSE); rc = sqlite3_prepare_v2 (self->db, "INSERT INTO approved_firmware (checksum) " "VALUES (?1)", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to insert checksum: %s", sqlite3_errmsg (self->db)); return FALSE; } sqlite3_bind_text (stmt, 1, checksum, -1, SQLITE_STATIC); return fu_history_stmt_exec (self, stmt, NULL, error); } static void fu_history_class_init (FuHistoryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_history_finalize; } static void fu_history_init (FuHistory *self) { g_rw_lock_init (&self->db_mutex); } static void fu_history_finalize (GObject *object) { FuHistory *self = FU_HISTORY (object); if (self->db != NULL) sqlite3_close (self->db); g_rw_lock_clear (&self->db_mutex); G_OBJECT_CLASS (fu_history_parent_class)->finalize (object); } FuHistory * fu_history_new (void) { FuHistory *self; self = g_object_new (FU_TYPE_PENDING, NULL); return FU_HISTORY (self); } fwupd-1.2.14/src/fu-history.h000066400000000000000000000036321402665037500160110ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-device.h" G_BEGIN_DECLS #define FU_TYPE_PENDING (fu_history_get_type ()) G_DECLARE_FINAL_TYPE (FuHistory, fu_history, FU, HISTORY, GObject) /** * FuHistoryFlags: * @FU_HISTORY_FLAGS_NONE: No flags set * @FU_HISTORY_FLAGS_MATCH_OLD_VERSION: Match previous firmware version * @FU_HISTORY_FLAGS_MATCH_NEW_VERSION: Match new firmware version * * The flags to use when matching devices against the history database. **/ typedef enum { FU_HISTORY_FLAGS_NONE = 0, FU_HISTORY_FLAGS_MATCH_OLD_VERSION = 1 << 0, FU_HISTORY_FLAGS_MATCH_NEW_VERSION = 1 << 1, /*< private >*/ FU_HISTORY_FLAGS_LAST } FuHistoryFlags; FuHistory *fu_history_new (void); gboolean fu_history_add_device (FuHistory *self, FuDevice *device, FwupdRelease *release, GError **error); gboolean fu_history_modify_device (FuHistory *self, FuDevice *device, FuHistoryFlags flags, GError **error); gboolean fu_history_remove_device (FuHistory *self, FuDevice *device, FwupdRelease *release, GError **error); gboolean fu_history_remove_all (FuHistory *self, GError **error); gboolean fu_history_remove_all_with_state (FuHistory *self, FwupdUpdateState update_state, GError **error); FuDevice *fu_history_get_device_by_id (FuHistory *self, const gchar *device_id, GError **error); GPtrArray *fu_history_get_devices (FuHistory *self, GError **error); gboolean fu_history_clear_approved_firmware (FuHistory *self, GError **error); gboolean fu_history_add_approved_firmware (FuHistory *self, const gchar *checksum, GError **error); GPtrArray *fu_history_get_approved_firmware (FuHistory *self, GError **error); G_END_DECLS fwupd-1.2.14/src/fu-hwids.c000066400000000000000000000307661402665037500154310ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHwids" #include "config.h" #include #include #include #include "fu-common.h" #include "fu-hwids.h" #include "fwupd-common.h" #include "fwupd-error.h" struct _FuHwids { GObject parent_instance; GHashTable *hash_dmi_hw; /* BiosVersion->"1.2.3 " */ GHashTable *hash_dmi_display; /* BiosVersion->"1.2.3" */ GHashTable *hash_guid; /* a-c-b-d->1 */ GPtrArray *array_guids; /* a-c-b-d */ }; G_DEFINE_TYPE (FuHwids, fu_hwids, G_TYPE_OBJECT) /** * fu_hwids_get_value: * @self: A #FuHwids * @key: A DMI ID, e.g. `BiosVersion` * * Gets the cached value for one specific key that is valid ASCII and suitable * for display. * * Returns: the string, e.g. `1.2.3`, or %NULL if not found **/ const gchar * fu_hwids_get_value (FuHwids *self, const gchar *key) { return g_hash_table_lookup (self->hash_dmi_display, key); } /** * fu_hwids_has_guid: * @self: A #FuHwids * @guid: A GUID, e.g. `059eb22d-6dc7-59af-abd3-94bbe017f67c` * * Finds out if a hardware GUID exists. * * Returns: %TRUE if the GUID exists **/ gboolean fu_hwids_has_guid (FuHwids *self, const gchar *guid) { return g_hash_table_lookup (self->hash_guid, guid) != NULL; } /** * fu_hwids_get_guids: * @self: A #FuHwids * * Returns all the defined HWIDs * * Returns: (transfer none) (element-type utf-8): An array of GUIDs **/ GPtrArray * fu_hwids_get_guids (FuHwids *self) { return self->array_guids; } static gchar * fu_hwids_get_guid_for_str (const gchar *str, GError **error) { glong items_written = 0; g_autofree gunichar2 *data = NULL; /* convert to UTF-16 and convert to GUID using custom namespace */ data = g_utf8_to_utf16 (str, -1, NULL, &items_written, error); if (data == NULL) return NULL; if (items_written == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no GUIDs in data"); return NULL; } /* ensure the data is in little endian format */ for (glong i = 0; i < items_written; i++) data[i] = GUINT16_TO_LE(data[i]); /* convert to a GUID */ return fwupd_guid_hash_data ((guint8*) data, items_written * 2, FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT); } /** * fu_hwids_get_replace_keys: * @self: A #FuHwids * @key: A HardwareID key, e.g. `HardwareID-3` * * Gets the replacement key for a well known value. * * Returns: the replacement value, e.g. `Manufacturer&ProductName`, or %NULL for error. **/ const gchar * fu_hwids_get_replace_keys (FuHwids *self, const gchar *key) { struct { const gchar *search; const gchar *replace; } msdefined[] = { { "HardwareID-0", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_PRODUCT_SKU "&" FU_HWIDS_KEY_BIOS_VENDOR "&" FU_HWIDS_KEY_BIOS_VERSION "&" FU_HWIDS_KEY_BIOS_MAJOR_RELEASE "&" FU_HWIDS_KEY_BIOS_MINOR_RELEASE }, { "HardwareID-1", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_BIOS_VENDOR "&" FU_HWIDS_KEY_BIOS_VERSION "&" FU_HWIDS_KEY_BIOS_MAJOR_RELEASE "&" FU_HWIDS_KEY_BIOS_MINOR_RELEASE }, { "HardwareID-2", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_BIOS_VENDOR "&" FU_HWIDS_KEY_BIOS_VERSION "&" FU_HWIDS_KEY_BIOS_MAJOR_RELEASE "&" FU_HWIDS_KEY_BIOS_MINOR_RELEASE }, { "HardwareID-3", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_PRODUCT_SKU "&" FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_PRODUCT }, { "HardwareID-4", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_PRODUCT_SKU }, { "HardwareID-5", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME }, { "HardwareID-6", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_PRODUCT_SKU "&" FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_PRODUCT }, { "HardwareID-7", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_PRODUCT_SKU }, { "HardwareID-8", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_PRODUCT }, { "HardwareID-9", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_PRODUCT_NAME }, { "HardwareID-10", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_PRODUCT }, { "HardwareID-11", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY }, { "HardwareID-12", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_ENCLOSURE_KIND }, { "HardwareID-13", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_PRODUCT }, { "HardwareID-14", FU_HWIDS_KEY_MANUFACTURER }, { NULL, NULL } }; /* defined for Windows 10 */ for (guint i = 0; msdefined[i].search != NULL; i++) { if (g_strcmp0 (msdefined[i].search, key) == 0) { key = msdefined[i].replace; break; } } return key; } /** * fu_hwids_get_replace_values: * @self: A #FuHwids * @keys: A key, e.g. `HardwareID-3` or %FU_HWIDS_KEY_PRODUCT_SKU * @error: A #GError or %NULL * * Gets the replacement values for a HardwareID key or plain key. * * Returns: a string, e.g. `LENOVO&ThinkPad T440s`, or %NULL for error. **/ gchar * fu_hwids_get_replace_values (FuHwids *self, const gchar *keys, GError **error) { g_auto(GStrv) split = NULL; g_autoptr(GString) str = g_string_new (NULL); /* do any replacements */ keys = fu_hwids_get_replace_keys (self, keys); /* get each part of the HWID */ split = g_strsplit (keys, "&", -1); for (guint j = 0; split[j] != NULL; j++) { const gchar *tmp = g_hash_table_lookup (self->hash_dmi_hw, split[j]); if (tmp == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "not available as '%s' unknown", split[j]); return NULL; } g_string_append_printf (str, "%s&", tmp); } if (str->len > 0) g_string_truncate (str, str->len - 1); return g_strdup (str->str); } /** * fu_hwids_get_guid: * @self: A #FuHwids * @keys: A key, e.g. `HardwareID-3` or %FU_HWIDS_KEY_PRODUCT_SKU * @error: A #GError or %NULL * * Gets the GUID for a specific key. * * Returns: a string, or %NULL for error. **/ gchar * fu_hwids_get_guid (FuHwids *self, const gchar *keys, GError **error) { g_autofree gchar *tmp = fu_hwids_get_replace_values (self, keys, error); if (tmp == NULL) return NULL; return fu_hwids_get_guid_for_str (tmp, error); } typedef gchar *(*FuHwidsConvertFunc) (FuSmbios *smbios, guint8 type, guint8 offset, GError **error); static gchar * fu_hwids_convert_string_table_cb (FuSmbios *smbios, guint8 type, guint8 offset, GError **error) { const gchar *tmp; tmp = fu_smbios_get_string (smbios, type, offset, error); if (tmp == NULL) return NULL; /* ComputerHardwareIds.exe seems to strip spaces */ return fu_common_strstrip (tmp); } static gchar * fu_hwids_convert_padded_integer_cb (FuSmbios *smbios, guint8 type, guint8 offset, GError **error) { g_autoptr(GBytes) data = NULL; const guint8 *data_raw; gsize data_sz = 0; data = fu_smbios_get_data (smbios, type, error); if (data == NULL) return NULL; data_raw = g_bytes_get_data (data, &data_sz); if (offset >= data_sz) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "offset bigger than data"); return NULL; } return g_strdup_printf ("%02x", data_raw[offset]); } static gchar * fu_hwids_convert_integer_cb (FuSmbios *smbios, guint8 type, guint8 offset, GError **error) { g_autoptr(GBytes) data = NULL; const guint8 *data_raw; gsize data_sz = 0; data = fu_smbios_get_data (smbios, type, error); if (data == NULL) return NULL; data_raw = g_bytes_get_data (data, &data_sz); if (offset >= data_sz) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "offset bigger than data"); return NULL; } return g_strdup_printf ("%x", data_raw[offset]); } /** * fu_hwids_setup: * @self: A #FuHwids * @smbios: A #FuSmbios * @error: A #GError or %NULL * * Reads all the SMBIOS values from the hardware. * * Returns: %TRUE for success **/ gboolean fu_hwids_setup (FuHwids *self, FuSmbios *smbios, GError **error) { struct { const gchar *key; guint8 type; guint8 offset; FuHwidsConvertFunc func; } map[] = { { FU_HWIDS_KEY_MANUFACTURER, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x04, fu_hwids_convert_string_table_cb }, { FU_HWIDS_KEY_ENCLOSURE_KIND, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS, 0x05, fu_hwids_convert_integer_cb }, { FU_HWIDS_KEY_FAMILY, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x1a, fu_hwids_convert_string_table_cb }, { FU_HWIDS_KEY_PRODUCT_NAME, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x05, fu_hwids_convert_string_table_cb }, { FU_HWIDS_KEY_PRODUCT_SKU, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x19, fu_hwids_convert_string_table_cb }, { FU_HWIDS_KEY_BIOS_VENDOR, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x04, fu_hwids_convert_string_table_cb }, { FU_HWIDS_KEY_BIOS_VERSION, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x05, fu_hwids_convert_string_table_cb }, { FU_HWIDS_KEY_BIOS_MAJOR_RELEASE, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x14, fu_hwids_convert_padded_integer_cb }, { FU_HWIDS_KEY_BIOS_MINOR_RELEASE, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x15, fu_hwids_convert_padded_integer_cb }, { FU_HWIDS_KEY_BASEBOARD_MANUFACTURER, FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD, 0x04, fu_hwids_convert_string_table_cb }, { FU_HWIDS_KEY_BASEBOARD_PRODUCT, FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD, 0x05, fu_hwids_convert_string_table_cb }, { NULL, 0x00, 0x00, NULL } }; g_return_val_if_fail (FU_IS_HWIDS (self), FALSE); g_return_val_if_fail (FU_IS_SMBIOS (smbios), FALSE); /* get all DMI data */ for (guint i = 0; map[i].key != NULL; i++) { const gchar *contents_hdr; g_autofree gchar *contents = NULL; g_autofree gchar *contents_safe = NULL; g_autoptr(GError) error_local = NULL; /* get the data from a SMBIOS table */ contents = map[i].func (smbios, map[i].type, map[i].offset, &error_local); if (contents == NULL) { g_debug ("ignoring %s: %s", map[i].key, error_local->message); continue; } g_debug ("smbios property %s=%s", map[i].key, contents); /* weirdly, remove leading zeros */ contents_hdr = contents; while (contents_hdr[0] == '0' && map[i].func != fu_hwids_convert_padded_integer_cb) contents_hdr++; g_hash_table_insert (self->hash_dmi_hw, g_strdup (map[i].key), g_strdup (contents_hdr)); /* make suitable for display */ contents_safe = g_str_to_ascii (contents_hdr, "C"); g_strdelimit (contents_safe, "\n\r", '\0'); g_strchomp (contents_safe); g_hash_table_insert (self->hash_dmi_display, g_strdup (map[i].key), g_steal_pointer (&contents_safe)); } /* add GUIDs */ for (guint i = 0; i < 15; i++) { g_autofree gchar *guid = NULL; g_autofree gchar *key = NULL; g_autoptr(GError) error_local = NULL; /* get the GUID and add to hash */ key = g_strdup_printf ("HardwareID-%u", i); guid = fu_hwids_get_guid (self, key, &error_local); if (guid == NULL) { g_debug ("%s is not available, %s", key, error_local->message); continue; } g_hash_table_insert (self->hash_guid, g_strdup (guid), GUINT_TO_POINTER (1)); g_ptr_array_add (self->array_guids, g_steal_pointer (&guid)); } return TRUE; } static void fu_hwids_finalize (GObject *object) { FuHwids *self; g_return_if_fail (FU_IS_HWIDS (object)); self = FU_HWIDS (object); g_hash_table_unref (self->hash_dmi_hw); g_hash_table_unref (self->hash_dmi_display); g_hash_table_unref (self->hash_guid); g_ptr_array_unref (self->array_guids); G_OBJECT_CLASS (fu_hwids_parent_class)->finalize (object); } static void fu_hwids_class_init (FuHwidsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_hwids_finalize; } static void fu_hwids_init (FuHwids *self) { self->hash_dmi_hw = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); self->hash_dmi_display = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); self->hash_guid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); self->array_guids = g_ptr_array_new_with_free_func (g_free); } FuHwids * fu_hwids_new (void) { FuHwids *self; self = g_object_new (FU_TYPE_HWIDS, NULL); return FU_HWIDS (self); } fwupd-1.2.14/src/fu-hwids.h000066400000000000000000000027221402665037500154250ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-smbios.h" G_BEGIN_DECLS #define FU_TYPE_HWIDS (fu_hwids_get_type ()) G_DECLARE_FINAL_TYPE (FuHwids, fu_hwids, FU, HWIDS, GObject) #define FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "BaseboardManufacturer" #define FU_HWIDS_KEY_BASEBOARD_PRODUCT "BaseboardProduct" #define FU_HWIDS_KEY_BIOS_MAJOR_RELEASE "BiosMajorRelease" #define FU_HWIDS_KEY_BIOS_MINOR_RELEASE "BiosMinorRelease" #define FU_HWIDS_KEY_BIOS_VENDOR "BiosVendor" #define FU_HWIDS_KEY_BIOS_VERSION "BiosVersion" #define FU_HWIDS_KEY_ENCLOSURE_KIND "EnclosureKind" #define FU_HWIDS_KEY_FAMILY "Family" #define FU_HWIDS_KEY_MANUFACTURER "Manufacturer" #define FU_HWIDS_KEY_PRODUCT_NAME "ProductName" #define FU_HWIDS_KEY_PRODUCT_SKU "ProductSku" FuHwids *fu_hwids_new (void); const gchar *fu_hwids_get_value (FuHwids *self, const gchar *key); const gchar *fu_hwids_get_replace_keys (FuHwids *self, const gchar *key); gchar *fu_hwids_get_replace_values (FuHwids *self, const gchar *keys, GError **error); gchar *fu_hwids_get_guid (FuHwids *self, const gchar *keys, GError **error); GPtrArray *fu_hwids_get_guids (FuHwids *self); gboolean fu_hwids_has_guid (FuHwids *self, const gchar *guid); gboolean fu_hwids_setup (FuHwids *self, FuSmbios *smbios, GError **error); G_END_DECLS fwupd-1.2.14/src/fu-idle.c000066400000000000000000000116561402665037500152250ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuIdle" #include "config.h" #include #include "fu-idle.h" #include "fu-mutex.h" static void fu_idle_finalize (GObject *obj); struct _FuIdle { GObject parent_instance; GPtrArray *items; /* of FuIdleItem */ GRWLock items_mutex; guint idle_id; guint timeout; FwupdStatus status; }; enum { PROP_0, PROP_STATUS, PROP_LAST }; static void fu_idle_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuIdle *self = FU_IDLE (object); switch (prop_id) { case PROP_STATUS: g_value_set_uint (value, self->status); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_idle_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } typedef struct { gchar *reason; guint32 token; } FuIdleItem; G_DEFINE_TYPE (FuIdle, fu_idle, G_TYPE_OBJECT) FwupdStatus fu_idle_get_status (FuIdle *self) { g_return_val_if_fail (FU_IS_IDLE (self), FWUPD_STATUS_UNKNOWN); return self->status; } static void fu_idle_set_status (FuIdle *self, FwupdStatus status) { if (self->status == status) return; self->status = status; g_debug ("status now %s", fwupd_status_to_string (status)); g_object_notify (G_OBJECT (self), "status"); } static gboolean fu_idle_check_cb (gpointer user_data) { FuIdle *self = FU_IDLE (user_data); fu_idle_set_status (self, FWUPD_STATUS_SHUTDOWN); return G_SOURCE_CONTINUE; } static void fu_idle_start (FuIdle *self) { if (self->idle_id != 0) return; if (self->timeout == 0) return; self->idle_id = g_timeout_add_seconds (self->timeout, fu_idle_check_cb, self); } static void fu_idle_stop (FuIdle *self) { if (self->idle_id == 0) return; g_source_remove (self->idle_id); self->idle_id = 0; } void fu_idle_reset (FuIdle *self) { g_return_if_fail (FU_IS_IDLE (self)); fu_idle_stop (self); if (self->items->len == 0) fu_idle_start (self); } void fu_idle_uninhibit (FuIdle *self, guint32 token) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_writer_locker_new (&self->items_mutex); g_return_if_fail (FU_IS_IDLE (self)); g_return_if_fail (token != 0); g_return_if_fail (locker != NULL); for (guint i = 0; i < self->items->len; i++) { FuIdleItem *item = g_ptr_array_index (self->items, i); if (item->token == token) { g_debug ("uninhibiting: %s", item->reason); g_ptr_array_remove_index (self->items, i); break; } } fu_idle_reset (self); } guint32 fu_idle_inhibit (FuIdle *self, const gchar *reason) { FuIdleItem *item; g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_writer_locker_new (&self->items_mutex); g_return_val_if_fail (FU_IS_IDLE (self), 0); g_return_val_if_fail (reason != NULL, 0); g_return_val_if_fail (locker != NULL, 0); g_debug ("inhibiting: %s", reason); item = g_new0 (FuIdleItem, 1); item->reason = g_strdup (reason); item->token = g_random_int_range (1, G_MAXINT); g_ptr_array_add (self->items, item); fu_idle_reset (self); return item->token; } void fu_idle_set_timeout (FuIdle *self, guint timeout) { g_return_if_fail (FU_IS_IDLE (self)); g_debug ("setting timeout to %us", timeout); self->timeout = timeout; fu_idle_reset (self); } static void fu_idle_item_free (FuIdleItem *item) { g_free (item->reason); g_free (item); } FuIdleLocker * fu_idle_locker_new (FuIdle *self, const gchar *reason) { FuIdleLocker *locker = g_new0 (FuIdleLocker, 1); locker->idle = g_object_ref (self); locker->token = fu_idle_inhibit (self, reason); return locker; } void fu_idle_locker_free (FuIdleLocker *locker) { if (locker == NULL) return; fu_idle_uninhibit (locker->idle, locker->token); g_object_unref (locker->idle); g_free (locker); } static void fu_idle_class_init (FuIdleClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; object_class->finalize = fu_idle_finalize; object_class->get_property = fu_idle_get_property; object_class->set_property = fu_idle_set_property; pspec = g_param_spec_uint ("status", NULL, NULL, FWUPD_STATUS_UNKNOWN, FWUPD_STATUS_LAST, FWUPD_STATUS_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_STATUS, pspec); } static void fu_idle_init (FuIdle *self) { self->status = FWUPD_STATUS_IDLE; self->items = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_idle_item_free); g_rw_lock_init (&self->items_mutex); } static void fu_idle_finalize (GObject *obj) { FuIdle *self = FU_IDLE (obj); fu_idle_stop (self); g_ptr_array_unref (self->items); g_rw_lock_clear (&self->items_mutex); G_OBJECT_CLASS (fu_idle_parent_class)->finalize (obj); } FuIdle * fu_idle_new (void) { FuIdle *self; self = g_object_new (FU_TYPE_IDLE, NULL); return FU_IDLE (self); } fwupd-1.2.14/src/fu-idle.h000066400000000000000000000020351402665037500152210ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-device.h" G_BEGIN_DECLS #define FU_TYPE_IDLE (fu_idle_get_type ()) G_DECLARE_FINAL_TYPE (FuIdle, fu_idle, FU, IDLE, GObject) FuIdle *fu_idle_new (void); guint32 fu_idle_inhibit (FuIdle *self, const gchar *reason); void fu_idle_uninhibit (FuIdle *self, guint32 token); void fu_idle_set_timeout (FuIdle *self, guint timeout); void fu_idle_reset (FuIdle *self); FwupdStatus fu_idle_get_status (FuIdle *self); /** * FuIdleLocker: * @idle: A #FuIdle * @token: A #guint32 number * * A locker to prevent daemon from shutting down on its own **/ typedef struct { FuIdle *idle; guint32 token; } FuIdleLocker; FuIdleLocker *fu_idle_locker_new (FuIdle *self, const gchar *reason); void fu_idle_locker_free (FuIdleLocker *locker); G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuIdleLocker, fu_idle_locker_free) G_END_DECLS fwupd-1.2.14/src/fu-install-task.c000066400000000000000000000263761402665037500167230ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuInstallTask" #include "config.h" #include #include "fu-common-version.h" #include "fu-device-private.h" #include "fu-install-task.h" #include "fu-keyring-utils.h" struct _FuInstallTask { GObject parent_instance; FuDevice *device; XbNode *component; FwupdReleaseFlags trust_flags; gboolean is_downgrade; }; G_DEFINE_TYPE (FuInstallTask, fu_install_task, G_TYPE_OBJECT) /** * fu_install_task_get_device: * @self: A #FuInstallTask * * Gets the device for this task. * * Returns: (transfer none): the device **/ FuDevice * fu_install_task_get_device (FuInstallTask *self) { g_return_val_if_fail (FU_IS_INSTALL_TASK (self), NULL); return self->device; } /** * fu_install_task_get_component: * @self: A #FuInstallTask * * Gets the component for this task. * * Returns: (transfer none): the component **/ XbNode * fu_install_task_get_component (FuInstallTask *self) { g_return_val_if_fail (FU_IS_INSTALL_TASK (self), NULL); return self->component; } /** * fu_install_task_get_trust_flags: * @self: A #FuInstallTask * * Gets the trust flags for this task. * * NOTE: This is only set after fu_install_task_check_requirements() has been * called successfully. * * Returns: the #FwupdReleaseFlags, e.g. #FWUPD_TRUST_FLAG_PAYLOAD **/ FwupdReleaseFlags fu_install_task_get_trust_flags (FuInstallTask *self) { g_return_val_if_fail (FU_IS_INSTALL_TASK (self), FALSE); return self->trust_flags; } /** * fu_install_task_get_is_downgrade: * @self: A #FuInstallTask * * Gets if this task is to downgrade firmware. * * NOTE: This is only set after fu_install_task_check_requirements() has been * called successfully. * * Returns: %TRUE if versions numbers are going backwards **/ gboolean fu_install_task_get_is_downgrade (FuInstallTask *self) { g_return_val_if_fail (FU_IS_INSTALL_TASK (self), FALSE); return self->is_downgrade; } /** * fu_install_task_check_requirements: * @self: A #FuInstallTask * @flags: A #FwupdInstallFlags, e.g. #FWUPD_INSTALL_FLAG_ALLOW_OLDER * @error: A #GError, or %NULL * * Checks any requirements of this task. This will typically involve checking * that the device can accept the component (the GUIDs match) and that the * device can be upgraded with this firmware version. * * Returns: %TRUE if the requirements passed **/ gboolean fu_install_task_check_requirements (FuInstallTask *self, FwupdInstallFlags flags, GError **error) { const gchar *tmp; const gchar *version; const gchar *version_release_raw; const gchar *version_lowest; gboolean matches_guid = FALSE; gint vercmp; g_autofree gchar *version_release = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) provides = NULL; g_autoptr(XbNode) release = NULL; g_return_val_if_fail (FU_IS_INSTALL_TASK (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* does this component provide a GUID the device has */ provides = xb_node_query (self->component, "provides/firmware[@type='flashed']", 0, &error_local); if (provides == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No supported devices found: %s", error_local->message); return FALSE; } for (guint i = 0; i < provides->len; i++) { XbNode *provide = g_ptr_array_index (provides, i); if (fu_device_has_guid (self->device, xb_node_get_text (provide))) { matches_guid = TRUE; break; } } if (!matches_guid) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No supported devices found"); return FALSE; } /* check the device is not locked */ if (fu_device_has_flag (self->device, FWUPD_DEVICE_FLAG_LOCKED)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s [%s] is locked", fu_device_get_name (self->device), fu_device_get_id (self->device)); return FALSE; } /* no update abilities */ if (!fu_device_has_flag (self->device, FWUPD_DEVICE_FLAG_UPDATABLE)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s [%s] does not currently allow updates", fu_device_get_name (self->device), fu_device_get_id (self->device)); return FALSE; } /* called with online update, test if device is supposed to allow this */ if ((flags & FWUPD_INSTALL_FLAG_OFFLINE) == 0 && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && fu_device_has_flag (self->device, FWUPD_DEVICE_FLAG_ONLY_OFFLINE)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s [%s] only allows offline updates", fu_device_get_name (self->device), fu_device_get_id (self->device)); return FALSE; } /* get device */ version = fu_device_get_version (self->device); if (version == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Device %s [%s] has no firmware version", fu_device_get_name (self->device), fu_device_get_id (self->device)); return FALSE; } /* get latest release */ release = xb_node_query_first (self->component, "releases/release", NULL); if (release == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "%s [%s] has no firmware update metadata", fu_device_get_name (self->device), fu_device_get_id (self->device)); return FALSE; } /* is this a downgrade or re-install */ version_release_raw = xb_node_get_attr (release, "version"); if (version_release_raw == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Release has no firmware version"); return FALSE; } /* check the version formats match if set in the release */ tmp = xb_node_query_text (self->component, "custom/value[@key='LVFS::VersionFormat']", NULL); if (tmp != NULL) { FwupdVersionFormat fmt_dev = fu_device_get_version_format (self->device); FwupdVersionFormat fmt_rel = fwupd_version_format_from_string (tmp); if (fmt_rel == FWUPD_VERSION_FORMAT_UNKNOWN && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "release version format '%s' unsupported", tmp); return FALSE; } if (fmt_dev == FWUPD_VERSION_FORMAT_UNKNOWN && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "release version format '%s' but no device version format", tmp); return FALSE; } if (fmt_dev != fmt_rel) { if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Firmware version formats were different, " "device was '%s' and release is '%s'", fwupd_version_format_to_string (fmt_dev), fwupd_version_format_to_string (fmt_rel)); return FALSE; } g_warning ("ignoring version format difference %s:%s", fwupd_version_format_to_string (fmt_dev), fwupd_version_format_to_string (fmt_rel)); } } /* compare to the lowest supported version, if it exists */ version_lowest = fu_device_get_version_lowest (self->device); if (version_lowest != NULL && fu_common_vercmp (version_lowest, version) > 0 && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_VERSION_NEWER, "Specified firmware is older than the minimum " "required version '%s < %s'", version, version_lowest); return FALSE; } /* check semver */ version_release = fu_common_version_parse_from_format (version_release_raw, fu_device_get_version_format (self->device)); vercmp = fu_common_vercmp (version, version_release); if (vercmp == 0 && (flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_VERSION_SAME, "Specified firmware is already installed '%s'", version_release); return FALSE; } self->is_downgrade = vercmp > 0; if (self->is_downgrade && (flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_VERSION_NEWER, "Specified firmware is older than installed '%s < %s'", version_release, version); return FALSE; } /* verify */ if (!fu_keyring_get_release_flags (release, &self->trust_flags, &error_local)) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_warning ("Ignoring verification for %s: %s", fu_device_get_name (self->device), error_local->message); } else { g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } } return TRUE; } /** * fu_install_task_get_action_id: * @self: A #FuEngine * * Gets the PolicyKit action ID to use for the install operation. * * Returns: string, e.g. `org.freedesktop.fwupd.update-internal-trusted` **/ const gchar * fu_install_task_get_action_id (FuInstallTask *self) { /* relax authentication checks for removable devices */ if (!fu_device_has_flag (self->device, FWUPD_DEVICE_FLAG_INTERNAL)) { if (self->is_downgrade) return "org.freedesktop.fwupd.downgrade-hotplug"; if (self->trust_flags & FWUPD_TRUST_FLAG_PAYLOAD) return "org.freedesktop.fwupd.update-hotplug-trusted"; return "org.freedesktop.fwupd.update-hotplug"; } /* internal device */ if (self->is_downgrade) return "org.freedesktop.fwupd.downgrade-internal"; if (self->trust_flags & FWUPD_TRUST_FLAG_PAYLOAD) return "org.freedesktop.fwupd.update-internal-trusted"; return "org.freedesktop.fwupd.update-internal"; } static void fu_install_task_init (FuInstallTask *self) { self->trust_flags = FWUPD_TRUST_FLAG_NONE; } static void fu_install_task_finalize (GObject *object) { FuInstallTask *self = FU_INSTALL_TASK (object); g_object_unref (self->component); if (self->device != NULL) g_object_unref (self->device); G_OBJECT_CLASS (fu_install_task_parent_class)->finalize (object); } static void fu_install_task_class_init (FuInstallTaskClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_install_task_finalize; } /** * fu_install_task_compare: * @task1: first #FuInstallTask to compare. * @task2: second #FuInstallTask to compare. * * Compares two install tasks. * * Returns: 1, 0 or -1 if @task1 is greater, equal, or less than @task2, respectively. **/ gint fu_install_task_compare (FuInstallTask *task1, FuInstallTask *task2) { FuDevice *device1 = fu_install_task_get_device (task1); FuDevice *device2 = fu_install_task_get_device (task2); if (fu_device_get_order (device1) < fu_device_get_order (device2)) return -1; if (fu_device_get_order (device1) > fu_device_get_order (device2)) return 1; return 0; } /** * fu_install_task_new: * @device: A #FuDevice * @component: a #XbNode * * Creates a new install task that may or may not be valid. * * Returns: (transfer full): the #FuInstallTask **/ FuInstallTask * fu_install_task_new (FuDevice *device, XbNode *component) { FuInstallTask *self; self = g_object_new (FU_TYPE_TASK, NULL); self->component = g_object_ref (component); if (device != NULL) self->device = g_object_ref (device); return FU_INSTALL_TASK (self); } fwupd-1.2.14/src/fu-install-task.h000066400000000000000000000017271402665037500167210ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-device.h" G_BEGIN_DECLS #define FU_TYPE_TASK (fu_install_task_get_type ()) G_DECLARE_FINAL_TYPE (FuInstallTask, fu_install_task, FU, INSTALL_TASK, GObject) FuInstallTask *fu_install_task_new (FuDevice *device, XbNode *component); FuDevice *fu_install_task_get_device (FuInstallTask *self); XbNode *fu_install_task_get_component (FuInstallTask *self); FwupdReleaseFlags fu_install_task_get_trust_flags (FuInstallTask *self); gboolean fu_install_task_get_is_downgrade (FuInstallTask *self); gboolean fu_install_task_check_requirements (FuInstallTask *self, FwupdInstallFlags flags, GError **error); const gchar *fu_install_task_get_action_id (FuInstallTask *self); gint fu_install_task_compare (FuInstallTask *task1, FuInstallTask *task2); G_END_DECLS fwupd-1.2.14/src/fu-io-channel.c000066400000000000000000000226521402665037500163230ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuIOChannel" #include "config.h" #include #include #include #include #include #include #include "fwupd-error.h" #include "fu-common.h" #include "fu-io-channel.h" struct _FuIOChannel { GObject parent_instance; gint fd; }; G_DEFINE_TYPE (FuIOChannel, fu_io_channel, G_TYPE_OBJECT) /** * fu_io_channel_unix_get_fd: * @self: a #FuIOChannel * * Gets the file descriptor for the device. * * Returns: fd, or -1 for not open. * * Since: 1.2.2 **/ gint fu_io_channel_unix_get_fd (FuIOChannel *self) { g_return_val_if_fail (FU_IS_IO_CHANNEL (self), -1); return self->fd; } /** * fu_io_channel_shutdown: * @self: a #FuIOChannel * @error: a #GError, or %NULL * * Closes the file descriptor for the device. * * Returns: %TRUE if all the FD was closed. * * Since: 1.2.2 **/ gboolean fu_io_channel_shutdown (FuIOChannel *self, GError **error) { g_return_val_if_fail (FU_IS_IO_CHANNEL (self), FALSE); if (!g_close (self->fd, error)) return FALSE; self->fd = -1; return TRUE; } static gboolean fu_io_channel_flush_input (FuIOChannel *self, GError **error) { GPollFD poll = { .fd = self->fd, .events = G_IO_IN | G_IO_ERR, }; while (g_poll (&poll, 1, 0) > 0) { gchar c; gint r = read (self->fd, &c, 1); if (r < 0 && errno != EINTR) break; } return TRUE; } /** * fu_io_channel_write_bytes: * @self: a #FuIOChannel * @bytes: buffer to write * @timeout_ms: timeout in ms * @flags: some #FuIOChannelFlags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: a #GError, or %NULL * * Writes bytes to the TTY, that will fail if exceeding @timeout_ms. * * Returns: %TRUE if all the bytes was written * * Since: 1.2.2 **/ gboolean fu_io_channel_write_bytes (FuIOChannel *self, GBytes *bytes, guint timeout_ms, FuIOChannelFlags flags, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data (bytes, &bufsz); return fu_io_channel_write_raw (self, buf, bufsz, timeout_ms, flags, error); } /** * fu_io_channel_write_raw: * @self: a #FuIOChannel * @data: buffer to write * @datasz: size of @data * @timeout_ms: timeout in ms * @flags: some #FuIOChannelFlags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: a #GError, or %NULL * * Writes bytes to the TTY, that will fail if exceeding @timeout_ms. * * Returns: %TRUE if all the bytes was written * * Since: 1.2.2 **/ gboolean fu_io_channel_write_raw (FuIOChannel *self, const guint8 *data, gsize datasz, guint timeout_ms, FuIOChannelFlags flags, GError **error) { gsize idx = 0; g_return_val_if_fail (FU_IS_IO_CHANNEL (self), FALSE); /* flush pending reads */ if (flags & FU_IO_CHANNEL_FLAG_FLUSH_INPUT) { if (!fu_io_channel_flush_input (self, error)) return FALSE; } /* blocking IO */ if (flags & FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO) { gssize wrote = write (self->fd, data, datasz); if (wrote != (gssize) datasz) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write: " "wrote %" G_GSSIZE_FORMAT " of %" G_GSIZE_FORMAT, wrote, datasz); return FALSE; } return TRUE; } /* nonblocking IO */ while (idx < datasz) { gint rc; GPollFD fds = { .fd = self->fd, .events = G_IO_OUT | G_IO_ERR, }; /* wait for data to be allowed to write without blocking */ rc = g_poll (&fds, 1, (gint) timeout_ms); if (rc == 0) break; if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to poll %i", self->fd); return FALSE; } /* we can write data */ if (fds.revents & G_IO_OUT) { gssize len = write (self->fd, data + idx, datasz - idx); if (len < 0) { if (errno == EAGAIN) { g_debug ("got EAGAIN, trying harder"); continue; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write %" G_GSIZE_FORMAT " bytes to %i: %s" , datasz, self->fd, strerror (errno)); return FALSE; } if (flags & FU_IO_CHANNEL_FLAG_SINGLE_SHOT) break; idx += len; } } return TRUE; } /** * fu_io_channel_read_bytes: * @self: a #FuIOChannel * @max_size: maximum size of the returned blob, or -1 for no limit * @timeout_ms: timeout in ms * @flags: some #FuIOChannelFlags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: a #GError, or %NULL * * Reads bytes from the TTY, that will fail if exceeding @timeout_ms. * * Returns: a #GBytes, or %NULL for error * * Since: 1.2.2 **/ GBytes * fu_io_channel_read_bytes (FuIOChannel *self, gssize max_size, guint timeout_ms, FuIOChannelFlags flags, GError **error) { GPollFD fds = { .fd = self->fd, .events = G_IO_IN | G_IO_PRI | G_IO_ERR, }; g_autoptr(GString) str = g_string_new (NULL); g_return_val_if_fail (FU_IS_IO_CHANNEL (self), NULL); /* blocking IO */ if (flags & FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO) { guint8 buf[1024]; gssize len = read (self->fd, buf, sizeof (buf)); if (len < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read %i: %s", self->fd, strerror (errno)); return NULL; } if (len > 0) g_string_append_len (str, (gchar *) buf, len); return g_bytes_new (str->str, str->len); } /* nonblocking IO */ while (TRUE) { /* wait for data to appear */ gint rc = g_poll (&fds, 1, (gint) timeout_ms); if (rc == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timeout"); return NULL; } if (rc < 0) { if (errno == EINTR) continue; g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to poll %i", self->fd); return NULL; } /* we have data to read */ if (fds.revents & G_IO_IN) { guint8 buf[1024]; gssize len = read (self->fd, buf, sizeof (buf)); if (len < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) continue; g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read %i: %s", self->fd, strerror (errno)); return NULL; } if (len > 0) g_string_append_len (str, (gchar *) buf, len); /* check maximum size */ if (max_size > 0 && str->len >= (guint) max_size) break; if (flags & FU_IO_CHANNEL_FLAG_SINGLE_SHOT) break; continue; } if (fds.revents & G_IO_ERR) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "error condition"); return NULL; } if (fds.revents & G_IO_HUP) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "connection hung up"); return NULL; } if (fds.revents & G_IO_NVAL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid request"); return NULL; } } /* no data */ if (str->len == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "no data received from device in %ums", timeout_ms); return NULL; } /* return blob */ return g_bytes_new (str->str, str->len); } /** * fu_io_channel_read_raw: * @self: a #FuIOChannel * @buf: buffer, or %NULL * @bufsz: size of @buf * @bytes_read: (out): data written to @buf, or %NULL * @timeout_ms: timeout in ms * @flags: some #FuIOChannelFlags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: a #GError, or %NULL * * Reads bytes from the TTY, that will fail if exceeding @timeout_ms. * * Returns: a #GBytes, or %NULL for error * * Since: 1.2.2 **/ gboolean fu_io_channel_read_raw (FuIOChannel *self, guint8 *buf, gsize bufsz, gsize *bytes_read, guint timeout_ms, FuIOChannelFlags flags, GError **error) { const guint8 *tmpbuf = NULL; gsize bytes_read_tmp; g_autoptr(GBytes) tmp = NULL; g_return_val_if_fail (FU_IS_IO_CHANNEL (self), FALSE); tmp = fu_io_channel_read_bytes (self, bufsz, timeout_ms, flags, error); if (tmp == NULL) return FALSE; tmpbuf = g_bytes_get_data (tmp, &bytes_read_tmp); if (tmpbuf != NULL) memcpy (buf, tmpbuf, bytes_read_tmp); if (bytes_read != NULL) *bytes_read = bytes_read_tmp; return TRUE; } static void fu_io_channel_finalize (GObject *object) { FuIOChannel *self = FU_IO_CHANNEL (object); if (self->fd != -1) g_close (self->fd, NULL); G_OBJECT_CLASS (fu_io_channel_parent_class)->finalize (object); } static void fu_io_channel_class_init (FuIOChannelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_io_channel_finalize; } static void fu_io_channel_init (FuIOChannel *self) { self->fd = -1; } /** * fu_io_channel_unix_new: * @fd: file descriptor * * Creates a new object to write and read from. * * Returns: a #FuIOChannel **/ FuIOChannel * fu_io_channel_unix_new (gint fd) { FuIOChannel *self; self = g_object_new (FU_TYPE_IO_CHANNEL, NULL); self->fd = fd; return FU_IO_CHANNEL (self); } /** * fu_io_channel_new_file: * @filename: device file * @error: a #GError, or %NULL * * Creates a new object to write and read from. * * Returns: a #FuIOChannel **/ FuIOChannel * fu_io_channel_new_file (const gchar *filename, GError **error) { gint fd = g_open (filename, O_RDWR | O_NONBLOCK, S_IRWXU); if (fd < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to open %s", filename); return NULL; } return fu_io_channel_unix_new (fd); } fwupd-1.2.14/src/fu-io-channel.h000066400000000000000000000036531402665037500163300ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FU_TYPE_IO_CHANNEL (fu_io_channel_get_type ()) G_DECLARE_FINAL_TYPE (FuIOChannel, fu_io_channel, FU, IO_CHANNEL, GObject) /** * FuIOChannelFlags: * @FU_IO_CHANNEL_FLAG_NONE: No flags are set * @FU_IO_CHANNEL_FLAG_SINGLE_SHOT: Only one read or write is expected * @FU_IO_CHANNEL_FLAG_FLUSH_INPUT: Flush pending input before writing * @FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO: Block waiting for the TTY * * The flags used when reading data from the TTY. **/ typedef enum { FU_IO_CHANNEL_FLAG_NONE = 0, /* Since: 1.2.2 */ FU_IO_CHANNEL_FLAG_SINGLE_SHOT = 1 << 0, /* Since: 1.2.2 */ FU_IO_CHANNEL_FLAG_FLUSH_INPUT = 1 << 1, /* Since: 1.2.2 */ FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO = 1 << 2, /* Since: 1.2.2 */ /*< private >*/ FU_IO_CHANNEL_FLAG_LAST } FuIOChannelFlags; FuIOChannel *fu_io_channel_unix_new (gint fd); FuIOChannel *fu_io_channel_new_file (const gchar *filename, GError **error); gint fu_io_channel_unix_get_fd (FuIOChannel *self); gboolean fu_io_channel_shutdown (FuIOChannel *self, GError **error); gboolean fu_io_channel_write_raw (FuIOChannel *self, const guint8 *data, gsize datasz, guint timeout_ms, FuIOChannelFlags flags, GError **error); gboolean fu_io_channel_read_raw (FuIOChannel *self, guint8 *buf, gsize bufsz, gsize *bytes_read, guint timeout_ms, FuIOChannelFlags flags, GError **error); gboolean fu_io_channel_write_bytes (FuIOChannel *self, GBytes *bytes, guint timeout_ms, FuIOChannelFlags flags, GError **error); GBytes *fu_io_channel_read_bytes (FuIOChannel *self, gssize max_size, guint timeout_ms, FuIOChannelFlags flags, GError **error); G_END_DECLS fwupd-1.2.14/src/fu-keyring-gpg.c000066400000000000000000000216211402665037500165240ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuKeyring" #include "config.h" #include #include "fwupd-error.h" #include "fu-common.h" #include "fu-keyring-gpg.h" struct _FuKeyringGpg { FuKeyring parent_instance; gpgme_ctx_t ctx; }; G_DEFINE_TYPE (FuKeyringGpg, fu_keyring_gpg, FU_TYPE_KEYRING) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gpgme_data_t, gpgme_data_release, NULL) static gboolean fu_keyring_gpg_add_public_key (FuKeyringGpg *self, const gchar *filename, GError **error) { gpgme_error_t rc; gpgme_import_result_t result; gpgme_import_status_t s; g_auto(gpgme_data_t) data = NULL; /* import public key */ g_debug ("Adding GnuPG public key %s", filename); rc = gpgme_data_new_from_file (&data, filename, 1); if (rc != GPG_ERR_NO_ERROR) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to load %s: %s", filename, gpgme_strerror (rc)); return FALSE; } rc = gpgme_op_import (self->ctx, data); if (rc != GPG_ERR_NO_ERROR) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to import %s: %s", filename, gpgme_strerror (rc)); return FALSE; } /* print what keys were imported */ result = gpgme_op_import_result (self->ctx); for (s = result->imports; s != NULL; s = s->next) { g_debug ("importing key %s [%u] %s", s->fpr, s->status, gpgme_strerror (s->result)); } /* make sure keys were really imported */ if (result->imported == 0 && result->unchanged == 0) { g_debug("imported: %d, unchanged: %d, not_imported: %d", result->imported, result->unchanged, result->not_imported); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "key import failed %s", filename); return FALSE; } return TRUE; } static gboolean fu_keyring_gpg_setup (FuKeyring *keyring, GError **error) { FuKeyringGpg *self = FU_KEYRING_GPG (keyring); gpgme_error_t rc; g_autofree gchar *gpg_home = NULL; g_autofree gchar *localstatedir = NULL; if (self->ctx != NULL) return TRUE; /* startup gpgme */ rc = gpg_err_init (); if (rc != GPG_ERR_NO_ERROR) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to init: %s", gpgme_strerror (rc)); return FALSE; } /* create a new GPG context */ rc = gpgme_new (&self->ctx); if (rc != GPG_ERR_NO_ERROR) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to create context: %s", gpgme_strerror (rc)); return FALSE; } /* set the protocol */ rc = gpgme_set_protocol (self->ctx, GPGME_PROTOCOL_OpenPGP); if (rc != GPG_ERR_NO_ERROR) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to set protocol: %s", gpgme_strerror (rc)); return FALSE; } /* set a custom home directory */ localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); gpg_home = g_build_filename (localstatedir, "gnupg", NULL); if (g_mkdir_with_parents (gpg_home, 0700) < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to create %s", gpg_home); return FALSE; } g_debug ("Using keyring at %s", gpg_home); rc = gpgme_ctx_set_engine_info (self->ctx, GPGME_PROTOCOL_OpenPGP, NULL, gpg_home); if (rc != GPG_ERR_NO_ERROR) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to set protocol: %s", gpgme_strerror (rc)); return FALSE; } /* enable armor mode */ gpgme_set_armor (self->ctx, TRUE); return TRUE; } static gboolean fu_keyring_gpg_add_public_keys (FuKeyring *keyring, const gchar *path, GError **error) { FuKeyringGpg *self = FU_KEYRING_GPG (keyring); const gchar *fn_tmp; g_autoptr(GDir) dir = NULL; /* search all the public key files */ dir = g_dir_open (path, 0, error); if (dir == NULL) return FALSE; while ((fn_tmp = g_dir_read_name (dir)) != NULL) { g_autofree gchar *path_tmp = NULL; if (!g_str_has_prefix (fn_tmp, "GPG-KEY-")) continue; path_tmp = g_build_filename (path, fn_tmp, NULL); if (!fu_keyring_gpg_add_public_key (self, path_tmp, error)) return FALSE; } return TRUE; } static gboolean fu_keyring_gpg_check_signature (gpgme_signature_t signature, GError **error) { gboolean ret = FALSE; /* look at the signature status */ switch (gpgme_err_code (signature->status)) { case GPG_ERR_NO_ERROR: ret = TRUE; break; case GPG_ERR_SIG_EXPIRED: case GPG_ERR_KEY_EXPIRED: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "valid signature '%s' has expired", signature->fpr); break; case GPG_ERR_CERT_REVOKED: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "valid signature '%s' has been revoked", signature->fpr); break; case GPG_ERR_BAD_SIGNATURE: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "'%s' is not a valid signature", signature->fpr); break; case GPG_ERR_NO_PUBKEY: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "Could not check signature '%s' as no public key", signature->fpr); break; default: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "gpgme failed to verify signature '%s'", signature->fpr); break; } return ret; } static FuKeyringResult * fu_keyring_gpg_verify_data (FuKeyring *keyring, GBytes *blob, GBytes *blob_signature, FuKeyringVerifyFlags flags, GError **error) { FuKeyringGpg *self = FU_KEYRING_GPG (keyring); gpgme_error_t rc; gpgme_signature_t s; gpgme_verify_result_t result; gint64 timestamp_newest = 0; g_auto(gpgme_data_t) data = NULL; g_auto(gpgme_data_t) sig = NULL; g_autoptr(GString) authority_newest = g_string_new (NULL); /* not supported */ if (flags & FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no GPG client certificate support"); return NULL; } /* load file data */ rc = gpgme_data_new_from_mem (&data, g_bytes_get_data (blob, NULL), g_bytes_get_size (blob), 0); if (rc != GPG_ERR_NO_ERROR) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to load data: %s", gpgme_strerror (rc)); return NULL; } rc = gpgme_data_new_from_mem (&sig, g_bytes_get_data (blob_signature, NULL), g_bytes_get_size (blob_signature), 0); if (rc != GPG_ERR_NO_ERROR) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to load signature: %s", gpgme_strerror (rc)); return NULL; } /* verify */ rc = gpgme_op_verify (self->ctx, sig, data, NULL); if (rc != GPG_ERR_NO_ERROR) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to verify data: %s", gpgme_strerror (rc)); return NULL; } /* verify the result */ result = gpgme_op_verify_result (self->ctx); if (result == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no result record from libgpgme"); return NULL; } if (result->signatures == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no signatures from libgpgme"); return NULL; } /* look at each signature */ for (s = result->signatures; s != NULL ; s = s->next ) { g_debug ("returned signature fingerprint %s", s->fpr); if (!fu_keyring_gpg_check_signature (s, error)) return NULL; /* save details about the key for the result */ if ((gint64) s->timestamp > timestamp_newest) { timestamp_newest = (gint64) s->timestamp; g_string_assign (authority_newest, s->fpr); } } return FU_KEYRING_RESULT (g_object_new (FU_TYPE_KEYRING_RESULT, "timestamp", timestamp_newest, "authority", authority_newest->str, NULL)); } static void fu_keyring_gpg_finalize (GObject *object) { FuKeyringGpg *self = FU_KEYRING_GPG (object); if (self->ctx != NULL) gpgme_release (self->ctx); G_OBJECT_CLASS (fu_keyring_gpg_parent_class)->finalize (object); } static void fu_keyring_gpg_class_init (FuKeyringGpgClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuKeyringClass *klass_app = FU_KEYRING_CLASS (klass); klass_app->setup = fu_keyring_gpg_setup; klass_app->add_public_keys = fu_keyring_gpg_add_public_keys; klass_app->verify_data = fu_keyring_gpg_verify_data; object_class->finalize = fu_keyring_gpg_finalize; } static void fu_keyring_gpg_init (FuKeyringGpg *self) { FuKeyring *keyring = FU_KEYRING (self); g_autofree gchar *name = NULL; name = g_strdup_printf ("gpgme-v%s", gpgme_check_version (NULL)); fu_keyring_set_name (keyring, name); } FuKeyring * fu_keyring_gpg_new (void) { return FU_KEYRING (g_object_new (FU_TYPE_KEYRING_GPG, NULL)); } fwupd-1.2.14/src/fu-keyring-gpg.h000066400000000000000000000005411402665037500165270ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-keyring.h" G_BEGIN_DECLS #define FU_TYPE_KEYRING_GPG (fu_keyring_gpg_get_type ()) G_DECLARE_FINAL_TYPE (FuKeyringGpg, fu_keyring_gpg, FU, KEYRING_GPG, FuKeyring) FuKeyring *fu_keyring_gpg_new (void); G_END_DECLS fwupd-1.2.14/src/fu-keyring-pkcs7.c000066400000000000000000000554061402665037500170060ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuKeyring" #include "config.h" #include #include #include #include "fu-common.h" #include "fu-keyring-pkcs7.h" #include "fwupd-error.h" struct _FuKeyringPkcs7 { FuKeyring parent_instance; gnutls_x509_trust_list_t tl; }; G_DEFINE_TYPE (FuKeyringPkcs7, fu_keyring_pkcs7, FU_TYPE_KEYRING) typedef guchar gnutls_data_t; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pkcs7_t, gnutls_pkcs7_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_privkey_t, gnutls_privkey_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pubkey_t, gnutls_pubkey_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_crt_t, gnutls_x509_crt_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_dn_t, gnutls_x509_dn_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_privkey_t, gnutls_x509_privkey_deinit, NULL) #ifdef HAVE_GNUTLS_3_6_0 G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_spki_t, gnutls_x509_spki_deinit, NULL) #endif G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_data_t, gnutls_free) #pragma clang diagnostic pop static gnutls_x509_crt_t fu_keyring_pkcs7_load_crt_from_filename (const gchar *filename, gnutls_x509_crt_fmt_t format, GError **error) { gnutls_datum_t d = { 0 }; gsize bufsz = 0; int rc; g_autofree gchar *buf = NULL; g_auto(gnutls_x509_crt_t) crt = NULL; /* create certificate */ rc = gnutls_x509_crt_init (&crt); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "crt_init: %s [%i]", gnutls_strerror (rc), rc); return NULL; } /* import the certificate */ if (!g_file_get_contents (filename, &buf, &bufsz, error)) return NULL; d.size = bufsz; d.data = (unsigned char *) buf; rc = gnutls_x509_crt_import (crt, &d, GNUTLS_X509_FMT_PEM); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "crt_import: %s [%i]", gnutls_strerror (rc), rc); return NULL; } return g_steal_pointer (&crt); } static gboolean fu_keyring_pkcs7_add_public_key (FuKeyringPkcs7 *self, const gchar *filename, gnutls_x509_crt_fmt_t format, GError **error) { guint key_usage = 0; int rc; g_auto(gnutls_x509_crt_t) crt = NULL; /* load file and add to the trust list */ g_debug ("trying to load certificate from %s", filename); crt = fu_keyring_pkcs7_load_crt_from_filename (filename, format, error); if (crt == NULL) return FALSE; rc = gnutls_x509_crt_get_key_usage (crt, &key_usage, NULL); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "failed to get key usage: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } if ((key_usage & GNUTLS_KEY_DIGITAL_SIGNATURE) == 0 && (key_usage & GNUTLS_KEY_KEY_CERT_SIGN) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "certificate %s not suitable for use [0x%x]", filename, key_usage); return FALSE; } rc = gnutls_x509_trust_list_add_cas (self->tl, &crt, 1, 0); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "failed to add to trust list: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } g_debug ("loaded %i certificates", rc); /* confusingly the trust list does not copy the certificate */ crt = NULL; return TRUE; } static gboolean fu_keyring_pkcs7_add_public_keys (FuKeyring *keyring, const gchar *path, GError **error) { FuKeyringPkcs7 *self = FU_KEYRING_PKCS7 (keyring); const gchar *fn_tmp; g_autoptr(GDir) dir = NULL; /* search all the public key files */ dir = g_dir_open (path, 0, error); if (dir == NULL) return FALSE; while ((fn_tmp = g_dir_read_name (dir)) != NULL) { g_autofree gchar *path_tmp = NULL; path_tmp = g_build_filename (path, fn_tmp, NULL); if (g_str_has_suffix (fn_tmp, ".pem")) { if (!fu_keyring_pkcs7_add_public_key (self, path_tmp, GNUTLS_X509_FMT_PEM, error)) return FALSE; } if (g_str_has_suffix (fn_tmp, ".cer") || g_str_has_suffix (fn_tmp, ".crt") || g_str_has_suffix (fn_tmp, ".der")) { if (!fu_keyring_pkcs7_add_public_key (self, path_tmp, GNUTLS_X509_FMT_DER, error)) return FALSE; } } return TRUE; } static gnutls_privkey_t fu_keyring_pkcs7_load_privkey (FuKeyringPkcs7 *self, GError **error) { int rc; gnutls_datum_t d = { 0 }; gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *fn = NULL; g_autofree gchar *localstatedir = NULL; g_auto(gnutls_privkey_t) key = NULL; /* load the private key */ rc = gnutls_privkey_init (&key); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "privkey_init: %s [%i]", gnutls_strerror (rc), rc); return NULL; } localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); fn = g_build_filename (localstatedir, "pki", "secret.key", NULL); if (!g_file_get_contents (fn, &buf, &bufsz, error)) return NULL; d.size = bufsz; d.data = (unsigned char *) buf; rc = gnutls_privkey_import_x509_raw (key, &d, GNUTLS_X509_FMT_PEM, NULL, 0); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "privkey_import_x509_raw: %s [%i]", gnutls_strerror (rc), rc); return NULL; } return g_steal_pointer (&key); } static gnutls_x509_crt_t fu_keyring_pkcs7_load_client_certificate (FuKeyringPkcs7 *self, GError **error) { g_autofree gchar *filename = NULL; g_autofree gchar *localstatedir = NULL; localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); filename = g_build_filename (localstatedir, "pki", "client.pem", NULL); return fu_keyring_pkcs7_load_crt_from_filename (filename, GNUTLS_X509_FMT_PEM, error); } static gnutls_pubkey_t fu_keyring_pkcs7_load_pubkey_from_privkey (gnutls_privkey_t privkey, GError **error) { g_auto(gnutls_pubkey_t) pubkey = NULL; int rc; /* get the public key part of the private key */ rc = gnutls_pubkey_init (&pubkey); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "pubkey_init: %s [%i]", gnutls_strerror (rc), rc); return NULL; } rc = gnutls_pubkey_import_privkey (pubkey, privkey, 0, 0); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "pubkey_import_privkey: %s [%i]", gnutls_strerror (rc), rc); return NULL; } /* success */ return g_steal_pointer (&pubkey); } /* generates a private key just like: * `certtool --generate-privkey` */ static gboolean fu_keyring_pkcs7_ensure_private_key (FuKeyringPkcs7 *self, GError **error) { #ifdef HAVE_GNUTLS_3_6_0 gnutls_datum_t d = { 0 }; int bits; int key_type = GNUTLS_PK_RSA; int rc; g_autofree gchar *fn = NULL; g_autofree gchar *localstatedir = NULL; g_auto(gnutls_x509_privkey_t) key = NULL; g_auto(gnutls_x509_spki_t) spki = NULL; g_autoptr(GFile) file = NULL; g_autoptr(gnutls_data_t) d_payload = NULL; /* check exists */ localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); fn = g_build_filename (localstatedir, "pki", "secret.key", NULL); if (g_file_test (fn, G_FILE_TEST_EXISTS)) return TRUE; /* initialize key and SPKI */ rc = gnutls_x509_privkey_init (&key); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "privkey_init: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } rc = gnutls_x509_spki_init (&spki); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "spki_init: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* generate key */ bits = gnutls_sec_param_to_pk_bits (key_type, GNUTLS_SEC_PARAM_HIGH); g_debug ("generating a %d bit %s private key...", bits, gnutls_pk_algorithm_get_name (key_type)); rc = gnutls_x509_privkey_generate2(key, key_type, bits, 0, NULL, 0); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "privkey_generate2: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } rc = gnutls_x509_privkey_verify_params (key); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "privkey_verify_params: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* create parents if required */ if (!fu_common_mkdir_parent (fn, error)) return FALSE; /* save to file */ rc = gnutls_x509_privkey_export2 (key, GNUTLS_X509_FMT_PEM, &d); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "privkey_export2: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } d_payload = d.data; file = g_file_new_for_path (fn); return g_file_replace_contents (file, (const char *) d_payload, d.size, NULL, FALSE, G_FILE_CREATE_PRIVATE, NULL, NULL, error); #else g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot build private key as GnuTLS version is too old"); return FALSE; #endif } /* generates a self signed certificate just like: * `certtool --generate-self-signed --load-privkey priv.pem` */ static gboolean fu_keyring_pkcs7_ensure_client_certificate (FuKeyringPkcs7 *self, GError **error) { int rc; gnutls_datum_t d = { 0 }; guchar sha1buf[20]; gsize sha1bufsz = sizeof(sha1buf); g_autofree gchar *fn = NULL; g_autofree gchar *localstatedir = NULL; g_auto(gnutls_privkey_t) key = NULL; g_auto(gnutls_pubkey_t) pubkey = NULL; g_auto(gnutls_x509_crt_t) crt = NULL; g_autoptr(gnutls_data_t) d_payload = NULL; /* check exists */ localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); fn = g_build_filename (localstatedir, "pki", "client.pem", NULL); if (g_file_test (fn, G_FILE_TEST_EXISTS)) return TRUE; /* ensure the private key exists */ if (!fu_keyring_pkcs7_ensure_private_key (self, error)) { g_prefix_error (error, "failed to generate private key: "); return FALSE; } /* load private key */ key = fu_keyring_pkcs7_load_privkey (self, error); if (key == NULL) return FALSE; /* load the public key from the private key */ pubkey = fu_keyring_pkcs7_load_pubkey_from_privkey (key, error); if (pubkey == NULL) return FALSE; /* create certificate */ rc = gnutls_x509_crt_init (&crt); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "crt_init: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* set public key */ rc = gnutls_x509_crt_set_pubkey (crt, pubkey); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "crt_set_pubkey: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* set positive random serial number */ rc = gnutls_rnd (GNUTLS_RND_NONCE, sha1buf, sizeof(sha1buf)); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "gnutls_rnd: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } sha1buf[0] &= 0x7f; rc = gnutls_x509_crt_set_serial(crt, sha1buf, sizeof(sha1buf)); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "crt_set_serial: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* set activation */ rc = gnutls_x509_crt_set_activation_time (crt, time (NULL)); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "set_activation_time: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* set expiration */ rc = gnutls_x509_crt_set_expiration_time (crt, (time_t) -1); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "set_expiration_time: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* set basic constraints */ rc = gnutls_x509_crt_set_basic_constraints (crt, 0, -1); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "set_basic_constraints: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* set usage */ rc = gnutls_x509_crt_set_key_usage (crt, GNUTLS_KEY_DIGITAL_SIGNATURE); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "set_key_usage: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* set subject key ID */ rc = gnutls_x509_crt_get_key_id (crt, GNUTLS_KEYID_USE_SHA1, sha1buf, &sha1bufsz); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "get_key_id: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } rc = gnutls_x509_crt_set_subject_key_id (crt, sha1buf, sha1bufsz); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "set_subject_key_id: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* set version */ rc = gnutls_x509_crt_set_version (crt, 3); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "error setting certificate version: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* self-sign certificate */ rc = gnutls_x509_crt_privkey_sign (crt, crt, key, GNUTLS_DIG_SHA256, 0); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "crt_privkey_sign: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } /* export to file */ rc = gnutls_x509_crt_export2 (crt, GNUTLS_X509_FMT_PEM, &d); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "crt_export2: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } d_payload = d.data; return g_file_set_contents (fn, (const gchar *) d_payload, d.size, error); } static gboolean fu_keyring_pkcs7_setup (FuKeyring *keyring, GError **error) { FuKeyringPkcs7 *self = FU_KEYRING_PKCS7 (keyring); int rc; if (self->tl != NULL) return TRUE; /* create trust list, a bit like a keyring */ rc = gnutls_x509_trust_list_init (&self->tl, 0); if (rc != GNUTLS_E_SUCCESS) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "failed to create trust list: %s [%i]", gnutls_strerror (rc), rc); return FALSE; } return TRUE; } static void _gnutls_datum_deinit (gnutls_datum_t *d) { gnutls_free (d->data); gnutls_free (d); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_datum_t, _gnutls_datum_deinit) #pragma clang diagnostic pop static gchar * fu_keyring_pkcs7_datum_to_dn_str (const gnutls_datum_t *raw) { g_auto(gnutls_x509_dn_t) dn = NULL; g_autoptr(gnutls_datum_t) str = NULL; int rc; rc = gnutls_x509_dn_init (&dn); if (rc < 0) return NULL; rc = gnutls_x509_dn_import (dn, raw); if (rc < 0) return NULL; str = (gnutls_datum_t *) gnutls_malloc (sizeof (gnutls_datum_t)); str->data = NULL; rc = gnutls_x509_dn_get_str2 (dn, str, 0); if (rc < 0) return NULL; return g_strndup ((const gchar *) str->data, str->size); } /* verifies a detached signature just like: * `certtool --p7-verify --load-certificate client.pem --infile=test.p7b` */ static FuKeyringResult * fu_keyring_pkcs7_verify_data (FuKeyring *keyring, GBytes *blob, GBytes *blob_signature, FuKeyringVerifyFlags flags, GError **error) { FuKeyringPkcs7 *self = FU_KEYRING_PKCS7 (keyring); gnutls_datum_t datum = { 0 }; gint64 timestamp_newest = 0; int count; int rc; g_auto(gnutls_pkcs7_t) pkcs7 = NULL; g_auto(gnutls_x509_crt_t) crt = NULL; g_autoptr(GString) authority_newest = g_string_new (NULL); /* startup */ rc = gnutls_pkcs7_init (&pkcs7); if (rc != GNUTLS_E_SUCCESS) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "failed to init pkcs7: %s [%i]", gnutls_strerror (rc), rc); return NULL; } /* import the signature */ datum.data = (guchar *) g_bytes_get_data (blob_signature, NULL); datum.size = g_bytes_get_size (blob_signature); rc = gnutls_pkcs7_import (pkcs7, &datum, GNUTLS_X509_FMT_PEM); if (rc != GNUTLS_E_SUCCESS) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "failed to import the PKCS7 signature: %s [%i]", gnutls_strerror (rc), rc); return NULL; } /* verify the blob */ datum.data = (guchar *) g_bytes_get_data (blob, NULL); datum.size = g_bytes_get_size (blob); count = gnutls_pkcs7_get_signature_count (pkcs7); g_debug ("got %i PKCS7 signatures", count); if (count == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "no PKCS7 signatures found"); return NULL; } /* use client certificate */ if (flags & FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT) { if (!fu_keyring_pkcs7_ensure_client_certificate (self, error)) { g_prefix_error (error, "failed to generate client certificate: "); return NULL; } crt = fu_keyring_pkcs7_load_client_certificate (self, error); if (crt == NULL) return NULL; } for (gint i = 0; i < count; i++) { gnutls_pkcs7_signature_info_st info; gint64 signing_time = 0; gnutls_certificate_verify_flags verify_flags = 0; /* use with care */ if (flags & FU_KEYRING_VERIFY_FLAG_DISABLE_TIME_CHECKS) { g_debug ("WARNING: disabling time checks"); verify_flags |= GNUTLS_VERIFY_DISABLE_TIME_CHECKS; verify_flags |= GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS; } /* verify the data against the detached signature */ if (flags & FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT) { rc = gnutls_pkcs7_verify_direct (pkcs7, crt, i, &datum, 0); } else { rc = gnutls_pkcs7_verify (pkcs7, self->tl, NULL, /* vdata */ 0, /* vdata_size */ i, /* index */ &datum, /* data */ verify_flags); } if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "failed to verify data: %s [%i]", gnutls_strerror (rc), rc); return NULL; } /* save details about the key for the result */ rc = gnutls_pkcs7_get_signature_info (pkcs7, i, &info); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "failed to get signature info: %s [%i]", gnutls_strerror (rc), rc); return NULL; } signing_time = info.signing_time > 0 ? (gint64) info.signing_time : 1; if (signing_time > timestamp_newest) { g_autofree gchar *dn = NULL; timestamp_newest = signing_time; dn = fu_keyring_pkcs7_datum_to_dn_str (&info.issuer_dn); if (dn != NULL) g_string_assign (authority_newest, dn); } gnutls_pkcs7_signature_info_deinit (&info); } /* success */ return FU_KEYRING_RESULT (g_object_new (FU_TYPE_KEYRING_RESULT, "timestamp", timestamp_newest, "authority", authority_newest->str, NULL)); } /* creates a detached signature just like: * `certtool --p7-detached-sign --load-certificate client.pem \ * --load-privkey secret.pem --outfile=test.p7b` */ static GBytes * fu_keyring_pkcs7_sign_data (FuKeyring *keyring, GBytes *blob, FuKeyringSignFlags flags, GError **error) { FuKeyringPkcs7 *self = FU_KEYRING_PKCS7 (keyring); gnutls_datum_t d = { 0 }; gnutls_digest_algorithm_t dig = GNUTLS_DIG_NULL; guint gnutls_flags = 0; int rc; g_auto(gnutls_pkcs7_t) pkcs7 = NULL; g_auto(gnutls_privkey_t) key = NULL; g_auto(gnutls_pubkey_t) pubkey = NULL; g_auto(gnutls_x509_crt_t) crt = NULL; g_autoptr(gnutls_data_t) d_payload = NULL; /* ensure the client certificate exists */ if (!fu_keyring_pkcs7_ensure_client_certificate (self, error)) { g_prefix_error (error, "failed to generate client certificate: "); return NULL; } /* import the keys */ crt = fu_keyring_pkcs7_load_client_certificate (self, error); if (crt == NULL) return NULL; key = fu_keyring_pkcs7_load_privkey (self, error); if (key == NULL) return NULL; /* get the digest algorithm from the publix key */ pubkey = fu_keyring_pkcs7_load_pubkey_from_privkey (key, error); if (pubkey == NULL) return NULL; rc = gnutls_pubkey_get_preferred_hash_algorithm (pubkey, &dig, NULL); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "preferred_hash_algorithm: %s [%i]", gnutls_strerror (rc), rc); return NULL; } /* create container */ rc = gnutls_pkcs7_init (&pkcs7); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "pkcs7_init: %s [%i]", gnutls_strerror (rc), rc); return NULL; } /* sign data */ d.data = (unsigned char *) g_bytes_get_data (blob, NULL); d.size = g_bytes_get_size (blob); if (flags & FU_KEYRING_SIGN_FLAG_ADD_TIMESTAMP) gnutls_flags |= GNUTLS_PKCS7_INCLUDE_TIME; if (flags & FU_KEYRING_SIGN_FLAG_ADD_CERT) gnutls_flags |= GNUTLS_PKCS7_INCLUDE_CERT; rc = gnutls_pkcs7_sign (pkcs7, crt, key, &d, NULL, NULL, dig, gnutls_flags); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "pkcs7_sign: %s [%i]", gnutls_strerror (rc), rc); return NULL; } /* set certificate */ if (flags & FU_KEYRING_SIGN_FLAG_ADD_CERT) { rc = gnutls_pkcs7_set_crt (pkcs7, crt); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "pkcs7_set_cr: %s", gnutls_strerror (rc)); return NULL; } } /* export */ rc = gnutls_pkcs7_export2 (pkcs7, GNUTLS_X509_FMT_PEM, &d); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "pkcs7_export: %s", gnutls_strerror (rc)); return NULL; } d_payload = d.data; return g_bytes_new (d_payload, d.size); } static void fu_keyring_pkcs7_finalize (GObject *object) { FuKeyringPkcs7 *self = FU_KEYRING_PKCS7 (object); gnutls_x509_trust_list_deinit (self->tl, 1); G_OBJECT_CLASS (fu_keyring_pkcs7_parent_class)->finalize (object); } static void fu_keyring_pkcs7_class_init (FuKeyringPkcs7Class *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuKeyringClass *klass_app = FU_KEYRING_CLASS (klass); klass_app->setup = fu_keyring_pkcs7_setup; klass_app->add_public_keys = fu_keyring_pkcs7_add_public_keys; klass_app->sign_data = fu_keyring_pkcs7_sign_data; klass_app->verify_data = fu_keyring_pkcs7_verify_data; object_class->finalize = fu_keyring_pkcs7_finalize; } static void fu_keyring_pkcs7_init (FuKeyringPkcs7 *self) { FuKeyring *keyring = FU_KEYRING (self); g_autofree gchar *name = NULL; name = g_strdup_printf ("gnutls-v%s", gnutls_check_version (NULL)); fu_keyring_set_name (keyring, name); } FuKeyring * fu_keyring_pkcs7_new (void) { return FU_KEYRING (g_object_new (FU_TYPE_KEYRING_PKCS7, NULL)); } fwupd-1.2.14/src/fu-keyring-pkcs7.h000066400000000000000000000005551402665037500170060ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-keyring.h" G_BEGIN_DECLS #define FU_TYPE_KEYRING_PKCS7 (fu_keyring_pkcs7_get_type ()) G_DECLARE_FINAL_TYPE (FuKeyringPkcs7, fu_keyring_pkcs7, FU, KEYRING_PKCS7, FuKeyring) FuKeyring *fu_keyring_pkcs7_new (void); G_END_DECLS fwupd-1.2.14/src/fu-keyring-result.c000066400000000000000000000052271402665037500172710ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuKeyring" #include "config.h" #include "fwupd-error.h" #include "fu-keyring-result.h" struct _FuKeyringResult { GObject parent_instance; gint64 timestamp; gchar *authority; }; G_DEFINE_TYPE (FuKeyringResult, fu_keyring_result, G_TYPE_OBJECT) enum { PROP_0, PROP_TIMESTAMP, PROP_AUTHORITY, PROP_LAST }; gint64 fu_keyring_result_get_timestamp (FuKeyringResult *self) { g_return_val_if_fail (FU_IS_KEYRING_RESULT (self), 0); return self->timestamp; } const gchar * fu_keyring_result_get_authority (FuKeyringResult *self) { g_return_val_if_fail (FU_IS_KEYRING_RESULT (self), NULL); return self->authority; } static void fu_keyring_result_finalize (GObject *object) { FuKeyringResult *self = FU_KEYRING_RESULT (object); g_free (self->authority); G_OBJECT_CLASS (fu_keyring_result_parent_class)->finalize (object); } static void fu_keyring_result_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuKeyringResult *self = FU_KEYRING_RESULT (object); switch (prop_id) { case PROP_TIMESTAMP: g_value_set_int64 (value, self->timestamp); break; case PROP_AUTHORITY: g_value_set_string (value, self->authority); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_keyring_result_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuKeyringResult *self = FU_KEYRING_RESULT (object); switch (prop_id) { case PROP_TIMESTAMP: self->timestamp = g_value_get_int64 (value); break; case PROP_AUTHORITY: self->authority = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_keyring_result_class_init (FuKeyringResultClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; object_class->get_property = fu_keyring_result_get_property; object_class->set_property = fu_keyring_result_set_property; object_class->finalize = fu_keyring_result_finalize; pspec = g_param_spec_int64 ("timestamp", NULL, NULL, 0, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_TIMESTAMP, pspec); pspec = g_param_spec_string ("authority", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_AUTHORITY, pspec); } static void fu_keyring_result_init (FuKeyringResult *self) { } fwupd-1.2.14/src/fu-keyring-result.h000066400000000000000000000007201402665037500172670ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FU_TYPE_KEYRING_RESULT (fu_keyring_result_get_type ()) G_DECLARE_FINAL_TYPE (FuKeyringResult, fu_keyring_result, FU, KEYRING_RESULT, GObject) gint64 fu_keyring_result_get_timestamp (FuKeyringResult *self); const gchar *fu_keyring_result_get_authority (FuKeyringResult *self); G_END_DECLS fwupd-1.2.14/src/fu-keyring-utils.c000066400000000000000000000112601402665037500171050ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuKeyring" #include #include "fwupd-error.h" #include "fu-common.h" #include "fu-keyring-utils.h" #ifdef ENABLE_GPG #include "fu-keyring-gpg.h" #endif #ifdef ENABLE_PKCS7 #include "fu-keyring-pkcs7.h" #endif /** * fu_keyring_create_for_kind: * @kind: A #FwupdKeyringKind, e.g. %FWUPD_KEYRING_KIND_GPG * @error: A #GError, or %NULL * * Creates a new keyring of the specified kind. * * If the keyring cannot be created (for example, if fwupd is compiled without * GPG support) then an error is returned. * * Returns: (transfer full): a new #FuKeyring, or %NULL for error **/ FuKeyring * fu_keyring_create_for_kind (FwupdKeyringKind kind, GError **error) { if (kind == FWUPD_KEYRING_KIND_GPG) { #ifdef ENABLE_GPG return fu_keyring_gpg_new (); #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not compiled with GPG support"); return NULL; #endif } if (kind == FWUPD_KEYRING_KIND_PKCS7) { #ifdef ENABLE_PKCS7 return fu_keyring_pkcs7_new (); #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not compiled with PKCS7 support"); return NULL; #endif } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Keyring kind %s not supported", fwupd_keyring_kind_to_string (kind)); return NULL; } /** * fu_keyring_get_release_flags: * @release: A #XbNode, e.g. %FWUPD_KEYRING_KIND_GPG * @flags: A #FwupdReleaseFlags, e.g. %FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD * @error: A #GError, or %NULL * * Uses the correct keyring to get the trust flags for a given release. * * Returns: %TRUE if @flags has been set **/ gboolean fu_keyring_get_release_flags (XbNode *release, FwupdReleaseFlags *flags, GError **error) { FwupdKeyringKind keyring_kind = FWUPD_KEYRING_KIND_UNKNOWN; GBytes *blob_payload; GBytes *blob_signature; const gchar *fn; g_autofree gchar *pki_dir = NULL; g_autofree gchar *release_key = NULL; g_autofree gchar *sysconfdir = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(FuKeyring) kr = NULL; g_autoptr(FuKeyringResult) kr_result = NULL; struct { FwupdKeyringKind kind; const gchar *ext; } keyrings[] = { { FWUPD_KEYRING_KIND_GPG, "asc" }, { FWUPD_KEYRING_KIND_PKCS7, "p7b" }, { FWUPD_KEYRING_KIND_PKCS7, "p7c" }, { FWUPD_KEYRING_KIND_NONE, NULL } }; /* custom filename specified */ fn = xb_node_query_attr (release, "checksum[@target='content']", "filename", NULL); if (fn == NULL) fn = "filename.bin"; /* no signature == no trust */ for (guint i = 0; keyrings[i].ext != NULL; i++) { g_autofree gchar *fn_tmp = NULL; fn_tmp = g_strdup_printf ("fwupd::ReleaseBlob(%s.%s)", fn, keyrings[i].ext); blob_signature = g_object_get_data (G_OBJECT (release), fn_tmp); if (blob_signature != NULL) { keyring_kind = keyrings[i].kind; break; } } if (keyring_kind == FWUPD_KEYRING_KIND_UNKNOWN) { g_debug ("firmware archive contained no signature"); return TRUE; } /* get payload */ release_key = g_strdup_printf ("fwupd::ReleaseBlob(%s)", fn); blob_payload = g_object_get_data (G_OBJECT (release), release_key); if (blob_payload == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no payload"); return FALSE; } /* check we were installed correctly */ sysconfdir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR); pki_dir = g_build_filename (sysconfdir, "pki", PACKAGE_NAME, NULL); #if defined(ENABLE_PKCS7) || defined(ENABLE_PKCS7) if (!g_file_test (pki_dir, G_FILE_TEST_EXISTS)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "PKI directory %s not found", pki_dir); return FALSE; } #endif /* verify against the system trusted keys */ kr = fu_keyring_create_for_kind (keyring_kind, error); if (kr == NULL) return FALSE; if (!fu_keyring_setup (kr, error)) { g_prefix_error (error, "failed to set up %s keyring: ", fu_keyring_get_name (kr)); return FALSE; } if (!fu_keyring_add_public_keys (kr, pki_dir, error)) { g_prefix_error (error, "failed to add public keys to %s keyring: ", fu_keyring_get_name (kr)); return FALSE; } kr_result = fu_keyring_verify_data (kr, blob_payload, blob_signature, FU_KEYRING_VERIFY_FLAG_NONE, &error_local); if (kr_result == NULL) { g_warning ("untrusted as failed to verify from %s keyring: %s", fu_keyring_get_name (kr), error_local->message); return TRUE; } /* awesome! */ g_debug ("marking payload as trusted"); *flags |= FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD; return TRUE; } fwupd-1.2.14/src/fu-keyring-utils.h000066400000000000000000000006601402665037500171140ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-keyring.h" #include "fwupd-enums.h" G_BEGIN_DECLS FuKeyring *fu_keyring_create_for_kind (FwupdKeyringKind kind, GError **error); gboolean fu_keyring_get_release_flags (XbNode *release, FwupdReleaseFlags *flags, GError **error); G_END_DECLS fwupd-1.2.14/src/fu-keyring.c000066400000000000000000000051421402665037500157510ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuKeyring" #include "config.h" #include "fwupd-error.h" #include "fu-keyring.h" typedef struct { gchar *name; } FuKeyringPrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuKeyring, fu_keyring, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_keyring_get_instance_private (o)) gboolean fu_keyring_setup (FuKeyring *keyring, GError **error) { FuKeyringClass *klass = FU_KEYRING_GET_CLASS (keyring); g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE); return klass->setup (keyring, error); } gboolean fu_keyring_add_public_keys (FuKeyring *keyring, const gchar *path, GError **error) { FuKeyringClass *klass = FU_KEYRING_GET_CLASS (keyring); g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE); g_return_val_if_fail (path != NULL, FALSE); return klass->add_public_keys (keyring, path, error); } FuKeyringResult * fu_keyring_verify_data (FuKeyring *keyring, GBytes *blob, GBytes *blob_signature, FuKeyringVerifyFlags flags, GError **error) { FuKeyringClass *klass = FU_KEYRING_GET_CLASS (keyring); g_return_val_if_fail (FU_IS_KEYRING (keyring), NULL); g_return_val_if_fail (blob != NULL, NULL); g_return_val_if_fail (blob_signature != NULL, NULL); return klass->verify_data (keyring, blob, blob_signature, flags, error); } GBytes * fu_keyring_sign_data (FuKeyring *keyring, GBytes *blob, FuKeyringSignFlags flags, GError **error) { FuKeyringClass *klass = FU_KEYRING_GET_CLASS (keyring); g_return_val_if_fail (FU_IS_KEYRING (keyring), NULL); g_return_val_if_fail (blob != NULL, NULL); if (klass->sign_data == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "signing data is not supported"); return NULL; } return klass->sign_data (keyring, blob, flags, error); } const gchar * fu_keyring_get_name (FuKeyring *self) { FuKeyringPrivate *priv = GET_PRIVATE (self); return priv->name; } void fu_keyring_set_name (FuKeyring *self, const gchar *name) { FuKeyringPrivate *priv = GET_PRIVATE (self); g_free (priv->name); priv->name = g_strdup (name); } static void fu_keyring_finalize (GObject *object) { FuKeyring *self = FU_KEYRING (object); FuKeyringPrivate *priv = GET_PRIVATE (self); g_free (priv->name); G_OBJECT_CLASS (fu_keyring_parent_class)->finalize (object); } static void fu_keyring_class_init (FuKeyringClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_keyring_finalize; } static void fu_keyring_init (FuKeyring *keyring) { } fwupd-1.2.14/src/fu-keyring.h000066400000000000000000000046651402665037500157670ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-keyring-result.h" G_BEGIN_DECLS #define FU_TYPE_KEYRING (fu_keyring_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuKeyring, fu_keyring, FU, KEYRING, GObject) /** * FuKeyringVerifyFlags: * @FU_KEYRING_VERIFY_FLAG_NONE: No flags set * @FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT: Use client certificate to verify * @FU_KEYRING_VERIFY_FLAG_DISABLE_TIME_CHECKS: Disable checking of validity periods * * The flags to use when interacting with a keyring **/ typedef enum { FU_KEYRING_VERIFY_FLAG_NONE = 0, FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT = 1 << 1, FU_KEYRING_VERIFY_FLAG_DISABLE_TIME_CHECKS = 1 << 2, /*< private >*/ FU_KEYRING_VERIFY_FLAG_LAST } FuKeyringVerifyFlags; /** * FuKeyringSignFlags: * @FU_KEYRING_SIGN_FLAG_NONE: No flags set * @FU_KEYRING_SIGN_FLAG_ADD_TIMESTAMP: Add a timestamp * @FU_KEYRING_SIGN_FLAG_ADD_CERT: Add a certificate * * The flags to when signing a binary **/ typedef enum { FU_KEYRING_SIGN_FLAG_NONE = 0, FU_KEYRING_SIGN_FLAG_ADD_TIMESTAMP = 1 << 0, FU_KEYRING_SIGN_FLAG_ADD_CERT = 1 << 1, /*< private >*/ FU_KEYRING_SIGN_FLAG_LAST } FuKeyringSignFlags; struct _FuKeyringClass { GObjectClass parent_class; gboolean (*setup) (FuKeyring *keyring, GError **error); gboolean (*add_public_keys) (FuKeyring *keyring, const gchar *path, GError **error); FuKeyringResult *(*verify_data) (FuKeyring *keyring, GBytes *payload, GBytes *payload_signature, FuKeyringVerifyFlags flags, GError **error); GBytes *(*sign_data) (FuKeyring *keyring, GBytes *payload, FuKeyringSignFlags flags, GError **error); }; gboolean fu_keyring_setup (FuKeyring *keyring, GError **error); gboolean fu_keyring_add_public_keys (FuKeyring *keyring, const gchar *path, GError **error); FuKeyringResult *fu_keyring_verify_data (FuKeyring *keyring, GBytes *blob, GBytes *blob_signature, FuKeyringVerifyFlags flags, GError **error); GBytes *fu_keyring_sign_data (FuKeyring *keyring, GBytes *blob, FuKeyringSignFlags flags, GError **error); const gchar *fu_keyring_get_name (FuKeyring *self); void fu_keyring_set_name (FuKeyring *self, const gchar *name); G_END_DECLS fwupd-1.2.14/src/fu-main.c000066400000000000000000001443131402665037500152310ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include #include #include #include #include #include #include "fwupd-device-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" #include "fwupd-resources.h" #include "fu-common.h" #include "fu-debug.h" #include "fu-device-private.h" #include "fu-engine.h" #include "fu-install-task.h" #ifndef HAVE_POLKIT_0_114 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(PolkitAuthorizationResult, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(PolkitSubject, g_object_unref) #pragma clang diagnostic pop #endif typedef struct { GDBusConnection *connection; GDBusNodeInfo *introspection_daemon; GDBusProxy *proxy_uid; GMainLoop *loop; GFileMonitor *argv0_monitor; PolkitAuthority *authority; guint owner_id; FuEngine *engine; gboolean update_in_progress; gboolean pending_sigterm; } FuMainPrivate; static gboolean fu_main_sigterm_cb (gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *) user_data; if (!priv->update_in_progress) { g_main_loop_quit (priv->loop); return G_SOURCE_REMOVE; } g_warning ("Received SIGTERM during a firmware update, ignoring"); priv->pending_sigterm = TRUE; return G_SOURCE_CONTINUE; } static void fu_main_engine_changed_cb (FuEngine *engine, FuMainPrivate *priv) { /* not yet connected */ if (priv->connection == NULL) return; g_dbus_connection_emit_signal (priv->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "Changed", NULL, NULL); } static void fu_main_engine_device_added_cb (FuEngine *engine, FuDevice *device, FuMainPrivate *priv) { GVariant *val; /* not yet connected */ if (priv->connection == NULL) return; val = fwupd_device_to_variant (FWUPD_DEVICE (device)); g_dbus_connection_emit_signal (priv->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceAdded", g_variant_new_tuple (&val, 1), NULL); } static void fu_main_engine_device_removed_cb (FuEngine *engine, FuDevice *device, FuMainPrivate *priv) { GVariant *val; /* not yet connected */ if (priv->connection == NULL) return; val = fwupd_device_to_variant (FWUPD_DEVICE (device)); g_dbus_connection_emit_signal (priv->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceRemoved", g_variant_new_tuple (&val, 1), NULL); } static void fu_main_engine_device_changed_cb (FuEngine *engine, FuDevice *device, FuMainPrivate *priv) { GVariant *val; /* not yet connected */ if (priv->connection == NULL) return; val = fwupd_device_to_variant (FWUPD_DEVICE (device)); g_dbus_connection_emit_signal (priv->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceChanged", g_variant_new_tuple (&val, 1), NULL); } static void fu_main_emit_property_changed (FuMainPrivate *priv, const gchar *property_name, GVariant *property_value) { GVariantBuilder builder; GVariantBuilder invalidated_builder; /* not yet connected */ if (priv->connection == NULL) { g_variant_unref (g_variant_ref_sink (property_value)); return; } /* build the dict */ g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as")); g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add (&builder, "{sv}", property_name, property_value); g_dbus_connection_emit_signal (priv->connection, NULL, FWUPD_DBUS_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", g_variant_new ("(sa{sv}as)", FWUPD_DBUS_INTERFACE, &builder, &invalidated_builder), NULL); g_variant_builder_clear (&builder); g_variant_builder_clear (&invalidated_builder); } static void fu_main_set_status (FuMainPrivate *priv, FwupdStatus status) { g_debug ("Emitting PropertyChanged('Status'='%s')", fwupd_status_to_string (status)); fu_main_emit_property_changed (priv, "Status", g_variant_new_uint32 (status)); } static void fu_main_engine_status_changed_cb (FuEngine *engine, FwupdStatus status, FuMainPrivate *priv) { fu_main_set_status (priv, status); /* engine has gone idle */ if (status == FWUPD_STATUS_SHUTDOWN) g_main_loop_quit (priv->loop); } static void fu_main_engine_percentage_changed_cb (FuEngine *engine, guint percentage, FuMainPrivate *priv) { g_debug ("Emitting PropertyChanged('Percentage'='%u%%')", percentage); fu_main_emit_property_changed (priv, "Percentage", g_variant_new_uint32 (percentage)); } static gboolean fu_main_get_device_flags_for_sender (FuMainPrivate *priv, const char *sender, FwupdDeviceFlags *flags, GError **error) { uid_t calling_uid; g_autoptr(GVariant) value = NULL; g_return_val_if_fail (sender != NULL, FALSE); g_return_val_if_fail (flags != NULL, FALSE); value = g_dbus_proxy_call_sync (priv->proxy_uid, "GetConnectionUnixUser", g_variant_new ("(s)", sender), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, error); if (value == NULL) { g_prefix_error (error, "failed to read user id of caller: "); return FALSE; } g_variant_get (value, "(u)", &calling_uid); if (calling_uid == 0) *flags |= FWUPD_DEVICE_FLAG_TRUSTED; return TRUE; } static GVariant * fu_main_device_array_to_variant (FuMainPrivate *priv, const gchar *sender, GPtrArray *devices, GError **error) { GVariantBuilder builder; FwupdDeviceFlags flags = FWUPD_DEVICE_FLAG_NONE; g_return_val_if_fail (devices->len > 0, NULL); g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); if (!fu_main_get_device_flags_for_sender (priv, sender, &flags, error)) return NULL; for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); GVariant *tmp = fwupd_device_to_variant_full (FWUPD_DEVICE (device), flags); g_variant_builder_add_value (&builder, tmp); } return g_variant_new ("(aa{sv})", &builder); } static GVariant * fu_main_release_array_to_variant (GPtrArray *results) { GVariantBuilder builder; g_return_val_if_fail (results->len > 0, NULL); g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); for (guint i = 0; i < results->len; i++) { FwupdRelease *rel = g_ptr_array_index (results, i); GVariant *tmp = fwupd_release_to_variant (rel); g_variant_builder_add_value (&builder, tmp); } return g_variant_new ("(aa{sv})", &builder); } static GVariant * fu_main_remote_array_to_variant (GPtrArray *remotes) { GVariantBuilder builder; g_return_val_if_fail (remotes->len > 0, NULL); g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); GVariant *tmp = fwupd_remote_to_variant (remote); g_variant_builder_add_value (&builder, tmp); } return g_variant_new ("(aa{sv})", &builder); } static GVariant * fu_main_result_array_to_variant (GPtrArray *results) { GVariantBuilder builder; g_return_val_if_fail (results->len > 0, NULL); g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); for (guint i = 0; i < results->len; i++) { FwupdDevice *result = g_ptr_array_index (results, i); GVariant *tmp = fwupd_device_to_variant (result); g_variant_builder_add_value (&builder, tmp); } return g_variant_new ("(aa{sv})", &builder); } typedef struct { GDBusMethodInvocation *invocation; PolkitSubject *subject; GPtrArray *install_tasks; GPtrArray *action_ids; GPtrArray *checksums; guint64 flags; GBytes *blob_cab; FuMainPrivate *priv; gchar *device_id; gchar *remote_id; gchar *key; gchar *value; XbSilo *silo; } FuMainAuthHelper; static void fu_main_auth_helper_free (FuMainAuthHelper *helper) { if (helper->blob_cab != NULL) g_bytes_unref (helper->blob_cab); if (helper->subject != NULL) g_object_unref (helper->subject); if (helper->silo != NULL) g_object_unref (helper->silo); if (helper->install_tasks != NULL) g_ptr_array_unref (helper->install_tasks); if (helper->action_ids != NULL) g_ptr_array_unref (helper->action_ids); if (helper->checksums != NULL) g_ptr_array_unref (helper->checksums); g_free (helper->device_id); g_free (helper->remote_id); g_free (helper->key); g_free (helper->value); g_object_unref (helper->invocation); g_free (helper); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuMainAuthHelper, fu_main_auth_helper_free) #pragma clang diagnostic pop /* error may or may not already have been set */ static gboolean fu_main_authorization_is_valid (PolkitAuthorizationResult *auth, GError **error) { /* failed */ if (auth == NULL) { g_autofree gchar *message = g_strdup ((*error)->message); g_clear_error (error); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "Could not check for auth: %s", message); return FALSE; } /* did not auth */ if (!polkit_authorization_result_get_is_authorized (auth)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "Failed to obtain auth"); return FALSE; } /* success */ return TRUE; } static void fu_main_authorize_unlock_cb (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *) user_data; g_autoptr(GError) error = NULL; g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status (helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source), res, &error); if (!fu_main_authorization_is_valid (auth, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* authenticated */ if (!fu_engine_unlock (helper->priv->engine, helper->device_id, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value (helper->invocation, NULL); } static void fu_main_authorize_set_approved_firmware_cb (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *) user_data; g_autoptr(GError) error = NULL; g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status (helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source), res, &error); if (!fu_main_authorization_is_valid (auth, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* success */ for (guint i = 0; i < helper->checksums->len; i++) { const gchar *csum = g_ptr_array_index (helper->checksums, i); fu_engine_add_approved_firmware (helper->priv->engine, csum); } g_dbus_method_invocation_return_value (helper->invocation, NULL); } static void fu_main_authorize_self_sign_cb (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *) user_data; g_autofree gchar *sig = NULL; g_autoptr(GError) error = NULL; g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status (helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source), res, &error); if (!fu_main_authorization_is_valid (auth, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* authenticated */ sig = fu_engine_self_sign (helper->priv->engine, helper->value, helper->flags, &error); if (sig == NULL) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value (helper->invocation, g_variant_new ("(s)", sig)); } static void fu_main_modify_config_cb (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *) user_data; g_autoptr(GError) error = NULL; g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ auth = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source), res, &error); if (!fu_main_authorization_is_valid (auth, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } if (!fu_engine_modify_config (helper->priv->engine, helper->key, helper->value, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value (helper->invocation, NULL); } static void fu_main_authorize_activate_cb (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *) user_data; g_autoptr(GError) error = NULL; g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status (helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source), res, &error); if (!fu_main_authorization_is_valid (auth, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* authenticated */ if (!fu_engine_activate (helper->priv->engine, helper->device_id, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value (helper->invocation, NULL); } static void fu_main_authorize_verify_update_cb (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *) user_data; g_autoptr(GError) error = NULL; g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status (helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source), res, &error); if (!fu_main_authorization_is_valid (auth, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* authenticated */ if (!fu_engine_verify_update (helper->priv->engine, helper->device_id, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value (helper->invocation, NULL); } static void fu_main_authorize_modify_remote_cb (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *) user_data; g_autoptr(GError) error = NULL; g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status (helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source), res, &error); if (!fu_main_authorization_is_valid (auth, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* authenticated */ if (!fu_engine_modify_remote (helper->priv->engine, helper->remote_id, helper->key, helper->value, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value (helper->invocation, NULL); } static void fu_main_authorize_install_queue (FuMainAuthHelper *helper); static void fu_main_authorize_install_cb (GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *) user_data; g_autoptr(GError) error = NULL; g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status (helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source), res, &error); if (!fu_main_authorization_is_valid (auth, &error)) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* do the next authentication action ID */ fu_main_authorize_install_queue (g_steal_pointer (&helper)); } static void fu_main_authorize_install_queue (FuMainAuthHelper *helper_ref) { FuMainPrivate *priv = helper_ref->priv; g_autoptr(FuMainAuthHelper) helper = helper_ref; g_autoptr(GError) error = NULL; gboolean ret; /* still more things to to authenticate */ if (helper->action_ids->len > 0) { g_autofree gchar *action_id = g_strdup (g_ptr_array_index (helper->action_ids, 0)); g_autoptr(PolkitSubject) subject = g_object_ref (helper->subject); g_ptr_array_remove_index (helper->action_ids, 0); polkit_authority_check_authorization (priv->authority, subject, action_id, NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_install_cb, g_steal_pointer (&helper)); return; } /* all authenticated, so install all the things */ priv->update_in_progress = TRUE; ret = fu_engine_install_tasks (helper->priv->engine, helper->install_tasks, helper->blob_cab, helper->flags, &error); priv->update_in_progress = FALSE; if (priv->pending_sigterm) g_main_loop_quit (priv->loop); if (!ret) { g_dbus_method_invocation_return_gerror (helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value (helper->invocation, NULL); } #if !GLIB_CHECK_VERSION(2,54,0) static gboolean g_ptr_array_find (GPtrArray *haystack, gconstpointer needle, guint *index_) { for (guint i = 0; i < haystack->len; i++) { gconstpointer *tmp = g_ptr_array_index (haystack, i); if (tmp == needle) { if (index_ != NULL) { *index_ = i; return TRUE; } } } return FALSE; } #endif static gint fu_main_install_task_sort_cb (gconstpointer a, gconstpointer b) { FuInstallTask *task_a = *((FuInstallTask **) a); FuInstallTask *task_b = *((FuInstallTask **) b); return fu_install_task_compare (task_a, task_b); } static GPtrArray * fu_main_get_device_family (FuMainAuthHelper *helper, GError **error) { FuDevice *parent; GPtrArray *children; g_autoptr(FuDevice) device = NULL; g_autoptr(GPtrArray) devices_possible = NULL; /* get the device */ device = fu_engine_get_device (helper->priv->engine, helper->device_id, error); if (device == NULL) return NULL; /* device itself */ devices_possible = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_ptr_array_add (devices_possible, g_object_ref (device)); /* add device children */ children = fu_device_get_children (device); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index (children, i); g_ptr_array_add (devices_possible, g_object_ref (child)); } /* add parent and siblings, not including the device itself */ parent = fu_device_get_parent (device); if (parent != NULL) { GPtrArray *siblings = fu_device_get_children (parent); g_ptr_array_add (devices_possible, g_object_ref (parent)); for (guint i = 0; i < siblings->len; i++) { FuDevice *sibling = g_ptr_array_index (siblings, i); if (sibling == device) continue; g_ptr_array_add (devices_possible, g_object_ref (sibling)); } } /* success */ return g_steal_pointer (&devices_possible); } static gboolean fu_main_install_with_helper (FuMainAuthHelper *helper_ref, GError **error) { FuMainPrivate *priv = helper_ref->priv; g_autoptr(FuMainAuthHelper) helper = helper_ref; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) devices_possible = NULL; g_autoptr(GPtrArray) errors = NULL; /* get a list of devices that in some way match the device_id */ if (g_strcmp0 (helper->device_id, FWUPD_DEVICE_ID_ANY) == 0) { devices_possible = fu_engine_get_devices (priv->engine, error); if (devices_possible == NULL) return FALSE; } else { devices_possible = fu_main_get_device_family (helper, error); if (devices_possible == NULL) return FALSE; } /* parse silo */ helper->silo = fu_engine_get_silo_from_blob (priv->engine, helper->blob_cab, error); if (helper->silo == NULL) return FALSE; /* for each component in the silo */ components = xb_silo_query (helper->silo, "components/component", 0, error); if (components == NULL) return FALSE; helper->action_ids = g_ptr_array_new_with_free_func (g_free); helper->install_tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); errors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_error_free); for (guint i = 0; i < components->len; i++) { XbNode *component = g_ptr_array_index (components, i); /* do any devices pass the requirements */ for (guint j = 0; j < devices_possible->len; j++) { FuDevice *device = g_ptr_array_index (devices_possible, j); const gchar *action_id; g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error_local = NULL; /* is this component valid for the device */ task = fu_install_task_new (device, component); if (!fu_engine_check_requirements (priv->engine, task, helper->flags, &error_local)) { g_debug ("requirement on %s:%s failed: %s", fu_device_get_id (device), xb_node_query_text (component, "id", NULL), error_local->message); g_ptr_array_add (errors, g_steal_pointer (&error_local)); continue; } /* if component should have an update message from CAB */ fu_device_incorporate_from_component (device, component); /* get the action IDs for the valid device */ action_id = fu_install_task_get_action_id (task); if (!g_ptr_array_find (helper->action_ids, action_id, NULL)) g_ptr_array_add (helper->action_ids, g_strdup (action_id)); g_ptr_array_add (helper->install_tasks, g_steal_pointer (&task)); } } /* order the install tasks by the device priority */ g_ptr_array_sort (helper->install_tasks, fu_main_install_task_sort_cb); /* nothing suitable */ if (helper->install_tasks->len == 0) { GError *error_tmp = fu_common_error_array_get_best (errors); g_propagate_error (error, error_tmp); return FALSE; } /* authenticate all things in the action_ids */ fu_main_set_status (priv, FWUPD_STATUS_WAITING_FOR_AUTH); fu_main_authorize_install_queue (g_steal_pointer (&helper)); return TRUE; } static gboolean fu_main_device_id_valid (const gchar *device_id, GError **error) { if (g_strcmp0 (device_id, FWUPD_DEVICE_ID_ANY) == 0) return TRUE; if (device_id != NULL && strlen (device_id) >= 4) return TRUE; g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid device ID: %s", device_id); return FALSE; } static void fu_main_daemon_method_call (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *) user_data; GVariant *val = NULL; g_autoptr(GError) error = NULL; /* activity */ fu_engine_idle_reset (priv->engine); if (g_strcmp0 (method_name, "GetDevices") == 0) { g_autoptr(GPtrArray) devices = NULL; g_debug ("Called %s()", method_name); devices = fu_engine_get_devices (priv->engine, &error); if (devices == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } val = fu_main_device_array_to_variant (priv, sender, devices, &error); if (val == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } g_dbus_method_invocation_return_value (invocation, val); return; } if (g_strcmp0 (method_name, "GetReleases") == 0) { const gchar *device_id; g_autoptr(GPtrArray) releases = NULL; g_variant_get (parameters, "(&s)", &device_id); g_debug ("Called %s(%s)", method_name, device_id); if (!fu_main_device_id_valid (device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } releases = fu_engine_get_releases (priv->engine, device_id, &error); if (releases == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } val = fu_main_release_array_to_variant (releases); g_dbus_method_invocation_return_value (invocation, val); return; } if (g_strcmp0 (method_name, "GetApprovedFirmware") == 0) { GVariantBuilder builder; GPtrArray *checksums = fu_engine_get_approved_firmware (priv->engine); g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index (checksums, i); g_variant_builder_add_value (&builder, g_variant_new_string (checksum)); } val = g_variant_builder_end (&builder); g_dbus_method_invocation_return_value (invocation, g_variant_new_tuple (&val, 1)); return; } if (g_strcmp0 (method_name, "SetApprovedFirmware") == 0) { g_autofree gchar *checksums_str = NULL; g_auto(GStrv) checksums = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_autoptr(PolkitSubject) subject = NULL; g_variant_get (parameters, "(^as)", &checksums); checksums_str = g_strjoinv (",", checksums); g_debug ("Called %s(%s)", method_name, checksums_str); /* authenticate */ fu_main_set_status (priv, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0 (FuMainAuthHelper, 1); helper->priv = priv; helper->invocation = g_object_ref (invocation); helper->checksums = g_ptr_array_new_with_free_func (g_free); for (guint i = 0; checksums[i] != NULL; i++) g_ptr_array_add (helper->checksums, g_strdup (checksums[i])); subject = polkit_system_bus_name_new (sender); polkit_authority_check_authorization (priv->authority, subject, "org.freedesktop.fwupd.set-approved-firmware", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_set_approved_firmware_cb, g_steal_pointer (&helper)); return; } if (g_strcmp0 (method_name, "SelfSign") == 0) { GVariant *prop_value; gchar *prop_key; g_autofree gchar *value = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_autoptr(PolkitSubject) subject = NULL; g_autoptr(GVariantIter) iter = NULL; g_variant_get (parameters, "(sa{sv})", &value, &iter); g_debug ("Called %s(%s)", method_name, value); /* get flags */ helper = g_new0 (FuMainAuthHelper, 1); while (g_variant_iter_next (iter, "{&sv}", &prop_key, &prop_value)) { g_debug ("got option %s", prop_key); if (g_strcmp0 (prop_key, "add-timestamp") == 0 && g_variant_get_boolean (prop_value) == TRUE) helper->flags |= FU_KEYRING_SIGN_FLAG_ADD_TIMESTAMP; if (g_strcmp0 (prop_key, "add-cert") == 0 && g_variant_get_boolean (prop_value) == TRUE) helper->flags |= FU_KEYRING_SIGN_FLAG_ADD_CERT; g_variant_unref (prop_value); } /* authenticate */ fu_main_set_status (priv, FWUPD_STATUS_WAITING_FOR_AUTH); helper->priv = priv; helper->value = g_steal_pointer (&value); helper->invocation = g_object_ref (invocation); subject = polkit_system_bus_name_new (sender); polkit_authority_check_authorization (priv->authority, subject, "org.freedesktop.fwupd.self-sign", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_self_sign_cb, g_steal_pointer (&helper)); return; } if (g_strcmp0 (method_name, "GetDowngrades") == 0) { const gchar *device_id; g_autoptr(GPtrArray) releases = NULL; g_variant_get (parameters, "(&s)", &device_id); g_debug ("Called %s(%s)", method_name, device_id); if (!fu_main_device_id_valid (device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } releases = fu_engine_get_downgrades (priv->engine, device_id, &error); if (releases == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } val = fu_main_release_array_to_variant (releases); g_dbus_method_invocation_return_value (invocation, val); return; } if (g_strcmp0 (method_name, "GetUpgrades") == 0) { const gchar *device_id; g_autoptr(GPtrArray) releases = NULL; g_variant_get (parameters, "(&s)", &device_id); g_debug ("Called %s(%s)", method_name, device_id); if (!fu_main_device_id_valid (device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } releases = fu_engine_get_upgrades (priv->engine, device_id, &error); if (releases == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } val = fu_main_release_array_to_variant (releases); g_dbus_method_invocation_return_value (invocation, val); return; } if (g_strcmp0 (method_name, "GetRemotes") == 0) { g_autoptr(GPtrArray) remotes = NULL; g_debug ("Called %s()", method_name); remotes = fu_engine_get_remotes (priv->engine, &error); if (remotes == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } val = fu_main_remote_array_to_variant (remotes); g_dbus_method_invocation_return_value (invocation, val); return; } if (g_strcmp0 (method_name, "GetHistory") == 0) { g_autoptr(GPtrArray) devices = NULL; g_debug ("Called %s()", method_name); devices = fu_engine_get_history (priv->engine, &error); if (devices == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } val = fu_main_device_array_to_variant (priv, sender, devices, &error); if (val == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } g_dbus_method_invocation_return_value (invocation, val); return; } if (g_strcmp0 (method_name, "ClearResults") == 0) { const gchar *device_id; g_variant_get (parameters, "(&s)", &device_id); g_debug ("Called %s(%s)", method_name, device_id); if (!fu_engine_clear_results (priv->engine, device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } g_dbus_method_invocation_return_value (invocation, NULL); return; } if (g_strcmp0 (method_name, "ModifyDevice") == 0) { const gchar *device_id; const gchar *key = NULL; const gchar *value = NULL; /* check the id exists */ g_variant_get (parameters, "(&s&s&s)", &device_id, &key, &value); g_debug ("Called %s(%s,%s=%s)", method_name, device_id, key, value); if (!fu_engine_modify_device (priv->engine, device_id, key, value, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } g_dbus_method_invocation_return_value (invocation, NULL); return; } if (g_strcmp0 (method_name, "GetResults") == 0) { const gchar *device_id = NULL; g_autoptr(FwupdDevice) result = NULL; g_variant_get (parameters, "(&s)", &device_id); g_debug ("Called %s(%s)", method_name, device_id); if (!fu_main_device_id_valid (device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } result = fu_engine_get_results (priv->engine, device_id, &error); if (result == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } val = fwupd_device_to_variant (result); g_dbus_method_invocation_return_value (invocation, g_variant_new_tuple (&val, 1)); return; } if (g_strcmp0 (method_name, "UpdateMetadata") == 0) { GDBusMessage *message; GUnixFDList *fd_list; const gchar *remote_id = NULL; gint fd_data; gint fd_sig; g_variant_get (parameters, "(&shh)", &remote_id, &fd_data, &fd_sig); g_debug ("Called %s(%s,%i,%i)", method_name, remote_id, fd_data, fd_sig); /* update the metadata store */ message = g_dbus_method_invocation_get_message (invocation); fd_list = g_dbus_message_get_unix_fd_list (message); if (fd_list == NULL || g_unix_fd_list_get_length (fd_list) != 2) { g_set_error (&error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid handle"); g_dbus_method_invocation_return_gerror (invocation, error); return; } fd_data = g_unix_fd_list_get (fd_list, 0, &error); if (fd_data < 0) { g_dbus_method_invocation_return_gerror (invocation, error); return; } fd_sig = g_unix_fd_list_get (fd_list, 1, &error); if (fd_sig < 0) { g_dbus_method_invocation_return_gerror (invocation, error); return; } /* store new metadata (will close the fds when done) */ if (!fu_engine_update_metadata (priv->engine, remote_id, fd_data, fd_sig, &error)) { g_prefix_error (&error, "Failed to update metadata for %s: ", remote_id); g_dbus_method_invocation_return_gerror (invocation, error); return; } g_dbus_method_invocation_return_value (invocation, NULL); return; } if (g_strcmp0 (method_name, "Unlock") == 0) { const gchar *device_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_autoptr(PolkitSubject) subject = NULL; g_variant_get (parameters, "(&s)", &device_id); g_debug ("Called %s(%s)", method_name, device_id); if (!fu_main_device_id_valid (device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } /* authenticate */ fu_main_set_status (priv, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0 (FuMainAuthHelper, 1); helper->priv = priv; helper->invocation = g_object_ref (invocation); helper->device_id = g_strdup (device_id); subject = polkit_system_bus_name_new (sender); polkit_authority_check_authorization (priv->authority, subject, "org.freedesktop.fwupd.device-unlock", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_unlock_cb, g_steal_pointer (&helper)); return; } if (g_strcmp0 (method_name, "Activate") == 0) { const gchar *device_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_autoptr(PolkitSubject) subject = NULL; g_variant_get (parameters, "(&s)", &device_id); g_debug ("Called %s(%s)", method_name, device_id); if (!fu_main_device_id_valid (device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } /* authenticate */ fu_main_set_status (priv, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0 (FuMainAuthHelper, 1); helper->priv = priv; helper->invocation = g_object_ref (invocation); helper->device_id = g_strdup (device_id); subject = polkit_system_bus_name_new (sender); polkit_authority_check_authorization (priv->authority, subject, "org.freedesktop.fwupd.device-activate", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_activate_cb, g_steal_pointer (&helper)); return; } if (g_strcmp0 (method_name, "ModifyConfig") == 0) { g_autofree gchar *key = NULL; g_autofree gchar *value = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_autoptr(PolkitSubject) subject = NULL; g_variant_get (parameters, "(ss)", &key, &value); g_debug ("Called %s(%s=%s)", method_name, key, value); /* authenticate */ helper = g_new0 (FuMainAuthHelper, 1); helper->priv = priv; helper->key = g_steal_pointer (&key); helper->value = g_steal_pointer (&value); helper->invocation = g_object_ref (invocation); subject = polkit_system_bus_name_new (sender); polkit_authority_check_authorization (priv->authority, subject, "org.freedesktop.fwupd.modify-config", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_modify_config_cb, g_steal_pointer (&helper)); return; } if (g_strcmp0 (method_name, "ModifyRemote") == 0) { const gchar *remote_id = NULL; const gchar *key = NULL; const gchar *value = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_autoptr(PolkitSubject) subject = NULL; /* check the id exists */ g_variant_get (parameters, "(&s&s&s)", &remote_id, &key, &value); g_debug ("Called %s(%s,%s=%s)", method_name, remote_id, key, value); /* create helper object */ helper = g_new0 (FuMainAuthHelper, 1); helper->invocation = g_object_ref (invocation); helper->remote_id = g_strdup (remote_id); helper->key = g_strdup (key); helper->value = g_strdup (value); helper->priv = priv; /* authenticate */ fu_main_set_status (priv, FWUPD_STATUS_WAITING_FOR_AUTH); subject = polkit_system_bus_name_new (sender); polkit_authority_check_authorization (priv->authority, subject, "org.freedesktop.fwupd.modify-remote", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_modify_remote_cb, g_steal_pointer (&helper)); return; } if (g_strcmp0 (method_name, "VerifyUpdate") == 0) { const gchar *device_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_autoptr(PolkitSubject) subject = NULL; /* check the id exists */ g_variant_get (parameters, "(&s)", &device_id); g_debug ("Called %s(%s)", method_name, device_id); if (!fu_main_device_id_valid (device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } /* create helper object */ helper = g_new0 (FuMainAuthHelper, 1); helper->invocation = g_object_ref (invocation); helper->device_id = g_strdup (device_id); helper->priv = priv; /* authenticate */ fu_main_set_status (priv, FWUPD_STATUS_WAITING_FOR_AUTH); subject = polkit_system_bus_name_new (sender); polkit_authority_check_authorization (priv->authority, subject, "org.freedesktop.fwupd.verify-update", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_verify_update_cb, g_steal_pointer (&helper)); return; } if (g_strcmp0 (method_name, "Verify") == 0) { const gchar *device_id = NULL; g_variant_get (parameters, "(&s)", &device_id); g_debug ("Called %s(%s)", method_name, device_id); if (!fu_main_device_id_valid (device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } if (!fu_engine_verify (priv->engine, device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } g_dbus_method_invocation_return_value (invocation, NULL); return; } if (g_strcmp0 (method_name, "Install") == 0) { GVariant *prop_value; const gchar *device_id = NULL; gchar *prop_key; gint32 fd_handle = 0; gint fd; guint64 archive_size_max; GDBusMessage *message; GUnixFDList *fd_list; g_autoptr(FuMainAuthHelper) helper = NULL; g_autoptr(GVariantIter) iter = NULL; /* check the id exists */ g_variant_get (parameters, "(&sha{sv})", &device_id, &fd_handle, &iter); g_debug ("Called %s(%s,%i)", method_name, device_id, fd_handle); if (!fu_main_device_id_valid (device_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } /* create helper object */ helper = g_new0 (FuMainAuthHelper, 1); helper->invocation = g_object_ref (invocation); helper->device_id = g_strdup (device_id); helper->priv = priv; /* get flags */ while (g_variant_iter_next (iter, "{&sv}", &prop_key, &prop_value)) { g_debug ("got option %s", prop_key); if (g_strcmp0 (prop_key, "offline") == 0 && g_variant_get_boolean (prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_OFFLINE; if (g_strcmp0 (prop_key, "allow-older") == 0 && g_variant_get_boolean (prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; if (g_strcmp0 (prop_key, "allow-reinstall") == 0 && g_variant_get_boolean (prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; if (g_strcmp0 (prop_key, "force") == 0 && g_variant_get_boolean (prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_FORCE; if (g_strcmp0 (prop_key, "no-history") == 0 && g_variant_get_boolean (prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_NO_HISTORY; g_variant_unref (prop_value); } /* get the fd */ message = g_dbus_method_invocation_get_message (invocation); fd_list = g_dbus_message_get_unix_fd_list (message); if (fd_list == NULL || g_unix_fd_list_get_length (fd_list) != 1) { g_set_error (&error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid handle"); g_dbus_method_invocation_return_gerror (invocation, error); return; } fd = g_unix_fd_list_get (fd_list, 0, &error); if (fd < 0) { g_dbus_method_invocation_return_gerror (invocation, error); return; } /* parse the cab file before authenticating so we can work out * what action ID to use, for instance, if this is trusted -- * this will also close the fd when done */ archive_size_max = fu_engine_get_archive_size_max (priv->engine); helper->blob_cab = fu_common_get_contents_fd (fd, archive_size_max, &error); if (helper->blob_cab == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } /* install all the things in the store */ helper->subject = polkit_system_bus_name_new (sender); if (!fu_main_install_with_helper (g_steal_pointer (&helper), &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return; } /* async return */ return; } if (g_strcmp0 (method_name, "GetDetails") == 0) { GDBusMessage *message; GUnixFDList *fd_list; gint32 fd_handle = 0; gint fd; g_autoptr(GPtrArray) results = NULL; /* get parameters */ g_variant_get (parameters, "(h)", &fd_handle); g_debug ("Called %s(%i)", method_name, fd_handle); /* get the fd */ message = g_dbus_method_invocation_get_message (invocation); fd_list = g_dbus_message_get_unix_fd_list (message); if (fd_list == NULL || g_unix_fd_list_get_length (fd_list) != 1) { g_set_error (&error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid handle"); g_dbus_method_invocation_return_gerror (invocation, error); return; } fd = g_unix_fd_list_get (fd_list, 0, &error); if (fd < 0) { g_dbus_method_invocation_return_gerror (invocation, error); return; } /* get details about the file (will close the fd when done) */ results = fu_engine_get_details (priv->engine, fd, &error); if (results == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return; } val = fu_main_result_array_to_variant (results); g_dbus_method_invocation_return_value (invocation, val); return; } g_set_error (&error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "no such method %s", method_name); g_dbus_method_invocation_return_gerror (invocation, error); } static GVariant * fu_main_daemon_get_property (GDBusConnection *connection_, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *) user_data; /* activity */ fu_engine_idle_reset (priv->engine); if (g_strcmp0 (property_name, "DaemonVersion") == 0) return g_variant_new_string (VERSION); if (g_strcmp0 (property_name, "Tainted") == 0) return g_variant_new_boolean (fu_engine_get_tainted (priv->engine)); if (g_strcmp0 (property_name, "Status") == 0) return g_variant_new_uint32 (fu_engine_get_status (priv->engine)); /* return an error */ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "failed to get daemon property %s", property_name); return NULL; } static void fu_main_on_bus_acquired_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *) user_data; guint registration_id; g_autoptr(GError) error = NULL; static const GDBusInterfaceVTable interface_vtable = { fu_main_daemon_method_call, fu_main_daemon_get_property, NULL }; priv->connection = g_object_ref (connection); registration_id = g_dbus_connection_register_object (connection, FWUPD_DBUS_PATH, priv->introspection_daemon->interfaces[0], &interface_vtable, priv, /* user_data */ NULL, /* user_data_free_func */ NULL); /* GError** */ g_assert (registration_id > 0); /* connect to D-Bus directly */ priv->proxy_uid = g_dbus_proxy_new_sync (priv->connection, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", NULL, &error); if (priv->proxy_uid == NULL) { g_warning ("cannot connect to DBus: %s", error->message); return; } } static void fu_main_on_name_acquired_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { g_debug ("FuMain: acquired name: %s", name); } static void fu_main_on_name_lost_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *) user_data; g_debug ("FuMain: lost name: %s", name); g_main_loop_quit (priv->loop); } static gboolean fu_main_timed_exit_cb (gpointer user_data) { GMainLoop *loop = (GMainLoop *) user_data; g_main_loop_quit (loop); return G_SOURCE_REMOVE; } static void fu_main_argv_changed_cb (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *) user_data; /* can do straight away? */ if (priv->update_in_progress) { g_warning ("binary changed during a firmware update, ignoring"); return; } g_debug ("binary changed, shutting down"); g_main_loop_quit (priv->loop); } static GDBusNodeInfo * fu_main_load_introspection (const gchar *filename, GError **error) { g_autoptr(GBytes) data = NULL; g_autofree gchar *path = NULL; /* lookup data */ path = g_build_filename ("/org/freedesktop/fwupd", filename, NULL); data = g_resource_lookup_data (fu_get_resource (), path, G_RESOURCE_LOOKUP_FLAGS_NONE, error); if (data == NULL) return NULL; /* build introspection from XML */ return g_dbus_node_info_new_for_xml (g_bytes_get_data (data, NULL), error); } static void fu_main_private_free (FuMainPrivate *priv) { if (priv->loop != NULL) g_main_loop_unref (priv->loop); if (priv->owner_id > 0) g_bus_unown_name (priv->owner_id); if (priv->proxy_uid != NULL) g_object_unref (priv->proxy_uid); if (priv->engine != NULL) g_object_unref (priv->engine); if (priv->connection != NULL) g_object_unref (priv->connection); if (priv->authority != NULL) g_object_unref (priv->authority); if (priv->argv0_monitor != NULL) g_object_unref (priv->argv0_monitor); if (priv->introspection_daemon != NULL) g_dbus_node_info_unref (priv->introspection_daemon); g_free (priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuMainPrivate, fu_main_private_free) #pragma clang diagnostic pop int main (int argc, char *argv[]) { gboolean immediate_exit = FALSE; gboolean timed_exit = FALSE; const GOptionEntry options[] = { { "timed-exit", '\0', 0, G_OPTION_ARG_NONE, &timed_exit, /* TRANSLATORS: exit after we've started up, used for user profiling */ _("Exit after a small delay"), NULL }, { "immediate-exit", '\0', 0, G_OPTION_ARG_NONE, &immediate_exit, /* TRANSLATORS: exit straight away, used for automatic profiling */ _("Exit after the engine has loaded"), NULL }, { NULL} }; g_autoptr(FuMainPrivate) priv = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) argv0_file = g_file_new_for_path (argv[0]); g_autoptr(GOptionContext) context = NULL; setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); /* TRANSLATORS: program name */ g_set_application_name (_("Firmware Update Daemon")); context = g_option_context_new (NULL); g_option_context_add_main_entries (context, options, NULL); g_option_context_add_group (context, fu_debug_get_option_group ()); /* TRANSLATORS: program summary */ g_option_context_set_summary (context, _("Firmware Update D-Bus Service")); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_printerr ("Failed to parse command line: %s\n", error->message); return EXIT_FAILURE; } /* create new objects */ priv = g_new0 (FuMainPrivate, 1); priv->loop = g_main_loop_new (NULL, FALSE); /* load engine */ priv->engine = fu_engine_new (FU_APP_FLAGS_NONE); g_signal_connect (priv->engine, "changed", G_CALLBACK (fu_main_engine_changed_cb), priv); g_signal_connect (priv->engine, "device-added", G_CALLBACK (fu_main_engine_device_added_cb), priv); g_signal_connect (priv->engine, "device-removed", G_CALLBACK (fu_main_engine_device_removed_cb), priv); g_signal_connect (priv->engine, "device-changed", G_CALLBACK (fu_main_engine_device_changed_cb), priv); g_signal_connect (priv->engine, "status-changed", G_CALLBACK (fu_main_engine_status_changed_cb), priv); g_signal_connect (priv->engine, "percentage-changed", G_CALLBACK (fu_main_engine_percentage_changed_cb), priv); if (!fu_engine_load (priv->engine, FU_ENGINE_LOAD_FLAG_NONE, &error)) { g_printerr ("Failed to load engine: %s\n", error->message); return EXIT_FAILURE; } g_unix_signal_add_full (G_PRIORITY_DEFAULT, SIGTERM, fu_main_sigterm_cb, priv, NULL); /* restart the daemon if the binary gets replaced */ priv->argv0_monitor = g_file_monitor_file (argv0_file, G_FILE_MONITOR_NONE, NULL, &error); g_signal_connect (priv->argv0_monitor, "changed", G_CALLBACK (fu_main_argv_changed_cb), priv); /* load introspection from file */ priv->introspection_daemon = fu_main_load_introspection (FWUPD_DBUS_INTERFACE ".xml", &error); if (priv->introspection_daemon == NULL) { g_printerr ("Failed to load introspection: %s\n", error->message); return EXIT_FAILURE; } /* get authority */ priv->authority = polkit_authority_get_sync (NULL, &error); if (priv->authority == NULL) { g_printerr ("Failed to load authority: %s\n", error->message); return EXIT_FAILURE; } /* own the object */ priv->owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, FWUPD_DBUS_SERVICE, G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | G_BUS_NAME_OWNER_FLAGS_REPLACE, fu_main_on_bus_acquired_cb, fu_main_on_name_acquired_cb, fu_main_on_name_lost_cb, priv, NULL); /* Only timeout and close the mainloop if we have specified it * on the command line */ if (immediate_exit) g_idle_add (fu_main_timed_exit_cb, priv->loop); else if (timed_exit) g_timeout_add_seconds (5, fu_main_timed_exit_cb, priv->loop); g_debug ("Started with locale %s", g_getenv ("LANG")); /* wait */ g_message ("Daemon ready for requests"); g_main_loop_run (priv->loop); /* success */ return EXIT_SUCCESS; } fwupd-1.2.14/src/fu-mutex.h000066400000000000000000000022071402665037500154470ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2019 Kalev Lember * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #if !GLIB_CHECK_VERSION(2, 61, 1) /* Backported GRWLock autoptr support for older glib versions */ typedef void GRWLockWriterLocker; static inline GRWLockWriterLocker * g_rw_lock_writer_locker_new (GRWLock *rw_lock) { g_rw_lock_writer_lock (rw_lock); return (GRWLockWriterLocker *) rw_lock; } static inline void g_rw_lock_writer_locker_free (GRWLockWriterLocker *locker) { g_rw_lock_writer_unlock ((GRWLock *) locker); } typedef void GRWLockReaderLocker; static inline GRWLockReaderLocker * g_rw_lock_reader_locker_new (GRWLock *rw_lock) { g_rw_lock_reader_lock (rw_lock); return (GRWLockReaderLocker *) rw_lock; } static inline void g_rw_lock_reader_locker_free (GRWLockReaderLocker *locker) { g_rw_lock_reader_unlock ((GRWLock *) locker); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(GRWLockWriterLocker, g_rw_lock_writer_locker_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GRWLockReaderLocker, g_rw_lock_reader_locker_free) #endif G_END_DECLS fwupd-1.2.14/src/fu-offline.c000066400000000000000000000204351402665037500157250ustar00rootroot00000000000000/* * Copyright (C) 2015-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "fu-history.h" #include "fu-plugin-private.h" #include "fu-util-common.h" typedef enum { FU_OFFLINE_FLAG_NONE = 0, FU_OFFLINE_FLAG_ENABLE = 1 << 0, FU_OFFLINE_FLAG_USE_PROGRESS = 1 << 1, } FuOfflineFlag; struct FuUtilPrivate { gchar *splash_cmd; GTimer *splash_timer; FuOfflineFlag splash_flags; }; static gboolean fu_offline_set_splash_progress (FuUtilPrivate *priv, guint percentage, GError **error) { g_autofree gchar *str = g_strdup_printf ("%u", percentage); const gchar *argv[] = { priv->splash_cmd, "system-update", "--progress", str, NULL }; /* call into plymouth if installed */ if (priv->splash_flags == FU_OFFLINE_FLAG_NONE) { /* TRANSLATORS: console message when not using plymouth */ g_printerr ("%s: %u%%\n", _("Percentage complete"), percentage); return TRUE; } /* fall back to really old mode that should be supported by anything */ if ((priv->splash_flags & FU_OFFLINE_FLAG_USE_PROGRESS) == 0) { argv[1] = "display-message"; argv[2] = "--text"; } return fu_common_spawn_sync (argv, NULL, NULL, 200, NULL, error); } static gboolean fu_offline_set_splash_mode (FuUtilPrivate *priv, GError **error) { g_autoptr(GError) error_local = NULL; const gchar *argv[] = { priv->splash_cmd, "change-mode", "--system-upgrade", NULL }; /* call into plymouth if installed */ if (priv->splash_cmd == NULL) { /* TRANSLATORS: console message when no Plymouth is installed */ g_printerr ("%s\n", _("Installing Firmware…")); return TRUE; } /* try the new fancy mode, then fall back to really old mode */ if (!fu_common_spawn_sync (argv, NULL, NULL, 1500, NULL, &error_local)) { argv[2] = "--updates"; if (!fu_common_spawn_sync (argv, NULL, NULL, 1500, NULL, error)) { g_prefix_error (error, "%s: ", error_local->message); return FALSE; } priv->splash_flags = FU_OFFLINE_FLAG_ENABLE; return TRUE; } /* success */ priv->splash_flags = FU_OFFLINE_FLAG_ENABLE | FU_OFFLINE_FLAG_USE_PROGRESS; return TRUE; } static gboolean fu_offline_set_splash_reboot (FuUtilPrivate *priv, GError **error) { g_autoptr(GError) error_local = NULL; const gchar *argv[] = { priv->splash_cmd, "change-mode", "--reboot", NULL }; /* call into plymouth if installed */ if (priv->splash_flags == FU_OFFLINE_FLAG_NONE) { /* TRANSLATORS: console message when not using plymouth */ g_printerr ("%s\n", _("Rebooting…")); return TRUE; } /* try the new fancy mode, then fall back to really old mode */ if (!fu_common_spawn_sync (argv, NULL, NULL, 200, NULL, &error_local)) { /* fall back to really old mode that should be supported */ argv[2] = "--shutdown"; if (!fu_common_spawn_sync (argv, NULL, NULL, 200, NULL, error)) { g_prefix_error (error, "%s: ", error_local->message); return FALSE; } return TRUE; } /* success */ return TRUE; } static void fu_offline_client_notify_cb (GObject *object, GParamSpec *pspec, gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *) user_data; FwupdClient *client = FWUPD_CLIENT (object); /* rate limit to 1 second */ if (g_timer_elapsed (priv->splash_timer, NULL) < 1.f || fwupd_client_get_percentage (client) < 5) return; fu_offline_set_splash_progress (priv, fwupd_client_get_percentage (client), NULL); g_timer_reset (priv->splash_timer); } static void fu_util_private_free (FuUtilPrivate *priv) { if (priv->splash_timer != NULL) g_timer_destroy (priv->splash_timer); g_free (priv->splash_cmd); g_free (priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free) #pragma clang diagnostic pop int main (int argc, char *argv[]) { gint vercmp; guint cnt = 0; g_autofree gchar *link = NULL; g_autofree gchar *target = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); g_autoptr(FuHistory) history = NULL; g_autoptr(FwupdClient) client = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(FuUtilPrivate) priv = g_new0 (FuUtilPrivate, 1); setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); /* verify this is pointing to our cache */ link = g_file_read_link (FU_OFFLINE_TRIGGER_FILENAME, NULL); if (link == NULL) return EXIT_SUCCESS; if (g_strcmp0 (link, target) != 0) return EXIT_SUCCESS; /* do this first to avoid a loop if this tool segfaults */ g_unlink (FU_OFFLINE_TRIGGER_FILENAME); /* ensure root user */ if (getuid () != 0 || geteuid () != 0) { /* TRANSLATORS: the user needs to stop playing with stuff */ g_printerr ("%s\n", _("This tool can only be used by the root user")); return EXIT_FAILURE; } /* find plymouth, but not an error if not found */ priv->splash_cmd = g_find_program_in_path ("plymouth"); priv->splash_timer = g_timer_new (); /* ensure D-Bus errors are registered */ fwupd_error_quark (); /* get prepared updates */ history = fu_history_new (); results = fu_history_get_devices (history, &error); if (results == NULL) { /* TRANSLATORS: we could not get the devices to update offline */ g_printerr ("%s: %s\n", _("Failed to get pending devices"), error->message); return EXIT_FAILURE; } /* connect to the daemon */ client = fwupd_client_new (); g_signal_connect (client, "notify::percentage", G_CALLBACK (fu_offline_client_notify_cb), priv); if (!fwupd_client_connect (client, NULL, &error)) { /* TRANSLATORS: we could not talk to the fwupd daemon */ g_printerr ("%s: %s\n", _("Failed to connect to daemon"), error->message); return EXIT_FAILURE; } /* set up splash */ if (!fu_offline_set_splash_mode (priv, &error)) { /* TRANSLATORS: we could not talk to plymouth */ g_printerr ("%s: %s\n", _("Failed to set splash mode"), error->message); return EXIT_FAILURE; } /* apply each update */ for (guint i = 0; i < results->len; i++) { FwupdDevice *dev = g_ptr_array_index (results, i); FwupdRelease *rel = fwupd_device_get_release_default (dev); /* check not already done */ if (fwupd_device_get_update_state (dev) != FWUPD_UPDATE_STATE_PENDING) continue; /* tell the user what's going to happen */ vercmp = fu_common_vercmp (fwupd_device_get_version (dev), fwupd_release_get_version (rel)); if (vercmp == 0) { /* TRANSLATORS: the first replacement is a display name * e.g. "ColorHugALS" and the second is a version number * e.g. "1.2.3" */ g_print (_("Reinstalling %s with %s... "), fwupd_device_get_name (dev), fwupd_release_get_version (rel)); } else if (vercmp > 0) { /* TRANSLATORS: the first replacement is a display name * e.g. "ColorHugALS" and the second and third are * version numbers e.g. "1.2.3" */ g_print (_("Downgrading %s from %s to %s... "), fwupd_device_get_name (dev), fwupd_device_get_version (dev), fwupd_release_get_version (rel)); } else if (vercmp < 0) { /* TRANSLATORS: the first replacement is a display name * e.g. "ColorHugALS" and the second and third are * version numbers e.g. "1.2.3" */ g_print (_("Updating %s from %s to %s... "), fwupd_device_get_name (dev), fwupd_device_get_version (dev), fwupd_release_get_version (rel)); } if (!fwupd_client_install (client, fwupd_device_get_id (dev), fwupd_release_get_filename (rel), FWUPD_INSTALL_FLAG_ALLOW_REINSTALL | FWUPD_INSTALL_FLAG_ALLOW_OLDER | FWUPD_INSTALL_FLAG_OFFLINE, NULL, &error)) { /* TRANSLATORS: we could not install for some reason */ g_printerr ("%s: %s\n", _("Failed to install firmware update"), error->message); return EXIT_FAILURE; } cnt++; } /* nothing to do */ if (cnt == 0) { /* TRANSLATORS: nothing was updated offline */ g_printerr ("%s\n", _("No updates were applied")); return EXIT_FAILURE; } /* reboot */ fu_offline_set_splash_reboot (priv, NULL); if (!fu_util_update_reboot (&error)) { /* TRANSLATORS: we could not reboot for some reason */ g_printerr ("%s: %s\n", _("Failed to reboot"), error->message); return EXIT_FAILURE; } /* success */ g_print ("%s\n", _("Done!")); return EXIT_SUCCESS; } fwupd-1.2.14/src/fu-plugin-list.c000066400000000000000000000206541402665037500165550ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuPluginList" #include "config.h" #include #include "fu-plugin-list.h" #include "fu-plugin-private.h" #include "fwupd-error.h" /** * SECTION:fu-plugin-list * @short_description: a list of plugins * * This list of plugins provides a way to get the specific plugin quickly using * a hash table and also any plugin-list specific functionality such as * sorting by dependency order. * * See also: #FuPlugin */ static void fu_plugin_list_finalize (GObject *obj); struct _FuPluginList { GObject parent_instance; GPtrArray *plugins; /* of FuPlugin */ GHashTable *plugins_hash; /* of name : FuPlugin */ }; G_DEFINE_TYPE (FuPluginList, fu_plugin_list, G_TYPE_OBJECT) /** * fu_plugin_list_get_all: * @self: A #FuPluginList * * Gets all the plugins that have been added. * * Returns: (transfer none) (element-type FuPlugin): the plugins * * Since: 1.0.2 **/ GPtrArray * fu_plugin_list_get_all (FuPluginList *self) { g_return_val_if_fail (FU_IS_PLUGIN_LIST (self), NULL); return self->plugins; } /** * fu_plugin_list_add: * @self: A #FuPluginList * @plugin: A #FuPlugin * * Adds a plugin to the list. The plugin name is used for a hash key and must * be set before calling this function. * * Since: 1.0.2 **/ void fu_plugin_list_add (FuPluginList *self, FuPlugin *plugin) { g_return_if_fail (FU_IS_PLUGIN_LIST (self)); g_return_if_fail (FU_IS_PLUGIN (plugin)); g_return_if_fail (fu_plugin_get_name (plugin) != NULL); g_ptr_array_add (self->plugins, g_object_ref (plugin)); g_hash_table_insert (self->plugins_hash, g_strdup (fu_plugin_get_name (plugin)), g_object_ref (plugin)); } /** * fu_plugin_list_find_by_name: * @self: A #FuPluginList * @name: A #FuPlugin name, e.g. "dfu" * @error: A #GError, or %NULL * * Finds a specific plugin using the plugin name. * * Returns: (transfer none): a plugin, or %NULL * * Since: 1.0.2 **/ FuPlugin * fu_plugin_list_find_by_name (FuPluginList *self, const gchar *name, GError **error) { g_return_val_if_fail (FU_IS_PLUGIN_LIST (self), NULL); g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); for (guint i = 0; i < self->plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index (self->plugins, i); if (g_strcmp0 (fu_plugin_get_name (plugin), name) == 0) return plugin; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no plugin %s found", name); return NULL; } static gint fu_plugin_list_sort_cb (gconstpointer a, gconstpointer b) { FuPlugin **pa = (FuPlugin **) a; FuPlugin **pb = (FuPlugin **) b; return fu_plugin_order_compare (*pa, *pb); } /** * fu_plugin_list_depsolve: * @self: A #FuPluginList * @error: A #GError, or %NULL * * Depsolves the list of plugins into the correct order. Some plugin methods * are called on all plugins and for some situations the order they are called * may be important. Use fu_plugin_add_rule() to affect the depsolved order * if required. * * Returns: %TRUE for success, or %FALSE if the set could not be depsolved * * Since: 1.0.2 **/ gboolean fu_plugin_list_depsolve (FuPluginList *self, GError **error) { FuPlugin *dep; GPtrArray *deps; gboolean changes; guint dep_loop_check = 0; g_return_val_if_fail (FU_IS_PLUGIN_LIST (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* order by deps */ do { changes = FALSE; for (guint i = 0; i < self->plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index (self->plugins, i); deps = fu_plugin_get_rules (plugin, FU_PLUGIN_RULE_RUN_AFTER); for (guint j = 0; j < deps->len && !changes; j++) { const gchar *plugin_name = g_ptr_array_index (deps, j); dep = fu_plugin_list_find_by_name (self, plugin_name, NULL); if (dep == NULL) { g_debug ("cannot find plugin '%s' " "requested by '%s'", plugin_name, fu_plugin_get_name (plugin)); continue; } if (!fu_plugin_get_enabled (dep)) continue; if (fu_plugin_get_order (plugin) <= fu_plugin_get_order (dep)) { g_debug ("%s [%u] to be ordered after %s [%u] " "so promoting to [%u]", fu_plugin_get_name (plugin), fu_plugin_get_order (plugin), fu_plugin_get_name (dep), fu_plugin_get_order (dep), fu_plugin_get_order (dep) + 1); fu_plugin_set_order (plugin, fu_plugin_get_order (dep) + 1); changes = TRUE; } } } for (guint i = 0; i < self->plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index (self->plugins, i); deps = fu_plugin_get_rules (plugin, FU_PLUGIN_RULE_RUN_BEFORE); for (guint j = 0; j < deps->len && !changes; j++) { const gchar *plugin_name = g_ptr_array_index (deps, j); dep = fu_plugin_list_find_by_name (self, plugin_name, NULL); if (dep == NULL) { g_debug ("cannot find plugin '%s' " "requested by '%s'", plugin_name, fu_plugin_get_name (plugin)); continue; } if (!fu_plugin_get_enabled (dep)) continue; if (fu_plugin_get_order (plugin) >= fu_plugin_get_order (dep)) { g_debug ("%s [%u] to be ordered before %s [%u] " "so promoting to [%u]", fu_plugin_get_name (plugin), fu_plugin_get_order (plugin), fu_plugin_get_name (dep), fu_plugin_get_order (dep), fu_plugin_get_order (dep) + 1); fu_plugin_set_order (dep, fu_plugin_get_order (plugin) + 1); changes = TRUE; } } } /* set priority as well */ for (guint i = 0; i < self->plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index (self->plugins, i); deps = fu_plugin_get_rules (plugin, FU_PLUGIN_RULE_BETTER_THAN); for (guint j = 0; j < deps->len && !changes; j++) { const gchar *plugin_name = g_ptr_array_index (deps, j); dep = fu_plugin_list_find_by_name (self, plugin_name, NULL); if (dep == NULL) { g_debug ("cannot find plugin '%s' " "referenced by '%s'", plugin_name, fu_plugin_get_name (plugin)); continue; } if (!fu_plugin_get_enabled (dep)) continue; if (fu_plugin_get_priority (plugin) <= fu_plugin_get_priority (dep)) { g_debug ("%s [%u] better than %s [%u] " "so bumping to [%u]", fu_plugin_get_name (plugin), fu_plugin_get_priority (plugin), fu_plugin_get_name (dep), fu_plugin_get_priority (dep), fu_plugin_get_priority (dep) + 1); fu_plugin_set_priority (plugin, fu_plugin_get_priority (dep) + 1); changes = TRUE; } } } /* check we're not stuck */ if (dep_loop_check++ > 100) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "got stuck in dep loop"); return FALSE; } } while (changes); /* check for conflicts */ for (guint i = 0; i < self->plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index (self->plugins, i); if (!fu_plugin_get_enabled (plugin)) continue; deps = fu_plugin_get_rules (plugin, FU_PLUGIN_RULE_CONFLICTS); for (guint j = 0; j < deps->len && !changes; j++) { const gchar *plugin_name = g_ptr_array_index (deps, j); dep = fu_plugin_list_find_by_name (self, plugin_name, NULL); if (dep == NULL) continue; if (!fu_plugin_get_enabled (dep)) continue; g_debug ("disabling %s as conflicts with %s", fu_plugin_get_name (dep), fu_plugin_get_name (plugin)); fu_plugin_set_enabled (dep, FALSE); } } /* sort by order */ g_ptr_array_sort (self->plugins, fu_plugin_list_sort_cb); return TRUE; } static void fu_plugin_list_class_init (FuPluginListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_plugin_list_finalize; } static void fu_plugin_list_init (FuPluginList *self) { self->plugins = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); self->plugins_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); } static void fu_plugin_list_finalize (GObject *obj) { FuPluginList *self = FU_PLUGIN_LIST (obj); g_ptr_array_unref (self->plugins); g_hash_table_unref (self->plugins_hash); G_OBJECT_CLASS (fu_plugin_list_parent_class)->finalize (obj); } /** * fu_plugin_list_new: * * Creates a new plugin list. * * Returns: (transfer full): a #FuPluginList * * Since: 1.0.2 **/ FuPluginList * fu_plugin_list_new (void) { FuPluginList *self; self = g_object_new (FU_TYPE_PLUGIN_LIST, NULL); return FU_PLUGIN_LIST (self); } fwupd-1.2.14/src/fu-plugin-list.h000066400000000000000000000013041402665037500165510ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_PLUGIN_LIST (fu_plugin_list_get_type ()) G_DECLARE_FINAL_TYPE (FuPluginList, fu_plugin_list, FU, PLUGIN_LIST, GObject) FuPluginList *fu_plugin_list_new (void); void fu_plugin_list_add (FuPluginList *self, FuPlugin *plugin); GPtrArray *fu_plugin_list_get_all (FuPluginList *self); FuPlugin *fu_plugin_list_find_by_name (FuPluginList *self, const gchar *name, GError **error); gboolean fu_plugin_list_depsolve (FuPluginList *self, GError **error); G_END_DECLS fwupd-1.2.14/src/fu-plugin-private.h000066400000000000000000000106071402665037500172560ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-quirks.h" #include "fu-plugin.h" #include "fu-smbios.h" G_BEGIN_DECLS #define FU_OFFLINE_TRIGGER_FILENAME FU_OFFLINE_DESTDIR "/system-update" FuPlugin *fu_plugin_new (void); void fu_plugin_set_usb_context (FuPlugin *self, GUsbContext *usb_ctx); void fu_plugin_set_hwids (FuPlugin *self, FuHwids *hwids); void fu_plugin_set_udev_subsystems (FuPlugin *self, GPtrArray *udev_subsystems); void fu_plugin_set_quirks (FuPlugin *self, FuQuirks *quirks); void fu_plugin_set_runtime_versions (FuPlugin *self, GHashTable *runtime_versions); void fu_plugin_set_compile_versions (FuPlugin *self, GHashTable *compile_versions); void fu_plugin_set_smbios (FuPlugin *self, FuSmbios *smbios); guint fu_plugin_get_order (FuPlugin *self); void fu_plugin_set_order (FuPlugin *self, guint order); guint fu_plugin_get_priority (FuPlugin *self); void fu_plugin_set_priority (FuPlugin *self, guint priority); void fu_plugin_set_name (FuPlugin *self, const gchar *name); const gchar *fu_plugin_get_build_hash (FuPlugin *self); GPtrArray *fu_plugin_get_rules (FuPlugin *self, FuPluginRule rule); gboolean fu_plugin_has_rule (FuPlugin *self, FuPluginRule rule, const gchar *name); GHashTable *fu_plugin_get_report_metadata (FuPlugin *self); gboolean fu_plugin_open (FuPlugin *self, const gchar *filename, GError **error); gboolean fu_plugin_runner_startup (FuPlugin *self, GError **error); gboolean fu_plugin_runner_coldplug (FuPlugin *self, GError **error); gboolean fu_plugin_runner_coldplug_prepare (FuPlugin *self, GError **error); gboolean fu_plugin_runner_coldplug_cleanup (FuPlugin *self, GError **error); gboolean fu_plugin_runner_recoldplug (FuPlugin *self, GError **error); gboolean fu_plugin_runner_update_prepare (FuPlugin *self, FwupdInstallFlags flags, FuDevice *device, GError **error); gboolean fu_plugin_runner_update_cleanup (FuPlugin *self, FwupdInstallFlags flags, FuDevice *device, GError **error); gboolean fu_plugin_runner_composite_prepare (FuPlugin *self, GPtrArray *devices, GError **error); gboolean fu_plugin_runner_composite_cleanup (FuPlugin *self, GPtrArray *devices, GError **error); gboolean fu_plugin_runner_update_attach (FuPlugin *self, FuDevice *device, GError **error); gboolean fu_plugin_runner_update_detach (FuPlugin *self, FuDevice *device, GError **error); gboolean fu_plugin_runner_update_reload (FuPlugin *self, FuDevice *device, GError **error); gboolean fu_plugin_runner_usb_device_added (FuPlugin *self, FuUsbDevice *device, GError **error); gboolean fu_plugin_runner_udev_device_added (FuPlugin *self, FuUdevDevice *device, GError **error); void fu_plugin_runner_device_removed (FuPlugin *self, FuDevice *device); void fu_plugin_runner_device_register (FuPlugin *self, FuDevice *device); gboolean fu_plugin_runner_update (FuPlugin *self, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error); gboolean fu_plugin_runner_verify (FuPlugin *self, FuDevice *device, FuPluginVerifyFlags flags, GError **error); gboolean fu_plugin_runner_activate (FuPlugin *self, FuDevice *device, GError **error); gboolean fu_plugin_runner_unlock (FuPlugin *self, FuDevice *device, GError **error); gboolean fu_plugin_runner_clear_results (FuPlugin *self, FuDevice *device, GError **error); gboolean fu_plugin_runner_get_results (FuPlugin *self, FuDevice *device, GError **error); gboolean fu_plugin_runner_schedule_update (FuPlugin *self, FuDevice *device, FwupdRelease *release, GBytes *blob_cab, FwupdInstallFlags flags, GError **error); gint fu_plugin_name_compare (FuPlugin *plugin1, FuPlugin *plugin2); gint fu_plugin_order_compare (FuPlugin *plugin1, FuPlugin *plugin2); /* utils */ gchar *fu_plugin_guess_name_from_fn (const gchar *filename); G_END_DECLS fwupd-1.2.14/src/fu-plugin-vfuncs.h000066400000000000000000000054101402665037500171040ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-device.h" #include "fu-hash.h" G_BEGIN_DECLS void fu_plugin_init (FuPlugin *plugin); void fu_plugin_destroy (FuPlugin *plugin); gboolean fu_plugin_startup (FuPlugin *plugin, GError **error); gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error); gboolean fu_plugin_coldplug_prepare (FuPlugin *plugin, GError **error); gboolean fu_plugin_coldplug_cleanup (FuPlugin *plugin, GError **error); gboolean fu_plugin_recoldplug (FuPlugin *plugin, GError **error); gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FwupdInstallFlags flags, GError **error); gboolean fu_plugin_verify (FuPlugin *plugin, FuDevice *dev, FuPluginVerifyFlags flags, GError **error); gboolean fu_plugin_verify_attach (FuPlugin *plugin, FuDevice *dev, GError **error); gboolean fu_plugin_verify_detach (FuPlugin *plugin, FuDevice *dev, GError **error); gboolean fu_plugin_unlock (FuPlugin *plugin, FuDevice *dev, GError **error); gboolean fu_plugin_activate (FuPlugin *plugin, FuDevice *dev, GError **error); gboolean fu_plugin_clear_results (FuPlugin *plugin, FuDevice *dev, GError **error); gboolean fu_plugin_get_results (FuPlugin *plugin, FuDevice *dev, GError **error); gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *dev, GError **error); gboolean fu_plugin_update_detach (FuPlugin *plugin, FuDevice *dev, GError **error); gboolean fu_plugin_update_reload (FuPlugin *plugin, FuDevice *dev, GError **error); gboolean fu_plugin_update_prepare (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *dev, GError **error); gboolean fu_plugin_update_cleanup (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *dev, GError **error); gboolean fu_plugin_composite_prepare (FuPlugin *plugin, GPtrArray *devices, GError **error); gboolean fu_plugin_composite_cleanup (FuPlugin *plugin, GPtrArray *devices, GError **error); gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error); gboolean fu_plugin_udev_device_added (FuPlugin *plugin, FuUdevDevice *device, GError **error); gboolean fu_plugin_device_removed (FuPlugin *plugin, FuDevice *device, GError **error); void fu_plugin_device_registered (FuPlugin *plugin, FuDevice *dev); G_END_DECLS fwupd-1.2.14/src/fu-plugin.c000066400000000000000000001535521402665037500156100ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuPlugin" #include "config.h" #include #include #include #include #include #include #ifdef HAVE_VALGRIND #include #endif /* HAVE_VALGRIND */ #include "fu-device-private.h" #include "fu-plugin-private.h" #include "fu-history.h" #include "fu-mutex.h" /** * SECTION:fu-plugin * @short_description: a daemon plugin * * An object that represents a plugin run by the daemon. * * See also: #FuDevice */ #define FU_PLUGIN_COLDPLUG_DELAY_MAXIMUM 3000u /* ms */ static void fu_plugin_finalize (GObject *object); typedef struct { GModule *module; GUsbContext *usb_ctx; gboolean enabled; guint order; guint priority; GPtrArray *rules[FU_PLUGIN_RULE_LAST]; gchar *name; gchar *build_hash; FuHwids *hwids; FuQuirks *quirks; GHashTable *runtime_versions; GHashTable *compile_versions; GPtrArray *udev_subsystems; FuSmbios *smbios; GHashTable *devices; /* platform_id:GObject */ GRWLock devices_mutex; GHashTable *report_metadata; /* key:value */ FuPluginData *data; } FuPluginPrivate; enum { SIGNAL_DEVICE_ADDED, SIGNAL_DEVICE_REMOVED, SIGNAL_DEVICE_REGISTER, SIGNAL_RULES_CHANGED, SIGNAL_RECOLDPLUG, SIGNAL_SET_COLDPLUG_DELAY, SIGNAL_CHECK_SUPPORTED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE_WITH_PRIVATE (FuPlugin, fu_plugin, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_plugin_get_instance_private (o)) typedef const gchar *(*FuPluginGetNameFunc) (void); typedef void (*FuPluginInitFunc) (FuPlugin *self); typedef gboolean (*FuPluginStartupFunc) (FuPlugin *self, GError **error); typedef void (*FuPluginDeviceRegisterFunc) (FuPlugin *self, FuDevice *device); typedef gboolean (*FuPluginDeviceFunc) (FuPlugin *self, FuDevice *device, GError **error); typedef gboolean (*FuPluginFlaggedDeviceFunc) (FuPlugin *self, FwupdInstallFlags flags, FuDevice *device, GError **error); typedef gboolean (*FuPluginDeviceArrayFunc) (FuPlugin *self, GPtrArray *devices, GError **error); typedef gboolean (*FuPluginVerifyFunc) (FuPlugin *self, FuDevice *device, FuPluginVerifyFlags flags, GError **error); typedef gboolean (*FuPluginUpdateFunc) (FuPlugin *self, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error); typedef gboolean (*FuPluginUsbDeviceAddedFunc) (FuPlugin *self, FuUsbDevice *device, GError **error); typedef gboolean (*FuPluginUdevDeviceAddedFunc) (FuPlugin *self, FuUdevDevice *device, GError **error); /** * fu_plugin_get_name: * @self: A #FuPlugin * * Gets the plugin name. * * Returns: a plugin name, or %NULL for unknown. * * Since: 0.8.0 **/ const gchar * fu_plugin_get_name (FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_PLUGIN (self), NULL); return priv->name; } void fu_plugin_set_name (FuPlugin *self, const gchar *name) { FuPluginPrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_PLUGIN (self)); g_return_if_fail (name != NULL); g_free (priv->name); priv->name = g_strdup (name); } /** * fu_plugin_set_build_hash: * @self: A #FuPlugin * @build_hash: A checksum * * Sets the plugin build hash, typically a SHA256 checksum. All plugins must * set the correct checksum to avoid the daemon being marked as tainted. * * Since: 1.2.4 **/ void fu_plugin_set_build_hash (FuPlugin *self, const gchar *build_hash) { FuPluginPrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_PLUGIN (self)); g_return_if_fail (build_hash != NULL); g_free (priv->build_hash); priv->build_hash = g_strdup (build_hash); } const gchar * fu_plugin_get_build_hash (FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_PLUGIN (self), NULL); return priv->build_hash; } /** * fu_plugin_cache_lookup: * @self: A #FuPlugin * @id: the key * * Finds an object in the per-plugin cache. * * Returns: (transfer none): a #GObject, or %NULL for unfound. * * Since: 0.8.0 **/ gpointer fu_plugin_cache_lookup (FuPlugin *self, const gchar *id) { FuPluginPrivate *priv = GET_PRIVATE (self); g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&priv->devices_mutex); g_return_val_if_fail (FU_IS_PLUGIN (self), NULL); g_return_val_if_fail (id != NULL, NULL); g_return_val_if_fail (locker != NULL, NULL); return g_hash_table_lookup (priv->devices, id); } /** * fu_plugin_cache_add: * @self: A #FuPlugin * @id: the key * @dev: a #GObject, typically a #FuDevice * * Adds an object to the per-plugin cache. * * Since: 0.8.0 **/ void fu_plugin_cache_add (FuPlugin *self, const gchar *id, gpointer dev) { FuPluginPrivate *priv = GET_PRIVATE (self); g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_writer_locker_new (&priv->devices_mutex); g_return_if_fail (FU_IS_PLUGIN (self)); g_return_if_fail (id != NULL); g_return_if_fail (locker != NULL); g_hash_table_insert (priv->devices, g_strdup (id), g_object_ref (dev)); } /** * fu_plugin_cache_remove: * @self: A #FuPlugin * @id: the key * * Removes an object from the per-plugin cache. * * Since: 0.8.0 **/ void fu_plugin_cache_remove (FuPlugin *self, const gchar *id) { FuPluginPrivate *priv = GET_PRIVATE (self); g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_writer_locker_new (&priv->devices_mutex); g_return_if_fail (FU_IS_PLUGIN (self)); g_return_if_fail (id != NULL); g_return_if_fail (locker != NULL); g_hash_table_remove (priv->devices, id); } /** * fu_plugin_get_data: * @self: A #FuPlugin * * Gets the per-plugin allocated private data. This will return %NULL unless * fu_plugin_alloc_data() has been called by the plugin. * * Returns: (transfer none): a pointer to a structure, or %NULL for unset. * * Since: 0.8.0 **/ FuPluginData * fu_plugin_get_data (FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_PLUGIN (self), NULL); return priv->data; } /** * fu_plugin_alloc_data: * @self: A #FuPlugin * @data_sz: the size to allocate * * Allocates the per-plugin allocated private data. * * Returns: (transfer full): a pointer to a structure, or %NULL for unset. * * Since: 0.8.0 **/ FuPluginData * fu_plugin_alloc_data (FuPlugin *self, gsize data_sz) { FuPluginPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_PLUGIN (self), NULL); if (priv->data != NULL) { g_critical ("fu_plugin_alloc_data() already used by plugin"); return priv->data; } priv->data = g_malloc0 (data_sz); return priv->data; } /** * fu_plugin_get_usb_context: * @self: A #FuPlugin * * Gets the shared USB context that all plugins can use. * * Returns: (transfer none): a #GUsbContext. * * Since: 0.8.0 **/ GUsbContext * fu_plugin_get_usb_context (FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_PLUGIN (self), NULL); return priv->usb_ctx; } void fu_plugin_set_usb_context (FuPlugin *self, GUsbContext *usb_ctx) { FuPluginPrivate *priv = GET_PRIVATE (self); g_set_object (&priv->usb_ctx, usb_ctx); } /** * fu_plugin_get_enabled: * @self: A #FuPlugin * * Returns if the plugin is enabled. Plugins may self-disable using * fu_plugin_set_enabled() or can be disabled by the daemon. * * Returns: %TRUE if the plugin is currently enabled. * * Since: 0.8.0 **/ gboolean fu_plugin_get_enabled (FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_PLUGIN (self), FALSE); return priv->enabled; } /** * fu_plugin_set_enabled: * @self: A #FuPlugin * @enabled: the enabled value * * Enables or disables a plugin. Plugins can self-disable at any point. * * Since: 0.8.0 **/ void fu_plugin_set_enabled (FuPlugin *self, gboolean enabled) { FuPluginPrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_PLUGIN (self)); priv->enabled = enabled; } gchar * fu_plugin_guess_name_from_fn (const gchar *filename) { const gchar *prefix = "libfu_plugin_"; gchar *name; gchar *str = g_strstr_len (filename, -1, prefix); if (str == NULL) return NULL; name = g_strdup (str + strlen (prefix)); g_strdelimit (name, ".", '\0'); return name; } gboolean fu_plugin_open (FuPlugin *self, const gchar *filename, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginInitFunc func = NULL; priv->module = g_module_open (filename, 0); if (priv->module == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to open plugin: %s", g_module_error ()); return FALSE; } /* set automatically */ if (priv->name == NULL) priv->name = fu_plugin_guess_name_from_fn (filename); /* optional */ g_module_symbol (priv->module, "fu_plugin_init", (gpointer *) &func); if (func != NULL) { g_debug ("performing init() on %s", filename); func (self); } return TRUE; } /** * fu_plugin_device_add: * @self: A #FuPlugin * @device: A #FuDevice * * Asks the daemon to add a device to the exported list. If this device ID * has already been added by a different plugin then this request will be * ignored. * * Plugins should use fu_plugin_device_add_delay() if they are not capable of * actually flashing an image to the hardware so that higher-priority plugins * can add the device themselves. * * Since: 0.8.0 **/ void fu_plugin_device_add (FuPlugin *self, FuDevice *device) { GPtrArray *children; g_autoptr(GError) error = NULL; g_return_if_fail (FU_IS_PLUGIN (self)); g_return_if_fail (FU_IS_DEVICE (device)); /* ensure the device ID is set from the physical and logical IDs */ if (!fu_device_ensure_id (device, &error)) { g_warning ("ignoring add: %s", error->message); return; } g_debug ("emit added from %s: %s", fu_plugin_get_name (self), fu_device_get_id (device)); fu_device_set_created (device, (guint64) g_get_real_time () / G_USEC_PER_SEC); fu_device_set_plugin (device, fu_plugin_get_name (self)); g_signal_emit (self, signals[SIGNAL_DEVICE_ADDED], 0, device); /* add children if they have not already been added */ children = fu_device_get_children (device); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index (children, i); if (fu_device_get_created (child) == 0) fu_plugin_device_add (self, child); } } /** * fu_plugin_device_register: * @self: A #FuPlugin * @device: A #FuDevice * * Registers the device with other plugins so they can set metadata. * * Plugins do not have to call this manually as this is done automatically * when using fu_plugin_device_add(). They may wish to use this manually * if for intance the coldplug should be ignored based on the metadata * set from other plugins. * * Since: 0.9.7 **/ void fu_plugin_device_register (FuPlugin *self, FuDevice *device) { g_autoptr(GError) error = NULL; g_return_if_fail (FU_IS_PLUGIN (self)); g_return_if_fail (FU_IS_DEVICE (device)); /* ensure the device ID is set from the physical and logical IDs */ if (!fu_device_ensure_id (device, &error)) { g_warning ("ignoring registration: %s", error->message); return; } g_debug ("emit device-register from %s: %s", fu_plugin_get_name (self), fu_device_get_id (device)); g_signal_emit (self, signals[SIGNAL_DEVICE_REGISTER], 0, device); } /** * fu_plugin_device_remove: * @self: A #FuPlugin * @device: A #FuDevice * * Asks the daemon to remove a device from the exported list. * * Since: 0.8.0 **/ void fu_plugin_device_remove (FuPlugin *self, FuDevice *device) { g_return_if_fail (FU_IS_PLUGIN (self)); g_return_if_fail (FU_IS_DEVICE (device)); g_debug ("emit removed from %s: %s", fu_plugin_get_name (self), fu_device_get_id (device)); g_signal_emit (self, signals[SIGNAL_DEVICE_REMOVED], 0, device); } /** * fu_plugin_request_recoldplug: * @self: A #FuPlugin * * Ask all the plugins to coldplug all devices, which will include the prepare() * and cleanup() phases. Duplicate devices added will be ignored. * * Since: 0.8.0 **/ void fu_plugin_request_recoldplug (FuPlugin *self) { g_return_if_fail (FU_IS_PLUGIN (self)); g_signal_emit (self, signals[SIGNAL_RECOLDPLUG], 0); } /** * fu_plugin_check_hwid: * @self: A #FuPlugin * @hwid: A Hardware ID GUID, e.g. `6de5d951-d755-576b-bd09-c5cf66b27234` * * Checks to see if a specific GUID exists. All hardware IDs on a * specific system can be shown using the `fwupdmgr hwids` command. * * Returns: %TRUE if the HwId is found on the system. * * Since: 0.9.1 **/ gboolean fu_plugin_check_hwid (FuPlugin *self, const gchar *hwid) { FuPluginPrivate *priv = GET_PRIVATE (self); if (priv->hwids == NULL) return FALSE; return fu_hwids_has_guid (priv->hwids, hwid); } /** * fu_plugin_get_hwids: * @self: A #FuPlugin * * Returns all the HWIDs defined in the system. All hardware IDs on a * specific system can be shown using the `fwupdmgr hwids` command. * * Returns: (transfer none) (element-type utf-8): An array of GUIDs * * Since: 1.1.1 **/ GPtrArray * fu_plugin_get_hwids (FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE (self); if (priv->hwids == NULL) return NULL; return fu_hwids_get_guids (priv->hwids); } /** * fu_plugin_check_supported: * @self: A #FuPlugin * @guid: A Hardware ID GUID, e.g. `6de5d951-d755-576b-bd09-c5cf66b27234` * * Checks to see if a specific device GUID is supported, i.e. available in the * AppStream metadata. * * Returns: %TRUE if the device is supported. * * Since: 1.0.0 **/ gboolean fu_plugin_check_supported (FuPlugin *self, const gchar *guid) { gboolean retval = FALSE; g_signal_emit (self, signals[SIGNAL_CHECK_SUPPORTED], 0, guid, &retval); return retval; } /** * fu_plugin_get_dmi_value: * @self: A #FuPlugin * @dmi_id: A DMI ID, e.g. `BiosVersion` * * Gets a hardware DMI value. * * Returns: The string, or %NULL * * Since: 0.9.7 **/ const gchar * fu_plugin_get_dmi_value (FuPlugin *self, const gchar *dmi_id) { FuPluginPrivate *priv = GET_PRIVATE (self); if (priv->hwids == NULL) return NULL; return fu_hwids_get_value (priv->hwids, dmi_id); } /** * fu_plugin_get_smbios_string: * @self: A #FuPlugin * @structure_type: A SMBIOS structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * @offset: A SMBIOS offset * * Gets a hardware SMBIOS string. * * The @type and @offset can be referenced from the DMTF SMBIOS specification: * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf * * Returns: A string, or %NULL * * Since: 0.9.8 **/ const gchar * fu_plugin_get_smbios_string (FuPlugin *self, guint8 structure_type, guint8 offset) { FuPluginPrivate *priv = GET_PRIVATE (self); if (priv->smbios == NULL) return NULL; return fu_smbios_get_string (priv->smbios, structure_type, offset, NULL); } /** * fu_plugin_get_smbios_data: * @self: A #FuPlugin * @structure_type: A SMBIOS structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * * Gets a hardware SMBIOS data. * * Returns: (transfer full): A #GBytes, or %NULL * * Since: 0.9.8 **/ GBytes * fu_plugin_get_smbios_data (FuPlugin *self, guint8 structure_type) { FuPluginPrivate *priv = GET_PRIVATE (self); if (priv->smbios == NULL) return NULL; return fu_smbios_get_data (priv->smbios, structure_type, NULL); } void fu_plugin_set_hwids (FuPlugin *self, FuHwids *hwids) { FuPluginPrivate *priv = GET_PRIVATE (self); g_set_object (&priv->hwids, hwids); } void fu_plugin_set_udev_subsystems (FuPlugin *self, GPtrArray *udev_subsystems) { FuPluginPrivate *priv = GET_PRIVATE (self); if (priv->udev_subsystems != NULL) g_ptr_array_unref (priv->udev_subsystems); priv->udev_subsystems = g_ptr_array_ref (udev_subsystems); } void fu_plugin_set_quirks (FuPlugin *self, FuQuirks *quirks) { FuPluginPrivate *priv = GET_PRIVATE (self); g_set_object (&priv->quirks, quirks); } /** * fu_plugin_get_quirks: * @self: A #FuPlugin * * Returns the hardware database object. This can be used to discover device * quirks or other device-specific settings. * * Returns: (transfer none): a #FuQuirks, or %NULL if not set * * Since: 1.0.1 **/ FuQuirks * fu_plugin_get_quirks (FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE (self); return priv->quirks; } void fu_plugin_set_runtime_versions (FuPlugin *self, GHashTable *runtime_versions) { FuPluginPrivate *priv = GET_PRIVATE (self); priv->runtime_versions = g_hash_table_ref (runtime_versions); } /** * fu_plugin_add_runtime_version: * @self: A #FuPlugin * @component_id: An AppStream component id, e.g. "org.gnome.Software" * @version: A version string, e.g. "1.2.3" * * Sets a runtime version of a specific dependency. * * Since: 1.0.7 **/ void fu_plugin_add_runtime_version (FuPlugin *self, const gchar *component_id, const gchar *version) { FuPluginPrivate *priv = GET_PRIVATE (self); if (priv->runtime_versions == NULL) return; g_hash_table_insert (priv->runtime_versions, g_strdup (component_id), g_strdup (version)); } void fu_plugin_set_compile_versions (FuPlugin *self, GHashTable *compile_versions) { FuPluginPrivate *priv = GET_PRIVATE (self); priv->compile_versions = g_hash_table_ref (compile_versions); } /** * fu_plugin_add_compile_version: * @self: A #FuPlugin * @component_id: An AppStream component id, e.g. "org.gnome.Software" * @version: A version string, e.g. "1.2.3" * * Sets a compile-time version of a specific dependency. * * Since: 1.0.7 **/ void fu_plugin_add_compile_version (FuPlugin *self, const gchar *component_id, const gchar *version) { FuPluginPrivate *priv = GET_PRIVATE (self); if (priv->compile_versions == NULL) return; g_hash_table_insert (priv->compile_versions, g_strdup (component_id), g_strdup (version)); } /** * fu_plugin_lookup_quirk_by_id: * @self: A #FuPlugin * @group: A string, e.g. "DfuFlags" * @key: An ID to match the entry, e.g. "Summary" * * Looks up an entry in the hardware database using a string value. * * Returns: (transfer none): values from the database, or %NULL if not found * * Since: 1.0.1 **/ const gchar * fu_plugin_lookup_quirk_by_id (FuPlugin *self, const gchar *group, const gchar *key) { FuPluginPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_PLUGIN (self), NULL); /* exact ID */ return fu_quirks_lookup_by_id (priv->quirks, group, key); } /** * fu_plugin_lookup_quirk_by_id_as_uint64: * @self: A #FuPlugin * @group: A string, e.g. "DfuFlags" * @key: An ID to match the entry, e.g. "Size" * * Looks up an entry in the hardware database using a string key, returning * an integer value. Values are assumed base 10, unless prefixed with "0x" * where they are parsed as base 16. * * Returns: (transfer none): value from the database, or 0 if not found * * Since: 1.1.2 **/ guint64 fu_plugin_lookup_quirk_by_id_as_uint64 (FuPlugin *self, const gchar *group, const gchar *key) { return fu_common_strtoull (fu_plugin_lookup_quirk_by_id (self, group, key)); } void fu_plugin_set_smbios (FuPlugin *self, FuSmbios *smbios) { FuPluginPrivate *priv = GET_PRIVATE (self); g_set_object (&priv->smbios, smbios); } /** * fu_plugin_set_coldplug_delay: * @self: A #FuPlugin * @duration: A delay in milliseconds * * Set the minimum time that should be waited inbetween the call to * fu_plugin_coldplug_prepare() and fu_plugin_coldplug(). This is usually going * to be the minimum hardware initialisation time from a datasheet. * * It is better to use this function rather than using a sleep() in the plugin * itself as then only one delay is done in the daemon rather than waiting for * each coldplug prepare in a serial way. * * Additionally, very long delays should be avoided as the daemon will be * blocked from processing requests whilst the coldplug delay is being * performed. * * Since: 0.8.0 **/ void fu_plugin_set_coldplug_delay (FuPlugin *self, guint duration) { g_return_if_fail (FU_IS_PLUGIN (self)); g_return_if_fail (duration > 0); /* check sanity */ if (duration > FU_PLUGIN_COLDPLUG_DELAY_MAXIMUM) { g_warning ("duration of %ums is crazy, truncating to %ums", duration, FU_PLUGIN_COLDPLUG_DELAY_MAXIMUM); duration = FU_PLUGIN_COLDPLUG_DELAY_MAXIMUM; } /* emit */ g_signal_emit (self, signals[SIGNAL_SET_COLDPLUG_DELAY], 0, duration); } gboolean fu_plugin_runner_startup (FuPlugin *self, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginStartupFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, "fu_plugin_startup", (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing startup() on %s", priv->name); if (!func (self, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for startup()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to startup using %s: ", priv->name); return FALSE; } return TRUE; } static gboolean fu_plugin_runner_offline_invalidate (GError **error) { g_autoptr(GError) error_local = NULL; g_autoptr(GFile) file1 = NULL; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); file1 = g_file_new_for_path (FU_OFFLINE_TRIGGER_FILENAME); if (!g_file_query_exists (file1, NULL)) return TRUE; if (!g_file_delete (file1, NULL, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot delete %s: %s", FU_OFFLINE_TRIGGER_FILENAME, error_local->message); return FALSE; } return TRUE; } static gboolean fu_plugin_runner_offline_setup (GError **error) { gint rc; g_autofree gchar *filename = NULL; g_autofree gchar *symlink_target = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* does already exist */ filename = fu_common_realpath (FU_OFFLINE_TRIGGER_FILENAME, NULL); if (g_strcmp0 (filename, symlink_target) == 0) { g_debug ("%s already points to %s, skipping creation", FU_OFFLINE_TRIGGER_FILENAME, symlink_target); return TRUE; } /* create symlink for the systemd-system-update-generator */ rc = symlink (symlink_target, FU_OFFLINE_TRIGGER_FILENAME); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to create symlink %s to %s: %s", FU_OFFLINE_TRIGGER_FILENAME, "/var/lib", strerror (errno)); return FALSE; } return TRUE; } static gboolean fu_plugin_runner_device_generic (FuPlugin *self, FuDevice *device, const gchar *symbol_name, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginDeviceFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, symbol_name, (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing %s() on %s", symbol_name + 10, priv->name); if (!func (self, device, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for %s()", priv->name, symbol_name + 10); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to %s using %s: ", symbol_name + 10, priv->name); return FALSE; } return TRUE; } static gboolean fu_plugin_runner_flagged_device_generic (FuPlugin *self, FwupdInstallFlags flags, FuDevice *device, const gchar *symbol_name, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginFlaggedDeviceFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, symbol_name, (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing %s() on %s", symbol_name + 10, priv->name); if (!func (self, flags, device, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for %s()", priv->name, symbol_name + 10); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to %s using %s: ", symbol_name + 10, priv->name); return FALSE; } return TRUE; } static gboolean fu_plugin_runner_device_array_generic (FuPlugin *self, GPtrArray *devices, const gchar *symbol_name, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginDeviceArrayFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, symbol_name, (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing %s() on %s", symbol_name + 10, priv->name); if (!func (self, devices, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for %s()", priv->name, symbol_name + 10); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to %s using %s: ", symbol_name + 10, priv->name); return FALSE; } return TRUE; } gboolean fu_plugin_runner_coldplug (FuPlugin *self, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginStartupFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, "fu_plugin_coldplug", (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing coldplug() on %s", priv->name); if (!func (self, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for coldplug()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to coldplug using %s: ", priv->name); return FALSE; } return TRUE; } gboolean fu_plugin_runner_recoldplug (FuPlugin *self, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginStartupFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, "fu_plugin_recoldplug", (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing recoldplug() on %s", priv->name); if (!func (self, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for recoldplug()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to recoldplug using %s: ", priv->name); return FALSE; } return TRUE; } gboolean fu_plugin_runner_coldplug_prepare (FuPlugin *self, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginStartupFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, "fu_plugin_coldplug_prepare", (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing coldplug_prepare() on %s", priv->name); if (!func (self, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for coldplug_prepare()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to coldplug_prepare using %s: ", priv->name); return FALSE; } return TRUE; } gboolean fu_plugin_runner_coldplug_cleanup (FuPlugin *self, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginStartupFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, "fu_plugin_coldplug_cleanup", (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing coldplug_cleanup() on %s", priv->name); if (!func (self, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for coldplug_cleanup()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to coldplug_cleanup using %s: ", priv->name); return FALSE; } return TRUE; } gboolean fu_plugin_runner_composite_prepare (FuPlugin *self, GPtrArray *devices, GError **error) { return fu_plugin_runner_device_array_generic (self, devices, "fu_plugin_composite_prepare", error); } gboolean fu_plugin_runner_composite_cleanup (FuPlugin *self, GPtrArray *devices, GError **error) { return fu_plugin_runner_device_array_generic (self, devices, "fu_plugin_composite_cleanup", error); } gboolean fu_plugin_runner_update_prepare (FuPlugin *self, FwupdInstallFlags flags, FuDevice *device, GError **error) { return fu_plugin_runner_flagged_device_generic (self, flags, device, "fu_plugin_update_prepare", error); } gboolean fu_plugin_runner_update_cleanup (FuPlugin *self, FwupdInstallFlags flags, FuDevice *device, GError **error) { return fu_plugin_runner_flagged_device_generic (self, flags, device, "fu_plugin_update_cleanup", error); } gboolean fu_plugin_runner_update_attach (FuPlugin *self, FuDevice *device, GError **error) { return fu_plugin_runner_device_generic (self, device, "fu_plugin_update_attach", error); } gboolean fu_plugin_runner_update_detach (FuPlugin *self, FuDevice *device, GError **error) { return fu_plugin_runner_device_generic (self, device, "fu_plugin_update_detach", error); } gboolean fu_plugin_runner_update_reload (FuPlugin *self, FuDevice *device, GError **error) { return fu_plugin_runner_device_generic (self, device, "fu_plugin_update_reload", error); } /** * fu_plugin_add_udev_subsystem: * @self: a #FuPlugin * @subsystem: a subsystem name, e.g. `pciport` * * Registers the udev subsystem to be watched by the daemon. * * Plugins can use this method only in fu_plugin_init() **/ void fu_plugin_add_udev_subsystem (FuPlugin *self, const gchar *subsystem) { FuPluginPrivate *priv = GET_PRIVATE (self); for (guint i = 0; i < priv->udev_subsystems->len; i++) { const gchar *subsystem_tmp = g_ptr_array_index (priv->udev_subsystems, i); if (g_strcmp0 (subsystem_tmp, subsystem) == 0) return; } g_debug ("added udev subsystem watch of %s", subsystem); g_ptr_array_add (priv->udev_subsystems, g_strdup (subsystem)); } gboolean fu_plugin_runner_usb_device_added (FuPlugin *self, FuUsbDevice *device, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginUsbDeviceAddedFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, "fu_plugin_usb_device_added", (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing usb_device_added() on %s", priv->name); if (!func (self, device, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for usb_device_added()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to add device using on %s: ", priv->name); return FALSE; } return TRUE; } gboolean fu_plugin_runner_udev_device_added (FuPlugin *self, FuUdevDevice *device, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginUdevDeviceAddedFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, "fu_plugin_udev_device_added", (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing udev_device_added() on %s", priv->name); if (!func (self, device, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for udev_device_added()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to add device using on %s: ", priv->name); return FALSE; } return TRUE; } void fu_plugin_runner_device_removed (FuPlugin *self, FuDevice *device) { g_autoptr(GError) error_local= NULL; if (!fu_plugin_runner_device_generic (self, device, "fu_plugin_device_removed", &error_local)) g_warning ("%s", error_local->message); } void fu_plugin_runner_device_register (FuPlugin *self, FuDevice *device) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginDeviceRegisterFunc func = NULL; /* not enabled */ if (!priv->enabled) return; if (priv->module == NULL) return; /* don't notify plugins on their own devices */ if (g_strcmp0 (fu_device_get_plugin (device), fu_plugin_get_name (self)) == 0) return; /* optional */ g_module_symbol (priv->module, "fu_plugin_device_registered", (gpointer *) &func); if (func != NULL) { g_debug ("performing fu_plugin_device_registered() on %s", priv->name); func (self, device); } } gboolean fu_plugin_runner_schedule_update (FuPlugin *self, FuDevice *device, FwupdRelease *release, GBytes *blob_cab, FwupdInstallFlags flags, GError **error) { gchar tmpname[] = {"XXXXXX.cab"}; g_autofree gchar *dirname = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuHistory) history = NULL; g_autoptr(GFile) file = NULL; /* id already exists */ history = fu_history_new (); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_autoptr(FuDevice) res_tmp = NULL; res_tmp = fu_history_get_device_by_id (history, fu_device_get_id (device), NULL); if (res_tmp != NULL && fu_device_get_update_state (res_tmp) == FWUPD_UPDATE_STATE_PENDING) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_ALREADY_PENDING, "%s is already scheduled to be updated", fu_device_get_id (device)); return FALSE; } } /* create directory */ dirname = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); file = g_file_new_for_path (dirname); if (!g_file_query_exists (file, NULL)) { if (!g_file_make_directory_with_parents (file, NULL, error)) return FALSE; } /* get a random filename */ for (guint i = 0; i < 6; i++) tmpname[i] = (gchar) g_random_int_range ('A', 'Z'); filename = g_build_filename (dirname, tmpname, NULL); /* just copy to the temp file */ fu_device_set_status (device, FWUPD_STATUS_SCHEDULING); if (!g_file_set_contents (filename, g_bytes_get_data (blob_cab, NULL), (gssize) g_bytes_get_size (blob_cab), error)) return FALSE; /* schedule for next boot */ g_debug ("schedule %s to be installed to %s on next boot", filename, fu_device_get_id (device)); fwupd_release_set_filename (release, filename); /* add to database */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_set_update_state (device, FWUPD_UPDATE_STATE_PENDING); if (!fu_history_add_device (history, device, release, error)) return FALSE; /* next boot we run offline */ fu_device_set_progress (device, 100); return fu_plugin_runner_offline_setup (error); } gboolean fu_plugin_runner_verify (FuPlugin *self, FuDevice *device, FuPluginVerifyFlags flags, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginVerifyFunc func = NULL; GPtrArray *checksums; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, "fu_plugin_verify", (gpointer *) &func); if (func == NULL) return TRUE; /* clear any existing verification checksums */ checksums = fu_device_get_checksums (device); g_ptr_array_set_size (checksums, 0); /* run additional detach */ if (!fu_plugin_runner_device_generic (self, device, "fu_plugin_verify_detach", error)) return FALSE; /* run vfunc */ g_debug ("performing verify() on %s", priv->name); if (!func (self, device, flags, &error_local)) { g_autoptr(GError) error_attach = NULL; if (error_local == NULL) { g_critical ("unset error in plugin %s for verify()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to verify using %s: ", priv->name); /* make the device "work" again, but don't prefix the error */ if (!fu_plugin_runner_device_generic (self, device, "fu_plugin_verify_attach", &error_attach)) { g_warning ("failed to attach whilst aborting verify(): %s", error_attach->message); } return FALSE; } /* run optional attach */ if (!fu_plugin_runner_device_generic (self, device, "fu_plugin_verify_attach", error)) return FALSE; /* success */ return TRUE; } gboolean fu_plugin_runner_activate (FuPlugin *self, FuDevice *device, GError **error) { guint64 flags; /* final check */ flags = fu_device_get_flags (device); if ((flags & FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s does not need activation", fu_device_get_id (device)); return FALSE; } /* run vfunc */ if (!fu_plugin_runner_device_generic (self, device, "fu_plugin_activate", error)) return FALSE; /* update with correct flags */ fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); fu_device_set_modified (device, (guint64) g_get_real_time () / G_USEC_PER_SEC); return TRUE; } gboolean fu_plugin_runner_unlock (FuPlugin *self, FuDevice *device, GError **error) { guint64 flags; /* final check */ flags = fu_device_get_flags (device); if ((flags & FWUPD_DEVICE_FLAG_LOCKED) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s is not locked", fu_device_get_id (device)); return FALSE; } /* run vfunc */ if (!fu_plugin_runner_device_generic (self, device, "fu_plugin_unlock", error)) return FALSE; /* update with correct flags */ flags = fu_device_get_flags (device); fu_device_set_flags (device, flags &= ~FWUPD_DEVICE_FLAG_LOCKED); fu_device_set_modified (device, (guint64) g_get_real_time () / G_USEC_PER_SEC); return TRUE; } gboolean fu_plugin_runner_update (FuPlugin *self, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginUpdateFunc update_func; g_autoptr(FuHistory) history = NULL; g_autoptr(FuDevice) device_pending = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) { g_debug ("plugin not enabled, skipping"); return TRUE; } /* no object loaded */ if (priv->module == NULL) { g_debug ("module not enabled, skipping"); return TRUE; } /* optional */ g_module_symbol (priv->module, "fu_plugin_update", (gpointer *) &update_func); if (update_func == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No update possible"); return FALSE; } /* cancel the pending action */ if (!fu_plugin_runner_offline_invalidate (error)) return FALSE; /* online */ history = fu_history_new (); device_pending = fu_history_get_device_by_id (history, fu_device_get_id (device), NULL); if (!update_func (self, device, blob_fw, flags, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for update()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); return FALSE; } fu_device_set_update_error (device, error_local->message); g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } /* no longer valid */ if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT) && !fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) { GPtrArray *checksums = fu_device_get_checksums (device); g_ptr_array_set_size (checksums, 0); } /* cleanup */ if (device_pending != NULL) { const gchar *tmp; FwupdRelease *release; /* update history database */ fu_device_set_update_state (device, FWUPD_UPDATE_STATE_SUCCESS); if (!fu_history_modify_device (history, device, FU_HISTORY_FLAGS_MATCH_NEW_VERSION, error)) return FALSE; /* delete cab file */ release = fu_device_get_release_default (device_pending); tmp = fwupd_release_get_filename (release); if (tmp != NULL && g_str_has_prefix (tmp, LIBEXECDIR)) { g_autoptr(GError) error_delete = NULL; g_autoptr(GFile) file = NULL; file = g_file_new_for_path (tmp); if (!g_file_delete (file, NULL, &error_delete)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to delete %s: %s", tmp, error_delete->message); return FALSE; } } } return TRUE; } gboolean fu_plugin_runner_clear_results (FuPlugin *self, FuDevice *device, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginDeviceFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, "fu_plugin_get_results", (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing clear_result() on %s", priv->name); if (!func (self, device, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for clear_result()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to clear_result using %s: ", priv->name); return FALSE; } return TRUE; } gboolean fu_plugin_runner_get_results (FuPlugin *self, FuDevice *device, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginDeviceFunc func = NULL; g_autoptr(GError) error_local = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ if (priv->module == NULL) return TRUE; /* optional */ g_module_symbol (priv->module, "fu_plugin_get_results", (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing get_results() on %s", priv->name); if (!func (self, device, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for get_results()", priv->name); g_set_error_literal (&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to get_results using %s: ", priv->name); return FALSE; } return TRUE; } /** * fu_plugin_get_order: * @self: a #FuPlugin * * Gets the plugin order, where higher numbers are run after lower * numbers. * * Returns: the integer value **/ guint fu_plugin_get_order (FuPlugin *self) { FuPluginPrivate *priv = fu_plugin_get_instance_private (self); return priv->order; } /** * fu_plugin_set_order: * @self: a #FuPlugin * @order: a integer value * * Sets the plugin order, where higher numbers are run after lower * numbers. **/ void fu_plugin_set_order (FuPlugin *self, guint order) { FuPluginPrivate *priv = fu_plugin_get_instance_private (self); priv->order = order; } /** * fu_plugin_get_priority: * @self: a #FuPlugin * * Gets the plugin priority, where higher numbers are better. * * Returns: the integer value **/ guint fu_plugin_get_priority (FuPlugin *self) { FuPluginPrivate *priv = fu_plugin_get_instance_private (self); return priv->priority; } /** * fu_plugin_set_priority: * @self: a #FuPlugin * @priority: a integer value * * Sets the plugin priority, where higher numbers are better. **/ void fu_plugin_set_priority (FuPlugin *self, guint priority) { FuPluginPrivate *priv = fu_plugin_get_instance_private (self); priv->priority = priority; } /** * fu_plugin_add_rule: * @self: a #FuPlugin * @rule: a #FuPluginRule, e.g. %FU_PLUGIN_RULE_CONFLICTS * @name: a plugin name, e.g. `upower` * * If the plugin name is found, the rule will be used to sort the plugin list, * for example the plugin specified by @name will be ordered after this plugin * when %FU_PLUGIN_RULE_RUN_AFTER is used. * * NOTE: The depsolver is iterative and may not solve overly-complicated rules; * If depsolving fails then fwupd will not start. **/ void fu_plugin_add_rule (FuPlugin *self, FuPluginRule rule, const gchar *name) { FuPluginPrivate *priv = fu_plugin_get_instance_private (self); g_ptr_array_add (priv->rules[rule], g_strdup (name)); g_signal_emit (self, signals[SIGNAL_RULES_CHANGED], 0); } /** * fu_plugin_get_rules: * @self: a #FuPlugin * @rule: a #FuPluginRule, e.g. %FU_PLUGIN_RULE_CONFLICTS * * Gets the plugin IDs that should be run after this plugin. * * Returns: (element-type utf8) (transfer none): the list of plugin names, e.g. ['appstream'] **/ GPtrArray * fu_plugin_get_rules (FuPlugin *self, FuPluginRule rule) { FuPluginPrivate *priv = fu_plugin_get_instance_private (self); return priv->rules[rule]; } /** * fu_plugin_has_rule: * @self: a #FuPlugin * @rule: a #FuPluginRule, e.g. %FU_PLUGIN_RULE_CONFLICTS * @name: a plugin name, e.g. `upower` * * Gets the plugin IDs that should be run after this plugin. * * Returns: %TRUE if the name exists for the specific rule **/ gboolean fu_plugin_has_rule (FuPlugin *self, FuPluginRule rule, const gchar *name) { FuPluginPrivate *priv = fu_plugin_get_instance_private (self); for (guint i = 0; i < priv->rules[rule]->len; i++) { const gchar *tmp = g_ptr_array_index (priv->rules[rule], i); if (g_strcmp0 (tmp, name) == 0) return TRUE; } return FALSE; } /** * fu_plugin_add_report_metadata: * @self: a #FuPlugin * @key: a string, e.g. `FwupdateVersion` * @value: a string, e.g. `10` * * Sets any additional metadata to be included in the firmware report to aid * debugging problems. * * Any data included here will be sent to the metadata server after user * confirmation. **/ void fu_plugin_add_report_metadata (FuPlugin *self, const gchar *key, const gchar *value) { FuPluginPrivate *priv = fu_plugin_get_instance_private (self); g_hash_table_insert (priv->report_metadata, g_strdup (key), g_strdup (value)); } /** * fu_plugin_get_report_metadata: * @self: a #FuPlugin * * Returns the list of additional metadata to be added when filing a report. * * Returns: (transfer none): the map of report metadata **/ GHashTable * fu_plugin_get_report_metadata (FuPlugin *self) { FuPluginPrivate *priv = fu_plugin_get_instance_private (self); return priv->report_metadata; } /** * fu_plugin_get_config_value: * @self: a #FuPlugin * @key: A settings key * * Return the value of a key if it's been configured * * Since: 1.0.6 **/ gchar * fu_plugin_get_config_value (FuPlugin *self, const gchar *key) { g_autofree gchar *conf_dir = NULL; g_autofree gchar *conf_file = NULL; g_autofree gchar *conf_path = NULL; g_autoptr(GKeyFile) keyfile = NULL; const gchar *plugin_name; conf_dir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR_PKG); plugin_name = fu_plugin_get_name (self); conf_file = g_strdup_printf ("%s.conf", plugin_name); conf_path = g_build_filename (conf_dir, conf_file, NULL); if (!g_file_test (conf_path, G_FILE_TEST_IS_REGULAR)) return NULL; keyfile = g_key_file_new (); if (!g_key_file_load_from_file (keyfile, conf_path, G_KEY_FILE_NONE, NULL)) return NULL; return g_key_file_get_string (keyfile, plugin_name, key, NULL); } /** * fu_plugin_name_compare: * @plugin1: first #FuPlugin to compare. * @plugin2: second #FuPlugin to compare. * * Compares two plugins by their names. * * Returns: 1, 0 or -1 if @plugin1 is greater, equal, or less than @plugin2. **/ gint fu_plugin_name_compare (FuPlugin *plugin1, FuPlugin *plugin2) { FuPluginPrivate *priv1 = fu_plugin_get_instance_private (plugin1); FuPluginPrivate *priv2 = fu_plugin_get_instance_private (plugin2); return g_strcmp0 (priv1->name, priv2->name); } /** * fu_plugin_order_compare: * @plugin1: first #FuPlugin to compare. * @plugin2: second #FuPlugin to compare. * * Compares two plugins by their depsolved order. * * Returns: 1, 0 or -1 if @plugin1 is greater, equal, or less than @plugin2. **/ gint fu_plugin_order_compare (FuPlugin *plugin1, FuPlugin *plugin2) { FuPluginPrivate *priv1 = fu_plugin_get_instance_private (plugin1); FuPluginPrivate *priv2 = fu_plugin_get_instance_private (plugin2); if (priv1->order < priv2->order) return -1; if (priv1->order > priv2->order) return 1; return 0; } static void fu_plugin_class_init (FuPluginClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_plugin_finalize; signals[SIGNAL_DEVICE_ADDED] = g_signal_new ("device-added", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FuPluginClass, device_added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); signals[SIGNAL_DEVICE_REMOVED] = g_signal_new ("device-removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FuPluginClass, device_removed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); signals[SIGNAL_DEVICE_REGISTER] = g_signal_new ("device-register", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FuPluginClass, device_register), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); signals[SIGNAL_RECOLDPLUG] = g_signal_new ("recoldplug", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FuPluginClass, recoldplug), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[SIGNAL_SET_COLDPLUG_DELAY] = g_signal_new ("set-coldplug-delay", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FuPluginClass, set_coldplug_delay), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); signals[SIGNAL_CHECK_SUPPORTED] = g_signal_new ("check-supported", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FuPluginClass, check_supported), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1, G_TYPE_STRING); signals[SIGNAL_RULES_CHANGED] = g_signal_new ("rules-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FuPluginClass, rules_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void fu_plugin_init (FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE (self); priv->enabled = TRUE; priv->udev_subsystems = g_ptr_array_new_with_free_func (g_free); priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); g_rw_lock_init (&priv->devices_mutex); priv->report_metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); for (guint i = 0; i < FU_PLUGIN_RULE_LAST; i++) priv->rules[i] = g_ptr_array_new_with_free_func (g_free); } static void fu_plugin_finalize (GObject *object) { FuPlugin *self = FU_PLUGIN (object); FuPluginPrivate *priv = GET_PRIVATE (self); FuPluginInitFunc func = NULL; /* optional */ if (priv->module != NULL) { g_module_symbol (priv->module, "fu_plugin_destroy", (gpointer *) &func); if (func != NULL) { g_debug ("performing destroy() on %s", priv->name); func (self); } } for (guint i = 0; i < FU_PLUGIN_RULE_LAST; i++) g_ptr_array_unref (priv->rules[i]); if (priv->usb_ctx != NULL) g_object_unref (priv->usb_ctx); if (priv->hwids != NULL) g_object_unref (priv->hwids); if (priv->quirks != NULL) g_object_unref (priv->quirks); if (priv->udev_subsystems != NULL) g_ptr_array_unref (priv->udev_subsystems); if (priv->smbios != NULL) g_object_unref (priv->smbios); if (priv->runtime_versions != NULL) g_hash_table_unref (priv->runtime_versions); if (priv->compile_versions != NULL) g_hash_table_unref (priv->compile_versions); g_hash_table_unref (priv->devices); g_hash_table_unref (priv->report_metadata); g_rw_lock_clear (&priv->devices_mutex); g_free (priv->build_hash); g_free (priv->name); g_free (priv->data); /* Must happen as the last step to avoid prematurely * freeing memory held by the plugin */ #ifndef RUNNING_ON_VALGRIND if (priv->module != NULL) g_module_close (priv->module); #endif G_OBJECT_CLASS (fu_plugin_parent_class)->finalize (object); } FuPlugin * fu_plugin_new (void) { return FU_PLUGIN (g_object_new (FU_TYPE_PLUGIN, NULL)); } fwupd-1.2.14/src/fu-plugin.h000066400000000000000000000115701402665037500156060ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include #include "fu-common.h" #include "fu-common-guid.h" #include "fu-common-version.h" #include "fu-device.h" #include "fu-device-locker.h" #include "fu-quirks.h" #include "fu-hwids.h" #include "fu-usb-device.h" #include "fu-udev-device.h" #include "fwupd-common.h" G_BEGIN_DECLS #define FU_TYPE_PLUGIN (fu_plugin_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuPlugin, fu_plugin, FU, PLUGIN, GObject) struct _FuPluginClass { GObjectClass parent_class; /* signals */ void (* device_added) (FuPlugin *self, FuDevice *device); void (* device_removed) (FuPlugin *self, FuDevice *device); void (* status_changed) (FuPlugin *self, FwupdStatus status); void (* percentage_changed) (FuPlugin *self, guint percentage); void (* recoldplug) (FuPlugin *self); void (* set_coldplug_delay) (FuPlugin *self, guint duration); void (* device_register) (FuPlugin *self, FuDevice *device); gboolean (* check_supported) (FuPlugin *self, const gchar *guid); void (* rules_changed) (FuPlugin *self); /*< private >*/ gpointer padding[22]; }; /** * FuPluginVerifyFlags: * @FU_PLUGIN_VERIFY_FLAG_NONE: No flags set * * Flags used when verifying, currently unused. **/ typedef enum { FU_PLUGIN_VERIFY_FLAG_NONE = 0, /*< private >*/ FU_PLUGIN_VERIFY_FLAG_LAST } FuPluginVerifyFlags; /** * FuPluginRule: * @FU_PLUGIN_RULE_CONFLICTS: The plugin conflicts with another * @FU_PLUGIN_RULE_RUN_AFTER: Order the plugin after another * @FU_PLUGIN_RULE_RUN_BEFORE: Order the plugin before another * @FU_PLUGIN_RULE_REQUIRES_QUIRK: Requires a specific quirk * @FU_PLUGIN_RULE_BETTER_THAN: Is better than another plugin * @FU_PLUGIN_RULE_INHIBITS_IDLE: The plugin inhibits the idle shutdown * @FU_PLUGIN_RULE_SUPPORTS_PROTOCOL: The plugin supports a well known protocol * * The rules used for ordering plugins. * Plugins are expected to add rules in fu_plugin_initialize(). **/ typedef enum { FU_PLUGIN_RULE_CONFLICTS, FU_PLUGIN_RULE_RUN_AFTER, FU_PLUGIN_RULE_RUN_BEFORE, FU_PLUGIN_RULE_REQUIRES_QUIRK, FU_PLUGIN_RULE_BETTER_THAN, FU_PLUGIN_RULE_INHIBITS_IDLE, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, /*< private >*/ FU_PLUGIN_RULE_LAST } FuPluginRule; typedef struct FuPluginData FuPluginData; /* for plugins to use */ const gchar *fu_plugin_get_name (FuPlugin *self); FuPluginData *fu_plugin_get_data (FuPlugin *self); FuPluginData *fu_plugin_alloc_data (FuPlugin *self, gsize data_sz); gboolean fu_plugin_get_enabled (FuPlugin *self); void fu_plugin_set_enabled (FuPlugin *self, gboolean enabled); void fu_plugin_set_build_hash (FuPlugin *self, const gchar *build_hash); GUsbContext *fu_plugin_get_usb_context (FuPlugin *self); void fu_plugin_device_add (FuPlugin *self, FuDevice *device); void fu_plugin_device_remove (FuPlugin *self, FuDevice *device); void fu_plugin_device_register (FuPlugin *self, FuDevice *device); void fu_plugin_request_recoldplug (FuPlugin *self); void fu_plugin_set_coldplug_delay (FuPlugin *self, guint duration); gpointer fu_plugin_cache_lookup (FuPlugin *self, const gchar *id); void fu_plugin_cache_remove (FuPlugin *self, const gchar *id); void fu_plugin_cache_add (FuPlugin *self, const gchar *id, gpointer dev); gboolean fu_plugin_check_hwid (FuPlugin *self, const gchar *hwid); GPtrArray *fu_plugin_get_hwids (FuPlugin *self); gboolean fu_plugin_check_supported (FuPlugin *self, const gchar *guid); const gchar *fu_plugin_get_dmi_value (FuPlugin *self, const gchar *dmi_id); const gchar *fu_plugin_get_smbios_string (FuPlugin *self, guint8 structure_type, guint8 offset); GBytes *fu_plugin_get_smbios_data (FuPlugin *self, guint8 structure_type); void fu_plugin_add_rule (FuPlugin *self, FuPluginRule rule, const gchar *name); void fu_plugin_add_udev_subsystem (FuPlugin *self, const gchar *subsystem); FuQuirks *fu_plugin_get_quirks (FuPlugin *self); const gchar *fu_plugin_lookup_quirk_by_id (FuPlugin *self, const gchar *group, const gchar *key); guint64 fu_plugin_lookup_quirk_by_id_as_uint64 (FuPlugin *self, const gchar *group, const gchar *key); void fu_plugin_add_report_metadata (FuPlugin *self, const gchar *key, const gchar *value); gchar *fu_plugin_get_config_value (FuPlugin *self, const gchar *key); void fu_plugin_add_runtime_version (FuPlugin *self, const gchar *component_id, const gchar *version); void fu_plugin_add_compile_version (FuPlugin *self, const gchar *component_id, const gchar *version); G_END_DECLS fwupd-1.2.14/src/fu-progressbar.c000066400000000000000000000221011402665037500166240ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuProgressBar" #include "config.h" #include #include #include "fu-progressbar.h" static void fu_progressbar_finalize (GObject *obj); struct _FuProgressbar { GObject parent_instance; FwupdStatus status; gboolean spinner_count_up; /* chars */ guint spinner_idx; /* chars */ guint length_percentage; /* chars */ guint length_status; /* chars */ guint percentage; guint to_erase; /* chars */ guint timer_id; gint64 last_animated; /* monotonic */ GTimer *time_elapsed; gdouble last_estimate; gboolean interactive; }; G_DEFINE_TYPE (FuProgressbar, fu_progressbar, G_TYPE_OBJECT) static const gchar * fu_progressbar_status_to_string (FwupdStatus status) { switch (status) { case FWUPD_STATUS_IDLE: /* TRANSLATORS: daemon is inactive */ return _("Idle…"); break; case FWUPD_STATUS_DECOMPRESSING: /* TRANSLATORS: decompressing the firmware file */ return _("Decompressing…"); break; case FWUPD_STATUS_LOADING: /* TRANSLATORS: parsing the firmware information */ return _("Loading…"); break; case FWUPD_STATUS_DEVICE_RESTART: /* TRANSLATORS: restarting the device to pick up new F/W */ return _("Restarting device…"); break; case FWUPD_STATUS_DEVICE_READ: /* TRANSLATORS: reading from the flash chips */ return _("Reading…"); break; case FWUPD_STATUS_DEVICE_WRITE: /* TRANSLATORS: writing to the flash chips */ return _("Writing…"); break; case FWUPD_STATUS_DEVICE_ERASE: /* TRANSLATORS: erasing contents of the flash chips */ return _("Erasing…"); break; case FWUPD_STATUS_DEVICE_VERIFY: /* TRANSLATORS: verifying we wrote the firmware correctly */ return _("Verifying…"); break; case FWUPD_STATUS_SCHEDULING: /* TRANSLATORS: scheduing an update to be done on the next boot */ return _("Scheduling…"); break; case FWUPD_STATUS_DOWNLOADING: /* TRANSLATORS: downloading from a remote server */ return _("Downloading…"); break; case FWUPD_STATUS_WAITING_FOR_AUTH: /* TRANSLATORS: waiting for user to authenticate */ return _("Authenticating…"); break; case FWUPD_STATUS_DEVICE_BUSY: /* TRANSLATORS: waiting for device to do something */ return _("Waiting…"); break; default: break; } /* TRANSLATORS: currect daemon status is unknown */ return _("Unknown"); } static void fu_progressbar_erase_line (FuProgressbar *self) { if (!self->interactive) return; for (guint i = 0; i < self->to_erase; i++) g_print ("\b"); self->to_erase = 0; } static gboolean fu_progressbar_estimate_ready (FuProgressbar *self, guint percentage) { gdouble old; gdouble elapsed; if (percentage == 0 || percentage == 100) return FALSE; old = self->last_estimate; elapsed = g_timer_elapsed (self->time_elapsed, NULL); self->last_estimate = elapsed / percentage * (100 - percentage); /* estimate is ready if we have decreased */ return old > self->last_estimate; } static gchar * fu_progressbar_time_remaining_str (FuProgressbar *self) { /* less than 5 seconds remaining */ if (self->last_estimate < 5) return NULL; /* less than 60 seconds remaining */ if (self->last_estimate < 60) { /* TRANSLATORS: time remaining for completing firmware flash */ return g_strdup (_("Less than one minute remaining")); } /* more than a minute */ return g_strdup_printf (ngettext ("%.0f minute remaining", "%.0f minutes remaining", self->last_estimate / 60), self->last_estimate / 60); } static void fu_progressbar_refresh (FuProgressbar *self, FwupdStatus status, guint percentage) { const gchar *title; guint i; gboolean is_idle_newline = FALSE; g_autoptr(GString) str = g_string_new (NULL); /* erase previous line */ fu_progressbar_erase_line (self); /* add status */ if (status == FWUPD_STATUS_IDLE) { percentage = 100; status = self->status; is_idle_newline = TRUE; } title = fu_progressbar_status_to_string (status); g_string_append (str, title); for (i = g_utf8_strlen (str->str, -1); i < self->length_status; i++) g_string_append_c (str, ' '); /* add progressbar */ g_string_append (str, "["); if (percentage > 0) { for (i = 0; i < (self->length_percentage - 1) * percentage / 100; i++) g_string_append_c (str, '*'); for (i = i + 1; i < self->length_percentage; i++) g_string_append_c (str, ' '); } else { const gchar chars[] = { '-', '\\', '|', '/', }; for (i = 0; i < self->spinner_idx; i++) g_string_append_c (str, ' '); g_string_append_c (str, chars[i / 4 % G_N_ELEMENTS(chars)]); for (i = i + 1; i < self->length_percentage - 1; i++) g_string_append_c (str, ' '); } g_string_append_c (str, ']'); /* once we have good data show an estimate of time remaining */ if (fu_progressbar_estimate_ready (self, percentage)) { g_autofree gchar *remaining = fu_progressbar_time_remaining_str (self); if (remaining != NULL) g_string_append_printf (str, " %s…", remaining); } /* dump to screen */ g_print ("%s", str->str); self->to_erase = str->len; /* done */ if (is_idle_newline) { g_print ("\n"); self->to_erase = 0; return; } } void fu_progressbar_set_title (FuProgressbar *self, const gchar *title) { fu_progressbar_erase_line (self); g_print ("%s\n", title); fu_progressbar_refresh (self, self->status, self->percentage); } static void fu_progressbar_spin_inc (FuProgressbar *self) { /* reset */ self->last_animated = g_get_monotonic_time (); /* up to down */ if (self->spinner_count_up) { if (++self->spinner_idx > self->length_percentage - 3) self->spinner_count_up = FALSE; } else { if (--self->spinner_idx == 0) self->spinner_count_up = TRUE; } } static gboolean fu_progressbar_spin_cb (gpointer user_data) { FuProgressbar *self = FU_PROGRESSBAR (user_data); /* ignore */ if (self->status == FWUPD_STATUS_IDLE) return G_SOURCE_CONTINUE; /* move the spinner index up to down */ fu_progressbar_spin_inc (self); /* update the terminal */ fu_progressbar_refresh (self, self->status, self->percentage); return G_SOURCE_CONTINUE; } static void fu_progressbar_spin_end (FuProgressbar *self) { if (self->timer_id != 0) { g_source_remove (self->timer_id); self->timer_id = 0; /* reset when the spinner has been stopped */ g_timer_start (self->time_elapsed); } /* go back to the start when we next go into unknown percentage mode */ self->spinner_idx = 0; self->spinner_count_up = TRUE; } static void fu_progressbar_spin_start (FuProgressbar *self) { if (self->timer_id != 0) g_source_remove (self->timer_id); self->timer_id = g_timeout_add (40, fu_progressbar_spin_cb, self); } void fu_progressbar_update (FuProgressbar *self, FwupdStatus status, guint percentage) { g_return_if_fail (FU_IS_PROGRESSBAR (self)); /* use cached value */ if (status == FWUPD_STATUS_UNKNOWN) status = self->status; if (!self->interactive) { if (self->status != status) { g_debug ("%s\n", fu_progressbar_status_to_string (status)); self->status = status; } return; } /* if the main loop isn't spinning and we've not had a chance to * execute the callback just do the refresh now manually */ if (percentage == 0 && status != FWUPD_STATUS_IDLE && self->status != FWUPD_STATUS_UNKNOWN) { if ((g_get_monotonic_time () - self->last_animated) / 1000 > 40) { fu_progressbar_spin_inc (self); fu_progressbar_refresh (self, status, percentage); } } /* ignore duplicates */ if (self->status == status && self->percentage == percentage) return; /* enable or disable the spinner timeout */ if (percentage > 0) { fu_progressbar_spin_end (self); } else { fu_progressbar_spin_start (self); } /* update the terminal */ fu_progressbar_refresh (self, status, percentage); /* cache */ self->status = status; self->percentage = percentage; } void fu_progressbar_set_interactive (FuProgressbar *self, gboolean interactive) { g_return_if_fail (FU_IS_PROGRESSBAR (self)); self->interactive = interactive; } void fu_progressbar_set_length_status (FuProgressbar *self, guint len) { g_return_if_fail (FU_IS_PROGRESSBAR (self)); g_return_if_fail (len > 3); self->length_status = len; } void fu_progressbar_set_length_percentage (FuProgressbar *self, guint len) { g_return_if_fail (FU_IS_PROGRESSBAR (self)); g_return_if_fail (len > 3); self->length_percentage = len; } static void fu_progressbar_class_init (FuProgressbarClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_progressbar_finalize; } static void fu_progressbar_init (FuProgressbar *self) { self->length_percentage = 40; self->length_status = 25; self->spinner_count_up = TRUE; self->time_elapsed = g_timer_new (); self->interactive = TRUE; } static void fu_progressbar_finalize (GObject *obj) { FuProgressbar *self = FU_PROGRESSBAR (obj); if (self->timer_id != 0) g_source_remove (self->timer_id); g_timer_destroy (self->time_elapsed); G_OBJECT_CLASS (fu_progressbar_parent_class)->finalize (obj); } FuProgressbar * fu_progressbar_new (void) { FuProgressbar *self; self = g_object_new (FU_TYPE_PROGRESSBAR, NULL); return FU_PROGRESSBAR (self); } fwupd-1.2.14/src/fu-progressbar.h000066400000000000000000000015061402665037500166370ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-enums.h" G_BEGIN_DECLS #define FU_TYPE_PROGRESSBAR (fu_progressbar_get_type ()) G_DECLARE_FINAL_TYPE (FuProgressbar, fu_progressbar, FU, PROGRESSBAR, GObject) FuProgressbar *fu_progressbar_new (void); void fu_progressbar_update (FuProgressbar *self, FwupdStatus status, guint percentage); void fu_progressbar_set_length_status (FuProgressbar *self, guint len); void fu_progressbar_set_length_percentage (FuProgressbar *self, guint len); void fu_progressbar_set_title (FuProgressbar *self, const gchar *title); void fu_progressbar_set_interactive (FuProgressbar *self, gboolean interactive); G_END_DECLS fwupd-1.2.14/src/fu-quirks.c000066400000000000000000000261051402665037500156210ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuQuirks" #include "config.h" #include #include #include #include "fu-common.h" #include "fu-mutex.h" #include "fu-quirks.h" #include "fwupd-common.h" #include "fwupd-error.h" #include "fwupd-remote-private.h" /** * SECTION:fu-quirks * @short_description: device quirks * * Quirks can be used to modify device behaviour. * When fwupd is installed in long-term support distros it's very hard to * backport new versions as new hardware is released. * * There are several reasons why we can't just include the mapping and quirk * information in the AppStream metadata: * * * The extra data is hugely specific to the installed fwupd plugin versions * * The device-id is per-device, and the mapping is usually per-plugin * * Often the information is needed before the FuDevice is created * * There are security implications in allowing plugins to handle new devices * * The idea with quirks is that the end user can drop an additional (or replace * an existing) file in a .d director with a simple format and the hardware will * magically start working. This assumes no new quirks are required, as this would * obviously need code changes, but allows us to get most existing devices working * in an easy way without the user compiling anything. * * See also: #FuDevice, #FuPlugin */ static void fu_quirks_finalize (GObject *obj); struct _FuQuirks { GObject parent_instance; GPtrArray *monitors; GHashTable *hash; /* of group:{key:value} */ GRWLock hash_mutex; }; G_DEFINE_TYPE (FuQuirks, fu_quirks, G_TYPE_OBJECT) static void fu_quirks_monitor_changed_cb (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuQuirks *self = FU_QUIRKS (user_data); g_autoptr(GError) error = NULL; g_autofree gchar *filename = g_file_get_path (file); g_debug ("%s changed, reloading all configs", filename); if (!fu_quirks_load (self, &error)) g_warning ("failed to rescan quirks: %s", error->message); } static gboolean fu_quirks_add_inotify (FuQuirks *self, const gchar *filename, GError **error) { GFileMonitor *monitor; g_autoptr(GFile) file = g_file_new_for_path (filename); /* set up a notify watch */ monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, error); if (monitor == NULL) return FALSE; g_signal_connect (monitor, "changed", G_CALLBACK (fu_quirks_monitor_changed_cb), self); g_ptr_array_add (self->monitors, monitor); return TRUE; } static gchar * fu_quirks_build_group_key (const gchar *group) { const gchar *guid_prefixes[] = { "DeviceInstanceId=", "Guid=", "HwId=", NULL }; /* this is a GUID */ for (guint i = 0; guid_prefixes[i] != NULL; i++) { if (g_str_has_prefix (group, guid_prefixes[i])) { gsize len = strlen (guid_prefixes[i]); if (fwupd_guid_is_valid (group + len)) return g_strdup (group + len); return fwupd_guid_hash_string (group + len); } } /* fallback */ return g_strdup (group); } /** * fu_quirks_lookup_by_id: * @self: A #FuPlugin * @group: A string group, e.g. "DeviceInstanceId=USB\VID_1235&PID_AB11" * @key: An ID to match the entry, e.g. "Name" * * Looks up an entry in the hardware database using a string value. * * Returns: (transfer none): values from the database, or %NULL if not found * * Since: 1.0.1 **/ const gchar * fu_quirks_lookup_by_id (FuQuirks *self, const gchar *group, const gchar *key) { GHashTable *kvs; g_autofree gchar *group_key = NULL; g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&self->hash_mutex); g_return_val_if_fail (FU_IS_QUIRKS (self), NULL); g_return_val_if_fail (group != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); g_return_val_if_fail (locker != NULL, NULL); group_key = fu_quirks_build_group_key (group); kvs = g_hash_table_lookup (self->hash, group_key); if (kvs == NULL) return NULL; return g_hash_table_lookup (kvs, key); } /** * fu_quirks_get_kvs_for_guid: * @self: A #FuPlugin * @guid: a GUID * @iter: A #GHashTableIter, typically allocated on the stack by the caller * * Looks up all entries in the hardware database using a GUID value. * * Returns: %TRUE if the GUID was found, and @iter was set * * Since: 1.1.2 **/ gboolean fu_quirks_get_kvs_for_guid (FuQuirks *self, const gchar *guid, GHashTableIter *iter) { GHashTable *kvs; g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&self->hash_mutex); g_return_val_if_fail (locker != NULL, FALSE); kvs = g_hash_table_lookup (self->hash, guid); if (kvs == NULL) return FALSE; g_hash_table_iter_init (iter, kvs); return TRUE; } static gchar * fu_quirks_merge_values (const gchar *old, const gchar *new) { guint cnt = 0; g_autofree gchar **resv = NULL; g_auto(GStrv) newv = g_strsplit (new, ",", -1); g_auto(GStrv) oldv = g_strsplit (old, ",", -1); /* segment flags, and append if they do not already exists */ resv = g_new0 (gchar *, g_strv_length (oldv) + g_strv_length (newv) + 1); for (guint i = 0; oldv[i] != NULL; i++) { if (!g_strv_contains ((const gchar * const *) resv, oldv[i])) resv[cnt++] = oldv[i]; } for (guint i = 0; newv[i] != NULL; i++) { if (!g_strv_contains ((const gchar * const *) resv, newv[i])) resv[cnt++] = newv[i]; } return g_strjoinv (",", resv); } /** * fu_quirks_add_value: (skip) * @self: A #FuQuirks * @group: group, e.g. `DeviceInstanceId=USB\VID_0BDA&PID_1100` * @key: group, e.g. `Name` * @value: group, e.g. `Unknown Device` * * Adds a value to the quirk database. Normally this is achieved by loading a * quirk file using fu_quirks_load(). * * Since: 1.1.2 **/ void fu_quirks_add_value (FuQuirks *self, const gchar *group, const gchar *key, const gchar *value) { GHashTable *kvs; const gchar *value_old; g_autofree gchar *group_key = NULL; g_autofree gchar *value_new = NULL; g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_writer_locker_new (&self->hash_mutex); g_return_if_fail (locker != NULL); /* does the key already exists in our hash */ group_key = fu_quirks_build_group_key (group); kvs = g_hash_table_lookup (self->hash, group_key); if (kvs == NULL) { kvs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert (self->hash, g_steal_pointer (&group_key), kvs); value_new = g_strdup (value); } else { /* look up in the 2nd level hash */ value_old = g_hash_table_lookup (kvs, key); if (value_old != NULL) { g_debug ("already found %s=%s, merging with %s", group_key, value_old, value); value_new = fu_quirks_merge_values (value_old, value); } else { value_new = g_strdup (value); } } /* insert the new value */ g_hash_table_insert (kvs, g_strdup (key), g_steal_pointer (&value_new)); } static gboolean fu_quirks_add_quirks_from_filename (FuQuirks *self, const gchar *filename, GError **error) { g_autoptr(GKeyFile) kf = g_key_file_new (); g_auto(GStrv) groups = NULL; /* load keyfile */ if (!g_key_file_load_from_file (kf, filename, G_KEY_FILE_NONE, error)) return FALSE; /* add each set of groups and keys */ groups = g_key_file_get_groups (kf, NULL); for (guint i = 0; groups[i] != NULL; i++) { g_auto(GStrv) keys = NULL; keys = g_key_file_get_keys (kf, groups[i], NULL, error); if (keys == NULL) return FALSE; for (guint j = 0; keys[j] != NULL; j++) { g_autofree gchar *value = NULL; /* get value from keyfile */ value = g_key_file_get_value (kf, groups[i], keys[j], error); if (value == NULL) return FALSE; fu_quirks_add_value (self, groups[i], keys[j], value); } } return TRUE; } static gint fu_quirks_filename_sort_cb (gconstpointer a, gconstpointer b) { const gchar *stra = *((const gchar **) a); const gchar *strb = *((const gchar **) b); return g_strcmp0 (stra, strb); } static gboolean fu_quirks_add_quirks_for_path (FuQuirks *self, const gchar *path, GError **error) { const gchar *tmp; g_autofree gchar *path_hw = NULL; g_autoptr(GDir) dir = NULL; g_autoptr(GPtrArray) filenames = g_ptr_array_new_with_free_func (g_free); /* add valid files to the array */ path_hw = g_build_filename (path, "quirks.d", NULL); if (!g_file_test (path_hw, G_FILE_TEST_EXISTS)) { g_debug ("no %s, skipping", path_hw); return TRUE; } dir = g_dir_open (path_hw, 0, error); if (dir == NULL) return FALSE; while ((tmp = g_dir_read_name (dir)) != NULL) { if (!g_str_has_suffix (tmp, ".quirk")) { g_debug ("skipping invalid file %s", tmp); continue; } g_ptr_array_add (filenames, g_build_filename (path_hw, tmp, NULL)); } /* sort */ g_ptr_array_sort (filenames, fu_quirks_filename_sort_cb); /* process files */ for (guint i = 0; i < filenames->len; i++) { const gchar *filename = g_ptr_array_index (filenames, i); /* load from keyfile */ g_debug ("loading quirks from %s", filename); if (!fu_quirks_add_quirks_from_filename (self, filename, error)) { g_prefix_error (error, "failed to load %s: ", filename); return FALSE; } /* watch the file for changes */ if (!fu_quirks_add_inotify (self, filename, error)) return FALSE; } /* success */ g_debug ("now %u quirk entries", g_hash_table_size (self->hash)); return TRUE; } /** * fu_quirks_load: (skip) * @self: A #FuQuirks * @error: A #GError, or %NULL * * Loads the various files that define the hardware quirks used in plugins. * * Returns: %TRUE for success * * Since: 1.0.1 **/ gboolean fu_quirks_load (FuQuirks *self, GError **error) { g_autofree gchar *datadir = NULL; g_autofree gchar *localstatedir = NULL; g_return_val_if_fail (FU_IS_QUIRKS (self), FALSE); /* ensure empty in case we're called from a monitor change */ g_ptr_array_set_size (self->monitors, 0); g_rw_lock_writer_lock (&self->hash_mutex); g_hash_table_remove_all (self->hash); g_rw_lock_writer_unlock (&self->hash_mutex); /* system datadir */ datadir = fu_common_get_path (FU_PATH_KIND_DATADIR_PKG); if (!fu_quirks_add_quirks_for_path (self, datadir, error)) return FALSE; /* something we can write when using Ostree */ localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); if (!fu_quirks_add_quirks_for_path (self, localstatedir, error)) return FALSE; /* success */ return TRUE; } static void fu_quirks_class_init (FuQuirksClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_quirks_finalize; } static void fu_quirks_init (FuQuirks *self) { self->monitors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); self->hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_unref); g_rw_lock_init (&self->hash_mutex); } static void fu_quirks_finalize (GObject *obj) { FuQuirks *self = FU_QUIRKS (obj); g_ptr_array_unref (self->monitors); g_rw_lock_clear (&self->hash_mutex); g_hash_table_unref (self->hash); G_OBJECT_CLASS (fu_quirks_parent_class)->finalize (obj); } /** * fu_quirks_new: (skip) * * Creates a new quirks object. * * Return value: a new #FuQuirks **/ FuQuirks * fu_quirks_new (void) { FuQuirks *self; self = g_object_new (FU_TYPE_QUIRKS, NULL); return FU_QUIRKS (self); } fwupd-1.2.14/src/fu-quirks.h000066400000000000000000000031701402665037500156230ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FU_TYPE_QUIRKS (fu_quirks_get_type ()) G_DECLARE_FINAL_TYPE (FuQuirks, fu_quirks, FU, QUIRKS, GObject) FuQuirks *fu_quirks_new (void); gboolean fu_quirks_load (FuQuirks *self, GError **error); const gchar *fu_quirks_lookup_by_id (FuQuirks *self, const gchar *group, const gchar *key); void fu_quirks_add_value (FuQuirks *self, const gchar *group, const gchar *key, const gchar *value); gboolean fu_quirks_get_kvs_for_guid (FuQuirks *self, const gchar *guid, GHashTableIter *iter); #define FU_QUIRKS_PLUGIN "Plugin" #define FU_QUIRKS_UEFI_VERSION_FORMAT "UefiVersionFormat" #define FU_QUIRKS_DAEMON_VERSION_FORMAT "ComponentIDs" #define FU_QUIRKS_FLAGS "Flags" #define FU_QUIRKS_SUMMARY "Summary" #define FU_QUIRKS_ICON "Icon" #define FU_QUIRKS_NAME "Name" #define FU_QUIRKS_GUID "Guid" #define FU_QUIRKS_COUNTERPART_GUID "CounterpartGuid" #define FU_QUIRKS_PARENT_GUID "ParentGuid" #define FU_QUIRKS_CHILDREN "Children" #define FU_QUIRKS_VERSION "Version" #define FU_QUIRKS_VENDOR "Vendor" #define FU_QUIRKS_VENDOR_ID "VendorId" #define FU_QUIRKS_FIRMWARE_SIZE_MIN "FirmwareSizeMin" #define FU_QUIRKS_FIRMWARE_SIZE_MAX "FirmwareSizeMax" #define FU_QUIRKS_FIRMWARE_SIZE "FirmwareSize" #define FU_QUIRKS_INSTALL_DURATION "InstallDuration" #define FU_QUIRKS_VERSION_FORMAT "VersionFormat" G_END_DECLS fwupd-1.2.14/src/fu-self-test.c000066400000000000000000004054571402665037500162240ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include #include "fu-archive.h" #include "fu-common-cab.h" #include "fu-common-version.h" #include "fu-chunk.h" #include "fu-config.h" #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-engine.h" #include "fu-quirks.h" #include "fu-keyring.h" #include "fu-history.h" #include "fu-install-task.h" #include "fu-plugin-private.h" #include "fu-plugin-list.h" #include "fu-progressbar.h" #include "fu-hash.h" #include "fu-hwids.h" #include "fu-smbios.h" #include "fu-test.h" #ifdef ENABLE_GPG #include "fu-keyring-gpg.h" #endif #ifdef ENABLE_PKCS7 #include "fu-keyring-pkcs7.h" #endif static void fu_self_test_mkroot (void) { if (g_file_test ("/tmp/fwupd-self-test", G_FILE_TEST_EXISTS)) { g_autoptr(GError) error = NULL; if (!fu_common_rmtree ("/tmp/fwupd-self-test", &error)) g_warning ("failed to mkroot: %s", error->message); } g_assert_cmpint (g_mkdir_with_parents ("/tmp/fwupd-self-test/var/lib/fwupd", 0755), ==, 0); } static void fu_archive_invalid_func (void) { g_autofree gchar *filename = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) data = NULL; g_autoptr(GError) error = NULL; filename = fu_test_get_filename (TESTDATADIR, "metadata.xml"); g_assert_nonnull (filename); data = fu_common_get_contents_bytes (filename, &error); g_assert_no_error (error); g_assert_nonnull (data); archive = fu_archive_new (data, FU_ARCHIVE_FLAG_NONE, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_null (archive); } static void fu_engine_generate_md_func (void) { const gchar *tmp; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(GBytes) data = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; /* put cab file somewhere we can parse it */ filename = fu_test_get_filename (TESTDATADIR, "colorhug/colorhug-als-3.0.2.cab"); g_assert_nonnull (filename); data = fu_common_get_contents_bytes (filename, &error); g_assert_no_error (error); g_assert_nonnull (data); ret = fu_common_set_contents_bytes ("/tmp/fwupd-self-test/var/cache/fwupd/foo.cab", data, &error); g_assert_no_error (error); g_assert (ret); /* load engine and check the device was found */ ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); fu_device_set_version (device, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); component = fu_engine_get_component_by_guids (engine, device); g_assert_nonnull (component); /* check remote ID set */ tmp = xb_node_query_text (component, "../custom/value[@key='fwupd::RemoteId']", NULL); g_assert_cmpstr (tmp, ==, "directory"); /* verify checksums */ tmp = xb_node_query_text (component, "releases/release/checksum[@target='container']", NULL); g_assert_cmpstr (tmp, !=, NULL); tmp = xb_node_query_text (component, "releases/release/checksum[@target='content']", NULL); g_assert_cmpstr (tmp, ==, NULL); } static void fu_archive_cab_func (void) { g_autofree gchar *checksum1 = NULL; g_autofree gchar *checksum2 = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) data = NULL; g_autoptr(GError) error = NULL; GBytes *data_tmp; filename = fu_test_get_filename (TESTDATADIR, "colorhug/colorhug-als-3.0.2.cab"); g_assert_nonnull (filename); data = fu_common_get_contents_bytes (filename, &error); g_assert_no_error (error); g_assert_nonnull (data); archive = fu_archive_new (data, FU_ARCHIVE_FLAG_NONE, &error); g_assert_no_error (error); g_assert_nonnull (archive); data_tmp = fu_archive_lookup_by_fn (archive, "firmware.metainfo.xml", &error); g_assert_no_error (error); g_assert_nonnull (data_tmp); checksum1 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, data_tmp); g_assert_cmpstr (checksum1, ==, "8611114f51f7151f190de86a5c9259d79ff34216"); data_tmp = fu_archive_lookup_by_fn (archive, "firmware.bin", &error); g_assert_no_error (error); g_assert_nonnull (data_tmp); checksum2 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, data_tmp); g_assert_cmpstr (checksum2, ==, "7c0ae84b191822bcadbdcbe2f74a011695d783c7"); data_tmp = fu_archive_lookup_by_fn (archive, "NOTGOINGTOEXIST.xml", &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (data_tmp); } static void fu_common_version_guess_format_func (void) { g_assert_cmpint (fu_common_version_guess_format (NULL), ==, FWUPD_VERSION_FORMAT_UNKNOWN); g_assert_cmpint (fu_common_version_guess_format (""), ==, FWUPD_VERSION_FORMAT_UNKNOWN); g_assert_cmpint (fu_common_version_guess_format ("1234ac"), ==, FWUPD_VERSION_FORMAT_PLAIN); g_assert_cmpint (fu_common_version_guess_format ("1.2"), ==, FWUPD_VERSION_FORMAT_PAIR); g_assert_cmpint (fu_common_version_guess_format ("1.2.3"), ==, FWUPD_VERSION_FORMAT_TRIPLET); g_assert_cmpint (fu_common_version_guess_format ("1.2.3.4"), ==, FWUPD_VERSION_FORMAT_QUAD); g_assert_cmpint (fu_common_version_guess_format ("1.2.3.4.5"), ==, FWUPD_VERSION_FORMAT_UNKNOWN); g_assert_cmpint (fu_common_version_guess_format ("1a.2b.3"), ==, FWUPD_VERSION_FORMAT_PLAIN); g_assert_cmpint (fu_common_version_guess_format ("1"), ==, FWUPD_VERSION_FORMAT_NUMBER); g_assert_cmpint (fu_common_version_guess_format ("0x10201"), ==, FWUPD_VERSION_FORMAT_NUMBER); } static void fu_engine_requirements_missing_func (void) { gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; const gchar *xml = "" " " " not.going.to.exist" " " ""; /* set up a dummy version */ fu_engine_add_runtime_version (engine, "org.test.dummy", "1.2.3"); /* make the component require one thing */ silo = xb_silo_new_from_xml (xml, &error); g_assert_no_error (error); g_assert_nonnull (silo); component = xb_silo_query_first (silo, "component", &error); g_assert_no_error (error); g_assert_nonnull (component); /* check this fails */ task = fu_install_task_new (NULL, component); ret = fu_engine_check_requirements (engine, task, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert (!ret); } static void fu_engine_requirements_unsupported_func (void) { gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; const gchar *xml = "" " " " " " " ""; /* set up a dummy version */ fu_engine_add_runtime_version (engine, "org.test.dummy", "1.2.3"); /* make the component require one thing that we don't support */ silo = xb_silo_new_from_xml (xml, &error); g_assert_no_error (error); g_assert_nonnull (silo); component = xb_silo_query_first (silo, "component", &error); g_assert_no_error (error); g_assert_nonnull (component); /* check this fails */ task = fu_install_task_new (NULL, component); ret = fu_engine_check_requirements (engine, task, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert (!ret); } static void fu_engine_requirements_child_func (void) { gboolean ret; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuDevice) child = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " not-child" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version (device, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version_bootloader (device, "4.5.6"); fu_device_set_vendor_id (device, "FFFF"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); fu_device_set_version (child, "0.0.999", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_child (device, child); /* make the component require three things */ silo = xb_silo_new_from_xml (xml, &error); g_assert_no_error (error); g_assert_nonnull (silo); component = xb_silo_query_first (silo, "component", &error); g_assert_no_error (error); g_assert_nonnull (component); /* check this passes */ task = fu_install_task_new (device, component); ret = fu_engine_check_requirements (engine, task, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); } static void fu_engine_requirements_child_fail_func (void) { gboolean ret; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuDevice) child = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " not-child" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version (device, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version_bootloader (device, "4.5.6"); fu_device_set_vendor_id (device, "FFFF"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); fu_device_set_version (child, "0.0.1", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_child (device, child); /* make the component require three things */ silo = xb_silo_new_from_xml (xml, &error); g_assert_no_error (error); g_assert_nonnull (silo); component = xb_silo_query_first (silo, "component", &error); g_assert_no_error (error); g_assert_nonnull (component); /* check this passes */ task = fu_install_task_new (device, component); ret = fu_engine_check_requirements (engine, task, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_nonnull (g_strstr_len (error->message, -1, "Not compatible with child device version")); g_assert (!ret); } static void fu_engine_requirements_func (void) { gboolean ret; g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " org.test.dummy" " " ""; /* set up some dummy versions */ fu_engine_add_runtime_version (engine, "org.test.dummy", "1.2.3"); fu_engine_add_runtime_version (engine, "com.hughski.colorhug", "7.8.9"); /* make the component require one thing */ silo = xb_silo_new_from_xml (xml, &error); g_assert_no_error (error); g_assert_nonnull (silo); component = xb_silo_query_first (silo, "component", &error); g_assert_no_error (error); g_assert_nonnull (component); /* check this passes */ task = fu_install_task_new (NULL, component); ret = fu_engine_check_requirements (engine, task, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); } static void fu_engine_requirements_device_func (void) { gboolean ret; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " " " bootloader" " vendor-id" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version (device, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version_bootloader (device, "4.5.6"); fu_device_set_vendor_id (device, "FFFF"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); /* make the component require three things */ silo = xb_silo_new_from_xml (xml, &error); g_assert_no_error (error); g_assert_nonnull (silo); component = xb_silo_query_first (silo, "component", &error); g_assert_no_error (error); g_assert_nonnull (component); /* check this passes */ task = fu_install_task_new (device, component); ret = fu_engine_check_requirements (engine, task, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); } static void fu_engine_requirements_version_format_func (void) { gboolean ret; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " " " " triplet" " " ""; /* set up a dummy device */ fu_device_set_version (device, "1.2.3.4", FWUPD_VERSION_FORMAT_QUAD); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); /* make the component require three things */ silo = xb_silo_new_from_xml (xml, &error); g_assert_no_error (error); g_assert_nonnull (silo); component = xb_silo_query_first (silo, "component", &error); g_assert_no_error (error); g_assert_nonnull (component); /* check this fails */ task = fu_install_task_new (device, component); ret = fu_engine_check_requirements (engine, task, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_nonnull (g_strstr_len (error->message, -1, "Firmware version formats were different")); g_assert (!ret); } static void fu_engine_requirements_other_device_func (void) { gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new (); g_autoptr(FuDevice) device2 = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); const gchar *xml = "" " " " 1ff60ab2-3905-06a1-b476-0371f00c9e9b" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* set up a dummy device */ fu_device_set_version (device1, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag (device1, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_guid (device1, "12345678-1234-1234-1234-123456789012"); /* set up a different device */ fu_device_set_id (device2, "id2"); fu_device_set_name (device2, "Secondary firmware"); fu_device_set_version (device2, "4.5.6", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_vendor_id (device2, "FFFF"); fu_device_add_guid (device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b"); fu_engine_add_device (engine, device2); /* import firmware metainfo */ silo = xb_silo_new_from_xml (xml, &error); g_assert_no_error (error); g_assert_nonnull (silo); component = xb_silo_query_first (silo, "component", &error); g_assert_no_error (error); g_assert_nonnull (component); /* check this passes */ task = fu_install_task_new (device1, component); ret = fu_engine_check_requirements (engine, task, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); } static void fu_engine_device_priority_func (void) { g_autoptr(FuDevice) device1 = fu_device_new (); g_autoptr(FuDevice) device2 = fu_device_new (); g_autoptr(FuDevice) device3 = fu_device_new (); g_autoptr(FuDevice) device = NULL; g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(GError) error = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* add low prio then high then low */ fu_device_set_id (device1, "id1"); fu_device_set_priority (device1, 0); fu_device_set_plugin (device1, "udev"); fu_device_add_instance_id (device1, "GUID1"); fu_device_convert_instance_ids (device1); fu_engine_add_device (engine, device1); fu_device_set_id (device2, "id2"); fu_device_set_priority (device2, 1); fu_device_set_plugin (device2, "redfish"); fu_device_add_instance_id (device2, "GUID1"); fu_device_convert_instance_ids (device2); fu_engine_add_device (engine, device2); fu_device_set_id (device3, "id3"); fu_device_set_priority (device3, 0); fu_device_set_plugin (device3, "uefi"); fu_device_add_instance_id (device3, "GUID1"); fu_device_convert_instance_ids (device3); fu_engine_add_device (engine, device3); /* get the high prio device */ device = fu_engine_get_device (engine, "867d5f8110f8aa79dd63d7440f21724264f10430", &error); g_assert_no_error (error); g_assert_cmpint (fu_device_get_priority (device), ==, 1); g_clear_object (&device); /* the now-removed low-prio device */ device = fu_engine_get_device (engine, "4e89d81a2e6fb4be2578d245fd8511c1f4ad0b58", &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null (device); g_clear_error (&error); /* the never-added 2nd low-prio device */ device = fu_engine_get_device (engine, "c48feddbbcfee514f530ce8f7f2dccd98b6cc150", &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null (device); } static void fu_engine_device_parent_func (void) { g_autoptr(FuDevice) device1 = fu_device_new (); g_autoptr(FuDevice) device2 = fu_device_new (); g_autoptr(FuDevice) device3 = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(XbSilo) silo_empty = xb_silo_new (); /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* add child */ fu_device_set_id (device1, "child"); fu_device_add_instance_id (device1, "child-GUID-1"); fu_device_add_parent_guid (device1, "parent-GUID"); fu_device_convert_instance_ids (device1); fu_engine_add_device (engine, device1); /* parent */ fu_device_set_id (device2, "parent"); fu_device_add_instance_id (device2, "parent-GUID"); fu_device_set_vendor (device2, "oem"); fu_device_convert_instance_ids (device2); /* add another child */ fu_device_set_id (device3, "child2"); fu_device_add_instance_id (device3, "child-GUID-2"); fu_device_add_parent_guid (device3, "parent-GUID"); fu_device_convert_instance_ids (device3); fu_device_add_child (device2, device3); /* add two together */ fu_engine_add_device (engine, device2); /* verify both children were adopted */ g_assert (fu_device_get_parent (device3) == device2); g_assert (fu_device_get_parent (device1) == device2); g_assert_cmpstr (fu_device_get_vendor (device3), ==, "oem"); g_assert_cmpstr (fu_device_get_vendor (device1), ==, "oem"); /* verify order */ g_assert_cmpint (fu_device_get_order (device1), ==, 0); g_assert_cmpint (fu_device_get_order (device2), ==, 1); g_assert_cmpint (fu_device_get_order (device3), ==, 0); } static void fu_engine_partial_hash_func (void) { gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new (); g_autoptr(FuDevice) device2 = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuPlugin) plugin = fu_plugin_new (); g_autoptr(GError) error = NULL; g_autoptr(GError) error_none = NULL; g_autoptr(GError) error_both = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* set up dummy plugin */ fu_plugin_set_name (plugin, "test"); fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_engine_add_plugin (engine, plugin); /* add two dummy devices */ fu_device_set_id (device1, "device1"); fu_device_set_plugin (device1, "test"); fu_device_add_guid (device1, "12345678-1234-1234-1234-123456789012"); fu_engine_add_device (engine, device1); fu_device_set_id (device2, "device21"); fu_device_set_plugin (device2, "test"); fu_device_set_equivalent_id (device2, "b92f5b7560b84ca005a79f5a15de3c003ce494cf"); fu_device_add_guid (device2, "12345678-1234-1234-1234-123456789012"); fu_engine_add_device (engine, device2); /* match nothing */ ret = fu_engine_unlock (engine, "deadbeef", &error_none); g_assert_error (error_none, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert (!ret); /* match both */ ret = fu_engine_unlock (engine, "9", &error_both); g_assert_error (error_both, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert (!ret); /* match one exactly */ fu_device_add_flag (device1, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_flag (device2, FWUPD_DEVICE_FLAG_LOCKED); ret = fu_engine_unlock (engine, "934b4162a6daa0b033d649c8d464529cec41d3de", &error); g_assert_no_error (error); g_assert (ret); /* match one partially */ fu_device_add_flag (device1, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_flag (device2, FWUPD_DEVICE_FLAG_LOCKED); ret = fu_engine_unlock (engine, "934b", &error); g_assert_no_error (error); g_assert (ret); /* match equivalent ID */ fu_device_add_flag (device1, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_flag (device2, FWUPD_DEVICE_FLAG_LOCKED); ret = fu_engine_unlock (engine, "b92f", &error); g_assert_no_error (error); g_assert (ret); } static void fu_engine_device_unlock_func (void) { gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new (); g_autoptr(XbBuilderSource) source = xb_builder_source_new (); g_autoptr(XbSilo) silo = NULL; /* load engine to get FuConfig set up */ ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); /* add the hardcoded 'fwupd' metadata */ filename = fu_test_get_filename (TESTDATADIR, "metadata.xml"); g_assert (filename != NULL); file = g_file_new_for_path (filename); ret = xb_builder_source_load_file (source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error (error); g_assert_true (ret); xb_builder_import_source (builder, source); silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error (error); g_assert_nonnull (silo); fu_engine_set_silo (engine, silo); /* add a dummy device */ fu_device_set_id (device, "UEFI-dummy-dev0"); fu_device_add_guid (device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_LOCKED); fu_device_set_version_format (device, FWUPD_VERSION_FORMAT_PLAIN); fu_engine_add_device (engine, device); /* ensure the metainfo was matched */ g_assert_nonnull (fwupd_device_get_release_default (FWUPD_DEVICE (device))); } static void fu_engine_require_hwid_func (void) { gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); g_autoptr(XbSilo) silo = NULL; #if !defined(HAVE_GCAB_0_8) && defined(__s390x__) /* See https://github.com/hughsie/fwupd/issues/318 for more information */ g_test_skip ("Skipping HWID test on s390x due to known problem with gcab"); return; #endif /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* load engine to get FuConfig set up */ ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); /* get generated file as a blob */ filename = fu_test_get_filename (TESTDATADIR, "missing-hwid/hwid-1.2.3.cab"); g_assert (filename != NULL); blob_cab = fu_common_get_contents_bytes (filename, &error); g_assert_no_error (error); g_assert (blob_cab != NULL); silo = fu_engine_get_silo_from_blob (engine, blob_cab, &error); g_assert_no_error (error); g_assert_nonnull (silo); /* add a dummy device */ fu_device_set_id (device, "test_device"); fu_device_set_version (device, "1.2.2", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_engine_add_device (engine, device); /* get component */ component = xb_silo_query_first (silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error (error); g_assert_nonnull (component); /* check requirements */ task = fu_install_task_new (device, component); ret = fu_engine_check_requirements (engine, task, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert (error != NULL); g_assert_cmpstr (error->message, ==, "no HWIDs matched 9342d47a-1bab-5709-9869-c840b2eac501"); g_assert (!ret); } static void fu_engine_downgrade_func (void) { FwupdRelease *rel; gboolean ret; g_autofree gchar *testdatadir = NULL; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_pre = NULL; g_autoptr(GPtrArray) releases_dg = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(GPtrArray) releases_up = NULL; g_autoptr(GPtrArray) remotes = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); /* ensure empty tree */ fu_self_test_mkroot (); /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* write a broken file */ ret = g_file_set_contents ("/tmp/fwupd-self-test/broken.xml.gz", "this is not a valid", -1, &error); g_assert_no_error (error); g_assert (ret); /* write the main file */ ret = g_file_set_contents ("/tmp/fwupd-self-test/stable.xml", "" " " " test" " Test Device" " " " aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" " " " " " " " 123" " 456" " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " 123" " 456" " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " " "", -1, &error); g_assert_no_error (error); g_assert (ret); /* write the extra file */ ret = g_file_set_contents ("/tmp/fwupd-self-test/testing.xml", "" " " " test" " Test Device" " " " aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" " " " " " " " 123" " 456" " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " 123" " 456" " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " " "", -1, &error); g_assert_no_error (error); g_assert (ret); testdatadir = fu_test_get_filename (TESTDATADIR, "."); g_assert (testdatadir != NULL); g_setenv ("FU_SELF_TEST_REMOTES_DIR", testdatadir, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fu_engine_get_status (engine), ==, FWUPD_STATUS_IDLE); g_test_assert_expected_messages (); /* return all the remotes, even the broken one */ remotes = fu_engine_get_remotes (engine, &error); g_assert_no_error (error); g_assert (remotes != NULL); g_assert_cmpint (remotes->len, ==, 4); /* ensure there are no devices already */ devices_pre = fu_engine_get_devices (engine, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO); g_assert (devices_pre == NULL); g_clear_error (&error); /* add a device so we can get upgrades and downgrades */ fu_device_set_version (device, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_id (device, "test_device"); fu_device_set_name (device, "Test Device"); fu_device_add_guid (device, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_engine_add_device (engine, device); devices = fu_engine_get_devices (engine, &error); g_assert_no_error (error); g_assert (devices != NULL); g_assert_cmpint (devices->len, ==, 1); g_assert (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_SUPPORTED)); g_assert (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REGISTERED)); /* get the releases for one device */ releases = fu_engine_get_releases (engine, fu_device_get_id (device), &error); g_assert_no_error (error); g_assert (releases != NULL); g_assert_cmpint (releases->len, ==, 4); /* no upgrades, as no firmware is approved */ releases_up = fu_engine_get_upgrades (engine, fu_device_get_id (device), &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO); g_assert_null (releases_up); g_clear_error (&error); /* retry with approved firmware set */ fu_engine_add_approved_firmware (engine, "deadbeefdeadbeefdeadbeefdeadbeef"); fu_engine_add_approved_firmware (engine, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); /* upgrades */ releases_up = fu_engine_get_upgrades (engine, fu_device_get_id (device), &error); g_assert_no_error (error); g_assert (releases_up != NULL); g_assert_cmpint (releases_up->len, ==, 2); /* ensure the list is sorted */ rel = FWUPD_RELEASE (g_ptr_array_index (releases_up, 0)); g_assert_cmpstr (fwupd_release_get_version (rel), ==, "1.2.5"); rel = FWUPD_RELEASE (g_ptr_array_index (releases_up, 1)); g_assert_cmpstr (fwupd_release_get_version (rel), ==, "1.2.4"); /* downgrades */ releases_dg = fu_engine_get_downgrades (engine, fu_device_get_id (device), &error); g_assert_no_error (error); g_assert (releases_dg != NULL); g_assert_cmpint (releases_dg->len, ==, 1); rel = FWUPD_RELEASE (g_ptr_array_index (releases_dg, 0)); g_assert_cmpstr (fwupd_release_get_version (rel), ==, "1.2.2"); } static void fu_engine_install_duration_func (void) { FwupdRelease *rel; gboolean ret; g_autofree gchar *testdatadir = NULL; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); /* ensure empty tree */ fu_self_test_mkroot (); /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* write the main file */ ret = g_file_set_contents ("/tmp/fwupd-self-test/stable.xml", "" " " " test" " " " aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" " " " " " " " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " " "", -1, &error); g_assert_no_error (error); g_assert (ret); testdatadir = fu_test_get_filename (TESTDATADIR, "."); g_assert (testdatadir != NULL); g_setenv ("FU_SELF_TEST_REMOTES_DIR", testdatadir, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); /* add a device so we can get the install duration */ fu_device_set_version (device, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_id (device, "test_device"); fu_device_add_guid (device, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); fu_device_set_install_duration (device, 999); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_engine_add_device (engine, device); devices = fu_engine_get_devices (engine, &error); g_assert_no_error (error); g_assert (devices != NULL); g_assert_cmpint (devices->len, ==, 1); g_assert (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_SUPPORTED)); /* check the release install duration */ releases = fu_engine_get_releases (engine, fu_device_get_id (device), &error); g_assert_no_error (error); g_assert (releases != NULL); g_assert_cmpint (releases->len, ==, 1); rel = FWUPD_RELEASE (g_ptr_array_index (releases, 0)); g_assert_cmpint (fwupd_release_get_install_duration (rel), ==, 120); } static void fu_engine_history_func (void) { gboolean ret; g_autofree gchar *checksum = NULL; g_autofree gchar *device_str_expected = NULL; g_autofree gchar *device_str = NULL; g_autofree gchar *filename = NULL; g_autofree gchar *testdatadir = NULL; g_autoptr(FuDevice) device2 = NULL; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuHistory) history = NULL; g_autoptr(FuInstallTask) task = NULL; g_autoptr(FuPlugin) plugin = fu_plugin_new (); g_autoptr(FwupdDevice) device3 = NULL; g_autoptr(FwupdDevice) device4 = NULL; g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); g_autoptr(XbSilo) silo = NULL; /* ensure empty tree */ fu_self_test_mkroot (); /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* set up dummy plugin */ ret = fu_plugin_open (plugin, PLUGINBUILDDIR "/libfu_plugin_test.so", &error); g_assert_no_error (error); g_assert (ret); fu_engine_add_plugin (engine, plugin); testdatadir = fu_test_get_filename (TESTDATADIR, "."); g_assert (testdatadir != NULL); g_setenv ("FU_SELF_TEST_REMOTES_DIR", testdatadir, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fu_engine_get_status (engine), ==, FWUPD_STATUS_IDLE); /* add a device so we can get upgrade it */ fu_device_set_version (device, "1.2.2", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_id (device, "test_device"); fu_device_set_name (device, "Test Device"); fu_device_set_plugin (device, "test"); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); fu_device_add_checksum (device, "0123456789abcdef0123456789abcdef01234567"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_created (device, 1515338000); fu_engine_add_device (engine, device); devices = fu_engine_get_devices (engine, &error); g_assert_no_error (error); g_assert (devices != NULL); g_assert_cmpint (devices->len, ==, 1); g_assert (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REGISTERED)); filename = fu_test_get_filename (TESTDATADIR, "missing-hwid/noreqs-1.2.3.cab"); g_assert (filename != NULL); blob_cab = fu_common_get_contents_bytes (filename, &error); g_assert_no_error (error); g_assert (blob_cab != NULL); silo = fu_engine_get_silo_from_blob (engine, blob_cab, &error); g_assert_no_error (error); g_assert_nonnull (silo); /* get component */ component = xb_silo_query_first (silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error (error); g_assert_nonnull (component); /* set the counter */ g_setenv ("FWUPD_PLUGIN_TEST", "another-write-required", TRUE); fu_device_set_metadata_integer (device, "nr-update", 0); /* install it */ task = fu_install_task_new (device, component); ret = fu_engine_install (engine, task, blob_cab, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); /* check the write was done more than once */ g_assert_cmpint (fu_device_get_metadata_integer (device, "nr-update"), ==, 2); /* check the history database */ history = fu_history_new (); device2 = fu_history_get_device_by_id (history, fu_device_get_id (device), &error); g_assert_no_error (error); g_assert (device2 != NULL); g_assert_cmpint (fu_device_get_update_state (device2), ==, FWUPD_UPDATE_STATE_SUCCESS); g_assert_cmpstr (fu_device_get_update_error (device2), ==, NULL); fu_device_set_modified (device2, 1514338000); g_hash_table_remove_all (fwupd_release_get_metadata (fu_device_get_release_default (device2))); device_str = fu_device_to_string (device2); checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, blob_cab); device_str_expected = g_strdup_printf ( "Test Device\n" " DeviceId: 894e8c17a29428b09d10cd90d1db74ea76fbcfe8\n" " Guid: 12345678-1234-1234-1234-123456789012\n" " Plugin: test\n" " Flags: updatable\n" " Version: 1.2.2\n" " Created: 2018-01-07\n" " Modified: 2017-12-27\n" " UpdateState: success\n" " \n" " [Release]\n" " Version: 1.2.3\n" " Checksum: SHA1(%s)\n" " Flags: none\n", checksum); ret = fu_test_compare_lines (device_str, device_str_expected, &error); g_assert_no_error (error); g_assert (ret); /* GetResults() */ device3 = fu_engine_get_results (engine, FWUPD_DEVICE_ID_ANY, &error); g_assert (device3 != NULL); g_assert_cmpstr (fu_device_get_id (device3), ==, "894e8c17a29428b09d10cd90d1db74ea76fbcfe8"); g_assert_cmpint (fu_device_get_update_state (device3), ==, FWUPD_UPDATE_STATE_SUCCESS); g_assert_cmpstr (fu_device_get_update_error (device3), ==, NULL); /* ClearResults() */ ret = fu_engine_clear_results (engine, FWUPD_DEVICE_ID_ANY, &error); g_assert_no_error (error); g_assert (ret); /* GetResults() */ device4 = fu_engine_get_results (engine, FWUPD_DEVICE_ID_ANY, &error); g_assert (device4 == NULL); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO); } static void fu_engine_history_inherit (void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *testdatadir = NULL; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(FuPlugin) plugin = fu_plugin_new (); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); g_autoptr(XbSilo) silo = NULL; /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* set up dummy plugin */ g_setenv ("FWUPD_PLUGIN_TEST", "fail", TRUE); ret = fu_plugin_open (plugin, PLUGINBUILDDIR "/libfu_plugin_test.so", &error); g_assert_no_error (error); g_assert (ret); fu_engine_add_plugin (engine, plugin); testdatadir = fu_test_get_filename (TESTDATADIR, "."); g_assert (testdatadir != NULL); g_setenv ("FU_SELF_TEST_REMOTES_DIR", testdatadir, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fu_engine_get_status (engine), ==, FWUPD_STATUS_IDLE); /* add a device so we can get upgrade it */ fu_device_set_version (device, "1.2.2", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_id (device, "test_device"); fu_device_set_name (device, "Test Device"); fu_device_set_plugin (device, "test"); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_created (device, 1515338000); fu_engine_add_device (engine, device); devices = fu_engine_get_devices (engine, &error); g_assert_no_error (error); g_assert (devices != NULL); g_assert_cmpint (devices->len, ==, 1); g_assert (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REGISTERED)); filename = fu_test_get_filename (TESTDATADIR, "missing-hwid/noreqs-1.2.3.cab"); g_assert (filename != NULL); blob_cab = fu_common_get_contents_bytes (filename, &error); g_assert_no_error (error); g_assert (blob_cab != NULL); silo = fu_engine_get_silo_from_blob (engine, blob_cab, &error); g_assert_no_error (error); g_assert_nonnull (silo); /* get component */ component = xb_silo_query_first (silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error (error); g_assert_nonnull (component); /* install it */ g_setenv ("FWUPD_PLUGIN_TEST", "requires-activation", TRUE); task = fu_install_task_new (device, component); ret = fu_engine_install (engine, task, blob_cab, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); /* check the device requires an activation */ g_assert_true (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)); g_assert_cmpstr (fu_device_get_version (device), ==, "1.2.2"); /* activate the device */ ret = fu_engine_activate (engine, fu_device_get_id (device), &error); g_assert_no_error (error); g_assert (ret); /* check the device no longer requires an activation */ g_assert_false (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)); g_assert_cmpstr (fu_device_get_version (device), ==, "1.2.3"); /* emulate getting the flag for a fresh boot on old firmware */ fu_device_set_version (device, "1.2.2", FWUPD_VERSION_FORMAT_TRIPLET); ret = fu_engine_install (engine, task, blob_cab, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_object_unref (engine); g_object_unref (device); engine = fu_engine_new (FU_APP_FLAGS_NONE); fu_engine_set_silo (engine, silo_empty); fu_engine_add_plugin (engine, plugin); device = fu_device_new (); fu_device_set_id (device, "test_device"); fu_device_set_name (device, "Test Device"); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); fu_device_set_version (device, "1.2.2", FWUPD_VERSION_FORMAT_TRIPLET); fu_engine_add_device (engine, device); g_assert_true (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)); } static void fu_engine_history_error_func (void) { gboolean ret; g_autofree gchar *checksum = NULL; g_autofree gchar *device_str_expected = NULL; g_autofree gchar *device_str = NULL; g_autofree gchar *filename = NULL; g_autofree gchar *testdatadir = NULL; g_autoptr(FuDevice) device2 = NULL; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuHistory) history = NULL; g_autoptr(FuInstallTask) task = NULL; g_autoptr(FuPlugin) plugin = fu_plugin_new (); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); g_autoptr(XbSilo) silo = NULL; /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* set up dummy plugin */ g_setenv ("FWUPD_PLUGIN_TEST", "fail", TRUE); ret = fu_plugin_open (plugin, PLUGINBUILDDIR "/libfu_plugin_test.so", &error); g_assert_no_error (error); g_assert (ret); fu_engine_add_plugin (engine, plugin); testdatadir = fu_test_get_filename (TESTDATADIR, "."); g_assert (testdatadir != NULL); g_setenv ("FU_SELF_TEST_REMOTES_DIR", testdatadir, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fu_engine_get_status (engine), ==, FWUPD_STATUS_IDLE); /* add a device so we can get upgrade it */ fu_device_set_version (device, "1.2.2", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_id (device, "test_device"); fu_device_set_name (device, "Test Device"); fu_device_set_plugin (device, "test"); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_created (device, 1515338000); fu_engine_add_device (engine, device); devices = fu_engine_get_devices (engine, &error); g_assert_no_error (error); g_assert (devices != NULL); g_assert_cmpint (devices->len, ==, 1); g_assert (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REGISTERED)); /* install the wrong thing */ filename = fu_test_get_filename (TESTDATADIR, "missing-hwid/noreqs-1.2.3.cab"); g_assert (filename != NULL); blob_cab = fu_common_get_contents_bytes (filename, &error); g_assert_no_error (error); g_assert (blob_cab != NULL); silo = fu_engine_get_silo_from_blob (engine, blob_cab, &error); g_assert_no_error (error); g_assert_nonnull (silo); component = xb_silo_query_first (silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error (error); g_assert_nonnull (component); task = fu_install_task_new (device, component); ret = fu_engine_install (engine, task, blob_cab, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert (error != NULL); g_assert_cmpstr (error->message, ==, "device was not in supported mode"); g_assert (!ret); /* check the history database */ history = fu_history_new (); device2 = fu_history_get_device_by_id (history, fu_device_get_id (device), &error2); g_assert_no_error (error2); g_assert (device2 != NULL); g_assert_cmpint (fu_device_get_update_state (device2), ==, FWUPD_UPDATE_STATE_FAILED); g_assert_cmpstr (fu_device_get_update_error (device2), ==, error->message); g_clear_error (&error); fu_device_set_modified (device2, 1514338000); g_hash_table_remove_all (fwupd_release_get_metadata (fu_device_get_release_default (device2))); device_str = fu_device_to_string (device2); checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, blob_cab); device_str_expected = g_strdup_printf ( "Test Device\n" " DeviceId: 894e8c17a29428b09d10cd90d1db74ea76fbcfe8\n" " Guid: 12345678-1234-1234-1234-123456789012\n" " Plugin: test\n" " Flags: updatable\n" " Version: 1.2.2\n" " Created: 2018-01-07\n" " Modified: 2017-12-27\n" " UpdateState: failed\n" " UpdateError: device was not in supported mode\n" " \n" " [Release]\n" " Version: 1.2.3\n" " Checksum: SHA1(%s)\n" " Flags: none\n", checksum); ret = fu_test_compare_lines (device_str, device_str_expected, &error); g_assert_no_error (error); g_assert (ret); } static void _device_list_count_cb (FuDeviceList *device_list, FuDevice *device, gpointer user_data) { guint *cnt = (guint *) user_data; (*cnt)++; } static void fu_device_list_delay_func (void) { g_autoptr(FuDevice) device1 = fu_device_new (); g_autoptr(FuDevice) device2 = fu_device_new (); g_autoptr(FuDeviceList) device_list = fu_device_list_new (); guint added_cnt = 0; guint changed_cnt = 0; guint removed_cnt = 0; g_signal_connect (device_list, "added", G_CALLBACK (_device_list_count_cb), &added_cnt); g_signal_connect (device_list, "removed", G_CALLBACK (_device_list_count_cb), &removed_cnt); g_signal_connect (device_list, "changed", G_CALLBACK (_device_list_count_cb), &changed_cnt); /* add one device */ fu_device_set_id (device1, "device1"); fu_device_add_instance_id (device1, "foobar"); fu_device_set_remove_delay (device1, 100); fu_device_convert_instance_ids (device1); fu_device_list_add (device_list, device1); g_assert_cmpint (added_cnt, ==, 1); g_assert_cmpint (removed_cnt, ==, 0); g_assert_cmpint (changed_cnt, ==, 0); /* add the same device again */ fu_device_list_add (device_list, device1); g_assert_cmpint (added_cnt, ==, 1); g_assert_cmpint (removed_cnt, ==, 0); g_assert_cmpint (changed_cnt, ==, 0); /* add a device with the same ID */ fu_device_set_id (device2, "device1"); fu_device_list_add (device_list, device2); g_assert_cmpint (added_cnt, ==, 1); g_assert_cmpint (removed_cnt, ==, 0); g_assert_cmpint (changed_cnt, ==, 0); /* spin a bit */ fu_test_loop_run_with_timeout (10); fu_test_loop_quit (); /* verify only a changed event was generated */ added_cnt = removed_cnt = changed_cnt = 0; fu_device_list_remove (device_list, device1); fu_device_list_add (device_list, device1); g_assert_cmpint (added_cnt, ==, 0); g_assert_cmpint (removed_cnt, ==, 0); g_assert_cmpint (changed_cnt, ==, 1); } typedef struct { FuDevice *device_new; FuDevice *device_old; FuDeviceList *device_list; } FuDeviceListReplugHelper; static gboolean fu_device_list_remove_cb (gpointer user_data) { FuDeviceListReplugHelper *helper = (FuDeviceListReplugHelper *) user_data; fu_device_list_remove (helper->device_list, helper->device_old); return FALSE; } static gboolean fu_device_list_add_cb (gpointer user_data) { FuDeviceListReplugHelper *helper = (FuDeviceListReplugHelper *) user_data; fu_device_list_add (helper->device_list, helper->device_new); return FALSE; } static void fu_device_list_replug_auto_func (void) { gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new (); g_autoptr(FuDevice) device2 = fu_device_new (); g_autoptr(FuDevice) parent = fu_device_new (); g_autoptr(FuDeviceList) device_list = fu_device_list_new (); g_autoptr(GError) error = NULL; FuDeviceListReplugHelper helper; /* parent */ fu_device_set_id (parent, "parent"); /* fake child devices */ fu_device_set_id (device1, "device1"); fu_device_set_physical_id (device1, "ID"); fu_device_set_plugin (device1, "self-test"); fu_device_set_remove_delay (device1, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_child (parent, device1); fu_device_set_id (device2, "device2"); fu_device_set_physical_id (device2, "ID"); /* matches */ fu_device_set_plugin (device2, "self-test"); fu_device_set_remove_delay (device2, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); /* not yet added */ ret = fu_device_list_wait_for_replug (device_list, device1, &error); g_assert_no_error (error); g_assert (ret); /* add device */ fu_device_list_add (device_list, device1); /* not waiting */ ret = fu_device_list_wait_for_replug (device_list, device1, &error); g_assert_no_error (error); g_assert (ret); /* waiting */ helper.device_old = device1; helper.device_new = device2; helper.device_list = device_list; g_timeout_add (100, fu_device_list_remove_cb, &helper); g_timeout_add (200, fu_device_list_add_cb, &helper); fu_device_add_flag (device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); ret = fu_device_list_wait_for_replug (device_list, device1, &error); g_assert_no_error (error); g_assert (ret); g_assert_false (fu_device_has_flag (device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); /* check device2 now has parent too */ g_assert (fu_device_get_parent (device2) == parent); /* waiting, failed */ fu_device_add_flag (device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); ret = fu_device_list_wait_for_replug (device_list, device2, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert (!ret); g_assert_true (fu_device_has_flag (device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); } static void fu_device_list_replug_user_func (void) { gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new (); g_autoptr(FuDevice) device2 = fu_device_new (); g_autoptr(FuDeviceList) device_list = fu_device_list_new (); g_autoptr(GError) error = NULL; FuDeviceListReplugHelper helper; /* fake devices */ fu_device_set_id (device1, "device1"); fu_device_add_instance_id (device1, "foo"); fu_device_add_instance_id (device1, "bar"); fu_device_set_plugin (device1, "self-test"); fu_device_set_remove_delay (device1, FU_DEVICE_REMOVE_DELAY_USER_REPLUG); fu_device_convert_instance_ids (device1); fu_device_set_id (device2, "device2"); fu_device_add_instance_id (device2, "baz"); fu_device_add_instance_id (device2, "bar"); /* matches */ fu_device_set_plugin (device2, "self-test"); fu_device_set_remove_delay (device2, FU_DEVICE_REMOVE_DELAY_USER_REPLUG); fu_device_convert_instance_ids (device2); /* not yet added */ ret = fu_device_list_wait_for_replug (device_list, device1, &error); g_assert_no_error (error); g_assert (ret); /* add device */ fu_device_list_add (device_list, device1); /* not waiting */ ret = fu_device_list_wait_for_replug (device_list, device1, &error); g_assert_no_error (error); g_assert (ret); /* waiting */ helper.device_old = device1; helper.device_new = device2; helper.device_list = device_list; g_timeout_add (100, fu_device_list_remove_cb, &helper); g_timeout_add (200, fu_device_list_add_cb, &helper); fu_device_add_flag (device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); ret = fu_device_list_wait_for_replug (device_list, device1, &error); g_assert_no_error (error); g_assert (ret); g_assert_false (fu_device_has_flag (device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); } static void fu_device_list_compatible_func (void) { g_autoptr(FuDevice) device1 = fu_device_new (); g_autoptr(FuDevice) device2 = fu_device_new (); g_autoptr(FuDevice) device_old = NULL; g_autoptr(FuDeviceList) device_list = fu_device_list_new (); g_autoptr(GPtrArray) devices_all = NULL; g_autoptr(GPtrArray) devices_active = NULL; FuDevice *device; guint added_cnt = 0; guint changed_cnt = 0; guint removed_cnt = 0; g_signal_connect (device_list, "added", G_CALLBACK (_device_list_count_cb), &added_cnt); g_signal_connect (device_list, "removed", G_CALLBACK (_device_list_count_cb), &removed_cnt); g_signal_connect (device_list, "changed", G_CALLBACK (_device_list_count_cb), &changed_cnt); /* add one device in runtime mode */ fu_device_set_id (device1, "device1"); fu_device_set_plugin (device1, "plugin-for-runtime"); fu_device_set_vendor_id (device1, "USB:0x20A0"); fu_device_set_version (device1, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_instance_id (device1, "foobar"); fu_device_add_instance_id (device1, "bootloader"); fu_device_set_remove_delay (device1, 100); fu_device_convert_instance_ids (device1); fu_device_list_add (device_list, device1); g_assert_cmpint (added_cnt, ==, 1); g_assert_cmpint (removed_cnt, ==, 0); g_assert_cmpint (changed_cnt, ==, 0); /* add another device in bootloader mode */ fu_device_set_id (device2, "device2"); fu_device_set_plugin (device2, "plugin-for-bootloader"); fu_device_add_instance_id (device2, "bootloader"); fu_device_convert_instance_ids (device2); /* verify only a changed event was generated */ added_cnt = removed_cnt = changed_cnt = 0; fu_device_list_remove (device_list, device1); fu_device_list_add (device_list, device2); g_assert_cmpint (added_cnt, ==, 0); g_assert_cmpint (removed_cnt, ==, 0); g_assert_cmpint (changed_cnt, ==, 1); /* device2 should inherit the vendor ID and version from device1 */ g_assert_cmpstr (fu_device_get_vendor_id (device2), ==, "USB:0x20A0"); g_assert_cmpstr (fu_device_get_version (device2), ==, "1.2.3"); /* one device is active */ devices_active = fu_device_list_get_active (device_list); g_assert_cmpint (devices_active->len, ==, 1); device = g_ptr_array_index (devices_active, 0); g_assert_cmpstr (fu_device_get_id (device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882"); /* the list knows about both devices, list in order of active->old */ devices_all = fu_device_list_get_all (device_list); g_assert_cmpint (devices_all->len, ==, 2); device = g_ptr_array_index (devices_all, 0); g_assert_cmpstr (fu_device_get_id (device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882"); device = g_ptr_array_index (devices_all, 1); g_assert_cmpstr (fu_device_get_id (device), ==, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a"); /* verify we can get the old device from the new device */ device_old = fu_device_list_get_old (device_list, device2); g_assert (device_old == device1); } static void fu_device_list_remove_chain_func (void) { g_autoptr(FuDeviceList) device_list = fu_device_list_new (); g_autoptr(FuDevice) device_child = fu_device_new (); g_autoptr(FuDevice) device_parent = fu_device_new (); guint added_cnt = 0; guint changed_cnt = 0; guint removed_cnt = 0; g_signal_connect (device_list, "added", G_CALLBACK (_device_list_count_cb), &added_cnt); g_signal_connect (device_list, "removed", G_CALLBACK (_device_list_count_cb), &removed_cnt); g_signal_connect (device_list, "changed", G_CALLBACK (_device_list_count_cb), &changed_cnt); /* add child */ fu_device_set_id (device_child, "child"); fu_device_add_instance_id (device_child, "child-GUID-1"); fu_device_convert_instance_ids (device_child); fu_device_list_add (device_list, device_child); g_assert_cmpint (added_cnt, ==, 1); g_assert_cmpint (removed_cnt, ==, 0); g_assert_cmpint (changed_cnt, ==, 0); /* add parent */ fu_device_set_id (device_parent, "parent"); fu_device_add_instance_id (device_parent, "parent-GUID-1"); fu_device_convert_instance_ids (device_parent); fu_device_add_child (device_parent, device_child); fu_device_list_add (device_list, device_parent); g_assert_cmpint (added_cnt, ==, 2); g_assert_cmpint (removed_cnt, ==, 0); g_assert_cmpint (changed_cnt, ==, 0); /* make sure that removing the parent causes both to go; but the child to go first */ fu_device_list_remove (device_list, device_parent); g_assert_cmpint (added_cnt, ==, 2); g_assert_cmpint (removed_cnt, ==, 2); g_assert_cmpint (changed_cnt, ==, 0); } static void fu_device_list_func (void) { g_autoptr(FuDeviceList) device_list = fu_device_list_new (); g_autoptr(FuDevice) device1 = fu_device_new (); g_autoptr(FuDevice) device2 = fu_device_new (); g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices2 = NULL; g_autoptr(GError) error = NULL; FuDevice *device; guint added_cnt = 0; guint changed_cnt = 0; guint removed_cnt = 0; g_signal_connect (device_list, "added", G_CALLBACK (_device_list_count_cb), &added_cnt); g_signal_connect (device_list, "removed", G_CALLBACK (_device_list_count_cb), &removed_cnt); g_signal_connect (device_list, "changed", G_CALLBACK (_device_list_count_cb), &changed_cnt); /* add both */ fu_device_set_id (device1, "device1"); fu_device_add_instance_id (device1, "foobar"); fu_device_convert_instance_ids (device1); fu_device_list_add (device_list, device1); fu_device_set_id (device2, "device2"); fu_device_add_instance_id (device2, "baz"); fu_device_convert_instance_ids (device2); fu_device_list_add (device_list, device2); g_assert_cmpint (added_cnt, ==, 2); g_assert_cmpint (removed_cnt, ==, 0); g_assert_cmpint (changed_cnt, ==, 0); /* get all */ devices = fu_device_list_get_all (device_list); g_assert_cmpint (devices->len, ==, 2); device = g_ptr_array_index (devices, 0); g_assert_cmpstr (fu_device_get_id (device), ==, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a"); /* find by ID */ device = fu_device_list_get_by_id (device_list, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a", &error); g_assert_no_error (error); g_assert (device != NULL); g_assert_cmpstr (fu_device_get_id (device), ==, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a"); g_clear_object (&device); /* find by GUID */ device = fu_device_list_get_by_guid (device_list, "579a3b1c-d1db-5bdc-b6b9-e2c1b28d5b8a", &error); g_assert_no_error (error); g_assert (device != NULL); g_assert_cmpstr (fu_device_get_id (device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882"); g_clear_object (&device); /* find by missing GUID */ device = fu_device_list_get_by_guid (device_list, "notfound", &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert (device == NULL); /* remove device */ added_cnt = removed_cnt = changed_cnt = 0; fu_device_list_remove (device_list, device1); g_assert_cmpint (added_cnt, ==, 0); g_assert_cmpint (removed_cnt, ==, 1); g_assert_cmpint (changed_cnt, ==, 0); devices2 = fu_device_list_get_all (device_list); g_assert_cmpint (devices2->len, ==, 1); device = g_ptr_array_index (devices2, 0); g_assert_cmpstr (fu_device_get_id (device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882"); } static void fu_device_version_format_func (void) { g_autoptr(FuDevice) device = fu_device_new (); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_ENSURE_SEMVER); fu_device_set_version (device, "Ver1.2.3 RELEASE", FWUPD_VERSION_FORMAT_TRIPLET); g_assert_cmpstr (fu_device_get_version (device), ==, "1.2.3"); } static void fu_device_open_refcount_func (void) { gboolean ret; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(GError) error = NULL; fu_device_set_id (device, "test_device"); ret = fu_device_open (device, &error); g_assert_no_error (error); g_assert_true (ret); ret = fu_device_open (device, &error); g_assert_no_error (error); g_assert_true (ret); ret = fu_device_close (device, &error); g_assert_no_error (error); g_assert_true (ret); ret = fu_device_close (device, &error); g_assert_no_error (error); g_assert_true (ret); ret = fu_device_close (device, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL); g_assert_false (ret); } static void fu_device_metadata_func (void) { g_autoptr(FuDevice) device = fu_device_new (); /* string */ fu_device_set_metadata (device, "foo", "bar"); g_assert_cmpstr (fu_device_get_metadata (device, "foo"), ==, "bar"); fu_device_set_metadata (device, "foo", "baz"); g_assert_cmpstr (fu_device_get_metadata (device, "foo"), ==, "baz"); g_assert_null (fu_device_get_metadata (device, "unknown")); /* boolean */ fu_device_set_metadata_boolean (device, "baz", TRUE); g_assert_cmpstr (fu_device_get_metadata (device, "baz"), ==, "true"); g_assert_true (fu_device_get_metadata_boolean (device, "baz")); g_assert_false (fu_device_get_metadata_boolean (device, "unknown")); /* integer */ fu_device_set_metadata_integer (device, "dum", 12345); g_assert_cmpstr (fu_device_get_metadata (device, "dum"), ==, "12345"); g_assert_cmpint (fu_device_get_metadata_integer (device, "dum"), ==, 12345); g_assert_cmpint (fu_device_get_metadata_integer (device, "unknown"), ==, G_MAXUINT); /* broken integer */ fu_device_set_metadata (device, "dum", "123junk"); g_assert_cmpint (fu_device_get_metadata_integer (device, "dum"), ==, G_MAXUINT); fu_device_set_metadata (device, "huge", "4294967296"); /* not 32 bit */ g_assert_cmpint (fu_device_get_metadata_integer (device, "huge"), ==, G_MAXUINT); } static void fu_smbios_func (void) { const gchar *str; gboolean ret; g_autofree gchar *dump = NULL; g_autoptr(FuSmbios) smbios = NULL; g_autoptr(GError) error = NULL; smbios = fu_smbios_new (); ret = fu_smbios_setup (smbios, &error); g_assert_no_error (error); g_assert (ret); dump = fu_smbios_to_string (smbios); if (g_getenv ("VERBOSE") != NULL) g_debug ("%s", dump); /* test for missing table */ str = fu_smbios_get_string (smbios, 0xff, 0, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null (str); g_clear_error (&error); /* check for invalid offset */ str = fu_smbios_get_string (smbios, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0xff, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null (str); g_clear_error (&error); /* get vendor */ str = fu_smbios_get_string (smbios, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x04, &error); g_assert_no_error (error); g_assert_cmpstr (str, ==, "LENOVO"); } static void fu_smbios3_func (void) { const gchar *str; gboolean ret; g_autofree gchar *path = NULL; g_autoptr(FuSmbios) smbios = NULL; g_autoptr(GError) error = NULL; path = fu_test_get_filename (TESTDATADIR, "dmi/tables64"); g_assert_nonnull (path); smbios = fu_smbios_new (); ret = fu_smbios_setup_from_path (smbios, path, &error); g_assert_no_error (error); g_assert (ret); if (g_getenv ("VERBOSE") != NULL) { g_autofree gchar *dump = fu_smbios_to_string (smbios); g_debug ("%s", dump); } /* get vendor */ str = fu_smbios_get_string (smbios, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x04, &error); g_assert_no_error (error); g_assert_cmpstr (str, ==, "Dell Inc."); } static void fu_hwids_func (void) { g_autoptr(FuHwids) hwids = NULL; g_autoptr(FuSmbios) smbios = NULL; g_autoptr(GError) error = NULL; gboolean ret; struct { const gchar *key; const gchar *value; } guids[] = { { "Manufacturer", "6de5d951-d755-576b-bd09-c5cf66b27234" }, { "HardwareID-14", "6de5d951-d755-576b-bd09-c5cf66b27234" }, { "HardwareID-13", "f8e1de5f-b68c-5f52-9d1a-f1ba52f1f773" }, { "HardwareID-12", "e093d715-70f7-51f4-b6c8-b4a7e31def85" }, { "HardwareID-11", "db73af4c-4612-50f7-b8a7-787cf4871847" }, { "HardwareID-10", "f4275c1f-6130-5191-845c-3426247eb6a1" }, { "HardwareID-9", "0cf8618d-9eff-537c-9f35-46861406eb9c" }, { "HardwareID-8", "059eb22d-6dc7-59af-abd3-94bbe017f67c" }, { "HardwareID-7", "da1da9b6-62f5-5f22-8aaa-14db7eeda2a4" }, { "HardwareID-6", "178cd22d-ad9f-562d-ae0a-34009822cdbe" }, { "HardwareID-5", "8dc9b7c5-f5d5-5850-9ab3-bd6f0549d814" }, { "HardwareID-4", "660ccba8-1b78-5a33-80e6-9fb8354ee873" }, { "HardwareID-3", "3faec92a-3ae3-5744-be88-495e90a7d541" }, { "HardwareID-2", "f5ff077f-3eeb-5bae-be1c-e98ffe8ce5f8" }, { "HardwareID-1", "b7cceb67-774c-537e-bf8b-22c6107e9a74" }, { "HardwareID-0", "147efce9-f201-5fc8-ab0c-c859751c3440" }, { NULL, NULL } }; smbios = fu_smbios_new (); ret = fu_smbios_setup (smbios, &error); g_assert_no_error (error); g_assert (ret); hwids = fu_hwids_new (); ret = fu_hwids_setup (hwids, smbios, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpstr (fu_hwids_get_value (hwids, FU_HWIDS_KEY_MANUFACTURER), ==, "LENOVO"); g_assert_cmpstr (fu_hwids_get_value (hwids, FU_HWIDS_KEY_ENCLOSURE_KIND), ==, "a"); g_assert_cmpstr (fu_hwids_get_value (hwids, FU_HWIDS_KEY_FAMILY), ==, "ThinkPad T440s"); g_assert_cmpstr (fu_hwids_get_value (hwids, FU_HWIDS_KEY_PRODUCT_NAME), ==, "20ARS19C0C"); g_assert_cmpstr (fu_hwids_get_value (hwids, FU_HWIDS_KEY_BIOS_VENDOR), ==, "LENOVO"); g_assert_cmpstr (fu_hwids_get_value (hwids, FU_HWIDS_KEY_BIOS_VERSION), ==, "GJET75WW (2.25 )"); g_assert_cmpstr (fu_hwids_get_value (hwids, FU_HWIDS_KEY_BIOS_MAJOR_RELEASE), ==, "02"); g_assert_cmpstr (fu_hwids_get_value (hwids, FU_HWIDS_KEY_BIOS_MINOR_RELEASE), ==, "19"); g_assert_cmpstr (fu_hwids_get_value (hwids, FU_HWIDS_KEY_PRODUCT_SKU), ==, "LENOVO_MT_20AR_BU_Think_FM_ThinkPad T440s"); for (guint i = 0; guids[i].key != NULL; i++) { g_autofree gchar *guid = fu_hwids_get_guid (hwids, guids[i].key, &error); g_assert_no_error (error); g_assert_cmpstr (guid, ==, guids[i].value); } for (guint i = 0; guids[i].key != NULL; i++) g_assert (fu_hwids_has_guid (hwids, guids[i].value)); } static void _plugin_status_changed_cb (FuDevice *device, GParamSpec *pspec, gpointer user_data) { guint *cnt = (guint *) user_data; g_debug ("device %s now %s", fu_device_get_id (device), fwupd_status_to_string (fu_device_get_status (device))); (*cnt)++; fu_test_loop_quit (); } static void _plugin_device_added_cb (FuPlugin *plugin, FuDevice *device, gpointer user_data) { FuDevice **dev = (FuDevice **) user_data; *dev = g_object_ref (device); fu_test_loop_quit (); } static void fu_plugin_delay_func (void) { FuDevice *device_tmp; g_autoptr(FuPlugin) plugin = NULL; g_autoptr(FuDevice) device = NULL; plugin = fu_plugin_new (); g_signal_connect (plugin, "device-added", G_CALLBACK (_plugin_device_added_cb), &device_tmp); g_signal_connect (plugin, "device-removed", G_CALLBACK (_plugin_device_added_cb), &device_tmp); /* add device straight away */ device = fu_device_new (); fu_device_set_id (device, "testdev"); fu_plugin_device_add (plugin, device); g_assert (device_tmp != NULL); g_assert_cmpstr (fu_device_get_id (device_tmp), ==, "b7eccd0059d6d7dc2ef76c35d6de0048cc8c029d"); g_clear_object (&device_tmp); /* remove device */ fu_plugin_device_remove (plugin, device); g_assert (device_tmp != NULL); g_assert_cmpstr (fu_device_get_id (device_tmp), ==, "b7eccd0059d6d7dc2ef76c35d6de0048cc8c029d"); g_clear_object (&device_tmp); } static void _plugin_device_register_cb (FuPlugin *plugin, FuDevice *device, gpointer user_data) { /* fake being a daemon */ fu_plugin_runner_device_register (plugin, device); } static void fu_plugin_quirks_func (void) { const gchar *tmp; gboolean ret; g_autoptr(FuQuirks) quirks = fu_quirks_new (); g_autoptr(FuPlugin) plugin = fu_plugin_new (); g_autoptr(GError) error = NULL; ret = fu_quirks_load (quirks, &error); g_assert_no_error (error); g_assert (ret); fu_plugin_set_quirks (plugin, quirks); /* exact */ tmp = fu_plugin_lookup_quirk_by_id (plugin, "USB\\VID_0A5C&PID_6412", "Flags"); g_assert_cmpstr (tmp, ==, "MERGE_ME,ignore-runtime"); tmp = fu_plugin_lookup_quirk_by_id (plugin, "ACME Inc.=True", "Test"); g_assert_cmpstr (tmp, ==, "awesome"); tmp = fu_plugin_lookup_quirk_by_id (plugin, "CORP*", "Test"); g_assert_cmpstr (tmp, ==, "town"); tmp = fu_plugin_lookup_quirk_by_id (plugin, "USB\\VID_FFFF&PID_FFFF", "Flags"); g_assert_cmpstr (tmp, ==, ""); tmp = fu_plugin_lookup_quirk_by_id (plugin, "baz", "Unfound"); g_assert_cmpstr (tmp, ==, NULL); tmp = fu_plugin_lookup_quirk_by_id (plugin, "unfound", "tests"); g_assert_cmpstr (tmp, ==, NULL); tmp = fu_plugin_lookup_quirk_by_id (plugin, "unfound", "unfound"); g_assert_cmpstr (tmp, ==, NULL); tmp = fu_plugin_lookup_quirk_by_id (plugin, "bb9ec3e2-77b3-53bc-a1f1-b05916715627", "Flags"); g_assert_cmpstr (tmp, ==, "clever"); } static void fu_plugin_quirks_performance_func (void) { g_autoptr(FuQuirks) quirks = fu_quirks_new (); g_autoptr(GTimer) timer = g_timer_new (); const gchar *keys[] = { "Name", "Icon", "Children", "Plugin", "Flags", "FirmwareSizeMin", "FirmwareSizeMax", NULL }; /* insert */ for (guint j = 0; j < 1000; j++) { g_autofree gchar *group = NULL; group = g_strdup_printf ("DeviceInstanceId=USB\\VID_0BDA&PID_%04X", j); for (guint i = 0; keys[i] != NULL; i++) fu_quirks_add_value (quirks, group, keys[i], "Value"); } g_print ("insert=%.3fms ", g_timer_elapsed (timer, NULL) * 1000.f); /* lookup */ g_timer_reset (timer); for (guint j = 0; j < 1000; j++) { g_autofree gchar *group = NULL; group = g_strdup_printf ("DeviceInstanceId=USB\\VID_0BDA&PID_%04X", j); for (guint i = 0; keys[i] != NULL; i++) { const gchar *tmp = fu_quirks_lookup_by_id (quirks, group, keys[i]); g_assert_cmpstr (tmp, ==, "Value"); } } g_print ("lookup=%.3fms ", g_timer_elapsed (timer, NULL) * 1000.f); } static void fu_plugin_quirks_device_func (void) { FuDevice *device_tmp; GPtrArray *children; gboolean ret; g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuQuirks) quirks = fu_quirks_new (); g_autoptr(GError) error = NULL; ret = fu_quirks_load (quirks, &error); g_assert_no_error (error); g_assert (ret); /* use quirk file to set device attributes */ fu_device_set_physical_id (device, "usb:00:05"); fu_device_set_quirks (device, quirks); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_instance_id (device, "USB\\VID_0BDA&PID_1100"); fu_device_convert_instance_ids (device); g_assert_cmpstr (fu_device_get_name (device), ==, "Hub"); /* ensure children are created */ children = fu_device_get_children (device); g_assert_cmpint (children->len, ==, 1); device_tmp = g_ptr_array_index (children, 0); g_assert_cmpstr (fu_device_get_name (device_tmp), ==, "HDMI"); g_assert (fu_device_has_flag (device_tmp, FWUPD_DEVICE_FLAG_UPDATABLE)); } static void fu_plugin_hash_func (void) { GError *error = NULL; g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuPlugin) plugin = fu_plugin_new (); gboolean ret = FALSE; ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); /* make sure not tainted */ ret = fu_engine_get_tainted (engine); g_assert_false (ret); /* create a tainted plugin */ g_setenv ("FWUPD_PLUGIN_TEST", "build-hash", TRUE); ret = fu_plugin_open (plugin, PLUGINBUILDDIR "/libfu_plugin_test.so", &error); g_assert_no_error (error); /* make sure it tainted now */ g_test_expect_message ("FuEngine", G_LOG_LEVEL_WARNING, "* has incorrect built version*"); fu_engine_add_plugin (engine, plugin); ret = fu_engine_get_tainted (engine); g_assert_true (ret); } static void fu_plugin_module_func (void) { GError *error = NULL; FuDevice *device_tmp; FwupdRelease *release; gboolean ret; guint cnt = 0; g_autofree gchar *localstatedir = NULL; g_autofree gchar *mapped_file_fn = NULL; g_autofree gchar *pending_cap = NULL; g_autofree gchar *history_db = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(FuDevice) device2 = NULL; g_autoptr(FuDevice) device3 = NULL; g_autoptr(FuHistory) history = NULL; g_autoptr(FuPlugin) plugin = NULL; g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GMappedFile) mapped_file = NULL; /* create a fake device */ plugin = fu_plugin_new (); g_setenv ("FWUPD_PLUGIN_TEST", "registration", TRUE); ret = fu_plugin_open (plugin, PLUGINBUILDDIR "/libfu_plugin_test.so", &error); g_assert_no_error (error); g_assert (ret); ret = fu_plugin_runner_startup (plugin, &error); g_assert_no_error (error); g_assert (ret); g_signal_connect (plugin, "device-added", G_CALLBACK (_plugin_device_added_cb), &device); g_signal_connect (plugin, "device-register", G_CALLBACK (_plugin_device_register_cb), &device); ret = fu_plugin_runner_coldplug (plugin, &error); g_assert_no_error (error); g_assert (ret); /* check we did the right thing */ g_assert (device != NULL); g_assert_cmpstr (fu_device_get_id (device), ==, "08d460be0f1f9f128413f816022a6439e0078018"); g_assert_cmpstr (fu_device_get_version_lowest (device), ==, "1.2.0"); g_assert_cmpstr (fu_device_get_version (device), ==, "1.2.2"); g_assert_cmpstr (fu_device_get_version_bootloader (device), ==, "0.1.2"); g_assert_cmpstr (fu_device_get_guid_default (device), ==, "b585990a-003e-5270-89d5-3705a17f9a43"); g_assert_cmpstr (fu_device_get_name (device), ==, "Integrated Webcam™"); /* schedule an offline update */ g_signal_connect (device, "notify::status", G_CALLBACK (_plugin_status_changed_cb), &cnt); mapped_file_fn = fu_test_get_filename (TESTDATADIR, "colorhug/firmware.bin"); mapped_file = g_mapped_file_new (mapped_file_fn, FALSE, &error); g_assert_no_error (error); g_assert (mapped_file != NULL); blob_cab = g_mapped_file_get_bytes (mapped_file); release = fu_device_get_release_default (device); fwupd_release_set_version (release, "1.2.3"); ret = fu_plugin_runner_schedule_update (plugin, device, release, blob_cab, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (cnt, ==, 1); /* set on the current device */ g_assert_true (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)); /* lets check the history */ history = fu_history_new (); device2 = fu_history_get_device_by_id (history, fu_device_get_id (device), &error); g_assert_no_error (error); g_assert (device2 != NULL); g_assert_cmpint (fu_device_get_update_state (device2), ==, FWUPD_UPDATE_STATE_PENDING); g_assert_cmpstr (fu_device_get_update_error (device2), ==, NULL); g_assert_true (fu_device_has_flag (device2, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)); release = fu_device_get_release_default (device2); g_assert (release != NULL); g_assert_cmpstr (fwupd_release_get_filename (release), !=, NULL); g_assert_cmpstr (fwupd_release_get_version (release), ==, "1.2.3"); /* save this; we'll need to delete it later */ pending_cap = g_strdup (fwupd_release_get_filename (release)); /* lets do this online */ ret = fu_plugin_runner_update (plugin, device, blob_cab, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (cnt, ==, 4); /* check the new version */ g_assert_cmpstr (fu_device_get_version (device), ==, "1.2.3"); g_assert_cmpstr (fu_device_get_version_bootloader (device), ==, "0.1.2"); /* lets check the history */ device3 = fu_history_get_device_by_id (history, fu_device_get_id (device), &error); g_assert_no_error (error); g_assert (device3 != NULL); g_assert_cmpint (fu_device_get_update_state (device3), ==, FWUPD_UPDATE_STATE_SUCCESS); g_assert_cmpstr (fu_device_get_update_error (device3), ==, NULL); /* get the status */ device_tmp = fu_device_new (); fu_device_set_id (device_tmp, "FakeDevice"); ret = fu_plugin_runner_get_results (plugin, device_tmp, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fu_device_get_update_state (device_tmp), ==, FWUPD_UPDATE_STATE_SUCCESS); g_assert_cmpstr (fu_device_get_update_error (device_tmp), ==, NULL); /* clear */ ret = fu_plugin_runner_clear_results (plugin, device_tmp, &error); g_assert_no_error (error); g_assert (ret); g_object_unref (device_tmp); g_clear_error (&error); /* delete files */ localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); history_db = g_build_filename (localstatedir, "pending.db", NULL); g_unlink (history_db); g_unlink (pending_cap); } static void fu_plugin_list_func (void) { GPtrArray *plugins; FuPlugin *plugin; g_autoptr(FuPluginList) plugin_list = fu_plugin_list_new (); g_autoptr(FuPlugin) plugin1 = fu_plugin_new (); g_autoptr(FuPlugin) plugin2 = fu_plugin_new (); g_autoptr(GError) error = NULL; fu_plugin_set_name (plugin1, "plugin1"); fu_plugin_set_name (plugin2, "plugin2"); /* get all the plugins */ fu_plugin_list_add (plugin_list, plugin1); fu_plugin_list_add (plugin_list, plugin2); plugins = fu_plugin_list_get_all (plugin_list); g_assert_cmpint (plugins->len, ==, 2); /* get a single plugin */ plugin = fu_plugin_list_find_by_name (plugin_list, "plugin1", &error); g_assert_no_error (error); g_assert (plugin != NULL); g_assert_cmpstr (fu_plugin_get_name (plugin), ==, "plugin1"); /* does not exist */ plugin = fu_plugin_list_find_by_name (plugin_list, "nope", &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert (plugin == NULL); } static void fu_plugin_list_depsolve_func (void) { GPtrArray *plugins; FuPlugin *plugin; gboolean ret; g_autoptr(FuPluginList) plugin_list = fu_plugin_list_new (); g_autoptr(FuPlugin) plugin1 = fu_plugin_new (); g_autoptr(FuPlugin) plugin2 = fu_plugin_new (); g_autoptr(GError) error = NULL; fu_plugin_set_name (plugin1, "plugin1"); fu_plugin_set_name (plugin2, "plugin2"); /* add rule then depsolve */ fu_plugin_list_add (plugin_list, plugin1); fu_plugin_list_add (plugin_list, plugin2); fu_plugin_add_rule (plugin1, FU_PLUGIN_RULE_RUN_AFTER, "plugin2"); ret = fu_plugin_list_depsolve (plugin_list, &error); g_assert_no_error (error); g_assert (ret); plugins = fu_plugin_list_get_all (plugin_list); g_assert_cmpint (plugins->len, ==, 2); plugin = g_ptr_array_index (plugins, 0); g_assert_cmpstr (fu_plugin_get_name (plugin), ==, "plugin2"); g_assert_cmpint (fu_plugin_get_order (plugin), ==, 0); g_assert (fu_plugin_get_enabled (plugin)); /* add another rule, then re-depsolve */ fu_plugin_add_rule (plugin1, FU_PLUGIN_RULE_CONFLICTS, "plugin2"); ret = fu_plugin_list_depsolve (plugin_list, &error); g_assert_no_error (error); g_assert (ret); plugin = fu_plugin_list_find_by_name (plugin_list, "plugin1", &error); g_assert_no_error (error); g_assert (plugin != NULL); g_assert (fu_plugin_get_enabled (plugin)); plugin = fu_plugin_list_find_by_name (plugin_list, "plugin2", &error); g_assert_no_error (error); g_assert (plugin != NULL); g_assert (!fu_plugin_get_enabled (plugin)); } static void fu_history_migrate_func (void) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_dst = NULL; g_autoptr(GFile) file_src = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(FuHistory) history = NULL; g_autofree gchar *filename = NULL; /* load old version */ filename = fu_test_get_filename (TESTDATADIR, "history_v1.db"); file_src = g_file_new_for_path (filename); file_dst = g_file_new_for_path ("/tmp/fwupd-self-test/var/lib/fwupd/pending.db"); ret = g_file_copy (file_src, file_dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error); g_assert_no_error (error); g_assert (ret); /* create, migrating as required */ history = fu_history_new (); g_assert (history != NULL); /* get device */ device = fu_history_get_device_by_id (history, "2ba16d10df45823dd4494ff10a0bfccfef512c9d", &error); g_assert_no_error (error); g_assert (device != NULL); g_assert_cmpstr (fu_device_get_id (device), ==, "2ba16d10df45823dd4494ff10a0bfccfef512c9d"); } static void fu_history_func (void) { GError *error = NULL; GPtrArray *checksums; gboolean ret; FuDevice *device; FwupdRelease *release; g_autoptr(FuDevice) device_found = NULL; g_autoptr(FuHistory) history = NULL; g_autoptr(GPtrArray) approved_firmware = NULL; g_autofree gchar *dirname = NULL; g_autofree gchar *filename = NULL; /* create */ history = fu_history_new (); g_assert (history != NULL); /* delete the database */ dirname = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); if (!g_file_test (dirname, G_FILE_TEST_IS_DIR)) return; filename = g_build_filename (dirname, "pending.db", NULL); g_unlink (filename); /* add a device */ device = fu_device_new (); fu_device_set_id (device, "self-test"); fu_device_set_name (device, "ColorHug"), fu_device_set_version (device, "3.0.1", FWUPD_VERSION_FORMAT_TRIPLET), fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED); fu_device_set_update_error (device, "word"); fu_device_add_guid (device, "827edddd-9bb6-5632-889f-2c01255503da"); fu_device_set_flags (device, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_created (device, 123); fu_device_set_modified (device, 456); release = fwupd_release_new (); fwupd_release_set_filename (release, "/var/lib/dave.cap"), fwupd_release_add_checksum (release, "abcdef"); fwupd_release_set_version (release, "3.0.2"); fwupd_release_add_metadata_item (release, "FwupdVersion", VERSION); ret = fu_history_add_device (history, device, release, &error); g_assert_no_error (error); g_assert (ret); g_object_unref (release); /* ensure database was created */ g_assert (g_file_test (filename, G_FILE_TEST_EXISTS)); g_object_unref (device); /* get device */ device = fu_history_get_device_by_id (history, "2ba16d10df45823dd4494ff10a0bfccfef512c9d", &error); g_assert_no_error (error); g_assert (device != NULL); g_assert_cmpstr (fu_device_get_id (device), ==, "2ba16d10df45823dd4494ff10a0bfccfef512c9d"); g_assert_cmpstr (fu_device_get_name (device), ==, "ColorHug"); g_assert_cmpstr (fu_device_get_version (device), ==, "3.0.1"); g_assert_cmpint (fu_device_get_update_state (device), ==, FWUPD_UPDATE_STATE_FAILED); g_assert_cmpstr (fu_device_get_update_error (device), ==, "word"); g_assert_cmpstr (fu_device_get_guid_default (device), ==, "827edddd-9bb6-5632-889f-2c01255503da"); g_assert_cmpint (fu_device_get_flags (device), ==, FWUPD_DEVICE_FLAG_INTERNAL); g_assert_cmpint (fu_device_get_created (device), ==, 123); g_assert_cmpint (fu_device_get_modified (device), ==, 456); release = fu_device_get_release_default (device); g_assert (release != NULL); g_assert_cmpstr (fwupd_release_get_version (release), ==, "3.0.2"); g_assert_cmpstr (fwupd_release_get_filename (release), ==, "/var/lib/dave.cap"); g_assert_cmpstr (fwupd_release_get_metadata_item (release, "FwupdVersion"), ==, VERSION); checksums = fwupd_release_get_checksums (release); g_assert (checksums != NULL); g_assert_cmpint (checksums->len, ==, 1); g_assert_cmpstr (fwupd_checksum_get_by_kind (checksums, G_CHECKSUM_SHA1), ==, "abcdef"); ret = fu_history_add_device (history, device, release, &error); g_assert_no_error (error); g_assert (ret); /* get device that does not exist */ device_found = fu_history_get_device_by_id (history, "XXXXXXXXXXXXX", &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert (device_found == NULL); g_clear_error (&error); /* get device that does exist */ device_found = fu_history_get_device_by_id (history, "2ba16d10df45823dd4494ff10a0bfccfef512c9d", &error); g_assert_no_error (error); g_assert (device_found != NULL); g_object_unref (device_found); /* remove device */ ret = fu_history_remove_device (history, device, release, &error); g_assert_no_error (error); g_assert (ret); g_object_unref (device); /* get device that does not exist */ device_found = fu_history_get_device_by_id (history, "2ba16d10df45823dd4494ff10a0bfccfef512c9d", &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert (device_found == NULL); g_clear_error (&error); /* approved firmware */ ret = fu_history_clear_approved_firmware (history, &error); g_assert_no_error (error); g_assert (ret); ret = fu_history_add_approved_firmware (history, "foo", &error); g_assert_no_error (error); g_assert (ret); ret = fu_history_add_approved_firmware (history, "bar", &error); g_assert_no_error (error); g_assert (ret); approved_firmware = fu_history_get_approved_firmware (history, &error); g_assert_no_error (error); g_assert_nonnull (approved_firmware); g_assert_cmpint (approved_firmware->len, ==, 2); g_assert_cmpstr (g_ptr_array_index (approved_firmware, 0), ==, "foo"); g_assert_cmpstr (g_ptr_array_index (approved_firmware, 1), ==, "bar"); } static void fu_keyring_gpg_func (void) { #ifdef ENABLE_GPG gboolean ret; g_autofree gchar *fw_fail = NULL; g_autofree gchar *fw_pass = NULL; g_autofree gchar *pki_dir = NULL; g_autoptr(FuKeyring) keyring = NULL; g_autoptr(FuKeyringResult) result_fail = NULL; g_autoptr(FuKeyringResult) result_pass = NULL; g_autoptr(GBytes) blob_fail = NULL; g_autoptr(GBytes) blob_pass = NULL; g_autoptr(GBytes) blob_sig = NULL; g_autoptr(GError) error = NULL; const gchar *sig_gpgme = "-----BEGIN PGP SIGNATURE-----\n" "Version: GnuPG v1\n\n" "iQEcBAABCAAGBQJVt0B4AAoJEEim2A5FOLrCFb8IAK+QTLY34Wu8xZ8nl6p3JdMu" "HOaifXAmX7291UrsFRwdabU2m65pqxQLwcoFrqGv738KuaKtu4oIwo9LIrmmTbEh" "IID8uszxBt0bMdcIHrvwd+ADx+MqL4hR3guXEE3YOBTLvv2RF1UBcJPInNf/7Ui1" "3lW1c3trL8RAJyx1B5RdKqAMlyfwiuvKM5oT4SN4uRSbQf+9mt78ZSWfJVZZH/RR" "H9q7PzR5GdmbsRPM0DgC27Trvqjo3MzoVtoLjIyEb/aWqyulUbnJUNKPYTnZgkzM" "v2yVofWKIM3e3wX5+MOtf6EV58mWa2cHJQ4MCYmpKxbIvAIZagZ4c9A8BA6tQWg=" "=fkit\n" "-----END PGP SIGNATURE-----\n"; /* add keys to keyring */ keyring = fu_keyring_gpg_new (); ret = fu_keyring_setup (keyring, &error); g_assert_no_error (error); g_assert_true (ret); pki_dir = fu_test_get_filename (TESTDATADIR, "pki"); g_assert_nonnull (pki_dir); ret = fu_keyring_add_public_keys (keyring, pki_dir, &error); g_assert_no_error (error); g_assert_true (ret); /* verify with GnuPG */ fw_pass = fu_test_get_filename (TESTDATADIR, "colorhug/firmware.bin"); g_assert_nonnull (fw_pass); blob_pass = fu_common_get_contents_bytes (fw_pass, &error); g_assert_no_error (error); g_assert_nonnull (blob_pass); blob_sig = g_bytes_new_static (sig_gpgme, strlen (sig_gpgme)); result_pass = fu_keyring_verify_data (keyring, blob_pass, blob_sig, FU_KEYRING_VERIFY_FLAG_NONE, &error); g_assert_no_error (error); g_assert_nonnull (result_pass); g_assert_cmpint (fu_keyring_result_get_timestamp (result_pass), == , 1438072952); g_assert_cmpstr (fu_keyring_result_get_authority (result_pass), == , "3FC6B804410ED0840D8F2F9748A6D80E4538BAC2"); /* verify will fail with GnuPG */ fw_fail = fu_test_get_filename (TESTDATADIR, "colorhug/colorhug-als-3.0.2.cab"); g_assert_nonnull (fw_fail); blob_fail = fu_common_get_contents_bytes (fw_fail, &error); g_assert_no_error (error); g_assert_nonnull (blob_fail); result_fail = fu_keyring_verify_data (keyring, blob_fail, blob_sig, FU_KEYRING_VERIFY_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID); g_assert_null (result_fail); g_clear_error (&error); #else g_test_skip ("no GnuPG support enabled"); #endif } static void fu_keyring_pkcs7_func (void) { #ifdef ENABLE_PKCS7 gboolean ret; g_autofree gchar *fw_fail = NULL; g_autofree gchar *fw_pass = NULL; g_autofree gchar *pki_dir = NULL; g_autofree gchar *sig_fn = NULL; g_autofree gchar *sig_fn2 = NULL; g_autoptr(FuKeyring) keyring = NULL; g_autoptr(FuKeyringResult) result_fail = NULL; g_autoptr(FuKeyringResult) result_pass = NULL; g_autoptr(GBytes) blob_fail = NULL; g_autoptr(GBytes) blob_pass = NULL; g_autoptr(GBytes) blob_sig = NULL; g_autoptr(GBytes) blob_sig2 = NULL; g_autoptr(GError) error = NULL; /* add keys to keyring */ keyring = fu_keyring_pkcs7_new (); ret = fu_keyring_setup (keyring, &error); g_assert_no_error (error); g_assert_true (ret); pki_dir = fu_test_get_filename (TESTDATADIR_SRC, "pki"); g_assert_nonnull (pki_dir); ret = fu_keyring_add_public_keys (keyring, pki_dir, &error); g_assert_no_error (error); g_assert_true (ret); /* verify with a signature from the old LVFS */ fw_pass = fu_test_get_filename (TESTDATADIR_SRC, "colorhug/firmware.bin"); g_assert_nonnull (fw_pass); blob_pass = fu_common_get_contents_bytes (fw_pass, &error); g_assert_no_error (error); g_assert_nonnull (blob_pass); sig_fn = fu_test_get_filename (TESTDATADIR_SRC, "colorhug/firmware.bin.p7b"); g_assert_nonnull (sig_fn); blob_sig = fu_common_get_contents_bytes (sig_fn, &error); g_assert_no_error (error); g_assert_nonnull (blob_sig); result_pass = fu_keyring_verify_data (keyring, blob_pass, blob_sig, FU_KEYRING_VERIFY_FLAG_DISABLE_TIME_CHECKS, &error); g_assert_no_error (error); g_assert_nonnull (result_pass); g_assert_cmpint (fu_keyring_result_get_timestamp (result_pass), >= , 1502871248); g_assert_cmpstr (fu_keyring_result_get_authority (result_pass), == , "O=Linux Vendor Firmware Project,CN=LVFS CA"); /* verify will fail with a self-signed signature */ sig_fn2 = fu_test_get_filename (TESTDATADIR_DST, "colorhug/firmware.bin.p7c"); g_assert_nonnull (sig_fn2); blob_sig2 = fu_common_get_contents_bytes (sig_fn2, &error); g_assert_no_error (error); g_assert_nonnull (blob_sig2); result_fail = fu_keyring_verify_data (keyring, blob_pass, blob_sig2, FU_KEYRING_VERIFY_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID); g_assert_null (result_fail); g_clear_error (&error); /* verify will fail with valid signature and different data */ fw_fail = fu_test_get_filename (TESTDATADIR, "colorhug/colorhug-als-3.0.2.cab"); g_assert_nonnull (fw_fail); blob_fail = fu_common_get_contents_bytes (fw_fail, &error); g_assert_no_error (error); g_assert_nonnull (blob_fail); result_fail = fu_keyring_verify_data (keyring, blob_fail, blob_sig, FU_KEYRING_VERIFY_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID); g_assert_null (result_fail); g_clear_error (&error); #else g_test_skip ("no GnuTLS support enabled"); #endif } static void fu_keyring_pkcs7_self_signed_func (void) { #ifdef ENABLE_PKCS7 gboolean ret; g_autoptr(FuKeyring) kr = NULL; g_autoptr(FuKeyringResult) kr_result = NULL; g_autoptr(GBytes) payload = NULL; g_autoptr(GBytes) signature = NULL; g_autoptr(GError) error = NULL; #ifndef HAVE_GNUTLS_3_6_0 /* required to create the private key correctly */ g_test_skip ("GnuTLS version too old"); return; #endif /* create detached signature and verify */ kr = fu_keyring_pkcs7_new (); ret = fu_keyring_setup (kr, &error); g_assert_no_error (error); g_assert_true (ret); payload = fu_common_get_contents_bytes ("/etc/machine-id", &error); g_assert_no_error (error); g_assert_nonnull (payload); signature = fu_keyring_sign_data (kr, payload, FU_KEYRING_SIGN_FLAG_ADD_TIMESTAMP, &error); g_assert_no_error (error); g_assert_nonnull (signature); ret = fu_common_set_contents_bytes ("/tmp/test.p7b", signature, &error); g_assert_no_error (error); g_assert_true (ret); kr_result = fu_keyring_verify_data (kr, payload, signature, FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT, &error); g_assert_no_error (error); g_assert_nonnull (kr_result); #else g_test_skip ("no GnuTLS support enabled"); #endif } static void fu_common_firmware_builder_func (void) { const gchar *data; g_autofree gchar *archive_fn = NULL; g_autoptr(GBytes) archive_blob = NULL; g_autoptr(GBytes) firmware_blob = NULL; g_autoptr(GError) error = NULL; /* get test file */ archive_fn = fu_test_get_filename (TESTDATADIR, "builder/firmware.tar"); g_assert (archive_fn != NULL); archive_blob = fu_common_get_contents_bytes (archive_fn, &error); g_assert_no_error (error); g_assert (archive_blob != NULL); /* generate the firmware */ firmware_blob = fu_common_firmware_builder (archive_blob, "startup.sh", "firmware.bin", &error); if (firmware_blob == NULL) { if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED)) { g_test_skip ("Missing permissions to create namespace in container"); return; } if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip ("User namespaces not supported in container"); return; } g_assert_no_error (error); } /* check it */ data = g_bytes_get_data (firmware_blob, NULL); g_assert_cmpstr (data, ==, "xobdnas eht ni gninnur"); } static void fu_test_stdout_cb (const gchar *line, gpointer user_data) { guint *lines = (guint *) user_data; g_debug ("got '%s'", line); (*lines)++; } static gboolean _open_cb (GObject *device, GError **error) { g_assert_cmpstr (g_object_get_data (device, "state"), ==, "closed"); g_object_set_data (device, "state", (gpointer) "opened"); return TRUE; } static gboolean _close_cb (GObject *device, GError **error) { g_assert_cmpstr (g_object_get_data (device, "state"), ==, "opened"); g_object_set_data (device, "state", (gpointer) "closed-on-unref"); return TRUE; } static void fu_device_locker_func (void) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error = NULL; g_autoptr(GObject) device = g_object_new (G_TYPE_OBJECT, NULL); g_object_set_data (device, "state", (gpointer) "closed"); locker = fu_device_locker_new_full (device, _open_cb, _close_cb, &error); g_assert_no_error (error); g_assert_nonnull (locker); g_clear_object (&locker); g_assert_cmpstr (g_object_get_data (device, "state"), ==, "closed-on-unref"); } static gboolean _fail_open_cb (GObject *device, GError **error) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "fail"); return FALSE; } static gboolean _fail_close_cb (GObject *device, GError **error) { g_assert_not_reached (); return TRUE; } static void fu_device_locker_fail_func (void) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error = NULL; g_autoptr(GObject) device = g_object_new (G_TYPE_OBJECT, NULL); locker = fu_device_locker_new_full (device, _fail_open_cb, _fail_close_cb, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_null (locker); } static void fu_common_spawn_func (void) { gboolean ret; guint lines = 0; g_autoptr(GError) error = NULL; g_autofree gchar *fn = NULL; const gchar *argv[3] = { "replace", "test", NULL }; fn = fu_test_get_filename (TESTDATADIR, "spawn.sh"); g_assert (fn != NULL); argv[0] = fn; ret = fu_common_spawn_sync (argv, fu_test_stdout_cb, &lines, 0, NULL, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (lines, ==, 6); } static void fu_common_spawn_timeout_func (void) { gboolean ret; guint lines = 0; g_autoptr(GError) error = NULL; g_autofree gchar *fn = NULL; const gchar *argv[3] = { "replace", "test", NULL }; fn = fu_test_get_filename (TESTDATADIR, "spawn.sh"); g_assert (fn != NULL); argv[0] = fn; ret = fu_common_spawn_sync (argv, fu_test_stdout_cb, &lines, 50, NULL, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_assert (!ret); g_assert_cmpint (lines, ==, 1); } static void fu_progressbar_func (void) { g_autoptr(FuProgressbar) progressbar = fu_progressbar_new (); fu_progressbar_set_length_status (progressbar, 20); fu_progressbar_set_length_percentage (progressbar, 50); g_print ("\n"); for (guint i = 0; i < 100; i++) { fu_progressbar_update (progressbar, FWUPD_STATUS_DECOMPRESSING, i); g_usleep (10000); } fu_progressbar_update (progressbar, FWUPD_STATUS_IDLE, 0); for (guint i = 0; i < 100; i++) { guint pc = (i > 25 && i < 75) ? 0 : i; fu_progressbar_update (progressbar, FWUPD_STATUS_LOADING, pc); g_usleep (10000); } fu_progressbar_update (progressbar, FWUPD_STATUS_IDLE, 0); for (guint i = 0; i < 5000; i++) { fu_progressbar_update (progressbar, FWUPD_STATUS_LOADING, 0); g_usleep (1000); } fu_progressbar_update (progressbar, FWUPD_STATUS_IDLE, 0); } static void fu_common_endian_func (void) { guint8 buf[2]; fu_common_write_uint16 (buf, 0x1234, G_LITTLE_ENDIAN); g_assert_cmpint (buf[0], ==, 0x34); g_assert_cmpint (buf[1], ==, 0x12); g_assert_cmpint (fu_common_read_uint16 (buf, G_LITTLE_ENDIAN), ==, 0x1234); fu_common_write_uint16 (buf, 0x1234, G_BIG_ENDIAN); g_assert_cmpint (buf[0], ==, 0x12); g_assert_cmpint (buf[1], ==, 0x34); g_assert_cmpint (fu_common_read_uint16 (buf, G_BIG_ENDIAN), ==, 0x1234); } static GBytes * _build_cab (GCabCompression compression, ...) { #ifdef HAVE_GCAB_1_0 gboolean ret; va_list args; g_autoptr(GCabCabinet) cabinet = NULL; g_autoptr(GCabFolder) cabfolder = NULL; g_autoptr(GError) error = NULL; g_autoptr(GOutputStream) op = NULL; /* create a new archive */ cabinet = gcab_cabinet_new (); cabfolder = gcab_folder_new (compression); ret = gcab_cabinet_add_folder (cabinet, cabfolder, &error); g_assert_no_error (error); g_assert (ret); /* add each file */ va_start (args, compression); do { const gchar *fn; const gchar *text; g_autoptr(GCabFile) cabfile = NULL; g_autoptr(GBytes) blob = NULL; /* get filename */ fn = va_arg (args, const gchar *); if (fn == NULL) break; /* get contents */ text = va_arg (args, const gchar *); if (text == NULL) break; g_debug ("creating %s with %s", fn, text); /* add a GCabFile to the cabinet */ blob = g_bytes_new_static (text, strlen (text)); cabfile = gcab_file_new_with_bytes (fn, blob); ret = gcab_folder_add_file (cabfolder, cabfile, FALSE, NULL, &error); g_assert_no_error (error); g_assert (ret); } while (TRUE); va_end (args); /* write the archive to a blob */ op = g_memory_output_stream_new_resizable (); ret = gcab_cabinet_write_simple (cabinet, op, NULL, NULL, NULL, &error); g_assert_no_error (error); g_assert (ret); ret = g_output_stream_close (op, NULL, &error); g_assert_no_error (error); g_assert (ret); return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (op)); #else return NULL; #endif } static void _plugin_composite_device_added_cb (FuPlugin *plugin, FuDevice *device, gpointer user_data) { GPtrArray *devices = (GPtrArray *) user_data; g_ptr_array_add (devices, g_object_ref (device)); } static void fu_plugin_composite_func (void) { GError *error = NULL; gboolean ret; g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuPlugin) plugin = fu_plugin_new (); g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) install_tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_autoptr(XbSilo) silo_empty = xb_silo_new (); g_autoptr(XbSilo) silo = NULL; /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* create CAB file */ blob = _build_cab (GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " b585990a-003e-5270-89d5-3705a17f9a43\n" " \n" " \n" " \n" " \n" "", "acme.module1.metainfo.xml", "\n" " com.acme.example.firmware.module1\n" " \n" " 7fddead7-12b5-4fb9-9fa0-6d30305df755\n" " \n" " \n" " \n" " \n" " \n" " plain\n" " \n" "", "acme.module2.metainfo.xml", "\n" " com.acme.example.firmware.module2\n" " \n" " b8fe6b45-8702-4bcd-8120-ef236caac76f\n" " \n" " \n" " \n" " \n" " \n" " plain\n" " \n" "", "firmware.bin", "world", NULL); if (blob == NULL) { g_test_skip ("libgcab too old"); return; } silo = fu_common_cab_build_silo (blob, 10240, &error); g_assert_no_error (error); g_assert_nonnull (silo); components = xb_silo_query (silo, "components/component", 0, &error); g_assert_no_error (error); g_assert_nonnull (components); g_assert_cmpint (components->len, ==, 3); /* set up dummy plugin */ g_setenv ("FWUPD_PLUGIN_TEST", "composite", TRUE); ret = fu_plugin_open (plugin, PLUGINBUILDDIR "/libfu_plugin_test.so", &error); g_assert_no_error (error); g_assert_true (ret); fu_engine_add_plugin (engine, plugin); ret = fu_plugin_runner_startup (plugin, &error); g_assert_no_error (error); g_assert_true (ret); devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_signal_connect (plugin, "device-added", G_CALLBACK (_plugin_composite_device_added_cb), devices); ret = fu_plugin_runner_coldplug (plugin, &error); g_assert_no_error (error); g_assert_true (ret); /* check we found all composite devices */ g_assert_cmpint (devices->len, ==, 3); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); fu_engine_add_device (engine, device); if (g_strcmp0 (fu_device_get_id (device), "08d460be0f1f9f128413f816022a6439e0078018") == 0) { g_assert_cmpstr (fu_device_get_version (device), ==, "1.2.2"); } else if (g_strcmp0 (fu_device_get_id (device), "c0a0a4aa6480ac28eea1ce164fbb466ca934e1ff") == 0) { g_assert_cmpstr (fu_device_get_version (device), ==, "1"); g_assert_nonnull (fu_device_get_parent (device)); } else if (g_strcmp0 (fu_device_get_id (device), "bf455e9f371d2608d1cb67660fd2b335d3f6ef73") == 0) { g_assert_cmpstr (fu_device_get_version (device), ==, "10"); g_assert_nonnull (fu_device_get_parent (device)); } } /* produce install tasks */ for (guint i = 0; i < components->len; i++) { XbNode *component = g_ptr_array_index (components, i); /* do any devices pass the requirements */ for (guint j = 0; j < devices->len; j++) { FuDevice *device = g_ptr_array_index (devices, j); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error_local = NULL; /* is this component valid for the device */ task = fu_install_task_new (device, component); if (!fu_engine_check_requirements (engine, task, 0, &error_local)) { g_debug ("requirement on %s:%s failed: %s", fu_device_get_id (device), xb_node_query_text (component, "id", NULL), error_local->message); continue; } g_ptr_array_add (install_tasks, g_steal_pointer (&task)); } } g_assert_cmpint (install_tasks->len, ==, 3); /* install the cab */ ret = fu_engine_install_tasks (engine, install_tasks, blob, FWUPD_DEVICE_FLAG_NONE, &error); g_assert_no_error (error); g_assert_true (ret); /* verify everything upgraded */ for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); const gchar *metadata; if (g_strcmp0 (fu_device_get_id (device), "08d460be0f1f9f128413f816022a6439e0078018") == 0) { g_assert_cmpstr (fu_device_get_version (device), ==, "1.2.3"); } else if (g_strcmp0 (fu_device_get_id (device), "c0a0a4aa6480ac28eea1ce164fbb466ca934e1ff") == 0) { g_assert_cmpstr (fu_device_get_version (device), ==, "2"); } else if (g_strcmp0 (fu_device_get_id (device), "bf455e9f371d2608d1cb67660fd2b335d3f6ef73") == 0) { g_assert_cmpstr (fu_device_get_version (device), ==, "11"); } /* verify prepare and cleanup ran on all devices */ metadata = fu_device_get_metadata (device, "frimbulator"); g_assert_cmpstr (metadata, ==, "1"); metadata = fu_device_get_metadata (device, "frombulator"); g_assert_cmpstr (metadata, ==, "1"); } } static void fu_common_store_cab_func (void) { GBytes *blob_tmp; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbNode) csum = NULL; g_autoptr(XbNode) rel = NULL; g_autoptr(XbNode) req = NULL; g_autoptr(XbSilo) silo = NULL; #if LIBXMLB_CHECK_VERSION(0,2,0) g_autoptr(XbQuery) query = NULL; #endif /* create silo */ blob = _build_cab (GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " ACME Firmware\n" " \n" " ae56e3fb-6528-5bc4-8b03-012f124075d7\n" " \n" " \n" " \n" " 5\n" " 7c211433f02071597741e6ff5a8ea34789abbf43\n" "

We fixed things

\n" "
\n" "
\n" " \n" " org.freedesktop.fwupd\n" " \n" "
", "firmware.dfu", "world", "firmware.dfu.asc", "signature", NULL); if (blob == NULL) { g_test_skip ("libgcab too old"); return; } silo = fu_common_cab_build_silo (blob, 10240, &error); g_assert_no_error (error); g_assert_nonnull (silo); /* verify */ component = xb_silo_query_first (silo, "components/component/id[text()='com.acme.example.firmware']/..", &error); g_assert_no_error (error); g_assert_nonnull (component); #if LIBXMLB_CHECK_VERSION(0,2,0) query = xb_query_new_full (xb_node_get_silo (component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, &error); g_assert_no_error (error); g_assert_nonnull (query); rel = xb_node_query_first_full (component, query, &error); #else rel = xb_node_query_first (component, "releases/release", &error); #endif g_assert_no_error (error); g_assert_nonnull (rel); g_assert_cmpstr (xb_node_get_attr (rel, "version"), ==, "1.2.3"); csum = xb_node_query_first (rel, "checksum[@target='content']", &error); g_assert_nonnull (csum); g_assert_cmpstr (xb_node_get_text (csum), ==, "7c211433f02071597741e6ff5a8ea34789abbf43"); blob_tmp = xb_node_get_data (rel, "fwupd::ReleaseBlob(firmware.dfu)"); g_assert_nonnull (blob_tmp); blob_tmp = xb_node_get_data (rel, "fwupd::ReleaseBlob(firmware.dfu.asc)"); g_assert_nonnull (blob_tmp); req = xb_node_query_first (component, "requires/id", &error); g_assert_no_error (error); g_assert_nonnull (req); } static void fu_common_store_cab_unsigned_func (void) { GBytes *blob_tmp; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbNode) csum = NULL; g_autoptr(XbNode) rel = NULL; g_autoptr(XbSilo) silo = NULL; #if LIBXMLB_CHECK_VERSION(0,2,0) g_autoptr(XbQuery) query = NULL; #endif /* create silo */ blob = _build_cab (GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" "", "firmware.bin", "world", NULL); if (blob == NULL) { g_test_skip ("libgcab too old"); return; } silo = fu_common_cab_build_silo (blob, 10240, &error); g_assert_no_error (error); g_assert_nonnull (silo); /* verify */ component = xb_silo_query_first (silo, "components/component/id[text()='com.acme.example.firmware']/..", &error); g_assert_no_error (error); g_assert_nonnull (component); #if LIBXMLB_CHECK_VERSION(0,2,0) query = xb_query_new_full (xb_node_get_silo (component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, &error); g_assert_no_error (error); g_assert_nonnull (query); rel = xb_node_query_first_full (component, query, &error); #else rel = xb_node_query_first (component, "releases/release", &error); #endif g_assert_no_error (error); g_assert_nonnull (rel); g_assert_cmpstr (xb_node_get_attr (rel, "version"), ==, "1.2.3"); csum = xb_node_query_first (rel, "checksum[@target='content']", &error); g_assert_null (csum); blob_tmp = xb_node_get_data (rel, "fwupd::ReleaseBlob(firmware.bin)"); g_assert_nonnull (blob_tmp); blob_tmp = xb_node_get_data (rel, "fwupd::ReleaseBlob(firmware.bin.asc)"); g_assert_null (blob_tmp); } static void fu_common_store_cab_folder_func (void) { GBytes *blob_tmp; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbNode) rel = NULL; g_autoptr(XbSilo) silo = NULL; #if LIBXMLB_CHECK_VERSION(0,2,0) g_autoptr(XbQuery) query = NULL; #endif /* create silo */ blob = _build_cab (GCAB_COMPRESSION_NONE, "lvfs\\acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" "", "lvfs\\firmware.bin", "world", NULL); if (blob == NULL) { g_test_skip ("libgcab too old"); return; } silo = fu_common_cab_build_silo (blob, 10240, &error); g_assert_no_error (error); g_assert_nonnull (silo); /* verify */ component = xb_silo_query_first (silo, "components/component/id[text()='com.acme.example.firmware']/..", &error); g_assert_no_error (error); g_assert_nonnull (component); #if LIBXMLB_CHECK_VERSION(0,2,0) query = xb_query_new_full (xb_node_get_silo (component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, &error); g_assert_no_error (error); g_assert_nonnull (query); rel = xb_node_query_first_full (component, query, &error); #else rel = xb_node_query_first (component, "releases/release", &error); #endif g_assert_no_error (error); g_assert_nonnull (rel); g_assert_cmpstr (xb_node_get_attr (rel, "version"), ==, "1.2.3"); blob_tmp = xb_node_get_data (rel, "fwupd::ReleaseBlob(firmware.bin)"); g_assert_nonnull (blob_tmp); } static void fu_common_store_cab_error_no_metadata_func (void) { g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; blob = _build_cab (GCAB_COMPRESSION_NONE, "foo.txt", "hello", "bar.txt", "world", NULL); if (blob == NULL) { g_test_skip ("libgcab too old"); return; } silo = fu_common_cab_build_silo (blob, 10240, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null (silo); } static void fu_common_store_cab_error_wrong_size_func (void) { g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; blob = _build_cab (GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " 7004701\n" " deadbeef\n" " \n" " \n" "", "firmware.bin", "world", NULL); if (blob == NULL) { g_test_skip ("libgcab too old"); return; } silo = fu_common_cab_build_silo (blob, 10240, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null (silo); } static void fu_common_store_cab_error_missing_file_func (void) { g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; blob = _build_cab (GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" " \n" " \n" "", "firmware.bin", "world", NULL); if (blob == NULL) { g_test_skip ("libgcab too old"); return; } silo = fu_common_cab_build_silo (blob, 10240, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null (silo); } static void fu_common_store_cab_error_size_func (void) { g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; blob = _build_cab (GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" "", "firmware.bin", "world", NULL); if (blob == NULL) { g_test_skip ("libgcab too old"); return; } silo = fu_common_cab_build_silo (blob, 123, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null (silo); } static void fu_common_store_cab_error_wrong_checksum_func (void) { g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; blob = _build_cab (GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " deadbeef\n" " \n" " \n" "", "firmware.bin", "world", NULL); if (blob == NULL) { g_test_skip ("libgcab too old"); return; } silo = fu_common_cab_build_silo (blob, 10240, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null (silo); } static gboolean fu_device_poll_cb (FuDevice *device, GError **error) { guint64 cnt = fu_device_get_metadata_integer (device, "cnt"); g_debug ("poll cnt=%" G_GUINT64_FORMAT, cnt); fu_device_set_metadata_integer (device, "cnt", cnt + 1); return TRUE; } static void fu_device_poll_func (void) { g_autoptr(FuDevice) device = fu_device_new (); FuDeviceClass *klass = FU_DEVICE_GET_CLASS (device); guint cnt; /* set up a 10ms poll */ klass->poll = fu_device_poll_cb; fu_device_set_metadata_integer (device, "cnt", 0); fu_device_set_poll_interval (device, 10); fu_test_loop_run_with_timeout (100); fu_test_loop_quit (); cnt = fu_device_get_metadata_integer (device, "cnt"); g_assert_cmpint (cnt, >=, 8); /* disable the poll */ fu_device_set_poll_interval (device, 0); fu_test_loop_run_with_timeout (100); fu_test_loop_quit (); g_assert_cmpint (fu_device_get_metadata_integer (device, "cnt"), ==, cnt); } static void fu_device_incorporate_func (void) { g_autoptr(FuDevice) device = fu_device_new (); g_autoptr(FuDevice) donor = fu_device_new (); /* set up donor device */ fu_device_set_alternate_id (donor, "alt-id"); fu_device_set_equivalent_id (donor, "equiv-id"); fu_device_set_metadata (donor, "test", "me"); fu_device_set_metadata (donor, "test2", "me"); /* base properties */ fu_device_add_flag (donor, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_set_created (donor, 123); fu_device_set_modified (donor, 456); fu_device_add_icon (donor, "computer"); /* existing properties */ fu_device_set_equivalent_id (device, "DO_NOT_OVERWRITE"); fu_device_set_metadata (device, "test2", "DO_NOT_OVERWRITE"); fu_device_set_modified (device, 789); /* incorporate properties from donor to device */ fu_device_incorporate (device, donor); g_assert_cmpstr (fu_device_get_alternate_id (device), ==, "alt-id"); g_assert_cmpstr (fu_device_get_equivalent_id (device), ==, "DO_NOT_OVERWRITE"); g_assert_cmpstr (fu_device_get_metadata (device, "test"), ==, "me"); g_assert_cmpstr (fu_device_get_metadata (device, "test2"), ==, "DO_NOT_OVERWRITE"); g_assert_true (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REQUIRE_AC)); g_assert_cmpint (fu_device_get_created (device), ==, 123); g_assert_cmpint (fu_device_get_modified (device), ==, 789); g_assert_cmpint (fu_device_get_icons(device)->len, ==, 1); } static void fu_chunk_func (void) { g_autofree gchar *chunked1_str = NULL; g_autofree gchar *chunked2_str = NULL; g_autofree gchar *chunked3_str = NULL; g_autofree gchar *chunked4_str = NULL; g_autoptr(GPtrArray) chunked1 = NULL; g_autoptr(GPtrArray) chunked2 = NULL; g_autoptr(GPtrArray) chunked3 = NULL; g_autoptr(GPtrArray) chunked4 = NULL; chunked3 = fu_chunk_array_new ((const guint8 *) "123456", 6, 0x0, 3, 3); chunked3_str = fu_chunk_array_to_string (chunked3); g_print ("\n%s", chunked3_str); g_assert_cmpstr (chunked3_str, ==, "#00: page:00 addr:0000 len:03 123\n" "#01: page:01 addr:0000 len:03 456\n"); chunked4 = fu_chunk_array_new ((const guint8 *) "123456", 6, 0x4, 4, 4); chunked4_str = fu_chunk_array_to_string (chunked4); g_print ("\n%s", chunked4_str); g_assert_cmpstr (chunked4_str, ==, "#00: page:01 addr:0000 len:04 1234\n" "#01: page:02 addr:0000 len:02 56\n"); chunked1 = fu_chunk_array_new ((const guint8 *) "0123456789abcdef", 16, 0x0, 10, 4); chunked1_str = fu_chunk_array_to_string (chunked1); g_print ("\n%s", chunked1_str); g_assert_cmpstr (chunked1_str, ==, "#00: page:00 addr:0000 len:04 0123\n" "#01: page:00 addr:0004 len:04 4567\n" "#02: page:00 addr:0008 len:02 89\n" "#03: page:01 addr:0000 len:04 abcd\n" "#04: page:01 addr:0004 len:02 ef\n"); chunked2 = fu_chunk_array_new ((const guint8 *) "XXXXXXYYYYYYZZZZZZ", 18, 0x0, 6, 4); chunked2_str = fu_chunk_array_to_string (chunked2); g_print ("\n%s", chunked2_str); g_assert_cmpstr (chunked2_str, ==, "#00: page:00 addr:0000 len:04 XXXX\n" "#01: page:00 addr:0004 len:02 XX\n" "#02: page:01 addr:0000 len:04 YYYY\n" "#03: page:01 addr:0004 len:02 YY\n" "#04: page:02 addr:0000 len:04 ZZZZ\n" "#05: page:02 addr:0004 len:02 ZZ\n"); } static void fu_common_strstrip_func (void) { struct { const gchar *old; const gchar *new; } map[] = { { "same", "same" }, { " leading", "leading" }, { "tailing ", "tailing" }, { " b ", "b" }, { " ", "" }, { NULL, NULL } }; for (guint i = 0; map[i].old != NULL; i++) { g_autofree gchar *tmp = fu_common_strstrip (map[i].old); g_assert_cmpstr (tmp, ==, map[i].new); } } static void fu_common_version_func (void) { guint i; struct { guint32 val; const gchar *ver; FwupdVersionFormat flags; } version_from_uint32[] = { { 0x0, "0.0.0.0", FWUPD_VERSION_FORMAT_QUAD }, { 0xff, "0.0.0.255", FWUPD_VERSION_FORMAT_QUAD }, { 0xff01, "0.0.255.1", FWUPD_VERSION_FORMAT_QUAD }, { 0xff0001, "0.255.0.1", FWUPD_VERSION_FORMAT_QUAD }, { 0xff000100, "255.0.1.0", FWUPD_VERSION_FORMAT_QUAD }, { 0x0, "0.0.0", FWUPD_VERSION_FORMAT_TRIPLET }, { 0xff, "0.0.255", FWUPD_VERSION_FORMAT_TRIPLET }, { 0xff01, "0.0.65281", FWUPD_VERSION_FORMAT_TRIPLET }, { 0xff0001, "0.255.1", FWUPD_VERSION_FORMAT_TRIPLET }, { 0xff000100, "255.0.256", FWUPD_VERSION_FORMAT_TRIPLET }, { 0x0, "0", FWUPD_VERSION_FORMAT_NUMBER }, { 0xff000100, "4278190336", FWUPD_VERSION_FORMAT_NUMBER }, { 0x0, "11.0.0.0", FWUPD_VERSION_FORMAT_INTEL_ME }, { 0xffffffff, "18.31.255.65535", FWUPD_VERSION_FORMAT_INTEL_ME }, { 0x0b32057a, "11.11.50.1402", FWUPD_VERSION_FORMAT_INTEL_ME }, { 0xb8320d84, "11.8.50.3460", FWUPD_VERSION_FORMAT_INTEL_ME2 }, { 0, NULL } }; struct { guint16 val; const gchar *ver; FwupdVersionFormat flags; } version_from_uint16[] = { { 0x0, "0.0", FWUPD_VERSION_FORMAT_PAIR }, { 0xff, "0.255", FWUPD_VERSION_FORMAT_PAIR }, { 0xff01, "255.1", FWUPD_VERSION_FORMAT_PAIR }, { 0x0, "0.0", FWUPD_VERSION_FORMAT_BCD }, { 0x0110, "1.10", FWUPD_VERSION_FORMAT_BCD }, { 0x9999, "99.99", FWUPD_VERSION_FORMAT_BCD }, { 0x0, "0", FWUPD_VERSION_FORMAT_NUMBER }, { 0x1234, "4660", FWUPD_VERSION_FORMAT_NUMBER }, { 0, NULL } }; struct { const gchar *old; const gchar *new; } version_parse[] = { { "0", "0" }, { "0x1a", "0.0.26" }, { "257", "0.0.257" }, { "1.2.3", "1.2.3" }, { "0xff0001", "0.255.1" }, { "16711681", "0.255.1" }, { "20150915", "20150915" }, { "dave", "dave" }, { "0x1x", "0x1x" }, { NULL, NULL } }; /* check version conversion */ for (i = 0; version_from_uint32[i].ver != NULL; i++) { g_autofree gchar *ver = NULL; ver = fu_common_version_from_uint32 (version_from_uint32[i].val, version_from_uint32[i].flags); g_assert_cmpstr (ver, ==, version_from_uint32[i].ver); } for (i = 0; version_from_uint16[i].ver != NULL; i++) { g_autofree gchar *ver = NULL; ver = fu_common_version_from_uint16 (version_from_uint16[i].val, version_from_uint16[i].flags); g_assert_cmpstr (ver, ==, version_from_uint16[i].ver); } /* check version parsing */ for (i = 0; version_parse[i].old != NULL; i++) { g_autofree gchar *ver = NULL; ver = fu_common_version_parse (version_parse[i].old); g_assert_cmpstr (ver, ==, version_parse[i].new); } } static void fu_common_vercmp_func (void) { /* same */ g_assert_cmpint (fu_common_vercmp ("1.2.3", "1.2.3"), ==, 0); g_assert_cmpint (fu_common_vercmp ("001.002.003", "001.002.003"), ==, 0); /* upgrade and downgrade */ g_assert_cmpint (fu_common_vercmp ("1.2.3", "1.2.4"), <, 0); g_assert_cmpint (fu_common_vercmp ("001.002.000", "001.002.009"), <, 0); g_assert_cmpint (fu_common_vercmp ("1.2.3", "1.2.2"), >, 0); g_assert_cmpint (fu_common_vercmp ("001.002.009", "001.002.000"), >, 0); /* unequal depth */ g_assert_cmpint (fu_common_vercmp ("1.2.3", "1.2.3.1"), <, 0); g_assert_cmpint (fu_common_vercmp ("1.2.3.1", "1.2.4"), <, 0); /* mixed-alpha-numeric */ g_assert_cmpint (fu_common_vercmp ("1.2.3a", "1.2.3a"), ==, 0); g_assert_cmpint (fu_common_vercmp ("1.2.3a", "1.2.3b"), <, 0); g_assert_cmpint (fu_common_vercmp ("1.2.3b", "1.2.3a"), >, 0); /* alpha version append */ g_assert_cmpint (fu_common_vercmp ("1.2.3", "1.2.3a"), <, 0); g_assert_cmpint (fu_common_vercmp ("1.2.3a", "1.2.3"), >, 0); /* alpha only */ g_assert_cmpint (fu_common_vercmp ("alpha", "alpha"), ==, 0); g_assert_cmpint (fu_common_vercmp ("alpha", "beta"), <, 0); g_assert_cmpint (fu_common_vercmp ("beta", "alpha"), >, 0); /* alpha-compare */ g_assert_cmpint (fu_common_vercmp ("1.2a.3", "1.2a.3"), ==, 0); g_assert_cmpint (fu_common_vercmp ("1.2a.3", "1.2b.3"), <, 0); g_assert_cmpint (fu_common_vercmp ("1.2b.3", "1.2a.3"), >, 0); /* tilde is all-powerful */ g_assert_cmpint (fu_common_vercmp ("1.2.3~rc1", "1.2.3~rc1"), ==, 0); g_assert_cmpint (fu_common_vercmp ("1.2.3~rc1", "1.2.3"), <, 0); g_assert_cmpint (fu_common_vercmp ("1.2.3", "1.2.3~rc1"), >, 0); g_assert_cmpint (fu_common_vercmp ("1.2.3~rc2", "1.2.3~rc1"), >, 0); /* invalid */ g_assert_cmpint (fu_common_vercmp ("1", NULL), ==, G_MAXINT); g_assert_cmpint (fu_common_vercmp (NULL, "1"), ==, G_MAXINT); g_assert_cmpint (fu_common_vercmp (NULL, NULL), ==, G_MAXINT); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); g_setenv ("FWUPD_DATADIR", TESTDATADIR_SRC, TRUE); g_setenv ("FWUPD_PLUGINDIR", TESTDATADIR_SRC, TRUE); g_setenv ("FWUPD_SYSCONFDIR", TESTDATADIR_SRC, TRUE); g_setenv ("FWUPD_SYSFSFWDIR", TESTDATADIR_SRC, TRUE); g_setenv ("FWUPD_LOCALSTATEDIR", "/tmp/fwupd-self-test/var", TRUE); /* ensure empty tree */ fu_self_test_mkroot (); /* tests go here */ if (g_test_slow ()) g_test_add_func ("/fwupd/progressbar", fu_progressbar_func); g_test_add_func ("/fwupd/archive{invalid}", fu_archive_invalid_func); g_test_add_func ("/fwupd/archive{cab}", fu_archive_cab_func); g_test_add_func ("/fwupd/engine{requirements-other-device}", fu_engine_requirements_other_device_func); g_test_add_func ("/fwupd/device{incorporate}", fu_device_incorporate_func); g_test_add_func ("/fwupd/device{poll}", fu_device_poll_func); g_test_add_func ("/fwupd/device-locker{success}", fu_device_locker_func); g_test_add_func ("/fwupd/device-locker{fail}", fu_device_locker_fail_func); g_test_add_func ("/fwupd/device{metadata}", fu_device_metadata_func); g_test_add_func ("/fwupd/device{open-refcount}", fu_device_open_refcount_func); g_test_add_func ("/fwupd/device{version-format}", fu_device_version_format_func); g_test_add_func ("/fwupd/device-list", fu_device_list_func); g_test_add_func ("/fwupd/device-list{delay}", fu_device_list_delay_func); g_test_add_func ("/fwupd/device-list{compatible}", fu_device_list_compatible_func); g_test_add_func ("/fwupd/device-list{remove-chain}", fu_device_list_remove_chain_func); g_test_add_func ("/fwupd/engine{device-unlock}", fu_engine_device_unlock_func); g_test_add_func ("/fwupd/engine{history-success}", fu_engine_history_func); g_test_add_func ("/fwupd/engine{history-error}", fu_engine_history_error_func); if (g_test_slow ()) g_test_add_func ("/fwupd/device-list{replug-auto}", fu_device_list_replug_auto_func); g_test_add_func ("/fwupd/device-list{replug-user}", fu_device_list_replug_user_func); g_test_add_func ("/fwupd/engine{require-hwid}", fu_engine_require_hwid_func); g_test_add_func ("/fwupd/engine{history-inherit}", fu_engine_history_inherit); g_test_add_func ("/fwupd/engine{partial-hash}", fu_engine_partial_hash_func); g_test_add_func ("/fwupd/engine{downgrade}", fu_engine_downgrade_func); g_test_add_func ("/fwupd/engine{requirements-success}", fu_engine_requirements_func); g_test_add_func ("/fwupd/engine{requirements-missing}", fu_engine_requirements_missing_func); g_test_add_func ("/fwupd/engine{requirements-not-child}", fu_engine_requirements_child_func); g_test_add_func ("/fwupd/engine{requirements-not-child-fail}", fu_engine_requirements_child_fail_func); g_test_add_func ("/fwupd/engine{requirements-unsupported}", fu_engine_requirements_unsupported_func); g_test_add_func ("/fwupd/engine{requirements-device}", fu_engine_requirements_device_func); g_test_add_func ("/fwupd/engine{requirements-version-format}", fu_engine_requirements_version_format_func); g_test_add_func ("/fwupd/engine{device-auto-parent}", fu_engine_device_parent_func); g_test_add_func ("/fwupd/engine{device-priority}", fu_engine_device_priority_func); g_test_add_func ("/fwupd/engine{install-duration}", fu_engine_install_duration_func); g_test_add_func ("/fwupd/engine{generate-md}", fu_engine_generate_md_func); g_test_add_func ("/fwupd/hwids", fu_hwids_func); g_test_add_func ("/fwupd/smbios", fu_smbios_func); g_test_add_func ("/fwupd/smbios3", fu_smbios3_func); g_test_add_func ("/fwupd/history", fu_history_func); g_test_add_func ("/fwupd/history{migrate}", fu_history_migrate_func); g_test_add_func ("/fwupd/plugin-list", fu_plugin_list_func); g_test_add_func ("/fwupd/plugin-list{depsolve}", fu_plugin_list_depsolve_func); g_test_add_func ("/fwupd/plugin{delay}", fu_plugin_delay_func); g_test_add_func ("/fwupd/plugin{module}", fu_plugin_module_func); g_test_add_func ("/fwupd/plugin{quirks}", fu_plugin_quirks_func); g_test_add_func ("/fwupd/plugin{quirks-performance}", fu_plugin_quirks_performance_func); g_test_add_func ("/fwupd/plugin{quirks-device}", fu_plugin_quirks_device_func); g_test_add_func ("/fwupd/plugin{composite}", fu_plugin_composite_func); g_test_add_func ("/fwupd/keyring{gpg}", fu_keyring_gpg_func); g_test_add_func ("/fwupd/keyring{pkcs7}", fu_keyring_pkcs7_func); g_test_add_func ("/fwupd/keyring{pkcs7-self-signed}", fu_keyring_pkcs7_self_signed_func); g_test_add_func ("/fwupd/plugin{build-hash}", fu_plugin_hash_func); g_test_add_func ("/fwupd/chunk", fu_chunk_func); g_test_add_func ("/fwupd/common{version-guess-format}", fu_common_version_guess_format_func); g_test_add_func ("/fwupd/common{version}", fu_common_version_func); g_test_add_func ("/fwupd/common{vercmp}", fu_common_vercmp_func); g_test_add_func ("/fwupd/common{strstrip}", fu_common_strstrip_func); g_test_add_func ("/fwupd/common{endian}", fu_common_endian_func); g_test_add_func ("/fwupd/common{cab-success}", fu_common_store_cab_func); g_test_add_func ("/fwupd/common{cab-success-unsigned}", fu_common_store_cab_unsigned_func); g_test_add_func ("/fwupd/common{cab-success-folder}", fu_common_store_cab_folder_func); g_test_add_func ("/fwupd/common{cab-error-no-metadata}", fu_common_store_cab_error_no_metadata_func); g_test_add_func ("/fwupd/common{cab-error-wrong-size}", fu_common_store_cab_error_wrong_size_func); g_test_add_func ("/fwupd/common{cab-error-wrong-checksum}", fu_common_store_cab_error_wrong_checksum_func); g_test_add_func ("/fwupd/common{cab-error-missing-file}", fu_common_store_cab_error_missing_file_func); g_test_add_func ("/fwupd/common{cab-error-size}", fu_common_store_cab_error_size_func); g_test_add_func ("/fwupd/common{spawn)", fu_common_spawn_func); g_test_add_func ("/fwupd/common{spawn-timeout)", fu_common_spawn_timeout_func); g_test_add_func ("/fwupd/common{firmware-builder}", fu_common_firmware_builder_func); return g_test_run (); } fwupd-1.2.14/src/fu-smbios.c000066400000000000000000000306111402665037500155740ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuSmbios" #include "config.h" #include #include #include "fu-common.h" #include "fu-smbios.h" #include "fwupd-error.h" struct _FuSmbios { GObject parent_instance; gchar *smbios_ver; guint32 structure_table_len; GPtrArray *items; }; /* little endian */ typedef struct __attribute__((packed)) { gchar anchor_str[4]; guint8 entry_point_csum; guint8 entry_point_len; guint8 smbios_major_ver; guint8 smbios_minor_ver; guint16 max_structure_sz; guint8 entry_point_rev; guint8 formatted_area[5]; gchar intermediate_anchor_str[5]; guint8 intermediate_csum; guint16 structure_table_len; guint32 structure_table_addr; guint16 number_smbios_structs; guint8 smbios_bcd_rev; } FuSmbiosStructureEntryPoint32; /* little endian */ typedef struct __attribute__((packed)) { gchar anchor_str[5]; guint8 entry_point_csum; guint8 entry_point_len; guint8 smbios_major_ver; guint8 smbios_minor_ver; guint8 smbios_docrev; guint8 entry_point_rev; guint8 reserved0; guint32 structure_table_len; guint64 structure_table_addr; } FuSmbiosStructureEntryPoint64; /* little endian */ typedef struct __attribute__((packed)) { guint8 type; guint8 len; guint16 handle; } FuSmbiosStructure; typedef struct { guint8 type; guint16 handle; GBytes *data; GPtrArray *strings; } FuSmbiosItem; G_DEFINE_TYPE (FuSmbios, fu_smbios, G_TYPE_OBJECT) static gboolean fu_smbios_setup_from_data (FuSmbios *self, const guint8 *buf, gsize sz, GError **error) { /* go through each structure */ for (gsize i = 0; i < sz; i++) { FuSmbiosStructure *str = (FuSmbiosStructure *) &buf[i]; FuSmbiosItem *item; /* invalid */ if (str->len == 0x00) break; if (str->len >= sz) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "structure larger than available data"); return FALSE; } /* create a new result */ item = g_new0 (FuSmbiosItem, 1); item->type = str->type; item->handle = GUINT16_FROM_LE (str->handle); item->data = g_bytes_new (buf + i, str->len); item->strings = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (self->items, item); /* jump to the end of the struct */ i += str->len; if (buf[i] == '\0' && buf[i+1] == '\0') { i++; continue; } /* add strings from table */ for (gsize start_offset = i; i < sz; i++) { if (buf[i] == '\0') { if (start_offset == i) break; g_ptr_array_add (item->strings, g_strdup ((const gchar *) &buf[start_offset])); start_offset = i + 1; } } } return TRUE; } /** * fu_smbios_setup_from_file: * @self: A #FuSmbios * @filename: A filename * @error: A #GError or %NULL * * Reads all the SMBIOS values from a DMI blob. * * Returns: %TRUE for success **/ gboolean fu_smbios_setup_from_file (FuSmbios *self, const gchar *filename, GError **error) { gsize sz = 0; g_autofree gchar *buf = NULL; if (!g_file_get_contents (filename, &buf, &sz, error)) return FALSE; return fu_smbios_setup_from_data (self, (guint8 *) buf, sz, error); } static gboolean fu_smbios_parse_ep32 (FuSmbios *self, const gchar *buf, gsize sz, GError **error) { FuSmbiosStructureEntryPoint32 *ep; guint8 csum = 0; /* verify size */ if (sz != sizeof(FuSmbiosStructureEntryPoint32)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid smbios entry point got %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT, sz, sizeof(FuSmbiosStructureEntryPoint32)); return FALSE; } /* verify checksum */ for (guint i = 0; i < sz; i++) csum += buf[i]; if (csum != 0x00) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "entry point checksum invalid"); return FALSE; } /* verify intermediate section */ ep = (FuSmbiosStructureEntryPoint32 *) buf; if (memcmp (ep->intermediate_anchor_str, "_DMI_", 5) != 0) { g_autofree gchar *tmp = g_strndup (ep->intermediate_anchor_str, 5); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "intermediate anchor signature invalid, got %s", tmp); return FALSE; } for (guint i = 10; i < sz; i++) csum += buf[i]; if (csum != 0x00) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "intermediate checksum invalid"); return FALSE; } self->structure_table_len = GUINT16_FROM_LE (ep->structure_table_len); self->smbios_ver = g_strdup_printf ("%u.%u", ep->smbios_major_ver, ep->smbios_minor_ver); return TRUE; } static gboolean fu_smbios_parse_ep64 (FuSmbios *self, const gchar *buf, gsize sz, GError **error) { FuSmbiosStructureEntryPoint64 *ep; guint8 csum = 0; /* verify size */ if (sz != sizeof(FuSmbiosStructureEntryPoint64)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid smbios3 entry point got %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT, sz, sizeof(FuSmbiosStructureEntryPoint32)); return FALSE; } /* verify checksum */ for (guint i = 0; i < sz; i++) csum += buf[i]; if (csum != 0x00) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "entry point checksum invalid"); return FALSE; } ep = (FuSmbiosStructureEntryPoint64 *) buf; self->structure_table_len = GUINT32_FROM_LE (ep->structure_table_len); self->smbios_ver = g_strdup_printf ("%u.%u", ep->smbios_major_ver, ep->smbios_minor_ver); return TRUE; } /** * fu_smbios_setup: * @self: A #FuSmbios * @path: A path, e.g. `/sys/firmware/dmi/tables` * @error: A #GError or %NULL * * Reads all the SMBIOS values from a specific path. * * Returns: %TRUE for success **/ gboolean fu_smbios_setup_from_path (FuSmbios *self, const gchar *path, GError **error) { gsize sz = 0; g_autofree gchar *dmi_fn = NULL; g_autofree gchar *dmi_raw = NULL; g_autofree gchar *ep_fn = NULL; g_autofree gchar *ep_raw = NULL; g_return_val_if_fail (FU_IS_SMBIOS (self), FALSE); /* get the smbios entry point */ ep_fn = g_build_filename (path, "smbios_entry_point", NULL); if (!g_file_get_contents (ep_fn, &ep_raw, &sz, error)) return FALSE; /* check we got enough data to read the signature */ if (sz < 5) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid smbios entry point got %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT " or %" G_GSIZE_FORMAT, sz, sizeof(FuSmbiosStructureEntryPoint32), sizeof(FuSmbiosStructureEntryPoint64)); return FALSE; } /* parse 32 bit structure */ if (memcmp (ep_raw, "_SM_", 4) == 0) { if (!fu_smbios_parse_ep32 (self, ep_raw, sz, error)) return FALSE; } else if (memcmp (ep_raw, "_SM3_", 5) == 0) { if (!fu_smbios_parse_ep64 (self, ep_raw, sz, error)) return FALSE; } else { g_autofree gchar *tmp = g_strndup (ep_raw, 4); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "SMBIOS signature invalid, got %s", tmp); return FALSE; } /* get the DMI data */ dmi_fn = g_build_filename (path, "DMI", NULL); if (!g_file_get_contents (dmi_fn, &dmi_raw, &sz, error)) return FALSE; if (sz != self->structure_table_len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid DMI data size, got %" G_GSIZE_FORMAT " bytes, expected %" G_GUINT32_FORMAT, sz, self->structure_table_len); return FALSE; } /* parse blob */ return fu_smbios_setup_from_data (self, (guint8 *) dmi_raw, sz, error); } /** * fu_smbios_setup: * @self: A #FuSmbios * @error: A #GError or %NULL * * Reads all the SMBIOS values from the hardware. * * Returns: %TRUE for success **/ gboolean fu_smbios_setup (FuSmbios *self, GError **error) { g_autofree gchar *path = NULL; g_autofree gchar *sysfsfwdir = NULL; g_return_val_if_fail (FU_IS_SMBIOS (self), FALSE); sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); path = g_build_filename (sysfsfwdir, "dmi", "tables", NULL); return fu_smbios_setup_from_path (self, path, error); } /** * fu_smbios_to_string: * @self: A #FuSmbios * * Dumps the parsed SMBIOS data to a string. * * Returns: a UTF-8 string **/ gchar * fu_smbios_to_string (FuSmbios *self) { GString *str; g_return_val_if_fail (FU_IS_SMBIOS (self), NULL); str = g_string_new (NULL); g_string_append_printf (str, "SmbiosVersion: %s\n", self->smbios_ver); for (guint i = 0; i < self->items->len; i++) { FuSmbiosItem *item = g_ptr_array_index (self->items, i); g_string_append_printf (str, "Type: %02x\n", item->type); g_string_append_printf (str, " Length: %" G_GSIZE_FORMAT "\n", g_bytes_get_size (item->data)); g_string_append_printf (str, " Handle: 0x%04x\n", item->handle); for (guint j = 0; j < item->strings->len; j++) { const gchar *tmp = g_ptr_array_index (item->strings, j); g_string_append_printf (str, " String[%02u]: %s\n", j, tmp); } } return g_string_free (str, FALSE); } static FuSmbiosItem * fu_smbios_get_item_for_type (FuSmbios *self, guint8 type) { for (guint i = 0; i < self->items->len; i++) { FuSmbiosItem *item = g_ptr_array_index (self->items, i); if (item->type == type) return item; } return NULL; } /** * fu_smbios_get_data: * @self: A #FuSmbios * @type: A structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * @error: A #GError or %NULL * * Reads a SMBIOS data blob, which includes the SMBIOS section header. * * Returns: (transfer full): a #GBytes, or %NULL if invalid or not found **/ GBytes * fu_smbios_get_data (FuSmbios *self, guint8 type, GError **error) { FuSmbiosItem *item; g_return_val_if_fail (FU_IS_SMBIOS (self), NULL); item = fu_smbios_get_item_for_type (self, type); if (item == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no structure with type %02x", type); return NULL; } return g_bytes_ref (item->data); } /** * fu_smbios_get_string: * @self: A #FuSmbios * @type: A structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * @offset: A structure offset * @error: A #GError or %NULL * * Reads a string from the SMBIOS string table of a specific structure. * * The @type and @offset can be referenced from the DMTF SMBIOS specification: * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf * * Returns: a string, or %NULL if invalid or not found **/ const gchar * fu_smbios_get_string (FuSmbios *self, guint8 type, guint8 offset, GError **error) { FuSmbiosItem *item; const guint8 *data; gsize sz; g_return_val_if_fail (FU_IS_SMBIOS (self), NULL); /* get item */ item = fu_smbios_get_item_for_type (self, type); if (item == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no structure with type %02x", type); return NULL; } /* check offset valid */ data = g_bytes_get_data (item->data, &sz); if (offset >= sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "offset bigger than size %" G_GSIZE_FORMAT, sz); return NULL; } if (data[offset] == 0x00) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no data available"); return NULL; } /* check string index valid */ if (data[offset] > item->strings->len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "index larger than string table %u", data[offset]); return NULL; } return g_ptr_array_index (item->strings, data[offset] - 1); } static void fu_smbios_item_free (FuSmbiosItem *item) { g_bytes_unref (item->data); g_ptr_array_unref (item->strings); g_free (item); } static void fu_smbios_finalize (GObject *object) { FuSmbios *self = FU_SMBIOS (object); g_free (self->smbios_ver); g_ptr_array_unref (self->items); G_OBJECT_CLASS (fu_smbios_parent_class)->finalize (object); } static void fu_smbios_class_init (FuSmbiosClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_smbios_finalize; } static void fu_smbios_init (FuSmbios *self) { self->items = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_smbios_item_free); } /** * fu_smbios_new: * * Creates a new object to parse SMBIOS data. * * Returns: a #FuSmbios **/ FuSmbios * fu_smbios_new (void) { FuSmbios *self; self = g_object_new (FU_TYPE_SMBIOS, NULL); return FU_SMBIOS (self); } fwupd-1.2.14/src/fu-smbios.h000066400000000000000000000020361402665037500156010ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FU_TYPE_SMBIOS (fu_smbios_get_type ()) G_DECLARE_FINAL_TYPE (FuSmbios, fu_smbios, FU, SMBIOS, GObject) FuSmbios *fu_smbios_new (void); #define FU_SMBIOS_STRUCTURE_TYPE_BIOS 0x00 #define FU_SMBIOS_STRUCTURE_TYPE_SYSTEM 0x01 #define FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD 0x02 #define FU_SMBIOS_STRUCTURE_TYPE_CHASSIS 0x03 gboolean fu_smbios_setup (FuSmbios *self, GError **error); gboolean fu_smbios_setup_from_path (FuSmbios *self, const gchar *path, GError **error); gboolean fu_smbios_setup_from_file (FuSmbios *self, const gchar *filename, GError **error); gchar *fu_smbios_to_string (FuSmbios *self); const gchar *fu_smbios_get_string (FuSmbios *self, guint8 type, guint8 offset, GError **error); GBytes *fu_smbios_get_data (FuSmbios *self, guint8 type, GError **error); G_END_DECLS fwupd-1.2.14/src/fu-systemd.c000066400000000000000000000114101402665037500157640ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include "fu-systemd.h" #define SYSTEMD_SERVICE "org.freedesktop.systemd1" #define SYSTEMD_OBJECT_PATH "/org/freedesktop/systemd1" #define SYSTEMD_INTERFACE "org.freedesktop.systemd1" #define SYSTEMD_MANAGER_INTERFACE "org.freedesktop.systemd1.Manager" static GDBusProxy * fu_systemd_get_manager (GError **error) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GDBusProxy) proxy = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) { g_prefix_error (error, "failed to get bus: "); return NULL; } proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, NULL, SYSTEMD_SERVICE, SYSTEMD_OBJECT_PATH, SYSTEMD_MANAGER_INTERFACE, NULL, error); if (proxy == NULL) { g_prefix_error (error, "failed to find %s: ", SYSTEMD_SERVICE); return NULL; } return g_steal_pointer (&proxy); } static gchar * fu_systemd_unit_get_path (GDBusProxy *proxy_manager, const gchar *unit, GError **error) { g_autofree gchar *path = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_sync (proxy_manager, "GetUnit", g_variant_new ("(s)", unit), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (val == NULL) { g_prefix_error (error, "failed to find %s: ", unit); return NULL; } g_variant_get (val, "(o)", &path); return g_steal_pointer (&path); } static GDBusProxy * fu_systemd_unit_get_proxy (GDBusProxy *proxy_manager, const gchar *unit, GError **error) { g_autofree gchar *path = NULL; g_autoptr(GDBusProxy) proxy_unit = NULL; path = fu_systemd_unit_get_path (proxy_manager, unit, error); if (path == NULL) return NULL; proxy_unit = g_dbus_proxy_new_sync (g_dbus_proxy_get_connection (proxy_manager), G_DBUS_PROXY_FLAGS_NONE, NULL, SYSTEMD_SERVICE, path, SYSTEMD_INTERFACE, NULL, error); if (proxy_unit == NULL) { g_prefix_error (error, "failed to register proxy for %s: ", path); return NULL; } return g_steal_pointer (&proxy_unit); } gchar * fu_systemd_get_default_target (GError **error) { const gchar *path = NULL; g_autoptr(GDBusProxy) proxy_manager = NULL; g_autoptr(GVariant) val = NULL; proxy_manager = fu_systemd_get_manager (error); if (proxy_manager == NULL) return NULL; val = g_dbus_proxy_call_sync (proxy_manager, "GetDefaultTarget", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (val == NULL) return NULL; g_variant_get (val, "(&s)", &path); return g_strdup (path); } gboolean fu_systemd_unit_stop (const gchar *unit, GError **error) { g_autoptr(GDBusProxy) proxy_manager = NULL; g_autoptr(GDBusProxy) proxy_unit = NULL; g_autoptr(GVariant) val = NULL; g_return_val_if_fail (unit != NULL, FALSE); proxy_manager = fu_systemd_get_manager (error); if (proxy_manager == NULL) return FALSE; proxy_unit = fu_systemd_unit_get_proxy (proxy_manager, unit, error); if (proxy_unit == NULL) return FALSE; val = g_dbus_proxy_call_sync (proxy_unit, "StopUnit", g_variant_new ("(ss)", unit, "replace"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); return val != NULL; } gboolean fu_systemd_unit_enable (const gchar *unit, GError **error) { const gchar *units[] = { unit, NULL }; g_autoptr(GDBusProxy) proxy_manager = NULL; g_autoptr(GVariant) val = NULL; g_return_val_if_fail (unit != NULL, FALSE); proxy_manager = fu_systemd_get_manager (error); if (proxy_manager == NULL) return FALSE; val = g_dbus_proxy_call_sync (proxy_manager, "EnableUnitFiles", g_variant_new ("(^asbb)", units, TRUE, TRUE), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); return val != NULL; } gboolean fu_systemd_unit_disable (const gchar *unit, GError **error) { const gchar *units[] = { unit, NULL }; g_autoptr(GDBusProxy) proxy_manager = NULL; g_autoptr(GVariant) val = NULL; g_return_val_if_fail (unit != NULL, FALSE); proxy_manager = fu_systemd_get_manager (error); if (proxy_manager == NULL) return FALSE; val = g_dbus_proxy_call_sync (proxy_manager, "DisableUnitFiles", g_variant_new ("(^asb)", units, TRUE), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); return val != NULL; } gboolean fu_systemd_unit_check_exists (const gchar *unit, GError **error) { g_autoptr(GDBusProxy) proxy_manager = NULL; g_autofree gchar *path = NULL; g_return_val_if_fail (unit != NULL, FALSE); proxy_manager = fu_systemd_get_manager (error); if (proxy_manager == NULL) return FALSE; path = fu_systemd_unit_get_path (proxy_manager, unit, error); return path != NULL; } fwupd-1.2.14/src/fu-systemd.h000066400000000000000000000010461402665037500157750ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS gboolean fu_systemd_unit_check_exists (const gchar *unit, GError **error); gboolean fu_systemd_unit_stop (const gchar *unit, GError **error); gboolean fu_systemd_unit_enable (const gchar *unit, GError **error); gboolean fu_systemd_unit_disable (const gchar *unit, GError **error); gchar *fu_systemd_get_default_target (GError **error); G_END_DECLS fwupd-1.2.14/src/fu-test.c000066400000000000000000000040641402665037500152620ustar00rootroot00000000000000/* * Copyright (C) 2010-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-test.h" static GMainLoop *_test_loop = NULL; static guint _test_loop_timeout_id = 0; static gboolean fu_test_hang_check_cb (gpointer user_data) { g_main_loop_quit (_test_loop); _test_loop_timeout_id = 0; return G_SOURCE_REMOVE; } void fu_test_loop_run_with_timeout (guint timeout_ms) { g_assert (_test_loop_timeout_id == 0); g_assert (_test_loop == NULL); _test_loop = g_main_loop_new (NULL, FALSE); _test_loop_timeout_id = g_timeout_add (timeout_ms, fu_test_hang_check_cb, NULL); g_main_loop_run (_test_loop); } void fu_test_loop_quit (void) { if (_test_loop_timeout_id > 0) { g_source_remove (_test_loop_timeout_id); _test_loop_timeout_id = 0; } if (_test_loop != NULL) { g_main_loop_quit (_test_loop); g_main_loop_unref (_test_loop); _test_loop = NULL; } } gchar * fu_test_get_filename (const gchar *testdatadirs, const gchar *filename) { g_auto(GStrv) split = g_strsplit (testdatadirs, ":", -1); for (guint i = 0; split[i] != NULL; i++) { gchar *tmp; char full_tmp[PATH_MAX]; g_autofree gchar *path = NULL; path = g_build_filename (split[i], filename, NULL); tmp = realpath (path, full_tmp); if (tmp != NULL) return g_strdup (full_tmp); } return NULL; } gboolean fu_test_compare_lines (const gchar *txt1, const gchar *txt2, GError **error) { g_autofree gchar *output = NULL; /* exactly the same */ if (g_strcmp0 (txt1, txt2) == 0) return TRUE; /* matches a pattern */ if (fnmatch (txt2, txt1, FNM_NOESCAPE) == 0) return TRUE; /* save temp files and diff them */ if (!g_file_set_contents ("/tmp/a", txt1, -1, error)) return FALSE; if (!g_file_set_contents ("/tmp/b", txt2, -1, error)) return FALSE; if (!g_spawn_command_line_sync ("diff -urNp /tmp/b /tmp/a", &output, NULL, NULL, error)) return FALSE; /* just output the diff */ g_set_error_literal (error, 1, 0, output); return FALSE; } fwupd-1.2.14/src/fu-test.h000066400000000000000000000007141402665037500152650ustar00rootroot00000000000000/* * Copyright (C) 2010-2011 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS gchar *fu_test_get_filename (const gchar *testdatadirs, const gchar *filename); void fu_test_loop_run_with_timeout (guint timeout_ms); void fu_test_loop_quit (void); gboolean fu_test_compare_lines (const gchar *txt1, const gchar *txt2, GError **error); G_END_DECLS fwupd-1.2.14/src/fu-tool.c000066400000000000000000001324701402665037500152630ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include #include #include #include #include #include #include "fu-device-private.h" #include "fu-engine.h" #include "fu-history.h" #include "fu-plugin-private.h" #include "fu-progressbar.h" #include "fu-smbios.h" #include "fu-util-common.h" #include "fu-debug.h" #include "fwupd-common-private.h" #include "fwupd-device-private.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #endif /* custom return code */ #define EXIT_NOTHING_TO_DO 2 typedef enum { FU_UTIL_OPERATION_UNKNOWN, FU_UTIL_OPERATION_UPDATE, FU_UTIL_OPERATION_INSTALL, FU_UTIL_OPERATION_LAST } FuUtilOperation; struct FuUtilPrivate { GCancellable *cancellable; GMainLoop *loop; GOptionContext *context; FuEngine *engine; FuProgressbar *progressbar; gboolean no_reboot_check; gboolean prepare_blob; gboolean cleanup_blob; gboolean enable_json_state; FwupdInstallFlags flags; gboolean show_all_devices; /* only valid in update and downgrade */ FuUtilOperation current_operation; FwupdDevice *current_device; gchar *current_message; FwupdDeviceFlags completion_flags; }; static gboolean fu_util_save_current_state (FuUtilPrivate *priv, GError **error) { g_autoptr(JsonBuilder) builder = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonNode) json_root = NULL; g_autoptr(GPtrArray) devices = NULL; g_autofree gchar *state = NULL; g_autofree gchar *dirname = NULL; g_autofree gchar *filename = NULL; if (!priv->enable_json_state) return TRUE; devices = fu_engine_get_devices (priv->engine, error); if (devices == NULL) return FALSE; /* create header */ builder = json_builder_new (); json_builder_begin_object (builder); /* add each device */ json_builder_set_member_name (builder, "Devices"); json_builder_begin_array (builder); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); json_builder_begin_object (builder); fwupd_device_to_json (dev, builder); json_builder_end_object (builder); } json_builder_end_array (builder); json_builder_end_object (builder); /* export as a string */ json_root = json_builder_get_root (builder); json_generator = json_generator_new (); json_generator_set_pretty (json_generator, TRUE); json_generator_set_root (json_generator, json_root); state = json_generator_to_data (json_generator, NULL); if (state == NULL) return FALSE; dirname = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); filename = g_build_filename (dirname, "state.json", NULL); return g_file_set_contents (filename, state, -1, error); } static gboolean fu_util_start_engine (FuUtilPrivate *priv, FuEngineLoadFlags flags, GError **error) { g_autoptr(GError) error_local = NULL; #ifdef HAVE_SYSTEMD if (!fu_systemd_unit_stop (fu_util_get_systemd_unit (), &error_local)) g_debug ("Failed top stop daemon: %s", error_local->message); #endif if (!fu_engine_load (priv->engine, flags, error)) return FALSE; if (fu_engine_get_tainted (priv->engine)) { g_printerr ("WARNING: This tool has loaded 3rd party code and " "is no longer supported by the upstream developers!\n"); } return TRUE; } static void fu_util_maybe_prefix_sandbox_error (const gchar *value, GError **error) { g_autofree gchar *path = g_path_get_dirname (value); if (!g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { g_prefix_error (error, "Unable to access %s. You may need to copy %s to %s: ", path, value, g_getenv ("HOME")); } } static void fu_util_cancelled_cb (GCancellable *cancellable, gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *) user_data; /* TRANSLATORS: this is when a device ctrl+c's a watch */ g_print ("%s\n", _("Cancelled")); g_main_loop_quit (priv->loop); } static gboolean fu_util_smbios_dump (FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *tmp = NULL; g_autoptr(FuSmbios) smbios = NULL; if (g_strv_length (values) < 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } smbios = fu_smbios_new (); if (!fu_smbios_setup_from_file (smbios, values[0], error)) return FALSE; tmp = fu_smbios_to_string (smbios); g_print ("%s\n", tmp); return TRUE; } static gboolean fu_util_sigint_cb (gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *) user_data; g_debug ("Handling SIGINT"); g_cancellable_cancel (priv->cancellable); return FALSE; } static void fu_util_private_free (FuUtilPrivate *priv) { if (priv->current_device != NULL) g_object_unref (priv->current_device); if (priv->engine != NULL) g_object_unref (priv->engine); if (priv->loop != NULL) g_main_loop_unref (priv->loop); if (priv->cancellable != NULL) g_object_unref (priv->cancellable); if (priv->progressbar != NULL) g_object_unref (priv->progressbar); if (priv->context != NULL) g_option_context_free (priv->context); g_free (priv->current_message); g_free (priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free) #pragma clang diagnostic pop static void fu_main_engine_device_added_cb (FuEngine *engine, FuDevice *device, FuUtilPrivate *priv) { g_autofree gchar *tmp = fu_device_to_string (device); g_debug ("ADDED:\n%s", tmp); } static void fu_main_engine_device_removed_cb (FuEngine *engine, FuDevice *device, FuUtilPrivate *priv) { g_autofree gchar *tmp = fu_device_to_string (device); g_debug ("REMOVED:\n%s", tmp); } static void fu_main_engine_status_changed_cb (FuEngine *engine, FwupdStatus status, FuUtilPrivate *priv) { fu_progressbar_update (priv->progressbar, status, 0); } static void fu_main_engine_percentage_changed_cb (FuEngine *engine, guint percentage, FuUtilPrivate *priv) { fu_progressbar_update (priv->progressbar, FWUPD_STATUS_UNKNOWN, percentage); } static gboolean fu_util_watch (FuUtilPrivate *priv, gchar **values, GError **error) { if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; g_main_loop_run (priv->loop); return TRUE; } static gint fu_util_plugin_name_sort_cb (FuPlugin **item1, FuPlugin **item2) { return fu_plugin_name_compare (*item1, *item2); } static gboolean fu_util_get_plugins (FuUtilPrivate *priv, gchar **values, GError **error) { GPtrArray *plugins; guint cnt = 0; /* load engine */ if (!fu_engine_load_plugins (priv->engine, error)) return FALSE; /* print */ plugins = fu_engine_get_plugins (priv->engine); g_ptr_array_sort (plugins, (GCompareFunc) fu_util_plugin_name_sort_cb); for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index (plugins, i); if (!fu_plugin_get_enabled (plugin)) continue; g_print ("%s\n", fu_plugin_get_name (plugin)); cnt++; } if (cnt == 0) { /* TRANSLATORS: nothing found */ g_print ("%s\n", _("No plugins found")); return TRUE; } return TRUE; } static gboolean fu_util_get_updates (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* get devices from daemon */ devices = fu_engine_get_devices (priv->engine, error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; /* not going to have results, so save a D-Bus round-trip */ if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; /* get the releases for this device and filter for validity */ rels = fu_engine_get_upgrades (priv->engine, fwupd_device_get_id (dev), &error_local); if (rels == NULL) { g_printerr ("%s\n", error_local->message); continue; } g_print ("%s", fwupd_device_to_string (dev)); g_print (" Release information:\n"); /* print all releases */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel = g_ptr_array_index (rels, j); g_print ("%s\n", fwupd_release_to_string (rel)); } } /* save the device state for other applications to see */ if (!fu_util_save_current_state (priv, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_util_get_details (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) array = NULL; gint fd; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* check args */ if (g_strv_length (values) != 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* open file */ fd = open (values[0], O_RDONLY); if (fd < 0) { fu_util_maybe_prefix_sandbox_error (values[0], error); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to open %s", values[0]); return FALSE; } array = fu_engine_get_details (priv->engine, fd, error); close (fd); if (array == NULL) return FALSE; for (guint i = 0; i < array->len; i++) { FwupdDevice *dev = g_ptr_array_index (array, i); g_autofree gchar *tmp = NULL; tmp = fwupd_device_to_string (dev); g_print ("%s\n", tmp); } return TRUE; } static gboolean fu_util_get_devices (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devs = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* print */ devs = fu_engine_get_devices (priv->engine, error); if (devs == NULL) return FALSE; if (devs->len == 0) { /* TRANSLATORS: nothing attached */ g_print ("%s\n", _("No hardware detected with firmware update capability")); return TRUE; } for (guint i = 0; i < devs->len; i++) { FwupdDevice *dev = g_ptr_array_index (devs, i); if (priv->show_all_devices || fu_util_is_interesting_device (dev)) { g_autofree gchar *tmp = fwupd_device_to_string (dev); g_print ("%s\n", tmp); } } /* save the device state for other applications to see */ if (!fu_util_save_current_state (priv, error)) return FALSE; return TRUE; } static void fu_util_build_device_tree (FuUtilPrivate *priv, GNode *root, GPtrArray *devs, FuDevice *dev) { for (guint i = 0; i < devs->len; i++) { FuDevice *dev_tmp = g_ptr_array_index (devs, i); if (!priv->show_all_devices && !fu_util_is_interesting_device (FWUPD_DEVICE (dev_tmp))) continue; if (fu_device_get_parent (dev_tmp) == dev) { GNode *child = g_node_append_data (root, dev_tmp); fu_util_build_device_tree (priv, child, devs, dev_tmp); } } } static gboolean fu_util_get_topology (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new (NULL); g_autoptr(GPtrArray) devs = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* print */ devs = fu_engine_get_devices (priv->engine, error); if (devs == NULL) return FALSE; /* print */ if (devs->len == 0) { /* TRANSLATORS: nothing attached that can be upgraded */ g_print ("%s\n", _("No hardware detected with firmware update capability")); return TRUE; } fu_util_build_device_tree (priv, root, devs, NULL); g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, fu_util_print_device_tree, priv); return TRUE; } static FuDevice * fu_util_prompt_for_device (FuUtilPrivate *priv, GError **error) { FuDevice *dev; guint idx; g_autoptr(GPtrArray) devices = NULL; /* get devices from daemon */ devices = fu_engine_get_devices (priv->engine, error); if (devices == NULL) return NULL; /* exactly one */ if (devices->len == 1) { dev = g_ptr_array_index (devices, 0); return g_object_ref (dev); } /* TRANSLATORS: get interactive prompt */ g_print ("%s\n", _("Choose a device:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print ("0.\t%s\n", _("Cancel")); for (guint i = 0; i < devices->len; i++) { dev = g_ptr_array_index (devices, i); g_print ("%u.\t%s (%s)\n", i + 1, fu_device_get_id (dev), fu_device_get_name (dev)); } idx = fu_util_prompt_for_number (devices->len); if (idx == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return NULL; } dev = g_ptr_array_index (devices, idx - 1); return g_object_ref (dev); } static void fu_util_update_device_changed_cb (FwupdClient *client, FwupdDevice *device, FuUtilPrivate *priv) { g_autofree gchar *str = NULL; /* allowed to set whenever the device has changed */ if (fwupd_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; if (fwupd_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; /* same as last time, so ignore */ if (priv->current_device != NULL && fwupd_device_compare (priv->current_device, device) == 0) return; /* show message in progressbar */ if (priv->current_operation == FU_UTIL_OPERATION_UPDATE) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf (_("Updating %s…"), fwupd_device_get_name (device)); fu_progressbar_set_title (priv->progressbar, str); } else if (priv->current_operation == FU_UTIL_OPERATION_INSTALL) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf (_("Installing on %s…"), fwupd_device_get_name (device)); fu_progressbar_set_title (priv->progressbar, str); } else { g_warning ("no FuUtilOperation set"); } g_set_object (&priv->current_device, device); if (priv->current_message == NULL) { const gchar *tmp = fwupd_device_get_update_message (priv->current_device); if (tmp != NULL) priv->current_message = g_strdup (tmp); } } static void fu_util_display_current_message (FuUtilPrivate *priv) { if (priv->current_message == NULL) return; g_print ("%s\n", priv->current_message); g_clear_pointer (&priv->current_message, g_free); } static gboolean fu_util_install_blob (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GBytes) blob_fw = NULL; /* invalid args */ if (g_strv_length (values) == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* parse blob */ blob_fw = fu_common_get_contents_bytes (values[0], error); if (blob_fw == NULL) { fu_util_maybe_prefix_sandbox_error (values[0], error); return FALSE; } /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* get device */ if (g_strv_length (values) >= 2) { device = fu_engine_get_device (priv->engine, values[1], error); if (device == NULL) return FALSE; } else { device = fu_util_prompt_for_device (priv, error); if (device == NULL) return FALSE; } priv->current_operation = FU_UTIL_OPERATION_INSTALL; g_signal_connect (priv->engine, "device-changed", G_CALLBACK (fu_util_update_device_changed_cb), priv); /* write bare firmware */ if (priv->prepare_blob) { g_autoptr(GPtrArray) devices = NULL; devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_ptr_array_add (devices, g_object_ref (device)); if (!fu_engine_composite_prepare (priv->engine, devices, error)) { g_prefix_error (error, "failed to prepare composite action: "); return FALSE; } } priv->flags = FWUPD_INSTALL_FLAG_NO_HISTORY; if (!fu_engine_install_blob (priv->engine, device, blob_fw, priv->flags, error)) return FALSE; if (priv->cleanup_blob) { g_autoptr(FuDevice) device_new = NULL; g_autoptr(GError) error_local = NULL; /* get the possibly new device from the old ID */ device_new = fu_engine_get_device (priv->engine, fu_device_get_id (device), &error_local); if (device_new == NULL) { g_debug ("failed to find new device: %s", error_local->message); } else { g_autoptr(GPtrArray) devices_new = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_ptr_array_add (devices_new, g_steal_pointer (&device_new)); if (!fu_engine_composite_cleanup (priv->engine, devices_new, error)) { g_prefix_error (error, "failed to cleanup composite action: "); return FALSE; } } } fu_util_display_current_message (priv); /* success */ return fu_util_prompt_complete (priv->completion_flags, TRUE, error); } static gint fu_util_install_task_sort_cb (gconstpointer a, gconstpointer b) { FuInstallTask *task1 = *((FuInstallTask **) a); FuInstallTask *task2 = *((FuInstallTask **) b); return fu_install_task_compare (task1, task2); } static gboolean fu_util_download_out_of_process (const gchar *uri, const gchar *fn, GError **error) { const gchar *argv[][5] = { { "wget", uri, "-O", fn, NULL }, { "curl", uri, "--output", fn, NULL }, { NULL } }; for (guint i = 0; argv[i][0] != NULL; i++) { g_autoptr(GError) error_local = NULL; if (!fu_common_find_program_in_path (argv[i][0], &error_local)) { g_debug ("%s", error_local->message); continue; } return fu_common_spawn_sync (argv[i], NULL, NULL, 0, NULL, error); } g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no supported out-of-process downloaders found"); return FALSE; } static gchar * fu_util_download_if_required (FuUtilPrivate *priv, const gchar *perhapsfn, GError **error) { g_autofree gchar *filename = NULL; g_autoptr(SoupURI) uri = NULL; /* a local file */ uri = soup_uri_new (perhapsfn); if (uri == NULL) return g_strdup (perhapsfn); /* download the firmware to a cachedir */ filename = fu_util_get_user_cache_path (perhapsfn); if (!fu_common_mkdir_parent (filename, error)) return NULL; if (!fu_util_download_out_of_process (perhapsfn, filename, error)) return NULL; return g_steal_pointer (&filename); } static gboolean fu_util_install (FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *filename = NULL; g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) devices_possible = NULL; g_autoptr(GPtrArray) errors = NULL; g_autoptr(GPtrArray) install_tasks = NULL; g_autoptr(XbSilo) silo = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* handle both forms */ if (g_strv_length (values) == 1) { devices_possible = fu_engine_get_devices (priv->engine, error); if (devices_possible == NULL) return FALSE; } else if (g_strv_length (values) == 2) { FuDevice *device = fu_engine_get_device (priv->engine, values[1], error); if (device == NULL) return FALSE; devices_possible = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_ptr_array_add (devices_possible, device); } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* download if required */ filename = fu_util_download_if_required (priv, values[0], error); if (filename == NULL) return FALSE; /* parse silo */ blob_cab = fu_common_get_contents_bytes (filename, error); if (blob_cab == NULL) { fu_util_maybe_prefix_sandbox_error (filename, error); return FALSE; } silo = fu_engine_get_silo_from_blob (priv->engine, blob_cab, error); if (silo == NULL) return FALSE; components = xb_silo_query (silo, "components/component", 0, error); if (components == NULL) return FALSE; /* for each component in the silo */ errors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_error_free); install_tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < components->len; i++) { XbNode *component = g_ptr_array_index (components, i); /* do any devices pass the requirements */ for (guint j = 0; j < devices_possible->len; j++) { FuDevice *device = g_ptr_array_index (devices_possible, j); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error_local = NULL; /* is this component valid for the device */ task = fu_install_task_new (device, component); if (!fu_engine_check_requirements (priv->engine, task, priv->flags, &error_local)) { g_debug ("requirement on %s:%s failed: %s", fu_device_get_id (device), xb_node_query_text (component, "id", NULL), error_local->message); g_ptr_array_add (errors, g_steal_pointer (&error_local)); continue; } /* if component should have an update message from CAB */ fu_device_incorporate_from_component (device, component); /* success */ g_ptr_array_add (install_tasks, g_steal_pointer (&task)); } } /* order the install tasks by the device priority */ g_ptr_array_sort (install_tasks, fu_util_install_task_sort_cb); /* nothing suitable */ if (install_tasks->len == 0) { GError *error_tmp = fu_common_error_array_get_best (errors); g_propagate_error (error, error_tmp); return FALSE; } priv->current_operation = FU_UTIL_OPERATION_INSTALL; g_signal_connect (priv->engine, "device-changed", G_CALLBACK (fu_util_update_device_changed_cb), priv); /* install all the tasks */ if (!fu_engine_install_tasks (priv->engine, install_tasks, blob_cab, priv->flags, error)) return FALSE; fu_util_display_current_message (priv); /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug ("skipping reboot check"); return TRUE; } /* save the device state for other applications to see */ if (!fu_util_save_current_state (priv, error)) return FALSE; /* success */ return fu_util_prompt_complete (priv->completion_flags, TRUE, error); } static gboolean fu_util_install_release (FuUtilPrivate *priv, FwupdRelease *rel, GError **error) { FwupdRemote *remote; const gchar *remote_id; const gchar *uri_tmp; g_auto(GStrv) argv = NULL; uri_tmp = fwupd_release_get_uri (rel); if (uri_tmp == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "release missing URI"); return FALSE; } remote_id = fwupd_release_get_remote_id (rel); if (remote_id == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to find remote for %s", uri_tmp); return FALSE; } remote = fu_engine_get_remote_by_id (priv->engine, remote_id, error); if (remote == NULL) return FALSE; argv = g_new0 (gchar *, 2); /* local remotes have the firmware already */ if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_LOCAL) { const gchar *fn_cache = fwupd_remote_get_filename_cache (remote); g_autofree gchar *path = g_path_get_dirname (fn_cache); argv[0] = g_build_filename (path, uri_tmp, NULL); } else if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_DIRECTORY) { argv[0] = g_strdup (uri_tmp + 7); /* web remote, fu_util_install will download file */ } else { argv[0] = fwupd_remote_build_firmware_uri (remote, uri_tmp, error); } return fu_util_install (priv, argv, error); } static gboolean fu_util_update_all (FuUtilPrivate *priv, GError **error) { g_autoptr(GPtrArray) devices = NULL; devices = fu_engine_get_devices (priv->engine, error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); FwupdRelease *rel; const gchar *device_id; g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; if (!fu_util_is_interesting_device (dev)) continue; /* only show stuff that has metadata available */ if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; device_id = fu_device_get_id (dev); rels = fu_engine_get_upgrades (priv->engine, device_id, &error_local); if (rels == NULL) { g_printerr ("%s\n", error_local->message); continue; } rel = g_ptr_array_index (rels, 0); if (!fu_util_install_release (priv, rel, &error_local)) { g_printerr ("%s\n", error_local->message); continue; } fu_util_display_current_message (priv); } return TRUE; } static gboolean fu_util_update_by_id (FuUtilPrivate *priv, const gchar *device_id, GError **error) { FwupdRelease *rel; g_autoptr(FuDevice) dev = NULL; g_autoptr(GPtrArray) rels = NULL; /* do not allow a partial device-id */ dev = fu_engine_get_device (priv->engine, device_id, error); if (dev == NULL) return FALSE; /* get the releases for this device and filter for validity */ rels = fu_engine_get_upgrades (priv->engine, device_id, error); if (rels == NULL) return FALSE; rel = g_ptr_array_index (rels, 0); if (!fu_util_install_release (priv, rel, error)) return FALSE; fu_util_display_current_message (priv); return TRUE; } static gboolean fu_util_update (FuUtilPrivate *priv, gchar **values, GError **error) { if (g_strv_length (values) > 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; priv->current_operation = FU_UTIL_OPERATION_UPDATE; g_signal_connect (priv->engine, "device-changed", G_CALLBACK (fu_util_update_device_changed_cb), priv); if (g_strv_length (values) == 1) { if (!fu_util_update_by_id (priv, values[0], error)) return FALSE; } else { if (!fu_util_update_all (priv, error)) return FALSE; } /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug ("skipping reboot check"); return TRUE; } /* save the device state for other applications to see */ if (!fu_util_save_current_state (priv, error)) return FALSE; return fu_util_prompt_complete (priv->completion_flags, TRUE, error); } static gboolean fu_util_detach (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* invalid args */ if (g_strv_length (values) == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* get device */ if (g_strv_length (values) >= 1) { device = fu_engine_get_device (priv->engine, values[0], error); if (device == NULL) return FALSE; } else { device = fu_util_prompt_for_device (priv, error); if (device == NULL) return FALSE; } /* run vfunc */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_detach (device, error); } static gboolean fu_util_attach (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* invalid args */ if (g_strv_length (values) == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* get device */ if (g_strv_length (values) >= 1) { device = fu_engine_get_device (priv->engine, values[0], error); if (device == NULL) return FALSE; } else { device = fu_util_prompt_for_device (priv, error); if (device == NULL) return FALSE; } /* run vfunc */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_attach (device, error); } static gboolean fu_util_activate (FuUtilPrivate *priv, gchar **values, GError **error) { gboolean has_pending = FALSE; g_autoptr(FuHistory) history = fu_history_new (); g_autoptr(GPtrArray) devices = NULL; /* check the history database before starting the daemon */ if (g_strv_length (values) == 0) { devices = fu_history_get_devices (history, error); if (devices == NULL) return FALSE; } else if (g_strv_length (values) == 1) { FuDevice *device; device = fu_history_get_device_by_id (history, values[0], error); if (device == NULL) return FALSE; devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_ptr_array_add (devices, device); } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* nothing to do */ for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index (devices, i); if (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { fu_engine_add_plugin_filter (priv->engine, fu_device_get_plugin (dev)); has_pending = TRUE; } } if (!has_pending) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No firmware to activate"); return FALSE; } /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_READONLY_FS, error)) return FALSE; /* activate anything with _NEEDS_ACTIVATION */ for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) continue; /* TRANSLATORS: shown when shutting down to switch to the new version */ g_print ("%s %s…\n", _("Activating firmware update"), fu_device_get_name (device)); if (!fu_engine_activate (priv->engine, fu_device_get_id (device), error)) return FALSE; } return TRUE; } static gboolean fu_util_hwids (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuSmbios) smbios = fu_smbios_new (); g_autoptr(FuHwids) hwids = fu_hwids_new (); const gchar *hwid_keys[] = { FU_HWIDS_KEY_BIOS_VENDOR, FU_HWIDS_KEY_BIOS_VERSION, FU_HWIDS_KEY_BIOS_MAJOR_RELEASE, FU_HWIDS_KEY_BIOS_MINOR_RELEASE, FU_HWIDS_KEY_MANUFACTURER, FU_HWIDS_KEY_FAMILY, FU_HWIDS_KEY_PRODUCT_NAME, FU_HWIDS_KEY_PRODUCT_SKU, FU_HWIDS_KEY_ENCLOSURE_KIND, FU_HWIDS_KEY_BASEBOARD_MANUFACTURER, FU_HWIDS_KEY_BASEBOARD_PRODUCT, NULL }; /* read DMI data */ if (g_strv_length (values) == 0) { if (!fu_smbios_setup (smbios, error)) return FALSE; } else if (g_strv_length (values) == 1) { if (!fu_smbios_setup_from_file (smbios, values[0], error)) return FALSE; } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } if (!fu_hwids_setup (hwids, smbios, error)) return FALSE; /* show debug output */ g_print ("Computer Information\n"); g_print ("--------------------\n"); for (guint i = 0; hwid_keys[i] != NULL; i++) { const gchar *tmp = fu_hwids_get_value (hwids, hwid_keys[i]); if (tmp == NULL) continue; if (g_strcmp0 (hwid_keys[i], FU_HWIDS_KEY_BIOS_MAJOR_RELEASE) == 0 || g_strcmp0 (hwid_keys[i], FU_HWIDS_KEY_BIOS_MINOR_RELEASE) == 0) { guint64 val = g_ascii_strtoull (tmp, NULL, 16); g_print ("%s: %" G_GUINT64_FORMAT "\n", hwid_keys[i], val); } else { g_print ("%s: %s\n", hwid_keys[i], tmp); } } /* show GUIDs */ g_print ("\nHardware IDs\n"); g_print ("------------\n"); for (guint i = 0; i < 15; i++) { const gchar *keys = NULL; g_autofree gchar *guid = NULL; g_autofree gchar *key = NULL; g_autofree gchar *keys_str = NULL; g_auto(GStrv) keysv = NULL; g_autoptr(GError) error_local = NULL; /* get the GUID */ key = g_strdup_printf ("HardwareID-%u", i); keys = fu_hwids_get_replace_keys (hwids, key); guid = fu_hwids_get_guid (hwids, key, &error_local); if (guid == NULL) { g_print ("%s\n", error_local->message); continue; } /* show what makes up the GUID */ keysv = g_strsplit (keys, "&", -1); keys_str = g_strjoinv (" + ", keysv); g_print ("{%s} <- %s\n", guid, keys_str); } return TRUE; } static gboolean fu_util_firmware_builder (FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *script_fn = "startup.sh"; const gchar *output_fn = "firmware.bin"; g_autoptr(GBytes) archive_blob = NULL; g_autoptr(GBytes) firmware_blob = NULL; if (g_strv_length (values) < 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } archive_blob = fu_common_get_contents_bytes (values[0], error); if (archive_blob == NULL) return FALSE; if (g_strv_length (values) > 2) script_fn = values[2]; if (g_strv_length (values) > 3) output_fn = values[3]; firmware_blob = fu_common_firmware_builder (archive_blob, script_fn, output_fn, error); if (firmware_blob == NULL) return FALSE; return fu_common_set_contents_bytes (values[1], firmware_blob, error); } static gboolean fu_util_self_sign (FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *sig = NULL; /* check args */ if (g_strv_length (values) != 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: value expected"); return FALSE; } /* start engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; sig = fu_engine_self_sign (priv->engine, values[0], FU_KEYRING_SIGN_FLAG_ADD_TIMESTAMP | FU_KEYRING_SIGN_FLAG_ADD_CERT, error); if (sig == NULL) return FALSE; g_print ("%s\n", sig); return TRUE; } static void fu_util_device_added_cb (FwupdClient *client, FwupdDevice *device, gpointer user_data) { g_autofree gchar *tmp = fwupd_device_to_string (device); /* TRANSLATORS: this is when a device is hotplugged */ g_print ("%s\n%s", _("Device added:"), tmp); } static void fu_util_device_removed_cb (FwupdClient *client, FwupdDevice *device, gpointer user_data) { g_autofree gchar *tmp = fwupd_device_to_string (device); /* TRANSLATORS: this is when a device is hotplugged */ g_print ("%s\n%s", _("Device removed:"), tmp); } static void fu_util_device_changed_cb (FwupdClient *client, FwupdDevice *device, gpointer user_data) { g_autofree gchar *tmp = fwupd_device_to_string (device); /* TRANSLATORS: this is when a device has been updated */ g_print ("%s\n%s", _("Device changed:"), tmp); } static void fu_util_changed_cb (FwupdClient *client, gpointer user_data) { /* TRANSLATORS: this is when the daemon state changes */ g_print ("%s\n", _("Changed")); } static gboolean fu_util_monitor (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdClient) client = fwupd_client_new (); /* get all the devices */ if (!fwupd_client_connect (client, priv->cancellable, error)) return FALSE; /* watch for any hotplugged device */ g_signal_connect (client, "changed", G_CALLBACK (fu_util_changed_cb), priv); g_signal_connect (client, "device-added", G_CALLBACK (fu_util_device_added_cb), priv); g_signal_connect (client, "device-removed", G_CALLBACK (fu_util_device_removed_cb), priv); g_signal_connect (client, "device-changed", G_CALLBACK (fu_util_device_changed_cb), priv); g_signal_connect (priv->cancellable, "cancelled", G_CALLBACK (fu_util_cancelled_cb), priv); g_main_loop_run (priv->loop); return TRUE; } static gboolean fu_util_verify_update (FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *str = NULL; g_autoptr(FuDevice) dev = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* get device */ if (g_strv_length (values) == 1) { dev = fu_engine_get_device (priv->engine, values[1], error); if (dev == NULL) return FALSE; } else { dev = fu_util_prompt_for_device (priv, error); if (dev == NULL) return FALSE; } /* add checksums */ if (!fu_engine_verify_update (priv->engine, fu_device_get_id (dev), error)) return FALSE; /* show checksums */ str = fu_device_to_string (dev); g_print ("%s\n", str); return TRUE; } static gboolean fu_util_get_history (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* get all devices from the history database */ devices = fu_engine_get_history (priv->engine, error); if (devices == NULL) return FALSE; /* show each device */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); g_autofree gchar *str = fwupd_device_to_string (dev); g_print ("%s\n", str); } return TRUE; } int main (int argc, char *argv[]) { gboolean allow_older = FALSE; gboolean allow_reinstall = FALSE; gboolean force = FALSE; gboolean ret; gboolean version = FALSE; gboolean interactive = isatty (fileno (stdout)) != 0; g_auto(GStrv) plugin_glob = NULL; g_autoptr(FuUtilPrivate) priv = g_new0 (FuUtilPrivate, 1); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new (); g_autofree gchar *cmd_descriptions = NULL; const GOptionEntry options[] = { { "version", '\0', 0, G_OPTION_ARG_NONE, &version, /* TRANSLATORS: command line option */ _("Show client and daemon versions"), NULL }, { "allow-reinstall", '\0', 0, G_OPTION_ARG_NONE, &allow_reinstall, /* TRANSLATORS: command line option */ _("Allow re-installing existing firmware versions"), NULL }, { "allow-older", '\0', 0, G_OPTION_ARG_NONE, &allow_older, /* TRANSLATORS: command line option */ _("Allow downgrading firmware versions"), NULL }, { "force", '\0', 0, G_OPTION_ARG_NONE, &force, /* TRANSLATORS: command line option */ _("Override plugin warning"), NULL }, { "no-reboot-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_reboot_check, /* TRANSLATORS: command line option */ _("Do not check for reboot after update"), NULL }, { "show-all-devices", '\0', 0, G_OPTION_ARG_NONE, &priv->show_all_devices, /* TRANSLATORS: command line option */ _("Show devices that are not updatable"), NULL }, { "plugin-whitelist", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &plugin_glob, /* TRANSLATORS: command line option */ _("Manually whitelist specific plugins"), NULL }, { "prepare", '\0', 0, G_OPTION_ARG_NONE, &priv->prepare_blob, /* TRANSLATORS: command line option */ _("Run the plugin composite prepare routine when using install-blob"), NULL }, { "cleanup", '\0', 0, G_OPTION_ARG_NONE, &priv->cleanup_blob, /* TRANSLATORS: command line option */ _("Run the plugin composite cleanup routine when using install-blob"), NULL }, { "enable-json-state", '\0', 0, G_OPTION_ARG_NONE, &priv->enable_json_state, /* TRANSLATORS: command line option */ _("Save device state into a JSON file between executions"), NULL }, { NULL} }; setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); /* ensure root user */ if (interactive && (getuid () != 0 || geteuid () != 0)) /* TRANSLATORS: we're poking around as a power user */ g_printerr ("%s\n", _("This program may only work correctly as root")); /* create helper object */ priv->loop = g_main_loop_new (NULL, FALSE); priv->progressbar = fu_progressbar_new (); /* add commands */ fu_util_cmd_array_add (cmd_array, "build-firmware", "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]", /* TRANSLATORS: command description */ _("Build firmware using a sandbox"), fu_util_firmware_builder); fu_util_cmd_array_add (cmd_array, "smbios-dump", "FILE", /* TRANSLATORS: command description */ _("Dump SMBIOS data from a file"), fu_util_smbios_dump); fu_util_cmd_array_add (cmd_array, "get-plugins", NULL, /* TRANSLATORS: command description */ _("Get all enabled plugins registered with the system"), fu_util_get_plugins); fu_util_cmd_array_add (cmd_array, "get-details", NULL, /* TRANSLATORS: command description */ _("Gets details about a firmware file"), fu_util_get_details); fu_util_cmd_array_add (cmd_array, "get-history", NULL, /* TRANSLATORS: command description */ _("Show history of firmware updates"), fu_util_get_history); fu_util_cmd_array_add (cmd_array, "get-updates", NULL, /* TRANSLATORS: command description */ _("Gets the list of updates for connected hardware"), fu_util_get_updates); fu_util_cmd_array_add (cmd_array, "get-devices", NULL, /* TRANSLATORS: command description */ _("Get all devices that support firmware updates"), fu_util_get_devices); fu_util_cmd_array_add (cmd_array, "get-topology", NULL, /* TRANSLATORS: command description */ _("Get all devices according to the system topology"), fu_util_get_topology); fu_util_cmd_array_add (cmd_array, "watch", NULL, /* TRANSLATORS: command description */ _("Watch for hardware changes"), fu_util_watch); fu_util_cmd_array_add (cmd_array, "install-blob", "FILENAME DEVICE-ID", /* TRANSLATORS: command description */ _("Install a firmware blob on a device"), fu_util_install_blob); fu_util_cmd_array_add (cmd_array, "install", "FILE [ID]", /* TRANSLATORS: command description */ _("Install a firmware file on this hardware"), fu_util_install); fu_util_cmd_array_add (cmd_array, "attach", "DEVICE-ID", /* TRANSLATORS: command description */ _("Attach to firmware mode"), fu_util_attach); fu_util_cmd_array_add (cmd_array, "detach", "DEVICE-ID", /* TRANSLATORS: command description */ _("Detach to bootloader mode"), fu_util_detach); fu_util_cmd_array_add (cmd_array, "activate", "[DEVICE-ID]", /* TRANSLATORS: command description */ _("Activate pending devices"), fu_util_activate); fu_util_cmd_array_add (cmd_array, "hwids", "[FILE]", /* TRANSLATORS: command description */ _("Return all the hardware IDs for the machine"), fu_util_hwids); fu_util_cmd_array_add (cmd_array, "monitor", NULL, /* TRANSLATORS: command description */ _("Monitor the daemon for events"), fu_util_monitor); fu_util_cmd_array_add (cmd_array, "update", NULL, /* TRANSLATORS: command description */ _("Update all devices that match local metadata"), fu_util_update); fu_util_cmd_array_add (cmd_array, "self-sign", "TEXT", /* TRANSLATORS: command description */ C_("command-description", "Sign data using the client certificate"), fu_util_self_sign); fu_util_cmd_array_add (cmd_array, "verify-update", "[DEVICE_ID]", /* TRANSLATORS: command description */ _("Update the stored metadata with current contents"), fu_util_verify_update); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new (); g_unix_signal_add_full (G_PRIORITY_DEFAULT, SIGINT, fu_util_sigint_cb, priv, NULL); g_signal_connect (priv->cancellable, "cancelled", G_CALLBACK (fu_util_cancelled_cb), priv); /* sort by command name */ fu_util_cmd_array_sort (cmd_array); /* non-TTY consoles cannot answer questions */ if (!interactive) { priv->no_reboot_check = TRUE; fu_progressbar_set_interactive (priv->progressbar, FALSE); } /* get a list of the commands */ priv->context = g_option_context_new (NULL); cmd_descriptions = fu_util_cmd_array_to_string (cmd_array); g_option_context_set_summary (priv->context, cmd_descriptions); g_option_context_set_description (priv->context, "This tool allows an administrator to use the fwupd plugins " "without being installed on the host system."); /* TRANSLATORS: program name */ g_set_application_name (_("Firmware Utility")); g_option_context_add_main_entries (priv->context, options, NULL); g_option_context_add_group (priv->context, fu_debug_get_option_group ()); ret = g_option_context_parse (priv->context, &argc, &argv, &error); if (!ret) { /* TRANSLATORS: the user didn't read the man page */ g_print ("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* set flags */ if (allow_reinstall) priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; if (allow_older) priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; if (force) priv->flags |= FWUPD_INSTALL_FLAG_FORCE; /* load engine */ priv->engine = fu_engine_new (FU_APP_FLAGS_NO_IDLE_SOURCES); g_signal_connect (priv->engine, "device-added", G_CALLBACK (fu_main_engine_device_added_cb), priv); g_signal_connect (priv->engine, "device-removed", G_CALLBACK (fu_main_engine_device_removed_cb), priv); g_signal_connect (priv->engine, "status-changed", G_CALLBACK (fu_main_engine_status_changed_cb), priv); g_signal_connect (priv->engine, "percentage-changed", G_CALLBACK (fu_main_engine_percentage_changed_cb), priv); /* just show versions and exit */ if (version) { g_autofree gchar *version_str = fu_util_get_versions (); g_print ("%s\n", version_str); return EXIT_SUCCESS; } /* any plugin whitelist specified */ for (guint i = 0; plugin_glob != NULL && plugin_glob[i] != NULL; i++) fu_engine_add_plugin_filter (priv->engine, plugin_glob[i]); /* run the specified command */ ret = fu_util_cmd_array_run (cmd_array, priv, argv[1], (gchar**) &argv[2], &error); if (!ret) { if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help (priv->context, TRUE, NULL); g_print ("%s\n\n%s", error->message, tmp); return EXIT_FAILURE; } if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_print ("%s\n", error->message); return EXIT_NOTHING_TO_DO; } g_print ("%s\n", error->message); return EXIT_FAILURE; } /* success */ return EXIT_SUCCESS; } fwupd-1.2.14/src/fu-udev-device-private.h000066400000000000000000000003621402665037500201550ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-udev-device.h" G_BEGIN_DECLS void fu_udev_device_emit_changed (FuUdevDevice *self); G_END_DECLS fwupd-1.2.14/src/fu-udev-device.c000066400000000000000000000376101402665037500165060ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuUdevDevice" #include "config.h" #include #include "fu-device-private.h" #include "fu-udev-device-private.h" /** * SECTION:fu-udev-device * @short_description: a udev device * * An object that represents a udev device. * * See also: #FuDevice */ typedef struct { GUdevDevice *udev_device; guint16 vendor; guint16 model; guint8 revision; } FuUdevDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuUdevDevice, fu_udev_device, FU_TYPE_DEVICE) #ifndef HAVE_GUDEV_232 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref) #pragma clang diagnostic pop #endif enum { PROP_0, PROP_UDEV_DEVICE, PROP_LAST }; enum { SIGNAL_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = { 0 }; #define GET_PRIVATE(o) (fu_udev_device_get_instance_private (o)) /** * fu_udev_device_emit_changed: * @self: A #FuUdevDevice * * Emits the ::changed signal for the object. * * Since: 1.1.2 **/ void fu_udev_device_emit_changed (FuUdevDevice *self) { g_return_if_fail (FU_IS_UDEV_DEVICE (self)); g_debug ("FuUdevDevice emit changed"); g_signal_emit (self, signals[SIGNAL_CHANGED], 0); } static guint64 fu_udev_device_get_sysfs_attr_as_uint64 (GUdevDevice *udev_device, const gchar *name) { return fu_common_strtoull (g_udev_device_get_sysfs_attr (udev_device, name)); } static guint16 fu_udev_device_read_uint16 (const gchar *str) { gchar buf[5] = { 0x0, 0x0, 0x0, 0x0, 0x0 }; memcpy (buf, str, 4); return (guint16) g_ascii_strtoull (buf, NULL, 16); } static void fu_udev_device_dump_internal (GUdevDevice *udev_device) { #ifdef HAVE_GUDEV_232 const gchar * const *keys; keys = g_udev_device_get_property_keys (udev_device); for (guint i = 0; keys[i] != NULL; i++) { g_debug ("%s={%s}", keys[i], g_udev_device_get_property (udev_device, keys[i])); } keys = g_udev_device_get_sysfs_attr_keys (udev_device); for (guint i = 0; keys[i] != NULL; i++) { g_debug ("%s=[%s]", keys[i], g_udev_device_get_sysfs_attr (udev_device, keys[i])); } #endif } void fu_udev_device_dump (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); fu_udev_device_dump_internal (priv->udev_device); } static gboolean fu_udev_device_probe (FuDevice *device, GError **error) { FuUdevDeviceClass *klass = FU_UDEV_DEVICE_GET_CLASS (device); FuUdevDevice *self = FU_UDEV_DEVICE (device); FuUdevDevicePrivate *priv = GET_PRIVATE (self); const gchar *tmp; g_autofree gchar *subsystem = NULL; g_autoptr(GUdevDevice) udev_parent = NULL; /* set ven:dev:rev */ priv->vendor = fu_udev_device_get_sysfs_attr_as_uint64 (priv->udev_device, "vendor"); priv->model = fu_udev_device_get_sysfs_attr_as_uint64 (priv->udev_device, "device"); priv->revision = fu_udev_device_get_sysfs_attr_as_uint64 (priv->udev_device, "revision"); /* fallback to the parent */ udev_parent = g_udev_device_get_parent (priv->udev_device); if (udev_parent != NULL && priv->vendor == 0x0 && priv->model == 0x0 && priv->revision == 0x0) { priv->vendor = fu_udev_device_get_sysfs_attr_as_uint64 (udev_parent, "vendor"); priv->model = fu_udev_device_get_sysfs_attr_as_uint64 (udev_parent, "device"); priv->revision = fu_udev_device_get_sysfs_attr_as_uint64 (udev_parent, "revision"); } /* hidraw helpfully encodes the information in a different place */ if (udev_parent != NULL && priv->vendor == 0x0 && priv->model == 0x0 && priv->revision == 0x0 && g_strcmp0 (g_udev_device_get_subsystem (priv->udev_device), "hidraw") == 0) { tmp = g_udev_device_get_property (udev_parent, "HID_ID"); if (tmp != NULL && strlen (tmp) == 22) { priv->vendor = fu_udev_device_read_uint16 (tmp + 9); priv->model = fu_udev_device_read_uint16 (tmp + 18); } tmp = g_udev_device_get_property (udev_parent, "HID_NAME"); if (tmp != NULL) { g_auto(GStrv) vm = g_strsplit (tmp, " ", 2); if (g_strv_length (vm) == 2) { if (fu_device_get_vendor (device) == NULL) fu_device_set_vendor (device, vm[0]); if (fu_device_get_name (device) == NULL) fu_device_set_name (device, vm[1]); } } } /* set the version if the revision has been set */ if (fu_device_get_version (device) == NULL) { if (priv->revision != 0x00) { g_autofree gchar *version = g_strdup_printf ("%02x", priv->revision); fu_device_set_version (device, version, FWUPD_VERSION_FORMAT_PLAIN); } } /* set model */ if (fu_device_get_name (device) == NULL) { tmp = g_udev_device_get_property (priv->udev_device, "FWUPD_MODEL"); if (tmp == NULL) tmp = g_udev_device_get_property (priv->udev_device, "ID_MODEL_FROM_DATABASE"); if (tmp == NULL) tmp = g_udev_device_get_property (priv->udev_device, "ID_MODEL"); if (tmp != NULL) fu_device_set_name (device, tmp); } /* set vendor */ if (fu_device_get_vendor (device) == NULL) { tmp = g_udev_device_get_property (priv->udev_device, "FWUPD_VENDOR"); if (tmp == NULL) tmp = g_udev_device_get_property (priv->udev_device, "ID_VENDOR_FROM_DATABASE"); if (tmp == NULL) tmp = g_udev_device_get_property (priv->udev_device, "ID_VENDOR"); if (tmp != NULL) fu_device_set_vendor (device, tmp); } /* set serial */ if (fu_device_get_serial (device) == NULL) { tmp = g_udev_device_get_property (priv->udev_device, "ID_SERIAL_SHORT"); if (tmp == NULL) tmp = g_udev_device_get_property (priv->udev_device, "ID_SERIAL"); if (tmp != NULL) fu_device_set_serial (device, tmp); } /* set revision */ if (fu_device_get_version (device) == NULL) { tmp = g_udev_device_get_property (priv->udev_device, "ID_REVISION"); if (tmp != NULL) fu_device_set_version (device, tmp, FWUPD_VERSION_FORMAT_UNKNOWN); } /* set vendor ID */ subsystem = g_ascii_strup (fu_udev_device_get_subsystem (self), -1); if (subsystem != NULL && priv->vendor != 0x0000) { g_autofree gchar *vendor_id = NULL; vendor_id = g_strdup_printf ("%s:0x%04X", subsystem, (guint) priv->vendor); fu_device_set_vendor_id (device, vendor_id); } /* add GUIDs in order of priority */ if (priv->vendor != 0x0000 && priv->model != 0x0000 && priv->revision != 0x00) { g_autofree gchar *devid = NULL; devid = g_strdup_printf ("%s\\VEN_%04X&DEV_%04X&REV_%02X", subsystem, priv->vendor, priv->model, priv->revision); fu_device_add_instance_id (device, devid); } if (priv->vendor != 0x0000 && priv->model != 0x0000) { g_autofree gchar *devid = NULL; devid = g_strdup_printf ("%s\\VEN_%04X&DEV_%04X", subsystem, priv->vendor, priv->model); fu_device_add_instance_id (device, devid); } if (priv->vendor != 0x0000) { g_autofree gchar *devid = NULL; devid = g_strdup_printf ("%s\\VEN_%04X", subsystem, priv->vendor); fu_device_add_instance_id_full (device, devid, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* subclassed */ if (klass->probe != NULL) { if (!klass->probe (self, error)) return FALSE; } /* success */ return TRUE; } static void fu_udev_device_set_dev (FuUdevDevice *self, GUdevDevice *udev_device) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_UDEV_DEVICE (self)); /* set new device */ g_set_object (&priv->udev_device, udev_device); if (priv->udev_device == NULL) return; } guint fu_udev_device_get_slot_depth (FuUdevDevice *self, const gchar *subsystem) { GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (self)); g_autoptr(GUdevDevice) device_tmp = NULL; device_tmp = g_udev_device_get_parent_with_subsystem (udev_device, subsystem, NULL); if (device_tmp == NULL) return 0; for (guint i = 0; i < 0xff; i++) { g_autoptr(GUdevDevice) parent = g_udev_device_get_parent (device_tmp); if (parent == NULL) return i; g_set_object (&device_tmp, parent); } return 0; } static void fu_udev_device_incorporate (FuDevice *self, FuDevice *donor) { g_return_if_fail (FU_IS_UDEV_DEVICE (self)); g_return_if_fail (FU_IS_UDEV_DEVICE (donor)); fu_udev_device_set_dev (FU_UDEV_DEVICE (self), fu_udev_device_get_dev (FU_UDEV_DEVICE (donor))); } /** * fu_udev_device_get_dev: * @self: A #FuUdevDevice * * Gets the #GUdevDevice. * * Returns: (transfer none): a #GUdevDevice, or %NULL * * Since: 1.1.2 **/ GUdevDevice * fu_udev_device_get_dev (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), NULL); return priv->udev_device; } /** * fu_udev_device_get_subsystem: * @self: A #GUdevDevice * * Gets the device subsystem, e.g. "pci". * * Returns: a subsystem, or NULL if unset or invalid * * Since: 1.1.2 **/ const gchar * fu_udev_device_get_subsystem (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), NULL); return g_udev_device_get_subsystem (priv->udev_device); } /** * fu_udev_device_get_sysfs_path: * @self: A #GUdevDevice * * Gets the device sysfs path, e.g. "/sys/devices/pci0000:00/0000:00:14.0". * * Returns: a local path, or NULL if unset or invalid * * Since: 1.1.2 **/ const gchar * fu_udev_device_get_sysfs_path (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), NULL); return g_udev_device_get_sysfs_path (priv->udev_device); } /** * fu_udev_device_get_vendor: * @self: A #GUdevDevice * * Gets the device vendor code. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.1.2 **/ guint16 fu_udev_device_get_vendor (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), 0x0000); return priv->vendor; } /** * fu_udev_device_get_model: * @self: A #GUdevDevice * * Gets the device device code. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.1.2 **/ guint16 fu_udev_device_get_model (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), 0x0000); return priv->model; } /** * fu_udev_device_get_revision: * @self: A #GUdevDevice * * Gets the device revision. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.1.2 **/ guint8 fu_udev_device_get_revision (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), 0x00); return priv->revision; } static GString * fu_udev_device_get_parent_subsystems (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); GString *str = g_string_new (NULL); g_autoptr(GUdevDevice) udev_device = g_object_ref (priv->udev_device); /* find subsystems of all parent devices */ while (TRUE) { g_autoptr(GUdevDevice) parent = g_udev_device_get_parent (udev_device); if (parent == NULL) break; if (g_udev_device_get_subsystem (parent) != NULL) { g_string_append_printf (str, "%s,", g_udev_device_get_subsystem (parent)); } g_set_object (&udev_device, g_steal_pointer (&parent)); } if (str->len > 0) g_string_truncate (str, str->len - 1); return str; } /** * fu_udev_device_set_physical_id: * @self: A #GUdevDevice * @subsystem: A subsystem string, e.g. `usb` * @error: A #GError, or %NULL * * Sets the physical ID from the device subsystem. Plugins should choose the * subsystem that is "deepest" in the udev tree, for instance choosing 'usb' * over 'pci' for a mouse device. * * Returns: %TRUE if the physical device was set. * * Since: 1.1.2 **/ gboolean fu_udev_device_set_physical_id (FuUdevDevice *self, const gchar *subsystem, GError **error) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); const gchar *tmp; g_autofree gchar *physical_id = NULL; g_autoptr(GUdevDevice) udev_device = NULL; g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), FALSE); g_return_val_if_fail (subsystem != NULL, FALSE); /* get the correct device */ if (g_strcmp0 (g_udev_device_get_subsystem (priv->udev_device), subsystem) == 0) { udev_device = g_object_ref (priv->udev_device); } else { udev_device = g_udev_device_get_parent_with_subsystem (priv->udev_device, subsystem, NULL); if (udev_device == NULL) { g_autoptr(GString) str = NULL; str = fu_udev_device_get_parent_subsystems (self); g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find device with subsystem %s, only got %s", subsystem, str->str); return FALSE; } } if (g_strcmp0 (subsystem, "pci") == 0) { tmp = g_udev_device_get_property (udev_device, "PCI_SLOT_NAME"); if (tmp == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find PCI_SLOT_NAME"); return FALSE; } physical_id = g_strdup_printf ("PCI_SLOT_NAME=%s", tmp); } else if (g_strcmp0 (subsystem, "usb") == 0 || g_strcmp0 (subsystem, "scsi") == 0) { tmp = g_udev_device_get_property (udev_device, "DEVPATH"); if (tmp == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find DEVPATH"); return FALSE; } physical_id = g_strdup_printf ("DEVPATH=%s", tmp); } else if (g_strcmp0 (subsystem, "hid") == 0) { tmp = g_udev_device_get_property (udev_device, "HID_PHYS"); if (tmp == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find HID_PHYS"); return FALSE; } physical_id = g_strdup_printf ("HID_PHYS=%s", tmp); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot handle subsystem %s", subsystem); return FALSE; } /* success */ fu_device_set_physical_id (FU_DEVICE (self), physical_id); return TRUE; } static void fu_udev_device_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuUdevDevice *self = FU_UDEV_DEVICE (object); FuUdevDevicePrivate *priv = GET_PRIVATE (self); switch (prop_id) { case PROP_UDEV_DEVICE: g_value_set_object (value, priv->udev_device); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_udev_device_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuUdevDevice *self = FU_UDEV_DEVICE (object); switch (prop_id) { case PROP_UDEV_DEVICE: fu_udev_device_set_dev (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_udev_device_finalize (GObject *object) { FuUdevDevice *self = FU_UDEV_DEVICE (object); FuUdevDevicePrivate *priv = GET_PRIVATE (self); if (priv->udev_device != NULL) g_object_unref (priv->udev_device); G_OBJECT_CLASS (fu_udev_device_parent_class)->finalize (object); } static void fu_udev_device_init (FuUdevDevice *self) { } static void fu_udev_device_class_init (FuUdevDeviceClass *klass) { FuDeviceClass *device_class = FU_DEVICE_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; object_class->finalize = fu_udev_device_finalize; object_class->get_property = fu_udev_device_get_property; object_class->set_property = fu_udev_device_set_property; device_class->probe = fu_udev_device_probe; device_class->incorporate = fu_udev_device_incorporate; signals[SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); pspec = g_param_spec_object ("udev-device", NULL, NULL, G_UDEV_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_UDEV_DEVICE, pspec); } /** * fu_udev_device_new: * @udev_device: A #GUdevDevice * * Creates a new #FuUdevDevice. * * Returns: (transfer full): a #FuUdevDevice * * Since: 1.1.2 **/ FuUdevDevice * fu_udev_device_new (GUdevDevice *udev_device) { FuUdevDevice *self = g_object_new (FU_TYPE_UDEV_DEVICE, "udev-device", udev_device, NULL); return FU_UDEV_DEVICE (self); } fwupd-1.2.14/src/fu-udev-device.h000066400000000000000000000023111402665037500165010ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_UDEV_DEVICE (fu_udev_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuUdevDevice, fu_udev_device, FU, UDEV_DEVICE, FuDevice) struct _FuUdevDeviceClass { FuDeviceClass parent_class; gboolean (*probe) (FuUdevDevice *device, GError **error); gpointer __reserved[31]; }; FuUdevDevice *fu_udev_device_new (GUdevDevice *udev_device); GUdevDevice *fu_udev_device_get_dev (FuUdevDevice *self); const gchar *fu_udev_device_get_sysfs_path (FuUdevDevice *self); const gchar *fu_udev_device_get_subsystem (FuUdevDevice *self); guint16 fu_udev_device_get_vendor (FuUdevDevice *self); guint16 fu_udev_device_get_model (FuUdevDevice *self); guint8 fu_udev_device_get_revision (FuUdevDevice *self); guint fu_udev_device_get_slot_depth (FuUdevDevice *self, const gchar *subsystem); gboolean fu_udev_device_set_physical_id (FuUdevDevice *self, const gchar *subsystem, GError **error); void fu_udev_device_dump (FuUdevDevice *self); G_END_DECLS fwupd-1.2.14/src/fu-usb-device-private.h000066400000000000000000000003631402665037500200040ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-usb-device.h" G_BEGIN_DECLS const gchar *fu_usb_device_get_platform_id (FuUsbDevice *self); G_END_DECLS fwupd-1.2.14/src/fu-usb-device.c000066400000000000000000000310651402665037500163320ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuUsbDevice" #include "config.h" #include "fu-device-private.h" #include "fu-usb-device-private.h" /** * SECTION:fu-device * @short_description: a USB device * * An object that represents a USB device. * * See also: #FuDevice */ typedef struct { GUsbDevice *usb_device; FuDeviceLocker *usb_device_locker; } FuUsbDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuUsbDevice, fu_usb_device, FU_TYPE_DEVICE) enum { PROP_0, PROP_USB_DEVICE, PROP_LAST }; #define GET_PRIVATE(o) (fu_usb_device_get_instance_private (o)) static void fu_usb_device_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuUsbDevice *device = FU_USB_DEVICE (object); FuUsbDevicePrivate *priv = GET_PRIVATE (device); switch (prop_id) { case PROP_USB_DEVICE: g_value_set_object (value, priv->usb_device); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_usb_device_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuUsbDevice *device = FU_USB_DEVICE (object); switch (prop_id) { case PROP_USB_DEVICE: fu_usb_device_set_dev (device, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void fu_usb_device_finalize (GObject *object) { FuUsbDevice *device = FU_USB_DEVICE (object); FuUsbDevicePrivate *priv = GET_PRIVATE (device); if (priv->usb_device_locker != NULL) g_object_unref (priv->usb_device_locker); if (priv->usb_device != NULL) g_object_unref (priv->usb_device); G_OBJECT_CLASS (fu_usb_device_parent_class)->finalize (object); } static void fu_usb_device_init (FuUsbDevice *device) { } /** * fu_usb_device_is_open: * @device: A #FuUsbDevice * * Finds out if a USB device is currently open. * * Returns: %TRUE if the device is open. * * Since: 1.0.3 **/ gboolean fu_usb_device_is_open (FuUsbDevice *device) { FuUsbDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FU_IS_USB_DEVICE (device), FALSE); return priv->usb_device_locker != NULL; } static gboolean fu_usb_device_open (FuDevice *device, GError **error) { FuUsbDevice *self = FU_USB_DEVICE (device); FuUsbDevicePrivate *priv = GET_PRIVATE (self); FuUsbDeviceClass *klass = FU_USB_DEVICE_GET_CLASS (device); guint idx; g_autoptr(FuDeviceLocker) locker = NULL; g_return_val_if_fail (FU_IS_USB_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* already open */ if (priv->usb_device_locker != NULL) return TRUE; /* open */ locker = fu_device_locker_new (priv->usb_device, error); if (locker == NULL) return FALSE; /* get vendor */ if (fu_device_get_vendor (device) == NULL) { idx = g_usb_device_get_manufacturer_index (priv->usb_device); if (idx != 0x00) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; tmp = g_usb_device_get_string_descriptor (priv->usb_device, idx, &error_local); if (tmp != NULL) fu_device_set_vendor (device, g_strchomp (tmp)); else g_debug ("failed to load manufacturer string for usb device %u:%u: %s", g_usb_device_get_bus (priv->usb_device), g_usb_device_get_address (priv->usb_device), error_local->message); } } /* get product */ if (fu_device_get_name (device) == NULL) { idx = g_usb_device_get_product_index (priv->usb_device); if (idx != 0x00) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; tmp = g_usb_device_get_string_descriptor (priv->usb_device, idx, &error_local); if (tmp != NULL) fu_device_set_name (device, g_strchomp (tmp)); else g_debug ("failed to load product string for usb device %u:%u: %s", g_usb_device_get_bus (priv->usb_device), g_usb_device_get_address (priv->usb_device), error_local->message); } } /* get serial number */ if (fu_device_get_serial (device) == NULL) { idx = g_usb_device_get_serial_number_index (priv->usb_device); if (idx != 0x00) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; tmp = g_usb_device_get_string_descriptor (priv->usb_device, idx, &error_local); if (tmp != NULL) fu_device_set_serial (device, g_strchomp (tmp)); else g_debug ("failed to load serial number string for usb device %u:%u: %s", g_usb_device_get_bus (priv->usb_device), g_usb_device_get_address (priv->usb_device), error_local->message); } } /* get version number, falling back to the USB device release */ idx = g_usb_device_get_custom_index (priv->usb_device, G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, 'F', 'W', NULL); if (idx != 0x00) { g_autofree gchar *tmp = NULL; tmp = g_usb_device_get_string_descriptor (priv->usb_device, idx, NULL); /* although guessing is a route to insanity, if the device has * provided the extra data it's because the BCD type was not * suitable -- and INTEL_ME is not relevant here */ fu_device_set_version (device, tmp, fu_common_version_guess_format (tmp)); } /* get GUID from the descriptor if set */ idx = g_usb_device_get_custom_index (priv->usb_device, G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, 'G', 'U', NULL); if (idx != 0x00) { g_autofree gchar *tmp = NULL; tmp = g_usb_device_get_string_descriptor (priv->usb_device, idx, NULL); fu_device_add_guid (device, tmp); } /* subclassed */ if (klass->open != NULL) { if (!klass->open (self, error)) return FALSE; } /* success */ priv->usb_device_locker = g_steal_pointer (&locker); return TRUE; } static gboolean fu_usb_device_close (FuDevice *device, GError **error) { FuUsbDevice *self = FU_USB_DEVICE (device); FuUsbDevicePrivate *priv = GET_PRIVATE (self); FuUsbDeviceClass *klass = FU_USB_DEVICE_GET_CLASS (device); g_return_val_if_fail (FU_IS_USB_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* already open */ if (priv->usb_device_locker == NULL) return TRUE; /* subclassed */ if (klass->close != NULL) { if (!klass->close (self, error)) return FALSE; } g_clear_object (&priv->usb_device_locker); return TRUE; } static gboolean fu_usb_device_probe (FuDevice *device, GError **error) { FuUsbDevice *self = FU_USB_DEVICE (device); FuUsbDeviceClass *klass = FU_USB_DEVICE_GET_CLASS (device); FuUsbDevicePrivate *priv = GET_PRIVATE (self); guint16 release; g_autofree gchar *devid0 = NULL; g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; g_autofree gchar *vendor_id = NULL; g_autoptr(GPtrArray) intfs = NULL; /* set vendor ID */ vendor_id = g_strdup_printf ("USB:0x%04X", g_usb_device_get_vid (priv->usb_device)); fu_device_set_vendor_id (device, vendor_id); /* set the version if the release has been set */ release = g_usb_device_get_release (priv->usb_device); if (release != 0x0) { g_autofree gchar *version = NULL; version = fu_common_version_from_uint16 (release, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version (device, version, FWUPD_VERSION_FORMAT_BCD); } /* add GUIDs in order of priority */ devid2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&REV_%04X", g_usb_device_get_vid (priv->usb_device), g_usb_device_get_pid (priv->usb_device), release); fu_device_add_instance_id (device, devid2); devid1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X", g_usb_device_get_vid (priv->usb_device), g_usb_device_get_pid (priv->usb_device)); fu_device_add_instance_id (device, devid1); devid0 = g_strdup_printf ("USB\\VID_%04X", g_usb_device_get_vid (priv->usb_device)); fu_device_add_instance_id_full (device, devid0, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); /* add the interface GUIDs */ intfs = g_usb_device_get_interfaces (priv->usb_device, error); if (intfs == NULL) return FALSE; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index (intfs, i); g_autofree gchar *intid1 = NULL; g_autofree gchar *intid2 = NULL; g_autofree gchar *intid3 = NULL; intid1 = g_strdup_printf ("USB\\CLASS_%02X&SUBCLASS_%02X&PROT_%02X", g_usb_interface_get_class (intf), g_usb_interface_get_subclass (intf), g_usb_interface_get_protocol (intf)); fu_device_add_instance_id_full (device, intid1, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); intid2 = g_strdup_printf ("USB\\CLASS_%02X&SUBCLASS_%02X", g_usb_interface_get_class (intf), g_usb_interface_get_subclass (intf)); fu_device_add_instance_id_full (device, intid2, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); intid3 = g_strdup_printf ("USB\\CLASS_%02X", g_usb_interface_get_class (intf)); fu_device_add_instance_id_full (device, intid3, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* subclassed */ if (klass->probe != NULL) { if (!klass->probe (self, error)) return FALSE; } /* success */ return TRUE; } /** * fu_usb_device_get_vid: * @self: A #FuUsbDevice * * Gets the device vendor code. * * Returns: integer, or 0x0 if unset or invalid * * Since: 1.1.2 **/ guint16 fu_usb_device_get_vid (FuUsbDevice *self) { FuUsbDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_USB_DEVICE (self), 0x0000); if (priv->usb_device == NULL) return 0x0; return g_usb_device_get_vid (priv->usb_device); } /** * fu_usb_device_get_pid: * @self: A #FuUsbDevice * * Gets the device product code. * * Returns: integer, or 0x0 if unset or invalid * * Since: 1.1.2 **/ guint16 fu_usb_device_get_pid (FuUsbDevice *self) { FuUsbDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_USB_DEVICE (self), 0x0000); if (priv->usb_device == NULL) return 0x0; return g_usb_device_get_pid (priv->usb_device); } /** * fu_usb_device_get_platform_id: * @self: A #FuUsbDevice * * Gets the device platform ID. * * Returns: string, or NULL if unset or invalid * * Since: 1.1.2 **/ const gchar * fu_usb_device_get_platform_id (FuUsbDevice *self) { FuUsbDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_USB_DEVICE (self), NULL); if (priv->usb_device == NULL) return NULL; return g_usb_device_get_platform_id (priv->usb_device); } /** * fu_usb_device_set_dev: * @device: A #FuUsbDevice * @usb_device: A #GUsbDevice, or %NULL * * Sets the #GUsbDevice to use. * * Since: 1.0.2 **/ void fu_usb_device_set_dev (FuUsbDevice *device, GUsbDevice *usb_device) { FuUsbDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FU_IS_USB_DEVICE (device)); /* need to re-probe hardware */ fu_device_probe_invalidate (FU_DEVICE (device)); /* allow replacement */ g_set_object (&priv->usb_device, usb_device); if (usb_device == NULL) { g_clear_object (&priv->usb_device_locker); return; } /* set device ID automatically */ fu_device_set_physical_id (FU_DEVICE (device), g_usb_device_get_platform_id (usb_device)); } /** * fu_usb_device_get_dev: * @device: A #FuUsbDevice * * Gets the #GUsbDevice. * * Returns: (transfer none): a #GUsbDevice, or %NULL * * Since: 1.0.2 **/ GUsbDevice * fu_usb_device_get_dev (FuUsbDevice *device) { FuUsbDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FU_IS_USB_DEVICE (device), NULL); return priv->usb_device; } static void fu_usb_device_incorporate (FuDevice *self, FuDevice *donor) { g_return_if_fail (FU_IS_USB_DEVICE (self)); g_return_if_fail (FU_IS_USB_DEVICE (donor)); fu_usb_device_set_dev (FU_USB_DEVICE (self), fu_usb_device_get_dev (FU_USB_DEVICE (donor))); } /** * fu_usb_device_new: * @usb_device: A #GUsbDevice * * Creates a new #FuUsbDevice. * * Returns: (transfer full): a #FuUsbDevice * * Since: 1.0.2 **/ FuUsbDevice * fu_usb_device_new (GUsbDevice *usb_device) { FuUsbDevice *device = g_object_new (FU_TYPE_USB_DEVICE, NULL); fu_usb_device_set_dev (device, usb_device); return FU_USB_DEVICE (device); } static void fu_usb_device_class_init (FuUsbDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *device_class = FU_DEVICE_CLASS (klass); GParamSpec *pspec; object_class->finalize = fu_usb_device_finalize; object_class->get_property = fu_usb_device_get_property; object_class->set_property = fu_usb_device_set_property; device_class->open = fu_usb_device_open; device_class->close = fu_usb_device_close; device_class->probe = fu_usb_device_probe; device_class->incorporate = fu_usb_device_incorporate; pspec = g_param_spec_object ("usb-device", NULL, NULL, G_USB_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_USB_DEVICE, pspec); } fwupd-1.2.14/src/fu-usb-device.h000066400000000000000000000023351402665037500163350ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-plugin.h" G_BEGIN_DECLS #define FU_TYPE_USB_DEVICE (fu_usb_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuUsbDevice, fu_usb_device, FU, USB_DEVICE, FuDevice) /* HID */ #define HID_REPORT_GET 0x01 #define HID_REPORT_SET 0x09 #define HID_REPORT_TYPE_INPUT 0x01 #define HID_REPORT_TYPE_OUTPUT 0x02 #define HID_REPORT_TYPE_FEATURE 0x03 #define HID_FEATURE 0x0300 struct _FuUsbDeviceClass { FuDeviceClass parent_class; gboolean (*open) (FuUsbDevice *device, GError **error); gboolean (*close) (FuUsbDevice *device, GError **error); gboolean (*probe) (FuUsbDevice *device, GError **error); gpointer __reserved[28]; }; FuUsbDevice *fu_usb_device_new (GUsbDevice *usb_device); guint16 fu_usb_device_get_vid (FuUsbDevice *self); guint16 fu_usb_device_get_pid (FuUsbDevice *self); GUsbDevice *fu_usb_device_get_dev (FuUsbDevice *device); void fu_usb_device_set_dev (FuUsbDevice *device, GUsbDevice *usb_device); gboolean fu_usb_device_is_open (FuUsbDevice *device); G_END_DECLS fwupd-1.2.14/src/fu-util-common.c000066400000000000000000000425261402665037500165530ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include "fu-util-common.h" #include "fu-device.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #endif #define SYSTEMD_FWUPD_UNIT "fwupd.service" #define SYSTEMD_SNAP_FWUPD_UNIT "snap.fwupd.fwupd.service" const gchar * fu_util_get_systemd_unit (void) { if (g_getenv ("SNAP") != NULL) return SYSTEMD_SNAP_FWUPD_UNIT; return SYSTEMD_FWUPD_UNIT; } #ifdef HAVE_SYSTEMD static const gchar * fu_util_get_expected_command (const gchar *target) { if (g_strcmp0 (target, SYSTEMD_SNAP_FWUPD_UNIT) == 0) return "fwupd.fwupdmgr"; return "fwupdmgr"; } #endif gboolean fu_util_using_correct_daemon (GError **error) { #ifdef HAVE_SYSTEMD g_autofree gchar *default_target = NULL; g_autoptr(GError) error_local = NULL; const gchar *target = fu_util_get_systemd_unit (); default_target = fu_systemd_get_default_target (&error_local); if (default_target == NULL) { g_debug ("Systemd isn't accessible: %s\n", error_local->message); return TRUE; } if (!fu_systemd_unit_check_exists (target, &error_local)) { g_debug ("wrong target: %s\n", error_local->message); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATORS: error message */ _("Mismatched daemon and client, use %s instead"), fu_util_get_expected_command (target)); return FALSE; } #endif return TRUE; } void fu_util_print_data (const gchar *title, const gchar *msg) { gsize title_len; g_auto(GStrv) lines = NULL; if (msg == NULL) return; g_print ("%s:", title); /* pad */ title_len = strlen (title) + 1; lines = g_strsplit (msg, "\n", -1); for (guint j = 0; lines[j] != NULL; j++) { for (gsize i = title_len; i < 25; i++) g_print (" "); g_print ("%s\n", lines[j]); title_len = 0; } } guint fu_util_prompt_for_number (guint maxnum) { gint retval; guint answer = 0; do { char buffer[64]; /* swallow the \n at end of line too */ if (!fgets (buffer, sizeof (buffer), stdin)) break; if (strlen (buffer) == sizeof (buffer) - 1) continue; /* get a number */ retval = sscanf (buffer, "%u", &answer); /* positive */ if (retval == 1 && answer <= maxnum) break; /* TRANSLATORS: the user isn't reading the question */ g_print (_("Please enter a number from 0 to %u: "), maxnum); } while (TRUE); return answer; } gboolean fu_util_prompt_for_boolean (gboolean def) { do { char buffer[4]; if (!fgets (buffer, sizeof (buffer), stdin)) continue; if (strlen (buffer) == sizeof (buffer) - 1) continue; if (g_strcmp0 (buffer, "\n") == 0) return def; buffer[0] = g_ascii_toupper (buffer[0]); if (g_strcmp0 (buffer, "Y\n") == 0) return TRUE; if (g_strcmp0 (buffer, "N\n") == 0) return FALSE; } while (TRUE); return FALSE; } gboolean fu_util_print_device_tree (GNode *n, gpointer data) { FwupdDevice *dev = FWUPD_DEVICE (n->data); const gchar *name; g_autoptr(GString) str = g_string_new (NULL); /* root node */ if (dev == NULL) { g_print ("○\n"); return FALSE; } /* add previous branches */ for (GNode *c = n->parent; c->parent != NULL; c = c->parent) { if (g_node_next_sibling (c) == NULL) g_string_prepend (str, " "); else g_string_prepend (str, "│ "); } /* add this branch */ if (g_node_last_sibling (n) == n) g_string_append (str, "└─ "); else g_string_append (str, "├─ "); /* dump to the console */ name = fwupd_device_get_name (dev); if (name == NULL) name = "Unknown device"; g_string_append (str, name); for (guint i = strlen (name) + 2 * g_node_depth (n); i < 45; i++) g_string_append_c (str, ' '); g_print ("%s %s\n", str->str, fu_device_get_id (dev)); return FALSE; } gboolean fu_util_is_interesting_device (FwupdDevice *dev) { if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE)) return TRUE; if (fwupd_device_get_update_error (dev) != NULL) return TRUE; return FALSE; } gchar * fu_util_get_user_cache_path (const gchar *fn) { g_autofree gchar *basename = g_path_get_basename (fn); g_autofree gchar *cachedir_legacy = NULL; /* return the legacy path if it exists rather than renaming it to * prevent problems when using old and new versions of fwupd */ cachedir_legacy = g_build_filename (g_get_user_cache_dir (), "fwupdmgr", NULL); if (g_file_test (cachedir_legacy, G_FILE_TEST_IS_DIR)) return g_build_filename (cachedir_legacy, basename, NULL); return g_build_filename (g_get_user_cache_dir (), "fwupd", basename, NULL); } gchar * fu_util_get_versions (void) { GString *string = g_string_new (""); g_string_append_printf (string, "client version:\t%i.%i.%i\n", FWUPD_MAJOR_VERSION, FWUPD_MINOR_VERSION, FWUPD_MICRO_VERSION); #ifdef FWUPD_GIT_DESCRIBE g_string_append_printf (string, "checkout info:\t%s\n", FWUPD_GIT_DESCRIBE); #endif g_string_append_printf (string, "compile-time dependency versions\n"); g_string_append_printf (string, "\tgusb:\t%d.%d.%d\n", G_USB_MAJOR_VERSION, G_USB_MINOR_VERSION, G_USB_MICRO_VERSION); #ifdef EFIVAR_LIBRARY_VERSION g_string_append_printf (string, "\tefivar:\t%s", EFIVAR_LIBRARY_VERSION); #endif return g_string_free (string, FALSE); } static gboolean fu_util_update_shutdown (GError **error) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GVariant) val = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) return FALSE; #ifdef HAVE_LOGIND /* shutdown using logind */ val = g_dbus_connection_call_sync (connection, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "PowerOff", g_variant_new ("(b)", TRUE), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #elif defined(HAVE_CONSOLEKIT) /* shutdown using ConsoleKit */ val = g_dbus_connection_call_sync (connection, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "Stop", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "No supported backend compiled in to perform the operation."); #endif return val != NULL; } gboolean fu_util_update_reboot (GError **error) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GVariant) val = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) return FALSE; #ifdef HAVE_LOGIND /* reboot using logind */ val = g_dbus_connection_call_sync (connection, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "Reboot", g_variant_new ("(b)", TRUE), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #elif defined(HAVE_CONSOLEKIT) /* reboot using ConsoleKit */ val = g_dbus_connection_call_sync (connection, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "Restart", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "No supported backend compiled in to perform the operation."); #endif return val != NULL; } gboolean fu_util_prompt_complete (FwupdDeviceFlags flags, gboolean prompt, GError **error) { if (flags & FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) { if (prompt) { g_print ("\n%s %s [Y|n]: ", /* TRANSLATORS: explain why we want to shutdown */ _("An update requires the system to shutdown to complete."), /* TRANSLATORS: shutdown to apply the update */ _("Shutdown now?")); if (!fu_util_prompt_for_boolean (TRUE)) return TRUE; } return fu_util_update_shutdown (error); } if (flags & FWUPD_DEVICE_FLAG_NEEDS_REBOOT) { if (prompt) { g_print ("\n%s %s [Y|n]: ", /* TRANSLATORS: explain why we want to reboot */ _("An update requires a reboot to complete."), /* TRANSLATORS: reboot to apply the update */ _("Restart now?")); if (!fu_util_prompt_for_boolean (TRUE)) return TRUE; } return fu_util_update_reboot (error); } return TRUE; } static void fu_util_cmd_free (FuUtilCmd *item) { g_free (item->name); g_free (item->arguments); g_free (item->description); g_free (item); } GPtrArray * fu_util_cmd_array_new (void) { return g_ptr_array_new_with_free_func ((GDestroyNotify) fu_util_cmd_free); } static gint fu_util_cmd_sort_cb (FuUtilCmd **item1, FuUtilCmd **item2) { return g_strcmp0 ((*item1)->name, (*item2)->name); } void fu_util_cmd_array_sort (GPtrArray *array) { g_ptr_array_sort (array, (GCompareFunc) fu_util_cmd_sort_cb); } void fu_util_cmd_array_add (GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, FuUtilCmdFunc callback) { g_auto(GStrv) names = NULL; g_return_if_fail (name != NULL); g_return_if_fail (description != NULL); g_return_if_fail (callback != NULL); /* add each one */ names = g_strsplit (name, ",", -1); for (guint i = 0; names[i] != NULL; i++) { FuUtilCmd *item = g_new0 (FuUtilCmd, 1); item->name = g_strdup (names[i]); if (i == 0) { item->description = g_strdup (description); } else { /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */ item->description = g_strdup_printf (_("Alias to %s"), names[0]); } item->arguments = g_strdup (arguments); item->callback = callback; g_ptr_array_add (array, item); } } gboolean fu_util_cmd_array_run (GPtrArray *array, FuUtilPrivate *priv, const gchar *command, gchar **values, GError **error) { /* find command */ for (guint i = 0; i < array->len; i++) { FuUtilCmd *item = g_ptr_array_index (array, i); if (g_strcmp0 (item->name, command) == 0) return item->callback (priv, values, error); } /* not found */ g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATORS: error message */ _("Command not found")); return FALSE; } gchar * fu_util_cmd_array_to_string (GPtrArray *array) { gsize len; const gsize max_len = 35; GString *string; /* print each command */ string = g_string_new (""); for (guint i = 0; i < array->len; i++) { FuUtilCmd *item = g_ptr_array_index (array, i); g_string_append (string, " "); g_string_append (string, item->name); len = strlen (item->name) + 2; if (item->arguments != NULL) { g_string_append (string, " "); g_string_append (string, item->arguments); len += strlen (item->arguments) + 1; } if (len < max_len) { for (gsize j = len; j < max_len + 1; j++) g_string_append_c (string, ' '); g_string_append (string, item->description); g_string_append_c (string, '\n'); } else { g_string_append_c (string, '\n'); for (gsize j = 0; j < max_len + 1; j++) g_string_append_c (string, ' '); g_string_append (string, item->description); g_string_append_c (string, '\n'); } } /* remove trailing newline */ if (string->len > 0) g_string_set_size (string, string->len - 1); return g_string_free (string, FALSE); } SoupSession * fu_util_setup_networking (GError **error) { const gchar *http_proxy; g_autofree gchar *user_agent = NULL; g_autoptr(SoupSession) session = NULL; /* create the soup session */ user_agent = fwupd_build_user_agent (PACKAGE_NAME, PACKAGE_VERSION); session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent, SOUP_SESSION_TIMEOUT, 60, NULL); if (session == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to setup networking"); return NULL; } /* set the proxy */ http_proxy = g_getenv ("https_proxy"); if (http_proxy == NULL) http_proxy = g_getenv ("HTTPS_PROXY"); if (http_proxy == NULL) http_proxy = g_getenv ("http_proxy"); if (http_proxy == NULL) http_proxy = g_getenv ("HTTP_PROXY"); if (http_proxy != NULL && strlen (http_proxy) > 0) { g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy); if (proxy_uri == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid proxy URI: %s", http_proxy); return NULL; } g_object_set (session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL); } /* this disables the double-compression of the firmware.xml.gz file */ soup_session_remove_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER); return g_steal_pointer (&session); } gchar * fu_util_release_get_name (FwupdRelease *release) { const gchar *name = fwupd_release_get_name (release); GPtrArray *cats = fwupd_release_get_categories (release); for (guint i = 0; i < cats->len; i++) { const gchar *cat = g_ptr_array_index (cats, i); if (g_strcmp0 (cat, "X-Device") == 0) { /* TRANSLATORS: a specific part of hardware, * the first %s is the device name, e.g. 'Unifying Receiver` */ return g_strdup_printf (_("%s Device Update"), name); } if (g_strcmp0 (cat, "X-System") == 0) { /* TRANSLATORS: the entire system, e.g. all internal devices, * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s System Update"), name); } if (g_strcmp0 (cat, "X-EmbeddedController") == 0) { /* TRANSLATORS: the EC is typically the keyboard controller chip, * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s Embedded Controller Update"), name); } if (g_strcmp0 (cat, "X-ManagementEngine") == 0) { /* TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s ME Update"), name); } if (g_strcmp0 (cat, "X-CorporateManagementEngine") == 0) { /* TRANSLATORS: ME stands for Management Engine (with Intel AMT), * where the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s Corporate ME Update"), name); } if (g_strcmp0 (cat, "X-ConsumerManagementEngine") == 0) { /* TRANSLATORS: ME stands for Management Engine, where * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s Consumer ME Update"), name); } if (g_strcmp0 (cat, "X-Controller") == 0) { /* TRANSLATORS: the controller is a device that has other devices * plugged into it, for example ThunderBolt, FireWire or USB, * the first %s is the device name, e.g. 'Intel ThunderBolt` */ return g_strdup_printf (_("%s Controller Update"), name); } if (g_strcmp0 (cat, "X-ThunderboltController") == 0) { /* TRANSLATORS: the Thunderbolt controller is a device that * has other high speed Thunderbolt devices plugged into it; * the first %s is the system name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s Thunderbolt Controller Update"), name); } } /* TRANSLATORS: this is the fallback where we don't know if the release * is updating the system, the device, or a device class, or something else -- * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s Update"), name); } static GPtrArray * fu_util_strsplit_words (const gchar *text, guint line_len) { g_auto(GStrv) tokens = NULL; g_autoptr(GPtrArray) lines = g_ptr_array_new (); g_autoptr(GString) curline = g_string_new (NULL); /* sanity check */ if (text == NULL || text[0] == '\0') return NULL; if (line_len == 0) return NULL; /* tokenize the string */ tokens = g_strsplit (text, " ", -1); for (guint i = 0; tokens[i] != NULL; i++) { /* current line plus new token is okay */ if (curline->len + strlen (tokens[i]) < line_len) { g_string_append_printf (curline, "%s ", tokens[i]); continue; } /* too long, so remove space, add newline and dump */ if (curline->len > 0) g_string_truncate (curline, curline->len - 1); g_ptr_array_add (lines, g_strdup (curline->str)); g_string_truncate (curline, 0); g_string_append_printf (curline, "%s ", tokens[i]); } /* any incomplete line? */ if (curline->len > 0) { g_string_truncate (curline, curline->len - 1); g_ptr_array_add (lines, g_strdup (curline->str)); } return g_steal_pointer (&lines); } static void fu_util_warning_box_line (const gchar *start, const gchar *text, const gchar *end, const gchar *padding, guint width) { guint offset = 0; if (start != NULL) { offset += g_utf8_strlen (start, -1); g_print ("%s", start); } if (text != NULL) { offset += g_utf8_strlen (text, -1); g_print ("%s", text); } if (end != NULL) offset += g_utf8_strlen (end, -1); for (guint i = offset; i < width; i++) g_print ("%s", padding); if (end != NULL) g_print ("%s\n", end); } void fu_util_warning_box (const gchar *str, guint width) { g_auto(GStrv) split = g_strsplit (str, "\n", -1); /* header */ fu_util_warning_box_line ("╔", NULL, "╗", "═", width); /* body */ for (guint i = 0; split[i] != NULL; i++) { g_autoptr(GPtrArray) lines = fu_util_strsplit_words (split[i], width - 4); if (lines == NULL) continue; for (guint j = 0; j < lines->len; j++) { const gchar *line = g_ptr_array_index (lines, j); fu_util_warning_box_line ("║ ", line, " ║", " ", width); } fu_util_warning_box_line ("║", NULL, "║", " ", width); } /* footer */ fu_util_warning_box_line ("╚", NULL, "╝", "═", width); } fwupd-1.2.14/src/fu-util-common.h000066400000000000000000000035621402665037500165550ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include G_BEGIN_DECLS /* this is only valid for tools */ #define FWUPD_ERROR_INVALID_ARGS (FWUPD_ERROR_LAST+1) typedef struct FuUtilPrivate FuUtilPrivate; typedef gboolean (*FuUtilCmdFunc) (FuUtilPrivate *util, gchar **values, GError **error); typedef struct { gchar *name; gchar *arguments; gchar *description; FuUtilCmdFunc callback; } FuUtilCmd; void fu_util_print_data (const gchar *title, const gchar *msg); guint fu_util_prompt_for_number (guint maxnum); gboolean fu_util_prompt_for_boolean (gboolean def); gboolean fu_util_print_device_tree (GNode *n, gpointer data); gboolean fu_util_is_interesting_device (FwupdDevice *dev); gchar *fu_util_get_user_cache_path (const gchar *fn); SoupSession *fu_util_setup_networking (GError **error); gchar *fu_util_get_versions (void); void fu_util_warning_box (const gchar *str, guint width); gboolean fu_util_prompt_complete (FwupdDeviceFlags flags, gboolean prompt, GError **error); gboolean fu_util_update_reboot (GError **error); GPtrArray *fu_util_cmd_array_new (void); void fu_util_cmd_array_add (GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, FuUtilCmdFunc callback); gchar *fu_util_cmd_array_to_string (GPtrArray *array); void fu_util_cmd_array_sort (GPtrArray *array); gboolean fu_util_cmd_array_run (GPtrArray *array, FuUtilPrivate *priv, const gchar *command, gchar **values, GError **error); gchar *fu_util_release_get_name (FwupdRelease *release); const gchar *fu_util_get_systemd_unit (void); gboolean fu_util_using_correct_daemon (GError **error); G_END_DECLS fwupd-1.2.14/src/fu-util.c000066400000000000000000002343501402665037500152630ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fu-history.h" #include "fu-plugin-private.h" #include "fu-progressbar.h" #include "fu-util-common.h" #include "fwupd-common-private.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #endif /* custom return code */ #define EXIT_NOTHING_TO_DO 2 typedef enum { FU_UTIL_OPERATION_UNKNOWN, FU_UTIL_OPERATION_UPDATE, FU_UTIL_OPERATION_DOWNGRADE, FU_UTIL_OPERATION_INSTALL, FU_UTIL_OPERATION_LAST } FuUtilOperation; struct FuUtilPrivate { GCancellable *cancellable; GMainLoop *loop; GOptionContext *context; SoupSession *soup_session; FwupdInstallFlags flags; FwupdClient *client; FuProgressbar *progressbar; gboolean no_metadata_check; gboolean no_reboot_check; gboolean no_unreported_check; gboolean assume_yes; gboolean sign; gboolean show_all_devices; /* only valid in update and downgrade */ FuUtilOperation current_operation; FwupdDevice *current_device; gchar *current_message; FwupdDeviceFlags completion_flags; }; static gboolean fu_util_report_history (FuUtilPrivate *priv, gchar **values, GError **error); static gboolean fu_util_download_file (FuUtilPrivate *priv, SoupURI *uri, const gchar *fn, const gchar *checksum_expected, GError **error); static void fu_util_client_notify_cb (GObject *object, GParamSpec *pspec, FuUtilPrivate *priv) { fu_progressbar_update (priv->progressbar, fwupd_client_get_status (priv->client), fwupd_client_get_percentage (priv->client)); } static void fu_util_update_device_changed_cb (FwupdClient *client, FwupdDevice *device, FuUtilPrivate *priv) { g_autofree gchar *str = NULL; /* allowed to set whenever the device has changed */ if (fwupd_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; if (fwupd_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; /* same as last time, so ignore */ if (priv->current_device != NULL && fwupd_device_compare (priv->current_device, device) == 0) return; /* show message in progressbar */ if (priv->current_operation == FU_UTIL_OPERATION_UPDATE) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf (_("Updating %s…"), fwupd_device_get_name (device)); fu_progressbar_set_title (priv->progressbar, str); } else if (priv->current_operation == FU_UTIL_OPERATION_DOWNGRADE) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf (_("Downgrading %s…"), fwupd_device_get_name (device)); fu_progressbar_set_title (priv->progressbar, str); } else if (priv->current_operation == FU_UTIL_OPERATION_INSTALL) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf (_("Installing on %s…"), fwupd_device_get_name (device)); fu_progressbar_set_title (priv->progressbar, str); } else { g_warning ("no FuUtilOperation set"); } g_set_object (&priv->current_device, device); if (priv->current_message == NULL) { const gchar *tmp = fwupd_device_get_update_message (priv->current_device); if (tmp != NULL) priv->current_message = g_strdup (tmp); } } static FwupdDevice * fu_util_prompt_for_device (FuUtilPrivate *priv, GError **error) { FwupdDevice *dev; guint idx; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_filtered = NULL; /* get devices from daemon */ devices = fwupd_client_get_devices (priv->client, NULL, error); if (devices == NULL) return NULL; /* filter results */ devices_filtered = g_ptr_array_new (); for (guint i = 0; i < devices->len; i++) { dev = g_ptr_array_index (devices, i); if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; g_ptr_array_add (devices_filtered, dev); } /* nothing */ if (devices_filtered->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No supported devices"); return NULL; } /* exactly one */ if (devices_filtered->len == 1) { dev = g_ptr_array_index (devices_filtered, 0); return g_object_ref (dev); } /* TRANSLATORS: get interactive prompt */ g_print ("%s\n", _("Choose a device:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print ("0.\t%s\n", _("Cancel")); for (guint i = 0; i < devices_filtered->len; i++) { dev = g_ptr_array_index (devices_filtered, i); g_print ("%u.\t%s (%s)\n", i + 1, fwupd_device_get_id (dev), fwupd_device_get_name (dev)); } idx = fu_util_prompt_for_number (devices_filtered->len); if (idx == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return NULL; } dev = g_ptr_array_index (devices_filtered, idx - 1); return g_object_ref (dev); } static gboolean fu_util_perhaps_show_unreported (FuUtilPrivate *priv, GError **error) { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_failed = g_ptr_array_new (); g_autoptr(GPtrArray) devices_success = g_ptr_array_new (); g_autoptr(GPtrArray) remotes = NULL; g_autoptr(GHashTable) remote_id_uri_map = NULL; /* we don't want to ask anything */ if (priv->no_unreported_check) { g_debug ("skipping unreported check"); return TRUE; } /* get all devices from the history database */ devices = fwupd_client_get_history (priv->client, NULL, &error_local); if (devices == NULL) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) return TRUE; g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } /* create a map of RemoteID to RemoteURI */ remotes = fwupd_client_get_remotes (priv->client, NULL, error); if (remotes == NULL) return FALSE; remote_id_uri_map = g_hash_table_new (g_str_hash, g_str_equal); for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); if (fwupd_remote_get_id (remote) == NULL) continue; if (fwupd_remote_get_report_uri (remote) == NULL) continue; g_debug ("adding %s for %s", fwupd_remote_get_report_uri (remote), fwupd_remote_get_id (remote)); g_hash_table_insert (remote_id_uri_map, (gpointer) fwupd_remote_get_id (remote), (gpointer) fwupd_remote_get_report_uri (remote)); } /* check that they can be reported */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); FwupdRelease *rel = fwupd_device_get_release_default (dev); const gchar *remote_id; const gchar *remote_uri; if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_REPORTED)) continue; if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; /* find the RemoteURI to use for the device */ remote_id = fwupd_release_get_remote_id (rel); if (remote_id == NULL) { g_debug ("%s has no RemoteID", fwupd_device_get_id (dev)); continue; } remote_uri = g_hash_table_lookup (remote_id_uri_map, remote_id); if (remote_uri == NULL) { g_debug ("%s has no RemoteURI", remote_id); continue; } /* only send success and failure */ if (fwupd_device_get_update_state (dev) == FWUPD_UPDATE_STATE_FAILED) { g_ptr_array_add (devices_failed, dev); } else if (fwupd_device_get_update_state (dev) == FWUPD_UPDATE_STATE_SUCCESS) { g_ptr_array_add (devices_success, dev); } else { g_debug ("ignoring %s with UpdateState %s", fwupd_device_get_id (dev), fwupd_update_state_to_string (fwupd_device_get_update_state (dev))); } } /* nothing to do */ if (devices_failed->len == 0 && devices_success->len == 0) { g_debug ("no unreported devices"); return TRUE; } /* show the success and failures */ if (!priv->assume_yes) { /* delimit */ g_print ("________________________________________________\n"); /* failures */ if (devices_failed->len > 0) { /* TRANSLATORS: a list of failed updates */ g_print ("\n%s\n\n", _("Devices that were not updated correctly:")); for (guint i = 0; i < devices_failed->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices_failed, i); FwupdRelease *rel = fwupd_device_get_release_default (dev); g_print (" • %s (%s → %s)\n", fwupd_device_get_name (dev), fwupd_device_get_version (dev), fwupd_release_get_version (rel)); } } /* success */ if (devices_success->len > 0) { /* TRANSLATORS: a list of successful updates */ g_print ("\n%s\n\n", _("Devices that have been updated successfully:")); for (guint i = 0; i < devices_success->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices_success, i); FwupdRelease *rel = fwupd_device_get_release_default (dev); g_print (" • %s (%s → %s)\n", fwupd_device_get_name (dev), fwupd_device_get_version (dev), fwupd_release_get_version (rel)); } } /* ask for permission */ g_print ("\n%s\n%s (%s) [Y|n]: ", /* TRANSLATORS: explain why we want to upload */ _("Uploading firmware reports helps hardware vendors" " to quickly identify failing and successful updates" " on real devices."), /* TRANSLATORS: ask the user to upload */ _("Upload report now?"), /* TRANSLATORS: metadata is downloaded from the Internet */ _("Requires internet connection")); if (!fu_util_prompt_for_boolean (TRUE)) return TRUE; } /* success */ return fu_util_report_history (priv, NULL, error); } static gchar * fu_util_convert_appstream_description (const gchar *xml, GError **error) { g_autoptr(GString) str = g_string_new (NULL); g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; /* parse XML */ silo = xb_silo_new_from_xml (xml, error); if (silo == NULL) return NULL; n = xb_silo_get_root (silo); while (n != NULL) { g_autoptr(XbNode) n2 = NULL; /* support

,

    ,
      and
    1. , ignore all else */ if (g_strcmp0 (xb_node_get_element (n), "p") == 0) { g_string_append_printf (str, "%s\n\n", xb_node_get_text (n)); } else if (g_strcmp0 (xb_node_get_element (n), "ul") == 0) { g_autoptr(GPtrArray) children = xb_node_get_children (n); for (guint i = 0; i < children->len; i++) { XbNode *nc = g_ptr_array_index (children, i); if (g_strcmp0 (xb_node_get_element (nc), "li") == 0) { g_string_append_printf (str, " • %s\n", xb_node_get_text (nc)); } } g_string_append (str, "\n"); } else if (g_strcmp0 (xb_node_get_element (n), "ol") == 0) { g_autoptr(GPtrArray) children = xb_node_get_children (n); for (guint i = 0; i < children->len; i++) { XbNode *nc = g_ptr_array_index (children, i); if (g_strcmp0 (xb_node_get_element (nc), "li") == 0) { g_string_append_printf (str, " %u. %s\n", i + 1, xb_node_get_text (nc)); } } g_string_append (str, "\n"); } n2 = xb_node_get_next (n); g_set_object (&n, n2); } /* remove extra newline */ if (str->len > 0) g_string_truncate (str, str->len - 1); /* success */ return g_string_free (g_steal_pointer (&str), FALSE); } static gboolean fu_util_modify_remote_warning (FuUtilPrivate *priv, FwupdRemote *remote, GError **error) { const gchar *warning_markup = NULL; g_autofree gchar *warning_plain = NULL; /* get formatted text */ warning_markup = fwupd_remote_get_agreement (remote); if (warning_markup == NULL) return TRUE; warning_plain = fu_util_convert_appstream_description (warning_markup, error); if (warning_plain == NULL) return FALSE; /* show and ask user to confirm */ fu_util_warning_box (warning_plain, 80); if (!priv->assume_yes) { /* ask for permission */ g_print ("\n%s [Y|n]: ", /* TRANSLATORS: should the remote still be enabled */ _("Agree and enable the remote?")); if (!fu_util_prompt_for_boolean (TRUE)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Declined agreement"); return FALSE; } } return TRUE; } static gboolean fu_util_modify_remote (FuUtilPrivate *priv, const gchar *remote_id, const gchar *key, const gchar *value, GError **error) { g_autoptr(FwupdRemote) remote = NULL; /* ensure the remote exists */ remote = fwupd_client_get_remote_by_id (priv->client, remote_id, NULL, error); if (remote == NULL) return FALSE; /* show some kind of warning when enabling download-type remotes */ if (g_strcmp0 (key, "Enabled") == 0 && g_strcmp0 (value, "true") == 0) { if (!fu_util_modify_remote_warning (priv, remote, error)) return FALSE; } return fwupd_client_modify_remote (priv->client, remote_id, key, value, NULL, error); } static void fu_util_build_device_tree (FuUtilPrivate *priv, GNode *root, GPtrArray *devs, FwupdDevice *dev) { for (guint i = 0; i < devs->len; i++) { FwupdDevice *dev_tmp = g_ptr_array_index (devs, i); if (!priv->show_all_devices && !fu_util_is_interesting_device (dev_tmp)) continue; if (fwupd_device_get_parent (dev_tmp) == dev) { GNode *child = g_node_append_data (root, dev_tmp); fu_util_build_device_tree (priv, child, devs, dev_tmp); } } } static gboolean fu_util_get_topology (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new (NULL); g_autoptr(GPtrArray) devs = NULL; /* get results from daemon */ devs = fwupd_client_get_devices (priv->client, NULL, error); if (devs == NULL) return FALSE; /* print */ if (devs->len == 0) { /* TRANSLATORS: nothing attached that can be upgraded */ g_print ("%s\n", _("No hardware detected with firmware update capability")); return TRUE; } fu_util_build_device_tree (priv, root, devs, NULL); g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, fu_util_print_device_tree, priv); return TRUE; } static gboolean fu_util_get_devices (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devs = NULL; /* get results from daemon */ devs = fwupd_client_get_devices (priv->client, NULL, error); if (devs == NULL) return FALSE; /* print */ if (devs->len == 0) { /* TRANSLATORS: nothing attached that can be upgraded */ g_print ("%s\n", _("No hardware detected with firmware update capability")); return TRUE; } for (guint i = 0; i < devs->len; i++) { g_autofree gchar *tmp = NULL; FwupdDevice *dev = g_ptr_array_index (devs, i); if (!priv->show_all_devices) { if (!fu_util_is_interesting_device (dev)) continue; } tmp = fwupd_device_to_string (dev); g_print ("%s\n", tmp); } /* nag? */ if (!fu_util_perhaps_show_unreported (priv, error)) return FALSE; return TRUE; } static gchar * fu_util_download_if_required (FuUtilPrivate *priv, const gchar *perhapsfn, GError **error) { g_autofree gchar *filename = NULL; g_autoptr(SoupURI) uri = NULL; /* a local file */ uri = soup_uri_new (perhapsfn); if (uri == NULL) return g_strdup (perhapsfn); /* download the firmware to a cachedir */ filename = fu_util_get_user_cache_path (perhapsfn); if (!fu_common_mkdir_parent (filename, error)) return NULL; if (!fu_util_download_file (priv, uri, filename, NULL, error)) return NULL; return g_steal_pointer (&filename); } static void fu_util_display_current_message (FuUtilPrivate *priv) { if (priv->current_message == NULL) return; g_print ("%s\n", priv->current_message); g_clear_pointer (&priv->current_message, g_free); } static gboolean fu_util_install (FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *id; g_autofree gchar *filename = NULL; /* handle both forms */ if (g_strv_length (values) == 1) { id = FWUPD_DEVICE_ID_ANY; } else if (g_strv_length (values) == 2) { id = values[1]; } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } priv->current_operation = FU_UTIL_OPERATION_INSTALL; g_signal_connect (priv->client, "device-changed", G_CALLBACK (fu_util_update_device_changed_cb), priv); /* install with flags chosen by the user */ filename = fu_util_download_if_required (priv, values[0], error); if (filename == NULL) return FALSE; if (!fwupd_client_install (priv->client, id, filename, priv->flags, NULL, error)) return FALSE; fu_util_display_current_message (priv); /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug ("skipping reboot check"); return TRUE; } /* show reboot if needed */ return fu_util_prompt_complete (priv->completion_flags, TRUE, error); } static gboolean fu_util_get_details (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) array = NULL; /* check args */ if (g_strv_length (values) != 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } array = fwupd_client_get_details (priv->client, values[0], NULL, error); if (array == NULL) return FALSE; for (guint i = 0; i < array->len; i++) { FwupdDevice *dev = g_ptr_array_index (array, i); g_autofree gchar *tmp = NULL; tmp = fwupd_device_to_string (dev); g_print ("%s\n", tmp); } return TRUE; } static gboolean fu_util_clear_history (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuHistory) history = fu_history_new (); return fu_history_remove_all (history, error); } static gboolean fu_util_report_history_for_uri (FuUtilPrivate *priv, const gchar *report_uri, GPtrArray *devices, GError **error) { JsonNode *json_root; JsonObject *json_object; const gchar *server_msg = NULL; guint status_code; g_autofree gchar *data = NULL; g_autofree gchar *sig = NULL; g_autoptr(JsonParser) json_parser = NULL; g_autoptr(SoupMessage) msg = NULL; /* convert to JSON */ data = fwupd_build_history_report_json (devices, error); if (data == NULL) return FALSE; /* self sign data */ if (priv->sign) { sig = fwupd_client_self_sign (priv->client, data, FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP, priv->cancellable, error); if (sig == NULL) return FALSE; } /* ask for permission */ if (!priv->assume_yes) { fu_util_print_data (_("Target"), report_uri); fu_util_print_data (_("Payload"), data); if (sig != NULL) fu_util_print_data (_("Signature"), sig); g_print ("%s [Y|n]: ", _("Proceed with upload?")); if (!fu_util_prompt_for_boolean (TRUE)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "User declined action"); return FALSE; } } /* POST request */ if (sig != NULL) { g_autoptr(SoupMultipart) mp = NULL; mp = soup_multipart_new (SOUP_FORM_MIME_TYPE_MULTIPART); soup_multipart_append_form_string (mp, "payload", data); soup_multipart_append_form_string (mp, "signature", sig); msg = soup_form_request_new_from_multipart (report_uri, mp); } else { msg = soup_message_new (SOUP_METHOD_POST, report_uri); soup_message_set_request (msg, "application/json; charset=utf-8", SOUP_MEMORY_COPY, data, strlen (data)); } status_code = soup_session_send_message (priv->soup_session, msg); g_debug ("server returned: %s", msg->response_body->data); /* server returned nothing, and probably exploded in a ball of flames */ if (msg->response_body->length == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to upload to %s: %s", report_uri, soup_status_get_phrase (status_code)); return FALSE; } /* parse JSON reply */ json_parser = json_parser_new (); if (!json_parser_load_from_data (json_parser, msg->response_body->data, msg->response_body->length, error)) { g_autofree gchar *str = g_strndup (msg->response_body->data, msg->response_body->length); g_prefix_error (error, "Failed to parse JSON response from '%s': ", str); return FALSE; } json_root = json_parser_get_root (json_parser); if (json_root == NULL) { g_autofree gchar *str = g_strndup (msg->response_body->data, msg->response_body->length); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "JSON response was malformed: '%s'", str); return FALSE; } json_object = json_node_get_object (json_root); if (json_object == NULL) { g_autofree gchar *str = g_strndup (msg->response_body->data, msg->response_body->length); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "JSON response object was malformed: '%s'", str); return FALSE; } /* get any optional server message */ if (json_object_has_member (json_object, "msg")) server_msg = json_object_get_string_member (json_object, "msg"); /* server reported failed */ if (!json_object_get_boolean_member (json_object, "success")) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "Server rejected report: %s", server_msg != NULL ? server_msg : "unspecified"); return FALSE; } /* server wanted us to see the message */ if (server_msg != NULL) { if (g_strstr_len (server_msg, -1, "known issue") != NULL && json_object_has_member (json_object, "uri")) { g_print ("%s %s\n", /* TRANSLATORS: the server sent the user a small message */ _("Update failure is a known issue, visit this URL for more information:"), json_object_get_string_member (json_object, "uri")); } else { /* TRANSLATORS: the server sent the user a small message */ g_print ("%s %s\n", _("Upload message:"), server_msg); } } /* fall back to HTTP status codes in case the server is offline */ if (!SOUP_STATUS_IS_SUCCESSFUL (status_code)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to upload to %s: %s", report_uri, soup_status_get_phrase (status_code)); return FALSE; } return TRUE; } static gboolean fu_util_report_history (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GHashTable) remote_id_uri_map = NULL; g_autoptr(GHashTable) report_map = NULL; g_autoptr(GList) uris = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) remotes = NULL; /* set up networking */ if (priv->soup_session == NULL) { priv->soup_session = fu_util_setup_networking (error); if (priv->soup_session == NULL) return FALSE; } /* create a map of RemoteID to RemoteURI */ remotes = fwupd_client_get_remotes (priv->client, NULL, error); if (remotes == NULL) return FALSE; remote_id_uri_map = g_hash_table_new (g_str_hash, g_str_equal); for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); if (fwupd_remote_get_id (remote) == NULL) continue; if (fwupd_remote_get_report_uri (remote) == NULL) continue; g_debug ("adding %s for %s", fwupd_remote_get_report_uri (remote), fwupd_remote_get_id (remote)); g_hash_table_insert (remote_id_uri_map, (gpointer) fwupd_remote_get_id (remote), (gpointer) fwupd_remote_get_report_uri (remote)); } /* get all devices from the history database, then filter them, * adding to a hash map of report-ids */ devices = fwupd_client_get_history (priv->client, NULL, error); if (devices == NULL) return FALSE; report_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); FwupdRelease *rel = fwupd_device_get_release_default (dev); const gchar *remote_id; const gchar *remote_uri; GPtrArray *devices_tmp; /* filter, if not forcing */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_REPORTED)) continue; if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; } /* only send success and failure */ if (fwupd_device_get_update_state (dev) != FWUPD_UPDATE_STATE_FAILED && fwupd_device_get_update_state (dev) != FWUPD_UPDATE_STATE_SUCCESS) { g_debug ("ignoring %s with UpdateState %s", fwupd_device_get_id (dev), fwupd_update_state_to_string (fwupd_device_get_update_state (dev))); continue; } /* find the RemoteURI to use for the device */ remote_id = fwupd_release_get_remote_id (rel); if (remote_id == NULL) { g_debug ("%s has no RemoteID", fwupd_device_get_id (dev)); continue; } remote_uri = g_hash_table_lookup (remote_id_uri_map, remote_id); if (remote_uri == NULL) { g_debug ("%s has no RemoteURI", remote_id); continue; } /* add this to the hash map */ devices_tmp = g_hash_table_lookup (report_map, remote_uri); if (devices_tmp == NULL) { devices_tmp = g_ptr_array_new (); g_hash_table_insert (report_map, g_strdup (remote_uri), devices_tmp); } g_debug ("using %s for %s", remote_uri, fwupd_device_get_id (dev)); g_ptr_array_add (devices_tmp, dev); } /* nothing to report */ if (g_hash_table_size (report_map) == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No reports require uploading"); return FALSE; } /* process each uri */ uris = g_hash_table_get_keys (report_map); for (GList *l = uris; l != NULL; l = l->next) { const gchar *uri = l->data; GPtrArray *devices_tmp = g_hash_table_lookup (report_map, uri); if (!fu_util_report_history_for_uri (priv, uri, devices_tmp, error)) return FALSE; } /* mark each device as reported */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_REPORTED)) continue; if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; g_debug ("setting flag on %s", fwupd_device_get_id (dev)); if (!fwupd_client_modify_device (priv->client, fwupd_device_get_id (dev), "Flags", "reported", NULL, error)) return FALSE; } return TRUE; } static gboolean fu_util_get_history (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* get all devices from the history database */ devices = fwupd_client_get_history (priv->client, NULL, error); if (devices == NULL) return FALSE; /* show each device */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); g_autofree gchar *str = fwupd_device_to_string (dev); g_print ("%s\n", str); } return TRUE; } static FwupdDevice* fu_util_get_device_or_prompt (FuUtilPrivate *priv, gchar **values, GError **error) { FwupdDevice *dev = NULL; /* get device to use */ if (g_strv_length (values) >= 1) { g_autoptr(GError) error_local = NULL; if (g_strv_length (values) > 1) { for (guint i = 1; i < g_strv_length (values); i++) g_debug ("Ignoring extra input %s", values[i]); } dev = fwupd_client_get_device_by_id (priv->client, values[0], NULL, &error_local); if (dev != NULL) return dev; g_print ("%s\n", error_local->message); } return fu_util_prompt_for_device (priv, error); } static gboolean fu_util_clear_results (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; dev = fu_util_get_device_or_prompt (priv, values, error); if (dev == NULL) return FALSE; return fwupd_client_clear_results (priv->client, fwupd_device_get_id (dev), NULL, error); } static gboolean fu_util_clear_offline (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuHistory) history = fu_history_new (); return fu_history_remove_all_with_state (history, FWUPD_UPDATE_STATE_PENDING, error); } static gboolean fu_util_verify_update_all (FuUtilPrivate *priv, GError **error) { g_autoptr(GPtrArray) devs = NULL; /* get devices from daemon */ devs = fwupd_client_get_devices (priv->client, NULL, error); if (devs == NULL) return FALSE; /* get results */ for (guint i = 0; i < devs->len; i++) { g_autoptr(GError) error_local = NULL; FwupdDevice *dev = g_ptr_array_index (devs, i); if (!fwupd_client_verify_update (priv->client, fwupd_device_get_id (dev), NULL, &error_local)) { g_print ("%s\tFAILED: %s\n", fwupd_device_get_guid_default (dev), error_local->message); continue; } g_print ("%s\t%s\n", fwupd_device_get_guid_default (dev), _("OK")); } return TRUE; } static gboolean fu_util_verify_update (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; if (g_strv_length (values) == 0) return fu_util_verify_update_all (priv, error); dev = fu_util_get_device_or_prompt (priv, values, error); if (dev == NULL) return FALSE; return fwupd_client_verify_update (priv->client, fwupd_device_get_id (dev), NULL, error); } static gboolean fu_util_file_exists_with_checksum (const gchar *fn, const gchar *checksum_expected, GChecksumType checksum_type) { gsize len = 0; g_autofree gchar *checksum_actual = NULL; g_autofree gchar *data = NULL; if (!g_file_get_contents (fn, &data, &len, NULL)) return FALSE; checksum_actual = g_compute_checksum_for_data (checksum_type, (guchar *) data, len); return g_strcmp0 (checksum_expected, checksum_actual) == 0; } static void fu_util_download_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) { guint percentage; goffset header_size; goffset body_length; FuUtilPrivate *priv = (FuUtilPrivate *) user_data; /* if it's returning "Found" or an error, ignore the percentage */ if (msg->status_code != SOUP_STATUS_OK) { g_debug ("ignoring status code %u (%s)", msg->status_code, msg->reason_phrase); return; } /* get data */ body_length = msg->response_body->length; header_size = soup_message_headers_get_content_length (msg->response_headers); /* size is not known */ if (header_size < body_length) return; /* calculate percentage */ percentage = (guint) ((100 * body_length) / header_size); g_debug ("progress: %u%%", percentage); fu_progressbar_update (priv->progressbar, FWUPD_STATUS_DOWNLOADING, percentage); } static gboolean fu_util_download_file (FuUtilPrivate *priv, SoupURI *uri, const gchar *fn, const gchar *checksum_expected, GError **error) { GChecksumType checksum_type; guint status_code; g_autoptr(GError) error_local = NULL; g_autofree gchar *checksum_actual = NULL; g_autofree gchar *uri_str = NULL; g_autoptr(SoupMessage) msg = NULL; /* check if the file already exists with the right checksum */ checksum_type = fwupd_checksum_guess_kind (checksum_expected); if (fu_util_file_exists_with_checksum (fn, checksum_expected, checksum_type)) { g_debug ("skpping download as file already exists"); return TRUE; } /* set up networking */ if (priv->soup_session == NULL) { priv->soup_session = fu_util_setup_networking (error); if (priv->soup_session == NULL) return FALSE; } /* download data */ uri_str = soup_uri_to_string (uri, FALSE); g_debug ("downloading %s to %s", uri_str, fn); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); if (msg == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse URI %s", uri_str); return FALSE; } if (g_str_has_suffix (uri_str, ".asc") || g_str_has_suffix (uri_str, ".p7b") || g_str_has_suffix (uri_str, ".p7c")) { /* TRANSLATORS: downloading new signing file */ g_print ("%s %s\n", _("Fetching signature"), uri_str); } else if (g_str_has_suffix (uri_str, ".gz")) { /* TRANSLATORS: downloading new metadata file */ g_print ("%s %s\n", _("Fetching metadata"), uri_str); } else if (g_str_has_suffix (uri_str, ".cab")) { /* TRANSLATORS: downloading new firmware file */ g_print ("%s %s\n", _("Fetching firmware"), uri_str); } else { /* TRANSLATORS: downloading unknown file */ g_print ("%s %s\n", _("Fetching file"), uri_str); } g_signal_connect (msg, "got-chunk", G_CALLBACK (fu_util_download_chunk_cb), priv); status_code = soup_session_send_message (priv->soup_session, msg); g_print ("\n"); if (status_code == 429) { g_autofree gchar *str = g_strndup (msg->response_body->data, msg->response_body->length); if (g_strcmp0 (str, "Too Many Requests") == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, /* TRANSLATORS: the server is rate-limiting downloads */ "%s", _("Failed to download due to server limit")); return FALSE; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to download due to server limit: %s", str); return FALSE; } if (status_code != SOUP_STATUS_OK) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to download %s: %s", uri_str, soup_status_get_phrase (status_code)); return FALSE; } /* verify checksum */ if (checksum_expected != NULL) { checksum_actual = g_compute_checksum_for_data (checksum_type, (guchar *) msg->response_body->data, (gsize) msg->response_body->length); if (g_strcmp0 (checksum_expected, checksum_actual) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Checksum invalid, expected %s got %s", checksum_expected, checksum_actual); return FALSE; } } /* save file */ if (!g_file_set_contents (fn, msg->response_body->data, msg->response_body->length, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "Failed to save file: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_util_download_metadata_for_remote (FuUtilPrivate *priv, FwupdRemote *remote, GError **error) { g_autofree gchar *basename_asc = NULL; g_autofree gchar *basename_id_asc = NULL; g_autofree gchar *basename_id = NULL; g_autofree gchar *basename = NULL; g_autofree gchar *filename = NULL; g_autofree gchar *filename_asc = NULL; g_autoptr(SoupURI) uri = NULL; g_autoptr(SoupURI) uri_sig = NULL; /* generate some plausible local filenames */ basename = g_path_get_basename (fwupd_remote_get_filename_cache (remote)); basename_id = g_strdup_printf ("%s-%s", fwupd_remote_get_id (remote), basename); /* download the metadata */ filename = fu_util_get_user_cache_path (basename_id); if (!fu_common_mkdir_parent (filename, error)) return FALSE; uri = soup_uri_new (fwupd_remote_get_metadata_uri (remote)); if (!fu_util_download_file (priv, uri, filename, NULL, error)) return FALSE; /* download the signature */ basename_asc = g_path_get_basename (fwupd_remote_get_filename_cache_sig (remote)); basename_id_asc = g_strdup_printf ("%s-%s", fwupd_remote_get_id (remote), basename_asc); filename_asc = fu_util_get_user_cache_path (basename_id_asc); uri_sig = soup_uri_new (fwupd_remote_get_metadata_uri_sig (remote)); if (!fu_util_download_file (priv, uri_sig, filename_asc, NULL, error)) return FALSE; /* send all this to fwupd */ return fwupd_client_update_metadata (priv->client, fwupd_remote_get_id (remote), filename, filename_asc, NULL, error); } static gboolean fu_util_download_metadata_enable_lvfs (FuUtilPrivate *priv, GError **error) { g_autoptr(FwupdRemote) remote = NULL; /* is the LVFS available but disabled? */ remote = fwupd_client_get_remote_by_id (priv->client, "lvfs", NULL, error); if (remote == NULL) return TRUE; g_print ("%s\n%s\n%s [Y|n]: ", /* TRANSLATORS: explain why no metadata available */ _("No remotes are currently enabled so no metadata is available."), /* TRANSLATORS: explain why no metadata available */ _("Metadata can be obtained from the Linux Vendor Firmware Service."), /* TRANSLATORS: Turn on the remote */ _("Enable this remote?")); if (!fu_util_prompt_for_boolean (TRUE)) return TRUE; if (!fu_util_modify_remote (priv, "lvfs", "Enabled", "true", error)) return FALSE; /* refresh the newly-enabled remote */ return fu_util_download_metadata_for_remote (priv, remote, error); } static gboolean fu_util_download_metadata (FuUtilPrivate *priv, GError **error) { gboolean download_remote_enabled = FALSE; g_autoptr(GPtrArray) remotes = NULL; remotes = fwupd_client_get_remotes (priv->client, NULL, error); if (remotes == NULL) return FALSE; for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); if (!fwupd_remote_get_enabled (remote)) continue; if (fwupd_remote_get_kind (remote) != FWUPD_REMOTE_KIND_DOWNLOAD) continue; download_remote_enabled = TRUE; if (!fu_util_download_metadata_for_remote (priv, remote, error)) return FALSE; } /* no web remote is declared; try to enable LVFS */ if (!download_remote_enabled) { if (!fu_util_download_metadata_enable_lvfs (priv, error)) return FALSE; } return TRUE; } static gboolean fu_util_refresh (FuUtilPrivate *priv, gchar **values, GError **error) { if (g_strv_length (values) == 0) return fu_util_download_metadata (priv, error); if (g_strv_length (values) != 3) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* open file */ return fwupd_client_update_metadata (priv->client, values[2], values[0], values[1], NULL, error); } static gboolean fu_util_get_results (FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *tmp = NULL; g_autoptr(FwupdDevice) dev = NULL; g_autoptr(FwupdDevice) rel = NULL; dev = fu_util_get_device_or_prompt (priv, values, error); if (dev == NULL) return FALSE; rel = fwupd_client_get_results (priv->client, fwupd_device_get_id (dev), NULL, error); if (rel == NULL) return FALSE; tmp = fwupd_device_to_string (rel); g_print ("%s", tmp); return TRUE; } static gboolean fu_util_get_releases (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; g_autoptr(GPtrArray) rels = NULL; dev = fu_util_get_device_or_prompt (priv, values, error); if (dev == NULL) return FALSE; /* get the releases for this device */ rels = fwupd_client_get_releases (priv->client, fwupd_device_get_id (dev), NULL, error); if (rels == NULL) return FALSE; g_print ("%s:\n", fwupd_device_get_name (dev)); for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index (rels, i); FwupdReleaseFlags flags = fwupd_release_get_flags (rel); GPtrArray *checksums; const gchar *tmp; /* TRANSLATORS: section header for release version number */ fu_util_print_data (_("Version"), fwupd_release_get_version (rel)); /* TRANSLATORS: section header for the release name */ fu_util_print_data (_("Name"), fu_util_release_get_name (rel)); /* TRANSLATORS: section header for the release one line summary */ fu_util_print_data (_("Summary"), fwupd_release_get_summary (rel)); /* TRANSLATORS: section header for the remote the file is coming from */ fu_util_print_data (_("Remote"), fwupd_release_get_remote_id (rel)); /* TRANSLATORS: section header for firmware URI */ fu_util_print_data (_("URI"), fwupd_release_get_uri (rel)); tmp = fwupd_release_get_description (rel); if (tmp != NULL) { g_autofree gchar *desc = NULL; desc = fu_util_convert_appstream_description (tmp, NULL); /* TRANSLATORS: section header for firmware description */ fu_util_print_data (_("Description"), desc); } checksums = fwupd_release_get_checksums (rel); for (guint j = 0; j < checksums->len; j++) { const gchar *checksum = g_ptr_array_index (checksums, j); g_autofree gchar *checksum_display = NULL; checksum_display = fwupd_checksum_format_for_display (checksum); /* TRANSLATORS: section header for firmware checksum */ fu_util_print_data (_("Checksum"), checksum_display); } /* show flags if set */ if (flags != FWUPD_RELEASE_FLAG_NONE) { g_autoptr(GString) str = g_string_new (""); for (guint j = 0; j < 64; j++) { if ((flags & ((guint64) 1 << j)) == 0) continue; g_string_append_printf (str, "%s,", fwupd_release_flag_to_string ((guint64) 1 << j)); } if (str->len == 0) { g_string_append (str, fwupd_release_flag_to_string (0)); } else { g_string_truncate (str, str->len - 1); } /* TRANSLATORS: section header for firmware flags */ fu_util_print_data (_("Flags"), str->str); } /* new line between all but last entries */ if (i != rels->len - 1) g_print ("\n"); } return TRUE; } static FwupdRelease * fu_util_prompt_for_release (FuUtilPrivate *priv, GPtrArray *rels, GError **error) { FwupdRelease *rel; guint idx; /* nothing */ if (rels->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No supported releases"); return NULL; } /* exactly one */ if (rels->len == 1) { rel = g_ptr_array_index (rels, 0); return g_object_ref (rel); } /* TRANSLATORS: get interactive prompt */ g_print ("%s\n", _("Choose a release:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print ("0.\t%s\n", _("Cancel")); for (guint i = 0; i < rels->len; i++) { const gchar *desc_tmp; g_autofree gchar *desc = NULL; rel = g_ptr_array_index (rels, i); /* no description provided */ desc_tmp = fwupd_release_get_description (rel); if (desc_tmp == NULL) { g_print ("%u.\t%s\n", i + 1, fwupd_release_get_version (rel)); continue; } /* remove markup, and fall back if we fail */ desc = fu_util_convert_appstream_description (desc_tmp, NULL); if (desc == NULL) desc = g_strdup (desc_tmp); g_print ("%u.\t%s (%s)\n", i + 1, fwupd_release_get_version (rel), desc); } idx = fu_util_prompt_for_number (rels->len); if (idx == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return NULL; } rel = g_ptr_array_index (rels, idx - 1); return g_object_ref (rel); } static gboolean fu_util_verify_all (FuUtilPrivate *priv, GError **error) { g_autoptr(GPtrArray) devs = NULL; /* get devices from daemon */ devs = fwupd_client_get_devices (priv->client, NULL, error); if (devs == NULL) return FALSE; /* get results */ for (guint i = 0; i < devs->len; i++) { g_autoptr(GError) error_local = NULL; FwupdDevice *dev = g_ptr_array_index (devs, i); if (!fwupd_client_verify (priv->client, fwupd_device_get_id (dev), NULL, &error_local)) { g_print ("%s\tFAILED: %s\n", fwupd_device_get_guid_default (dev), error_local->message); continue; } g_print ("%s\t%s\n", fwupd_device_get_guid_default (dev), _("OK")); } return TRUE; } static gboolean fu_util_verify (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; if (g_strv_length (values) == 0) return fu_util_verify_all (priv, error); dev = fu_util_get_device_or_prompt (priv, values, error); if (dev == NULL) return FALSE; return fwupd_client_verify (priv->client, fwupd_device_get_id (dev), NULL, error); } static gboolean fu_util_unlock (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; dev = fu_util_get_device_or_prompt (priv, values, error); if (dev == NULL) return FALSE; if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; if (!fwupd_client_unlock (priv->client, fwupd_device_get_id (dev), NULL, error)) return FALSE; return fu_util_prompt_complete (priv->completion_flags, TRUE, error); } static gboolean fu_util_perhaps_refresh_remotes (FuUtilPrivate *priv, GError **error) { g_autoptr(GPtrArray) remotes = NULL; guint64 age_oldest = 0; const guint64 age_limit_days = 30; /* we don't want to ask anything */ if (priv->no_metadata_check) { g_debug ("skipping metadata check"); return TRUE; } /* get the age of the oldest enabled remotes */ remotes = fwupd_client_get_remotes (priv->client, NULL, error); if (remotes == NULL) return FALSE; for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); if (!fwupd_remote_get_enabled (remote)) continue; if (fwupd_remote_get_kind (remote) != FWUPD_REMOTE_KIND_DOWNLOAD) continue; if (fwupd_remote_get_age (remote) > age_oldest) age_oldest = fwupd_remote_get_age (remote); } /* metadata is new enough */ if (age_oldest < 60 * 60 * 24 * age_limit_days) return TRUE; /* ask for permission */ if (!priv->assume_yes) { /* TRANSLATORS: the metadata is very out of date; %u is a number > 1 */ g_print (ngettext("Firmware metadata has not been updated for %u" " day and may not be up to date.", "Firmware metadata has not been updated for %u" " days and may not be up to date.", (gint) age_limit_days), (guint) age_limit_days); g_print ("\n\n"); g_print ("%s (%s) [y|N]: ", /* TRANSLATORS: ask the user if we can update the metadata */ _("Update now?"), /* TRANSLATORS: metadata is downloaded from the Internet */ _("Requires internet connection")); if (!fu_util_prompt_for_boolean (FALSE)) return TRUE; } /* downloads new metadata */ return fu_util_download_metadata (priv, error); } static gchar * fu_util_time_to_str (guint64 tmp) { g_return_val_if_fail (tmp != 0, NULL); /* seconds */ if (tmp < 60) { /* TRANSLATORS: duration in seconds */ return g_strdup_printf (ngettext ("%u second", "%u seconds", (gint) tmp), (guint) tmp); } /* minutes */ tmp /= 60; if (tmp < 60) { /* TRANSLATORS: duration in minutes */ return g_strdup_printf (ngettext ("%u minute", "%u minutes", (gint) tmp), (guint) tmp); } /* hours */ tmp /= 60; if (tmp < 60) { /* TRANSLATORS: duration in minutes */ return g_strdup_printf (ngettext ("%u hour", "%u hours", (gint) tmp), (guint) tmp); } /* days */ tmp /= 24; /* TRANSLATORS: duration in days! */ return g_strdup_printf (ngettext ("%u day", "%u days", (gint) tmp), (guint) tmp); } static gboolean fu_util_get_updates (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* are the remotes very old */ if (!fu_util_perhaps_refresh_remotes (priv, error)) return FALSE; /* get devices from daemon */ devices = fwupd_client_get_devices (priv->client, NULL, error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); GPtrArray *guids; const gchar *tmp; g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; /* not going to have results, so save a D-Bus round-trip */ if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; /* get the releases for this device and filter for validity */ rels = fwupd_client_get_upgrades (priv->client, fwupd_device_get_id (dev), NULL, &error_local); if (rels == NULL) { g_printerr ("%s\n", error_local->message); continue; } /* TRANSLATORS: first replacement is device name */ g_print (_("%s has firmware updates:"), fwupd_device_get_name (dev)); g_print ("\n"); /* TRANSLATORS: ID for hardware, typically a SHA1 sum */ fu_util_print_data (_("Device ID"), fwupd_device_get_id (dev)); /* TRANSLATORS: a GUID for the hardware */ guids = fwupd_device_get_guids (dev); for (guint j = 0; j < guids->len; j++) { tmp = g_ptr_array_index (guids, j); fu_util_print_data (_("GUID"), tmp); } /* print all releases */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel = g_ptr_array_index (rels, j); guint64 duration; GPtrArray *checksums; /* TRANSLATORS: Appstream ID for the hardware type */ fu_util_print_data (_("ID"), fwupd_release_get_appstream_id (rel)); /* TRANSLATORS: section header for firmware version */ fu_util_print_data (_("Update Version"), fwupd_release_get_version (rel)); /* TRANSLATORS: section header for the release name */ fu_util_print_data (_("Update Name"), fu_util_release_get_name (rel)); /* TRANSLATORS: section header for the release one line summary */ fu_util_print_data (_("Update Summary"), fwupd_release_get_summary (rel)); /* TRANSLATORS: section header for remote ID, e.g. lvfs-testing */ fu_util_print_data (_("Update Remote ID"), fwupd_release_get_remote_id (rel)); /* optional approximate duration */ duration = fwupd_release_get_install_duration (rel); if (duration > 0) { g_autofree gchar *str = fu_util_time_to_str (duration); /* TRANSLATORS: section header for the amount * of time it takes to install the update */ fu_util_print_data (_("Update Duration"), str); } checksums = fwupd_release_get_checksums (rel); for (guint k = 0; k < checksums->len; k++) { const gchar *checksum = g_ptr_array_index (checksums, k); g_autofree gchar *checksum_display = NULL; checksum_display = fwupd_checksum_format_for_display (checksum); /* TRANSLATORS: section header for firmware checksum */ fu_util_print_data (_("Update Checksum"), checksum_display); } /* TRANSLATORS: section header for firmware remote http:// */ fu_util_print_data (_("Update Location"), fwupd_release_get_uri (rel)); /* convert XML -> text */ tmp = fwupd_release_get_description (rel); if (tmp != NULL) { g_autofree gchar *md = NULL; md = fu_util_convert_appstream_description (tmp, NULL); if (md != NULL) { /* TRANSLATORS: section header for long firmware desc */ fu_util_print_data (_("Update Description"), md); } } } } /* nag? */ if (!fu_util_perhaps_show_unreported (priv, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_util_get_remotes (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) remotes = NULL; /* print any updates */ remotes = fwupd_client_get_remotes (priv->client, NULL, error); if (remotes == NULL) return FALSE; for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); FwupdRemoteKind kind = fwupd_remote_get_kind (remote); FwupdKeyringKind keyring_kind = fwupd_remote_get_keyring_kind (remote); const gchar *tmp; gint priority; gdouble age; /* TRANSLATORS: remote identifier, e.g. lvfs-testing */ fu_util_print_data (_("Remote ID"), fwupd_remote_get_id (remote)); /* TRANSLATORS: remote title, e.g. "Linux Vendor Firmware Service" */ fu_util_print_data (_("Title"), fwupd_remote_get_title (remote)); /* TRANSLATORS: remote type, e.g. remote or local */ fu_util_print_data (_("Type"), fwupd_remote_kind_to_string (kind)); /* TRANSLATORS: keyring type, e.g. GPG or PKCS7 */ if (keyring_kind != FWUPD_KEYRING_KIND_UNKNOWN) { fu_util_print_data (_("Keyring"), fwupd_keyring_kind_to_string (keyring_kind)); } /* TRANSLATORS: if the remote is enabled */ fu_util_print_data (_("Enabled"), fwupd_remote_get_enabled (remote) ? "true" : "false"); /* TRANSLATORS: remote checksum */ fu_util_print_data (_("Checksum"), fwupd_remote_get_checksum (remote)); /* optional parameters */ age = fwupd_remote_get_age (remote); if (kind == FWUPD_REMOTE_KIND_DOWNLOAD && age > 0 && age != G_MAXUINT64) { const gchar *unit = "s"; g_autofree gchar *age_str = NULL; if (age > 60) { age /= 60.f; unit = "m"; } if (age > 60) { age /= 60.f; unit = "h"; } if (age > 24) { age /= 24.f; unit = "d"; } if (age > 7) { age /= 7.f; unit = "w"; } age_str = g_strdup_printf ("%.2f%s", age, unit); /* TRANSLATORS: the age of the metadata */ fu_util_print_data (_("Age"), age_str); } priority = fwupd_remote_get_priority (remote); if (priority != 0) { g_autofree gchar *priority_str = NULL; priority_str = g_strdup_printf ("%i", priority); /* TRANSLATORS: the numeric priority */ fu_util_print_data (_("Priority"), priority_str); } tmp = fwupd_remote_get_username (remote); if (tmp != NULL) { /* TRANSLATORS: remote filename base */ fu_util_print_data (_("Username"), tmp); } tmp = fwupd_remote_get_password (remote); if (tmp != NULL) { g_autofree gchar *hidden = g_strnfill (strlen (tmp), '*'); /* TRANSLATORS: remote filename base */ fu_util_print_data (_("Password"), hidden); } tmp = fwupd_remote_get_filename_cache (remote); if (tmp != NULL) { /* TRANSLATORS: filename of the local file */ fu_util_print_data (_("Filename"), tmp); } tmp = fwupd_remote_get_filename_cache_sig (remote); if (tmp != NULL) { /* TRANSLATORS: filename of the local file */ fu_util_print_data (_("Filename Signature"), tmp); } tmp = fwupd_remote_get_metadata_uri (remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_util_print_data (_("Metadata URI"), tmp); } tmp = fwupd_remote_get_metadata_uri_sig (remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_util_print_data (_("Metadata URI Signature"), tmp); } tmp = fwupd_remote_get_firmware_base_uri (remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_util_print_data (_("Firmware Base URI"), tmp); } tmp = fwupd_remote_get_report_uri (remote); if (tmp != NULL) { /* TRANSLATORS: URI to send success/failure reports */ fu_util_print_data (_("Report URI"), tmp); } /* newline */ if (i != remotes->len - 1) g_print ("\n"); } return TRUE; } static gboolean fu_util_update_device_with_release (FuUtilPrivate *priv, FwupdDevice *dev, FwupdRelease *rel, GError **error) { GPtrArray *checksums; const gchar *remote_id; const gchar *uri_tmp; g_autofree gchar *fn = NULL; g_autofree gchar *uri_str = NULL; g_autoptr(SoupURI) uri = NULL; /* work out what remote-specific URI fields this should use */ uri_tmp = fwupd_release_get_uri (rel); remote_id = fwupd_release_get_remote_id (rel); if (remote_id != NULL) { g_autoptr(FwupdRemote) remote = NULL; remote = fwupd_client_get_remote_by_id (priv->client, remote_id, NULL, error); if (remote == NULL) return FALSE; /* local and directory remotes have the firmware already */ if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_LOCAL) { const gchar *fn_cache = fwupd_remote_get_filename_cache (remote); g_autofree gchar *path = g_path_get_dirname (fn_cache); fn = g_build_filename (path, uri_tmp, NULL); } else if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_DIRECTORY) { fn = g_strdup (uri_tmp + 7); } /* install with flags chosen by the user */ if (fn != NULL) { return fwupd_client_install (priv->client, fwupd_device_get_id (dev), fn, priv->flags, NULL, error); } uri_str = fwupd_remote_build_firmware_uri (remote, uri_tmp, error); if (uri_str == NULL) return FALSE; } else { uri_str = g_strdup (uri_tmp); } /* download file */ g_print ("Downloading %s for %s...\n", fwupd_release_get_version (rel), fwupd_device_get_name (dev)); fn = fu_util_get_user_cache_path (uri_str); if (!fu_common_mkdir_parent (fn, error)) return FALSE; checksums = fwupd_release_get_checksums (rel); uri = soup_uri_new (uri_str); if (!fu_util_download_file (priv, uri, fn, fwupd_checksum_get_best (checksums), error)) return FALSE; /* if the device specifies ONLY_OFFLINE automatically set this flag */ if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_ONLY_OFFLINE)) priv->flags |= FWUPD_INSTALL_FLAG_OFFLINE; return fwupd_client_install (priv->client, fwupd_device_get_id (dev), fn, priv->flags, NULL, error); } static gboolean fu_util_update_all (FuUtilPrivate *priv, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* get devices from daemon */ devices = fwupd_client_get_devices (priv->client, NULL, error); if (devices == NULL) return FALSE; priv->current_operation = FU_UTIL_OPERATION_UPDATE; g_signal_connect (priv->client, "device-changed", G_CALLBACK (fu_util_update_device_changed_cb), priv); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); FwupdRelease *rel; g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; /* not going to have results, so save a D-Bus round-trip */ if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; /* get the releases for this device and filter for validity */ rels = fwupd_client_get_upgrades (priv->client, fwupd_device_get_id (dev), NULL, &error_local); if (rels == NULL) { g_printerr ("%s\n", error_local->message); continue; } rel = g_ptr_array_index (rels, 0); if (!fu_util_update_device_with_release (priv, dev, rel, error)) return FALSE; fu_util_display_current_message (priv); } /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug ("skipping reboot check"); return TRUE; } return fu_util_prompt_complete (priv->completion_flags, TRUE, error); } static gboolean fu_util_update_by_id (FuUtilPrivate *priv, const gchar *device_id, GError **error) { FwupdRelease *rel; g_autoptr(FwupdDevice) dev = NULL; g_autoptr(GPtrArray) rels = NULL; /* do not allow a partial device-id */ dev = fwupd_client_get_device_by_id (priv->client, device_id, NULL, error); if (dev == NULL) return FALSE; /* get devices from daemon */ priv->current_operation = FU_UTIL_OPERATION_UPDATE; g_signal_connect (priv->client, "device-changed", G_CALLBACK (fu_util_update_device_changed_cb), priv); /* get the releases for this device and filter for validity */ rels = fwupd_client_get_upgrades (priv->client, fwupd_device_get_id (dev), NULL, error); if (rels == NULL) return FALSE; rel = g_ptr_array_index (rels, 0); if (!fu_util_update_device_with_release (priv, dev, rel, error)) return FALSE; fu_util_display_current_message (priv); /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug ("skipping reboot check"); return TRUE; } /* the update needs the user to restart the computer */ return fu_util_prompt_complete (priv->completion_flags, TRUE, error); } static gboolean fu_util_update (FuUtilPrivate *priv, gchar **values, GError **error) { if (g_strv_length (values) == 0) return fu_util_update_all (priv, error); if (g_strv_length (values) == 1) return fu_util_update_by_id (priv, values[0], error); g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } static gboolean fu_util_remote_modify (FuUtilPrivate *priv, gchar **values, GError **error) { if (g_strv_length (values) < 3) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } return fu_util_modify_remote (priv, values[0], values[1], values[2], error); } static gboolean fu_util_remote_enable (FuUtilPrivate *priv, gchar **values, GError **error) { if (g_strv_length (values) != 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } return fu_util_modify_remote (priv, values[0], "Enabled", "true", error); } static gboolean fu_util_remote_disable (FuUtilPrivate *priv, gchar **values, GError **error) { if (g_strv_length (values) != 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } return fu_util_modify_remote (priv, values[0], "Enabled", "false", error); } static gboolean fu_util_downgrade (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GPtrArray) rels = NULL; dev = fu_util_get_device_or_prompt (priv, values, error); if (dev == NULL) return FALSE; /* get the releases for this device and filter for validity */ rels = fwupd_client_get_downgrades (priv->client, fwupd_device_get_id (dev), NULL, error); if (rels == NULL) return FALSE; /* get the chosen release */ rel = fu_util_prompt_for_release (priv, rels, error); if (rel == NULL) return FALSE; /* update the console if composite devices are also updated */ priv->current_operation = FU_UTIL_OPERATION_DOWNGRADE; g_signal_connect (priv->client, "device-changed", G_CALLBACK (fu_util_update_device_changed_cb), priv); priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; return fu_util_update_device_with_release (priv, dev, rel, error); } static gboolean fu_util_activate (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; gboolean has_pending = FALSE; /* handle both forms */ if (g_strv_length (values) == 0) { /* activate anything with _NEEDS_ACTIVATION */ devices = fwupd_client_get_devices (priv->client, NULL, error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { has_pending = TRUE; break; } } } else if (g_strv_length (values) == 1) { FwupdDevice *device = fwupd_client_get_device_by_id (priv->client, values[0], NULL, error); if (device == NULL) return FALSE; devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_ptr_array_add (devices, device); if (fwupd_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) has_pending = TRUE; } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* nothing to do */ if (!has_pending) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No firmware to activate"); return FALSE; } /* activate anything with _NEEDS_ACTIVATION */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *device = g_ptr_array_index (devices, i); if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) continue; /* TRANSLATORS: shown when shutting down to switch to the new version */ g_print ("%s %s…\n", _("Activating firmware update for"), fwupd_device_get_name (device)); if (!fwupd_client_activate (priv->client, NULL, fwupd_device_get_id (device), error)) return FALSE; } return TRUE; } static gboolean fu_util_set_approved_firmware (FuUtilPrivate *priv, gchar **values, GError **error) { g_auto(GStrv) checksums = NULL; /* check args */ if (g_strv_length (values) != 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: list of checksums expected"); return FALSE; } /* call into daemon */ checksums = g_strsplit (values[0], ",", -1); return fwupd_client_set_approved_firmware (priv->client, checksums, priv->cancellable, error); } static gboolean fu_util_get_approved_firmware (FuUtilPrivate *priv, gchar **values, GError **error) { g_auto(GStrv) checksums = NULL; /* check args */ if (g_strv_length (values) != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: none expected"); return FALSE; } /* call into daemon */ checksums = fwupd_client_get_approved_firmware (priv->client, priv->cancellable, error); if (checksums == NULL) return FALSE; if (g_strv_length (checksums) == 0) { /* TRANSLATORS: approved firmware has been checked by * the domain administrator */ g_print ("%s\n", _("There is no approved firmware.")); } else { /* TRANSLATORS: approved firmware has been checked by * the domain administrator */ g_print ("%s\n", ngettext ("Approved firmware:", "Approved firmware:", g_strv_length (checksums))); for (guint i = 0; checksums[i] != NULL; i++) g_print (" * %s\n", checksums[i]); } return TRUE; } static gboolean fu_util_modify_config (FuUtilPrivate *priv, gchar **values, GError **error) { /* check args */ if (g_strv_length (values) != 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: KEY VALUE expected"); return FALSE; } if (!fwupd_client_modify_config (priv->client, values[0], values[1], priv->cancellable, error)) return FALSE; if (!priv->assume_yes) { g_print ("%s [Y|n]: ", /* TRANSLATORS: configuration changes only take effect on restart */ _("Restart the daemon to make the change effective?")); if (!fu_util_prompt_for_boolean (FALSE)) return TRUE; } #ifdef HAVE_SYSTEMD if (!fu_systemd_unit_stop (fu_util_get_systemd_unit (), error)) return FALSE; #endif return TRUE; } static void fu_util_ignore_cb (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { } static gboolean fu_util_sigint_cb (gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *) user_data; g_debug ("Handling SIGINT"); g_cancellable_cancel (priv->cancellable); return FALSE; } static void fu_util_private_free (FuUtilPrivate *priv) { if (priv->client != NULL) g_object_unref (priv->client); if (priv->current_device != NULL) g_object_unref (priv->current_device); if (priv->soup_session != NULL) g_object_unref (priv->soup_session); g_free (priv->current_message); g_main_loop_unref (priv->loop); g_object_unref (priv->cancellable); g_object_unref (priv->progressbar); g_option_context_free (priv->context); g_free (priv); } static gboolean fu_util_check_daemon_version (FuUtilPrivate *priv, GError **error) { g_autofree gchar *client = g_strdup_printf ("%i.%i.%i", FWUPD_MAJOR_VERSION, FWUPD_MINOR_VERSION, FWUPD_MICRO_VERSION); const gchar *daemon = fwupd_client_get_daemon_version (priv->client); if (g_strcmp0 (daemon, client) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, /* TRANSLATORS: error message */ _("Unsupported daemon version %s, client version is %s"), daemon, client); return FALSE; } return TRUE; } static gboolean fu_util_check_polkit_actions (GError **error) { g_autofree gchar *directory = fu_common_get_path (FU_PATH_KIND_POLKIT_ACTIONS); g_autofree gchar *filename = g_build_filename (directory, "org.freedesktop.fwupd.policy", NULL); if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "PolicyKit files are missing, see https://github.com/hughsie/fwupd/wiki/PolicyKit-files-are-missing"); return FALSE; } return TRUE; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free) #pragma clang diagnostic pop int main (int argc, char *argv[]) { gboolean force = FALSE; gboolean allow_older = FALSE; gboolean allow_reinstall = FALSE; gboolean no_history = FALSE; gboolean offline = FALSE; gboolean ret; gboolean verbose = FALSE; gboolean version = FALSE; g_autoptr(FuUtilPrivate) priv = g_new0 (FuUtilPrivate, 1); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new (); g_autofree gchar *cmd_descriptions = NULL; const GOptionEntry options[] = { { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: command line option */ _("Show extra debugging information"), NULL }, { "version", '\0', 0, G_OPTION_ARG_NONE, &version, /* TRANSLATORS: command line option */ _("Show client and daemon versions"), NULL }, { "offline", '\0', 0, G_OPTION_ARG_NONE, &offline, /* TRANSLATORS: command line option */ _("Schedule installation for next reboot when possible"), NULL }, { "allow-reinstall", '\0', 0, G_OPTION_ARG_NONE, &allow_reinstall, /* TRANSLATORS: command line option */ _("Allow re-installing existing firmware versions"), NULL }, { "allow-older", '\0', 0, G_OPTION_ARG_NONE, &allow_older, /* TRANSLATORS: command line option */ _("Allow downgrading firmware versions"), NULL }, { "force", '\0', 0, G_OPTION_ARG_NONE, &force, /* TRANSLATORS: command line option */ _("Override warnings and force the action"), NULL }, { "assume-yes", 'y', 0, G_OPTION_ARG_NONE, &priv->assume_yes, /* TRANSLATORS: command line option */ _("Answer yes to all questions"), NULL }, { "sign", '\0', 0, G_OPTION_ARG_NONE, &priv->sign, /* TRANSLATORS: command line option */ _("Sign the uploaded data with the client certificate"), NULL }, { "no-unreported-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_unreported_check, /* TRANSLATORS: command line option */ _("Do not check for unreported history"), NULL }, { "no-metadata-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_metadata_check, /* TRANSLATORS: command line option */ _("Do not check for old metadata"), NULL }, { "no-reboot-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_reboot_check, /* TRANSLATORS: command line option */ _("Do not check for reboot after update"), NULL }, { "no-history", '\0', 0, G_OPTION_ARG_NONE, &no_history, /* TRANSLATORS: command line option */ _("Do not write to the history database"), NULL }, { "show-all-devices", '\0', 0, G_OPTION_ARG_NONE, &priv->show_all_devices, /* TRANSLATORS: command line option */ _("Show devices that are not updatable"), NULL }, { NULL} }; setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); /* ensure D-Bus errors are registered */ fwupd_error_quark (); /* create helper object */ priv->loop = g_main_loop_new (NULL, FALSE); priv->progressbar = fu_progressbar_new (); /* add commands */ fu_util_cmd_array_add (cmd_array, "get-devices", NULL, /* TRANSLATORS: command description */ _("Get all devices that support firmware updates"), fu_util_get_devices); fu_util_cmd_array_add (cmd_array, "get-topology", NULL, /* TRANSLATORS: command description */ _("Get all devices according to the system topology"), fu_util_get_topology); fu_util_cmd_array_add (cmd_array, "get-history", NULL, /* TRANSLATORS: command description */ _("Show history of firmware updates"), fu_util_get_history); fu_util_cmd_array_add (cmd_array, "clear-history", NULL, /* TRANSLATORS: command description */ _("Erase all firmware update history"), fu_util_clear_history); fu_util_cmd_array_add (cmd_array, "report-history", NULL, /* TRANSLATORS: command description */ _("Share firmware history with the developers"), fu_util_report_history); fu_util_cmd_array_add (cmd_array, "install", "FILE [ID]", /* TRANSLATORS: command description */ _("Install a firmware file on this hardware"), fu_util_install); fu_util_cmd_array_add (cmd_array, "get-details", "FILE", /* TRANSLATORS: command description */ _("Gets details about a firmware file"), fu_util_get_details); fu_util_cmd_array_add (cmd_array, "get-updates", NULL, /* TRANSLATORS: command description */ _("Gets the list of updates for connected hardware"), fu_util_get_updates); fu_util_cmd_array_add (cmd_array, "update", NULL, /* TRANSLATORS: command description */ _("Updates all firmware to latest versions available"), fu_util_update); fu_util_cmd_array_add (cmd_array, "verify", "[DEVICE_ID]", /* TRANSLATORS: command description */ _("Gets the cryptographic hash of the dumped firmware"), fu_util_verify); fu_util_cmd_array_add (cmd_array, "unlock", "DEVICE_ID", /* TRANSLATORS: command description */ _("Unlocks the device for firmware access"), fu_util_unlock); fu_util_cmd_array_add (cmd_array, "clear-results", "DEVICE_ID", /* TRANSLATORS: command description */ _("Clears the results from the last update"), fu_util_clear_results); fu_util_cmd_array_add (cmd_array, "clear-offline", NULL, /* TRANSLATORS: command description */ _("Clears any updates scheduled to be updated offline"), fu_util_clear_offline); fu_util_cmd_array_add (cmd_array, "get-results", "DEVICE_ID", /* TRANSLATORS: command description */ _("Gets the results from the last update"), fu_util_get_results); fu_util_cmd_array_add (cmd_array, "get-releases", "[DEVICE_ID]", /* TRANSLATORS: command description */ _("Gets the releases for a device"), fu_util_get_releases); fu_util_cmd_array_add (cmd_array, "get-remotes", NULL, /* TRANSLATORS: command description */ _("Gets the configured remotes"), fu_util_get_remotes); fu_util_cmd_array_add (cmd_array, "downgrade", "[DEVICE_ID]", /* TRANSLATORS: command description */ _("Downgrades the firmware on a device"), fu_util_downgrade); fu_util_cmd_array_add (cmd_array, "refresh", "[FILE FILE_SIG REMOTE_ID]", /* TRANSLATORS: command description */ _("Refresh metadata from remote server"), fu_util_refresh); fu_util_cmd_array_add (cmd_array, "verify-update", "[DEVICE_ID]", /* TRANSLATORS: command description */ _("Update the stored metadata with current ROM contents"), fu_util_verify_update); fu_util_cmd_array_add (cmd_array, "modify-remote", "REMOTE-ID KEY VALUE", /* TRANSLATORS: command description */ _("Modifies a given remote"), fu_util_remote_modify); fu_util_cmd_array_add (cmd_array, "enable-remote", "REMOTE-ID", /* TRANSLATORS: command description */ _("Enables a given remote"), fu_util_remote_enable); fu_util_cmd_array_add (cmd_array, "disable-remote", "REMOTE-ID", /* TRANSLATORS: command description */ _("Disables a given remote"), fu_util_remote_disable); fu_util_cmd_array_add (cmd_array, "activate", "[DEVICE-ID]", /* TRANSLATORS: command description */ _("Activate devices"), fu_util_activate); fu_util_cmd_array_add (cmd_array, "get-approved-firmware", NULL, /* TRANSLATORS: firmware approved by the admin */ _("Gets the list of approved firmware."), fu_util_get_approved_firmware); fu_util_cmd_array_add (cmd_array, "set-approved-firmware", "CHECKSUM1[,CHECKSUM2][,CHECKSUM3]", /* TRANSLATORS: firmware approved by the admin */ _("Sets the list of approved firmware."), fu_util_set_approved_firmware); fu_util_cmd_array_add (cmd_array, "modify-config", "KEY,VALUE", /* TRANSLATORS: sets something in daemon.conf */ _("Modifies a daemon configuration value."), fu_util_modify_config); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new (); g_unix_signal_add_full (G_PRIORITY_DEFAULT, SIGINT, fu_util_sigint_cb, priv, NULL); /* sort by command name */ fu_util_cmd_array_sort (cmd_array); /* non-TTY consoles cannot answer questions */ if (isatty (fileno (stdout)) == 0) { priv->no_unreported_check = TRUE; priv->no_metadata_check = TRUE; priv->no_reboot_check = TRUE; fu_progressbar_set_interactive (priv->progressbar, FALSE); } /* get a list of the commands */ priv->context = g_option_context_new (NULL); cmd_descriptions = fu_util_cmd_array_to_string (cmd_array); g_option_context_set_summary (priv->context, cmd_descriptions); g_option_context_set_description (priv->context, "This tool allows an administrator to query and control the " "fwupd daemon, allowing them to perform actions such as " "installing or downgrading firmware."); /* TRANSLATORS: program name */ g_set_application_name (_("Firmware Utility")); g_option_context_add_main_entries (priv->context, options, NULL); ret = g_option_context_parse (priv->context, &argc, &argv, &error); if (!ret) { /* TRANSLATORS: the user didn't read the man page */ g_print ("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* set verbose? */ if (verbose) { g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); } else { g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, fu_util_ignore_cb, NULL); } /* set flags */ if (offline) priv->flags |= FWUPD_INSTALL_FLAG_OFFLINE; if (allow_reinstall) priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; if (allow_older) priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; if (force) priv->flags |= FWUPD_INSTALL_FLAG_FORCE; if (no_history) priv->flags |= FWUPD_INSTALL_FLAG_NO_HISTORY; /* connect to the daemon */ priv->client = fwupd_client_new (); g_signal_connect (priv->client, "notify::percentage", G_CALLBACK (fu_util_client_notify_cb), priv); g_signal_connect (priv->client, "notify::status", G_CALLBACK (fu_util_client_notify_cb), priv); /* just show versions and exit */ if (version) { g_autofree gchar *version_str = fu_util_get_versions(); g_print ("%s\n", version_str); if (!fwupd_client_connect (priv->client, priv->cancellable, &error)) { g_printerr ("Failed to connect to daemon: %s\n", error->message); return EXIT_FAILURE; } g_print ("daemon version:\t%s\n", fwupd_client_get_daemon_version (priv->client)); return EXIT_SUCCESS; } /* show a warning if the daemon is tainted */ if (!fwupd_client_connect (priv->client, priv->cancellable, &error)) { g_printerr ("Failed to connect to daemon: %s\n", error->message); return EXIT_FAILURE; } if (fwupd_client_get_tainted (priv->client)) { g_printerr ("WARNING: The daemon has loaded 3rd party code and " "is no longer supported by the upstream developers!\n"); } /* check that we have at least this version daemon running */ if (!fu_util_check_daemon_version (priv, &error)) { g_printerr ("%s\n", error->message); return EXIT_FAILURE; } #ifdef HAVE_SYSTEMD /* make sure the correct daemon is in use */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && !fu_util_using_correct_daemon (&error)) { g_printerr ("%s\n", error->message); return EXIT_FAILURE; } #endif /* make sure polkit actions were installed */ if (!fu_util_check_polkit_actions (&error)) { g_printerr ("%s\n", error->message); return EXIT_FAILURE; } /* run the specified command */ ret = fu_util_cmd_array_run (cmd_array, priv, argv[1], (gchar**) &argv[2], &error); if (!ret) { if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help (priv->context, TRUE, NULL); g_print ("%s\n\n%s", error->message, tmp); return EXIT_FAILURE; } if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_print ("%s\n", error->message); return EXIT_NOTHING_TO_DO; } g_print ("%s\n", error->message); return EXIT_FAILURE; } /* success */ return EXIT_SUCCESS; } fwupd-1.2.14/src/fuzzing/000077500000000000000000000000001402665037500152175ustar00rootroot00000000000000fwupd-1.2.14/src/fuzzing/README.md000066400000000000000000000002741402665037500165010ustar00rootroot00000000000000Fuzzing ======= CC=afl-gcc meson --default-library=static ../ AFL_HARDEN=1 ninja afl-fuzz -m 300 -i ../src/fuzzing/smbios -o src/smbios/findings ./src/fwupdmgr smbios-dump @@ fwupd-1.2.14/src/fuzzing/smbios/000077500000000000000000000000001402665037500165135ustar00rootroot00000000000000fwupd-1.2.14/src/fuzzing/smbios/DMI-MicroServer.bin000066400000000000000000000000001402665037500220420ustar00rootroot00000000000000fwupd-1.2.14/src/fuzzing/smbios/DMI-T440s.bin000066400000000000000000000000001402665037500204200ustar00rootroot00000000000000fwupd-1.2.14/src/fuzzing/smbios/DMI-xps13.bin000066400000000000000000000000001402665037500205600ustar00rootroot00000000000000fwupd-1.2.14/src/fwupd.gresource.xml000066400000000000000000000003251402665037500173670ustar00rootroot00000000000000 org.freedesktop.fwupd.xml fwupd-1.2.14/src/meson.build000066400000000000000000000213201402665037500156630ustar00rootroot00000000000000if get_option('daemon') install_data(['org.freedesktop.fwupd.xml'], install_dir : join_paths(datadir, 'dbus-1', 'interfaces') ) endif keyring_deps = [] keyring_src = [] test_deps = [] init_src = [] if get_option('gpg') keyring_src += 'fu-keyring-gpg.c' keyring_deps += gpgme keyring_deps += gpgerror endif if get_option('pkcs7') keyring_src += 'fu-keyring-pkcs7.c' keyring_deps += gnutls if get_option('tests') test_deps += colorhug_pkcs7_signature endif endif if get_option('systemd') init_src += 'fu-systemd.c' endif libfwupdprivate = static_library( 'fwupdprivate', sources : [ init_src, 'fu-archive.c', 'fu-common.c', 'fu-common-guid.c', 'fu-common-version.c', 'fu-chunk.c', 'fu-device.c', 'fu-device-locker.c', 'fu-hwids.c', 'fu-history.c', 'fu-io-channel.c', 'fu-plugin.c', 'fu-progressbar.c', 'fu-quirks.c', 'fu-smbios.c', 'fu-test.c', 'fu-udev-device.c', 'fu-usb-device.c', ], include_directories : [ include_directories('..'), include_directories('../libfwupd'), ], dependencies : [ giounix, gudev, gusb, soup, sqlite, libarchive, libjsonglib, libxmlb, valgrind, ], link_with : [ fwupd, ], c_args : [ '-DFU_OFFLINE_DESTDIR=""', ], ) if get_option('daemon') fwupdmgr = executable( 'fwupdmgr', sources : [ 'fu-util.c', 'fu-util-common.c', ], include_directories : [ include_directories('..'), include_directories('../libfwupd'), ], dependencies : [ libxmlb, giounix, gudev, gusb, soup, sqlite, libarchive, libjsonglib, ], link_with : [ fwupd, libfwupdprivate, ], c_args : [ '-DFU_OFFLINE_DESTDIR=""', ], install : true, install_dir : bindir ) endif if get_option('agent') fwupdagent = executable( 'fwupdagent', sources : [ 'fu-agent.c', 'fu-util-common.c', ], include_directories : [ include_directories('..'), include_directories('../libfwupd'), ], dependencies : [ libxmlb, giounix, gudev, gusb, soup, libjsonglib, ], link_with : [ fwupd, libfwupdprivate, ], install : true, install_dir : join_paths(libexecdir, 'fwupd') ) endif if get_option('systemd') fwupdagent = executable( 'fwupdoffline', sources : [ 'fu-offline.c', 'fu-util-common.c', ], include_directories : [ include_directories('..'), include_directories('../libfwupd'), ], dependencies : [ giounix, gudev, gusb, soup, ], link_with : [ fwupd, libfwupdprivate, ], c_args : [ '-DFU_OFFLINE_DESTDIR=""', ], install : true, install_dir : join_paths(libexecdir, 'fwupd') ) endif resources_src = gnome.compile_resources( 'fwupd-resources', 'fwupd.gresource.xml', source_dir : '.', c_name : 'fu' ) fu_hash = custom_target( 'fu-hash.h', input : libfwupdprivate, output : 'fu-hash.h', command : [python3.path(), join_paths(meson.current_source_dir(), 'fu-hash.py'), '@INPUT@', '@OUTPUT@'] ) fwupdtool = executable( 'fwupdtool', resources_src, fu_hash, sources : [ 'fu-tool.c', keyring_src, init_src, 'fu-archive.c', 'fu-chunk.c', 'fu-common.c', 'fu-common-cab.c', 'fu-common-guid.c', 'fu-common-version.c', 'fu-config.c', 'fu-keyring.c', 'fu-keyring-result.c', 'fu-engine.c', 'fu-hwids.c', 'fu-debug.c', 'fu-device.c', 'fu-device-list.c', 'fu-device-locker.c', 'fu-idle.c', 'fu-install-task.c', 'fu-io-channel.c', 'fu-keyring.c', 'fu-keyring-utils.c', 'fu-history.c', 'fu-plugin.c', 'fu-plugin-list.c', 'fu-quirks.c', 'fu-smbios.c', 'fu-udev-device.c', 'fu-usb-device.c', 'fu-util-common.c', ], include_directories : [ include_directories('..'), include_directories('../libfwupd'), ], dependencies : [ keyring_deps, libxmlb, libgcab, giounix, gmodule, gudev, gusb, soup, sqlite, valgrind, libarchive, libjsonglib, ], link_with : [ fwupd, libfwupdprivate, ], c_args : [ '-DFU_OFFLINE_DESTDIR=""', ], install : true, install_dir : join_paths(libexecdir, 'fwupd') ) if get_option('daemon') and get_option('man') help2man = find_program('help2man') custom_target('fwupdmgr-man', input : fwupdmgr, output : 'fwupdmgr.1', command : [ help2man, '@INPUT@', '--no-info', '--output', '@OUTPUT@', '--name', 'fwupd', '--manual', 'User Commands', '--version-string', fwupd_version, ], install : true, install_dir : join_paths(mandir, 'man1'), ) endif if get_option('daemon') executable( 'fwupd', resources_src, fu_hash, sources : [ keyring_src, init_src, 'fu-archive.c', 'fu-chunk.c', 'fu-common.c', 'fu-common-cab.c', 'fu-common-guid.c', 'fu-common-version.c', 'fu-config.c', 'fu-keyring.c', 'fu-keyring-result.c', 'fu-engine.c', 'fu-main.c', 'fu-hwids.c', 'fu-debug.c', 'fu-device.c', 'fu-device-list.c', 'fu-device-locker.c', 'fu-idle.c', 'fu-io-channel.c', 'fu-install-task.c', 'fu-keyring.c', 'fu-keyring-utils.c', 'fu-history.c', 'fu-plugin.c', 'fu-plugin-list.c', 'fu-quirks.c', 'fu-smbios.c', 'fu-udev-device.c', 'fu-usb-device.c', ], include_directories : [ include_directories('..'), include_directories('../libfwupd'), ], dependencies : [ keyring_deps, libxmlb, libgcab, giounix, gmodule, gudev, gusb, polkit, soup, sqlite, valgrind, libarchive, libjsonglib, ], link_with : fwupd, c_args : [ '-DFU_OFFLINE_DESTDIR=""', ], install : true, install_dir : join_paths(libexecdir, 'fwupd') ) endif if get_option('tests') testdatadir_src = join_paths(meson.source_root(), 'data', 'tests') testdatadir_dst = join_paths(meson.build_root(), 'data', 'tests') pluginbuilddir = join_paths(meson.build_root(), 'plugins', 'test') e = executable( 'fu-self-test', resources_src, colorhug_test_firmware, builder_test_firmware, hwid_test_firmware, noreqs_test_firmware, test_deps, fu_hash, sources : [ keyring_src, init_src, 'fu-self-test.c', 'fu-archive.c', 'fu-chunk.c', 'fu-common.c', 'fu-common-cab.c', 'fu-common-guid.c', 'fu-common-version.c', 'fu-config.c', 'fu-engine.c', 'fu-keyring.c', 'fu-keyring-utils.c', 'fu-hwids.c', 'fu-device.c', 'fu-device-list.c', 'fu-device-locker.c', 'fu-history.c', 'fu-idle.c', 'fu-install-task.c', 'fu-io-channel.c', 'fu-keyring.c', 'fu-keyring-result.c', 'fu-plugin.c', 'fu-plugin-list.c', 'fu-progressbar.c', 'fu-quirks.c', 'fu-smbios.c', 'fu-test.c', 'fu-udev-device.c', 'fu-usb-device.c', ], include_directories : [ include_directories('..'), include_directories('../libfwupd'), ], dependencies : [ keyring_deps, libxmlb, libgcab, giounix, gmodule, gudev, gusb, soup, sqlite, valgrind, libarchive, libjsonglib, ], link_with : [ fwupd, ], c_args : [ '-DTESTDATADIR_SRC="' + testdatadir_src + '"', '-DTESTDATADIR_DST="' + testdatadir_dst + '"', '-DTESTDATADIR="' + testdatadir_src + ':' + testdatadir_dst + '"', '-DPLUGINBUILDDIR="' + pluginbuilddir + '"', '-DFU_OFFLINE_DESTDIR="/tmp/fwupd-self-test"', ], ) test('fu-self-test', e, is_parallel:false, timeout:180) endif if get_option('introspection') gir_dep = declare_dependency(sources: gir) gnome.generate_gir(fwupd, sources : [ 'fu-archive.c', 'fu-archive.h', 'fu-chunk.c', 'fu-chunk.h', 'fu-common.c', 'fu-common-guid.c', 'fu-common-guid.h', 'fu-common-version.c', 'fu-common-version.h', 'fu-common.h', 'fu-device.c', 'fu-device.h', 'fu-device-locker.c', 'fu-device-locker.h', 'fu-io-channel.c', 'fu-plugin.c', 'fu-plugin.h', 'fu-quirks.c', 'fu-quirks.h', 'fu-udev-device.c', 'fu-usb-device.c', ], nsversion : '1.0', namespace : 'Fu', symbol_prefix : 'fu', identifier_prefix : 'Fu', export_packages : 'fu', include_directories : [ include_directories('..'), include_directories('../libfwupd'), ], dependencies : [ libxmlb, gir_dep, giounix, gusb, soup, sqlite, ], link_with : [ libfwupdprivate, ], includes : [ 'Gio-2.0', 'GObject-2.0', 'GUsb-1.0', ], ) endif fwupd-1.2.14/src/org.freedesktop.fwupd.xml000066400000000000000000000471101402665037500204750ustar00rootroot00000000000000 The interface used for querying firmware for the system. The daemon version. If the daemon has been tainted with a 3rd party plugin. The daemon status, e.g. decompressing. The job percentage completion, or 0 for unknown. Gets a list of all the devices that are supported. An array of devices, with any properties set on each. Gets a list of all the releases for a specific device. A device ID. An array of releases (with the release number as the key), with any properties set on each. Gets a list of all the downgrades possible for a specific device. A device ID. An array of releases (with the release number as the key), with any properties set on each. Gets a list of all the upgrades possible for a specific device. A device ID. An array of releases (with the release number as the key), with any properties set on each. Gets details about a local firmware file. An index into the array of file descriptors that may have been sent with the DBus message. An array of results, with any properties set on each. Gets a list of all the past firmware updates. An array of devices, with any properties set on each. Schedules a firmware to be installed. An ID, typically a GUID of the hardware to update, or the string * to match any applicable hardware. An index into the array of file descriptors that may have been sent with the DBus message. Options to be used when constructing the profile, e.g. offline=True. Verifies firmware on a device by reading it back and performing a cryptographic hash, typically SHA1. An ID, typically a GUID of the hardware. Updates the cryptographic hash stored for a device. An ID, typically a GUID of the hardware. Unlock the device to allow firmware access. An ID, typically a GUID of the hardware. Activate a firmware update on the device. An ID, typically the sha hash of the device string. Gets the results of an offline update. An ID, typically a GUID of the hardware that was updated, or the string * to match any hardware. Results about the update, e.g. success=True Gets the list of remotes. The array remotes, with properties Gets the list of approved firmware that can be applied to devices. In an enterprise this will be configured by a domain administrator. The checksums of the archives Sets the list of approved firmware that can be applied to devices. In an enterprise this will be configured by a domain administrator. The checksums of the archives Clears the results of an offline update. An ID, typically a GUID of the hardware that was updated, or the string * to match any hardware. Modifies a remote in some way. A device ID, or the string * to match any hardware. The key, e.g. 'Flags'. The value of the correct type, e.g. a URL. Modify persistent configuration for daemon The key, e.g. 'BlacklistPlugins'. The value of the correct type, e.g. a URL. Adds AppStream resource information from a session client. Remote ID to tag the metadata objects with, e.g. 'lvfs-testing'. File handle to AppStream metadata. File handle to AppStream metadata GPG signature. Modifies a remote in some way. Remote ID, e.g. 'lvfs-testing'. The key, e.g. 'Enabled'. The value of the correct type, e.g. a URL. Signs some text, typically using a self-signed PKCS-7 certificate. String input data, certainly *NOT* binary data. Options to be used when signing, e.g. add-cert=True or add-timestamp=True. The detached signature string. Some value on the interface or the number of devices or profiles has changed. A device structure. A device has been added. A device structure. A device has been removed. A device structure. A device has been changed. fwupd-1.2.14/subprojects/000077500000000000000000000000001402665037500152775ustar00rootroot00000000000000fwupd-1.2.14/subprojects/.gitignore000066400000000000000000000000211402665037500172600ustar00rootroot00000000000000flashrom libxmlb fwupd-1.2.14/subprojects/flashrom.wrap000066400000000000000000000001531402665037500200040ustar00rootroot00000000000000[wrap-git] directory = flashrom url = https://github.com/hughsie/flashrom.git revision = wip/hughsie/fwupd fwupd-1.2.14/subprojects/libxmlb.wrap000066400000000000000000000001351402665037500176220ustar00rootroot00000000000000[wrap-git] directory = libxmlb url = https://github.com/hughsie/libxmlb.git revision = 0.1.7