pax_global_header00006660000000000000000000000064136277523360014530gustar00rootroot0000000000000052 comment=3343edb45b6c8d9d38662414eafb40cf8918e147 fwupd-1.3.9/000077500000000000000000000000001362775233600126675ustar00rootroot00000000000000fwupd-1.3.9/.circleci/000077500000000000000000000000001362775233600145225ustar00rootroot00000000000000fwupd-1.3.9/.circleci/config.yml000066400000000000000000000134041362775233600165140ustar00rootroot00000000000000version: 2 jobs: check-abi: machine: image: circleci/classic:latest steps: - checkout - run: name: "Build container" command: OS=debian-x86_64 ./contrib/ci/generate_docker.py - run: name: "Run build script" command: docker run -t -v `pwd`:/build fwupd-debian-x86_64 ./contrib/ci/check-abi $(git describe --abbrev=0 --tags) $(git rev-parse HEAD) build-ubuntu-x86_64: machine: image: circleci/classic:latest steps: - checkout - run: name: "Build container" command: OS=ubuntu-x86_64 ./contrib/ci/generate_docker.py - run: name: "Run build script" command: docker run --privileged -e CI=true -t -v `pwd`/dist:/build/dist fwupd-ubuntu-x86_64 - persist_to_workspace: root: . paths: - "dist/docs" build-windows: docker: - image: fedora:31 steps: - run: name: "Install deps" command: dnf install -y diffutils glib2-devel git-core gnutls-utils meson git gcc gcab mingw32-nsis mingw64-gcc mingw64-pkg-config mingw64-glib2 mingw64-libgusb mingw64-sqlite mingw64-libarchive mingw64-json-glib mingw64-libsoup wine - checkout - run: name: "Build Win32" command: ./contrib/ci/build_windows.sh - persist_to_workspace: root: . paths: - "dist/setup/*.exe" - "dist/VERSION" - store_artifacts: path: dist/setup build-snap: docker: - image: ubuntu:18.04 steps: - run: name: "Update apt" command: apt update - run: name: "install snapcraft" command: apt install snapcraft git -y - checkout - run: name: "Build Snap" command: snapcraft - persist_to_workspace: root: . paths: - "*.snap" publish-docs: machine: true steps: - attach_workspace: at: . - add_ssh_keys: fingerprints: - "d8:73:05:1b:7c:93:8c:12:41:78:15:3d:5d:af:b4:c2" - run: name: Clone docs working_directory: dist/docs command: | git clone --depth 1 git@github.com:fwupd/fwupd.github.io.git - deploy: name: Trigger docs deployment working_directory: dist/docs/fwupd.github.io command: | git config credential.helper 'cache --timeout=120' git config user.email "info@fwupd.org" git config user.name "Documentation deployment Bot" rm -rf * cp ../html/* . -R git add . git commit -a --allow-empty -m "Trigger deployment" git push git@github.com:fwupd/fwupd.github.io.git publish-edge: docker: - image: cibuilds/snapcraft:stable steps: - attach_workspace: at: . - run: name: "Publish to Store" command: | mkdir .snapcraft echo $SNAPCRAFT_LOGIN_FILE | base64 --decode --ignore-garbage > .snapcraft/snapcraft.cfg snapcraft push *.snap --release edge publish-stable: docker: - image: cibuilds/snapcraft:stable steps: - attach_workspace: at: . - run: name: "Publish to Store" command: | mkdir .snapcraft echo $SNAPCRAFT_LOGIN_FILE | base64 --decode --ignore-garbage > .snapcraft/snapcraft.cfg snapcraft push *.snap --release stable publish-github-exe-release: docker: - image: circleci/golang:1.9 steps: - attach_workspace: at: . - run: name: "Publish Release on GitHub" command: | go get github.com/tcnksm/ghr VERSION=$(cat dist/VERSION) ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${VERSION} ./dist/setup/ workflows: version: 2 main: jobs: - build-windows - build-ubuntu-x86_64 - build-snap - check-abi - publish-edge: requires: - build-snap filters: branches: only: master deploy: jobs: - build-ubuntu-x86_64: filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ - build-windows: filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ - publish-github-exe-release: requires: - build-windows filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ - publish-docs: requires: - build-ubuntu-x86_64 filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ - build-snap: filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ - publish-stable: requires: - build-snap filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ fwupd-1.3.9/.github/000077500000000000000000000000001362775233600142275ustar00rootroot00000000000000fwupd-1.3.9/.github/ISSUE_TEMPLATE.md000066400000000000000000000020751362775233600167400ustar00rootroot00000000000000To 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.3.9/.github/pull_request_template.md000066400000000000000000000002741362775233600211730ustar00rootroot00000000000000Type 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.3.9/.github/stale.yml000066400000000000000000000012631362775233600160640ustar00rootroot00000000000000# Number of days of inactivity before an issue becomes stale daysUntilStale: 30 # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - enhancement - regression # Label to use when marking an issue as stale staleLabel: wontfix # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false fwupd-1.3.9/.gitignore000066400000000000000000000003431362775233600146570ustar00rootroot00000000000000/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.3.9/.gitmodules000066400000000000000000000001561362775233600150460ustar00rootroot00000000000000[submodule "contrib/flatpak"] path = contrib/flatpak url = https://github.com/flathub/org.freedesktop.fwupd fwupd-1.3.9/.lgtm.yml000066400000000000000000000023031362775233600144310ustar00rootroot00000000000000extraction: python: python_setup: version: "3" cpp: prepare: packages: - python3-gi - libarchive-tools - libcogl-pango-dev - python3-pil - python3-cairo - libssl-dev after_prepare: - "wget -O libxmlb.zip https://github.com/hughsie/libxmlb/archive/0.1.13.zip" - "mkdir -p subprojects/libxmlb" - "bsdtar --strip-components=1 -xvf libxmlb.zip -C subprojects/libxmlb" - "wget https://download.flashrom.org/releases/flashrom-v1.2.tar.bz2" - "mkdir -p subprojects/flashrom" - "bsdtar --strip-components=1 -xvf flashrom-v1.2.tar.bz2 -C subprojects/flashrom" - "cd $LGTM_WORKSPACE" - "mkdir installdir" - "wget https://github.com/tpm2-software/tpm2-tss/releases/download/2.3.0/tpm2-tss-2.3.0.tar.gz" - "tar xf tpm2-tss-2.3.0.tar.gz" - "cd tpm2-tss-2.3.0" - "./configure --prefix=$LGTM_WORKSPACE/installdir/usr --disable-doxygen-doc" - "make install" - "export PKG_CONFIG_PATH=$LGTM_WORKSPACE/installdir/usr/lib/pkgconfig:$PKG_CONFIG_PATH" - "export LD_LIBRARY_PATH=$LGTM_WORKSPACE/installdir/usr/lib:$LD_LIBRARY_PATH" index: build_command: - "meson setup build" - "ninja -C build" fwupd-1.3.9/.travis.yml000066400000000000000000000004131362775233600147760ustar00rootroot00000000000000language: c sudo: required dist: trusty services: - docker env: - OS=fedora - OS=debian-x86_64 - OS=arch - OS=debian-i386 install: - ./contrib/ci/generate_docker.py script: - docker run --privileged -e CI=true -t -v `pwd`/dist:/build/dist fwupd-$OS fwupd-1.3.9/.tx/000077500000000000000000000000001362775233600134005ustar00rootroot00000000000000fwupd-1.3.9/.tx/config000066400000000000000000000002111362775233600145620ustar00rootroot00000000000000[main] host = https://www.transifex.com [fwupd.master] file_filter = po/.po source_file = po/fwupd.pot source_lang = en type = PO fwupd-1.3.9/AUTHORS000066400000000000000000000000451362775233600137360ustar00rootroot00000000000000Richard Hughes fwupd-1.3.9/CODE_OF_CONDUCT.md000066400000000000000000000062231362775233600154710ustar00rootroot00000000000000# 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.3.9/COMMITMENT000066400000000000000000000037761362775233600143030ustar00rootroot00000000000000Common 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.3.9/CONTRIBUTING.md000066400000000000000000000025111362775233600151170ustar00rootroot00000000000000Coding 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.3.9/COPYING000066400000000000000000000636421362775233600137350ustar00rootroot00000000000000 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.3.9/MAINTAINERS000066400000000000000000000000451362775233600143630ustar00rootroot00000000000000Richard Hughes fwupd-1.3.9/README.md000066400000000000000000000110301362775233600141410ustar00rootroot00000000000000fwupd ===== [![Build Status](https://travis-ci.org/fwupd/fwupd.png?branch=master)](https://travis-ci.org/fwupd/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/fwupd/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. Fuzzing ------- There are several automated fuzzing tests in fwupd. These take some time to run: CC=afl-gcc meson --default-library=static ../ AFL_HARDEN=1 ninja ninja fuzz-synaptics-rmi ninja fuzz-firmware ninja fuzz-smbios fwupd-1.3.9/RELEASE000066400000000000000000000017211362775233600136730ustar00rootroot00000000000000fwupd Release Notes 0. Create release version export release_ver= 1. Write release entries: git log --format="%s" --cherry-pick --right-only $(git describe --tags --abbrev=0)..HEAD | 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. Update translations: ninja-build fwupd-pot tx push --source tx pull --all --force --minimum-perc=5 ninja-build fix-translations git add ../po/*.po 3. Commit changes to git: git commit -a -m "Release fwupd ${release_ver}" git tag -s -f -m "Release fwupd ${release_ver}" "${release_ver}" git push --tags git push 4. Generate the tarball: ninja dist 4a. Generate the additional verification metadata gpg -b -a meson-dist/fwupd-${release_ver}.tar.xz 5. Upload tarball: scp meson-dist/fwupd-${release_ver}.tar.* hughsient@people.freedesktop.org:~/public_html/releases fwupd-1.3.9/SECURITY.md000066400000000000000000000025611362775233600144640ustar00rootroot00000000000000# 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. Additionally, 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.3.9/contrib/000077500000000000000000000000001362775233600143275ustar00rootroot00000000000000fwupd-1.3.9/contrib/PKGBUILD000066400000000000000000000017051362775233600154560ustar00rootroot00000000000000# Maintainer: Bruno Pagani (a.k.a. ArchangeGabriel) # Contributor: Mirco Tischler pkgname=fwupd pkgver=dummy pkgrel=1 pkgdesc='A simple daemon to allow session software to update firmware' arch=('i686' 'x86_64') url='https://github.com/fwupd/fwupd' license=('GPL2') depends=('libgusb' 'modemmanager' 'tpm2-tss') makedepends=('meson' 'valgrind' 'gobject-introspection' 'gtk-doc' 'python-pillow' 'git' 'python-cairo' 'noto-fonts' 'noto-fonts-cjk' 'python-gobject' 'vala' 'libsoup' 'polkit' 'gcab') pkgver() { cd ${pkgname} VERSION=$(./contrib/get-version.py | sed 's/-/.r/;s/-/./') echo $VERSION } build() { cd ${pkgname} if [ -n "$CI" ]; then export CI="--werror --wrap-mode=default" fi arch-meson -D b_lto=false $CI ../build ninja -v -C ../build } check() { ninja -C build test } package() { DESTDIR="${pkgdir}" ninja -C build install } fwupd-1.3.9/contrib/README.md000066400000000000000000000042171362775233600156120ustar00rootroot00000000000000Distribution 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.3.9/contrib/afl-fuzz.py000077500000000000000000000032461362775233600164470ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ import argparse import sys import subprocess import os def main(): parser = argparse.ArgumentParser(description='Run afl-fuzz on all cores') parser.add_argument('--input', '-i', help='fuzzing input directory') parser.add_argument('--output', '-o', help='findings output directory') parser.add_argument('--command', type=str, help='fuzzer tool command') parser.add_argument('path', type=str, help='the fuzzer tool') args = parser.parse_args() if not args.input and not args.output: print('-i and -o required') return 1 if not args.path: print('tool name required') return 1 # create if not already exists if not os.path.exists(args.output): os.makedirs(args.output) # run the main instance envp = None argv = ['afl-fuzz', '-m300', '-i', args.input, '-o', args.output, '-M', 'fuzzer00', args.path] if args.command: argv.append(args.command) argv.append('@@') print(argv) p = subprocess.Popen(argv, env=envp) # run the secondary instances cs = [] for i in range(1, os.cpu_count()): argv = ['afl-fuzz', '-m300', '-i', args.input, '-o', args.output, '-S', 'fuzzer%02i' % i, args.path] if args.command: argv.append(args.command) argv.append('@@') print(argv) cs.append(subprocess.Popen(argv, env=envp, stdout=subprocess.DEVNULL)) # wait for the main instance try: p.wait() except KeyboardInterrupt as _: pass for c in cs: c.terminate() return 0 if __name__ == '__main__': sys.exit(main()) fwupd-1.3.9/contrib/ci/000077500000000000000000000000001362775233600147225ustar00rootroot00000000000000fwupd-1.3.9/contrib/ci/Dockerfile-arch.in000066400000000000000000000005271362775233600202400ustar00rootroot00000000000000FROM 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%%% RUN mkdir /build WORKDIR /build COPY . . CMD ["./contrib/ci/arch.sh"] fwupd-1.3.9/contrib/ci/Dockerfile-centos.in000066400000000000000000000011001362775233600206020ustar00rootroot00000000000000FROM 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 RUN mkdir /build WORKDIR /build COPY . . CMD ["./contrib/ci/centos.sh"] fwupd-1.3.9/contrib/ci/Dockerfile-debian.in000066400000000000000000000012151362775233600205400ustar00rootroot00000000000000FROM %%%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: lintian\n\ Pin: release a=unstable\n\ Pin-Priority: 901\n\ \n\ Package: tpm-udev\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%%% RUN mkdir /build WORKDIR /build COPY . . CMD ["./contrib/ci/debian.sh"] fwupd-1.3.9/contrib/ci/Dockerfile-fedora.in000066400000000000000000000005151362775233600205600ustar00rootroot00000000000000FROM fedora:31 %%%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 mkdir /build WORKDIR /build COPY . . CMD ["./contrib/ci/fedora.sh"] fwupd-1.3.9/contrib/ci/Dockerfile-flatpak.in000066400000000000000000000004261362775233600207430ustar00rootroot00000000000000FROM 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 RUN mkdir /build WORKDIR /build COPY . . CMD ["./contrib/ci/flatpak.py"] fwupd-1.3.9/contrib/ci/Dockerfile-snap.in000066400000000000000000000017651362775233600202710ustar00rootroot00000000000000FROM 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.3.9/contrib/ci/Dockerfile-ubuntu.in000066400000000000000000000003241362775233600206400ustar00rootroot00000000000000FROM ubuntu:devel %%%OS%%% ENV CC clang-6.0 RUN echo fubar > /etc/machine-id %%%ARCH_SPECIFIC_COMMAND%%% %%%INSTALL_DEPENDENCIES_COMMAND%%% RUN mkdir /build WORKDIR /build COPY . . CMD ["./contrib/ci/ubuntu.sh"] fwupd-1.3.9/contrib/ci/README.md000066400000000000000000000106461362775233600162100ustar00rootroot00000000000000Continuous Integration ====================== Continuous integration for fwupd is provided by [Travis CI](https://travis-ci.org/fwupd/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 For convenience there is also a standalone script __generate_dependencies.py__ that parses ___dependencies.xml___. 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.3.9/contrib/ci/abidiff.suppr000066400000000000000000000004341362775233600174020ustar00rootroot00000000000000[suppress_type] type_kind = enum changed_enumerators = FWUPD_ERROR_LAST,FWUPD_GUID_FLAG_LAST,FWUPD_INSTALL_FLAG_LAST,FWUPD_KEYRING_KIND_LAST,FWUPD_REMOTE_KIND_LAST,FWUPD_SELF_SIGN_FLAG_LAST,FWUPD_STATUS_LAST,FWUPD_TRUST_FLAG_LAST,FWUPD_UPDATE_STATE_LAST,FWUPD_VERSION_FORMAT_LAST fwupd-1.3.9/contrib/ci/arch.sh000077500000000000000000000014211362775233600161740ustar00rootroot00000000000000#!/bin/bash set -e set -x shopt -s extglob # prepare the build tree rm -rf build mkdir build && pushd build cp ../contrib/PKGBUILD . mkdir -p src/fwupd && pushd src/fwupd cp -R ../../../!(build|dist) . popd chown nobody . -R # install and run TPM simulator necessary for plugins/uefi/uefi-self-test pacman -S --noconfirm ibm-sw-tpm2 tpm2-tools tpm_server & trap "kill $!" EXIT # extend a PCR0 value for test suite sleep 2 tpm2_startup -c tpm2_pcrextend 0:sha1=f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 export TPM_SERVER_RUNNING=1 # build the package and install it sudo -E -u nobody PKGEXT='.pkg.tar' makepkg -e --noconfirm pacman -U --noconfirm *.pkg.* # move the package to working dir mv *.pkg.* ../dist # no testing here because gnome-desktop-testing isn’t available in Arch fwupd-1.3.9/contrib/ci/build_windows.sh000077500000000000000000000032451362775233600201360ustar00rootroot00000000000000#!/bin/sh set -e #prep export LC_ALL=C.UTF-8 export DESTDIR=`pwd`/dist build=`pwd`/build-win32 rm -rf $DESTDIR $build #build mkdir -p $build $DESTDIR && cd $build echo $(../contrib/get-version.py) > $DESTDIR/VERSION meson .. \ --cross-file=../contrib/mingw64.cross \ --prefix=/ \ --libexecdir=$target \ --bindir=$target \ -Dbuild=standalone \ -Dgpg=false \ -Dplugin_coreboot=false \ -Dplugin_flashrom=false \ -Dplugin_uefi=false \ -Dplugin_redfish=false \ -Dplugin_altos=false \ -Dplugin_dell=false \ -Dplugin_nvme=false \ -Dplugin_tpm=false \ -Dsystemd=false \ -Dplugin_emmc=false \ -Dplugin_amt=false \ -Dintrospection=false \ -Dplugin_thunderbolt=false \ -Dplugin_synaptics=false \ -Dman=false \ -Dgcab:introspection=false \ -Dgcab:docs=false \ -Dgcab:nls=false \ -Dgcab:vapi=false \ -Dgcab:tests=false \ -Dlibxmlb:introspection=false \ -Dlibxmlb:gtkdoc=false \ -Dgudev=false $@ ninja -v #prepare archive to run on Windows ninja -v install cd $DESTDIR # create a setup binary mkdir -p $DESTDIR/setup makensis -NOCD $build/contrib/setup-win32.nsi #so that it's actually executable cp /usr/x86_64-w64-mingw32/sys-root/mingw/bin/*.dll . #remove static archives find -type f -name "*.dll.a" | xargs rm -f #remove stuff that we really don't need rm -fr gcab.exe \ xb-tool.exe \ share/man \ include \ fwupd \ lib/*.a \ lib/pkgconfig/ \ var export WINEPATH="/usr/x86_64-w64-mingw32/sys-root/mingw/bin/;$build/libfwupd/;$build/subprojects/libxmlb/src/;$build/subprojects/gcab/libgcab/" #TODO: fixup tests ninja -C $build test || true fwupd-1.3.9/contrib/ci/centos.sh000077500000000000000000000005071362775233600165560ustar00rootroot00000000000000#!/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.3.9/contrib/ci/check-abi000077500000000000000000000063071362775233600164640ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import contextlib import os import shutil import subprocess import sys def format_title(title): box = { 'tl': '╔', 'tr': '╗', 'bl': '╚', 'br': '╝', 'h': '═', 'v': '║', } hline = box['h'] * (len(title) + 2) return '\n'.join([ f"{box['tl']}{hline}{box['tr']}", f"{box['v']} {title} {box['v']}", f"{box['bl']}{hline}{box['br']}", ]) def rm_rf(path): try: shutil.rmtree(path) except FileNotFoundError: pass def sanitize_path(name): return name.replace('/', '-') def get_current_revision(): revision = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], encoding='utf-8').strip() if revision == 'HEAD': # This is a detached HEAD, get the commit hash revision = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('utf-8') return revision @contextlib.contextmanager def checkout_git_revision(revision): current_revision = get_current_revision() subprocess.check_call(['git', 'checkout', '-q', revision]) try: yield finally: subprocess.check_call(['git', 'checkout', '-q', current_revision]) def build_install(revision): build_dir = '_build' dest_dir = os.path.abspath(sanitize_path(revision)) print(format_title(f'# Building and installing {revision} in {dest_dir}'), end='\n\n', flush=True) with checkout_git_revision(revision): rm_rf(build_dir) rm_rf(revision) subprocess.check_call(['meson', build_dir, '--prefix=/usr', '--libdir=lib', '-Db_coverage=false', '-Dgtkdoc=false', '-Dtests=false']) subprocess.check_call(['ninja', '-v', '-C', build_dir]) subprocess.check_call(['ninja', '-v', '-C', build_dir, 'install'], env={'DESTDIR': dest_dir}) return dest_dir def compare(old_tree, new_tree): print(format_title(f'# Comparing the two ABIs'), end='\n\n', flush=True) old_headers = os.path.join(old_tree, 'usr', 'include') old_lib = os.path.join(old_tree, 'usr', 'lib', 'libfwupd.so') new_headers = os.path.join(new_tree, 'usr', 'include') new_lib = os.path.join(new_tree, 'usr', 'lib', 'libfwupd.so') subprocess.check_call([ 'abidiff', '--headers-dir1', old_headers, '--headers-dir2', new_headers, '--drop-private-types', '--suppressions', 'contrib/ci/abidiff.suppr', '--fail-no-debug-info', '--no-added-syms', old_lib, new_lib]) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('old', help='the previous revision, considered the reference') parser.add_argument('new', help='the new revision, to compare to the reference') args = parser.parse_args() if args.old == args.new: print("Let's not waste time comparing something to itself") sys.exit(0) old_tree = build_install(args.old) new_tree = build_install(args.new) try: compare(old_tree, new_tree) except subprocess.CalledProcessError: sys.exit(1) print(f'Hurray! {args.old} and {args.new} are ABI-compatible!') fwupd-1.3.9/contrib/ci/check_missing_translations.sh000077500000000000000000000001251362775233600226660ustar00rootroot00000000000000#!/bin/sh set -e cd po intltool-update -m if [ -f missing ]; then exit 1 fi fwupd-1.3.9/contrib/ci/debian.sh000077500000000000000000000035121362775233600165040ustar00rootroot00000000000000#!/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=`./contrib/get-version.py | sed 's/-/+r/;s/-/+/'` 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 #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 #check lintian output #suppress tags that are side effects of building in docker this way lintian ../*changes \ -IE \ --pedantic \ --no-tag-display-limit \ --suppress-tags bad-distribution-in-changes-file \ --suppress-tags source-contains-unsafe-symlink \ --suppress-tags changelog-should-mention-nmu \ --suppress-tags debian-watch-file-in-native-package \ --suppress-tags source-nmu-has-incorrect-version-number \ --suppress-tags no-symbols-control-file \ --allow-root #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.3.9/contrib/ci/debian_s390x.sh000077500000000000000000000013311362775233600174470ustar00rootroot00000000000000#!/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.3.9/contrib/ci/dependencies.xml000066400000000000000000001054731362775233600201040ustar00rootroot00000000000000 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 (>= 12) (>= 12) 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) glibc-langpack-en 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.13) libxmlb-dev:s390x (>= 0.1.13) 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 amd64 arm64 armhf i386 amd64 arm64 armhf i386 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 tpm2-tss tpm2-tss-devel tpm2-tss-devel libtss2-dev:s390x amd64 arm64 armhf i386 fwupd-1.3.9/contrib/ci/fedora.sh000077500000000000000000000034751362775233600165320ustar00rootroot00000000000000#!/bin/bash set -e set -x #generate a tarball git config tar.tar.xz.command "xz -c" mkdir -p build && pushd build rm -rf * meson .. \ -Db_sanitize=address \ -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=`./contrib/get-version.py` RPMVERSION=${VERSION//-/.} mkdir -p $HOME/rpmbuild/SOURCES/ mv build/meson-dist/fwupd-$VERSION.tar.xz $HOME/rpmbuild/SOURCES/ #generate a spec file sed "s,#VERSION#,$RPMVERSION,; s,#TARBALL_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;invalid,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.3.9/contrib/ci/flatpak.py000077500000000000000000000057061362775233600167310ustar00rootroot00000000000000#!/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.3.9/contrib/ci/generate_debian.py000077500000000000000000000124411362775233600203750ustar00rootroot00000000000000#!/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 except FileNotFoundError: 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.3.9/contrib/ci/generate_dependencies.py000077500000000000000000000042651362775233600216060ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017-2018 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.3.9/contrib/ci/generate_docker.py000077500000000000000000000056371362775233600204330ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017-2018 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # import os import subprocess import sys import tempfile import shutil from generate_dependencies import parse_dependencies def get_container_cmd(): '''return docker or podman as container manager''' if shutil.which('docker'): return 'docker' if shutil.which('podman'): return 'podman' 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() out = tempfile.NamedTemporaryFile(dir='.', delete=True) with open(out.name, '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 --needed\\\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() cmd = get_container_cmd() args = [cmd, "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", "./%s" % os.path.basename(out.name), "."] subprocess.check_call(args) fwupd-1.3.9/contrib/ci/s390x_cross.txt000066400000000000000000000004221362775233600175600ustar00rootroot00000000000000[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.3.9/contrib/ci/snap.sh000077500000000000000000000000371362775233600162220ustar00rootroot00000000000000#!/bin/sh cd /build snapcraft fwupd-1.3.9/contrib/ci/snapcraft-wrapper000077500000000000000000000004671362775233600203160ustar00rootroot00000000000000#!/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.3.9/contrib/ci/ubuntu.sh000077500000000000000000000007761362775233600166150ustar00rootroot00000000000000#!/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 -Dman=false -Dgtkdoc=true #build with clang ninja -C build test -v #make docs available outside of docker ninja -C build install -v mkdir -p dist/docs cp build/docs/* dist/docs -R #run static analysis (these mostly won't be critical) ninja -C build scan-build -v fwupd-1.3.9/contrib/debian/000077500000000000000000000000001362775233600155515ustar00rootroot00000000000000fwupd-1.3.9/contrib/debian/README.Debian000066400000000000000000000013421362775233600176120ustar00rootroot00000000000000signed 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.3.9/contrib/debian/README.source000066400000000000000000000004201362775233600177240ustar00rootroot00000000000000fwupd 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.3.9/contrib/debian/clean000066400000000000000000000000331362775233600165520ustar00rootroot00000000000000gtk-doc.make m4/gtk-doc.m4 fwupd-1.3.9/contrib/debian/compat000066400000000000000000000000031362775233600167500ustar00rootroot0000000000000012 fwupd-1.3.9/contrib/debian/control.in000066400000000000000000000160161362775233600175650ustar00rootroot00000000000000Source: 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.5.0 Section: admin Homepage: https://github.com/fwupd/fwupd Vcs-Git: https://salsa.debian.org/efi-team/fwupd.git Vcs-Browser: https://salsa.debian.org/efi-team/fwupd Package: libfwupdplugin1 Section: libs Architecture: linux-any Depends: ${misc:Depends}, ${shlibs:Depends} Multi-Arch: same Description: Firmware update daemon plugin 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 for the interface between daemon and plugins. 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, fwupd-signed Provides: fwupdate Conflicts: fwupdate-amd64-signed, fwupdate-i386-signed, fwupdate-arm64-signed, fwupdate-armhf-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), fwupdate (<< 12-7) 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: libfwupdplugin-dev Architecture: linux-any Multi-Arch: same Depends: libfwupdplugin1 (= ${binary:Version}), gir1.2-fwupdplugin-1.0 (= ${binary:Version}), ${misc:Depends} Section: libdevel Description: development files for libfwupdplugin 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 libfwupdplugin Package: gir1.2-fwupdplugin-1.0 Architecture: linux-any Multi-Arch: same Depends: ${misc:Depends}, ${gir:Depends} Section: introspection Description: GObject introspection data for libfwupdplugin This package provides the introspection data for libfwupdplugin. . 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.3.9/contrib/debian/copyright.in000066400000000000000000000201131362775233600201060ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: fwupd Source: https://github.com/fwupd/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.3.9/contrib/debian/docs000066400000000000000000000000011362775233600164130ustar00rootroot00000000000000 fwupd-1.3.9/contrib/debian/fwupd-doc.install000066400000000000000000000000221362775233600210230ustar00rootroot00000000000000usr/share/gtk-doc fwupd-1.3.9/contrib/debian/fwupd-tests.install000066400000000000000000000006331362775233600214300ustar00rootroot00000000000000#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 usr/lib/*/fwupd-plugins-3/libfu_plugin_invalid.so debian/lintian/fwupd-tests usr/share/lintian/overrides etc/fwupd/remotes.d/fwupd-tests.conf fwupd-1.3.9/contrib/debian/fwupd-tests.postinst000066400000000000000000000011241362775233600216410ustar00rootroot00000000000000#!/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;invalid,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.3.9/contrib/debian/fwupd-tests.postrm000066400000000000000000000004711362775233600213060ustar00rootroot00000000000000#!/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;invalid," -i /etc/fwupd/daemon.conf else echo "To disable test suite, modify /etc/fwupd/daemon.conf" fi fi fi fwupd-1.3.9/contrib/debian/fwupd.dirs000066400000000000000000000000301362775233600175520ustar00rootroot00000000000000var/cache/app-info/xmls fwupd-1.3.9/contrib/debian/fwupd.install000066400000000000000000000011211362775233600202610ustar00rootroot00000000000000usr/bin/* etc/* usr/share/bash-completion usr/share/fish/vendor_completions.d usr/share/fwupd/* usr/share/dbus-1/* usr/share/icons/* usr/share/polkit-1/* usr/share/locale usr/share/metainfo/* usr/libexec/fwupd/fwupd usr/libexec/fwupd/fwupdoffline usr/share/man/man1/* lib/systemd/system/* lib/systemd/system-preset/* 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 obj*/data/motd/85-fwupd /etc/update-motd.d fwupd-1.3.9/contrib/debian/fwupd.pkla000066400000000000000000000002071362775233600175460ustar00rootroot00000000000000[Call internal fwupd actions] Identity=unix-group:admin;unix-group:sudo Action=org.freedesktop.fwupd.update-internal ResultActive=yes fwupd-1.3.9/contrib/debian/fwupd.postinst000066400000000000000000000020541362775233600205040ustar00rootroot00000000000000#!/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~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/dbus-1/system.d/org.freedesktop.fwupd.conf 1.3.2~ -- "$@" 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.3.9/contrib/debian/fwupd.postrm000066400000000000000000000007211362775233600201440ustar00rootroot00000000000000#!/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~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/fwupd/remotes.d/fwupd.conf 1.2.7~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/dbus-1/system.d/org.freedesktop.fwupd.conf 1.3.2~ -- "$@" fi fwupd-1.3.9/contrib/debian/fwupd.preinst000066400000000000000000000012051362775233600203020ustar00rootroot00000000000000#!/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~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/dbus-1/system.d/org.freedesktop.fwupd.conf 1.3.2~ -- "$@" fi # 1.3.2 had fwupd-refresh.service and fwupd.service both claiming # this directory, but fwupd-refresh.service used DynamicUser directive # meaning no other unit could access it. if [ -L /var/cache/fwupd ]; then rm -f /var/cache/fwupd fi fwupd-1.3.9/contrib/debian/gbp.conf000066400000000000000000000001611362775233600171660ustar00rootroot00000000000000[DEFAULT] debian-branch = debian upstream-tag = %(version)s [buildpackage] sign-tags = True dist = experimental fwupd-1.3.9/contrib/debian/gen_signing_changelog000077500000000000000000000023131362775233600217740ustar00rootroot00000000000000#!/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.3.9/contrib/debian/gen_signing_json000077500000000000000000000013431362775233600210200ustar00rootroot00000000000000#!/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/libexec/${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.3.9/contrib/debian/gir1.2-fwupd-2.0.install000066400000000000000000000000531362775233600215610ustar00rootroot00000000000000usr/lib/*/girepository-1.0/Fwupd-*.typelib fwupd-1.3.9/contrib/debian/gir1.2-fwupdplugin-1.0.install000066400000000000000000000000611362775233600227760ustar00rootroot00000000000000usr/lib/*/girepository-1.0/FwupdPlugin-*.typelib fwupd-1.3.9/contrib/debian/libfwupd-dev.install000066400000000000000000000003101362775233600215230ustar00rootroot00000000000000usr/include/fwupd-1/fwupd.h usr/include/fwupd-1/libfwupd usr/lib/*/libfwupd.so usr/lib/*/pkgconfig/fwupd.pc usr/share/gir-1.0/Fwupd-*.gir usr/share/vala/vapi/fwupd.deps usr/share/vala/vapi/fwupd.vapi fwupd-1.3.9/contrib/debian/libfwupd2.install000066400000000000000000000000301362775233600210300ustar00rootroot00000000000000usr/lib/*/libfwupd.so.* fwupd-1.3.9/contrib/debian/libfwupdplugin-dev.install000066400000000000000000000003621362775233600227510ustar00rootroot00000000000000usr/include/fwupd-1/fwupdplugin.h usr/include/fwupd-1/libfwupdplugin usr/lib/*/libfwupdplugin.so usr/lib/*/pkgconfig/fwupdplugin.pc usr/share/gir-1.0/FwupdPlugin-*.gir usr/share/vala/vapi/fwupdplugin.deps usr/share/vala/vapi/fwupdplugin.vapi fwupd-1.3.9/contrib/debian/libfwupdplugin1.install000066400000000000000000000000361362775233600222540ustar00rootroot00000000000000usr/lib/*/libfwupdplugin.so.* fwupd-1.3.9/contrib/debian/lintian/000077500000000000000000000000001362775233600172075ustar00rootroot00000000000000fwupd-1.3.9/contrib/debian/lintian/fwupd000066400000000000000000000015261362775233600202630ustar00rootroot00000000000000#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 fwupd: library-not-linked-against-libc usr/lib/*/fwupd-plugins-3/libfu_plugin_uefi_recovery.so #EFI applications are PE executables fwupd: executable-not-elf-or-script usr/libexec/fwupd/efi/*.efi fwupd: portable-executable-missing-security-features usr/libexec/fwupd/efi/*.efi SafeSEH fwupd: library-not-linked-against-libc usr/lib/*/fwupd-plugins-3/libfu_plugin_modem_manager.so fwupd-1.3.9/contrib/debian/lintian/fwupd-tests000066400000000000000000000003221362775233600214140ustar00rootroot00000000000000#see debian bug 896012 fwupd-tests: library-not-linked-against-libc usr/lib/*/fwupd-plugins-3/libfu_plugin_test.so fwupd-tests: library-not-linked-against-libc usr/lib/*/fwupd-plugins-3/libfu_plugin_invalid.so fwupd-1.3.9/contrib/debian/rules000077500000000000000000000077701362775233600166440ustar00rootroot00000000000000#!/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 ifneq ($(CI),) export CI=--werror --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 else TMPLDIR := debian/fwupd-$(DEB_HOST_ARCH)-signed-template/usr/share/code-signing/fwupd-$(DEB_HOST_ARCH)-signed-template endif %: dh $@ --with gir override_dh_auto_clean: rm -fr obj-* rm -fr debian/build ifeq (ubuntu,$(SB_STYLE)) rm -rf debian/fwupd-images endif override_dh_auto_configure: if pkg-config --exists libsmbios_c; then \ export DELL="-Dplugin_dell=true"; \ else \ export DELL="-Dplugin_dell=false"; \ fi; \ if pkg-config --exists efivar; then \ export UEFI="-Dplugin_uefi=true -Dplugin_redfish=true -Dplugin_nvme=true"; \ else \ export UEFI="-Dplugin_uefi=false -Dplugin_redfish=false -Dplugin_nvme=false"; \ fi; \ if [ ! -z "$$CI" ]; then \ export FLASHROM="-Dplugin_flashrom=true"; \ else \ export FLASHROM="-Dplugin_flashrom=false"; \ fi; \ dh_auto_configure -- $$UEFI $$DELL $$FLASHROM $$CI -Dplugin_dummy=true -Dgtkdoc=true 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/libexec/fwupd/efi/ ]; then \ dh_install -pfwupd usr/libexec/fwupd/efi ;\ fi if [ ! -z "$$CI" ] && [ -f debian/tmp/usr/sbin/flashrom ]; then \ dh_install -pfwupd usr/sbin/flashrom ;\ dh_install -plibfwupd2 usr/lib/*/libflashrom.so.*; \ dh_install -plibfwupd-dev usr/include/libflashrom.h; \ dh_install -plibfwupd-dev usr/lib/*/libflashrom.so; \ dh_install -plibfwupd-dev usr/lib/*/pkgconfig/flashrom.pc; \ 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/usr/lib/*/fwupd-plugins-3/libfu_plugin_invalid.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/libexec/fwupd/efi/ ]; then \ mkdir -p debian/fwupd-images/$(deb_version) ;\ cp debian/tmp/usr/libexec/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.3.9/contrib/debian/signing-template/000077500000000000000000000000001362775233600210205ustar00rootroot00000000000000fwupd-1.3.9/contrib/debian/signing-template/README.source000066400000000000000000000003311362775233600231740ustar00rootroot00000000000000This 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.3.9/contrib/debian/signing-template/changelog.in000066400000000000000000000002501362775233600232740ustar00rootroot00000000000000fwupd-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.3.9/contrib/debian/signing-template/compat000066400000000000000000000000021362775233600222160ustar00rootroot000000000000009 fwupd-1.3.9/contrib/debian/signing-template/control000066400000000000000000000022441362775233600224250ustar00rootroot00000000000000Source: 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/fwupd/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.3.9/contrib/debian/signing-template/copyright000066400000000000000000000024401362775233600227530ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: fwupd Source: https://github.com/fwupd/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.3.9/contrib/debian/signing-template/fwupd-SIGNARCH-signed.install000066400000000000000000000000441362775233600261760ustar00rootroot00000000000000*.efi.signed /usr/libexec/fwupd/efi fwupd-1.3.9/contrib/debian/signing-template/rules000077500000000000000000000006511362775233600221020ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- PACKAGE_NAME := fwupd SIG_PKG_NAME := fwupd-SIGNARCH-signed SIGNATURE_DIR := debian/signatures/$(PACKAGE_NAME) BINARY := $(shell find /usr/libexec/fwupd/efi -name '*.efi' | xargs basename) %: dh $@ override_dh_auto_build: cp /usr/libexec/fwupd/efi/$(BINARY) . sbattach --attach $(SIGNATURE_DIR)/usr/libexec/fwupd/efi/$(BINARY).sig $(BINARY) mv $(BINARY) $(BINARY).signed fwupd-1.3.9/contrib/debian/signing-template/source/000077500000000000000000000000001362775233600223205ustar00rootroot00000000000000fwupd-1.3.9/contrib/debian/signing-template/source/format000066400000000000000000000000151362775233600235270ustar00rootroot000000000000003.0 (native) fwupd-1.3.9/contrib/debian/source/000077500000000000000000000000001362775233600170515ustar00rootroot00000000000000fwupd-1.3.9/contrib/debian/source/format000066400000000000000000000000141362775233600202570ustar00rootroot000000000000003.0 (quilt) fwupd-1.3.9/contrib/debian/source/include-binaries000066400000000000000000000000421362775233600222050ustar00rootroot00000000000000debian/binary-patches/example.elf fwupd-1.3.9/contrib/debian/source/lintian-overrides000066400000000000000000000001231362775233600224260ustar00rootroot00000000000000#github doesn't have these fwupd source: debian-watch-does-not-check-gpg-signature fwupd-1.3.9/contrib/debian/source/options000066400000000000000000000000351362775233600204650ustar00rootroot00000000000000extend-diff-ignore=".vscode" fwupd-1.3.9/contrib/debian/tests/000077500000000000000000000000001362775233600167135ustar00rootroot00000000000000fwupd-1.3.9/contrib/debian/tests/ci000066400000000000000000000003061362775233600172300ustar00rootroot00000000000000#!/bin/sh set -e sed "s,^BlacklistPlugins=.*,BlacklistPlugins=," -i /etc/fwupd/daemon.conf sed "s,^VerboseDomains=.*,VerboseDomains=*," -i /etc/fwupd/daemon.conf gnome-desktop-testing-runner fwupd fwupd-1.3.9/contrib/debian/tests/control000066400000000000000000000000431362775233600203130ustar00rootroot00000000000000Tests: ci Restrictions: needs-root fwupd-1.3.9/contrib/debian/watch000066400000000000000000000003531362775233600166030ustar00rootroot00000000000000# 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/fwupd/fwupd/tags .*/v?(\d\S*)\.tar\.gz fwupd-1.3.9/contrib/firmware_packager/000077500000000000000000000000001362775233600200005ustar00rootroot00000000000000fwupd-1.3.9/contrib/firmware_packager/README.md000066400000000000000000000113661362775233600212660ustar00rootroot00000000000000# 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.3.9/contrib/firmware_packager/__init__.py000066400000000000000000000000001362775233600220770ustar00rootroot00000000000000fwupd-1.3.9/contrib/firmware_packager/add_capsule_header.py000077500000000000000000000046611362775233600241400ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2019 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import sys import uuid import argparse import ctypes CAPSULE_FLAGS_PERSIST_ACROSS_RESET = 0x00010000 CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE = 0x00020000 CAPSULE_FLAGS_INITIATE_RESET = 0x00040000 def add_header(infile, outfile, gd, fl=None): # parse GUID from command line try: guid = uuid.UUID(gd) except ValueError as e: print(e) return 1 import struct try: with open(infile, '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 | CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE if fl: flags = int(fl, 16) # build update capsule header hdrsz = 4096 imgsz = hdrsz + len(bin_data) hdr = ctypes.create_string_buffer(hdrsz) struct.pack_into('<16sIII', hdr, 0, guid.bytes_le, hdrsz, flags, imgsz) with open(outfile, 'wb') as f: f.write(hdr) f.write(bin_data) print('Wrote capsule %s' % outfile) print('GUID: %s' % guid) print('HdrSz: 0x%04x' % hdrsz) print('Flags: 0x%04x' % flags) print('PayloadSz: 0x%04x' % imgsz) return 0 if __name__ == '__main__': 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(add_header(args.bin, args.cap, args.guid, args.flags)) fwupd-1.3.9/contrib/firmware_packager/firmware_packager.py000077500000000000000000000105661362775233600240360ustar00rootroot00000000000000#!/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) if __name__ == '__main__': 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.3.9/contrib/firmware_packager/install_dell_bios_exe.py000077500000000000000000000063371362775233600247110ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2019 Mario Limonciello # # SPDX-License-Identifier: LGPL-2.1+ import dbus import os.path import sys import tempfile import gi gi.require_version('Fwupd', '2.0') from gi.repository import Fwupd #pylint: disable=wrong-import-position from simple_client import install, check_exists from add_capsule_header import add_header from firmware_packager import make_firmware_metainfo, create_firmware_cab class Variables: def __init__(self, device_guid, version): self.device_guid = device_guid self.developer_name = "Dell Inc" self.firmware_name = "New firmware" self.firmware_summary = "Unknown" self.firmware_description = "Unknown" self.firmware_homepage = "https://support.dell.com" self.contact_info = "Unknown" self.release_version = version self.release_description = "Unknown" def parse_args(): """Parse arguments for this client""" import argparse parser = argparse.ArgumentParser(description="Interact with fwupd daemon") parser.add_argument('exe', nargs='?', help='exe file') parser.add_argument('deviceid', nargs='?', help='DeviceID to operate on(optional)') args = parser.parse_args() return args def generate_cab(infile, directory, guid, version): output = os.path.join(directory, "firmware.bin") ret = add_header(infile, output, guid) if ret: sys.exit(ret) variables = Variables(guid, version) make_firmware_metainfo(variables, directory) create_firmware_cab(variables, directory) cab = os.path.join(directory, "firmware.cab") print("Generated CAB file %s" % cab) return cab def find_uefi_device(client, deviceid): devices = client.get_devices() for item in devices: #match the device we were given if deviceid: if item.get_id() != deviceid: continue # internal if not item.has_flag(1 << 0): continue # needs reboot if not item.has_flag(1 << 8): continue # return the first hit for UEFI plugin if item.get_plugin() == 'uefi': print("Installing to %s" % item.get_name()) return item.get_guid_default(),item.get_id(),item.get_version() print("Couldn't find any UEFI devices") sys.exit(1) def prompt_reboot(): print("An update requires a reboot to complete") while True: res = input("Restart now? (Y/N) ") if res.lower() == 'n': print("Reboot your machine manually to finish the update.") break if res.lower() != 'y': continue #reboot using logind obj = dbus.SystemBus().get_object('org.freedesktop.login1', '/org/freedesktop/login1') obj.Reboot(True, dbus_interface='org.freedesktop.login1.Manager') if __name__ == '__main__': ARGS = parse_args() CLIENT = Fwupd.Client() CLIENT.connect() check_exists(ARGS.exe) directory = tempfile.mkdtemp() guid, deviceid, version=find_uefi_device(CLIENT, ARGS.deviceid) cab = generate_cab(ARGS.exe, directory, guid, version) install(CLIENT, cab, deviceid, True, True) prompt_reboot()fwupd-1.3.9/contrib/firmware_packager/meson.build000066400000000000000000000005661362775233600221510ustar00rootroot00000000000000if get_option('firmware-packager') install_data('firmware_packager.py', install_dir : 'share/fwupd') install_data('simple_client.py', install_dir : 'share/fwupd') install_data('add_capsule_header.py', install_dir : 'share/fwupd') install_data('install_dell_bios_exe.py', install_dir : 'share/fwupd') endif fwupd-1.3.9/contrib/firmware_packager/simple_client.py000077500000000000000000000110511362775233600232020ustar00rootroot00000000000000#!/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) self.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) print("\n") def check_exists(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_exists(ARGS.cab) get_details(CLIENT, ARGS.cab) elif ARGS.command == "install": check_exists(ARGS.cab) install(CLIENT, ARGS.cab, ARGS.deviceid, ARGS.allow_older, ARGS.allow_reinstall) fwupd-1.3.9/contrib/fix_translations.py000077500000000000000000000023351362775233600202760ustar00rootroot00000000000000#!/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.3.9/contrib/flatpak/000077500000000000000000000000001362775233600157515ustar00rootroot00000000000000fwupd-1.3.9/contrib/fwupd.spec.in000066400000000000000000000273601362775233600167450ustar00rootroot00000000000000%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# %define tarball_version #TARBALL_VERSION# %global enable_ci 0 %global enable_tests 1 %global enable_dummy 1 %global enable_flashrom 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: 999.#BUILD#%{?alphatag}%{?dist} License: LGPLv2+ URL: https://github.com/fwupd/fwupd Source0: http://people.freedesktop.org/~hughsient/releases/%{name}-%{tarball_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: tpm2-tss-devel >= 2.2.3 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 %if 0%{?rhel} > 7 || 0%{?fedora} > 28 Recommends: python3 %endif Obsoletes: fwupd-sign < 0.1.6 Obsoletes: libebitdo < 0.7.5-3 Obsoletes: libdfu < 1.0.0 Obsoletes: fwupd-labels < 1.1.0-1 %if 0%{?rhel} > 7 Obsoletes: fwupdate < 11-4 Obsoletes: fwupdate-efi < 11-4 Provides: fwupdate Provides: fwupdate-efi %endif %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 -n %{name}-%{tarball_version} %build %meson \ %if 0%{?enable_ci} --werror \ %endif -Dgtkdoc=true \ %if 0%{?enable_tests} -Dtests=true \ %else -Dtests=false \ %endif %if 0%{?enable_dummy} -Dplugin_dummy=true \ %else -Dplugin_dummy=false \ %endif %if 0%{?enable_flashrom} -Dplugin_flashrom=true \ %else -Dplugin_flashrom=false \ %endif -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 \ -Dplugin_tpm=true \ %else -Dplugin_uefi=false \ -Dplugin_nvme=false \ -Dplugin_tpm=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 %if 0%{?enable_flashrom} # delete most files from the subproject rm ${RPM_BUILD_ROOT}%{_includedir}/libflashrom.h rm ${RPM_BUILD_ROOT}%{_libdir}/libflashrom.so rm ${RPM_BUILD_ROOT}%{_libdir}/pkgconfig/flashrom.pc rm ${RPM_BUILD_ROOT}%{_sbindir}/flashrom %endif %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 %config(noreplace)%{_sysconfdir}/fwupd/upower.conf %if 0%{?have_uefi} %config(noreplace)%{_sysconfdir}/fwupd/uefi.conf %endif %if 0%{?have_redfish} %config(noreplace)%{_sysconfdir}/fwupd/redfish.conf %endif %config(noreplace)%{_sysconfdir}/fwupd/thunderbolt.conf %dir %{_libexecdir}/fwupd %{_libexecdir}/fwupd/fwupd %{_libexecdir}/fwupd/fwupdoffline %if 0%{?have_uefi} %{_libexecdir}/fwupd/efi/*.efi %{_libexecdir}/fwupd/efi/*.efi.signed %{_bindir}/fwupdate %{_bindir}/fwupdtpmevlog %endif %{_bindir}/dfu-tool %{_bindir}/fwupdmgr %{_bindir}/fwupdtool %{_bindir}/fwupdagent %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 %{_datadir}/dbus-1/system.d/org.freedesktop.fwupd.conf %{_datadir}/bash-completion/completions/fwupdmgr %{_datadir}/bash-completion/completions/fwupdtool %{_datadir}/bash-completion/completions/fwupdagent %{_datadir}/fish/vendor_completions.d/fwupdmgr.fish %{_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/fwupdtool.1.gz %{_datadir}/man/man1/fwupdagent.1.gz %{_datadir}/man/man1/dfu-tool.1.gz %{_datadir}/man/man1/fwupdmgr.1.gz %if 0%{?have_uefi} %{_datadir}/man/man1/fwupdate.1.gz %{_datadir}/man/man1/fwupdtpmevlog.1.gz %endif %{_datadir}/metainfo/org.freedesktop.fwupd.metainfo.xml %{_datadir}/icons/hicolor/scalable/apps/org.freedesktop.fwupd.svg %{_datadir}/fwupd/firmware_packager.py %{_datadir}/fwupd/simple_client.py %{_datadir}/fwupd/add_capsule_header.py %{_datadir}/fwupd/install_dell_bios_exe.py %{_unitdir}/fwupd-offline-update.service %{_unitdir}/fwupd.service %{_unitdir}/fwupd-refresh.service %{_unitdir}/fwupd-refresh.timer %{_presetdir}/fwupd-refresh.preset %{_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 %{_libdir}/girepository-1.0/FwupdPlugin-1.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_coreboot.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_emmc.so %{_libdir}/fwupd-plugins-3/libfu_plugin_fastboot.so %if 0%{?enable_flashrom} %{_libdir}/fwupd-plugins-3/libfu_plugin_flashrom.so %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_fresco_pd.so %{_libdir}/fwupd-plugins-3/libfu_plugin_jabra.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 %{_libdir}/fwupd-plugins-3/libfu_plugin_optionrom.so %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_solokey.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_synaptics_mst.so %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_synaptics_cxaudio.so %{_libdir}/fwupd-plugins-3/libfu_plugin_synaptics_prometheus.so %{_libdir}/fwupd-plugins-3/libfu_plugin_synaptics_rmi.so %if 0%{?enable_dummy} %{_libdir}/fwupd-plugins-3/libfu_plugin_test.so %{_libdir}/fwupd-plugins-3/libfu_plugin_invalid.so %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_thelio_io.so %{_libdir}/fwupd-plugins-3/libfu_plugin_thunderbolt.so %{_libdir}/fwupd-plugins-3/libfu_plugin_thunderbolt_power.so %if 0%{?have_uefi} %{_libdir}/fwupd-plugins-3/libfu_plugin_tpm.so %{_libdir}/fwupd-plugins-3/libfu_plugin_tpm_eventlog.so %{_libdir}/fwupd-plugins-3/libfu_plugin_uefi.so %{_libdir}/fwupd-plugins-3/libfu_plugin_uefi_recovery.so %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_logind.so %{_libdir}/fwupd-plugins-3/libfu_plugin_logitech_hidpp.so %{_libdir}/fwupd-plugins-3/libfu_plugin_upower.so %{_libdir}/fwupd-plugins-3/libfu_plugin_vli.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 %if 0%{?enable_flashrom} # eww, but just until the Fedora package ships these... %{_libdir}/libflashrom.so.1* %endif %files devel %{_datadir}/gir-1.0/Fwupd-2.0.gir %{_datadir}/gir-1.0/FwupdPlugin-1.0.gir %{_datadir}/gtk-doc/html/fwupd %{_datadir}/vala/vapi %{_includedir}/fwupd-1 %{_libdir}/libfwupd*.so %{_libdir}/pkgconfig/fwupd.pc %{_libdir}/pkgconfig/fwupdplugin.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.3.9/contrib/generate-version-script.py000077500000000000000000000076321362775233600214730ustar00rootroot00000000000000#!/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] if identifier not in release: 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) # 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 parse_version(version_tmp) < parse_version(version_lowest): version_lowest = version_tmp # add the constructor for node in cls.findall(XMLNS + 'constructor'): version_tmp = self._add_node(node) if version_tmp: if not version_lowest or parse_version(version_tmp) < parse_version(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.3.9/contrib/get-version.py000077500000000000000000000023131362775233600171450ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2019 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # import xml.etree.ElementTree as etree import os import subprocess def sanitize_for_ci(version): if not 'CI' in os.environ: return version OS=os.getenv('OS') if not OS: return version if "fedora" in OS: return version.replace('-','.') return version def get_version_git(): try: version = subprocess.check_output(['git', 'describe'], stderr=subprocess.DEVNULL) return version.strip().decode('utf-8') except (subprocess.CalledProcessError, PermissionError, FileNotFoundError): return '' def get_version(): tree = etree.parse(os.path.join("data", "org.freedesktop.fwupd.metainfo.xml")) version = '' for child in tree.findall('releases'): for release in child: if not "version" in release.attrib: continue if release.attrib['version'] > version: version = release.attrib['version'] return version if __name__ == '__main__': version = get_version_git() if version: version = sanitize_for_ci(version) else: version = get_version() print(version) fwupd-1.3.9/contrib/meson.build000066400000000000000000000004361362775233600164740ustar00rootroot00000000000000subdir('firmware_packager') if host_machine.system() == 'windows' con2 = configuration_data() con2.set('FWUPD_VERSION', fwupd_version) # replace @FWUPD_VERSION@ configure_file( input : 'setup-win32.nsi', output : 'setup-win32.nsi', configuration : con2, ) endif fwupd-1.3.9/contrib/mingw64.cross000066400000000000000000000005261362775233600167000ustar00rootroot00000000000000[binaries] c = '/usr/bin/x86_64-w64-mingw32-gcc' cpp = '/usr/bin/x86_64-w64-mingw32-g++' ar = '/usr/bin/x86_64-w64-mingw32-ar' strip = '/usr/bin/x86_64-w64-mingw32-strip' pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' exe_wrapper = '/usr/bin/wine' [host_machine] system = 'windows' cpu_family = 'x86_64' cpu = 'i686' endian = 'little' fwupd-1.3.9/contrib/nvme-parse.py000077500000000000000000000106421362775233600167640ustar00rootroot00000000000000#!/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.3.9/contrib/setup-win32.nsi000066400000000000000000000070441362775233600171470ustar00rootroot00000000000000#!Nsis Installer Command Script # # To build an installer from the script you would normally do: # # dnf install mingw32-nsis # makensis setup-win32.nsi Name "" OutFile "setup/fwupd-@FWUPD_VERSION@-setup-x86_64.exe" InstallDir "$ProgramFiles\fwupd" InstallDirRegKey HKLM SOFTWARE\fwupd "Install_Dir" ShowInstDetails hide ShowUninstDetails hide XPStyle on Page directory Page instfiles ComponentText "Select which optional components you want to install." DirText "Please select the installation folder." Section "fwupd" SectionIn RO SetOutPath "$INSTDIR\bin" # deps File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/iconv.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libarchive-13.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libbrotlicommon.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libbrotlidec.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libbz2-1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libffi-6.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgcc_s_seh-1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgio-2.0-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libglib-2.0-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgmodule-2.0-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgmp-10.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgnutls-30.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgnutls-30.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgobject-2.0-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgusb-2.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libhogweed-4.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libidn2-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libintl-8.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libjson-glib-1.0-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/liblzma-5.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libnettle-6.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libp11-kit-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libpcre-1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libpsl-5.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libsoup-2.4-1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libsqlite3-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libtasn1-6.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libunistring-2.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libusb-1.0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libwinpthread-1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libxml2-2.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/zlib1.dll" # fwupd File "dfu-tool.exe" File "fwupdtool.exe" File "libfwupd-2.dll" File "libfwupdplugin-1.dll" File "libgcab-1.0-0.dll" File "libxmlb-1.dll" SetOutPath "$INSTDIR\fwupd-plugins-3" File /r "fwupd-plugins-3/libfu_plugin_*.dll" SetOutPath "$INSTDIR\etc\fwupd" File "etc/fwupd/daemon.conf" SetOutPath "$INSTDIR\etc\pki\fwupd" File "etc/pki/fwupd/LVFS-CA.pem" SetOutPath "$INSTDIR\share\fwupd\quirks.d" File /r "share/fwupd/quirks.d/*.quirk" ReadEnvStr $0 COMSPEC SetOutPath "$INSTDIR" SectionEnd Section "Uninstall" RMDir /rebootok /r "$SMPROGRAMS\fwupd" RMDir /rebootok /r "$INSTDIR\bin" RMDir /rebootok /r "$INSTDIR\etc" RMDir /rebootok /r "$INSTDIR\lib" RMDir /rebootok /r "$INSTDIR\share" RMDir /rebootok "$INSTDIR" SectionEnd Section -post WriteUninstaller "$INSTDIR\Uninstall fwupd.exe" SectionEnd fwupd-1.3.9/contrib/snap/000077500000000000000000000000001362775233600152705ustar00rootroot00000000000000fwupd-1.3.9/contrib/snap/README.md000066400000000000000000000015461362775233600165550ustar00rootroot00000000000000# 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.3.9/contrib/snap/activate-shutdown/000077500000000000000000000000001362775233600207415ustar00rootroot00000000000000fwupd-1.3.9/contrib/snap/activate-shutdown/Makefile000066400000000000000000000005221362775233600224000ustar00rootroot00000000000000build: 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.3.9/contrib/snap/activate-shutdown/fwupd-activate.service000066400000000000000000000003451362775233600252500ustar00rootroot00000000000000[Unit] Description=Activate fwupd updates RequiresMountsFor=/snap/fwupd/current [Service] Type=oneshot RemainAfterExit=true ExecStop=/snap/bin/fwupd.fwupdtool activate SuccessExitStatus=0 2 [Install] WantedBy=multi-user.target fwupd-1.3.9/contrib/snap/dfu-tool.wrapper000077500000000000000000000000731362775233600204260ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/dfu-tool $@ fwupd-1.3.9/contrib/snap/fix-bash-completion/000077500000000000000000000000001362775233600211405ustar00rootroot00000000000000fwupd-1.3.9/contrib/snap/fix-bash-completion/Makefile000066400000000000000000000006671362775233600226110ustar00rootroot00000000000000build: 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.3.9/contrib/snap/fwup-efi-signed/000077500000000000000000000000001362775233600202615ustar00rootroot00000000000000fwupd-1.3.9/contrib/snap/fwup-efi-signed/Makefile000066400000000000000000000010711362775233600217200ustar00rootroot00000000000000DEB_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.3.9/contrib/snap/fwup-efi-signed/download-fwupd000077500000000000000000000020301362775233600231340ustar00rootroot00000000000000#! /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.3.9/contrib/snap/fwupd-command000077500000000000000000000023621362775233600177620ustar00rootroot00000000000000#!/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.3.9/contrib/snap/fwupd.wrapper000077500000000000000000000001021362775233600200130ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/libexec/fwupd/fwupd $@ fwupd-1.3.9/contrib/snap/fwupdmgr.wrapper000077500000000000000000000000731362775233600205300ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/fwupdmgr $@ fwupd-1.3.9/contrib/snap/fwupdtool.wrapper000077500000000000000000000000741362775233600207210ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/fwupdtool $@ fwupd-1.3.9/contrib/snap/libefivar-fixpkgconfig/000077500000000000000000000000001362775233600217075ustar00rootroot00000000000000fwupd-1.3.9/contrib/snap/libefivar-fixpkgconfig/Makefile000066400000000000000000000011601362775233600233450ustar00rootroot00000000000000build: 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.3.9/contrib/snap/snapcraft-master.yaml000066400000000000000000000206131362775233600214300ustar00rootroot00000000000000name: 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.3.9/contrib/snap/update-mime/000077500000000000000000000000001362775233600174775ustar00rootroot00000000000000fwupd-1.3.9/contrib/snap/update-mime/Makefile000066400000000000000000000002021362775233600211310ustar00rootroot00000000000000build: true install: update-mime-database ../install/usr/share/mime glib-compile-schemas ../install/usr/share/glib-2.0/schemas fwupd-1.3.9/contrib/standalone-installer/000077500000000000000000000000001362775233600204525ustar00rootroot00000000000000fwupd-1.3.9/contrib/standalone-installer/README.md000066400000000000000000000004301362775233600217260ustar00rootroot00000000000000# 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.3.9/contrib/standalone-installer/assets/000077500000000000000000000000001362775233600217545ustar00rootroot00000000000000fwupd-1.3.9/contrib/standalone-installer/assets/header.py000066400000000000000000000241311362775233600235570ustar00rootroot00000000000000#!/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.3.9/contrib/standalone-installer/make.py000077500000000000000000000112301362775233600217410ustar00rootroot00000000000000#!/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.3.9/contrib/vscode/000077500000000000000000000000001362775233600156125ustar00rootroot00000000000000fwupd-1.3.9/contrib/vscode/README.md000066400000000000000000000030071362775233600170710ustar00rootroot00000000000000# 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.3.9/contrib/vscode/build.sh000077500000000000000000000015731362775233600172560ustar00rootroot00000000000000#!/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.3.9/contrib/vscode/launch.json000066400000000000000000000060021362775233600177550ustar00rootroot00000000000000{ // 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.3.9/contrib/vscode/launcher.sh000077500000000000000000000003351362775233600177530ustar00rootroot00000000000000#!/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.3.9/data/000077500000000000000000000000001362775233600136005ustar00rootroot00000000000000fwupd-1.3.9/data/90-fwupd-devices.rules000066400000000000000000000027001362775233600176460ustar00rootroot00000000000000######################################################################## # 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.3.9/data/bash-completion/000077500000000000000000000000001362775233600166645ustar00rootroot00000000000000fwupd-1.3.9/data/bash-completion/fwupdagent000066400000000000000000000011261362775233600207530ustar00rootroot00000000000000_fwupdagent_cmd_list=( 'get-devices' 'get-updates' 'get-upgrades' ) _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.3.9/data/bash-completion/fwupdmgr.in000066400000000000000000000066471362775233600210640ustar00rootroot00000000000000_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' 'get-upgrades' 'install' 'modify-config' 'modify-remote' 'reinstall' 'refresh' 'report-history' 'set-approved-firmware' 'unlock' 'update' 'upgrade' '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' '--no-safety-check' '--show-all-devices' '--sign' '--filter' '--disable-ssl-strict' ) _show_filters() { local flags flags="$(command @libexecdir@/fwupdtool get-device-flags 2>/dev/null)" COMPREPLY+=( $(compgen -W "${flags}" -- "$cur") ) } _show_modifiers() { COMPREPLY+=( $(compgen -W '${_fwupdmgr_opts[@]}' -- "$cur") ) } _show_device_ids() { local description OLDIFS=$IFS IFS=$'\n' description="$(command fwupdagent get-devices 2>/dev/null | sed -e 's,"Name" :,,; s,",,g; s,\,,,g; s,[[:space:]]\+,,' | command awk '!/DeviceId/ { line = $0 }; /DeviceId/ { print $3 " { "line" }"}')" COMPREPLY+=( $(compgen -W "${description}" -- "$cur") ) IFS=$OLDIFS } _show_remotes() { local remotes remotes="$(command fwupdmgr get-remotes | command awk '/Remote ID/ { print $4 }')" 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 $prev in --filter) _show_filters return 0 ;; esac 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.3.9/data/bash-completion/fwupdtool.in000066400000000000000000000050411362775233600212370ustar00rootroot00000000000000_fwupdtool_cmd_list=( 'activate' 'build-firmware' 'firmware-parse' 'get-updates' 'get-upgrades' 'get-details' 'get-firmware-types' 'get-device-flags' 'get-devices' 'get-history' 'get-plugins' 'get-remotes' 'get-topology' 'hwids' 'update' 'upgrade' 'install' 'install-blob' 'monitor' 'self-sign' 'smbios-dump' 'attach' 'detach' 'firmware-read' 'refresh' 'verify-update' 'watch' ) _fwupdtool_opts=( '--verbose' '--enable-json-state' '--allow-reinstall' '--allow-older' '--force' '--show-all-devices' '--plugin-whitelist' '--prepare' '--cleanup' '--filter' '--disable-ssl-strict' '--no-safety-check' ) _show_filters() { local flags flags="$(command @libexecdir@/fwupdtool get-device-flags 2>/dev/null)" COMPREPLY+=( $(compgen -W "${flags}" -- "$cur") ) } _show_firmware_types() { local firmware_types firmware_types="$(command @libexecdir@/fwupdtool get-firmware-types 2>/dev/null)" COMPREPLY+=( $(compgen -W "${firmware_types}" -- "$cur") ) } _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 ;; --filter) _show_filters return 0 ;; esac case $command in get-details|install|install-blob|firmware-read) #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 ;; firmware-parse) #find files if [[ "$prev" = "$command" ]]; then _filedir #firmware_type elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; then _show_firmware_types 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.3.9/data/bash-completion/meson.build000066400000000000000000000013371362775233600210320ustar00rootroot00000000000000if bashcomp.found() tgt = bashcomp.get_pkgconfig_variable('completionsdir', define_variable: [ 'prefix', prefix ], ) if get_option('agent') install_data(['fwupdagent'], install_dir : tgt, ) endif # get_option('agent') # 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) if build_daemon configure_file( input : 'fwupdmgr.in', output : 'fwupdmgr', configuration : con2, install: true, install_dir: tgt) endif # build_daemon endif # bashcomp.found() fwupd-1.3.9/data/builder/000077500000000000000000000000001362775233600152265ustar00rootroot00000000000000fwupd-1.3.9/data/builder/README.md000066400000000000000000000045761362775233600165210ustar00rootroot00000000000000Building 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.3.9/data/builder/meson.build000066400000000000000000000001411362775233600173640ustar00rootroot00000000000000install_data('README.md', install_dir : join_paths(localstatedir, 'lib', 'fwupd', 'builder') ) fwupd-1.3.9/data/daemon.conf000066400000000000000000000014201362775233600157070ustar00rootroot00000000000000[fwupd] # Allow blacklisting specific devices by their GUID # Uses semicolons as delimiter BlacklistDevices= # Allow blacklisting specific plugins # Uses semicolons as delimiter BlacklistPlugins=test;invalid # 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= # Update the message of the day (MOTD) on device and metadata changes UpdateMotd=true fwupd-1.3.9/data/fish-completion/000077500000000000000000000000001362775233600167005ustar00rootroot00000000000000fwupd-1.3.9/data/fish-completion/fwupdmgr.fish000066400000000000000000000126051362775233600214120ustar00rootroot00000000000000function __fish_fwupdmgr_devices --description 'Get device IDs used by fwupdmgr' set -l ids (fwupdmgr get-devices | string replace -f -r '.*Device ID:\s*(.*)' '$1') set -l names (fwupdmgr get-devices | string replace -f -r '.*─(.*):$' '$1') for i in (seq (count $ids)) echo -e "$ids[$i]\t$names[$i]" end end function __fish_fwupdmgr_remotes --description 'Get remote IDs used by fwupdmgr' fwupdmgr get-remotes | string replace -f -r '.*Remote ID:\s*(.*)' '$1' end # complete options complete -c fwupdmgr -s h -l help -d 'Show help options' complete -c fwupdmgr -s v -l verbose -d 'Show extra debugging information' complete -c fwupdmgr -l version -d 'Show client and daemon versions' complete -c fwupdmgr -l offline -d 'Schedule installation for next reboot when possible' complete -c fwupdmgr -l allow-reinstall -d 'Allow reinstalling existing firmware versions' complete -c fwupdmgr -l allow-older -d 'Allow downgrading firmware versions' complete -c fwupdmgr -l force -d 'Override warnings and force the action' complete -c fwupdmgr -s y -l assume-yes -d 'Answer yes to all questions' complete -c fwupdmgr -l sign -d 'Sign the uploaded data with the client certificate' complete -c fwupdmgr -l no-unreported-check -d 'Do not check for unreported history' complete -c fwupdmgr -l no-metadata-check -d 'Do not check for old metadata' complete -c fwupdmgr -l no-reboot-check -d 'Do not check for reboot after update' complete -c fwupdmgr -l no-safety-check -d 'Do not perform device safety checks' complete -c fwupdmgr -l no-history -d 'Do not write to the history database' complete -c fwupdmgr -l show-all-devices -d 'Show devices that are not updatable' complete -c fwupdmgr -l disable-ssl-strict -d 'Ignore SSL strict checks when downloading files' complete -c fwupdmgr -l filter -d 'Filter with a set of device flags' # complete subcommands complete -c fwupdmgr -n '__fish_use_subcommand' -x -a activate -d 'Activate devices' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a clear-history -d 'Erase all firmware update history' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a clear-offline -d 'Clears any updates scheduled to be updated offline' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a clear-results -d 'Clears the results from the last update' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a disable-remote -d 'Disables a given remote' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a downgrade -d 'Downgrades the firmware on a device' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a enable-remote -d 'Enables a given remote' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-approved-firmware -d 'Gets the list of approved firmware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-details -d 'Gets details about a firmware file' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-devices -d 'Get all devices that support firmware updates' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-history -d 'Show history of firmware updates' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-releases -d 'Gets the releases for a device' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-remotes -d 'Gets the configured remotes' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-results -d 'Gets the results from the last update' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-updates -d 'Gets the list of updates for connected hardware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a install -d 'Install a firmware file on this hardware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a modify-config -d 'Modifies a daemon configuration value.' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a modify-remote -d 'Modifies a given remote' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a refresh -d 'Refresh metadata from remote server' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a reinstall -d 'Reinstall current firmware on the device.' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a report-history -d 'Share firmware history with the developers' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a set-approved-firmware -d 'Sets the list of approved firmware.' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a unlock -d 'Unlocks the device for firmware access' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a update -d 'Updates all firmware to latest versions available' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a verify -d 'Checks cryptographic hash matches firmware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a verify-update -d 'Update the stored cryptographic hash with current ROM contents' # commands exclusively consuming device IDs set -l deviceid_consumers activate clear-results downgrade get-releases get-results reinstall unlock verify verify-update # complete device IDs complete -c fwupdmgr -n "__fish_seen_subcommand_from $deviceid_consumers" -x -a "(__fish_fwupdmgr_devices)" # complete files and device IDs complete -c fwupdmgr -n "__fish_seen_subcommand_from install" -r -a "(__fish_fwupdmgr_devices)" # commands exclusively consuming remote IDs set -l remoteid_consumers disable-remote enable-remote modify-remote # complete remote IDs complete -c fwupdmgr -n "__fish_seen_subcommand_from $remoteid_consumers" -x -a "(__fish_fwupdmgr_remotes)" # complete files and remote IDs complete -c fwupdmgr -n "__fish_seen_subcommand_from refresh" -r -a "(__fish_fwupdmgr_remotes)" fwupd-1.3.9/data/fish-completion/meson.build000066400000000000000000000001471362775233600210440ustar00rootroot00000000000000install_data(['fwupdmgr.fish'], install_dir : join_paths(datadir, 'fish', 'vendor_completions.d'), ) fwupd-1.3.9/data/fwupd-offline-update.service.in000066400000000000000000000006411362775233600216150ustar00rootroot00000000000000[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.3.9/data/fwupd.service.in000066400000000000000000000007661362775233600167250ustar00rootroot00000000000000[Unit] Description=Firmware update daemon Documentation=https://fwupd.org/ After=dbus.service Before=display-manager.service [Service] Type=dbus RuntimeDirectory=@motd_dir@ RuntimeDirectoryPreserve=yes 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.3.9/data/fwupd.shutdown.in000077500000000000000000000002751362775233600171360ustar00rootroot00000000000000#!/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.3.9/data/installed-tests/000077500000000000000000000000001362775233600167175ustar00rootroot00000000000000fwupd-1.3.9/data/installed-tests/README.md000066400000000000000000000054751362775233600202110ustar00rootroot00000000000000Installed 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.3.9/data/installed-tests/fakedevice123.bin000066400000000000000000000000211362775233600217160ustar00rootroot00000000000000fakedevice123 -n fwupd-1.3.9/data/installed-tests/fakedevice123.bin.asc000066400000000000000000000007521362775233600224760ustar00rootroot00000000000000-----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.3.9/data/installed-tests/fakedevice123.metainfo.xml000066400000000000000000000017571362775233600235700ustar00rootroot00000000000000 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.3.9/data/installed-tests/fakedevice124.bin000066400000000000000000000000211362775233600217170ustar00rootroot00000000000000fakedevice124 -n fwupd-1.3.9/data/installed-tests/fakedevice124.bin.asc000066400000000000000000000007521362775233600224770ustar00rootroot00000000000000-----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.3.9/data/installed-tests/fakedevice124.metainfo.xml000066400000000000000000000017711362775233600235650ustar00rootroot00000000000000 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.3.9/data/installed-tests/fwupd-tests.xml000066400000000000000000000105421362775233600217300ustar00rootroot00000000000000 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.3.9/data/installed-tests/fwupdmgr.sh000077500000000000000000000043621362775233600211160ustar00rootroot00000000000000#!/bin/bash exec 2>&1 dirname=`dirname $0` device=08d460be0f1f9f128413f816022a6439e0078018 error() { rc=$1 journalctl -u fwupd -b || true exit $rc } # --- echo "Getting the list of remotes..." fwupdmgr get-remotes rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # --- echo "Enabling fwupd-tests remote..." fwupdmgr enable-remote fwupd-tests rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # --- echo "Update the device hash database..." fwupdmgr verify-update $device rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # --- echo "Getting devices (should be one)..." fwupdmgr get-devices --no-unreported-check rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # --- echo "Testing the verification of firmware..." fwupdmgr verify $device rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # --- echo "Getting updates (should be one)..." fwupdmgr --no-unreported-check --no-metadata-check get-updates rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # --- echo "Installing test firmware..." fwupdmgr install ${dirname}/fakedevice124.cab rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # --- echo "Getting updates (should be none)..." fwupdmgr --no-unreported-check --no-metadata-check get-updates rc=$?; if [[ $rc != 2 ]]; then error $rc; fi # --- echo "Testing the verification of firmware (again)..." fwupdmgr verify $device rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # --- echo "Downgrading to older release (requires network access)" fwupdmgr downgrade $device rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # --- echo "Downgrading to older release (should be none)" fwupdmgr downgrade $device rc=$?; if [[ $rc != 2 ]]; then error $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 error $rc; fi # --- echo "Getting updates (should be none)..." fwupdmgr --no-unreported-check --no-metadata-check get-updates rc=$?; if [[ $rc != 2 ]]; then error $rc; fi # --- echo "Refreshing from the LVFS (requires network access)..." fwupdmgr refresh rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # --- echo "Flashing actual devices (requires specific hardware)" ${dirname}/hardware.py rc=$?; if [[ $rc != 0 ]]; then error $rc; fi # success! exit 0 fwupd-1.3.9/data/installed-tests/fwupdmgr.test.in000066400000000000000000000001011362775233600220500ustar00rootroot00000000000000[Test] Type=session Exec=sh -c "@installedtestsdir@/fwupdmgr.sh" fwupd-1.3.9/data/installed-tests/hardware.py000077500000000000000000000160511362775233600210740ustar00rootroot00000000000000#!/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 logitech_hidpp 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 logitech_hidpp 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.3.9/data/installed-tests/meson.build000066400000000000000000000024571362775233600210710ustar00rootroot00000000000000con2 = 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.3.9/data/installed-tests/remote.conf.in000066400000000000000000000004161362775233600214670ustar00rootroot00000000000000[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.3.9/data/meson.build000066400000000000000000000055401362775233600157460ustar00rootroot00000000000000subdir('builder') subdir('pki') subdir('remotes.d') subdir('bash-completion') subdir('fish-completion') if build_daemon subdir('motd') endif if get_option('tests') subdir('tests') endif if build_daemon subdir('installed-tests') endif if build_standalone install_data(['daemon.conf'], install_dir : join_paths(sysconfdir, 'fwupd') ) endif 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') ) if build_daemon install_data(['org.freedesktop.fwupd.conf'], install_dir : join_paths(datadir, 'dbus-1', 'system.d') ) endif if build_daemon and get_option('gudev') install_data(['90-fwupd-devices.rules'], install_dir : join_paths(udevdir, 'rules.d') ) endif if get_option('systemd') and build_daemon 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)) con2.set('motd_dir', motd_dir) # 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')) and build_daemon 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.3.9/data/motd/000077500000000000000000000000001362775233600145435ustar00rootroot00000000000000fwupd-1.3.9/data/motd/85-fwupd.motd.in000077500000000000000000000001121362775233600174110ustar00rootroot00000000000000#!/bin/sh if [ -f @motd_fullpath@ ]; then cat @motd_fullpath@ fi fwupd-1.3.9/data/motd/README.md000066400000000000000000000010221362775233600160150ustar00rootroot00000000000000# Message of the day integration Message on the day integration is used to display the availability of updates when connecting to a remote console. It has two elements: * Automatic firmware metadata refresh * Message of the day display ## Automatic firmware metadata refresh This uses a systemd timer to run on a regular cadence. To enable this, run ``` # systemctl enable fwupd-refresh.timer ``` ## Motd display Motd display is dependent upon the availability of the update-motd snippet consumption service such as pam_motd. fwupd-1.3.9/data/motd/fwupd-refresh.preset000066400000000000000000000000361362775233600205470ustar00rootroot00000000000000disable fwupd-refresh.service fwupd-1.3.9/data/motd/fwupd-refresh.service.in000066400000000000000000000007761362775233600213250ustar00rootroot00000000000000[Unit] Description=Refresh fwupd metadata and update motd Documentation=man:fwupdmgr(1) After=network.target network-online.target systemd-networkd.service NetworkManager.service connman.service [Service] Type=oneshot CacheDirectory=fwupdmgr StandardError=null DynamicUser=yes RestrictAddressFamilies=AF_NETLINK AF_UNIX AF_INET AF_INET6 SystemCallFilter=~@mount ProtectKernelModules=yes ProtectControlGroups=yes RestrictRealtime=yes SuccessExitStatus=2 ExecStart=@bindir@/fwupdmgr refresh --no-metadata-check fwupd-1.3.9/data/motd/fwupd-refresh.timer000066400000000000000000000002371362775233600203700ustar00rootroot00000000000000[Unit] Description=Refresh fwupd metadata regularly [Timer] OnCalendar=*-*-* 6,18:00 RandomizedDelaySec=12h Persistent=true [Install] WantedBy=timers.target fwupd-1.3.9/data/motd/meson.build000066400000000000000000000016231362775233600167070ustar00rootroot00000000000000 if get_option('systemd') install_data(['fwupd-refresh.timer'], install_dir: systemdunitdir) install_data(['fwupd-refresh.preset'], install_dir: systemdsystempresetdir) motd_fullpath = join_paths ('/run', motd_dir, 'fwupd', motd_file) else motd_fullpath = join_paths (localstatedir, motd_dir, motd_file) endif con2 = configuration_data() con2.set('bindir', bindir) con2.set('motd_fullpath', motd_fullpath) if get_option('systemd') configure_file( input : 'fwupd-refresh.service.in', output : 'fwupd-refresh.service', configuration : con2, install: true, install_dir: systemdunitdir, ) endif # This file is only used in Ubuntu, which chooses to use update-motd instead # of sourcing /run/motd.d/* # See https://bugs.launchpad.net/ubuntu/+source/pam/+bug/399071 configure_file( input : '85-fwupd.motd.in', output : motd_file, configuration : con2, install: false, ) fwupd-1.3.9/data/org.freedesktop.fwupd.conf000066400000000000000000000020231362775233600206710ustar00rootroot00000000000000 fwupd-1.3.9/data/org.freedesktop.fwupd.metainfo.xml000066400000000000000000002243431362775233600223600ustar00rootroot00000000000000 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/fwupd/fwupd/issues https://fwupd.org/ https://www.transifex.com/freedesktop/fwupd/ richard_at_hughsie.com fwupd moderate fwupdmgr fwupdtool fwupdtpmevlog fwupdagent

This release adds the following features:

  • Added completion script for fish shell
  • Inihbit all power management actions using logind when updating

This release fixes the following bugs:

  • Always check for PLAIN when doing vercmp() operations
  • Always return AppStream markup for remote agreements
  • Apply UEFI capsule update even with single valid capsule
  • Check the device protocol before de-duping devices
  • Copy the version and format from donor device in get-details
  • Correctly append the release to devices in `fwupdtool get-details`
  • Decrease minimum battery requirement to 10%
  • Discard the reason upgrades aren't available
  • Do not fail loading in /etc/machine-id is not available
  • Fix a critical warning when installing some firmware
  • For the `get-details` command make sure to always show devices
  • Set the MSP430 version format to pair
  • Switch off the ATA verbose logging by default
  • Use unknown for version format by default on get-details

This release adds the following features:

  • Add an extra instance ID to disambiguate USB hubs
  • Add a plugin to update PD controllers by Fresco Logic
  • Replay the TPM event log to get the PCRx values

This release fixes the following bugs:

  • Fix updating Synaptics MST devics with no PCI parent
  • Correctly reset VL100 PD devices
  • Do not rewrite BootOrder in the EFI helper
  • Do not use vercmp when the device version format is plain
  • Fix firmware regression in the EFI capsule helper
  • Ignore Unifying detach failures
  • Make the cxaudio version match that of the existing Windows tools
  • Set up more parent devices for various Lenovo USB hubs
  • Support the new gnuefi file locations
  • Use the correct command to get the VLI device firmware version

This release adds the following features:

  • Add 'get-remotes' and 'refresh' to fwupdtool
  • Add support for standalone VIA PD devices
  • Allow applying all releases to get to a target version
  • Discourage command line metadata refreshes more than once per day
  • Generate a win32 setup binary
  • Get the list of updates in JSON format from fwupdagent
  • Move MOTD population into the daemon
  • Shut down automatically when there is system memory pressure

This release fixes the following bugs:

  • Correctly delete UEFI variables
  • Correctly import PKCS-7 remote metadata
  • Disable the battery percentage checks if UPower is unavailable
  • Do not always get the vendor ID for udev devices using the parent
  • Fix display of UTF-8 characters on Windows
  • Show the device parent if there is an interesting child
  • Use a different protocol ID for VIA i2c devices
  • Use the correct timeout for Logitech IO channel writes

This release adds the following features:

  • Add a new plugin that can parse the TPM event log
  • Add a new plugin that exposes the TPM device firmware version
  • Allow building on Windows with MinGW
  • Enforce that device protocol matches the metadata value
  • Export the device protocol and raw device version to the client --verbose output

This release fixes the following bugs:

  • Add a dell-bios version format to match what is shown on the vendor website
  • Allow incremental version major and minor number for Synaptics Prometheus devices
  • Clarify error messages when no upgrades are available
  • Correct the default prompt for reboot/shutdown
  • Do not expose bootloader version errors to users
  • Fix the quirk for the legacy VIA 813 usbhub chip
  • Hardcode the vendor ID for Dell dock hardware
  • Only check the vendor ID if the device has one set
  • Return exit status success if there is no firmware to be updated
  • Set the correct vendor eMMC ID prefix
  • Use the baseboard vendor as the superio vendor ID
  • Use the BIOS vendor as the coreboot and flashrom vendor ID

This release adds the following features:

  • Convert libfwupdprivate to a shared library libfwupdplugin
  • Create a REV_00 instance ID as this may be what the vendor needs to target

This release fixes the following bugs:

  • Improve coreboot version detection
  • Invert default behavior to be safer for reboot and shutdown prompts
  • Reload the Synaptics prometheus device version after update
  • Use the correct unlocker when using GRWLock
  • Whitelist VIA USB hub PD and I²C devices

This release adds the following features:

  • Add a new property Interactive to the daemon
  • Add a new script for installing a Dell BIOS from an EXE file
  • Add support for Foxconn T77W968 and DW5821e eSIM
  • Add support for matching firmware requirements on device parents
  • Add support for writing VIA PD and I2C devices
  • Add versions formats for the Microsoft Surface devices

This release fixes the following bugs:

  • Allows confined snaps to activate fwupd via D-Bus
  • Correct Wacom panel HWID support
  • Don't assume all udev devices have device_file
  • Dynamically determine release version
  • Fall back to `ID_LIKE` when the path for `ID` doesn't exist
  • Fix a fastboot regression when updating modem firmware
  • Fix regression when coldplugging superio devices
  • Fix the linking of the UEFI update binary
  • Fix the vendor id of hidraw devices
  • Make loading USB device strings non-fatal
  • Reject invalid Synaptics MST chip IDs
  • Skip cleanup after device is done updating if required

This release adds the following features:

  • Add a plugin for systems running coreboot
  • Add a plugin to update eMMC devices
  • Add a plugin to update Synaptics RMI4 devices
  • Add a plugin to update VIA USB hub hardware
  • Add some success messages when CLI tasks have completed
  • Add support for automatically uploading reports
  • Add support for `fwupdmgr reinstall`
  • Allow fwupdtool to dump details of common firmware formats
  • Use XMLb to query quirks to reduce the RSS when running

This release fixes the following bugs:

  • Add several quirks for Realtek webcams
  • Add support for the 8bitdo SN30Pro+
  • Add support for the ThinkPad USB-C Dock Gen2 audio device
  • Always report the update-error correctly for multiple updates
  • Create a unique GUID for the Thunderbolt controller path
  • Fix a regression for Wacom EMR devices
  • Move the Jabra-specific detach out into its own plugin
  • Recognize new 'generation' Thunderbolt sysfs attribute for USB4
  • Reduce more boilerplate in plugins, modernizing where required
  • Remove unused DFU functionality
  • Rework ESP path detection and lifecycle to auto-unmount when required
  • Show a useful error for Logitech devices that cannot self-reset
  • Use correct method for stopping systemd units
  • Use device safety flags to show prompts before installing updates
  • Use `genpeimg` to mark ASLR and DP/NX on EFI binary
  • Use will-disappear flag for 8bitdo SF30/SN30 controllers

This release adds the following features:

  • Add a plugin to detach the Thelio IO board
  • Add a plugin to update Conexant audio devices
  • Support issues in AppStream metadata

This release fixes the following bugs:

  • Align the key values to the text width not the number of bytes
  • Display more helpful historical device information
  • Do not ask the user to upload a report if ReportURI is not set
  • Do not crash when starting tpm2-abrmd
  • Ensure HID++ v2.0 peripheral devices get added
  • Fall back to /var/lib/dbus/machine-id when required
  • Include all GUIDs when uploading a report
  • Move D-Bus conf file to datadir/dbus-1/system.d
  • Update device_modified in sql database during updates

This release adds the following features:

  • Add support for the Minnowboard Turbot
  • Add support for the SoloKey Secure
  • Add support for thunderbolt kernel safety checks
  • Add support to integrate into the motd
  • Allow filtering devices when using the command line tools
  • Allow setting custom flags when using fwupdate
  • Allow specifying a firmware GUID to check any version exists
  • Include the kernel release as a runtime version
  • Print devices, remotes, releases using a tree
  • Publish docs to fwupd.github.io using CircleCI

This release fixes the following bugs:

  • Add aliases for get-upgrades and upgrade
  • Allow disabling SSL strict mode for broken corporate proxies
  • Be more accepting when trying to recover a failed database migration
  • Do not segfault when trying to quit the downgrade selection
  • Fix a possible crash when stopping the fwupd service
  • Fix incomplete hex file parsing in unifying plugin
  • Fix thunderbolt logic to work properly with ICL thunderbolt controller
  • Never show AppStream markup on the console
  • Never use memcpy() in a possibly unsafe way
  • Only write the new UEFI device path if different than before
  • Partially rewrite the Synapticsmst plugin to support more hardware
  • Reload metadata store when configuration changes
  • Use environment variables for systemd managed directories
  • Use tpm2-tss library to read PCR values

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 required 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.3.9/data/org.freedesktop.fwupd.service.in000066400000000000000000000002611362775233600220130ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.fwupd Documentation=https://fwupd.org/ Exec=@libexecdir@/fwupd/fwupd User=root SystemdService=fwupd.service AssumedAppArmorLabel=unconfined fwupd-1.3.9/data/org.freedesktop.fwupd.svg000066400000000000000000000243251362775233600205540ustar00rootroot00000000000000 Adwaita Icon Template image/svg+xml GNOME Design Team Adwaita Icon Template fwupd firmwareupdater fwupd-1.3.9/data/pki/000077500000000000000000000000001362775233600143635ustar00rootroot00000000000000fwupd-1.3.9/data/pki/GPG-KEY-Hughski-Limited000066400000000000000000000032461362775233600203430ustar00rootroot00000000000000-----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.3.9/data/pki/GPG-KEY-Linux-Foundation-Firmware000066400000000000000000000041711362775233600223270ustar00rootroot00000000000000-----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.3.9/data/pki/GPG-KEY-Linux-Foundation-Metadata000066400000000000000000000041711362775233600222730ustar00rootroot00000000000000-----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.3.9/data/pki/GPG-KEY-Linux-Vendor-Firmware-Service000066400000000000000000000016771362775233600230640ustar00rootroot00000000000000-----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.3.9/data/pki/LVFS-CA.pem000066400000000000000000000032171362775233600161240ustar00rootroot00000000000000-----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.3.9/data/pki/meson.build000066400000000000000000000012401362775233600165220ustar00rootroot00000000000000if 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.3.9/data/remotes.d/000077500000000000000000000000001362775233600155005ustar00rootroot00000000000000fwupd-1.3.9/data/remotes.d/README.md000066400000000000000000000070231362775233600167610ustar00rootroot00000000000000Vendor 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.3.9/data/remotes.d/lvfs-testing.conf000066400000000000000000000005651362775233600210020ustar00rootroot00000000000000[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 AutomaticReports=false ApprovalRequired=false fwupd-1.3.9/data/remotes.d/lvfs-testing.metainfo.xml000066400000000000000000000027461362775233600224610ustar00rootroot00000000000000 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.3.9/data/remotes.d/lvfs.conf000066400000000000000000000005101362775233600173150ustar00rootroot00000000000000[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 AutomaticReports=false ApprovalRequired=false fwupd-1.3.9/data/remotes.d/lvfs.metainfo.xml000066400000000000000000000023221362775233600207740ustar00rootroot00000000000000 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.3.9/data/remotes.d/meson.build000066400000000000000000000026011362775233600176410ustar00rootroot00000000000000if build_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.3.9/data/remotes.d/vendor-directory.conf000066400000000000000000000004461362775233600216520ustar00rootroot00000000000000[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.3.9/data/remotes.d/vendor.conf000066400000000000000000000004721362775233600176470ustar00rootroot00000000000000[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.3.9/data/tests/000077500000000000000000000000001362775233600147425ustar00rootroot00000000000000fwupd-1.3.9/data/tests/builder/000077500000000000000000000000001362775233600163705ustar00rootroot00000000000000fwupd-1.3.9/data/tests/builder/meson.build000066400000000000000000000005341362775233600205340ustar00rootroot00000000000000if 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.3.9/data/tests/builder/source.bin000066400000000000000000000000261362775233600203600ustar00rootroot00000000000000running in the sandboxfwupd-1.3.9/data/tests/builder/startup.sh000077500000000000000000000000561362775233600204320ustar00rootroot00000000000000#!/bin/sh cat source.bin | rev > firmware.bin fwupd-1.3.9/data/tests/colorhug/000077500000000000000000000000001362775233600165645ustar00rootroot00000000000000fwupd-1.3.9/data/tests/colorhug/README.md000066400000000000000000000004371362775233600200470ustar00rootroot00000000000000# 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.3.9/data/tests/colorhug/firmware.bin000066400000000000000000000175001362775233600210750ustar00rootroot000000000000001 (~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.3.9/data/tests/colorhug/firmware.bin.asc000066400000000000000000000007311362775233600216400ustar00rootroot00000000000000-----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.3.9/data/tests/colorhug/firmware.bin.p7b000066400000000000000000000043251362775233600215650ustar00rootroot00000000000000-----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.3.9/data/tests/colorhug/firmware.metainfo.xml000066400000000000000000000023361362775233600227270ustar00rootroot00000000000000 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.3.9/data/tests/colorhug/meson.build000066400000000000000000000014041362775233600207250ustar00rootroot00000000000000colorhug_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.3.9/data/tests/daemon.conf000077700000000000000000000000001362775233600213742../daemon.confustar00rootroot00000000000000fwupd-1.3.9/data/tests/dmi/000077500000000000000000000000001362775233600155135ustar00rootroot00000000000000fwupd-1.3.9/data/tests/dmi/tables/000077500000000000000000000000001362775233600167655ustar00rootroot00000000000000fwupd-1.3.9/data/tests/dmi/tables/DMI000066400000000000000000000047331362775233600173300ustar00rootroot00000000000000*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.3.9/data/tests/dmi/tables/smbios_entry_point000066400000000000000000000000371362775233600226360ustar00rootroot00000000000000_SM__DMI_ Ӽ>'fwupd-1.3.9/data/tests/dmi/tables64/000077500000000000000000000000001362775233600171375ustar00rootroot00000000000000fwupd-1.3.9/data/tests/dmi/tables64/DMI000066400000000000000000000133051362775233600174750ustar00rootroot00000000000000ڲ@""##(())**++,,--..@@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.3.9/data/tests/dmi/tables64/smbios_entry_point000066400000000000000000000000301362775233600230010ustar00rootroot00000000000000_SM3_ jfwupd-1.3.9/data/tests/firmware-base-uri.conf000066400000000000000000000002461362775233600211340ustar00rootroot00000000000000[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.3.9/data/tests/firmware-nopath.conf000066400000000000000000000002001362775233600207040ustar00rootroot00000000000000[fwupd Remote] Enabled=true Type=download Keyring=gpg MetadataURI=https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz fwupd-1.3.9/data/tests/firmware.bin000066400000000000000000000002101362775233600172410ustar00rootroot00000000000000=   ? B = fwupd-1.3.9/data/tests/firmware.dfu000066400000000000000000000002301362775233600172510ustar00rootroot00000000000000=   ? B = !C4UFD4Yfwupd-1.3.9/data/tests/firmware.hex000066400000000000000000000006001362775233600172600ustar00rootroot00000000000000:044000003DEF20F080 :10400800FACF01F0FBCF02F0E9CF03F0EACF04F0DA :10401800E1CF05F0E2CF06F0D9CF07F0DACF08F00C :10402800F3CF09F0F4CF0AF0F6CF0BF0F7CF0CF08E :10403800F8CF0DF0F5CF0EF00EC0F5FF0DC0F8FF6C :104048000CC0F7FF0BC0F6FF0AC0F4FF09C0F3FF6E :1040580008C0DAFF07C0D9FF06C0E2FF05C0E1FFCC :1040680004C0EAFF03C0E9FF02C0FBFF01C0FAFF7A :1040780011003FEF20F0000142EF20F03DEF20F06B :00000001FF fwupd-1.3.9/data/tests/firmware.shex000066400000000000000000000006441362775233600174530ustar00rootroot00000000000000:100000003DEF20F000000000FACF01F0FBCF02F03E :10001000E9CF03F0EACF04F0E1CF05F0E2CF06F03C :10002000D9CF07F0DACF08F0F3CF09F0F4CF0AF018 :10003000F6CF0BF0F7CF0CF0F8CF0DF0F5CF0EF0B8 :100040000EC0F5FF0DC0F8FF0CC0F7FF0BC0F6FFA8 :100050000AC0F4FF09C0F3FF08C0DAFF07C0D9FFE8 :1000600006C0E2FF05C0E1FF04C0EAFF03C0E9FFEC :1000700002C0FBFF01C0FAFF11003FEF20F00001BA :0800800042EF20F03DEF20F0FB :080000FD6465616462656566DB :00000001FF fwupd-1.3.9/data/tests/firmware.srec000066400000000000000000000006331362775233600174360ustar00rootroot00000000000000S0220000687474703A2F2F737265636F72642E736F75726365666F7267652E6E65742F1D S12300003DEF20F000000000FACF01F0FBCF02F0E9CF03F0EACF04F0E1CF05F0E2CF06F086 S1230020D9CF07F0DACF08F0F3CF09F0F4CF0AF0F6CF0BF0F7CF0CF0F8CF0DF0F5CF0EF0FC S12300400EC0F5FF0DC0F8FF0CC0F7FF0BC0F6FF0AC0F4FF09C0F3FF08C0DAFF07C0D9FFDC S123006006C0E2FF05C0E1FF04C0EAFF03C0E9FF02C0FBFF01C0FAFF11003FEF20F0000112 S10B008042EF20F03DEF20F0F7 S5030005F7 fwupd-1.3.9/data/tests/fwupd/000077500000000000000000000000001362775233600160675ustar00rootroot00000000000000fwupd-1.3.9/data/tests/fwupd/daemon.conf000066400000000000000000000000631362775233600202000ustar00rootroot00000000000000[fwupd] ArchiveSizeMax=5 ApprovedFirmware=deadbeef fwupd-1.3.9/data/tests/history_v1.db000066400000000000000000000300001362775233600173510ustar00rootroot00000000000000SQLite 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.3.9/data/tests/hwids/000077500000000000000000000000001362775233600160605ustar00rootroot00000000000000fwupd-1.3.9/data/tests/hwids/bios_major_release000066400000000000000000000000031362775233600216200ustar00rootroot0000000000000004 fwupd-1.3.9/data/tests/hwids/bios_minor_release000066400000000000000000000000031362775233600216340ustar00rootroot0000000000000006 fwupd-1.3.9/data/tests/hwids/bios_vendor000066400000000000000000000000311362775233600203060ustar00rootroot00000000000000American Megatrends Inc. fwupd-1.3.9/data/tests/hwids/bios_version000066400000000000000000000000051362775233600204770ustar00rootroot000000000000001201 fwupd-1.3.9/data/tests/hwids/board_name000066400000000000000000000000271362775233600200710ustar00rootroot00000000000000To be filled by O.E.M. fwupd-1.3.9/data/tests/hwids/board_vendor000066400000000000000000000000271362775233600204460ustar00rootroot00000000000000To be filled by O.E.M. fwupd-1.3.9/data/tests/hwids/chassis_type000066400000000000000000000000021362775233600204710ustar00rootroot000000000000003 fwupd-1.3.9/data/tests/hwids/product_family000066400000000000000000000000271362775233600210230ustar00rootroot00000000000000To be filled by O.E.M. fwupd-1.3.9/data/tests/hwids/product_name000066400000000000000000000000271362775233600204620ustar00rootroot00000000000000To be filled by O.E.M. fwupd-1.3.9/data/tests/hwids/product_sku000066400000000000000000000000041362775233600203370ustar00rootroot00000000000000SKU fwupd-1.3.9/data/tests/hwids/sys_vendor000066400000000000000000000000271362775233600201750ustar00rootroot00000000000000To be filled by O.E.M. fwupd-1.3.9/data/tests/lockdown/000077500000000000000000000000001362775233600165625ustar00rootroot00000000000000fwupd-1.3.9/data/tests/lockdown/locked/000077500000000000000000000000001362775233600200235ustar00rootroot00000000000000fwupd-1.3.9/data/tests/lockdown/locked/lockdown000066400000000000000000000000411362775233600215610ustar00rootroot00000000000000none integrity [confidentiality] fwupd-1.3.9/data/tests/lockdown/none/000077500000000000000000000000001362775233600175215ustar00rootroot00000000000000fwupd-1.3.9/data/tests/lockdown/none/lockdown000066400000000000000000000000411362775233600212570ustar00rootroot00000000000000[none] integrity confidentiality fwupd-1.3.9/data/tests/meson.build000066400000000000000000000004741362775233600171110ustar00rootroot00000000000000# 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') subdir('multiple-rels') fwupd-1.3.9/data/tests/metadata.xml000066400000000000000000000007731362775233600172530ustar00rootroot00000000000000 org.fwupd.8330a096d9f1af8567c7374cb8403e1ce9cf3163.device 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e

Applying will enable UEFI firmware reporting

fwupd-1.3.9/data/tests/missing-hwid/000077500000000000000000000000001362775233600173445ustar00rootroot00000000000000fwupd-1.3.9/data/tests/missing-hwid/firmware.bin000066400000000000000000000175001362775233600216550ustar00rootroot000000000000001 (~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.3.9/data/tests/missing-hwid/firmware.metainfo.xml000066400000000000000000000007041362775233600235040ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 9342d47a-1bab-5709-9869-c840b2eac501 fwupd-1.3.9/data/tests/missing-hwid/firmware2.metainfo.xml000066400000000000000000000005531362775233600235700ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 fwupd-1.3.9/data/tests/missing-hwid/meson.build000066400000000000000000000007231362775233600215100ustar00rootroot00000000000000hwid_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.3.9/data/tests/multiple-rels/000077500000000000000000000000001362775233600175405ustar00rootroot00000000000000fwupd-1.3.9/data/tests/multiple-rels/firmware-123.bin000066400000000000000000000000121362775233600223420ustar00rootroot000000000000000x1020003 fwupd-1.3.9/data/tests/multiple-rels/firmware-124.bin000066400000000000000000000000121362775233600223430ustar00rootroot000000000000000x1020004 fwupd-1.3.9/data/tests/multiple-rels/firmware.metainfo.xml000066400000000000000000000011001362775233600236670ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 fwupd-1.3.9/data/tests/multiple-rels/meson.build000066400000000000000000000004351362775233600217040ustar00rootroot00000000000000multiple_rels_test_firmware = custom_target('multiple-rels-test-firmware', input : [ 'firmware-123.bin', 'firmware-124.bin', 'firmware.metainfo.xml', ], output : 'multiple-rels-1.2.4.cab', command : [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], ) fwupd-1.3.9/data/tests/pki/000077500000000000000000000000001362775233600155255ustar00rootroot00000000000000fwupd-1.3.9/data/tests/pki/GPG-KEY-Linux-Vendor-Firmware-Service000077700000000000000000000000001362775233600341052../../pki/GPG-KEY-Linux-Vendor-Firmware-Serviceustar00rootroot00000000000000fwupd-1.3.9/data/tests/pki/LVFS-CA.pem000077700000000000000000000000001362775233600222252../../pki/LVFS-CA.pemustar00rootroot00000000000000fwupd-1.3.9/data/tests/pki/meson.build000066400000000000000000000005741362775233600176750ustar00rootroot00000000000000# 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.3.9/data/tests/pki/test.cfg000066400000000000000000000001351362775233600171640ustar00rootroot00000000000000organization = "Hughski Limited" expiration_days = -1 email = "info@hughski.com" signing_key fwupd-1.3.9/data/tests/quirks.d/000077500000000000000000000000001362775233600165025ustar00rootroot00000000000000fwupd-1.3.9/data/tests/quirks.d/tests.quirk000066400000000000000000000004611362775233600207220ustar00rootroot00000000000000[USB\VID_0A5C&PID_6412] Flags= ignore-runtime [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.3.9/data/tests/remotes.d/000077500000000000000000000000001362775233600166425ustar00rootroot00000000000000fwupd-1.3.9/data/tests/remotes.d/broken.conf000066400000000000000000000001221362775233600207640ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/broken.xml.gz fwupd-1.3.9/data/tests/remotes.d/directory.conf000066400000000000000000000001411362775233600215110ustar00rootroot00000000000000[fwupd Remote] Enabled=true Keyring=none MetadataURI=file:///tmp/fwupd-self-test/var/cache/fwupd fwupd-1.3.9/data/tests/remotes.d/stable.conf000066400000000000000000000001171362775233600207620ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/stable.xml fwupd-1.3.9/data/tests/remotes.d/testing.conf000066400000000000000000000001461362775233600211670ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/testing.xml ApprovalRequired=true fwupd-1.3.9/data/tests/spawn.sh000077500000000000000000000002701362775233600164300ustar00rootroot00000000000000#!/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.3.9/data/tests/thunderbolt/000077500000000000000000000000001362775233600172745ustar00rootroot00000000000000fwupd-1.3.9/data/tests/thunderbolt/COPYING000066400000000000000000000027321362775233600203330ustar00rootroot00000000000000Copyright(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.3.9/data/tests/thunderbolt/minimal-fw-controller.bin000066400000000000000000000004771362775233600242170ustar00rootroot00000000000000$40`fwupd-1.3.9/data/tests/thunderbolt/minimal-fw.bin000066400000000000000000000005001362775233600220210ustar00rootroot00000000000000$40`fwupd-1.3.9/docs/000077500000000000000000000000001362775233600136175ustar00rootroot00000000000000fwupd-1.3.9/docs/architecture-plan.svg000066400000000000000000001667301362775233600177670ustar00rootroot00000000000000 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.3.9/docs/fwupd-docs.xml000066400000000000000000000404541362775233600164230ustar00rootroot00000000000000 ]> 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=logitech_hidpp would set FWUPD_LOGITECH_HID_VERBOSE=1.
API Index Index of deprecated API
fwupd-1.3.9/docs/meson.build000066400000000000000000000004541362775233600157640ustar00rootroot00000000000000gnome.gtkdoc( 'fwupd', src_dir : [ join_paths(meson.source_root(), 'libfwupd'), join_paths(meson.source_root(), 'libfwupdplugin'), join_paths(meson.build_root(), 'libfwupd'), join_paths(meson.build_root(), 'libfwupdplugin'), ], main_xml : 'fwupd-docs.xml', install : true ) fwupd-1.3.9/libfwupd/000077500000000000000000000000001362775233600145035ustar00rootroot00000000000000fwupd-1.3.9/libfwupd/README.md000066400000000000000000000020651362775233600157650ustar00rootroot00000000000000Migration 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.3.9/libfwupd/fwupd-client.c000066400000000000000000001566511362775233600172660ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #ifdef HAVE_GIO_UNIX #include #endif #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; gboolean interactive; guint percentage; gchar *daemon_version; gchar *host_product; gchar *host_machine_id; 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_HOST_PRODUCT, PROP_HOST_MACHINE_ID, PROP_INTERACTIVE, 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_host_product (FwupdClient *client, const gchar *host_product) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_free (priv->host_product); priv->host_product = g_strdup (host_product); g_object_notify (G_OBJECT (client), "host-product"); } static void fwupd_client_set_host_machine_id (FwupdClient *client, const gchar *host_machine_id) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_free (priv->host_machine_id); priv->host_machine_id = g_strdup (host_machine_id); g_object_notify (G_OBJECT (client), "host-machine-id"); } 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, "Interactive")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property (proxy, "Interactive"); if (val != NULL) { priv->interactive = g_variant_get_boolean (val); g_object_notify (G_OBJECT (client), "interactive"); } } 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)); } if (g_variant_dict_contains (dict, "HostProduct")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property (proxy, "HostProduct"); if (val != NULL) fwupd_client_set_host_product (client, g_variant_get_string (val, NULL)); } if (g_variant_dict_contains (dict, "HostMachineId")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property (proxy, "HostMachineId"); if (val != NULL) fwupd_client_set_host_machine_id (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); val2 = g_dbus_proxy_get_cached_property (priv->proxy, "Interactive"); if (val2 != NULL) priv->interactive = g_variant_get_boolean (val2); val = g_dbus_proxy_get_cached_property (priv->proxy, "HostProduct"); if (val != NULL) fwupd_client_set_host_product (client, g_variant_get_string (val, NULL)); val = g_dbus_proxy_get_cached_property (priv->proxy, "HostMachineId"); if (val != NULL) fwupd_client_set_host_machine_id (client, g_variant_get_string (val, NULL)); 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); } #ifdef HAVE_GIO_UNIX 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); } #endif /** * 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) { #ifdef HAVE_GIO_UNIX 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; #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return FALSE; #endif } /** * 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) { #ifdef HAVE_GIO_UNIX 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); #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return NULL; #endif } /** * 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_host_product: * @client: A #FwupdClient * * Gets the string that represents the host running fwupd * * Returns: a string, or %NULL for unknown. * * Since: 1.3.1 **/ const gchar * fwupd_client_get_host_product (FwupdClient *client) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); return priv->host_product; } /** * fwupd_client_get_host_machine_id: * @client: A #FwupdClient * * Gets the string that represents the host machine ID * * Returns: a string, or %NULL for unknown. * * Since: 1.3.2 **/ const gchar * fwupd_client_get_host_machine_id (FwupdClient *client) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_return_val_if_fail (FWUPD_IS_CLIENT (client), NULL); return priv->host_machine_id; } /** * 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_get_daemon_interactive: * @client: A #FwupdClient * * Gets if the daemon is running in an interactive terminal. * * Returns: %TRUE if the daemon is running in an interactive terminal * * Since: 1.3.4 **/ gboolean fwupd_client_get_daemon_interactive (FwupdClient *client) { FwupdClientPrivate *priv = GET_PRIVATE (client); g_return_val_if_fail (FWUPD_IS_CLIENT (client), FALSE); return priv->interactive; } /** * 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) { #ifdef HAVE_GIO_UNIX 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; #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return FALSE; #endif } /** * 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; case PROP_HOST_PRODUCT: g_value_set_string (value, priv->host_product); break; case PROP_HOST_MACHINE_ID: g_value_set_string (value, priv->host_machine_id); break; case PROP_INTERACTIVE: g_value_set_boolean (value, priv->interactive); 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:interactive: * * If the daemon is running in an interactive terminal * * Since: 1.3.4 */ pspec = g_param_spec_boolean ("interactive", NULL, NULL, FALSE, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_INTERACTIVE, 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); /** * FwupdClient:host-product: * * The host product string * * Since: 1.3.1 */ pspec = g_param_spec_string ("host-product", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_HOST_PRODUCT, pspec); /** * FwupdClient:host-machine-id: * * The host machine-id string * * Since: 1.3.2 */ pspec = g_param_spec_string ("host-machine-id", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_HOST_MACHINE_ID, 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); g_free (priv->host_product); g_free (priv->host_machine_id); 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.3.9/libfwupd/fwupd-client.h000066400000000000000000000124031362775233600172550ustar00rootroot00000000000000/* * 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); gboolean fwupd_client_get_daemon_interactive (FwupdClient *client); guint fwupd_client_get_percentage (FwupdClient *client); const gchar *fwupd_client_get_daemon_version (FwupdClient *client); const gchar *fwupd_client_get_host_product (FwupdClient *client); const gchar *fwupd_client_get_host_machine_id (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.3.9/libfwupd/fwupd-common-private.h000066400000000000000000000004071362775233600207400ustar00rootroot00000000000000/* * 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.3.9/libfwupd/fwupd-common.c000066400000000000000000000605721362775233600172740ustar00rootroot00000000000000/* * 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 #ifdef HAVE_UTSNAME_H #include #endif #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; /* TODO: Read the Windows version */ #ifdef _WIN32 hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert (hash, g_strdup("OS"), g_strdup("Windows")); return hash; #endif /* 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) { #ifdef HAVE_UTSNAME_H struct utsname name_tmp; #endif 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" */ #ifdef HAVE_UTSNAME_H 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)); } #endif /* current locale, e.g. "en-gb" */ #ifdef HAVE_LC_MESSAGES locale = g_strdup (setlocale (LC_MESSAGES, NULL)); #endif 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) { const gchar *fn = NULL; g_autofree gchar *buf = NULL; g_auto(GStrv) fns = g_new0 (gchar *, 5); g_autoptr(GChecksum) csum = NULL; gsize sz = 0; /* one of these has to exist */ fns[0] = g_build_filename (FWUPD_SYSCONFDIR, "machine-id", NULL); fns[1] = g_build_filename (FWUPD_LOCALSTATEDIR, "lib", "dbus", "machine-id", NULL); fns[2] = g_strdup ("/etc/machine-id"); fns[3] = g_strdup ("/var/lib/dbus/machine-id"); for (guint i = 0; fns[i] != NULL; i++) { if (g_file_test (fns[i], G_FILE_TEST_EXISTS)) { fn = fns[i]; break; } } if (fn == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ, "The machine-id is not present"); return NULL; } if (!g_file_get_contents (fn, &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; GPtrArray *guids; /* 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 */ guids = fwupd_device_get_guids (dev); if (guids->len > 0) { json_builder_set_member_name (builder, "Guid"); json_builder_begin_array (builder); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index (guids, i); json_builder_add_string_value (builder, guid); } json_builder_end_array (builder); } 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.3.9/libfwupd/fwupd-common.h000066400000000000000000000043201362775233600172660ustar00rootroot00000000000000/* * 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.3.9/libfwupd/fwupd-deprecated.h000066400000000000000000000002541362775233600201000ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once G_BEGIN_DECLS /* indeed, nothing */ G_END_DECLS fwupd-1.3.9/libfwupd/fwupd-device-private.h000066400000000000000000000010531362775233600207050ustar00rootroot00000000000000/* * 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.3.9/libfwupd/fwupd-device.c000066400000000000000000001721611362775233600172410ustar00rootroot00000000000000/* * 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 *protocol; gchar *version; gchar *version_lowest; gchar *version_bootloader; FwupdVersionFormat version_format; guint64 version_raw; GPtrArray *checksums; GPtrArray *children; 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_FLAGS, PROP_PROTOCOL, 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_children: * @device: A #FwupdDevice * * Gets the device children. These can only be assigned using fwupd_device_set_parent(). * * Returns: (element-type FwupdDevice) (transfer none): the children, which may be empty * * Since: 1.3.7 **/ GPtrArray * fwupd_device_get_children (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->children; } /** * 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); FwupdDevicePrivate *priv_parent = GET_PRIVATE (parent); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_set_object (&priv->parent, parent); g_ptr_array_add (priv_parent->children, g_object_ref (device)); } /** * 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_protocol: * @device: A #FwupdDevice * * Gets the protocol that the device uses for updating. * * Returns: the protocol name, or %NULL if unset * * Since: 1.3.6 **/ const gchar * fwupd_device_get_protocol (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), NULL); return priv->protocol; } /** * fwupd_device_set_protocol: * @device: A #FwupdDevice * @protocol: the protocol name, e.g. `com.hughski.colorhug` * * Sets the protocol that is used to update the device. * * Since: 1.3.6 **/ void fwupd_device_set_protocol (FwupdDevice *device, const gchar *protocol) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); g_free (priv->protocol); priv->protocol = g_strdup (protocol); } /** * 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)); if (priv->flags == flags) return; priv->flags = flags; g_object_notify (G_OBJECT (device), "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)); if (flag == 0) return; if ((priv->flags & flag) > 0) return; priv->flags |= flag; g_object_notify (G_OBJECT (device), "flags"); } /** * 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)); if (flag == 0) return; if ((priv->flags & flag) == 0) return; priv->flags &= ~flag; g_object_notify (G_OBJECT (device), "flags"); } /** * 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->protocol == NULL) fwupd_device_set_protocol (self, priv_donor->protocol); 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); if (priv->version_raw == 0) fwupd_device_set_version_raw (self, priv_donor->version_raw); 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->protocol != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_PROTOCOL, g_variant_new_string (priv->protocol)); } 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->version_raw > 0) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_RAW, g_variant_new_uint64 (priv->version_raw)); } 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_PROTOCOL) == 0) { fwupd_device_set_protocol (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; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_VERSION_RAW) == 0) { fwupd_device_set_version_raw (device, g_variant_get_uint64 (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_version_raw: * @device: A #FwupdDevice * * Gets the raw version number from the hardware before converted to a string. * * Returns: the hardware version, or 0 if unset * * Since: 1.3.6 **/ guint64 fwupd_device_get_version_raw (FwupdDevice *device) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_val_if_fail (FWUPD_IS_DEVICE (device), 0); return priv->version_raw; } /** * fwupd_device_set_version_raw: * @device: A #FwupdDevice * @version_raw: the raw hardware version * * Sets the raw version number from the hardware before converted to a string. * * Since: 1.3.6 **/ void fwupd_device_set_version_raw (FwupdDevice *device, guint64 version_raw) { FwupdDevicePrivate *priv = GET_PRIVATE (device); g_return_if_fail (FWUPD_IS_DEVICE (device)); priv->version_raw = version_raw; } /** * 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); fwupd_device_json_add_string (builder, FWUPD_RESULT_KEY_PROTOCOL, priv->protocol); 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->version_raw > 0) fwupd_device_json_add_int (builder, FWUPD_RESULT_KEY_VERSION_RAW, priv->version_raw); 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_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); if (priv->guids->len > 0) { g_autoptr(GHashTable) ids = NULL; 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); } } } else { for (guint i = 0; i < priv->instance_ids->len; i++) { const gchar *instance_id = g_ptr_array_index (priv->instance_ids, i); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_INSTANCE_IDS, instance_id); } } 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_str (str, FWUPD_RESULT_KEY_PROTOCOL, priv->protocol); 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->version_raw > 0) { g_autofree gchar *tmp = NULL; if (priv->version_raw > 0xffffffff) { tmp = g_strdup_printf ("0x%08x%08x", (guint) (priv->version_raw >> 32), (guint) (priv->version_raw & 0xffffffff)); } else { tmp = g_strdup_printf ("0x%08x", (guint) priv->version_raw); } fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_VERSION_RAW, tmp); } 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; case PROP_FLAGS: g_value_set_uint64 (value, priv->flags); break; case PROP_PROTOCOL: g_value_set_string (value, priv->protocol); 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; case PROP_FLAGS: fwupd_device_set_flags (self, g_value_get_uint64 (value)); break; case PROP_PROTOCOL: fwupd_device_set_protocol (self, g_value_get_string (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); pspec = g_param_spec_uint64 ("flags", NULL, NULL, FWUPD_DEVICE_FLAG_NONE, FWUPD_DEVICE_FLAG_UNKNOWN, FWUPD_DEVICE_FLAG_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_FLAGS, pspec); pspec = g_param_spec_string ("protocol", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_PROTOCOL, 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->children = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); 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->protocol); 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->children); 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_ensure_parents: * @devices: (element-type FwupdDevice): devices * * Sets the parent object on all devices in the array using the parent-id. * * Since: 1.3.7 **/ void fwupd_device_array_ensure_parents (GPtrArray *devices) { g_autoptr(GHashTable) devices_by_id = NULL; /* create hash of ID->FwupdDevice */ devices_by_id = g_hash_table_new (g_str_hash, g_str_equal); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); if (fwupd_device_get_id (dev) == NULL) continue; 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 < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, 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); if (dev_tmp != NULL) fwupd_device_set_parent (dev, dev_tmp); } } } /** * 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; 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++) { 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); } /* set the parent on each child */ fwupd_device_array_ensure_parents (array); 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.3.9/libfwupd/fwupd-device.h000066400000000000000000000137351362775233600172470ustar00rootroot00000000000000/* * 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); GPtrArray *fwupd_device_get_children (FwupdDevice *device); 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); guint64 fwupd_device_get_version_raw (FwupdDevice *device); void fwupd_device_set_version_raw (FwupdDevice *device, guint64 version_raw); 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_protocol (FwupdDevice *device); void fwupd_device_set_protocol (FwupdDevice *device, const gchar *protocol); 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); void fwupd_device_array_ensure_parents (GPtrArray *devices); G_END_DECLS fwupd-1.3.9/libfwupd/fwupd-enums-private.h000066400000000000000000000053241362775233600206020ustar00rootroot00000000000000/* * 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_DETACH_CAPTION "DetachCaption" /* s */ #define FWUPD_RESULT_KEY_DETACH_IMAGE "DetachImage" /* 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_ISSUES "Issues" /* 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_NAME_VARIANT_SUFFIX "NameVariantSuffix" /* s */ #define FWUPD_RESULT_KEY_PLUGIN "Plugin" /* s */ #define FWUPD_RESULT_KEY_PROTOCOL "Protocol" /* 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_RAW "VersionRaw" /* t */ #define FWUPD_RESULT_KEY_VERSION_LOWEST "VersionLowest" /* s */ #define FWUPD_RESULT_KEY_VERSION "Version" /* s */ G_END_DECLS fwupd-1.3.9/libfwupd/fwupd-enums.c000066400000000000000000000411441362775233600171250ustar00rootroot00000000000000/* * 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_HISTORICAL) return "historical"; if (device_flag == FWUPD_DEVICE_FLAG_ONLY_SUPPORTED) return "only-supported"; if (device_flag == FWUPD_DEVICE_FLAG_WILL_DISAPPEAR) return "will-disappear"; if (device_flag == FWUPD_DEVICE_FLAG_CAN_VERIFY) return "can-verify"; if (device_flag == FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE) return "can-verify-image"; if (device_flag == FWUPD_DEVICE_FLAG_DUAL_IMAGE) return "dual-image"; if (device_flag == FWUPD_DEVICE_FLAG_SELF_RECOVERY) return "self-recovery"; if (device_flag == FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE) return "usable-during-update"; if (device_flag == FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED) return "version-check-required"; if (device_flag == FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES) return "install-all-releases"; 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; if (g_strcmp0 (device_flag, "historical") == 0) return FWUPD_DEVICE_FLAG_HISTORICAL; if (g_strcmp0 (device_flag, "only-supported") == 0) return FWUPD_DEVICE_FLAG_ONLY_SUPPORTED; if (g_strcmp0 (device_flag, "will-disappear") == 0) return FWUPD_DEVICE_FLAG_WILL_DISAPPEAR; if (g_strcmp0 (device_flag, "can-verify") == 0) return FWUPD_DEVICE_FLAG_CAN_VERIFY; if (g_strcmp0 (device_flag, "can-verify-image") == 0) return FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE; if (g_strcmp0 (device_flag, "dual-image") == 0) return FWUPD_DEVICE_FLAG_DUAL_IMAGE; if (g_strcmp0 (device_flag, "self-recovery") == 0) return FWUPD_DEVICE_FLAG_SELF_RECOVERY; if (g_strcmp0 (device_flag, "usable-during-update") == 0) return FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE; if (g_strcmp0 (device_flag, "version-check-required") == 0) return FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED; if (g_strcmp0 (device_flag, "install-all-releases") == 0) return FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES; 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; if (g_strcmp0 (str, "surface-legacy") == 0) return FWUPD_VERSION_FORMAT_SURFACE_LEGACY; if (g_strcmp0 (str, "surface") == 0) return FWUPD_VERSION_FORMAT_SURFACE; if (g_strcmp0 (str, "dell-bios") == 0) return FWUPD_VERSION_FORMAT_DELL_BIOS; 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"; if (kind == FWUPD_VERSION_FORMAT_SURFACE_LEGACY) return "surface-legacy"; if (kind == FWUPD_VERSION_FORMAT_SURFACE) return "surface"; if (kind == FWUPD_VERSION_FORMAT_DELL_BIOS) return "dell-bios"; return NULL; } fwupd-1.3.9/libfwupd/fwupd-enums.h000066400000000000000000000335641362775233600171410ustar00rootroot00000000000000/* * 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_HISTORICAL Device is for historical data only * @FWUPD_DEVICE_FLAG_ENSURE_SEMVER: Ensure the version is a valid semantic version, e.g. numbers separated with dots * @FWUPD_DEVICE_FLAG_ONLY_SUPPORTED: Only devices supported in the metadata will be opened * @FWUPD_DEVICE_FLAG_WILL_DISAPPEAR: Device will disappear after update and can't be verified * @FWUPD_DEVICE_FLAG_CAN_VERIFY: Device checksums can be compared against metadata * @FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE: Image can be dumped from device for verification * @FWUPD_DEVICE_FLAG_DUAL_IMAGE: Device update architecture uses A/B partitions for updates * @FWUPD_DEVICE_FLAG_SELF_RECOVERY: In flashing mode device will only accept intended payloads * @FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE: Device remains usable while fwupd flashes or schedules the update * @FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED: All firmware updates for this device require a firmware version check * @FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES: Install each intermediate release rather than jumping direct to newest * * 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_HISTORICAL (1u << 22) /* Since: 1.3.2 */ #define FWUPD_DEVICE_FLAG_ONLY_SUPPORTED (1u << 23) /* Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_WILL_DISAPPEAR (1u << 24) /* Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_CAN_VERIFY (1u << 25) /* Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE (1u << 26) /* Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_DUAL_IMAGE (1u << 27) /* Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_SELF_RECOVERY (1u << 28) /* Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE (1u << 29) /* Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED (1u << 30) /* Since: 1.3.7 */ #define FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES (1u << 31) /* Since: 1.3.7 */ #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: UEFI-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 * @FWUPD_VERSION_FORMAT_SURFACE_LEGACY: Legacy Microsoft Surface 10b.12b.10b * @FWUPD_VERSION_FORMAT_SURFACE: Microsoft Surface 8b.16b.8b * @FWUPD_VERSION_FORMAT_DELL_BIOS: Dell BIOS BB.CC.DD style * * 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 */ FWUPD_VERSION_FORMAT_SURFACE_LEGACY, /* Since: 1.3.4 */ FWUPD_VERSION_FORMAT_SURFACE, /* Since: 1.3.4 */ FWUPD_VERSION_FORMAT_DELL_BIOS, /* Since: 1.3.6 */ /*< 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.3.9/libfwupd/fwupd-error.c000066400000000000000000000107721362775233600171320ustar00rootroot00000000000000/* * 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"; if (error == FWUPD_ERROR_NEEDS_USER_ACTION) return FWUPD_DBUS_INTERFACE ".NeedsUserAction"; 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; if (g_strcmp0 (error, FWUPD_DBUS_INTERFACE ".NeedsUserAction") == 0) return FWUPD_ERROR_NEEDS_USER_ACTION; 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.3.9/libfwupd/fwupd-error.h000066400000000000000000000044421362775233600171340ustar00rootroot00000000000000/* * 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 * @FWUPD_ERROR_NEEDS_USER_ACTION: User needs to do an action to complete the update * * 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 */ FWUPD_ERROR_NEEDS_USER_ACTION, /* Since: 1.3.3 */ /*< 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.3.9/libfwupd/fwupd-release-private.h000066400000000000000000000006011362775233600210640ustar00rootroot00000000000000/* * 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.3.9/libfwupd/fwupd-release.c000066400000000000000000001403601362775233600174160ustar00rootroot00000000000000/* * 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; GPtrArray *issues; GHashTable *metadata; gchar *description; gchar *filename; gchar *protocol; gchar *homepage; gchar *details_url; gchar *source_url; gchar *appstream_id; gchar *detach_caption; gchar *detach_image; gchar *license; gchar *name; gchar *name_variant_suffix; 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_issues: * @release: A #FwupdRelease * * Gets the list of issues fixed in this release. * * Returns: (element-type utf8) (transfer none): the issues, which may be empty * * Since: 1.3.2 **/ GPtrArray * fwupd_release_get_issues (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->issues; } /** * fwupd_release_add_issue: * @release: A #FwupdRelease * @issue: the update issue, e.g. `CVE-2019-12345` * * Adds an resolved issue to this release. * * Since: 1.3.2 **/ void fwupd_release_add_issue (FwupdRelease *release, const gchar *issue) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_return_if_fail (issue != NULL); for (guint i = 0; i < priv->issues->len; i++) { const gchar *issue_tmp = g_ptr_array_index (priv->issues, i); if (g_strcmp0 (issue_tmp, issue) == 0) return; } g_ptr_array_add (priv->issues, g_strdup (issue)); } /** * 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_detach_caption: * @release: A #FwupdRelease * * Gets the optional text caption used to manually detach the device. * * Returns: the string caption, or %NULL if unset * * Since: 1.3.3 **/ const gchar * fwupd_release_get_detach_caption (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->detach_caption; } /** * fwupd_release_set_detach_caption: * @release: A #FwupdRelease * @detach_caption: string caption * * Sets the optional text caption used to manually detach the device. * * Since: 1.3.3 **/ void fwupd_release_set_detach_caption (FwupdRelease *release, const gchar *detach_caption) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->detach_caption); priv->detach_caption = g_strdup (detach_caption); } /** * fwupd_release_get_detach_image: * @release: A #FwupdRelease * * Gets the optional image used to manually detach the device. * * Returns: the URI, or %NULL if unset * * Since: 1.3.3 **/ const gchar * fwupd_release_get_detach_image (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->detach_image; } /** * fwupd_release_set_detach_image: * @release: A #FwupdRelease * @detach_image: a fully qualified URI * * Sets the optional image used to manually detach the device. * * Since: 1.3.3 **/ void fwupd_release_set_detach_image (FwupdRelease *release, const gchar *detach_image) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->detach_image); priv->detach_image = g_strdup (detach_image); } /** * 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_name_variant_suffix: * @release: A #FwupdRelease * * Gets the update variant suffix. * * Returns: the update variant, or %NULL if unset * * Since: 1.3.2 **/ const gchar * fwupd_release_get_name_variant_suffix (FwupdRelease *release) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_val_if_fail (FWUPD_IS_RELEASE (release), NULL); return priv->name_variant_suffix; } /** * fwupd_release_set_name_variant_suffix: * @release: A #FwupdRelease * @name_variant_suffix: the description * * Sets the update variant suffix. * * Since: 1.3.2 **/ void fwupd_release_set_name_variant_suffix (FwupdRelease *release, const gchar *name_variant_suffix) { FwupdReleasePrivate *priv = GET_PRIVATE (release); g_return_if_fail (FWUPD_IS_RELEASE (release)); g_free (priv->name_variant_suffix); priv->name_variant_suffix = g_strdup (name_variant_suffix); } /** * 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->detach_caption != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_DETACH_CAPTION, g_variant_new_string (priv->detach_caption)); } if (priv->detach_image != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_DETACH_IMAGE, g_variant_new_string (priv->detach_image)); } 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->name_variant_suffix != NULL) { g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_NAME_VARIANT_SUFFIX, g_variant_new_string (priv->name_variant_suffix)); } 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->issues->len > 0) { g_autofree const gchar **strv = g_new0 (const gchar *, priv->issues->len + 1); for (guint i = 0; i < priv->issues->len; i++) strv[i] = (const gchar *) g_ptr_array_index (priv->issues, i); g_variant_builder_add (&builder, "{sv}", FWUPD_RESULT_KEY_ISSUES, 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_DETACH_CAPTION) == 0) { fwupd_release_set_detach_caption (release, g_variant_get_string (value, NULL)); return; } if (g_strcmp0 (key, FWUPD_RESULT_KEY_DETACH_IMAGE) == 0) { fwupd_release_set_detach_image (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_NAME_VARIANT_SUFFIX) == 0) { fwupd_release_set_name_variant_suffix (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_ISSUES) == 0) { g_autofree const gchar **strv = g_variant_get_strv (value, NULL); for (guint i = 0; strv[i] != NULL; i++) fwupd_release_add_issue (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->issues->len > 0) { json_builder_set_member_name (builder, FWUPD_RESULT_KEY_ISSUES); json_builder_begin_array (builder); for (guint i = 0; i < priv->issues->len; i++) { const gchar *tmp = g_ptr_array_index (priv->issues, 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_DETACH_CAPTION, priv->detach_caption); fwupd_release_json_add_string (builder, FWUPD_RESULT_KEY_DETACH_IMAGE, priv->detach_image); 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->issues->len; i++) { const gchar *tmp = g_ptr_array_index (priv->issues, i); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_ISSUES, 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); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_DETACH_CAPTION, priv->detach_caption); fwupd_pad_kv_str (str, FWUPD_RESULT_KEY_DETACH_IMAGE, priv->detach_image); 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->issues = 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->detach_caption); g_free (priv->detach_image); g_free (priv->license); g_free (priv->name); g_free (priv->name_variant_suffix); 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->issues); 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.3.9/libfwupd/fwupd-release.h000066400000000000000000000132431362775233600174220ustar00rootroot00000000000000/* * 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_issues (FwupdRelease *release); void fwupd_release_add_issue (FwupdRelease *release, const gchar *issue); 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_detach_caption (FwupdRelease *release); void fwupd_release_set_detach_caption (FwupdRelease *release, const gchar *detach_caption); const gchar *fwupd_release_get_detach_image (FwupdRelease *release); void fwupd_release_set_detach_image (FwupdRelease *release, const gchar *detach_image); 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_name_variant_suffix (FwupdRelease *release); void fwupd_release_set_name_variant_suffix (FwupdRelease *release, const gchar *name_variant_suffix); 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.3.9/libfwupd/fwupd-remote-private.h000066400000000000000000000015131362775233600207420ustar00rootroot00000000000000/* * 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); void fwupd_remote_set_remotes_dir (FwupdRemote *self, const gchar *directory); G_END_DECLS fwupd-1.3.9/libfwupd/fwupd-remote.c000066400000000000000000001100171362775233600172650ustar00rootroot00000000000000/* * 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; gchar *remotes_dir; gboolean automatic_reports; } FwupdRemotePrivate; enum { PROP_0, PROP_ID, PROP_ENABLED, PROP_APPROVAL_REQUIRED, PROP_AUTOMATIC_REPORTS, 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); /* automatic report uploading */ priv->automatic_reports = g_key_file_get_boolean (kf, group, "AutomaticReports", NULL); /* 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); if (priv->remotes_dir == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Remotes directory not set"); return FALSE; } /* set cache to /var/lib... */ filename_cache = g_build_filename (priv->remotes_dir, 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_remotes_dir: * @self: A #FwupdRemote * @directory: Remotes directory * * Sets the directory to store remote data * * Since: 1.3.1 **/ void fwupd_remote_set_remotes_dir (FwupdRemote *self, const gchar *directory) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FWUPD_IS_REMOTE (self)); g_free (priv->remotes_dir); priv->remotes_dir = g_strdup (directory); } /** * 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_remotes_dir: * @self: A #FwupdRemote * * Gets the base directory for storing remote metadata * * Returns: a string, or %NULL if unset * * Since: 1.3.1 **/ const gchar * fwupd_remote_get_remotes_dir (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), NULL); return priv->remotes_dir; } /** * 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_automatic_reports: * @self: A #FwupdRemote * * Gets if reports should be automatically uploaded to this remote * * Returns: a #TRUE if the remote should have reports uploaded automatically * * Since: 1.3.3 **/ gboolean fwupd_remote_get_automatic_reports (FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FWUPD_IS_REMOTE (self), FALSE); return priv->automatic_reports; } /** * 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)); } else if (g_strcmp0 (key, "AutomaticReports") == 0) { priv->automatic_reports = g_variant_get_boolean (value); } } } /** * 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)); } if (priv->remotes_dir != NULL) { g_variant_builder_add (&builder, "{sv}", "RemotesDir", g_variant_new_string (priv->remotes_dir)); } 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)); g_variant_builder_add (&builder, "{sv}", "AutomaticReports", g_variant_new_boolean (priv->automatic_reports)); 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; case PROP_AUTOMATIC_REPORTS: g_value_set_boolean (value, priv->automatic_reports); 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; case PROP_AUTOMATIC_REPORTS: priv->automatic_reports = g_value_get_boolean (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); /** * FwupdRemote:automatic-reports: * * The behavior for auto-uploading reports. * * Since: 1.3.3 */ pspec = g_param_spec_boolean ("automatic-reports", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_AUTOMATIC_REPORTS, 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->remotes_dir); 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.3.9/libfwupd/fwupd-remote.h000066400000000000000000000054731362775233600173030ustar00rootroot00000000000000/* * 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_remotes_dir (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); gboolean fwupd_remote_get_automatic_reports (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.3.9/libfwupd/fwupd-self-test.c000066400000000000000000000531721362775233600177100ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #ifdef HAVE_FNMATCH_H #include #endif #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 */ #ifdef HAVE_FNMATCH_H if (fnmatch (txt2, txt1, FNM_NOESCAPE) == 0) return TRUE; #else if (g_strcmp0 (txt1, txt2) == 0) return TRUE; #endif /* 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_autofree gchar *directory = NULL; g_autofree gchar *expected_metadata = NULL; g_autofree gchar *expected_signature = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error = NULL; remote = fwupd_remote_new (); directory = g_build_filename (FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes.d", NULL); expected_metadata = g_build_filename (FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes.d", "lvfs", "metadata.xml.gz", NULL); expected_signature = g_strdup_printf ("%s.asc", expected_metadata); fwupd_remote_set_remotes_dir (remote, directory); 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), ==, expected_metadata); g_assert_cmpstr (fwupd_remote_get_filename_cache_sig (remote), ==, expected_signature); } /* 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_autofree gchar *directory = NULL; g_autoptr(GError) error = NULL; remote = fwupd_remote_new (); directory = g_build_filename (FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes.d", NULL); fwupd_remote_set_remotes_dir (remote, directory); 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; g_autofree gchar *directory = NULL; remote = fwupd_remote_new (); directory = g_build_filename (FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes.d", NULL); fwupd_remote_set_remotes_dir (remote, directory); 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.3.9/libfwupd/fwupd-version.h.in000066400000000000000000000026521362775233600200760ustar00rootroot00000000000000/* * 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.3.9/libfwupd/fwupd.h000066400000000000000000000011551362775233600160030ustar00rootroot00000000000000/* * 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.3.9/libfwupd/fwupd.map000066400000000000000000000232101362775233600163250ustar00rootroot00000000000000# 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; LIBFWUPD_1.3.1 { global: fwupd_client_get_host_product; fwupd_remote_get_remotes_dir; fwupd_remote_set_remotes_dir; local: *; } LIBFWUPD_1.2.10; LIBFWUPD_1.3.2 { global: fwupd_client_get_host_machine_id; fwupd_release_add_issue; fwupd_release_get_issues; fwupd_release_get_name_variant_suffix; fwupd_release_set_name_variant_suffix; local: *; } LIBFWUPD_1.3.1; LIBFWUPD_1.3.3 { global: fwupd_release_get_detach_caption; fwupd_release_get_detach_image; fwupd_release_set_detach_caption; fwupd_release_set_detach_image; fwupd_remote_get_automatic_reports; local: *; } LIBFWUPD_1.3.2; LIBFWUPD_1.3.4 { global: fwupd_client_get_daemon_interactive; local: *; } LIBFWUPD_1.3.3; LIBFWUPD_1.3.6 { global: fwupd_device_get_protocol; fwupd_device_get_version_raw; fwupd_device_set_protocol; fwupd_device_set_version_raw; local: *; } LIBFWUPD_1.3.4; LIBFWUPD_1.3.7 { global: fwupd_device_array_ensure_parents; fwupd_device_get_children; local: *; } LIBFWUPD_1.3.6; fwupd-1.3.9/libfwupd/meson.build000066400000000000000000000076231362775233600166550ustar00rootroot00000000000000cargs = [ '-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', ) fwupd_mapfile = 'fwupd.map' vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), fwupd_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 : libfwupd_lt_current, version : libfwupd_lt_version, dependencies : [ giounix, soup, libjsonglib, ], c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], include_directories : root_incdir, link_args : vflag, link_depends : fwupd_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') fwupd_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 : fwupd_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('fwupd_mapfile', input: fwupd_gir[0], output: 'fwupd.map', command: [ join_paths(meson.source_root(), 'contrib', 'generate-version-script.py'), 'LIBFWUPD', '@INPUT@', '@OUTPUT@', ], ) 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 : [ root_incdir, ], 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_incdir = include_directories('.') fwupd-1.3.9/libfwupdplugin/000077500000000000000000000000001362775233600157225ustar00rootroot00000000000000fwupd-1.3.9/libfwupdplugin/fu-archive.c000066400000000000000000000134261362775233600201250ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuArchive" #include "config.h" #include #include #include #include "fu-archive.h" /** * SECTION:fu-archive * @title: FuArchive * @short_description: an in-memory archive decompressor */ struct _FuArchive { GObject parent_instance; GHashTable *entries; }; G_DEFINE_TYPE (FuArchive, fu_archive, G_TYPE_OBJECT) static void fu_archive_finalize (GObject *obj) { FuArchive *self = FU_ARCHIVE (obj); g_hash_table_unref (self->entries); G_OBJECT_CLASS (fu_archive_parent_class)->finalize (obj); } static void fu_archive_class_init (FuArchiveClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_archive_finalize; } static void fu_archive_init (FuArchive *self) { self->entries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_bytes_unref); } /** * fu_archive_lookup_by_fn: * @self: A #FuArchive * @fn: A filename * @error: A #GError, or %NULL * * Finds the blob referenced by filename * * Returns: (transfer none): a #GBytes, or %NULL if the filename was not found * * Since: 1.2.2 **/ GBytes * fu_archive_lookup_by_fn (FuArchive *self, const gchar *fn, GError **error) { GBytes *fw; g_return_val_if_fail (FU_IS_ARCHIVE (self), NULL); g_return_val_if_fail (fn != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); fw = g_hash_table_lookup (self->entries, fn); if (fw == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no blob for %s", fn); } return fw; } /** * fu_archive_iterate: * @self: A #FuArchive * @callback: (scope call): A #FuArchiveIterateFunc. * @user_data: User data. * @error: A #GError, or %NULL * * Iterates over the archive contents, calling the given function for each * of the files found. If any @callback returns %FALSE scanning is aborted. * * Returns: True if no @callback returned FALSE * * Since: 1.3.4 */ gboolean fu_archive_iterate (FuArchive *self, FuArchiveIterateFunc callback, gpointer user_data, GError **error) { GHashTableIter iter; gpointer key, value; g_return_val_if_fail (FU_IS_ARCHIVE (self), FALSE); g_return_val_if_fail (callback != NULL, FALSE); g_hash_table_iter_init (&iter, self->entries); while (g_hash_table_iter_next (&iter, &key, &value)) { if (!callback (self, (const gchar *)key, (GBytes *)value, user_data, error)) return FALSE; } return TRUE; } /* workaround the struct types of libarchive */ typedef struct archive _archive_read_ctx; static void _archive_read_ctx_free (_archive_read_ctx *arch) { archive_read_close (arch); archive_read_free (arch); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(_archive_read_ctx, _archive_read_ctx_free) static gboolean fu_archive_load (FuArchive *self, GBytes *blob, FuArchiveFlags flags, GError **error) { int r; g_autoptr(_archive_read_ctx) arch = NULL; /* decompress anything matching either glob */ arch = archive_read_new (); if (arch == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "libarchive startup failed"); return FALSE; } archive_read_support_format_all (arch); archive_read_support_filter_all (arch); r = archive_read_open_memory (arch, (void *) g_bytes_get_data (blob, NULL), (size_t) g_bytes_get_size (blob)); if (r != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot open: %s", archive_error_string (arch)); return FALSE; } while (TRUE) { const gchar *fn; gint64 bufsz; gssize rc; struct archive_entry *entry; g_autofree gchar *fn_key = NULL; g_autofree guint8 *buf = NULL; r = archive_read_next_header (arch, &entry); if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot read header: %s", archive_error_string (arch)); return FALSE; } /* only extract if valid */ fn = archive_entry_pathname (entry); if (fn == NULL) continue; bufsz = archive_entry_size (entry); if (bufsz > 1024 * 1024 * 1024) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot read huge files"); return FALSE; } buf = g_malloc (bufsz); rc = archive_read_data (arch, buf, (gsize) bufsz); if (rc < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot read data: %s", archive_error_string (arch)); return FALSE; } if (rc != bufsz) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "read %" G_GSSIZE_FORMAT " of %" G_GINT64_FORMAT, rc, bufsz); return FALSE; } if (flags & FU_ARCHIVE_FLAG_IGNORE_PATH) { fn_key = g_path_get_basename (fn); } else { fn_key = g_strdup (fn); } g_debug ("adding %s [%" G_GINT64_FORMAT "]", fn_key, bufsz); g_hash_table_insert (self->entries, g_steal_pointer (&fn_key), g_bytes_new_take (g_steal_pointer (&buf), bufsz)); } /* success */ return TRUE; } /** * fu_archive_new: * @data: A #GBytes * @flags: A #FuArchiveFlags, e.g. %FU_ARCHIVE_FLAG_NONE * @error: A #GError, or %NULL * * Parses @data as an archive and decompresses all files to memory blobs. * * Returns: a #FuArchive, or %NULL if the archive was invalid in any way. * * Since: 1.2.2 **/ FuArchive * fu_archive_new (GBytes *data, FuArchiveFlags flags, GError **error) { g_autoptr(FuArchive) self = g_object_new (FU_TYPE_ARCHIVE, NULL); g_return_val_if_fail (data != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (!fu_archive_load (self, data, flags, error)) return NULL; return g_steal_pointer (&self); } fwupd-1.3.9/libfwupdplugin/fu-archive.h000066400000000000000000000024271362775233600201310ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ARCHIVE (fu_archive_get_type ()) G_DECLARE_FINAL_TYPE (FuArchive, fu_archive, FU, ARCHIVE, GObject) /** * FuArchiveFlags: * @FU_ARCHIVE_FLAG_NONE: No flags set * @FU_ARCHIVE_FLAG_IGNORE_PATH: Ignore any path component * * The flags to use when loading the archive. **/ typedef enum { FU_ARCHIVE_FLAG_NONE = 0, FU_ARCHIVE_FLAG_IGNORE_PATH = 1 << 0, /*< private >*/ FU_ARCHIVE_FLAG_LAST } FuArchiveFlags; /** * FuArchiveIterateFunc: * @self: A #FuArchive. * @filename: A filename. * @bytes: The blob referenced by @filename. * @user_data: User data. * * Specifies the type of archive iteration function. */ typedef gboolean (*FuArchiveIterateFunc) (FuArchive *self, const gchar *filename, GBytes *bytes, gpointer user_data, GError **error); FuArchive *fu_archive_new (GBytes *data, FuArchiveFlags flags, GError **error); GBytes *fu_archive_lookup_by_fn (FuArchive *self, const gchar *fn, GError **error); gboolean fu_archive_iterate (FuArchive *self, FuArchiveIterateFunc callback, gpointer user_data, GError **error); fwupd-1.3.9/libfwupdplugin/fu-chunk.c000066400000000000000000000123131362775233600176060ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuChunk" #include "config.h" #include #include "fu-chunk.h" /** * SECTION:fu-chunk * @short_description: A packet of chunked data * * An object that represents a packet of data. * */ /** * fu_chunk_new: (skip): * @idx: the packet number * @page: the hardware memory page * @address: the address *within* the page * @data: the data * @data_sz: size of @data_sz * * Creates a new packet of chunked data. * * Return value: (transfer full): a #FuChunk * * Since: 1.1.2 **/ FuChunk * fu_chunk_new (guint32 idx, guint32 page, guint32 address, const guint8 *data, guint32 data_sz) { FuChunk *item = g_new0 (FuChunk, 1); item->idx = idx; item->page = page; item->address = address; item->data = data; item->data_sz = data_sz; return item; } /** * fu_chunk_to_string: * @item: a #FuChunk * * Converts the chunked packet to a string representation. * * Return value: (transfer full): A string * * Since: 1.1.2 **/ gchar * fu_chunk_to_string (FuChunk *item) { g_autoptr(GString) str = g_string_new (NULL); if (item->data != NULL) { for (guint32 i = 0; i < item->data_sz; i++) { gchar tmp = (gchar) item->data[i]; if (tmp == 0x00) break; g_string_append_c (str, g_ascii_isalnum (tmp) ? tmp : '?'); } } return g_strdup_printf ("#%02" G_GUINT32_FORMAT ": page:%02x " "addr:%04x len:%02" G_GUINT32_FORMAT " %s", item->idx, (guint) item->page, (guint) item->address, item->data_sz, str->str); } /** * fu_chunk_array_to_string: * @chunks: (element-type FuChunk): array of packets * * Converts all the chunked packets in an array to a string representation. * * Return value: (transfer full): A string * * Since: 1.0.1 **/ gchar * fu_chunk_array_to_string (GPtrArray *chunks) { GString *str = g_string_new (NULL); for (guint i = 0; i < chunks->len; i++) { FuChunk *item = g_ptr_array_index (chunks, i); g_autofree gchar *tmp = fu_chunk_to_string (item); g_string_append_printf (str, "%s\n", tmp); } return g_string_free (str, FALSE); } /** * fu_chunk_array_new: (skip): * @data: a linear blob of memory, or %NULL * @data_sz: size of @data_sz * @addr_start: the hardware address offset, or 0 * @page_sz: the hardware page size, or 0 * @packet_sz: the transfer size, or 0 * * Chunks a linear blob of memory into packets, ensuring each packet does not * cross a package boundary and is less that a specific transfer size. * * Return value: (transfer container) (element-type FuChunk): array of packets * * Since: 1.1.2 **/ GPtrArray * fu_chunk_array_new (const guint8 *data, guint32 data_sz, guint32 addr_start, guint32 page_sz, guint32 packet_sz) { GPtrArray *segments = NULL; guint32 page_old = G_MAXUINT32; guint32 idx; guint32 last_flush = 0; g_return_val_if_fail (data_sz > 0, NULL); segments = g_ptr_array_new_with_free_func (g_free); for (idx = 1; idx < data_sz; idx++) { guint32 page = 0; if (page_sz > 0) page = (addr_start + idx) / page_sz; if (page_old == G_MAXUINT32) { page_old = page; } else if (page != page_old) { const guint8 *data_offset = data != NULL ? data + last_flush : 0x0; guint32 address_offset = addr_start + last_flush; if (page_sz > 0) address_offset %= page_sz; g_ptr_array_add (segments, fu_chunk_new (segments->len, page_old, address_offset, data_offset, idx - last_flush)); last_flush = idx; page_old = page; continue; } if (packet_sz > 0 && idx - last_flush >= packet_sz) { const guint8 *data_offset = data != NULL ? data + last_flush : 0x0; guint32 address_offset = addr_start + last_flush; if (page_sz > 0) address_offset %= page_sz; g_ptr_array_add (segments, fu_chunk_new (segments->len, page, address_offset, data_offset, idx - last_flush)); last_flush = idx; continue; } } if (last_flush != idx) { const guint8 *data_offset = data != NULL ? data + last_flush : 0x0; guint32 address_offset = addr_start + last_flush; guint32 page = 0; if (page_sz > 0) { address_offset %= page_sz; page = (addr_start + (idx - 1)) / page_sz; } g_ptr_array_add (segments, fu_chunk_new (segments->len, page, address_offset, data_offset, data_sz - last_flush)); } return segments; } /** * fu_chunk_array_new_from_bytes: (skip): * @blob: a #GBytes * @addr_start: the hardware address offset, or 0 * @page_sz: the hardware page size, or 0 * @packet_sz: the transfer size, or 0 * * Chunks a linear blob of memory into packets, ensuring each packet does not * cross a package boundary and is less that a specific transfer size. * * Return value: (transfer container) (element-type FuChunk): array of packets * * Since: 1.1.2 **/ GPtrArray * fu_chunk_array_new_from_bytes (GBytes *blob, guint32 addr_start, guint32 page_sz, guint32 packet_sz) { gsize sz; const guint8 *data = g_bytes_get_data (blob, &sz); return fu_chunk_array_new (data, (guint32) sz, addr_start, page_sz, packet_sz); } fwupd-1.3.9/libfwupdplugin/fu-chunk.h000066400000000000000000000015071362775233600176160ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include typedef struct { guint32 idx; guint32 page; guint32 address; const guint8 *data; guint32 data_sz; } FuChunk; FuChunk *fu_chunk_new (guint32 idx, guint32 page, guint32 address, const guint8 *data, guint32 data_sz); gchar *fu_chunk_to_string (FuChunk *item); gchar *fu_chunk_array_to_string (GPtrArray *chunks); GPtrArray *fu_chunk_array_new (const guint8 *data, guint32 data_sz, guint32 addr_start, guint32 page_sz, guint32 packet_sz); GPtrArray *fu_chunk_array_new_from_bytes (GBytes *blob, guint32 addr_start, guint32 page_sz, guint32 packet_sz); fwupd-1.3.9/libfwupdplugin/fu-common-cab.c000066400000000000000000000364151362775233600205220ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommonCab" #include "config.h" #include #include "fu-common-cab.h" #include "fu-common.h" #include "fwupd-error.h" #ifndef HAVE_GCAB_1_0 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GCabCabinet, g_object_unref) #endif static GCabFile * _gcab_cabinet_get_file_by_name (GCabCabinet *cabinet, const gchar *basename) { GPtrArray *folders = gcab_cabinet_get_folders (cabinet); for (guint i = 0; i < folders->len; i++) { GCabFolder *cabfolder = GCAB_FOLDER (g_ptr_array_index (folders, i)); #ifdef HAVE_GCAB_1_0 GCabFile *cabfile = gcab_folder_get_file_by_name (cabfolder, basename); if (cabfile != NULL) return cabfile; #else g_autoptr(GSList) files = gcab_folder_get_files (cabfolder); for (GSList *l = files; l != NULL; l = l->next) { GCabFile *cabfile = GCAB_FILE (l->data); if (g_strcmp0 (gcab_file_get_extract_name (cabfile), basename) == 0) return cabfile; } #endif } return NULL; } #ifndef HAVE_GCAB_1_0 static GBytes * _gcab_file_get_bytes (GCabFile *cabfile) { GBytes *blob = NULL; g_autofree gchar *fn = NULL; g_autoptr(GError) error_local = NULL; fn = g_build_filename (g_object_get_data (G_OBJECT (cabfile), "fwupd::DecompressPath"), gcab_file_get_extract_name (cabfile), NULL); blob = fu_common_get_contents_bytes (fn, &error_local); if (blob == NULL) { g_warning ("failed to read temp file: %s", error_local->message); return NULL; } return blob; } #endif /* sets the firmware and signature blobs on XbNode */ static gboolean fu_common_store_from_cab_release (XbNode *release, GCabCabinet *cabinet, GError **error) { GCabFile *cabfile; GBytes *blob; const gchar *csum_filename = NULL; const gchar *suffixes[] = { "asc", "p7b", "p7c", NULL }; g_autofree gchar *basename = NULL; g_autofree gchar *release_key = NULL; g_autoptr(XbNode) csum_tmp = NULL; g_autoptr(XbNode) nsize = NULL; /* ensure we always have a content checksum */ csum_tmp = xb_node_query_first (release, "checksum[@target='content']", NULL); if (csum_tmp != NULL) csum_filename = xb_node_get_attr (csum_tmp, "filename"); /* if this isn't true, a firmware needs to set in the metainfo.xml file * something like: */ if (csum_filename == NULL) csum_filename = "firmware.bin"; /* get the main firmware file */ basename = g_path_get_basename (csum_filename); cabfile = _gcab_cabinet_get_file_by_name (cabinet, basename); if (cabfile == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot find %s in archive", basename); return FALSE; } #ifdef HAVE_GCAB_1_0 blob = gcab_file_get_bytes (cabfile); #else blob = _gcab_file_get_bytes (cabfile); #endif if (blob == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no GBytes from GCabFile firmware"); return FALSE; } /* set the blob */ release_key = g_strdup_printf ("fwupd::ReleaseBlob(%s)", basename); xb_node_set_data (release, release_key, blob); /* set as metadata if unset, but error if specified and incorrect */ nsize = xb_node_query_first (release, "size[@type='installed']", NULL); if (nsize != NULL) { guint64 size = fu_common_strtoull (xb_node_get_text (nsize)); if (size != g_bytes_get_size (blob)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "contents size invalid, expected " "%" G_GSIZE_FORMAT ", got %" G_GUINT64_FORMAT, g_bytes_get_size (blob), size); return FALSE; } } else { guint64 size = g_bytes_get_size (blob); g_autoptr(GBytes) blob_sz = g_bytes_new (&size, sizeof(guint64)); xb_node_set_data (release, "fwupd::ReleaseSize", blob_sz); } /* set if unspecified, but error out if specified and incorrect */ if (csum_tmp != NULL && xb_node_get_text (csum_tmp) != NULL) { g_autofree gchar *checksum = NULL; checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, blob); if (g_strcmp0 (checksum, xb_node_get_text (csum_tmp)) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "contents checksum invalid, expected %s, got %s", checksum, xb_node_get_text (csum_tmp)); return FALSE; } } /* if the signing file exists, set that too */ for (guint i = 0; suffixes[i] != NULL; i++) { g_autofree gchar *basename_sig = NULL; basename_sig = g_strdup_printf ("%s.%s", basename, suffixes[i]); cabfile = _gcab_cabinet_get_file_by_name (cabinet, basename_sig); if (cabfile != NULL) { g_autofree gchar *release_key_sig = NULL; #ifdef HAVE_GCAB_1_0 blob = gcab_file_get_bytes (cabfile); #else blob = _gcab_file_get_bytes (cabfile); #endif if (blob == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no GBytes from GCabFile %s", basename_sig); return FALSE; } release_key_sig = g_strdup_printf ("fwupd::ReleaseBlob(%s)", basename_sig); xb_node_set_data (release, release_key_sig, blob); } } /* success */ return TRUE; } /* adds each GCabFile to the silo */ static gboolean fu_common_store_from_cab_file (XbBuilder *builder, GCabCabinet *cabinet, GCabFile *cabfile, GError **error) { GBytes *blob; g_autoptr(GError) error_local = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new (); /* rewrite to be under a components root */ xb_builder_source_set_prefix (source, "components"); /* parse file */ #ifdef HAVE_GCAB_1_0 blob = gcab_file_get_bytes (cabfile); #else blob = _gcab_file_get_bytes (cabfile); #endif if (blob == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no GBytes from GCabFile"); return FALSE; } if (!xb_builder_source_load_xml (source, g_bytes_get_data (blob, NULL), XB_BUILDER_SOURCE_FLAG_NONE, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "could not parse MetaInfo XML: %s", error_local->message); return FALSE; } xb_builder_import_source (builder, source); /* success */ return TRUE; } /* adds each GCabFolder to the silo */ static gboolean fu_common_store_from_cab_folder (XbBuilder *builder, GCabCabinet *cabinet, GCabFolder *cabfolder, GError **error) { g_autoptr(GSList) cabfiles = gcab_folder_get_files (cabfolder); for (GSList *l = cabfiles; l != NULL; l = l->next) { GCabFile *cabfile = GCAB_FILE (l->data); const gchar *fn = gcab_file_get_extract_name (cabfile); g_debug ("processing file: %s", fn); if (g_str_has_suffix (fn, ".metainfo.xml")) { if (!fu_common_store_from_cab_file (builder, cabinet, cabfile, error)) { g_prefix_error (error, "%s could not be loaded: ", gcab_file_get_extract_name (cabfile)); return FALSE; } } } return TRUE; } typedef struct { guint64 size_total; guint64 size_max; const gchar *decompress_path; GError *error; } FuCommonCabHelper; static gboolean fu_common_store_file_cb (GCabFile *file, gpointer user_data) { FuCommonCabHelper *helper = (FuCommonCabHelper *) user_data; g_autofree gchar *basename = NULL; g_autofree gchar *name = NULL; /* already failed */ if (helper->error != NULL) return FALSE; /* check the size of the compressed file */ if (gcab_file_get_size (file) > helper->size_max) { g_autofree gchar *sz_val = g_format_size (gcab_file_get_size (file)); g_autofree gchar *sz_max = g_format_size (helper->size_max); g_set_error (&helper->error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "file %s was too large (%s, limit %s)", gcab_file_get_name (file), sz_val, sz_max); return FALSE; } /* check the total size of all the compressed files */ helper->size_total += gcab_file_get_size (file); if (helper->size_total > helper->size_max) { g_autofree gchar *sz_val = g_format_size (helper->size_total); g_autofree gchar *sz_max = g_format_size (helper->size_max); g_set_error (&helper->error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "uncompressed data too large (%s, limit %s)", sz_val, sz_max); return FALSE; } /* convert to UNIX paths */ name = g_strdup (gcab_file_get_name (file)); g_strdelimit (name, "\\", '/'); /* ignore the dirname completely */ basename = g_path_get_basename (name); gcab_file_set_extract_name (file, basename); #ifndef HAVE_GCAB_1_0 /* set this for old versions of GCab */ g_object_set_data_full (G_OBJECT (file), "fwupd::DecompressPath", g_strdup (helper->decompress_path), g_free); #endif return TRUE; } static gint fu_common_cab_sort_cb (XbBuilderNode *bn1, XbBuilderNode *bn2, gpointer user_data) { guint64 prio1 = xb_builder_node_get_attr_as_uint (bn1, "priority"); guint64 prio2 = xb_builder_node_get_attr_as_uint (bn2, "priority"); if (prio1 > prio2) return -1; if (prio1 < prio2) return 1; return 0; } static gboolean fu_common_cab_sort_priority_cb (XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { xb_builder_node_sort_children (bn, fu_common_cab_sort_cb, user_data); return TRUE; } static XbBuilderNode * _xb_builder_node_get_child_by_element_attr (XbBuilderNode *bn, const gchar *element, const gchar *attr_name, const gchar *attr_value) { GPtrArray *bcs = xb_builder_node_get_children (bn); for (guint i = 0; i < bcs->len; i++) { XbBuilderNode *bc = g_ptr_array_index (bcs, i); if (g_strcmp0 (xb_builder_node_get_element (bc), element) != 0) continue; if (g_strcmp0 (xb_builder_node_get_attr (bc, "type"), "container") == 0) return g_object_ref (bc); } return NULL; } static gboolean fu_common_cab_set_container_checksum_cb (XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { const gchar *container_checksum = (const gchar *) user_data; g_autoptr(XbBuilderNode) csum = NULL; /* not us */ if (g_strcmp0 (xb_builder_node_get_element (bn), "release") != 0) return TRUE; /* verify it exists */ csum = _xb_builder_node_get_child_by_element_attr (bn, "checksum", "target", "container"); if (csum == NULL) { csum = xb_builder_node_insert (bn, "checksum", "target", "container", NULL); } /* verify it is correct */ if (g_strcmp0 (xb_builder_node_get_text (csum), container_checksum) != 0) { g_debug ("invalid container checksum %s, fixing up to %s", xb_builder_node_get_text (csum), container_checksum); xb_builder_node_set_text (csum, container_checksum, -1); } return TRUE; } /** * fu_common_cab_build_silo: (skip): * @blob: A readable blob * @size_max: The maximum size of the archive * @error: A #FuEndianType, e.g. %G_LITTLE_ENDIAN * * Create an AppStream silo from a cabinet archive. * * Returns: a #XbSilo, or %NULL on error * * Since: 1.2.0 **/ XbSilo * fu_common_cab_build_silo (GBytes *blob, guint64 size_max, GError **error) { FuCommonCabHelper helper = { .size_total = 0, .size_max = size_max, .error = NULL, }; GPtrArray *folders; g_autofree gchar *container_checksum = NULL; #ifndef HAVE_GCAB_1_0 g_autofree gchar *tmp_path = NULL; g_autoptr(GFile) tmp_file = NULL; #endif g_autoptr(XbSilo) silo = NULL; g_autoptr(XbBuilder) builder = xb_builder_new (); g_autoptr(XbBuilderFixup) fixup = NULL; g_autoptr(XbBuilderFixup) fixup2 = NULL; g_autoptr(GCabCabinet) cabinet = gcab_cabinet_new (); g_autoptr(GError) error_local = NULL; g_autoptr(GInputStream) ip = NULL; g_autoptr(GPtrArray) components = NULL; /* sort the components by priority */ fixup = xb_builder_fixup_new ("OrderByPriority", fu_common_cab_sort_priority_cb, NULL, NULL); xb_builder_fixup_set_max_depth (fixup, 0); xb_builder_add_fixup (builder, fixup); /* load from a seekable stream */ ip = g_memory_input_stream_new_from_bytes (blob); if (!gcab_cabinet_load (cabinet, ip, NULL, error)) return NULL; #ifdef HAVE_GCAB_1_0 /* check the size is sane */ if (gcab_cabinet_get_size (cabinet) > size_max) { g_autofree gchar *sz_val = g_format_size (gcab_cabinet_get_size (cabinet)); g_autofree gchar *sz_max = g_format_size (size_max); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "archive too large (%s, limit %s)", sz_val, sz_max); return NULL; } /* decompress the file to memory */ if (!gcab_cabinet_extract_simple (cabinet, NULL, fu_common_store_file_cb, &helper, NULL, &error_local)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, error_local->message); return NULL; } #else /* decompress to /tmp */ tmp_path = g_dir_make_tmp ("fwupd-XXXXXX", &error_local); if (tmp_path == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to create temp dir: %s", error_local->message); return NULL; } helper.decompress_path = tmp_path; tmp_file = g_file_new_for_path (tmp_path); if (!gcab_cabinet_extract_simple (cabinet, tmp_file, fu_common_store_file_cb, &helper, NULL, &error_local)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, error_local->message); return NULL; } #endif /* the file callback set an error */ if (helper.error != NULL) { g_propagate_error (error, helper.error); return NULL; } /* verbose profiling */ if (g_getenv ("FWUPD_VERBOSE") != NULL) { xb_builder_set_profile_flags (builder, XB_SILO_PROFILE_FLAG_XPATH | XB_SILO_PROFILE_FLAG_DEBUG); } /* look at each folder */ folders = gcab_cabinet_get_folders (cabinet); for (guint i = 0; i < folders->len; i++) { GCabFolder *cabfolder = GCAB_FOLDER (g_ptr_array_index (folders, i)); g_debug ("processing folder: %u/%u", i + 1, folders->len); if (!fu_common_store_from_cab_folder (builder, cabinet, cabfolder, error)) return NULL; } /* ensure the container checksum is always set */ container_checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, blob); fixup2 = xb_builder_fixup_new ("SetContainerChecksum", fu_common_cab_set_container_checksum_cb, container_checksum, NULL); xb_builder_add_fixup (builder, fixup2); /* did we get any valid files */ silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, 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, "archive contained no valid metadata: %s", error_local->message); return NULL; } /* process each listed release */ for (guint i = 0; i < components->len; i++) { XbNode *component = g_ptr_array_index (components, i); g_autoptr(GPtrArray) releases = NULL; releases = xb_node_query (component, "releases/release", 0, &error_local); if (releases == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no releases in metainfo file: %s", error_local->message); return NULL; } for (guint j = 0; j < releases->len; j++) { XbNode *rel = g_ptr_array_index (releases, j); g_debug ("processing release: %s", xb_node_get_attr (rel, "version")); if (!fu_common_store_from_cab_release (rel, cabinet, error)) return NULL; } } /* success */ return g_steal_pointer (&silo); } fwupd-1.3.9/libfwupdplugin/fu-common-cab.h000066400000000000000000000003651362775233600205220ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include XbSilo *fu_common_cab_build_silo (GBytes *blob, guint64 size_max, GError **error); fwupd-1.3.9/libfwupdplugin/fu-common-guid.c000066400000000000000000000012051362775233600207120ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include #include "fu-common-guid.h" /** * fu_common_guid_is_plausible: * @buf: a buffer of data * * Checks whether a chunk of memory looks like it could be a GUID. * * Returns: TRUE if it looks like a GUID, FALSE if not * * Since: 1.2.5 **/ gboolean fu_common_guid_is_plausible (const guint8 *buf) { guint guint_sum = 0; for (guint i = 0; i < 16; i++) guint_sum += buf[i]; if (guint_sum == 0x00) return FALSE; if (guint_sum < 0xff) return FALSE; return TRUE; } fwupd-1.3.9/libfwupdplugin/fu-common-guid.h000066400000000000000000000003201362775233600207140ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gboolean fu_common_guid_is_plausible (const guint8 *buf); fwupd-1.3.9/libfwupdplugin/fu-common-version.c000066400000000000000000000330401362775233600214510ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include #include #include "fwupd-enums.h" #include "fwupd-error.h" #include "fu-common-version.h" #define FU_COMMON_VERSION_DECODE_BCD(val) ((((val) >> 4) & 0x0f) * 10 + ((val) & 0x0f)) /** * fu_common_version_from_uint64: * @val: A raw version number * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_QUAD * * Returns a dotted decimal version string from a 64 bit number. * * Returns: A version number, e.g. "1.2.3.4", or %NULL if not supported * * Since: 1.3.6 **/ gchar * fu_common_version_from_uint64 (guint64 val, FwupdVersionFormat kind) { if (kind == FWUPD_VERSION_FORMAT_QUAD) { /* AABB.CCDD.EEFF.GGHH */ return g_strdup_printf ("%" G_GUINT64_FORMAT "." "%" G_GUINT64_FORMAT "." "%" G_GUINT64_FORMAT "." "%" G_GUINT64_FORMAT "", (val >> 48) & 0xffff, (val >> 32) & 0xffff, (val >> 16) & 0xffff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_PAIR) { /* AABBCCDD.EEFFGGHH */ return g_strdup_printf ("%" G_GUINT64_FORMAT ".%" G_GUINT64_FORMAT "", (val >> 32) & 0xffffffff, val & 0xffffffff); } if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN) { /* AABBCCDD */ return g_strdup_printf ("%" G_GUINT64_FORMAT, val); } g_critical ("failed to convert version format %s: %" G_GUINT64_FORMAT "", fwupd_version_format_to_string (kind), val); return NULL; } /** * fu_common_version_from_uint32: * @val: A uint32le version number * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Returns a dotted decimal version string from a 32 bit number. * * Returns: A version number, e.g. "1.0.3", or %NULL if not supported * * Since: 1.2.0 **/ gchar * fu_common_version_from_uint32 (guint32 val, FwupdVersionFormat kind) { if (kind == FWUPD_VERSION_FORMAT_QUAD) { /* AA.BB.CC.DD */ return g_strdup_printf ("%u.%u.%u.%u", (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff); } if (kind == FWUPD_VERSION_FORMAT_TRIPLET) { /* AA.BB.CCDD */ return g_strdup_printf ("%u.%u.%u", (val >> 24) & 0xff, (val >> 16) & 0xff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_PAIR) { /* AABB.CCDD */ return g_strdup_printf ("%u.%u", (val >> 16) & 0xffff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN) { /* AABBCCDD */ return g_strdup_printf ("%" G_GUINT32_FORMAT, val); } if (kind == FWUPD_VERSION_FORMAT_BCD) { /* AA.BB.CC.DD, but BCD */ return g_strdup_printf ("%u.%u.%u.%u", FU_COMMON_VERSION_DECODE_BCD(val >> 24), FU_COMMON_VERSION_DECODE_BCD(val >> 16), FU_COMMON_VERSION_DECODE_BCD(val >> 8), FU_COMMON_VERSION_DECODE_BCD(val)); } if (kind == FWUPD_VERSION_FORMAT_INTEL_ME) { /* aaa+11.bbbbb.cccccccc.dddddddddddddddd */ return g_strdup_printf ("%u.%u.%u.%u", ((val >> 29) & 0x07) + 0x0b, (val >> 24) & 0x1f, (val >> 16) & 0xff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_INTEL_ME2) { /* A.B.CC.DDDD */ return g_strdup_printf ("%u.%u.%u.%u", (val >> 28) & 0x0f, (val >> 24) & 0x0f, (val >> 16) & 0xff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_SURFACE_LEGACY) { /* 10b.12b.10b */ return g_strdup_printf ("%u.%u.%u", (val >> 22) & 0x3ff, (val >> 10) & 0xfff, val & 0x3ff); } if (kind == FWUPD_VERSION_FORMAT_SURFACE) { /* 8b.16b.8b */ return g_strdup_printf ("%u.%u.%u", (val >> 24) & 0xff, (val >> 8) & 0xffff, val & 0xff); } if (kind == FWUPD_VERSION_FORMAT_DELL_BIOS) { /* BB.CC.DD */ return g_strdup_printf ("%u.%u.%u", (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff); } g_critical ("failed to convert version format %s: %u", fwupd_version_format_to_string (kind), val); return NULL; } /** * fu_common_version_from_uint16: * @val: A uint16le version number * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Returns a dotted decimal version string from a 16 bit number. * * Returns: A version number, e.g. "1.3", or %NULL if not supported * * Since: 1.2.0 **/ gchar * fu_common_version_from_uint16 (guint16 val, FwupdVersionFormat kind) { if (kind == FWUPD_VERSION_FORMAT_BCD) { return g_strdup_printf ("%i.%i", FU_COMMON_VERSION_DECODE_BCD(val >> 8), FU_COMMON_VERSION_DECODE_BCD(val)); } if (kind == FWUPD_VERSION_FORMAT_PAIR) { return g_strdup_printf ("%u.%u", (guint) (val >> 8) & 0xff, (guint) val & 0xff); } if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN) { return g_strdup_printf ("%" G_GUINT16_FORMAT, val); } g_critical ("failed to convert version format %s: %u", fwupd_version_format_to_string (kind), val); return NULL; } static gint fu_common_vercmp_char (gchar chr1, gchar chr2) { if (chr1 == chr2) return 0; if (chr1 == '~') return -1; if (chr2 == '~') return 1; return chr1 < chr2 ? -1 : 1; } static gint fu_common_vercmp_chunk (const gchar *str1, const gchar *str2) { guint i; /* trivial */ if (g_strcmp0 (str1, str2) == 0) return 0; if (str1 == NULL) return 1; if (str2 == NULL) return -1; /* check each char of the chunk */ for (i = 0; str1[i] != '\0' && str2[i] != '\0'; i++) { gint rc = fu_common_vercmp_char (str1[i], str2[i]); if (rc != 0) return rc; } return fu_common_vercmp_char (str1[i], str2[i]); } static gboolean _g_ascii_is_digits (const gchar *str) { g_return_val_if_fail (str != NULL, FALSE); for (gsize i = 0; str[i] != '\0'; i++) { if (!g_ascii_isdigit (str[i])) return FALSE; } return TRUE; } static gboolean fu_common_version_is_valid_semver_char (gchar c) { if (g_ascii_isdigit (c)) return TRUE; if (c == '.') return TRUE; return FALSE; } /** * fu_common_version_ensure_semver: * @version: A version number, e.g. ` V1.2.3 ` * * Builds a semver from the possibly crazy version number. * * Returns: A version number, e.g. "1.2.3" * * Since: 1.2.9 */ gchar * fu_common_version_ensure_semver (const gchar *version) { GString *version_safe = g_string_new (NULL); for (guint i = 0; version[i] != '\0'; i++) { if (fu_common_version_is_valid_semver_char (version[i])) g_string_append_c (version_safe, version[i]); } return g_string_free (version_safe, FALSE); } /** * fu_common_version_parse: * @version: A version number * * Returns a dotted decimal version string from a version string. The supported * formats are: * * - Dotted decimal, e.g. "1.2.3" * - Base 16, a hex number *with* a 0x prefix, e.g. "0x10203" * - Base 10, a string containing just [0-9], e.g. "66051" * - Date in YYYYMMDD format, e.g. 20150915 * * Anything with a '.' or that doesn't match [0-9] or 0x[a-f,0-9] is considered * a string and returned without modification. * * Returns: A version number, e.g. "1.0.3" * * Since: 1.2.0 */ gchar * fu_common_version_parse (const gchar *version) { return fu_common_version_parse_from_format (version, FWUPD_VERSION_FORMAT_TRIPLET); } /** * fu_common_version_parse_from_format * @version: A version number * @fmt: A FwupdVersionFormat, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Returns a dotted decimal version string from a version string using fmt. * The supported formats are: * * - Dotted decimal, e.g. "1.2.3" * - Base 16, a hex number *with* a 0x prefix, e.g. "0x10203" * - Base 10, a string containing just [0-9], e.g. "66051" * - Date in YYYYMMDD format, e.g. 20150915 * * Anything with a '.' or that doesn't match [0-9] or 0x[a-f,0-9] is considered * a string and returned without modification. * * Returns: A version number, e.g. "1.0.3" * * Since: 1.3.3 */ gchar * fu_common_version_parse_from_format (const gchar *version, FwupdVersionFormat fmt) { const gchar *version_noprefix = version; gchar *endptr = NULL; guint64 tmp; guint base; /* already dotted decimal */ if (g_strstr_len (version, -1, ".") != NULL) return g_strdup (version); /* is a date */ if (g_str_has_prefix (version, "20") && strlen (version) == 8) return g_strdup (version); /* convert 0x prefixed strings to dotted decimal */ if (g_str_has_prefix (version, "0x")) { version_noprefix += 2; base = 16; } else { /* for non-numeric content, just return the string */ if (!_g_ascii_is_digits (version)) return g_strdup (version); base = 10; } /* convert */ tmp = g_ascii_strtoull (version_noprefix, &endptr, base); if (endptr != NULL && endptr[0] != '\0') return g_strdup (version); if (tmp == 0) return g_strdup (version); return fu_common_version_from_uint32 ((guint32) tmp, fmt); } /** * fu_common_version_guess_format: * @version: A version number, e.g. "1.2.3" * * Guesses the version format from the version number. This is only a heuristic * and plugins and components should explicitly set the version format whenever * possible. * * If the version format cannot be guessed with any degree of accuracy, the * %FWUPD_VERSION_FORMAT_UNKNOWN constant is returned. * * Returns: A #FwupdVersionFormat, e.g. %FWUPD_VERSION_FORMAT_QUAD * * Since: 1.2.0 */ FwupdVersionFormat fu_common_version_guess_format (const gchar *version) { guint sz; g_auto(GStrv) split = NULL; /* nothing to use */ if (version == NULL || version[0] == '\0') return FWUPD_VERSION_FORMAT_UNKNOWN; /* no dots, assume just text */ split = g_strsplit (version, ".", -1); sz = g_strv_length (split); if (sz == 1) { if (g_str_has_prefix (version, "0x")) version += 2; if (_g_ascii_is_digits (version)) return FWUPD_VERSION_FORMAT_NUMBER; return FWUPD_VERSION_FORMAT_PLAIN; } /* check for only-digit semver version */ for (guint i = 0; split[i] != NULL; i++) { /* check sections are plain numbers */ if (!_g_ascii_is_digits (split[i])) return FWUPD_VERSION_FORMAT_PLAIN; } /* the most common formats */ if (sz == 2) return FWUPD_VERSION_FORMAT_PAIR; if (sz == 3) return FWUPD_VERSION_FORMAT_TRIPLET; if (sz == 4) return FWUPD_VERSION_FORMAT_QUAD; /* unknown! */ return FWUPD_VERSION_FORMAT_UNKNOWN; } static FwupdVersionFormat fu_common_version_convert_base (FwupdVersionFormat fmt) { if (fmt == FWUPD_VERSION_FORMAT_INTEL_ME || fmt == FWUPD_VERSION_FORMAT_INTEL_ME2) return FWUPD_VERSION_FORMAT_QUAD; if (fmt == FWUPD_VERSION_FORMAT_DELL_BIOS) return FWUPD_VERSION_FORMAT_TRIPLET; if (fmt == FWUPD_VERSION_FORMAT_BCD) return FWUPD_VERSION_FORMAT_PAIR; return fmt; } /** * fu_common_version_verify_format: * @version: A string, e.g. "0x1234" * @fmt: a #FwupdVersionFormat * @error: A #GError or %NULL * * Verifies if a version matches the input format. * * Returns: TRUE or FALSE * * Since: 1.2.9 **/ gboolean fu_common_version_verify_format (const gchar *version, FwupdVersionFormat fmt, GError **error) { FwupdVersionFormat fmt_base = fu_common_version_convert_base (fmt); /* don't touch */ if (fmt == FWUPD_VERSION_FORMAT_PLAIN) return TRUE; /* nothing we can check for */ if (fmt == FWUPD_VERSION_FORMAT_UNKNOWN) { g_debug ("not checking %s as no version format set", version); return TRUE; } /* check the base format */ if (fu_common_version_guess_format (version) != fmt_base) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s is not a valid %s", version, fwupd_version_format_to_string (fmt)); return FALSE; } return TRUE; } /** * fu_common_vercmp_full: * @version_a: the semver release version, e.g. 1.2.3 * @version_b: the semver release version, e.g. 1.2.3.1 * @fmt: a #FwupdVersionFormat, e.g. %FWUPD_VERSION_FORMAT_PLAIN * * Compares version numbers for sorting taking into account the version format * if required. * * Returns: -1 if a < b, +1 if a > b, 0 if they are equal, and %G_MAXINT on error * * Since: 1.3.9 */ gint fu_common_vercmp_full (const gchar *version_a, const gchar *version_b, FwupdVersionFormat fmt) { if (fmt == FWUPD_VERSION_FORMAT_PLAIN) return g_strcmp0 (version_a, version_b); return fu_common_vercmp (version_a, version_b); } /** * fu_common_vercmp: * @version_a: the semver release version, e.g. 1.2.3 * @version_b: the semver release version, e.g. 1.2.3.1 * * Compares version numbers for sorting. * * Returns: -1 if a < b, +1 if a > b, 0 if they are equal, and %G_MAXINT on error * * Since: 0.3.5 */ gint fu_common_vercmp (const gchar *version_a, const gchar *version_b) { guint longest_split; g_auto(GStrv) split_a = NULL; g_auto(GStrv) split_b = NULL; /* sanity check */ if (version_a == NULL || version_b == NULL) return G_MAXINT; /* optimisation */ if (g_strcmp0 (version_a, version_b) == 0) return 0; /* split into sections, and try to parse */ split_a = g_strsplit (version_a, ".", -1); split_b = g_strsplit (version_b, ".", -1); longest_split = MAX (g_strv_length (split_a), g_strv_length (split_b)); for (guint i = 0; i < longest_split; i++) { gchar *endptr_a = NULL; gchar *endptr_b = NULL; gint64 ver_a; gint64 ver_b; /* we lost or gained a dot */ if (split_a[i] == NULL) return -1; if (split_b[i] == NULL) return 1; /* compare integers */ ver_a = g_ascii_strtoll (split_a[i], &endptr_a, 10); ver_b = g_ascii_strtoll (split_b[i], &endptr_b, 10); if (ver_a < ver_b) return -1; if (ver_a > ver_b) return 1; /* compare strings */ if ((endptr_a != NULL && endptr_a[0] != '\0') || (endptr_b != NULL && endptr_b[0] != '\0')) { gint rc = fu_common_vercmp_chunk (endptr_a, endptr_b); if (rc < 0) return -1; if (rc > 0) return 1; } } /* we really shouldn't get here */ return 0; } fwupd-1.3.9/libfwupdplugin/fu-common-version.h000066400000000000000000000021621362775233600214570ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include gint fu_common_vercmp (const gchar *version_a, const gchar *version_b) G_DEPRECATED_FOR(fu_common_vercmp_full); gint fu_common_vercmp_full (const gchar *version_a, const gchar *version_b, FwupdVersionFormat fmt); gchar *fu_common_version_from_uint64 (guint64 val, FwupdVersionFormat kind); gchar *fu_common_version_from_uint32 (guint32 val, FwupdVersionFormat kind); gchar *fu_common_version_from_uint16 (guint16 val, FwupdVersionFormat kind); gchar *fu_common_version_parse (const gchar *version) G_DEPRECATED_FOR(fu_common_version_parse_from_format); gchar *fu_common_version_parse_from_format (const gchar *version, FwupdVersionFormat fmt); gchar *fu_common_version_ensure_semver (const gchar *version); FwupdVersionFormat fu_common_version_guess_format (const gchar *version); gboolean fu_common_version_verify_format (const gchar *version, FwupdVersionFormat fmt, GError **error); fwupd-1.3.9/libfwupdplugin/fu-common.c000066400000000000000000001432621362775233600177760ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include #ifdef HAVE_GIO_UNIX #include #endif #include #ifdef HAVE_FNMATCH_H #include #elif _WIN32 #include #endif #include #include #include #include #include #include #include "fwupd-error.h" #include "fu-common.h" /** * SECTION:fu-common * @short_description: common functionality for plugins to use * * Helper functions that can be used by the daemon and plugins. * * See also: #FuPlugin */ /** * fu_common_rmtree: * @directory: a directory name * @error: A #GError or %NULL * * Recursively removes a directory. * * Returns: %TRUE for success, %FALSE otherwise * * Since: 0.9.7 **/ gboolean fu_common_rmtree (const gchar *directory, GError **error) { const gchar *filename; g_autoptr(GDir) dir = NULL; /* try to open */ g_debug ("removing %s", directory); dir = g_dir_open (directory, 0, error); if (dir == NULL) return FALSE; /* find each */ while ((filename = g_dir_read_name (dir))) { g_autofree gchar *src = NULL; src = g_build_filename (directory, filename, NULL); if (g_file_test (src, G_FILE_TEST_IS_DIR)) { if (!fu_common_rmtree (src, error)) return FALSE; } else { if (g_unlink (src) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to delete: %s", src); return FALSE; } } } if (g_remove (directory) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to delete: %s", directory); return FALSE; } return TRUE; } static gboolean fu_common_get_file_list_internal (GPtrArray *files, const gchar *directory, GError **error) { const gchar *filename; g_autoptr(GDir) dir = NULL; /* try to open */ dir = g_dir_open (directory, 0, error); if (dir == NULL) return FALSE; /* find each */ while ((filename = g_dir_read_name (dir))) { g_autofree gchar *src = g_build_filename (directory, filename, NULL); if (g_file_test (src, G_FILE_TEST_IS_DIR)) { if (!fu_common_get_file_list_internal (files, src, error)) return FALSE; } else { g_ptr_array_add (files, g_steal_pointer (&src)); } } return TRUE; } /** * fu_common_get_files_recursive: * @path: a directory name * @error: A #GError or %NULL * * Returns every file found under @directory, and any subdirectory. * If any path under @directory cannot be accessed due to permissions an error * will be returned. * * Returns: (transfer container) (element-type utf8): array of files, or %NULL for error * * Since: 1.0.6 **/ GPtrArray * fu_common_get_files_recursive (const gchar *path, GError **error) { g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func (g_free); if (!fu_common_get_file_list_internal (files, path, error)) return NULL; return g_steal_pointer (&files); } /** * fu_common_mkdir_parent: * @filename: A full pathname * @error: A #GError, or %NULL * * Creates any required directories, including any parent directories. * * Returns: %TRUE for success * * Since: 0.9.7 **/ gboolean fu_common_mkdir_parent (const gchar *filename, GError **error) { g_autofree gchar *parent = NULL; parent = g_path_get_dirname (filename); g_debug ("creating path %s", parent); if (g_mkdir_with_parents (parent, 0755) == -1) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to create '%s': %s", parent, g_strerror (errno)); return FALSE; } return TRUE; } /** * fu_common_set_contents_bytes: * @filename: A filename * @bytes: The data to write * @error: A #GError, or %NULL * * Writes a blob of data to a filename, creating the parent directories as * required. * * Returns: %TRUE for success * * Since: 0.9.5 **/ gboolean fu_common_set_contents_bytes (const gchar *filename, GBytes *bytes, GError **error) { const gchar *data; gsize size; g_autoptr(GFile) file = NULL; g_autoptr(GFile) file_parent = NULL; file = g_file_new_for_path (filename); file_parent = g_file_get_parent (file); if (!g_file_query_exists (file_parent, NULL)) { if (!g_file_make_directory_with_parents (file_parent, NULL, error)) return FALSE; } data = g_bytes_get_data (bytes, &size); g_debug ("writing %s with %" G_GSIZE_FORMAT " bytes", filename, size); return g_file_set_contents (filename, data, size, error); } /** * fu_common_get_contents_bytes: * @filename: A filename * @error: A #GError, or %NULL * * Reads a blob of data from a file. * * Returns: a #GBytes, or %NULL for failure * * Since: 0.9.7 **/ GBytes * fu_common_get_contents_bytes (const gchar *filename, GError **error) { gchar *data = NULL; gsize len = 0; if (!g_file_get_contents (filename, &data, &len, error)) return NULL; g_debug ("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len); return g_bytes_new_take (data, len); } /** * fu_common_get_contents_fd: * @fd: A file descriptor * @count: The maximum number of bytes to read * @error: A #GError, or %NULL * * Reads a blob from a specific file descriptor. * * Note: this will close the fd when done * * Returns: (transfer full): a #GBytes, or %NULL * * Since: 0.9.5 **/ GBytes * fu_common_get_contents_fd (gint fd, gsize count, GError **error) { #ifdef HAVE_GIO_UNIX g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GInputStream) stream = NULL; g_return_val_if_fail (fd > 0, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* this is invalid */ if (count == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "A maximum read size must be specified"); return NULL; } /* read the entire fd to a data blob */ stream = g_unix_input_stream_new (fd, TRUE); blob = g_input_stream_read_bytes (stream, count, NULL, &error_local); if (blob == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, error_local->message); return NULL; } return g_steal_pointer (&blob); #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return NULL; #endif } static gboolean fu_common_extract_archive_entry (struct archive_entry *entry, const gchar *dir) { const gchar *tmp; g_autofree gchar *buf = NULL; /* no output file */ if (archive_entry_pathname (entry) == NULL) return FALSE; /* update output path */ tmp = archive_entry_pathname (entry); buf = g_build_filename (dir, tmp, NULL); archive_entry_update_pathname_utf8 (entry, buf); return TRUE; } /** * fu_common_extract_archive: * @blob: a #GBytes archive as a blob * @dir: a directory name to extract to * @error: A #GError, or %NULL * * Extracts an archive to a directory. * * Returns: %TRUE for success * * Since: 0.9.7 **/ gboolean fu_common_extract_archive (GBytes *blob, const gchar *dir, GError **error) { gboolean ret = TRUE; int r; struct archive *arch = NULL; struct archive_entry *entry; /* decompress anything matching either glob */ g_debug ("decompressing into %s", dir); arch = archive_read_new (); archive_read_support_format_all (arch); archive_read_support_filter_all (arch); r = archive_read_open_memory (arch, (void *) g_bytes_get_data (blob, NULL), (size_t) g_bytes_get_size (blob)); if (r != 0) { ret = FALSE; g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot open: %s", archive_error_string (arch)); goto out; } for (;;) { gboolean valid; r = archive_read_next_header (arch, &entry); if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) { ret = FALSE; g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot read header: %s", archive_error_string (arch)); goto out; } /* only extract if valid */ valid = fu_common_extract_archive_entry (entry, dir); if (!valid) continue; r = archive_read_extract (arch, entry, 0); if (r != ARCHIVE_OK) { ret = FALSE; g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot extract: %s", archive_error_string (arch)); goto out; } } out: if (arch != NULL) { archive_read_close (arch); archive_read_free (arch); } return ret; } static void fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...) G_GNUC_PRINTF (2, 3); static void fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...) { va_list args; g_autofree gchar *tmp = NULL; g_auto(GStrv) split = NULL; va_start (args, fmt); tmp = g_strdup_vprintf (fmt, args); va_end (args); split = g_strsplit (tmp, " ", -1); for (guint i = 0; split[i] != NULL; i++) g_ptr_array_add (argv, g_strdup (split[i])); } /** * fu_common_find_program_in_path: * @basename: The program to search * @error: A #GError, or %NULL * * Looks for a program in the PATH variable * * Returns: a new #gchar, or %NULL for error * * Since: 1.1.2 **/ gchar * fu_common_find_program_in_path (const gchar *basename, GError **error) { gchar *fn = g_find_program_in_path (basename); if (fn == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing executable %s in PATH", basename); return NULL; } return fn; } static gboolean fu_common_test_namespace_support (GError **error) { /* test if CONFIG_USER_NS is valid */ if (!g_file_test ("/proc/self/ns/user", G_FILE_TEST_IS_SYMLINK)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing CONFIG_USER_NS in kernel"); return FALSE; } if (g_file_test ("/proc/sys/kernel/unprivileged_userns_clone", G_FILE_TEST_EXISTS)) { g_autofree gchar *clone = NULL; if (!g_file_get_contents ("/proc/sys/kernel/unprivileged_userns_clone", &clone, NULL, error)) return FALSE; if (g_ascii_strtoll (clone, NULL, 10) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unprivileged user namespace clones disabled by distro"); return FALSE; } } return TRUE; } /** * fu_common_firmware_builder: * @bytes: The data to use * @script_fn: Name of the script to run in the tarball, e.g. `startup.sh` * @output_fn: Name of the generated firmware, e.g. `firmware.bin` * @error: A #GError, or %NULL * * Builds a firmware file using tools from the host session in a bubblewrap * jail. Several things happen during build: * * 1. The @bytes data is untarred to a temporary location * 2. A bubblewrap container is set up * 3. The startup.sh script is run inside the container * 4. The firmware.bin is extracted from the container * 5. The temporary location is deleted * * Returns: a new #GBytes, or %NULL for error * * Since: 0.9.7 **/ GBytes * fu_common_firmware_builder (GBytes *bytes, const gchar *script_fn, const gchar *output_fn, GError **error) { gint rc = 0; g_autofree gchar *argv_str = NULL; g_autofree gchar *bwrap_fn = NULL; g_autofree gchar *localstatebuilderdir = NULL; g_autofree gchar *localstatedir = NULL; g_autofree gchar *output2_fn = NULL; g_autofree gchar *standard_error = NULL; g_autofree gchar *standard_output = NULL; g_autofree gchar *tmpdir = NULL; g_autoptr(GBytes) firmware_blob = NULL; g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free); g_return_val_if_fail (bytes != NULL, NULL); g_return_val_if_fail (script_fn != NULL, NULL); g_return_val_if_fail (output_fn != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* find bwrap in the path */ bwrap_fn = fu_common_find_program_in_path ("bwrap", error); if (bwrap_fn == NULL) return NULL; /* test if CONFIG_USER_NS is valid */ if (!fu_common_test_namespace_support (error)) return NULL; /* untar file to temp location */ tmpdir = g_dir_make_tmp ("fwupd-gen-XXXXXX", error); if (tmpdir == NULL) return NULL; if (!fu_common_extract_archive (bytes, tmpdir, error)) return NULL; /* this is shared with the plugins */ localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); localstatebuilderdir = g_build_filename (localstatedir, "builder", NULL); /* launch bubblewrap and generate firmware */ g_ptr_array_add (argv, g_steal_pointer (&bwrap_fn)); fu_common_add_argv (argv, "--die-with-parent"); fu_common_add_argv (argv, "--ro-bind /usr /usr"); fu_common_add_argv (argv, "--ro-bind /lib /lib"); fu_common_add_argv (argv, "--ro-bind /lib64 /lib64"); fu_common_add_argv (argv, "--ro-bind /bin /bin"); fu_common_add_argv (argv, "--ro-bind /sbin /sbin"); fu_common_add_argv (argv, "--dir /tmp"); fu_common_add_argv (argv, "--dir /var"); fu_common_add_argv (argv, "--bind %s /tmp", tmpdir); if (g_file_test (localstatebuilderdir, G_FILE_TEST_EXISTS)) fu_common_add_argv (argv, "--ro-bind %s /boot", localstatebuilderdir); fu_common_add_argv (argv, "--dev /dev"); fu_common_add_argv (argv, "--chdir /tmp"); fu_common_add_argv (argv, "--unshare-all"); fu_common_add_argv (argv, "/tmp/%s", script_fn); g_ptr_array_add (argv, NULL); argv_str = g_strjoinv (" ", (gchar **) argv->pdata); g_debug ("running '%s' in %s", argv_str, tmpdir); if (!g_spawn_sync ("/tmp", (gchar **) argv->pdata, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, /* child_setup */ &standard_output, &standard_error, &rc, error)) { g_prefix_error (error, "failed to run '%s': ", argv_str); return NULL; } if (standard_output != NULL && standard_output[0] != '\0') g_debug ("console output was: %s", standard_output); if (rc != 0) { FwupdError code = FWUPD_ERROR_INTERNAL; if (errno == ENOTTY) code = FWUPD_ERROR_PERMISSION_DENIED; g_set_error (error, FWUPD_ERROR, code, "failed to build firmware: %s", standard_error); return NULL; } /* get generated file */ output2_fn = g_build_filename (tmpdir, output_fn, NULL); firmware_blob = fu_common_get_contents_bytes (output2_fn, error); if (firmware_blob == NULL) return NULL; /* cleanup temp directory */ if (!fu_common_rmtree (tmpdir, error)) return NULL; /* success */ return g_steal_pointer (&firmware_blob); } typedef struct { FuOutputHandler handler_cb; gpointer handler_user_data; GMainLoop *loop; GSource *source; GInputStream *stream; GCancellable *cancellable; guint timeout_id; } FuCommonSpawnHelper; static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper); static gboolean fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data) { FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data; gchar buffer[1024]; gssize sz; g_auto(GStrv) split = NULL; g_autoptr(GError) error = NULL; /* read from stream */ sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream), buffer, sizeof(buffer) - 1, NULL, &error); if (sz < 0) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { g_warning ("failed to get read from nonblocking fd: %s", error->message); } return G_SOURCE_REMOVE; } /* no read possible */ if (sz == 0) g_main_loop_quit (helper->loop); /* emit lines */ if (helper->handler_cb != NULL) { buffer[sz] = '\0'; split = g_strsplit (buffer, "\n", -1); for (guint i = 0; split[i] != NULL; i++) { if (split[i][0] == '\0') continue; helper->handler_cb (split[i], helper->handler_user_data); } } /* set up the source for the next read */ fu_common_spawn_create_pollable_source (helper); return G_SOURCE_REMOVE; } static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper) { if (helper->source != NULL) g_source_destroy (helper->source); helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream), helper->cancellable); g_source_attach (helper->source, NULL); g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL); } static void fu_common_spawn_helper_free (FuCommonSpawnHelper *helper) { g_object_unref (helper->cancellable); if (helper->stream != NULL) g_object_unref (helper->stream); if (helper->source != NULL) g_source_destroy (helper->source); if (helper->loop != NULL) g_main_loop_unref (helper->loop); if (helper->timeout_id != 0) g_source_remove (helper->timeout_id); g_free (helper); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free) #pragma clang diagnostic pop static gboolean fu_common_spawn_timeout_cb (gpointer user_data) { FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data; g_cancellable_cancel (helper->cancellable); g_main_loop_quit (helper->loop); helper->timeout_id = 0; return G_SOURCE_REMOVE; } static void fu_common_spawn_cancelled_cb (GCancellable *cancellable, FuCommonSpawnHelper *helper) { /* just propagate */ g_cancellable_cancel (helper->cancellable); } /** * fu_common_spawn_sync: * @argv: The argument list to run * @handler_cb: (scope call): A #FuOutputHandler or %NULL * @handler_user_data: the user data to pass to @handler_cb * @timeout_ms: a timeout in ms, or 0 for no limit * @cancellable: a #GCancellable, or %NULL * @error: A #GError or %NULL * * Runs a subprocess and waits for it to exit. Any output on standard out or * standard error will be forwarded to @handler_cb as whole lines. * * Returns: %TRUE for success * * Since: 0.9.7 **/ gboolean fu_common_spawn_sync (const gchar * const * argv, FuOutputHandler handler_cb, gpointer handler_user_data, guint timeout_ms, GCancellable *cancellable, GError **error) { g_autoptr(FuCommonSpawnHelper) helper = NULL; g_autoptr(GSubprocess) subprocess = NULL; g_autofree gchar *argv_str = NULL; gulong cancellable_id = 0; /* create subprocess */ argv_str = g_strjoinv (" ", (gchar **) argv); g_debug ("running '%s'", argv_str); subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE, error); if (subprocess == NULL) return FALSE; /* watch for process to exit */ helper = g_new0 (FuCommonSpawnHelper, 1); helper->handler_cb = handler_cb; helper->handler_user_data = handler_user_data; helper->loop = g_main_loop_new (NULL, FALSE); helper->stream = g_subprocess_get_stdout_pipe (subprocess); /* always create a cancellable, and connect up the parent */ helper->cancellable = g_cancellable_new (); if (cancellable != NULL) { cancellable_id = g_cancellable_connect (cancellable, G_CALLBACK (fu_common_spawn_cancelled_cb), helper, NULL); } /* allow timeout */ if (timeout_ms > 0) { helper->timeout_id = g_timeout_add (timeout_ms, fu_common_spawn_timeout_cb, helper); } fu_common_spawn_create_pollable_source (helper); g_main_loop_run (helper->loop); g_cancellable_disconnect (cancellable, cancellable_id); if (g_cancellable_set_error_if_cancelled (helper->cancellable, error)) return FALSE; return g_subprocess_wait_check (subprocess, cancellable, error); } /** * fu_common_write_uint16: * @buf: A writable buffer * @val_native: a value in host byte-order * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN * * Writes a value to a buffer using a specified endian. * * Since: 1.0.3 **/ void fu_common_write_uint16 (guint8 *buf, guint16 val_native, FuEndianType endian) { guint16 val_hw; switch (endian) { case G_BIG_ENDIAN: val_hw = GUINT16_TO_BE(val_native); break; case G_LITTLE_ENDIAN: val_hw = GUINT16_TO_LE(val_native); break; default: g_assert_not_reached (); } memcpy (buf, &val_hw, sizeof(val_hw)); } /** * fu_common_write_uint32: * @buf: A writable buffer * @val_native: a value in host byte-order * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN * * Writes a value to a buffer using a specified endian. * * Since: 1.0.3 **/ void fu_common_write_uint32 (guint8 *buf, guint32 val_native, FuEndianType endian) { guint32 val_hw; switch (endian) { case G_BIG_ENDIAN: val_hw = GUINT32_TO_BE(val_native); break; case G_LITTLE_ENDIAN: val_hw = GUINT32_TO_LE(val_native); break; default: g_assert_not_reached (); } memcpy (buf, &val_hw, sizeof(val_hw)); } /** * fu_common_read_uint16: * @buf: A readable buffer * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN * * Read a value from a buffer using a specified endian. * * Returns: a value in host byte-order * * Since: 1.0.3 **/ guint16 fu_common_read_uint16 (const guint8 *buf, FuEndianType endian) { guint16 val_hw, val_native; memcpy (&val_hw, buf, sizeof(val_hw)); switch (endian) { case G_BIG_ENDIAN: val_native = GUINT16_FROM_BE(val_hw); break; case G_LITTLE_ENDIAN: val_native = GUINT16_FROM_LE(val_hw); break; default: g_assert_not_reached (); } return val_native; } /** * fu_common_read_uint32: * @buf: A readable buffer * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN * * Read a value from a buffer using a specified endian. * * Returns: a value in host byte-order * * Since: 1.0.3 **/ guint32 fu_common_read_uint32 (const guint8 *buf, FuEndianType endian) { guint32 val_hw, val_native; memcpy (&val_hw, buf, sizeof(val_hw)); switch (endian) { case G_BIG_ENDIAN: val_native = GUINT32_FROM_BE(val_hw); break; case G_LITTLE_ENDIAN: val_native = GUINT32_FROM_LE(val_hw); break; default: g_assert_not_reached (); } return val_native; } /** * fu_common_strtoull: * @str: A string, e.g. "0x1234" * * Converts a string value to an integer. Values are assumed base 10, unless * prefixed with "0x" where they are parsed as base 16. * * Returns: integer value, or 0x0 for error * * Since: 1.1.2 **/ guint64 fu_common_strtoull (const gchar *str) { guint base = 10; if (str == NULL) return 0x0; if (g_str_has_prefix (str, "0x")) { str += 2; base = 16; } return g_ascii_strtoull (str, NULL, base); } /** * fu_common_strstrip: * @str: A string, e.g. " test " * * Removes leading and trailing whitespace from a constant string. * * Returns: newly allocated string * * Since: 1.1.2 **/ gchar * fu_common_strstrip (const gchar *str) { guint head = G_MAXUINT; guint tail = 0; g_return_val_if_fail (str != NULL, NULL); /* find first non-space char */ for (guint i = 0; str[i] != '\0'; i++) { if (str[i] != ' ') { head = i; break; } } if (head == G_MAXUINT) return g_strdup (""); /* find last non-space char */ for (guint i = head; str[i] != '\0'; i++) { if (!g_ascii_isspace (str[i])) tail = i; } return g_strndup (str + head, tail - head + 1); } static const GError * fu_common_error_array_find (GPtrArray *errors, FwupdError error_code) { for (guint j = 0; j < errors->len; j++) { const GError *error = g_ptr_array_index (errors, j); if (g_error_matches (error, FWUPD_ERROR, error_code)) return error; } return NULL; } static guint fu_common_error_array_count (GPtrArray *errors, FwupdError error_code) { guint cnt = 0; for (guint j = 0; j < errors->len; j++) { const GError *error = g_ptr_array_index (errors, j); if (g_error_matches (error, FWUPD_ERROR, error_code)) cnt++; } return cnt; } static gboolean fu_common_error_array_matches_any (GPtrArray *errors, FwupdError *error_codes) { for (guint j = 0; j < errors->len; j++) { const GError *error = g_ptr_array_index (errors, j); gboolean matches_any = FALSE; for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) { if (g_error_matches (error, FWUPD_ERROR, error_codes[i])) { matches_any = TRUE; break; } } if (!matches_any) return FALSE; } return TRUE; } /** * fu_common_error_array_get_best: * @errors: (element-type GError): array of errors * * Finds the 'best' error to show the user from a array of errors, creating a * completely bespoke error where required. * * Returns: (transfer full): a #GError, never %NULL * * Since: 1.0.8 **/ GError * fu_common_error_array_get_best (GPtrArray *errors) { FwupdError err_prio[] = { FWUPD_ERROR_INVALID_FILE, FWUPD_ERROR_VERSION_SAME, FWUPD_ERROR_VERSION_NEWER, FWUPD_ERROR_NOT_SUPPORTED, FWUPD_ERROR_INTERNAL, FWUPD_ERROR_NOT_FOUND, FWUPD_ERROR_LAST }; FwupdError err_all_uptodate[] = { FWUPD_ERROR_VERSION_SAME, FWUPD_ERROR_NOT_FOUND, FWUPD_ERROR_NOT_SUPPORTED, FWUPD_ERROR_LAST }; FwupdError err_all_newer[] = { FWUPD_ERROR_VERSION_NEWER, FWUPD_ERROR_VERSION_SAME, FWUPD_ERROR_NOT_FOUND, FWUPD_ERROR_NOT_SUPPORTED, FWUPD_ERROR_LAST }; /* are all the errors either GUID-not-matched or version-same? */ if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_SAME) > 1 && fu_common_error_array_matches_any (errors, err_all_uptodate)) { return g_error_new (FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "All updatable firmware is already installed"); } /* are all the errors either GUID-not-matched or version same or newer? */ if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_NEWER) > 1 && fu_common_error_array_matches_any (errors, err_all_newer)) { return g_error_new (FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "All updatable devices already have newer versions"); } /* get the most important single error */ for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) { const GError *error_tmp = fu_common_error_array_find (errors, err_prio[i]); if (error_tmp != NULL) return g_error_copy (error_tmp); } /* fall back to something */ return g_error_new (FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No supported devices found"); } /** * fu_common_get_path: * @path_kind: A #FuPathKind e.g. %FU_PATH_KIND_DATADIR_PKG * * Gets a fwupd-specific system path. These can be overridden with various * environment variables, for instance %FWUPD_DATADIR. * * Returns: a system path, or %NULL if invalid * * Since: 1.0.8 **/ gchar * fu_common_get_path (FuPathKind path_kind) { const gchar *tmp; g_autofree gchar *basedir = NULL; switch (path_kind) { /* /var */ case FU_PATH_KIND_LOCALSTATEDIR: tmp = g_getenv ("FWUPD_LOCALSTATEDIR"); if (tmp != NULL) return g_strdup (tmp); tmp = g_getenv ("SNAP_USER_DATA"); if (tmp != NULL) return g_build_filename (tmp, FWUPD_LOCALSTATEDIR, NULL); return g_build_filename (FWUPD_LOCALSTATEDIR, NULL); /* /sys/firmware */ case FU_PATH_KIND_SYSFSDIR_FW: tmp = g_getenv ("FWUPD_SYSFSFWDIR"); if (tmp != NULL) return g_strdup (tmp); return g_strdup ("/sys/firmware"); /* /sys/class/tpm */ case FU_PATH_KIND_SYSFSDIR_TPM: tmp = g_getenv ("FWUPD_SYSFSTPMDIR"); if (tmp != NULL) return g_strdup (tmp); return g_strdup ("/sys/class/tpm"); /* /sys/bus/platform/drivers */ case FU_PATH_KIND_SYSFSDIR_DRIVERS: tmp = g_getenv ("FWUPD_SYSFSDRIVERDIR"); if (tmp != NULL) return g_strdup (tmp); return g_strdup ("/sys/bus/platform/drivers"); /* /sys/kernel/security */ case FU_PATH_KIND_SYSFSDIR_SECURITY: tmp = g_getenv ("FWUPD_SYSFSSECURITYDIR"); if (tmp != NULL) return g_strdup (tmp); return g_strdup ("/sys/kernel/security"); /* /etc */ case FU_PATH_KIND_SYSCONFDIR: tmp = g_getenv ("FWUPD_SYSCONFDIR"); if (tmp != NULL) return g_strdup (tmp); tmp = g_getenv ("SNAP_USER_DATA"); if (tmp != NULL) return g_build_filename (tmp, FWUPD_SYSCONFDIR, NULL); return g_strdup (FWUPD_SYSCONFDIR); /* /usr/lib//fwupd-plugins-3 */ case FU_PATH_KIND_PLUGINDIR_PKG: tmp = g_getenv ("FWUPD_PLUGINDIR"); if (tmp != NULL) return g_strdup (tmp); tmp = g_getenv ("SNAP"); if (tmp != NULL) return g_build_filename (tmp, FWUPD_PLUGINDIR, NULL); return g_build_filename (FWUPD_PLUGINDIR, NULL); /* /usr/share/fwupd */ case FU_PATH_KIND_DATADIR_PKG: tmp = g_getenv ("FWUPD_DATADIR"); if (tmp != NULL) return g_strdup (tmp); tmp = g_getenv ("SNAP"); if (tmp != NULL) return g_build_filename (tmp, FWUPD_DATADIR, PACKAGE_NAME, NULL); return g_build_filename (FWUPD_DATADIR, PACKAGE_NAME, NULL); /* /usr/libexec/fwupd/efi */ case FU_PATH_KIND_EFIAPPDIR: tmp = g_getenv ("FWUPD_EFIAPPDIR"); if (tmp != NULL) return g_strdup (tmp); #ifdef EFI_APP_LOCATION tmp = g_getenv ("SNAP"); if (tmp != NULL) return g_build_filename (tmp, EFI_APP_LOCATION, NULL); return g_strdup (EFI_APP_LOCATION); #else return NULL; #endif /* /etc/fwupd */ case FU_PATH_KIND_SYSCONFDIR_PKG: tmp = g_getenv ("CONFIGURATION_DIRECTORY"); if (tmp != NULL && g_file_test (tmp, G_FILE_TEST_EXISTS)) return g_build_filename (tmp, NULL); basedir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR); return g_build_filename (basedir, PACKAGE_NAME, NULL); /* /var/lib/fwupd */ case FU_PATH_KIND_LOCALSTATEDIR_PKG: tmp = g_getenv ("STATE_DIRECTORY"); if (tmp != NULL && g_file_test (tmp, G_FILE_TEST_EXISTS)) return g_build_filename (tmp, NULL); basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR); return g_build_filename (basedir, "lib", PACKAGE_NAME, NULL); /* /var/cache/fwupd */ case FU_PATH_KIND_CACHEDIR_PKG: tmp = g_getenv ("CACHE_DIRECTORY"); if (tmp != NULL && g_file_test (tmp, G_FILE_TEST_EXISTS)) return g_build_filename (tmp, NULL); basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR); return g_build_filename (basedir, "cache", PACKAGE_NAME, NULL); case FU_PATH_KIND_OFFLINE_TRIGGER: tmp = g_getenv ("FWUPD_OFFLINE_TRIGGER"); if (tmp != NULL) return g_strdup (tmp); return g_strdup ("/system-update"); case FU_PATH_KIND_POLKIT_ACTIONS: #ifdef POLKIT_ACTIONDIR return g_strdup (POLKIT_ACTIONDIR); #else return NULL; #endif /* this shouldn't happen */ default: g_warning ("cannot build path for unknown kind %u", path_kind); } return NULL; } /** * fu_common_string_replace: * @string: The #GString to operate on * @search: The text to search for * @replace: The text to use for substitutions * * Performs multiple search and replace operations on the given string. * * Returns: the number of replacements done, or 0 if @search is not found. * * Since: 1.2.0 **/ guint fu_common_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; } /** * fu_common_strwidth: * @text: The string to operate on * * Returns the width of the string in displayed characters on the console. * * Returns: width of text * * Since: 1.3.2 **/ gsize fu_common_strwidth (const gchar *text) { const gchar *p = text; gsize width = 0; while (*p) { gunichar c = g_utf8_get_char (p); if (g_unichar_iswide (c)) width += 2; else if (!g_unichar_iszerowidth (c)) width += 1; p = g_utf8_next_char (p); } return width; } /** * fu_common_string_append_kv: * @str: A #GString * @idt: The indent * @key: A string to append * @value: a string to append * * Appends a key and string value to a string * * Since: 1.2.4 */ void fu_common_string_append_kv (GString *str, guint idt, const gchar *key, const gchar *value) { const guint align = 25; gsize keysz; g_return_if_fail (idt * 2 < align); /* ignore */ if (key == NULL) return; for (gsize i = 0; i < idt; i++) g_string_append (str, " "); if (key[0] != '\0') { g_string_append_printf (str, "%s:", key); keysz = (idt * 2) + fu_common_strwidth (key) + 1; } else { keysz = idt * 2; } if (value != NULL) { g_auto(GStrv) split = NULL; split = g_strsplit (value, "\n", -1); for (guint i = 0; split[i] != NULL; i++) { if (i == 0) { for (gsize j = keysz; j < align; j++) g_string_append (str, " "); } else { for (gsize j = 0; j < idt; j++) g_string_append (str, " "); } g_string_append (str, split[i]); g_string_append (str, "\n"); } } else { g_string_append (str, "\n"); } } /** * fu_common_string_append_ku: * @str: A #GString * @idt: The indent * @key: A string to append * @value: guint64 * * Appends a key and unsigned integer to a string * * Since: 1.2.4 */ void fu_common_string_append_ku (GString *str, guint idt, const gchar *key, guint64 value) { g_autofree gchar *tmp = g_strdup_printf ("%" G_GUINT64_FORMAT, value); fu_common_string_append_kv (str, idt, key, tmp); } /** * fu_common_string_append_kx: * @str: A #GString * @idt: The indent * @key: A string to append * @value: guint64 * * Appends a key and hex integer to a string * * Since: 1.2.4 */ void fu_common_string_append_kx (GString *str, guint idt, const gchar *key, guint64 value) { g_autofree gchar *tmp = g_strdup_printf ("0x%x", (guint) value); fu_common_string_append_kv (str, idt, key, tmp); } /** * fu_common_string_append_kb: * @str: A #GString * @idt: The indent * @key: A string to append * @value: Boolean * * Appends a key and boolean value to a string * * Since: 1.2.4 */ void fu_common_string_append_kb (GString *str, guint idt, const gchar *key, gboolean value) { fu_common_string_append_kv (str, idt, key, value ? "true" : "false"); } /** * fu_common_dump_full: * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL * @title: prefix title, or %NULL * @data: buffer to print * @len: the size of @data * @columns: break new lines after this many bytes * @flags: some #FuDumpFlags, e.g. %FU_DUMP_FLAGS_SHOW_ASCII * * Dumps a raw buffer to the screen. * * Since: 1.2.4 **/ void fu_common_dump_full (const gchar *log_domain, const gchar *title, const guint8 *data, gsize len, guint columns, FuDumpFlags flags) { g_autoptr(GString) str = g_string_new (NULL); /* optional */ if (title != NULL) g_string_append_printf (str, "%s:", title); /* if more than can fit on one line then start afresh */ if (len > columns || flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) { g_string_append (str, "\n"); } else { for (gsize i = str->len; i < 16; i++) g_string_append (str, " "); } /* offset line */ if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) { g_string_append (str, " │ "); for (gsize i = 0; i < columns; i++) g_string_append_printf (str, "%02x ", (guint) i); g_string_append (str, "\n───────┼"); for (gsize i = 0; i < columns; i++) g_string_append (str, "───"); g_string_append_printf (str, "\n0x%04x │ ", (guint) 0); } /* print each row */ for (gsize i = 0; i < len; i++) { g_string_append_printf (str, "%02x ", data[i]); /* optionally print ASCII char */ if (flags & FU_DUMP_FLAGS_SHOW_ASCII) { if (g_ascii_isprint (data[i])) g_string_append_printf (str, "[%c] ", data[i]); else g_string_append (str, "[?] "); } /* new row required */ if (i > 0 && i != len - 1 && (i + 1) % columns == 0) { g_string_append (str, "\n"); if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) g_string_append_printf (str, "0x%04x │ ", (guint) i + 1); } } g_log (log_domain, G_LOG_LEVEL_DEBUG, "%s", str->str); } /** * fu_common_dump_raw: * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL * @title: prefix title, or %NULL * @data: buffer to print * @len: the size of @data * * Dumps a raw buffer to the screen. * * Since: 1.2.2 **/ void fu_common_dump_raw (const gchar *log_domain, const gchar *title, const guint8 *data, gsize len) { FuDumpFlags flags = FU_DUMP_FLAGS_NONE; if (len > 64) flags |= FU_DUMP_FLAGS_SHOW_ADDRESSES; fu_common_dump_full (log_domain, title, data, len, 32, flags); } /** * fu_common_dump_bytes: * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL * @title: prefix title, or %NULL * @bytes: a #GBytes * * Dumps a byte buffer to the screen. * * Since: 1.2.2 **/ void fu_common_dump_bytes (const gchar *log_domain, const gchar *title, GBytes *bytes) { gsize len = 0; const guint8 *data = g_bytes_get_data (bytes, &len); fu_common_dump_raw (log_domain, title, data, len); } /** * fu_common_bytes_align: * @bytes: a #GBytes * @blksz: block size in bytes * @padval: the byte used to pad the byte buffer * * Aligns a block of memory to @blksize using the @padval value; if * the block is already aligned then the original @bytes is returned. * * Returns: (transfer full): a #GBytes, possibly @bytes * * Since: 1.2.4 **/ GBytes * fu_common_bytes_align (GBytes *bytes, gsize blksz, gchar padval) { const guint8 *data; gsize sz; g_return_val_if_fail (bytes != NULL, NULL); g_return_val_if_fail (blksz > 0, NULL); /* pad */ data = g_bytes_get_data (bytes, &sz); if (sz % blksz != 0) { gsize sz_align = ((sz / blksz) + 1) * blksz; guint8 *data_align = g_malloc (sz_align); memcpy (data_align, data, sz); memset (data_align + sz, padval, sz_align - sz); g_debug ("aligning 0x%x bytes to 0x%x", (guint) sz, (guint) sz_align); return g_bytes_new_take (data_align, sz_align); } /* perfectly aligned */ return g_bytes_ref (bytes); } /** * fu_common_bytes_is_empty: * @bytes: a #GBytes * * Checks if a byte array are just empty (0xff) bytes. * * Return value: %TRUE if @bytes is empty * * Since: 1.2.6 **/ gboolean fu_common_bytes_is_empty (GBytes *bytes) { gsize sz = 0; const guint8 *buf = g_bytes_get_data (bytes, &sz); for (gsize i = 0; i < sz; i++) { if (buf[i] != 0xff) return FALSE; } return TRUE; } /** * fu_common_bytes_compare_raw: * @buf1: a buffer * @bufsz1: sizeof @buf1 * @buf2: another buffer * @bufsz2: sizeof @buf2 * @error: A #GError or %NULL * * Compares the buffers for equality. * * Return value: %TRUE if @buf1 and @buf2 are identical * * Since: 1.3.2 **/ gboolean fu_common_bytes_compare_raw (const guint8 *buf1, gsize bufsz1, const guint8 *buf2, gsize bufsz2, GError **error) { g_return_val_if_fail (buf1 != NULL, FALSE); g_return_val_if_fail (buf2 != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* not the same length */ if (bufsz1 != bufsz2) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got %" G_GSIZE_FORMAT " bytes, expected " "%" G_GSIZE_FORMAT, bufsz1, bufsz2); return FALSE; } /* check matches */ for (guint i = 0x0; i < bufsz1; i++) { if (buf1[i] != buf2[i]) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got 0x%02x, expected 0x%02x @ 0x%04x", buf1[i], buf2[i], i); return FALSE; } } /* success */ return TRUE; } /** * fu_common_bytes_compare: * @bytes1: a #GBytes * @bytes2: another #GBytes * @error: A #GError or %NULL * * Compares the buffers for equality. * * Return value: %TRUE if @bytes1 and @bytes2 are identical * * Since: 1.2.6 **/ gboolean fu_common_bytes_compare (GBytes *bytes1, GBytes *bytes2, GError **error) { const guint8 *buf1; const guint8 *buf2; gsize bufsz1; gsize bufsz2; g_return_val_if_fail (bytes1 != NULL, FALSE); g_return_val_if_fail (bytes2 != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); buf1 = g_bytes_get_data (bytes1, &bufsz1); buf2 = g_bytes_get_data (bytes2, &bufsz2); return fu_common_bytes_compare_raw (buf1, bufsz1, buf2, bufsz2, error); } /** * fu_common_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 * * Since: 1.3.1 **/ GBytes * fu_common_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); } /** * fu_common_realpath: * @filename: a filename * @error: A #GError or %NULL * * Finds the canonicalized absolute filename for a path. * * Return value: A filename, or %NULL if invalid or not found * * Since: 1.2.6 **/ gchar * fu_common_realpath (const gchar *filename, GError **error) { char full_tmp[PATH_MAX]; g_return_val_if_fail (filename != NULL, NULL); #ifdef HAVE_REALPATH if (realpath (filename, full_tmp) == NULL) { #else if (_fullpath (full_tmp, filename, sizeof(full_tmp)) == NULL) { #endif g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot resolve path: %s", strerror (errno)); return NULL; } if (!g_file_test (full_tmp, G_FILE_TEST_EXISTS)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot find path: %s", full_tmp); return NULL; } return g_strdup (full_tmp); } /** * fu_common_fnmatch: * @pattern: a glob pattern, e.g. `*foo*` * @str: a string to match against the pattern, e.g. `bazfoobar` * * Matches a string against a glob pattern. * * Return value: %TRUE if the string matched * * Since: 1.3.5 **/ gboolean fu_common_fnmatch (const gchar *pattern, const gchar *str) { g_return_val_if_fail (pattern != NULL, FALSE); g_return_val_if_fail (str != NULL, FALSE); #ifdef HAVE_FNMATCH_H return fnmatch (pattern, str, FNM_NOESCAPE) == 0; #elif _WIN32 g_return_val_if_fail (strlen (pattern) < MAX_PATH, FALSE); g_return_val_if_fail (strlen (str) < MAX_PATH, FALSE); return PathMatchSpecA (str, pattern); #else return g_strcmp0 (pattern, str) == 0; #endif } /** * fu_common_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: (transfer full): a newly-allocated NULL-terminated array of strings * * Since: 1.3.1 **/ gchar ** fu_common_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); } /** * fu_memcpy_safe: * @dst: destination buffer * @dst_sz: maximum size of @dst, typically `sizeof(dst)` * @dst_offset: offset in bytes into @dst to copy to * @src: source buffer * @src_sz: maximum size of @dst, typically `sizeof(src)` * @src_offset: offset in bytes into @src to copy from * @n: number of bytes to copy from @src+@offset from * @error: A #GError or %NULL * * Copies some memory using memcpy in a safe way. Providing the buffer sizes * of both the destination and the source allows us to check for buffer overflow. * * Providing the buffer offsets also allows us to check reading past the end of * the source buffer. For this reason the caller should NEVER add an offset to * @src or @dst. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Return value: %TRUE if the bytes were copied, %FALSE otherwise * * Since: 1.3.1 **/ gboolean fu_memcpy_safe (guint8 *dst, gsize dst_sz, gsize dst_offset, const guint8 *src, gsize src_sz, gsize src_offset, gsize n, GError **error) { if (n == 0) return TRUE; if (n > src_sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "attempted to read 0x%02x bytes from buffer of 0x%02x", (guint) n, (guint) src_sz); return FALSE; } if (n + src_offset > src_sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "attempted to read 0x%02x bytes at offset 0x%02x from buffer of 0x%02x", (guint) n, (guint) src_offset, (guint) src_sz); return FALSE; } if (n > dst_sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "attempted to write 0x%02x bytes to buffer of 0x%02x", (guint) n, (guint) dst_sz); return FALSE; } if (n + dst_offset > dst_sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "attempted to write 0x%02x bytes at offset 0x%02x to buffer of 0x%02x", (guint) n, (guint) dst_offset, (guint) dst_sz); return FALSE; } /* phew! */ memcpy (dst + dst_offset, src + src_offset, n); return TRUE; } /** * fu_common_read_uint8_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to copy from * @value: (out) (allow-none): the parsed value * @error: A #GError or %NULL * * Read a value from a buffer in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Return value: %TRUE if @value was set, %FALSE otherwise * * Since: 1.3.3 **/ gboolean fu_common_read_uint8_safe (const guint8 *buf, gsize bufsz, gsize offset, guint8 *value, GError **error) { guint8 tmp; if (!fu_memcpy_safe (&tmp, sizeof(tmp), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(tmp), error)) return FALSE; if (value != NULL) *value = tmp; return TRUE; } /** * fu_common_read_uint16_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to copy from * @value: (out) (allow-none): the parsed value * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN * @error: A #GError or %NULL * * Read a value from a buffer using a specified endian in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Return value: %TRUE if @value was set, %FALSE otherwise * * Since: 1.3.3 **/ gboolean fu_common_read_uint16_safe (const guint8 *buf, gsize bufsz, gsize offset, guint16 *value, FuEndianType endian, GError **error) { guint8 dst[2] = { 0x0 }; if (!fu_memcpy_safe (dst, sizeof(dst), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(dst), error)) return FALSE; if (value != NULL) *value = fu_common_read_uint16 (dst, endian); return TRUE; } /** * fu_common_read_uint32_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to copy from * @value: (out) (allow-none): the parsed value * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN * @error: A #GError or %NULL * * Read a value from a buffer using a specified endian in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Return value: %TRUE if @value was set, %FALSE otherwise * * Since: 1.3.3 **/ gboolean fu_common_read_uint32_safe (const guint8 *buf, gsize bufsz, gsize offset, guint32 *value, FuEndianType endian, GError **error) { guint8 dst[4] = { 0x0 }; if (!fu_memcpy_safe (dst, sizeof(dst), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(dst), error)) return FALSE; if (value != NULL) *value = fu_common_read_uint32 (dst, endian); return TRUE; } /** * fu_byte_array_append_uint8: * @array: A #GByteArray * @data: #guint8 * * Adds a 8 bit integer to a byte array * * Since: 1.3.1 **/ void fu_byte_array_append_uint8 (GByteArray *array, guint8 data) { g_byte_array_append (array, &data, sizeof(data)); } /** * fu_byte_array_append_uint16: * @array: A #GByteArray * @data: #guint16 * @endian: #FuEndianType * * Adds a 16 bit integer to a byte array * * Since: 1.3.1 **/ void fu_byte_array_append_uint16 (GByteArray *array, guint16 data, FuEndianType endian) { guint8 buf[2]; fu_common_write_uint16 (buf, data, endian); g_byte_array_append (array, buf, sizeof(buf)); } /** * fu_byte_array_append_uint32: * @array: A #GByteArray * @data: #guint32 * @endian: #FuEndianType * * Adds a 32 bit integer to a byte array * * Since: 1.3.1 **/ void fu_byte_array_append_uint32 (GByteArray *array, guint32 data, FuEndianType endian) { guint8 buf[4]; fu_common_write_uint32 (buf, data, endian); g_byte_array_append (array, buf, sizeof(buf)); } /** * fu_common_kernel_locked_down: * * Determines if kernel lockdown in effect * * Since: 1.3.8 **/ gboolean fu_common_kernel_locked_down (void) { #ifndef _WIN32 gsize len = 0; g_autofree gchar *dir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_SECURITY); g_autofree gchar *fname = g_build_filename (dir, "lockdown", NULL); g_autofree gchar *data = NULL; g_auto(GStrv) options = NULL; if (!g_file_test (fname, G_FILE_TEST_EXISTS)) return FALSE; if (!g_file_get_contents (fname, &data, &len, NULL)) return FALSE; if (len < 1) return FALSE; options = g_strsplit (data, " ", -1); for (guint i = 0; options[i] != NULL; i++) { if (g_strcmp0 (options[i], "[none]") == 0) return FALSE; } return TRUE; #else return FALSE; #endif } fwupd-1.3.9/libfwupdplugin/fu-common.h000066400000000000000000000162061362775233600200000ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /** * FuAppFlags: * @FU_APP_FLAGS_NONE: No flags set * @FU_APP_FLAGS_NO_IDLE_SOURCES: Disallow idle sources * * The flags to use when loading an application. **/ typedef enum { FU_APP_FLAGS_NONE = 0, FU_APP_FLAGS_NO_IDLE_SOURCES = 1 << 0, /*< private >*/ FU_APP_FLAGS_LAST } FuAppFlags; /** * FuDumpFlags: * @FU_DUMP_FLAGS_NONE: No flags set * @FU_DUMP_FLAGS_SHOW_ASCII: Show ASCII in debugging dumps * @FU_DUMP_FLAGS_SHOW_ADDRESSES: Show addresses in debugging dumps * * The flags to use when configuring debugging **/ typedef enum { FU_DUMP_FLAGS_NONE = 0, FU_DUMP_FLAGS_SHOW_ASCII = 1 << 0, FU_DUMP_FLAGS_SHOW_ADDRESSES = 1 << 1, /*< private >*/ FU_DUMP_FLAGS_LAST } FuDumpFlags; typedef guint FuEndianType; /** * FuPathKind: * @FU_PATH_KIND_CACHEDIR_PKG: The cache directory (IE /var/cache/fwupd) * @FU_PATH_KIND_DATADIR_PKG: The non-volatile data store (IE /usr/share/fwupd) * @FU_PATH_KIND_EFIAPPDIR: The location to store EFI apps before install (IE /usr/libexec/fwupd/efi) * @FU_PATH_KIND_LOCALSTATEDIR: The local state directory (IE /var) * @FU_PATH_KIND_LOCALSTATEDIR_PKG: The local state directory for the package (IE /var/lib/fwupd) * @FU_PATH_KIND_PLUGINDIR_PKG: The location to look for plugins for package (IE /usr/lib/[triplet]/fwupd-plugins-3) * @FU_PATH_KIND_SYSCONFDIR: The configuration location (IE /etc) * @FU_PATH_KIND_SYSCONFDIR_PKG: The package configuration location (IE /etc/fwupd) * @FU_PATH_KIND_SYSFSDIR_FW: The sysfs firmware location (IE /sys/firmware) * @FU_PATH_KIND_SYSFSDIR_DRIVERS: The platform sysfs directory (IE /sys/bus/platform/drivers) * @FU_PATH_KIND_SYSFSDIR_TPM: The TPM sysfs directory (IE /sys/class/tpm) * @FU_PATH_KIND_POLKIT_ACTIONS: The directory for policy kit actions (IE /usr/share/polkit-1/actions/) * @FU_PATH_KIND_OFFLINE_TRIGGER: The file for the offline trigger (IE /system-update) * @FU_PATH_KIND_SYSFSDIR_SECURITY: The sysfs security location (IE /sys/kernel/security) * * Path types to use when dynamically determining a path at runtime **/ typedef enum { FU_PATH_KIND_CACHEDIR_PKG, FU_PATH_KIND_DATADIR_PKG, FU_PATH_KIND_EFIAPPDIR, FU_PATH_KIND_LOCALSTATEDIR, FU_PATH_KIND_LOCALSTATEDIR_PKG, FU_PATH_KIND_PLUGINDIR_PKG, FU_PATH_KIND_SYSCONFDIR, FU_PATH_KIND_SYSCONFDIR_PKG, FU_PATH_KIND_SYSFSDIR_FW, FU_PATH_KIND_SYSFSDIR_DRIVERS, FU_PATH_KIND_SYSFSDIR_TPM, FU_PATH_KIND_POLKIT_ACTIONS, FU_PATH_KIND_OFFLINE_TRIGGER, FU_PATH_KIND_SYSFSDIR_SECURITY, /*< private >*/ FU_PATH_KIND_LAST } FuPathKind; typedef void (*FuOutputHandler) (const gchar *line, gpointer user_data); gboolean fu_common_spawn_sync (const gchar * const *argv, FuOutputHandler handler_cb, gpointer handler_user_data, guint timeout_ms, GCancellable *cancellable, GError **error); gchar *fu_common_get_path (FuPathKind path_kind); gchar *fu_common_realpath (const gchar *filename, GError **error); gboolean fu_common_fnmatch (const gchar *pattern, const gchar *str); gboolean fu_common_rmtree (const gchar *directory, GError **error); GPtrArray *fu_common_get_files_recursive (const gchar *path, GError **error); gboolean fu_common_mkdir_parent (const gchar *filename, GError **error); gboolean fu_common_set_contents_bytes (const gchar *filename, GBytes *bytes, GError **error); GBytes *fu_common_get_contents_bytes (const gchar *filename, GError **error); GBytes *fu_common_get_contents_fd (gint fd, gsize count, GError **error); gboolean fu_common_extract_archive (GBytes *blob, const gchar *dir, GError **error); GBytes *fu_common_firmware_builder (GBytes *bytes, const gchar *script_fn, const gchar *output_fn, GError **error); GError *fu_common_error_array_get_best (GPtrArray *errors); guint64 fu_common_strtoull (const gchar *str); gchar *fu_common_find_program_in_path (const gchar *basename, GError **error); gchar *fu_common_strstrip (const gchar *str); void fu_common_dump_raw (const gchar *log_domain, const gchar *title, const guint8 *data, gsize len); void fu_common_dump_full (const gchar *log_domain, const gchar *title, const guint8 *data, gsize len, guint columns, FuDumpFlags flags); void fu_common_dump_bytes (const gchar *log_domain, const gchar *title, GBytes *bytes); GBytes *fu_common_bytes_align (GBytes *bytes, gsize blksz, gchar padval); gboolean fu_common_bytes_is_empty (GBytes *bytes); gboolean fu_common_bytes_compare (GBytes *bytes1, GBytes *bytes2, GError **error); gboolean fu_common_bytes_compare_raw (const guint8 *buf1, gsize bufsz1, const guint8 *buf2, gsize bufsz2, GError **error); GBytes *fu_common_bytes_pad (GBytes *bytes, gsize sz); gsize fu_common_strwidth (const gchar *text); gboolean fu_memcpy_safe (guint8 *dst, gsize dst_sz, gsize dst_offset, const guint8 *src, gsize src_sz, gsize src_offset, gsize n, GError **error); gboolean fu_common_read_uint8_safe (const guint8 *buf, gsize bufsz, gsize offset, guint8 *value, GError **error); gboolean fu_common_read_uint16_safe (const guint8 *buf, gsize bufsz, gsize offset, guint16 *value, FuEndianType endian, GError **error); gboolean fu_common_read_uint32_safe (const guint8 *buf, gsize bufsz, gsize offset, guint32 *value, FuEndianType endian, GError **error); void fu_byte_array_append_uint8 (GByteArray *array, guint8 data); void fu_byte_array_append_uint16 (GByteArray *array, guint16 data, FuEndianType endian); void fu_byte_array_append_uint32 (GByteArray *array, guint32 data, FuEndianType endian); void fu_common_write_uint16 (guint8 *buf, guint16 val_native, FuEndianType endian); void fu_common_write_uint32 (guint8 *buf, guint32 val_native, FuEndianType endian); guint16 fu_common_read_uint16 (const guint8 *buf, FuEndianType endian); guint32 fu_common_read_uint32 (const guint8 *buf, FuEndianType endian); guint fu_common_string_replace (GString *string, const gchar *search, const gchar *replace); void fu_common_string_append_kv (GString *str, guint idt, const gchar *key, const gchar *value); void fu_common_string_append_ku (GString *str, guint idt, const gchar *key, guint64 value); void fu_common_string_append_kx (GString *str, guint idt, const gchar *key, guint64 value); void fu_common_string_append_kb (GString *str, guint idt, const gchar *key, gboolean value); gchar **fu_common_strnsplit (const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens); gboolean fu_common_kernel_locked_down (void); fwupd-1.3.9/libfwupdplugin/fu-deprecated.h000066400000000000000000000002541362775233600206040ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once G_BEGIN_DECLS /* indeed, nothing */ G_END_DECLS fwupd-1.3.9/libfwupdplugin/fu-device-locker.c000066400000000000000000000104031362775233600212100ustar00rootroot00000000000000/* * 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. * * Since: 1.0.0 **/ 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. * * Since: 1.0.0 **/ 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.3.9/libfwupdplugin/fu-device-locker.h000066400000000000000000000011661362775233600212230ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); fwupd-1.3.9/libfwupdplugin/fu-device-metadata.h000066400000000000000000000033301362775233600215170ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /** * 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" fwupd-1.3.9/libfwupdplugin/fu-device-private.h000066400000000000000000000032471362775233600214200ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #define fu_device_set_plugin(d,v) fwupd_device_set_plugin(FWUPD_DEVICE(d),v) /** * 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); GType fu_device_get_specialized_gtype (FuDevice *self); 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); gchar *fu_device_get_guids_as_str (FuDevice *self); GPtrArray *fu_device_get_possible_plugins (FuDevice *self); fwupd-1.3.9/libfwupdplugin/fu-device.c000066400000000000000000002140301362775233600177350ustar00rootroot00000000000000/* * 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; gchar *physical_id; gchar *logical_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 */ GType specialized_gtype; GPtrArray *possible_plugins; } FuDevicePrivate; enum { PROP_0, PROP_STATUS, PROP_PROGRESS, PROP_PHYSICAL_ID, PROP_LOGICAL_ID, PROP_QUIRKS, PROP_PARENT, 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, priv->physical_id); break; case PROP_LOGICAL_ID: g_value_set_string (value, priv->logical_id); break; case PROP_QUIRKS: g_value_set_object (value, priv->quirks); break; case PROP_PARENT: g_value_set_object (value, priv->parent); 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; case PROP_PARENT: fu_device_set_parent (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * fu_device_get_possible_plugins: * @self: A #FuDevice * * Gets the list of possible plugin names, typically added from quirk files. * * Returns: (element-type utf8) (transfer container): plugin names * * Since: 1.3.3 **/ GPtrArray * fu_device_get_possible_plugins (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); return g_ptr_array_ref (priv->possible_plugins); } /** * fu_device_add_possible_plugin: * @self: A #FuDevice * @plugin: A plugin name, e.g. `dfu` * * Adds a plugin name to the list of plugins that *might* be able to handle this * device. This is tyically called from a quirk handler. * * Since: 1.3.3 **/ static void fu_device_add_possible_plugin (FuDevice *self, const gchar *plugin) { FuDevicePrivate *priv = GET_PRIVATE (self); g_ptr_array_add (priv->possible_plugins, g_strdup (plugin)); } /** * 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; } /** * fu_device_get_equivalent_id: * @self: A #FuDevice * * Gets any equivalent ID for a device * * Returns: (transfer none): a #gchar or NULL * * Since: 0.6.1 **/ 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; } /** * fu_device_set_equivalent_id: * @self: A #FuDevice * @equivalent_id: A string * * Sets any equivalent ID for a device * * Since: 0.6.1 **/ 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_id: * @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_id: * @self: A #FuDevice * @alternate_id: 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; } /** * fu_device_set_parent: * @self: A #FuDevice * @parent: A #FuDevice * * Sets 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. * * Since: 1.0.8 **/ void fu_device_set_parent (FuDevice *self, FuDevice *parent) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); if (priv->parent != NULL) g_object_remove_weak_pointer (G_OBJECT (priv->parent), (gpointer *) &priv->parent); if (parent != NULL) 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(GRWLockWriterLocker) 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) { FuDevicePrivate *priv = GET_PRIVATE (self); FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); if (g_strcmp0 (key, FU_QUIRKS_PLUGIN) == 0) { fu_device_add_possible_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_PROTOCOL) == 0) { fu_device_set_protocol (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_GTYPE) == 0) { if (priv->specialized_gtype != G_TYPE_INVALID) { g_debug ("already set GType to %s, ignoring %s", g_type_name (priv->specialized_gtype), value); return TRUE; } priv->specialized_gtype = g_type_from_name (value); if (priv->specialized_gtype == G_TYPE_INVALID) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "device GType %s not supported", 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; } /** * fu_device_get_specialized_gtype: * @self: A #FuDevice * * Gets the specialized type of the device * * Returns:#GType * * Since: 1.3.3 **/ GType fu_device_get_specialized_gtype (FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE (self); return priv->specialized_gtype; } static void fu_device_quirks_iter_cb (FuQuirks *quirks, const gchar *key, const gchar *value, gpointer user_data) { FuDevice *self = FU_DEVICE (user_data); 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); } } } static void fu_device_add_guid_quirks (FuDevice *self, const gchar *guid) { FuDevicePrivate *priv = GET_PRIVATE (self); if (priv->quirks == NULL) return; fu_quirks_lookup_by_id_iter (priv->quirks, guid, fu_device_quirks_iter_cb, self); } /** * 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); } /** * fu_device_add_instance_id_full: * @self: A #FuDevice * @instance_id: A Instance ID, e.g. `WacomAES` * @flags: A #FuDeviceInstanceFlags * * Adds an instance ID with all parameters set * * * Since: 1.2.9 **/ 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_remove_metadata: * @self: A #FuDevice * @key: the key * * Removes an item of metadata on the device. * * Since: 1.3.3 **/ void fu_device_remove_metadata (FuDevice *self, const gchar *key) { FuDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GRWLockWriterLocker) 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 (locker != NULL); g_hash_table_remove (priv->metadata, key); } /** * 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(GRWLockWriterLocker) 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); } /** * 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 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); 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) { FuDevicePrivate *priv = GET_PRIVATE (self); 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 (priv->physical_id == 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) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return priv->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) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); g_free (priv->logical_id); priv->logical_id = g_strdup (logical_id); } /** * fu_device_get_protocol: * @self: A #FuDevice * * Gets the protocol ID on the device. * * Returns: a string value e.g. `org.hughski.colorhug`, or %NULL * * Since: 1.3.5 **/ const gchar * fu_device_get_protocol (FuDevice *self) { g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return fwupd_device_get_protocol (FWUPD_DEVICE (self)); } /** * fu_device_set_protocol: * @self: A #FuDevice * @protocol: a defined protocol ID, e.g. `org.hughski.colorhug` * * Sets the protocol ID on the device. * * Since: 1.3.5 **/ void fu_device_set_protocol (FuDevice *self, const gchar *protocol) { g_return_if_fail (FU_IS_DEVICE (self)); fwupd_device_set_protocol (FWUPD_DEVICE (self), protocol); } /** * 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) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DEVICE (self)); g_return_if_fail (physical_id != NULL); g_free (priv->physical_id); priv->physical_id = g_strdup (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) { FuDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DEVICE (self), NULL); return priv->physical_id; } /** * fu_device_add_flag: * @self: A #FuDevice * @flag: A #FwupdDeviceFlags * * Adds a device flag to the device * * Since: 0.1.0 **/ void fu_device_add_flag (FuDevice *self, FwupdDeviceFlags flag) { if (flag & FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE) flag |= FWUPD_DEVICE_FLAG_CAN_VERIFY; if (flag & FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES) flag |= FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED; fwupd_device_add_flag (FWUPD_DEVICE (self), flag); } 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); } /** * 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); } static void fu_device_add_string (FuDevice *self, guint idt, GString *str) { GPtrArray *children; FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); FuDevicePrivate *priv = GET_PRIVATE (self); g_autofree gchar *tmp = NULL; g_autoptr(GList) keys = NULL; g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&priv->metadata_mutex); g_return_if_fail (locker != NULL); /* subclassed type */ fu_common_string_append_kv (str, idt, G_OBJECT_TYPE_NAME (self), 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) fu_common_string_append_kv (str, idt + 1, "AlternateId", priv->alternate_id); if (priv->equivalent_id != NULL) fu_common_string_append_kv (str, idt + 1, "EquivalentId", priv->equivalent_id); if (priv->physical_id != NULL) fu_common_string_append_kv (str, idt + 1, "PhysicalId", priv->physical_id); if (priv->logical_id != NULL) fu_common_string_append_kv (str, idt + 1, "LogicalId", priv->logical_id); if (priv->size_min > 0) { g_autofree gchar *sz = g_strdup_printf ("%" G_GUINT64_FORMAT, priv->size_min); fu_common_string_append_kv (str, idt + 1, "FirmwareSizeMin", sz); } if (priv->size_max > 0) { g_autofree gchar *sz = g_strdup_printf ("%" G_GUINT64_FORMAT, priv->size_max); fu_common_string_append_kv (str, idt + 1, "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); fu_common_string_append_kv (str, idt + 1, key, value); } /* subclassed */ if (klass->to_string != NULL) klass->to_string (self, idt + 1, str); /* print children also */ children = fu_device_get_children (self); if (children != NULL) { for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index (children, i); fu_device_add_string (child, idt + 1, str); } } } /** * 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) { GString *str = g_string_new (NULL); fu_device_add_string (self, 0, 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(FuFirmware) firmware = 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 */ firmware = fu_device_prepare_firmware (self, fw, flags, error); if (firmware == NULL) return FALSE; /* call vfunc */ return klass->write_firmware (self, firmware, 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 **/ FuFirmware * fu_device_prepare_firmware (FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self); FuDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(FuFirmware) firmware = NULL; g_autoptr(GBytes) fw_def = 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) { firmware = klass->prepare_firmware (self, fw, flags, error); if (firmware == NULL) return NULL; } else { firmware = fu_firmware_new_from_bytes (fw); } /* check size */ fw_def = fu_firmware_get_image_default_bytes (firmware, NULL); if (fw_def != NULL) { guint64 fw_sz = (guint64) g_bytes_get_size (fw_def); 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; } } /* success */ return g_steal_pointer (&firmware); } /** * 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 #FuFirmware, or %NULL for error * * Since: 1.0.8 **/ FuFirmware * 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 or device doesn't support */ if (!fu_device_has_flag (self, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE) || 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) return TRUE; /* 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) return TRUE; /* call vfunc */ return klass->attach (self, error); } /** * fu_device_reload: * @self: A #FuDevice * @error: A #GError * * Reloads a device that has just gone from bootloader into application mode. * * Returns: %TRUE on success * * Since: 1.3.3 **/ gboolean fu_device_reload (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->reload == NULL) return TRUE; /* call vfunc */ return klass->reload (self, error); } /** * fu_device_prepare: * @self: A #FuDevice * @flags: A #FwupdInstallFlags * @error: A #GError * * Prepares a device for update. A different plugin can handle each of * FuDevice->prepare(), FuDevice->detach() and FuDevice->write_firmware(). * * Returns: %TRUE on success * * Since: 1.3.3 **/ gboolean fu_device_prepare (FuDevice *self, FwupdInstallFlags flags, 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->prepare == NULL) return TRUE; /* call vfunc */ return klass->prepare (self, flags, error); } /** * fu_device_cleanup: * @self: A #FuDevice * @flags: A #FwupdInstallFlags * @error: A #GError * * Cleans up a device after an update. A different plugin can handle each of * FuDevice->write_firmware(), FuDevice->attach() and FuDevice->cleanup(). * * Returns: %TRUE on success * * Since: 1.3.3 **/ gboolean fu_device_cleanup (FuDevice *self, FwupdInstallFlags flags, 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->cleanup == NULL) return TRUE; /* call vfunc */ return klass->cleanup (self, flags, 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_rescan: * @self: A #FuDevice * @error: A #GError, or %NULL * * Rescans a device, re-adding GUIDs or flags based on some hardware change. * * Returns: %TRUE for success * * Since: 1.3.1 **/ gboolean fu_device_rescan (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); /* remove all GUIDs */ g_ptr_array_set_size (fu_device_get_instance_ids (self), 0); g_ptr_array_set_size (fu_device_get_guids (self), 0); /* subclassed */ if (klass->rescan != NULL) { if (!klass->rescan (self, error)) { fu_device_convert_instance_ids (self); return FALSE; } } fu_device_convert_instance_ids (self); 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. * * 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 *instance_ids = fu_device_get_instance_ids (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->physical_id == NULL && priv_donor->physical_id != NULL) fu_device_set_physical_id (self, priv_donor->physical_id); if (priv->logical_id == NULL && priv_donor->logical_id != NULL) fu_device_set_logical_id (self, priv_donor->logical_id); 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); /* call the set_quirk_kv() vfunc for the superclassed object */ 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); fu_device_add_guid_quirks (self, guid); } } /** * fu_device_incorporate_flag: * @self: A #FuDevice * @donor: Another #FuDevice * @flag: A #FwupdDeviceFlags value * * Copy the value of a specific flag from the donor object. * * Since: 1.3.5 **/ void fu_device_incorporate_flag (FuDevice *self, FuDevice *donor, FwupdDeviceFlags flag) { if (fu_device_has_flag (donor, flag) && !fu_device_has_flag (self, flag)) { g_debug ("donor set %s", fwupd_device_flag_to_string (flag)); fu_device_add_flag (self, flag); } else if (!fu_device_has_flag (donor, flag) && fu_device_has_flag (self, flag)) { g_debug ("donor unset %s", fwupd_device_flag_to_string (flag)); fu_device_remove_flag (self, flag); } } /** * fu_device_incorporate_from_component: (skip): * @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); pspec = g_param_spec_object ("parent", NULL, NULL, FU_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_PARENT, 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); priv->possible_plugins = 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_ptr_array_unref (priv->possible_plugins); g_free (priv->alternate_id); g_free (priv->equivalent_id); g_free (priv->physical_id); g_free (priv->logical_id); G_OBJECT_CLASS (fu_device_parent_class)->finalize (object); } /** * fu_device_new: * * Creates a new #Fudevice * * Since: 0.1.0 **/ FuDevice * fu_device_new (void) { FuDevice *self = g_object_new (FU_TYPE_DEVICE, NULL); return FU_DEVICE (self); } fwupd-1.3.9/libfwupdplugin/fu-device.h000066400000000000000000000270221362775233600177450ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-firmware.h" #include "fu-quirks.h" #include "fu-common-version.h" #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, guint indent, GString *str); gboolean (*write_firmware) (FuDevice *self, FuFirmware *firmware, FwupdInstallFlags flags, GError **error); FuFirmware *(*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); gboolean (*rescan) (FuDevice *self, GError **error); FuFirmware *(*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); gboolean (*reload) (FuDevice *self, GError **error); gboolean (*prepare) (FuDevice *self, FwupdInstallFlags flags, GError **error); gboolean (*cleanup) (FuDevice *self, FwupdInstallFlags flags, GError **error); /*< private >*/ gpointer padding[16]; }; /** * 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_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_version_raw(d,v) fwupd_device_set_version_raw(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_instance_ids(d) fwupd_device_get_instance_ids(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_version_raw(d) fwupd_device_get_version_raw(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); 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_remove_metadata (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_protocol (FuDevice *self); void fu_device_set_protocol (FuDevice *self, const gchar *protocol); void fu_device_add_flag (FuDevice *self, FwupdDeviceFlags flag); 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); FuFirmware *fu_device_prepare_firmware (FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error); FuFirmware *fu_device_read_firmware (FuDevice *self, GError **error); gboolean fu_device_attach (FuDevice *self, GError **error); gboolean fu_device_detach (FuDevice *self, GError **error); gboolean fu_device_reload (FuDevice *self, GError **error); gboolean fu_device_prepare (FuDevice *self, FwupdInstallFlags flags, GError **error); gboolean fu_device_cleanup (FuDevice *self, FwupdInstallFlags flags, GError **error); void fu_device_incorporate (FuDevice *self, FuDevice *donor); void fu_device_incorporate_flag (FuDevice *self, FuDevice *donor, FwupdDeviceFlags flag); 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_rescan (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); fwupd-1.3.9/libfwupdplugin/fu-dfu-firmware.c000066400000000000000000000272751362775233600211030ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-common.h" #include "fu-dfu-firmware.h" /** * SECTION:fu-dfu-firmware * @short_description: DFU firmware image * * An object that represents a DFU firmware image. * * See also: #FuFirmware */ typedef struct { guint16 vid; guint16 pid; guint16 release; guint16 version; } FuDfuFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuDfuFirmware, fu_dfu_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_dfu_firmware_get_instance_private (o)) static void fu_dfu_firmware_to_string (FuFirmware *firmware, guint idt, GString *str) { FuDfuFirmware *self = FU_DFU_FIRMWARE (firmware); FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); fu_common_string_append_kx (str, idt, "Vid", priv->vid); fu_common_string_append_kx (str, idt, "Pid", priv->pid); fu_common_string_append_kx (str, idt, "Release", priv->release); fu_common_string_append_kx (str, idt, "Version", priv->version); } /** * fu_dfu_firmware_get_vid: * @self: a #FuDfuFirmware * * Gets the vendor ID, or 0xffff for no restriction. * * Return value: integer * * Since: 1.3.3 **/ guint16 fu_dfu_firmware_get_vid (FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DFU_FIRMWARE (self), 0x0); return priv->vid; } /** * fu_dfu_firmware_get_pid: * @self: a #FuDfuFirmware * * Gets the product ID, or 0xffff for no restriction. * * Return value: integer * * Since: 1.3.3 **/ guint16 fu_dfu_firmware_get_pid (FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DFU_FIRMWARE (self), 0x0); return priv->pid; } /** * fu_dfu_firmware_get_release: * @self: a #FuDfuFirmware * * Gets the device ID, or 0xffff for no restriction. * * Return value: integer * * Since: 1.3.3 **/ guint16 fu_dfu_firmware_get_release (FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DFU_FIRMWARE (self), 0x0); return priv->release; } /** * fu_dfu_firmware_get_version: * @self: a #FuDfuFirmware * * Gets the file format version with is 0x0100 by default. * * Return value: integer * * Since: 1.3.3 **/ guint16 fu_dfu_firmware_get_version (FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_DFU_FIRMWARE (self), 0x0); return priv->version; } /** * fu_dfu_firmware_set_vid: * @self: a #FuDfuFirmware * @vid: vendor ID, or 0xffff if the firmware should match any vendor * * Sets the vendor ID. * * Since: 1.3.3 **/ void fu_dfu_firmware_set_vid (FuDfuFirmware *self, guint16 vid) { FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DFU_FIRMWARE (self)); priv->vid = vid; } /** * fu_dfu_firmware_set_pid: * @self: a #FuDfuFirmware * @pid: product ID, or 0xffff if the firmware should match any product * * Sets the product ID. * * Since: 1.3.3 **/ void fu_dfu_firmware_set_pid (FuDfuFirmware *self, guint16 pid) { FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DFU_FIRMWARE (self)); priv->pid = pid; } /** * fu_dfu_firmware_set_release: * @self: a #FuDfuFirmware * @release: release, or 0xffff if the firmware should match any release * * Sets the release for the dfu firmware. * * Since: 1.3.3 **/ void fu_dfu_firmware_set_release (FuDfuFirmware *self, guint16 release) { FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DFU_FIRMWARE (self)); priv->release = release; } /** * fu_dfu_firmware_set_version: * @self: a #FuDfuFirmware * @version: integer * * Sets the file format version. * * Since: 1.3.3 **/ void fu_dfu_firmware_set_version (FuDfuFirmware *self, guint16 version) { FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_DFU_FIRMWARE (self)); priv->version = version; } 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 fu_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; } typedef struct __attribute__((packed)) { guint16 release; guint16 pid; guint16 vid; guint16 ver; guint8 sig[3]; guint8 len; guint32 crc; } FuDfuFirmwareFooter; static gboolean fu_dfu_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuDfuFirmware *self = FU_DFU_FIRMWARE (firmware); FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); FuDfuFirmwareFooter ftr; gsize len; guint32 crc; guint32 crc_new; guint8 *data; g_autoptr(FuFirmwareImage) image = NULL; g_autoptr(GBytes) contents = NULL; /* check data size */ data = (guint8 *) g_bytes_get_data (fw, &len); if (len < sizeof(FuDfuFirmwareFooter)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "size check failed, too small"); return FALSE; } /* check for DFU signature */ if (memcmp (&data[len - G_STRUCT_OFFSET (FuDfuFirmwareFooter, sig)], "UFD", sizeof(ftr.sig)) != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no DFU signature"); return FALSE; } /* verify the checksum */ if (!fu_memcpy_safe ((guint8 *) &ftr, sizeof(FuDfuFirmwareFooter), 0x0, /* dst */ data, len, len - sizeof(FuDfuFirmwareFooter), /* src */ sizeof(FuDfuFirmwareFooter), error)) return FALSE; crc = GUINT32_FROM_LE(ftr.crc); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { crc_new = fu_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 */ priv->vid = GUINT16_FROM_LE(ftr.vid); priv->pid = GUINT16_FROM_LE(ftr.pid); priv->release = GUINT16_FROM_LE(ftr.release); priv->version = GUINT16_FROM_LE(ftr.ver); /* 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; } /* success */ contents = g_bytes_new_from_bytes (fw, 0, len - ftr.len); image = fu_firmware_image_new (contents); fu_firmware_add_image (firmware, image); return TRUE; } static GBytes * fu_dfu_firmware_add_footer (FuDfuFirmware *self, GBytes *contents, GError **error) { FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); GByteArray *buf = g_byte_array_new (); const guint8 *blob; gsize blobsz = 0; /* add the raw firmware data */ blob = g_bytes_get_data (contents, &blobsz); g_byte_array_append (buf, blob, blobsz); /* append footer */ fu_byte_array_append_uint16 (buf, priv->release, G_LITTLE_ENDIAN); fu_byte_array_append_uint16 (buf, priv->pid, G_LITTLE_ENDIAN); fu_byte_array_append_uint16 (buf, priv->vid, G_LITTLE_ENDIAN); fu_byte_array_append_uint16 (buf, priv->version, G_LITTLE_ENDIAN); g_byte_array_append (buf, (const guint8 *) "UFD", 3); fu_byte_array_append_uint8 (buf, sizeof(FuDfuFirmwareFooter)); fu_byte_array_append_uint32 (buf, fu_dfu_firmware_generate_crc32 (buf->data, buf->len), G_LITTLE_ENDIAN); return g_byte_array_free_to_bytes (buf); } static GBytes * fu_dfu_firmware_write (FuFirmware *firmware, GError **error) { FuDfuFirmware *self = FU_DFU_FIRMWARE (firmware); g_autoptr(GPtrArray) images = fu_firmware_get_images (firmware); g_autoptr(GBytes) fw = NULL; /* can only contain one image */ if (images->len > 1) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "DFU only supports writing one image"); return NULL; } /* add footer */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return NULL; return fu_dfu_firmware_add_footer (self, fw, error); } static void fu_dfu_firmware_init (FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE (self); priv->vid = 0xffff; priv->pid = 0xffff; priv->release = 0xffff; priv->version = 0x0100; } static void fu_dfu_firmware_class_init (FuDfuFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->to_string = fu_dfu_firmware_to_string; klass_firmware->parse = fu_dfu_firmware_parse; klass_firmware->write = fu_dfu_firmware_write; } /** * fu_dfu_firmware_new: * * Creates a new #FuFirmware of sub type Dfu * * Since: 1.3.3 **/ FuFirmware * fu_dfu_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_DFU_FIRMWARE, NULL)); } fwupd-1.3.9/libfwupdplugin/fu-dfu-firmware.h000066400000000000000000000017101362775233600210720ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_DFU_FIRMWARE (fu_dfu_firmware_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuDfuFirmware, fu_dfu_firmware, FU, DFU_FIRMWARE, FuFirmware) struct _FuDfuFirmwareClass { FuFirmwareClass parent_class; }; FuFirmware *fu_dfu_firmware_new (void); guint16 fu_dfu_firmware_get_vid (FuDfuFirmware *self); guint16 fu_dfu_firmware_get_pid (FuDfuFirmware *self); guint16 fu_dfu_firmware_get_release (FuDfuFirmware *self); guint16 fu_dfu_firmware_get_version (FuDfuFirmware *self); void fu_dfu_firmware_set_vid (FuDfuFirmware *self, guint16 vid); void fu_dfu_firmware_set_pid (FuDfuFirmware *self, guint16 pid); void fu_dfu_firmware_set_release (FuDfuFirmware *self, guint16 release); void fu_dfu_firmware_set_version (FuDfuFirmware *self, guint16 version); fwupd-1.3.9/libfwupdplugin/fu-firmware-common.c000066400000000000000000000052461362775233600216070ustar00rootroot00000000000000/* * Copyright (C) 2015-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include #include #include "fu-firmware-common.h" /** * fu_firmware_strparse_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 * * Since: 1.3.1 **/ guint8 fu_firmware_strparse_uint4 (const gchar *data) { gchar buffer[2]; memcpy (buffer, data, 1); buffer[1] = '\0'; return (guint8) g_ascii_strtoull (buffer, NULL, 16); } /** * fu_firmware_strparse_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 * * Since: 1.3.1 **/ guint8 fu_firmware_strparse_uint8 (const gchar *data) { gchar buffer[3]; memcpy (buffer, data, 2); buffer[2] = '\0'; return (guint8) g_ascii_strtoull (buffer, NULL, 16); } /** * fu_firmware_strparse_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 * * Since: 1.3.1 **/ guint16 fu_firmware_strparse_uint16 (const gchar *data) { gchar buffer[5]; memcpy (buffer, data, 4); buffer[4] = '\0'; return (guint16) g_ascii_strtoull (buffer, NULL, 16); } /** * fu_firmware_strparse_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 * * Since: 1.3.1 **/ guint32 fu_firmware_strparse_uint24 (const gchar *data) { gchar buffer[7]; memcpy (buffer, data, 6); buffer[6] = '\0'; return (guint32) g_ascii_strtoull (buffer, NULL, 16); } /** * fu_firmware_strparse_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 * * Since: 1.3.1 **/ guint32 fu_firmware_strparse_uint32 (const gchar *data) { gchar buffer[9]; memcpy (buffer, data, 8); buffer[8] = '\0'; return (guint32) g_ascii_strtoull (buffer, NULL, 16); } fwupd-1.3.9/libfwupdplugin/fu-firmware-common.h000066400000000000000000000006751362775233600216150ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include guint8 fu_firmware_strparse_uint4 (const gchar *data); guint8 fu_firmware_strparse_uint8 (const gchar *data); guint16 fu_firmware_strparse_uint16 (const gchar *data); guint32 fu_firmware_strparse_uint24 (const gchar *data); guint32 fu_firmware_strparse_uint32 (const gchar *data); fwupd-1.3.9/libfwupdplugin/fu-firmware-image-private.h000066400000000000000000000004021362775233600230430ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware-image.h" void fu_firmware_image_add_string (FuFirmwareImage *self, guint idt, GString *str); fwupd-1.3.9/libfwupdplugin/fu-firmware-image.c000066400000000000000000000215331362775233600213760ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-common.h" #include "fu-firmware-image-private.h" /** * SECTION:fu-firmware_image * @short_description: a firmware_image file * * An object that represents a firmware_image file. */ typedef struct { gchar *id; GBytes *bytes; guint64 addr; guint64 idx; gchar *version; } FuFirmwareImagePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuFirmwareImage, fu_firmware_image, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_firmware_image_get_instance_private (o)) /** * fu_firmware_image_get_version: * @self: A #FuFirmwareImage * * Gets an optional version that represents the firmware image. * * Returns: a string, or %NULL * * Since: 1.3.4 **/ const gchar * fu_firmware_image_get_version (FuFirmwareImage *self) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL); return priv->version; } /** * fu_firmware_image_set_version: * @self: A #FuFirmwareImage * @version: (nullable): A string version, or %NULL * * Sets an optional version that represents the firmware image. * * Since: 1.3.4 **/ void fu_firmware_image_set_version (FuFirmwareImage *self, const gchar *version) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self)); g_free (priv->version); priv->version = g_strdup (version); } /** * fu_firmware_image_set_id: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. "config" * * Since: 1.3.1 **/ void fu_firmware_image_set_id (FuFirmwareImage *self, const gchar *id) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self)); g_free (priv->id); priv->id = g_strdup (id); } /** * fu_firmware_image_get_id: * @self: a #FuPlugin * * Gets the image ID, typically set at construction. * * Returns: image ID, e.g. "config" * * Since: 1.3.1 **/ const gchar * fu_firmware_image_get_id (FuFirmwareImage *self) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL); return priv->id; } /** * fu_firmware_image_set_addr: * @self: a #FuPlugin * @addr: integer * * Sets the base address of the image. * * Since: 1.3.1 **/ void fu_firmware_image_set_addr (FuFirmwareImage *self, guint64 addr) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self)); priv->addr = addr; } /** * fu_firmware_image_get_addr: * @self: a #FuPlugin * * Gets the base address of the image. * * Returns: integer * * Since: 1.3.1 **/ guint64 fu_firmware_image_get_addr (FuFirmwareImage *self) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), G_MAXUINT64); return priv->addr; } /** * fu_firmware_image_set_idx: * @self: a #FuPlugin * @idx: integer * * Sets the index of the image which is used for ordering. * * Since: 1.3.1 **/ void fu_firmware_image_set_idx (FuFirmwareImage *self, guint64 idx) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self)); priv->idx = idx; } /** * fu_firmware_image_get_idx: * @self: a #FuPlugin * * Gets the index of the image which is used for ordering. * * Returns: integer * * Since: 1.3.1 **/ guint64 fu_firmware_image_get_idx (FuFirmwareImage *self) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), G_MAXUINT64); return priv->idx; } /** * fu_firmware_image_set_bytes: * @self: a #FuPlugin * @bytes: A #GBytes * * Sets the contents of the image if not created with fu_firmware_image_new(). * * Since: 1.3.1 **/ void fu_firmware_image_set_bytes (FuFirmwareImage *self, GBytes *bytes) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self)); g_return_if_fail (bytes != NULL); g_return_if_fail (priv->bytes == NULL); priv->bytes = g_bytes_ref (bytes); } /** * fu_firmware_image_write: * @self: a #FuPlugin * @error: A #GError, or %NULL * * Writes the image, which will try to call a superclassed ->write() function. * * By default (and in most cases) this just provides the value set by the * fu_firmware_image_set_bytes() function. * * Returns: (transfer full): a #GBytes of the bytes, or %NULL if the bytes is not set * * Since: 1.3.3 **/ GBytes * fu_firmware_image_write (FuFirmwareImage *self, GError **error) { FuFirmwareImageClass *klass = FU_FIRMWARE_IMAGE_GET_CLASS (self); FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* optional vfunc */ if (klass->write != NULL) return klass->write (self, error); /* fall back to what was set manually */ if (priv->bytes == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no bytes found in firmware bytes %s", priv->id); return NULL; } return g_bytes_ref (priv->bytes); } /** * fu_firmware_image_write_chunk: * @self: a #FuFirmwareImage * @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 image. If the contents of the image is * smaller than the requested chunk size then the #GBytes will be smaller * than @chunk_sz_max. Use fu_common_bytes_pad() if padding is required. * * If the @address is larger than the size of the image then an error is returned. * * Return value: (transfer full): a #GBytes, or %NULL * * Since: 1.3.1 **/ GBytes * fu_firmware_image_write_chunk (FuFirmwareImage *self, guint64 address, guint64 chunk_sz_max, GError **error) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); gsize chunk_left; guint64 offset; /* check address requested is larger than base address */ if (address < priv->addr) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "requested address 0x%x less than base address 0x%x", (guint) address, (guint) priv->addr); return NULL; } /* offset into data */ offset = address - priv->addr; if (offset > g_bytes_get_size (priv->bytes)) { 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 (priv->bytes)); return NULL; } /* if we have less data than requested */ chunk_left = g_bytes_get_size (priv->bytes) - offset; if (chunk_sz_max > chunk_left) return g_bytes_new_from_bytes (priv->bytes, offset, chunk_left); /* check chunk */ return g_bytes_new_from_bytes (priv->bytes, offset, chunk_sz_max); } void fu_firmware_image_add_string (FuFirmwareImage *self, guint idt, GString *str) { FuFirmwareImagePrivate *priv = GET_PRIVATE (self); FuFirmwareImageClass *klass = FU_FIRMWARE_IMAGE_GET_CLASS (self); fu_common_string_append_kv (str, idt, G_OBJECT_TYPE_NAME (self), NULL); if (priv->id != NULL) fu_common_string_append_kv (str, idt, "ID", priv->id); if (priv->idx != 0x0) fu_common_string_append_kx (str, idt, "Index", priv->idx); if (priv->addr != 0x0) fu_common_string_append_kx (str, idt, "Address", priv->addr); if (priv->version != NULL) fu_common_string_append_kv (str, 0, "Version", priv->version); if (priv->bytes != NULL) { fu_common_string_append_kx (str, idt, "Data", g_bytes_get_size (priv->bytes)); } /* vfunc */ if (klass->to_string != NULL) klass->to_string (self, idt, str); } /** * fu_firmware_image_to_string: * @self: A #FuFirmwareImage * * This allows us to easily print the object. * * Returns: a string value, or %NULL for invalid. * * Since: 1.3.1 **/ gchar * fu_firmware_image_to_string (FuFirmwareImage *self) { GString *str = g_string_new (NULL); fu_firmware_image_add_string (self, 0, str); return g_string_free (str, FALSE); } static void fu_firmware_image_init (FuFirmwareImage *self) { } static void fu_firmware_image_finalize (GObject *object) { FuFirmwareImage *self = FU_FIRMWARE_IMAGE (object); FuFirmwareImagePrivate *priv = GET_PRIVATE (self); g_free (priv->id); g_free (priv->version); if (priv->bytes != NULL) g_bytes_unref (priv->bytes); G_OBJECT_CLASS (fu_firmware_image_parent_class)->finalize (object); } static void fu_firmware_image_class_init (FuFirmwareImageClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_firmware_image_finalize; } /** * fu_firmware_image_new: * @bytes: Optional #GBytes * * Creates an empty firmware_image object. * * Returns: a #FuFirmwareImage * * Since: 1.3.1 **/ FuFirmwareImage * fu_firmware_image_new (GBytes *bytes) { FuFirmwareImage *self = g_object_new (FU_TYPE_FIRMWARE_IMAGE, NULL); if (bytes != NULL) fu_firmware_image_set_bytes (self, bytes); return self; } fwupd-1.3.9/libfwupdplugin/fu-firmware-image.h000066400000000000000000000035351362775233600214050ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #define FU_TYPE_FIRMWARE_IMAGE (fu_firmware_image_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuFirmwareImage, fu_firmware_image, FU, FIRMWARE_IMAGE, GObject) struct _FuFirmwareImageClass { GObjectClass parent_class; gboolean (*parse) (FuFirmwareImage *self, GBytes *fw, FwupdInstallFlags flags, GError **error); void (*to_string) (FuFirmwareImage *self, guint idt, GString *str); GBytes *(*write) (FuFirmwareImage *self, GError **error); /*< private >*/ gpointer padding[28]; }; #define FU_FIRMWARE_IMAGE_ID_PAYLOAD "payload" #define FU_FIRMWARE_IMAGE_ID_SIGNATURE "signature" #define FU_FIRMWARE_IMAGE_ID_HEADER "header" FuFirmwareImage *fu_firmware_image_new (GBytes *bytes); gchar *fu_firmware_image_to_string (FuFirmwareImage *self); const gchar *fu_firmware_image_get_version (FuFirmwareImage *self); void fu_firmware_image_set_version (FuFirmwareImage *self, const gchar *version); const gchar *fu_firmware_image_get_id (FuFirmwareImage *self); void fu_firmware_image_set_id (FuFirmwareImage *self, const gchar *id); guint64 fu_firmware_image_get_addr (FuFirmwareImage *self); void fu_firmware_image_set_addr (FuFirmwareImage *self, guint64 addr); guint64 fu_firmware_image_get_idx (FuFirmwareImage *self); void fu_firmware_image_set_idx (FuFirmwareImage *self, guint64 idx); void fu_firmware_image_set_bytes (FuFirmwareImage *self, GBytes *bytes); GBytes *fu_firmware_image_write (FuFirmwareImage *self, GError **error); GBytes *fu_firmware_image_write_chunk (FuFirmwareImage *self, guint64 address, guint64 chunk_sz_max, GError **error); fwupd-1.3.9/libfwupdplugin/fu-firmware.c000066400000000000000000000322521362775233600203160ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-common.h" #include "fu-firmware.h" #include "fu-firmware-image-private.h" /** * SECTION:fu-firmware * @short_description: a firmware file * * An object that represents a firmware file. * See also: #FuDfuFirmware, #FuIhexFirmware, #FuSrecFirmware */ typedef struct { GPtrArray *images; /* FuFirmwareImage */ gchar *version; } FuFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuFirmware, fu_firmware, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_firmware_get_instance_private (o)) /** * fu_firmware_get_version: * @self: A #FuFirmware * * Gets an optional version that represents the firmware. * * Returns: a string, or %NULL * * Since: 1.3.3 **/ const gchar * fu_firmware_get_version (FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_FIRMWARE (self), NULL); return priv->version; } /** * fu_firmware_set_version: * @self: A #FuFirmware * @version: A string version, or %NULL * * Sets an optional version that represents the firmware. * * Since: 1.3.3 **/ void fu_firmware_set_version (FuFirmware *self, const gchar *version) { FuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_FIRMWARE (self)); g_free (priv->version); priv->version = g_strdup (version); } /** * fu_firmware_tokenize: * @self: A #FuFirmware * @fw: A #GBytes * @flags: some #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: A #GError, or %NULL * * Tokenizes a firmware, typically breaking the firmware into records. * * Records can be enumerated using subclass-specific functionality, for example * using fu_srec_firmware_get_records(). * * Returns: %TRUE for success * * Since: 1.3.2 **/ gboolean fu_firmware_tokenize (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS (self); g_return_val_if_fail (FU_IS_FIRMWARE (self), FALSE); g_return_val_if_fail (fw != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* optionally subclassed */ if (klass->tokenize != NULL) return klass->tokenize (self, fw, flags, error); return TRUE; } /** * fu_firmware_parse_full: * @self: A #FuFirmware * @fw: A #GBytes * @addr_start: Start address, useful for ignoring a bootloader * @addr_end: End address, useful for ignoring config bytes * @flags: some #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: A #GError, or %NULL * * Parses a firmware, typically breaking the firmware into images. * * Returns: %TRUE for success * * Since: 1.3.1 **/ gboolean fu_firmware_parse_full (FuFirmware *self, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS (self); g_autoptr(FuFirmwareImage) img = NULL; g_return_val_if_fail (FU_IS_FIRMWARE (self), FALSE); g_return_val_if_fail (fw != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* subclassed */ if (klass->tokenize != NULL) { if (!klass->tokenize (self, fw, flags, error)) return FALSE; } if (klass->parse != NULL) return klass->parse (self, fw, addr_start, addr_end, flags, error); /* just add entire blob */ img = fu_firmware_image_new (fw); fu_firmware_add_image (self, img); return TRUE; } /** * fu_firmware_parse: * @self: A #FuFirmware * @fw: A #GBytes * @flags: some #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: A #GError, or %NULL * * Parses a firmware, typically breaking the firmware into images. * * Returns: %TRUE for success * * Since: 1.3.1 **/ gboolean fu_firmware_parse (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { return fu_firmware_parse_full (self, fw, 0x0, 0x0, flags, error); } /** * fu_firmware_parse_file: * @self: A #FuFirmware * @file: A #GFile * @flags: some #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: A #GError, or %NULL * * Parses a firmware file, typically breaking the firmware into images. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_firmware_parse_file (FuFirmware *self, GFile *file, FwupdInstallFlags flags, GError **error) { gchar *buf = NULL; gsize bufsz = 0; g_autoptr(GBytes) fw = NULL; if (!g_file_load_contents (file, NULL, &buf, &bufsz, NULL, error)) return FALSE; fw = g_bytes_new_take (buf, bufsz); return fu_firmware_parse (self, fw, flags, error); } /** * fu_firmware_write: * @self: A #FuFirmware * @error: A #GError, or %NULL * * Writes a firmware, typically packing the images into a binary blob. * * Returns: (transfer full): a #GBytes * * Since: 1.3.1 **/ GBytes * fu_firmware_write (FuFirmware *self, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS (self); g_return_val_if_fail (FU_IS_FIRMWARE (self), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* subclassed */ if (klass->write != NULL) return klass->write (self, error); /* just add default blob */ return fu_firmware_get_image_default_bytes (self, error); } /** * fu_firmware_write_file: * @self: A #FuFirmware * @file: A #GFile * @error: A #GError, or %NULL * * Writes a firmware, typically packing the images into a binary blob. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_firmware_write_file (FuFirmware *self, GFile *file, GError **error) { g_autoptr(GBytes) blob = NULL; blob = fu_firmware_write (self, error); if (blob == NULL) return FALSE; return g_file_replace_contents (file, g_bytes_get_data (blob, NULL), g_bytes_get_size (blob), NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, error); } /** * fu_firmware_add_image: * @self: a #FuPlugin * @img: A #FuFirmwareImage * * Adds an image to the firmware. * * If an image with the same ID is already present it is replaced. * * Since: 1.3.1 **/ void fu_firmware_add_image (FuFirmware *self, FuFirmwareImage *img) { FuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_FIRMWARE (self)); g_return_if_fail (FU_IS_FIRMWARE_IMAGE (img)); g_ptr_array_add (priv->images, g_object_ref (img)); } /** * fu_firmware_get_images: * @self: a #FuFirmware * * Returns all the images in the firmware. * * Returns: (transfer container) (element-type FuFirmwareImage): images * * Since: 1.3.1 **/ GPtrArray * fu_firmware_get_images (FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE (self); g_autoptr(GPtrArray) imgs = NULL; g_return_val_if_fail (FU_IS_FIRMWARE (self), NULL); imgs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < priv->images->len; i++) { FuFirmwareImage *img = g_ptr_array_index (priv->images, i); g_ptr_array_add (imgs, g_object_ref (img)); } return g_steal_pointer (&imgs); } /** * fu_firmware_get_image_by_id: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. "config" * @error: A #GError, or %NULL * * Gets the firmware image using the image ID. * * Returns: (transfer full): a #FuFirmwareImage, or %NULL if the image is not found * * Since: 1.3.1 **/ FuFirmwareImage * fu_firmware_get_image_by_id (FuFirmware *self, const gchar *id, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_FIRMWARE (self), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); for (guint i = 0; i < priv->images->len; i++) { FuFirmwareImage *img = g_ptr_array_index (priv->images, i); if (g_strcmp0 (fu_firmware_image_get_id (img), id) == 0) return g_object_ref (img); } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no image id %s found in firmware", id); return NULL; } /** * fu_firmware_get_image_by_id_bytes: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. "config" * @error: A #GError, or %NULL * * Gets the firmware image bytes using the image ID. * * Returns: (transfer full): a #GBytes of a #FuFirmwareImage, or %NULL if the image is not found * * Since: 1.3.1 **/ GBytes * fu_firmware_get_image_by_id_bytes (FuFirmware *self, const gchar *id, GError **error) { g_autoptr(FuFirmwareImage) img = fu_firmware_get_image_by_id (self, id, error); if (img == NULL) return NULL; return fu_firmware_image_write (img, error); } /** * fu_firmware_get_image_by_idx: * @self: a #FuPlugin * @idx: image index * @error: A #GError, or %NULL * * Gets the firmware image using the image index. * * Returns: (transfer full): a #FuFirmwareImage, or %NULL if the image is not found * * Since: 1.3.1 **/ FuFirmwareImage * fu_firmware_get_image_by_idx (FuFirmware *self, guint64 idx, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_FIRMWARE (self), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); for (guint i = 0; i < priv->images->len; i++) { FuFirmwareImage *img = g_ptr_array_index (priv->images, i); if (fu_firmware_image_get_idx (img) == idx) return g_object_ref (img); } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no image idx %" G_GUINT64_FORMAT " found in firmware", idx); return NULL; } /** * fu_firmware_get_image_by_idx_bytes: * @self: a #FuPlugin * @idx: image index * @error: A #GError, or %NULL * * Gets the firmware image bytes using the image index. * * Returns: (transfer full): a #GBytes of a #FuFirmwareImage, or %NULL if the image is not found * * Since: 1.3.1 **/ GBytes * fu_firmware_get_image_by_idx_bytes (FuFirmware *self, guint64 idx, GError **error) { g_autoptr(FuFirmwareImage) img = fu_firmware_get_image_by_idx (self, idx, error); if (img == NULL) return NULL; return fu_firmware_image_write (img, error); } /** * fu_firmware_get_image_default: * @self: a #FuPlugin * @error: A #GError, or %NULL * * Gets the default firmware image. * * NOTE: If the firmware has multiple images included then fu_firmware_get_image_by_id() * or fu_firmware_get_image_by_idx() must be used rather than this function. * * Returns: (transfer full): a #FuFirmwareImage, or %NULL if the image is not found * * Since: 1.3.1 **/ FuFirmwareImage * fu_firmware_get_image_default (FuFirmware *self, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE (self); if (priv->images->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no images in firmware"); return NULL; } if (priv->images->len > 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "multiple images present in firmware"); return NULL; } return g_object_ref (FU_FIRMWARE_IMAGE (g_ptr_array_index (priv->images, 0))); } /** * fu_firmware_get_image_default_bytes: * @self: a #FuPlugin * @error: A #GError, or %NULL * * Gets the default firmware image. * * Returns: (transfer full): a #GBytes of the image, or %NULL if the image is not found * * Since: 1.3.1 **/ GBytes * fu_firmware_get_image_default_bytes (FuFirmware *self, GError **error) { g_autoptr(FuFirmwareImage) img = fu_firmware_get_image_default (self, error); if (img == NULL) return NULL; return fu_firmware_image_write (img, error); } /** * fu_firmware_to_string: * @self: A #FuFirmware * * This allows us to easily print the object. * * Returns: a string value, or %NULL for invalid. * * Since: 1.3.1 **/ gchar * fu_firmware_to_string (FuFirmware *self) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS (self); FuFirmwarePrivate *priv = GET_PRIVATE (self); GString *str = g_string_new (NULL); /* subclassed type */ fu_common_string_append_kv (str, 0, G_OBJECT_TYPE_NAME (self), NULL); if (priv->version != NULL) fu_common_string_append_kv (str, 0, "Version", priv->version); /* vfunc */ if (klass->to_string != NULL) klass->to_string (self, 0, str); for (guint i = 0; i < priv->images->len; i++) { FuFirmwareImage *img = g_ptr_array_index (priv->images, i); fu_firmware_image_add_string (img, 1, str); } return g_string_free (str, FALSE); } static void fu_firmware_init (FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE (self); priv->images = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); } static void fu_firmware_finalize (GObject *object) { FuFirmware *self = FU_FIRMWARE (object); FuFirmwarePrivate *priv = GET_PRIVATE (self); g_free (priv->version); g_ptr_array_unref (priv->images); G_OBJECT_CLASS (fu_firmware_parent_class)->finalize (object); } static void fu_firmware_class_init (FuFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_firmware_finalize; } /** * fu_firmware_new: * * Creates an empty firmware object. * * Returns: a #FuFirmware * * Since: 1.3.1 **/ FuFirmware * fu_firmware_new (void) { FuFirmware *self = g_object_new (FU_TYPE_FIRMWARE, NULL); return FU_FIRMWARE (self); } /** * fu_firmware_new_from_bytes: * @fw: A #GBytes image * * Creates a firmware object with the provided image set as default. * * Returns: a #FuFirmware * * Since: 1.3.1 **/ FuFirmware * fu_firmware_new_from_bytes (GBytes *fw) { FuFirmware *self = fu_firmware_new (); g_autoptr(FuFirmwareImage) img = NULL; img = fu_firmware_image_new (fw); fu_firmware_add_image (self, img); return self; } fwupd-1.3.9/libfwupdplugin/fu-firmware.h000066400000000000000000000052221362775233600203200ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-firmware-image.h" #define FU_TYPE_FIRMWARE (fu_firmware_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuFirmware, fu_firmware, FU, FIRMWARE, GObject) struct _FuFirmwareClass { GObjectClass parent_class; gboolean (*parse) (FuFirmware *self, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error); GBytes *(*write) (FuFirmware *self, GError **error); void (*to_string) (FuFirmware *self, guint indent, GString *str); gboolean (*tokenize) (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error); /*< private >*/ gpointer padding[28]; }; FuFirmware *fu_firmware_new (void); FuFirmware *fu_firmware_new_from_bytes (GBytes *fw); gchar *fu_firmware_to_string (FuFirmware *self); const gchar *fu_firmware_get_version (FuFirmware *self); void fu_firmware_set_version (FuFirmware *self, const gchar *version); gboolean fu_firmware_tokenize (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error); gboolean fu_firmware_parse (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error); gboolean fu_firmware_parse_file (FuFirmware *self, GFile *file, FwupdInstallFlags flags, GError **error); gboolean fu_firmware_parse_full (FuFirmware *self, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error); GBytes *fu_firmware_write (FuFirmware *self, GError **error); gboolean fu_firmware_write_file (FuFirmware *self, GFile *file, GError **error); void fu_firmware_add_image (FuFirmware *self, FuFirmwareImage *img); GPtrArray *fu_firmware_get_images (FuFirmware *self); FuFirmwareImage *fu_firmware_get_image_by_id (FuFirmware *self, const gchar *id, GError **error); GBytes *fu_firmware_get_image_by_id_bytes (FuFirmware *self, const gchar *id, GError **error); FuFirmwareImage *fu_firmware_get_image_by_idx (FuFirmware *self, guint64 idx, GError **error); GBytes *fu_firmware_get_image_by_idx_bytes (FuFirmware *self, guint64 idx, GError **error); FuFirmwareImage *fu_firmware_get_image_default (FuFirmware *self, GError **error); GBytes *fu_firmware_get_image_default_bytes (FuFirmware *self, GError **error); fwupd-1.3.9/libfwupdplugin/fu-hash.py000066400000000000000000000015771362775233600176410ustar00rootroot00000000000000#!/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) m = hashlib.sha256() for argv in sys.argv[2:]: with open(argv, 'rb') as f: m.update(f.read()) with open(sys.argv[1], 'w') as f2: f2.write('#pragma once\n') f2.write('#define FU_BUILD_HASH "%s"\n' % m.hexdigest()) fwupd-1.3.9/libfwupdplugin/fu-hwids.c000066400000000000000000000313041362775233600176150ustar00rootroot00000000000000/* * 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 * * Since: 0.9.3 **/ 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 * * Since: 0.9.3 **/ 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 utf8): An array of GUIDs * * Since: 0.9.3 **/ 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. * * Since: 0.9.3 **/ 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. * * Since: 0.9.3 **/ 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. * * Since: 0.9.3 **/ 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 * * Since: 0.9.3 **/ 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); } /** * fu_hwids_new: * * Creates a new #FuHwids * * Since: 0.9.3 **/ FuHwids * fu_hwids_new (void) { FuHwids *self; self = g_object_new (FU_TYPE_HWIDS, NULL); return FU_HWIDS (self); } fwupd-1.3.9/libfwupdplugin/fu-hwids.h000066400000000000000000000026661362775233600176330ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-smbios.h" #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); fwupd-1.3.9/libfwupdplugin/fu-ihex-firmware.c000066400000000000000000000313471362775233600212550ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-common.h" #include "fu-firmware-common.h" #include "fu-ihex-firmware.h" /** * SECTION:fu-ihex-firmware * @short_description: Ihex firmware image * * An object that represents a Ihex firmware image. * * See also: #FuFirmware */ struct _FuIhexFirmware { FuFirmware parent_instance; GPtrArray *records; }; G_DEFINE_TYPE (FuIhexFirmware, fu_ihex_firmware, FU_TYPE_FIRMWARE) #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 /** * fu_ihex_firmware_get_records: * @self: A #FuIhexFirmware * * Returns the raw lines from tokenization. * * This might be useful if the plugin is expecting the hex file to be a list * of operations, rather than a simple linear image with filled holes. * * Returns: (transfer none) (element-type FuIhexFirmwareRecord): records * * Since: 1.3.4 **/ GPtrArray * fu_ihex_firmware_get_records (FuIhexFirmware *self) { g_return_val_if_fail (FU_IS_IHEX_FIRMWARE (self), NULL); return self->records; } static void fu_ihex_firmware_record_free (FuIhexFirmwareRecord *rcd) { g_string_free (rcd->buf, TRUE); g_free (rcd); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuIhexFirmwareRecord, fu_ihex_firmware_record_free) static FuIhexFirmwareRecord * fu_ihex_firmware_record_new (guint ln, const gchar *buf) { FuIhexFirmwareRecord *rcd = g_new0 (FuIhexFirmwareRecord, 1); rcd->ln = ln; rcd->buf = g_string_new (buf); return rcd; } static const gchar * fu_ihex_firmware_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; } static gboolean fu_ihex_firmware_tokenize (FuFirmware *firmware, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuIhexFirmware *self = FU_IHEX_FIRMWARE (firmware); gsize sz = 0; const gchar *data = g_bytes_get_data (fw, &sz); g_auto(GStrv) lines = fu_common_strnsplit (data, sz, "\n", -1); for (guint ln = 0; lines[ln] != NULL; ln++) { g_autoptr(FuIhexFirmwareRecord) rcd = NULL; g_strdelimit (lines[ln], "\r\x1a", '\0'); rcd = fu_ihex_firmware_record_new (ln + 1, lines[ln]); g_ptr_array_add (self->records, g_steal_pointer (&rcd)); } return TRUE; } static gboolean fu_ihex_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuIhexFirmware *self = FU_IHEX_FIRMWARE (firmware); gboolean got_eof = FALSE; guint32 abs_addr = 0x0; guint32 addr_last = 0x0; guint32 img_addr = G_MAXUINT32; guint32 seg_addr = 0x0; g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (NULL); g_autoptr(GBytes) img_bytes = NULL; g_autoptr(GByteArray) buf = g_byte_array_new (); g_autoptr(GByteArray) buf_signature = g_byte_array_new (); /* parse records */ for (guint k = 0; k < self->records->len; k++) { FuIhexFirmwareRecord *rcd = g_ptr_array_index (self->records, k); const gchar *line = rcd->buf->str; guint32 addr; guint8 byte_cnt; guint8 record_type; guint line_end; /* ignore comments */ if (g_str_has_prefix (line, ";")) continue; /* ignore blank lines */ if (rcd->buf->len == 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", rcd->ln, line); return FALSE; } /* check there's enough data for the smallest possible record */ if (rcd->buf->len < 11) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "line %u is incomplete, length %u", rcd->ln, (guint) rcd->buf->len); return FALSE; } /* length, 16-bit address, type */ byte_cnt = fu_firmware_strparse_uint8 (line + 1); addr = fu_firmware_strparse_uint16 (line + 3); record_type = fu_firmware_strparse_uint8 (line + 7); g_debug ("%s:", fu_ihex_firmware_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) rcd->buf->len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "line %u malformed, length: %u", rcd->ln, line_end); return FALSE; } /* verify checksum */ if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { guint8 checksum = 0; for (guint i = 1; i < line_end + 2; i += 2) { guint8 data_tmp = fu_firmware_strparse_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)", rcd->ln, checksum); return FALSE; } } /* process different record types */ switch (record_type) { case DFU_INHX32_RECORD_TYPE_DATA: /* base address for element */ if (img_addr == G_MAXUINT32) img_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 on line %u", (guint) addr, (guint) addr_last, rcd->ln); 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 on line %u", (guint) len_hole, rcd->ln); return FALSE; } if (addr_last > 0x0 && len_hole > 1) { g_debug ("filling address 0x%08x to 0x%08x on line %u", addr_last + 1, addr_last + len_hole - 1, rcd->ln); for (guint j = 1; j < len_hole; j++) { /* although 0xff might be clearer, * we can't write 0xffff to pic14 */ fu_byte_array_append_uint8 (buf, 0x00); } } /* write into buf */ data_tmp = fu_firmware_strparse_uint8 (line + i); fu_byte_array_append_uint8 (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 = fu_firmware_strparse_uint16 (line + 9) << 16; g_debug (" abs_addr:\t0x%02x on line %u", abs_addr, rcd->ln); break; case DFU_INHX32_RECORD_TYPE_START_LINEAR: abs_addr = fu_firmware_strparse_uint32 (line + 9); g_debug (" abs_addr:\t0x%08x on line %u", abs_addr, rcd->ln); break; case DFU_INHX32_RECORD_TYPE_EXTENDED_SEGMENT: /* segment base address, so ~1Mb addressable */ seg_addr = fu_firmware_strparse_uint16 (line + 9) * 16; g_debug (" seg_addr:\t0x%08x on line %u", seg_addr, rcd->ln); break; case DFU_INHX32_RECORD_TYPE_START_SEGMENT: /* initial content of the CS:IP registers */ seg_addr = fu_firmware_strparse_uint32 (line + 9); g_debug (" seg_addr:\t0x%02x on line %u", seg_addr, rcd->ln); break; case DFU_INHX32_RECORD_TYPE_SIGNATURE: for (guint i = 9; i < line_end; i += 2) { guint8 tmp_c = fu_firmware_strparse_uint8 (line + i); fu_byte_array_append_uint8 (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 on line %u", record_type, rcd->ln); 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 */ img_bytes = g_bytes_new (buf->data, buf->len); fu_firmware_image_set_bytes (img, img_bytes); if (img_addr != G_MAXUINT32) fu_firmware_image_set_addr (img, img_addr); fu_firmware_add_image (firmware, img); /* add optional signature */ if (buf_signature->len > 0) { g_autoptr(GBytes) data_sig = g_bytes_new (buf_signature->data, buf_signature->len); g_autoptr(FuFirmwareImage) img_sig = fu_firmware_image_new (data_sig); fu_firmware_image_set_id (img_sig, FU_FIRMWARE_IMAGE_ID_SIGNATURE); fu_firmware_add_image (firmware, img_sig); } return TRUE; } static void fu_ihex_firmware_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 gboolean dfu_firmware_to_ihex_image (FuFirmwareImage *img, GString *str, GError **error) { const guint8 *data; const guint chunk_size = 16; gsize len; guint32 address_offset_last = 0x0; guint8 record_type = DFU_INHX32_RECORD_TYPE_DATA; g_autoptr(GBytes) bytes = NULL; /* get data */ bytes = fu_firmware_image_write (img, error); if (bytes == NULL) return FALSE; /* special case */ if (g_strcmp0 (fu_firmware_image_get_id (img), FU_FIRMWARE_IMAGE_ID_SIGNATURE) == 0) record_type = DFU_INHX32_RECORD_TYPE_SIGNATURE; /* get number of chunks */ data = g_bytes_get_data (bytes, &len); for (gsize i = 0; i < len; i += chunk_size) { guint32 address_tmp = fu_firmware_image_get_addr (img) + 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); fu_ihex_firmware_emit_chunk (str, 0x0, DFU_INHX32_RECORD_TYPE_EXTENDED_LINEAR, buf, 2); address_offset_last = address_offset; } address_tmp &= 0xffff; fu_ihex_firmware_emit_chunk (str, address_tmp, record_type, data + i, chunk_len); } return TRUE; } static GBytes * fu_ihex_firmware_write (FuFirmware *firmware, GError **error) { g_autoptr(GPtrArray) imgs = NULL; g_autoptr(GString) str = NULL; /* write all the element data */ str = g_string_new (""); imgs = fu_firmware_get_images (firmware); for (guint i = 0; i < imgs->len; i++) { FuFirmwareImage *img = g_ptr_array_index (imgs, i); if (!dfu_firmware_to_ihex_image (img, str, error)) return NULL; } /* add EOF */ fu_ihex_firmware_emit_chunk (str, 0x0, DFU_INHX32_RECORD_TYPE_EOF, NULL, 0); return g_bytes_new (str->str, str->len); } static void fu_ihex_firmware_finalize (GObject *object) { FuIhexFirmware *self = FU_IHEX_FIRMWARE (object); g_ptr_array_unref (self->records); G_OBJECT_CLASS (fu_ihex_firmware_parent_class)->finalize (object); } static void fu_ihex_firmware_init (FuIhexFirmware *self) { self->records = g_ptr_array_new_with_free_func ((GFreeFunc) fu_ihex_firmware_record_free); } static void fu_ihex_firmware_class_init (FuIhexFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); object_class->finalize = fu_ihex_firmware_finalize; klass_firmware->parse = fu_ihex_firmware_parse; klass_firmware->tokenize = fu_ihex_firmware_tokenize; klass_firmware->write = fu_ihex_firmware_write; } /** * fu_ihex_firmware_new: * * Creates a new #FuFirmware of sub type Ihex * * Since: 1.3.1 **/ FuFirmware * fu_ihex_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_IHEX_FIRMWARE, NULL)); } fwupd-1.3.9/libfwupdplugin/fu-ihex-firmware.h000066400000000000000000000007321362775233600212540ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_IHEX_FIRMWARE (fu_ihex_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuIhexFirmware, fu_ihex_firmware, FU, IHEX_FIRMWARE, FuFirmware) typedef struct { guint ln; GString *buf; } FuIhexFirmwareRecord; FuFirmware *fu_ihex_firmware_new (void); GPtrArray *fu_ihex_firmware_get_records (FuIhexFirmware *self); fwupd-1.3.9/libfwupdplugin/fu-io-channel.c000066400000000000000000000260501362775233600205160ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuIOChannel" #include "config.h" #include #include #include #include #ifdef HAVE_POLL_H #include #endif #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_byte_array: * @self: a #FuIOChannel * @buf: 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.3.2 **/ gboolean fu_io_channel_write_byte_array (FuIOChannel *self, GByteArray *buf, guint timeout_ms, FuIOChannelFlags flags, GError **error) { return fu_io_channel_write_raw (self, buf->data, buf->len, 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) { GByteArray *buf = fu_io_channel_read_byte_array (self, max_size, timeout_ms, flags, error); if (buf == NULL) return NULL; return g_byte_array_free_to_bytes (buf); } /** * fu_io_channel_read_byte_array: * @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: (transfer full): a #GByteArray, or %NULL for error * * Since: 1.3.2 **/ GByteArray * fu_io_channel_read_byte_array (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(GByteArray) buf2 = g_byte_array_new (); 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_byte_array_append (buf2, buf, len); return g_steal_pointer (&buf2); } /* 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_byte_array_append (buf2, buf, len); /* check maximum size */ if (max_size > 0 && buf2->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 (buf2->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_steal_pointer (&buf2); } /** * 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 * * Since: 1.2.2 **/ 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 * * Since: 1.2.2 **/ FuIOChannel * fu_io_channel_new_file (const gchar *filename, GError **error) { #ifdef HAVE_POLL_H 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); #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return NULL; #endif } fwupd-1.3.9/libfwupdplugin/fu-io-channel.h000066400000000000000000000043421362775233600205230ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); gboolean fu_io_channel_write_byte_array (FuIOChannel *self, GByteArray *buf, 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); GByteArray *fu_io_channel_read_byte_array (FuIOChannel *self, gssize max_size, guint timeout_ms, FuIOChannelFlags flags, GError **error); fwupd-1.3.9/libfwupdplugin/fu-mutex.h000066400000000000000000000021531362775233600176460ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2019 Kalev Lember * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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 fwupd-1.3.9/libfwupdplugin/fu-plugin-private.h000066400000000000000000000103771362775233600214610ustar00rootroot00000000000000/* * 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" FuPlugin *fu_plugin_new (void); gboolean fu_plugin_is_open (FuPlugin *self); 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); gboolean fu_plugin_runner_udev_device_changed (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); 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); fwupd-1.3.9/libfwupdplugin/fu-plugin-vfuncs.h000066400000000000000000000166541362775233600213170ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-device.h" /** * SECTION:fu-plugin-vfuncs * @short_description: Virtual functions for plugins * * Optional functions that a plugin can implement. If implemented they will * be automatically called by the daemon as part of the plugin lifecycle. * * See also: #FuPlugin */ /** * fu_plugin_init: * @plugin: A #FuPlugin * * Initializes the plugin. * Sets up any static data structures for the plugin. * Most plugins should call fu_plugin_set_build_hash in here. * * Since: 0.8.0 **/ void fu_plugin_init (FuPlugin *plugin); /** * fu_plugin_destroy: * @plugin: A #FuPlugin * * Destroys the plugin. * Any allocated memory should be freed here. * * Since: 0.8.0 **/ void fu_plugin_destroy (FuPlugin *plugin); /** * fu_plugin_startup: * @plugin: A #FuPlugin * @error: A #GError * * Tries to start the plugin. * Returns: TRUE for success or FALSE for failure. * * Any plugins not intended for the system or that have failure communicating * with the device should return FALSE. * Any allocated memory should be freed here. * * Since: 0.8.0 **/ gboolean fu_plugin_startup (FuPlugin *plugin, GError **error); /** * fu_plugin_coldplug: * @plugin: A #FuPlugin * @error: A #GError * * Probes for devices. * * Since: 0.8.0 **/ gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error); /** * fu_plugin_coldplug_prepare: * @plugin: A #FuPlugin * @error: A #GError * * Prepares to probe for devices. * * Since: 0.8.0 **/ gboolean fu_plugin_coldplug_prepare (FuPlugin *plugin, GError **error); /** * fu_plugin_coldplug_cleanup: * @plugin: A #FuPlugin * @error: A #GError * * Cleans up from probe for devices. * * Since: 0.8.0 **/ gboolean fu_plugin_coldplug_cleanup (FuPlugin *plugin, GError **error); /** * fu_plugin_recoldplug: * @plugin: A #FuPlugin * @error: A #GError or NULL * * Re-runs the coldplug routine for devices. * * Since: 1.0.4 **/ gboolean fu_plugin_recoldplug (FuPlugin *plugin, GError **error); /** * fu_plugin_update: * @plugin: A #FuPlugin * @dev: A #FuDevice * @blob_fw: A #GBytes * @flags: A #FwupdInstallFlags * @error: A #GError or NULL * * Updates the firmware on the device with blob_fw * * Since: 0.9.7 **/ gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FwupdInstallFlags flags, GError **error); /** * fu_plugin_verify: * @plugin: A #FuPlugin * @dev: A #FuDevice * @flags: A #FuPluginVerifyFlags * @error: A #GError or NULL * * Verifies the firmware on the device matches the value stored in the database * * Since: 0.8.0 **/ gboolean fu_plugin_verify (FuPlugin *plugin, FuDevice *dev, FuPluginVerifyFlags flags, GError **error); /** * fu_plugin_unlock: * @plugin: A #FuPlugin * @dev: A #FuDevice * @error: A #GError or NULL * * Unlocks the device for writes. * * Since: 0.8.0 **/ gboolean fu_plugin_unlock (FuPlugin *plugin, FuDevice *dev, GError **error); /** * fu_plugin_activate: * @plugin: A #FuPlugin * @dev: A #FuDevice * @error: A #GError or NULL * * Activates the new firmware on the device. * * This is intended for devices that it is not safe to immediately activate * the firmware. It may be called at a more convenient time instead. * * Since: 1.2.6 **/ gboolean fu_plugin_activate (FuPlugin *plugin, FuDevice *dev, GError **error); /** * fu_plugin_clear_results: * @plugin: A #FuPlugin * @dev: A #FuDevice * @error: A #GError or NULL * * Clears stored update results for the device. * * Since: 0.8.0 **/ gboolean fu_plugin_clear_results (FuPlugin *plugin, FuDevice *dev, GError **error); /** * fu_plugin_get_results: * @plugin: A #FuPlugin * @dev: A #FuDevice * @error: A #GError or NULL * * Obtains historical update results for the device. * * Since: 0.8.0 **/ gboolean fu_plugin_get_results (FuPlugin *plugin, FuDevice *dev, GError **error); /** * fu_plugin_update_attach: * @plugin: A #FuPlugin * @dev: A #FuDevice * @error: A #GError or NULL * * Swaps the device from bootloader mode to runtime mode. * * Since: 1.0.2 **/ gboolean fu_plugin_update_attach (FuPlugin *plugin, FuDevice *dev, GError **error); /** * fu_plugin_update_detach: * @plugin: A #FuPlugin * @dev: A #FuDevice * @error: A #GError or NULL * * Swaps the device from runtime mode to bootloader mode. * * Since: 1.0.2 **/ gboolean fu_plugin_update_detach (FuPlugin *plugin, FuDevice *dev, GError **error); /** * fu_plugin_update_prepare: * @plugin: A #FuPlugin * @flags: A #FwupdInstallFlags * @dev: A #FuDevice * @error: A #GError or NULL * * Prepares the device to receive an update. * * Since: 0.8.0 **/ gboolean fu_plugin_update_prepare (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *dev, GError **error); /** * fu_plugin_update_cleanup * @plugin: A #FuPlugin * @flags: A #FwupdInstallFlags * @dev: A #FuDevice * @error: A #GError or NULL * * Cleans up the device after receiving an update. * * Since: 0.8.0 **/ gboolean fu_plugin_update_cleanup (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *dev, GError **error); /** * fu_plugin_composite_prepare * @plugin: A #FuPlugin * @devices: A #GPtrArray of #FuDevice * @error: A #GError or NULL * * Function run before updating group of composite devices. * * Since: 1.0.9 **/ gboolean fu_plugin_composite_prepare (FuPlugin *plugin, GPtrArray *devices, GError **error); /** * fu_plugin_composite_cleanup * @plugin: A #FuPlugin * @devices: A #GPtrArray of #FuDevice * @error: A #GError or NULL * * Function run after updating group of composite devices. * * Since: 1.0.9 **/ gboolean fu_plugin_composite_cleanup (FuPlugin *plugin, GPtrArray *devices, GError **error); /** * fu_plugin_usb_device_added * @plugin: A #FuPlugin * @device: A #FuUsbDevice * @error: A #GError or NULL * * Function run after USB device added to daemon. * * Since: 1.0.2 **/ gboolean fu_plugin_usb_device_added (FuPlugin *plugin, FuUsbDevice *device, GError **error); /** * fu_plugin_udev_device_added * @plugin: A #FuPlugin * @device: A #FuUdevDevice * @error: A #GError or NULL * * Function run after Udev device added to daemon. * * Since: 1.1.2 **/ gboolean fu_plugin_udev_device_added (FuPlugin *plugin, FuUdevDevice *device, GError **error); /** * fu_plugin_udev_device_changed * @plugin: A #FuPlugin * @device: A #FuUdevDevice * @error: A #GError or NULL * * Function run when Udev device changed. * * Since: 1.1.2 **/ gboolean fu_plugin_udev_device_changed (FuPlugin *plugin, FuUdevDevice *device, GError **error); /** * fu_plugin_device_removed * @plugin: A #FuPlugin * @device: A #FuDevice * @error: A #GError or NULL * * Function run when device removed. * * Since: 1.1.2 **/ gboolean fu_plugin_device_removed (FuPlugin *plugin, FuDevice *device, GError **error); /** * fu_plugin_device_registered * @plugin: A #FuPlugin * @device: A #FuDevice * * Function run when device registered from another plugin. * * Since: 0.9.7 **/ void fu_plugin_device_registered (FuPlugin *plugin, FuDevice *dev); fwupd-1.3.9/libfwupdplugin/fu-plugin.c000066400000000000000000002066621362775233600200100ustar00rootroot00000000000000/* * 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 #ifdef HAVE_VALGRIND #include #endif /* HAVE_VALGRIND */ #include "fu-device-private.h" #include "fu-plugin-private.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; GType device_gtype; 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_ADD_FIRMWARE_GTYPE, 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_is_open: * @self: A #FuPlugin * * Determines if the plugin is opened * * Returns: TRUE for opened, FALSE for not * * Since: 1.3.5 **/ gboolean fu_plugin_is_open (FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE (self); return priv->module != NULL; } /** * 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; } /** * fu_plugin_set_name: * @self: A #FuPlugin * @name: A string * * Sets the plugin name. * * Since: 0.8.0 **/ 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); } /** * fu_plugin_get_build_hash: * @self: A #FuPlugin * * Gets the build hash a plugin was generated with. * * Returns: (transfer none): a #gchar, or %NULL for unset. * * Since: 1.2.4 **/ 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(GRWLockWriterLocker) 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(GRWLockWriterLocker) 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: (skip): * @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; } /** * fu_plugin_set_usb_context: * @self: A #FuPlugin * @usb_ctx: A #FGUsbContext * * Sets the shared USB context for a plugin * * Since: 0.8.0 **/ 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; } /** * fu_plugin_guess_name_from_fn: * @filename: filename to guess * * Tries to guess the name of the plugin from a filename * * Returns: (transfer full): the guessed name of the plugin * * Since: 1.0.8 **/ 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; } /** * fu_plugin_open: * @self: A #FuPlugin * @filename: The shared object filename to open * @error: A #GError or NULL * * Opens the plugin module * * Returns: TRUE for success, FALSE for fail * * Since: 0.8.0 **/ 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: %s", filename, 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 instance 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_hwid_replace_value: * @self: A #FuPlugin * @keys: A key, e.g. `HardwareID-3` or %FU_HWIDS_KEY_PRODUCT_SKU * @error: A #GError or %NULL * * Gets the replacement value for a specific key. All hardware IDs on a * specific system can be shown using the `fwupdmgr hwids` command. * * Returns: (transfer full): a string, or %NULL for error. * * Since: 1.3.3 **/ gchar * fu_plugin_get_hwid_replace_value (FuPlugin *self, const gchar *keys, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); if (priv->hwids == NULL) return NULL; return fu_hwids_get_replace_values (priv->hwids, keys, error); } /** * 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 utf8): 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_has_custom_flag: * @self: A #FuPlugin * @flag: A custom text flag, specific to the plugin, e.g. `uefi-force-enable` * * Returns if a per-plugin HwId custom flag exists, typically added from a DMI quirk. * * Returns: %TRUE if the quirk entry exists * * Since: 1.3.1 **/ gboolean fu_plugin_has_custom_flag (FuPlugin *self, const gchar *flag) { FuPluginPrivate *priv = GET_PRIVATE (self); GPtrArray *hwids = fu_plugin_get_hwids (self); g_return_val_if_fail (FU_IS_PLUGIN (self), FALSE); g_return_val_if_fail (flag != NULL, FALSE); /* never set up, e.g. in tests */ if (hwids == NULL) return FALSE; /* search each hwid */ for (guint i = 0; i < hwids->len; i++) { const gchar *hwid = g_ptr_array_index (hwids, i); const gchar *value; g_autofree gchar *key = g_strdup_printf ("HwId=%s", hwid); /* does prefixed quirk exist */ value = fu_quirks_lookup_by_id (priv->quirks, key, FU_QUIRKS_FLAGS); if (value != NULL) { g_auto(GStrv) quirks = g_strsplit (value, ",", -1); if (g_strv_contains ((const gchar * const *) quirks, flag)) return TRUE; } } return FALSE; } /** * 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 **/ static 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); } /** * fu_plugin_set_hwids: * @self: A #FuPlugin * @hwids: A #FuHwids * * Sets the hwids for a plugin * * Since: 0.9.7 **/ void fu_plugin_set_hwids (FuPlugin *self, FuHwids *hwids) { FuPluginPrivate *priv = GET_PRIVATE (self); g_set_object (&priv->hwids, hwids); } /** * fu_plugin_set_udev_subsystems: * @self: A #FuPlugin * @udev_subsystems: (element-type utf8): A #GPtrArray * * Sets the udev subsystems used by a plugin * * Since: 1.1.2 **/ 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); } /** * fu_plugin_set_quirks: * @self: A #FuPlugin * @quirks: A #FuQuirks * * Sets the quirks for a plugin * * Since: 1.0.1 **/ 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; } /** * fu_plugin_set_runtime_versions: * @self: A #FuPlugin * @runtime_versions: A #GHashTables * * Sets the runtime versions for a plugin * * Since: 1.0.7 **/ 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)); } /** * fu_plugin_set_compile_versions: * @self: A #FuPlugin * @compile_versions: A #GHashTables * * Sets the compile time versions for a plugin * * Since: 1.0.7 **/ 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: guint64 id 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)); } /** * fu_plugin_set_smbios: * @self: A #FuPlugin * @smbios: A #FuSmbios * * Sets the smbios for a plugin * * Since: 1.0.0 **/ 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 in-between 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); } static gboolean fu_plugin_device_attach (FuPlugin *self, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug ("already in runtime mode, skipping"); return TRUE; } locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_attach (device, error); } static gboolean fu_plugin_device_detach (FuPlugin *self, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug ("already in bootloader mode, skipping"); return TRUE; } locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_detach (device, error); } static gboolean fu_plugin_device_activate (FuPlugin *self, 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); } static gboolean fu_plugin_device_write_firmware (FuPlugin *self, FuDevice *device, GBytes *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, fw, flags, error); } static gboolean fu_plugin_device_read_firmware (FuPlugin *self, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autoptr(GBytes) fw = NULL; GChecksumType checksum_types[] = { G_CHECKSUM_SHA1, G_CHECKSUM_SHA256, 0 }; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; if (!fu_device_detach (device, error)) return FALSE; firmware = fu_device_read_firmware (device, error); if (firmware == NULL) { g_autoptr(GError) error_local = NULL; if (!fu_device_attach (device, &error_local)) g_debug ("ignoring attach failure: %s", error_local->message); g_prefix_error (error, "failed to read firmware: "); return FALSE; } fw = fu_firmware_write (firmware, error); if (fw == NULL) { g_autoptr(GError) error_local = NULL; if (!fu_device_attach (device, &error_local)) g_debug ("ignoring attach failure: %s", error_local->message); g_prefix_error (error, "failed to write firmware: "); 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 fu_device_attach (device, error); } /** * fu_plugin_runner_startup: * @self: a #FuPlugin * @error: a #GError or NULL * * Runs the startup routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ 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_device_generic (FuPlugin *self, FuDevice *device, const gchar *symbol_name, FuPluginDeviceFunc device_func, 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) { if (device_func != NULL) { g_debug ("running superclassed %s() on %s", symbol_name + 10, priv->name); return device_func (self, device, error); } 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; } /** * fu_plugin_runner_coldplug: * @self: a #FuPlugin * @error: a #GError or NULL * * Runs the coldplug routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ 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; } /** * fu_plugin_runner_recoldplug: * @self: a #FuPlugin * @error: a #GError or NULL * * Runs the recoldplug routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.0.4 **/ 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; } /** * fu_plugin_runner_coldplug_prepare: * @self: a #FuPlugin * @error: a #GError or NULL * * Runs the coldplug_prepare routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ 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; } /** * fu_plugin_runner_coldplug_cleanup: * @self: a #FuPlugin * @error: a #GError or NULL * * Runs the coldplug_cleanup routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ 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; } /** * fu_plugin_runner_composite_prepare: * @self: a #FuPlugin * @devices: (element-type FuDevice): a #GPtrArray of devices * @error: a #GError or NULL * * Runs the composite_prepare routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.0.9 **/ 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); } /** * fu_plugin_runner_composite_cleanup: * @self: a #FuPlugin * @devices: (element-type FuDevice): a #GPtrArray of devices * @error: a #GError or NULL * * Runs the composite_cleanup routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.0.9 **/ 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); } /** * fu_plugin_runner_update_prepare: * @self: a #FuPlugin * @error: a #GError or NULL * * Runs the update_prepare routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.1.2 **/ 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); } /** * fu_plugin_runner_update_cleanup: * @self: a #FuPlugin * @error: a #GError or NULL * * Runs the update_cleanup routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.1.2 **/ 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); } /** * fu_plugin_runner_update_attach: * @self: a #FuPlugin * @device: a #FuDevice * @error: a #GError or NULL * * Runs the update_attach routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.1.2 **/ gboolean fu_plugin_runner_update_attach (FuPlugin *self, FuDevice *device, GError **error) { return fu_plugin_runner_device_generic (self, device, "fu_plugin_update_attach", fu_plugin_device_attach, error); } /** * fu_plugin_runner_update_detach: * @self: a #FuPlugin * @device: A #FuDevice * @error: a #GError or NULL * * Runs the update_detach routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.1.2 **/ gboolean fu_plugin_runner_update_detach (FuPlugin *self, FuDevice *device, GError **error) { return fu_plugin_runner_device_generic (self, device, "fu_plugin_update_detach", fu_plugin_device_detach, error); } /** * fu_plugin_runner_update_reload: * @self: a #FuPlugin * @error: a #GError or NULL * * Runs reload routine for a device * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.1.2 **/ gboolean fu_plugin_runner_update_reload (FuPlugin *self, FuDevice *device, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); g_autoptr(FuDeviceLocker) locker = NULL; /* not enabled */ if (!priv->enabled) return TRUE; /* no object loaded */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; return fu_device_reload (device, 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() * * Since: 1.1.2 **/ 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)); } /** * fu_plugin_set_device_gtype: * @self: a #FuPlugin * @device_gtype: a #GType `FU_TYPE_DEVICE` * * Sets the device #GType which is used when creating devices. * * If this method is used then fu_plugin_usb_device_added() is not called, and * instead the object is created in the daemon for the plugin. * * Plugins can use this method only in fu_plugin_init() * * Since: 1.3.3 **/ void fu_plugin_set_device_gtype (FuPlugin *self, GType device_gtype) { FuPluginPrivate *priv = GET_PRIVATE (self); priv->device_gtype = device_gtype; } /** * fu_plugin_add_firmware_gtype: * @self: a #FuPlugin * @id: A string describing the type * @gtype: a #GType `FU_TYPE_DEVICE` * * Adds a firmware #GType which is used when creating devices. * * * Plugins can use this method only in fu_plugin_init() * * Since: 1.3.3 **/ void fu_plugin_add_firmware_gtype (FuPlugin *self, const gchar *id, GType gtype) { g_signal_emit (self, signals[SIGNAL_ADD_FIRMWARE_GTYPE], 0, id, gtype); } static gboolean fu_plugin_check_supported_device (FuPlugin *self, FuDevice *device) { GPtrArray *instance_ids = fu_device_get_instance_ids (device); 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); if (fu_plugin_check_supported (self, guid)) return TRUE; } return FALSE; } static gboolean fu_plugin_usb_device_added (FuPlugin *self, FuUsbDevice *device, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); GType device_gtype = fu_device_get_specialized_gtype (FU_DEVICE (device)); g_autoptr(FuDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* fall back to plugin default */ if (device_gtype == G_TYPE_INVALID) device_gtype = priv->device_gtype; /* create new device and incorporate existing properties */ dev = g_object_new (device_gtype, NULL); fu_device_incorporate (dev, FU_DEVICE (device)); /* there are a lot of different devices that match, but not all respond * well to opening -- so limit some ones with issued updates */ if (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_ONLY_SUPPORTED)) { if (!fu_device_probe (dev, error)) return FALSE; fu_device_convert_instance_ids (dev); if (!fu_plugin_check_supported_device (self, dev)) { g_autofree gchar *guids = fu_device_get_guids_as_str (dev); g_debug ("%s has no updates, so ignoring device", guids); return TRUE; } } /* open and add */ locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (self, dev); return TRUE; } static gboolean fu_plugin_udev_device_added (FuPlugin *self, FuUdevDevice *device, GError **error) { FuPluginPrivate *priv = GET_PRIVATE (self); GType device_gtype = fu_device_get_specialized_gtype (FU_DEVICE (device)); g_autoptr(FuDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* fall back to plugin default */ if (device_gtype == G_TYPE_INVALID) device_gtype = priv->device_gtype; /* create new device and incorporate existing properties */ dev = g_object_new (device_gtype, NULL); fu_device_incorporate (FU_DEVICE (dev), FU_DEVICE (device)); /* there are a lot of different devices that match, but not all respond * well to opening -- so limit some ones with issued updates */ if (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_ONLY_SUPPORTED)) { if (!fu_device_probe (dev, error)) return FALSE; fu_device_convert_instance_ids (dev); if (!fu_plugin_check_supported_device (self, dev)) { g_autofree gchar *guids = fu_device_get_guids_as_str (dev); g_debug ("%s has no updates, so ignoring device", guids); return TRUE; } } /* open and add */ locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add (self, FU_DEVICE (dev)); return TRUE; } /** * fu_plugin_runner_usb_device_added: * @self: a #FuPlugin * @device: a #FuUsbDevice * @error: a #GError or NULL * * Call the usb_device_added routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.0.2 **/ 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) { if (priv->device_gtype != G_TYPE_INVALID || fu_device_get_specialized_gtype (FU_DEVICE (device)) != G_TYPE_INVALID) { if (!fu_plugin_usb_device_added (self, device, error)) return FALSE; } 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; } /** * fu_plugin_runner_udev_device_added: * @self: a #FuPlugin * @device: a #FuUdevDevice * @error: a #GError or NULL * * Call the udev_device_added routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.0.2 **/ 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) { if (priv->device_gtype != G_TYPE_INVALID || fu_device_get_specialized_gtype (FU_DEVICE (device)) != G_TYPE_INVALID) { if (!fu_plugin_udev_device_added (self, device, error)) return FALSE; } 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; } /** * fu_plugin_runner_udev_device_changed: * @self: a #FuPlugin * @device: a #FuUdevDevice * @error: a #GError or NULL * * Call the udev_device_changed routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.0.2 **/ gboolean fu_plugin_runner_udev_device_changed (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_changed", (gpointer *) &func); if (func == NULL) return TRUE; g_debug ("performing udev_device_changed() on %s", priv->name); if (!func (self, device, &error_local)) { if (error_local == NULL) { g_critical ("unset error in plugin %s for udev_device_changed()", 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 change device on %s: ", priv->name); return FALSE; } return TRUE; } /** * fu_plugin_runner_device_removed: * @self: a #FuPlugin * @device: a #FuDevice * * Call the device_removed routine for the plugin * * Since: 1.1.2 **/ 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", NULL, &error_local)) g_warning ("%s", error_local->message); } /** * fu_plugin_runner_device_register: * @self: a #FuPlugin * @device: a #FuDevice * * Call the device_registered routine for the plugin * * Since: 0.9.7 **/ 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); } } /** * fu_plugin_runner_verify: * @self: a #FuPlugin * @device: a #FuDevice * @flags: #FuPluginVerifyFlags * @error: A #GError or NULL * * Call into the plugin's verify routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ 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 fu_plugin_device_read_firmware (self, device, error); } /* 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_update_detach", fu_plugin_device_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_update_attach", fu_plugin_device_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_update_attach", fu_plugin_device_attach, error)) return FALSE; /* success */ return TRUE; } /** * fu_plugin_runner_activate: * @self: a #FuPlugin * @device: a #FuDevice * @error: A #GError or NULL * * Call into the plugin's activate routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.2.6 **/ 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", fu_plugin_device_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; } /** * fu_plugin_runner_unlock: * @self: a #FuPlugin * @device: a #FuDevice * @error: A #GError or NULL * * Call into the plugin's unlock routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ 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", NULL, 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; } /** * fu_plugin_runner_update: * @self: a #FuPlugin * @device: a #FuDevice * @blob_fw: A #GBytes * @flags: A #FwupdInstallFlags * @error: A #GError or NULL * * Call into the plugin's update routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ 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(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_debug ("running superclassed write_firmware() on %s", priv->name); return fu_plugin_device_write_firmware (self, device, blob_fw, flags, error); } /* online */ 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); } /* success */ return TRUE; } /** * fu_plugin_runner_clear_results: * @self: a #FuPlugin * @device: a #FuDevice * @error: A #GError or NULL * * Call into the plugin's clear results routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ 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_clear_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; } /** * fu_plugin_runner_get_results: * @self: a #FuPlugin * @device: a #FuDevice * @error: A #GError or NULL * * Call into the plugin's get results routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ 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 * * Since: 1.0.0 **/ 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. * * Since: 1.0.0 **/ 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 * * Since: 1.1.1 **/ 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. * * Since: 1.0.0 **/ 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. * * Since: 1.0.0 **/ 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'] * * Since: 1.0.0 **/ GPtrArray * fu_plugin_get_rules (FuPlugin *self, FuPluginRule rule) { FuPluginPrivate *priv = fu_plugin_get_instance_private (self); g_return_val_if_fail (rule < FU_PLUGIN_RULE_LAST, NULL); 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 * * Since: 1.0.0 **/ 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. * * Since: 1.0.4 **/ 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 * * Since: 1.0.4 **/ 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. * * Since: 1.0.8 **/ 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. * * Since: 1.0.8 **/ 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); signals[SIGNAL_ADD_FIRMWARE_GTYPE] = g_signal_new ("add-firmware-gtype", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FuPluginClass, add_firmware_gtype), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_GTYPE); } 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); } /** * fu_plugin_new: * * Creates a new #FuPlugin * * Since: 0.8.0 **/ FuPlugin * fu_plugin_new (void) { return FU_PLUGIN (g_object_new (FU_TYPE_PLUGIN, NULL)); } fwupd-1.3.9/libfwupdplugin/fu-plugin.h000066400000000000000000000122461362775233600200060ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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" #ifdef HAVE_GUDEV #include "fu-udev-device.h" #endif #include "fwupd-common.h" #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); gboolean (* add_firmware_gtype) (FuPlugin *self, const gchar *id, GType gtype); /*< private >*/ gpointer padding[21]; }; /** * 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_BETTER_THAN: Is better than another plugin * @FU_PLUGIN_RULE_INHIBITS_IDLE: The plugin inhibits the idle shutdown * @FU_PLUGIN_RULE_METADATA_SOURCE: Uses another plugin as a source of report metadata * * 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_BETTER_THAN, FU_PLUGIN_RULE_INHIBITS_IDLE, FU_PLUGIN_RULE_METADATA_SOURCE, /* Since: 1.3.6 */ /*< 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); void fu_plugin_set_device_gtype (FuPlugin *self, GType device_gtype); void fu_plugin_add_firmware_gtype (FuPlugin *self, const gchar *id, GType gtype); 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); gchar *fu_plugin_get_hwid_replace_value (FuPlugin *self, const gchar *keys, GError **error); GPtrArray *fu_plugin_get_hwids (FuPlugin *self); 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); gboolean fu_plugin_has_custom_flag (FuPlugin *self, const gchar *flag); fwupd-1.3.9/libfwupdplugin/fu-quirks.c000066400000000000000000000301251362775233600200150ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuQuirks" #include "config.h" #include #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; FuQuirksLoadFlags load_flags; XbSilo *silo; }; G_DEFINE_TYPE (FuQuirks, fu_quirks, G_TYPE_OBJECT) 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); } static GInputStream * fu_quirks_convert_quirk_to_xml_cb (XbBuilderSource *self, XbBuilderSourceCtx *ctx, gpointer user_data, GCancellable *cancellable, GError **error) { g_autofree gchar *xml = NULL; g_auto(GStrv) groups = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GKeyFile) kf = g_key_file_new (); g_autoptr(XbBuilderNode) root = xb_builder_node_new ("quirk"); /* parse keyfile */ bytes = xb_builder_source_ctx_get_bytes (ctx, cancellable, error); if (bytes == NULL) return NULL; if (!g_key_file_load_from_data (kf, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), G_KEY_FILE_NONE, error)) return NULL; /* 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; g_autofree gchar *group_id = NULL; g_autoptr(XbBuilderNode) bn = NULL; keys = g_key_file_get_keys (kf, groups[i], NULL, error); if (keys == NULL) return NULL; group_id = fu_quirks_build_group_key (groups[i]); bn = xb_builder_node_insert (root, "device", "id", group_id, NULL); for (guint j = 0; keys[j] != NULL; j++) { g_autofree gchar *value = NULL; value = g_key_file_get_value (kf, groups[i], keys[j], error); if (value == NULL) return NULL; xb_builder_node_insert_text (bn, "value", value, "key", keys[j], NULL); } } /* export as XML */ xml = xb_builder_node_export (root, XB_NODE_EXPORT_FLAG_ADD_HEADER, error); if (xml == NULL) return NULL; return g_memory_input_stream_new_from_data (g_steal_pointer (&xml), -1, g_free); } 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, XbBuilder *builder, 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); g_autoptr(GFile) file = g_file_new_for_path (filename); g_autoptr(XbBuilderSource) source = xb_builder_source_new (); /* load from keyfile */ #if LIBXMLB_CHECK_VERSION(0,1,15) xb_builder_source_add_simple_adapter (source, "text/plain,.quirk", fu_quirks_convert_quirk_to_xml_cb, NULL, NULL); #else xb_builder_source_add_adapter (source, "text/plain,.quirk", fu_quirks_convert_quirk_to_xml_cb, NULL, NULL); #endif if (!xb_builder_source_load_file (source, file, XB_BUILDER_SOURCE_FLAG_WATCH_FILE | XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT, NULL, error)) { g_prefix_error (error, "failed to load %s: ", filename); return FALSE; } /* watch the file for changes */ xb_builder_import_source (builder, source); } /* success */ return TRUE; } static gboolean fu_quirks_check_silo (FuQuirks *self, GError **error) { XbBuilderCompileFlags compile_flags = XB_BUILDER_COMPILE_FLAG_WATCH_BLOB; g_autofree gchar *cachedirpkg = NULL; g_autofree gchar *datadir = NULL; g_autofree gchar *localstatedir = NULL; g_autofree gchar *xmlbfn = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = NULL; /* everything is okay */ if (self->silo != NULL && xb_silo_is_valid (self->silo)) return TRUE; /* system datadir */ builder = xb_builder_new (); datadir = fu_common_get_path (FU_PATH_KIND_DATADIR_PKG); if (!fu_quirks_add_quirks_for_path (self, builder, 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, builder, localstatedir, error)) return FALSE; /* load silo */ cachedirpkg = fu_common_get_path (FU_PATH_KIND_CACHEDIR_PKG); xmlbfn = g_build_filename (cachedirpkg, "quirks.xmlb", NULL); file = g_file_new_for_path (xmlbfn); if (g_getenv ("XMLB_VERBOSE") != NULL) { xb_builder_set_profile_flags (builder, XB_SILO_PROFILE_FLAG_XPATH | XB_SILO_PROFILE_FLAG_DEBUG); } if (self->load_flags & FU_QUIRKS_LOAD_FLAG_READONLY_FS) compile_flags |= XB_BUILDER_COMPILE_FLAG_IGNORE_GUID; self->silo = xb_builder_ensure (builder, file, compile_flags, NULL, error); return self->silo != NULL; } /** * 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) { g_autofree gchar *group_key = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbQuery) query = NULL; 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); /* ensure up to date */ if (!fu_quirks_check_silo (self, &error)) { g_warning ("failed to build silo: %s", error->message); return NULL; } /* query */ group_key = fu_quirks_build_group_key (group); query = xb_query_new_full (self->silo, "quirk/device[@id=?]/value[@key=?]", XB_QUERY_FLAG_NONE, &error); if (query == NULL) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return NULL; if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return NULL; g_warning ("failed to build query: %s", error->message); return NULL; } if (!xb_query_bind_str (query, 0, group_key, &error)) { g_warning ("failed to bind 0: %s", error->message); return NULL; } if (!xb_query_bind_str (query, 1, key, &error)) { g_warning ("failed to bind 1: %s", error->message); return NULL; } n = xb_silo_query_first_full (self->silo, query, &error); if (n == NULL) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return NULL; if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return NULL; g_warning ("failed to query: %s", error->message); return NULL; } return xb_node_get_text (n); } /** * fu_quirks_lookup_by_id_iter: * @self: A #FuQuirks * @group: string of group to lookup * @iter_cb: (scope async): A #FuQuirksIter * @user_data: user data passed to @iter_cb * * Looks up all entries in the hardware database using a GUID value. * * Returns: %TRUE if the ID was found, and @iter was called * * Since: 1.3.3 **/ gboolean fu_quirks_lookup_by_id_iter (FuQuirks *self, const gchar *group, FuQuirksIter iter_cb, gpointer user_data) { g_autofree gchar *group_key = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(XbQuery) query = NULL; g_return_val_if_fail (FU_IS_QUIRKS (self), FALSE); g_return_val_if_fail (group != NULL, FALSE); g_return_val_if_fail (iter_cb != NULL, FALSE); /* ensure up to date */ if (!fu_quirks_check_silo (self, &error)) { g_warning ("failed to build silo: %s", error->message); return FALSE; } /* query */ group_key = fu_quirks_build_group_key (group); query = xb_query_new_full (self->silo, "quirk/device[@id=?]/value", XB_QUERY_FLAG_NONE, &error); if (query == NULL) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return FALSE; if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return FALSE; g_warning ("failed to build query: %s", error->message); return FALSE; } if (!xb_query_bind_str (query, 0, group_key, &error)) { g_warning ("failed to bind 0: %s", error->message); return FALSE; } results = xb_silo_query_full (self->silo, query, &error); if (results == NULL) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return FALSE; if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return FALSE; g_warning ("failed to query: %s", error->message); return FALSE; } for (guint i = 0; i < results->len; i++) { XbNode *n = g_ptr_array_index (results, i); iter_cb (self, xb_node_get_attr (n, "key"), xb_node_get_text (n), user_data); } return TRUE; } /** * fu_quirks_load: (skip) * @self: A #FuQuirks * @load_flags: A #FuQuirksLoadFlags * @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, FuQuirksLoadFlags load_flags, GError **error) { g_return_val_if_fail (FU_IS_QUIRKS (self), FALSE); self->load_flags = load_flags; return fu_quirks_check_silo (self, error); } 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) { } static void fu_quirks_finalize (GObject *obj) { FuQuirks *self = FU_QUIRKS (obj); if (self->silo != NULL) g_object_unref (self->silo); G_OBJECT_CLASS (fu_quirks_parent_class)->finalize (obj); } /** * fu_quirks_new: (skip) * * Creates a new quirks object. * * Return value: a new #FuQuirks * * Since: 1.0.1 **/ FuQuirks * fu_quirks_new (void) { FuQuirks *self; self = g_object_new (FU_TYPE_QUIRKS, NULL); return FU_QUIRKS (self); } fwupd-1.3.9/libfwupdplugin/fu-quirks.h000066400000000000000000000041141362775233600200210ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_QUIRKS (fu_quirks_get_type ()) G_DECLARE_FINAL_TYPE (FuQuirks, fu_quirks, FU, QUIRKS, GObject) /** * FuQuirksLoadFlags: * @FU_QUIRKS_LOAD_FLAG_NONE: No flags set * @FU_QUIRKS_LOAD_FLAG_READONLY_FS: Ignore readonly filesystem errors * * The flags to use when loading quirks. **/ typedef enum { FU_QUIRKS_LOAD_FLAG_NONE = 0, FU_QUIRKS_LOAD_FLAG_READONLY_FS = 1 << 0, /*< private >*/ FU_QUIRKS_LOAD_FLAG_LAST } FuQuirksLoadFlags; typedef void (*FuQuirksIter) (FuQuirks *self, const gchar *key, const gchar *value, gpointer user_data); FuQuirks *fu_quirks_new (void); gboolean fu_quirks_load (FuQuirks *self, FuQuirksLoadFlags load_flags, GError **error); const gchar *fu_quirks_lookup_by_id (FuQuirks *self, const gchar *group, const gchar *key); gboolean fu_quirks_lookup_by_id_iter (FuQuirks *self, const gchar *group, FuQuirksIter iter_cb, gpointer user_data); #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" #define FU_QUIRKS_GTYPE "GType" #define FU_QUIRKS_PROTOCOL "Protocol" fwupd-1.3.9/libfwupdplugin/fu-self-test.c000066400000000000000000001547331362775233600204210ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "fu-device-private.h" #include "fu-plugin-private.h" #include "fu-smbios-private.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; } static 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); } static 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; } } 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 = g_build_filename (TESTDATADIR_SRC, "metadata.xml", NULL); 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_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 = g_build_filename (TESTDATADIR_DST, "colorhug", "colorhug-als-3.0.2.cab", NULL); 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_string_append_kv_func (void) { g_autoptr(GString) str = g_string_new (NULL); fu_common_string_append_kv (str, 0, "hdr", NULL); fu_common_string_append_kv (str, 0, "key", "value"); fu_common_string_append_kv (str, 0, "key1", "value1"); fu_common_string_append_kv (str, 1, "key2", "value2"); fu_common_string_append_kv (str, 1, "", "value2"); fu_common_string_append_kv (str, 2, "key3", "value3"); g_assert_cmpstr (str->str, ==, "hdr:\n" "key: value\n" "key1: value1\n" " key2: value2\n" " value2\n" " key3: value3\n"); } 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_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 = g_build_filename (TESTDATADIR_SRC, "dmi", "tables64", NULL); 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_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 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, FU_QUIRKS_LOAD_FLAG_NONE, &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, ==, "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, "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) { gboolean ret; g_autoptr(FuQuirks) quirks = fu_quirks_new (); g_autoptr(GTimer) timer = g_timer_new (); g_autoptr(GError) error = NULL; const gchar *keys[] = { "Name", "Children", "Flags", NULL }; ret = fu_quirks_load (quirks, FU_QUIRKS_LOAD_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); /* lookup */ g_timer_reset (timer); for (guint j = 0; j < 1000; j++) { const gchar *group = "DeviceInstanceId=USB\\VID_0BDA&PID_1100"; for (guint i = 0; keys[i] != NULL; i++) { const gchar *tmp = fu_quirks_lookup_by_id (quirks, group, keys[i]); g_assert_cmpstr (tmp, !=, NULL); } } 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, FU_QUIRKS_LOAD_FLAG_NONE, &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_common_kernel_lockdown_func (void) { gboolean ret; g_autofree gchar *old_kernel_dir = g_build_filename (TESTDATADIR_SRC, "lockdown", NULL); g_autofree gchar *locked_dir = g_build_filename (TESTDATADIR_SRC, "lockdown", "locked", NULL); g_autofree gchar *none_dir = g_build_filename (TESTDATADIR_SRC, "lockedown", "none", NULL); g_setenv ("FWUPD_SYSFSSECURITYDIR", old_kernel_dir, TRUE); ret = fu_common_kernel_locked_down (); g_assert_false (ret); g_setenv ("FWUPD_SYSFSSECURITYDIR", locked_dir, TRUE); ret = fu_common_kernel_locked_down (); g_assert_true (ret); g_setenv ("FWUPD_SYSFSSECURITYDIR", none_dir, TRUE); ret = fu_common_kernel_locked_down (); g_assert_false (ret); } 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 = g_build_filename (TESTDATADIR_DST, "builder", "firmware.tar", 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 }; #ifdef _WIN32 g_test_skip ("Known failures on Windows right now, skipping spawn func test"); return; #endif fn = g_build_filename (TESTDATADIR_SRC, "spawn.sh", 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 }; #ifdef _WIN32 g_test_skip ("Known failures on Windows right now, skipping spawn timeout test"); return; #endif fn = g_build_filename (TESTDATADIR_SRC, "spawn.sh", 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_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 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; /* 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); rel = xb_node_query_first (component, "releases/release", &error); 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; /* 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); rel = xb_node_query_first (component, "releases/release", &error); 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; /* 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); rel = xb_node_query_first (component, "releases/release", &error); 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 }, { 0x226a4b00, "137.2706.768", FWUPD_VERSION_FORMAT_SURFACE_LEGACY }, { 0x6001988, "6.25.136", FWUPD_VERSION_FORMAT_SURFACE }, { 0x00ff0001, "255.0.1", FWUPD_VERSION_FORMAT_DELL_BIOS }, { 0, NULL } }; struct { guint64 val; const gchar *ver; FwupdVersionFormat flags; } version_from_uint64[] = { { 0x0, "0.0.0.0", FWUPD_VERSION_FORMAT_QUAD }, { 0xff, "0.0.0.255", FWUPD_VERSION_FORMAT_QUAD }, { 0xffffffffffffffff, "65535.65535.65535.65535", FWUPD_VERSION_FORMAT_QUAD }, { 0xff, "0.255", FWUPD_VERSION_FORMAT_PAIR }, { 0xffffffffffffffff, "4294967295.4294967295", FWUPD_VERSION_FORMAT_PAIR }, { 0x0, "0", FWUPD_VERSION_FORMAT_NUMBER }, { 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_uint64[i].ver != NULL; i++) { g_autofree gchar *ver = NULL; ver = fu_common_version_from_uint64 (version_from_uint64[i].val, version_from_uint64[i].flags); g_assert_cmpstr (ver, ==, version_from_uint64[i].ver); } 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_from_format (version_parse[i].old, FWUPD_VERSION_FORMAT_TRIPLET); 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); } static void fu_firmware_ihex_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(FuFirmware) firmware = fu_ihex_firmware_new (); g_autoptr(GBytes) data_file = NULL; g_autoptr(GBytes) data_fw = NULL; g_autoptr(GBytes) data_hex = NULL; g_autoptr(GBytes) data_ref = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_ref = NULL; g_autoptr(GFile) file_hex = NULL; /* load a Intel hex32 file */ filename_hex = g_build_filename (TESTDATADIR_SRC, "firmware.hex", NULL); file_hex = g_file_new_for_path (filename_hex); data_file = g_file_load_bytes (file_hex, NULL, NULL, &error); g_assert_no_error (error); g_assert (data_file != NULL); ret = fu_firmware_parse (firmware, data_file, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); data_fw = fu_firmware_get_image_default_bytes (firmware, &error); g_assert_no_error (error); g_assert_nonnull (data_fw); g_assert_cmpint (g_bytes_get_size (data_fw), ==, 136); /* did we match the reference file? */ filename_ref = g_build_filename (TESTDATADIR_SRC, "firmware.bin", NULL); file_ref = g_file_new_for_path (filename_ref); data_ref = g_file_load_bytes (file_ref, NULL, NULL, &error); g_assert_no_error (error); g_assert (data_ref != NULL); ret = fu_common_bytes_compare (data_fw, 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 */ data_hex = fu_firmware_write (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"); } static void fu_firmware_ihex_signed_func (void) { const guint8 *data; gboolean ret; gsize len; g_autofree gchar *filename_shex = NULL; g_autoptr(FuFirmware) firmware = fu_ihex_firmware_new (); g_autoptr(GBytes) data_file = NULL; g_autoptr(GBytes) data_fw = NULL; g_autoptr(GBytes) data_sig = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_hex = NULL; /* load a signed Intel hex32 file */ filename_shex = g_build_filename (TESTDATADIR_SRC, "firmware.shex", NULL); file_hex = g_file_new_for_path (filename_shex); data_file = g_file_load_bytes (file_hex, NULL, NULL, &error); g_assert_no_error (error); g_assert (data_file != NULL); ret = fu_firmware_parse (firmware, data_file, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); data_fw = fu_firmware_get_image_by_id_bytes (firmware, NULL, &error); g_assert_no_error (error); g_assert_nonnull (data_fw); g_assert_cmpint (g_bytes_get_size (data_fw), ==, 136); /* get the signed image */ data_sig = fu_firmware_get_image_by_id_bytes (firmware, FU_FIRMWARE_IMAGE_ID_SIGNATURE, &error); g_assert_no_error (error); g_assert_nonnull (data_sig); data = g_bytes_get_data (data_sig, &len); g_assert_cmpint (len, ==, 8); g_assert (data != NULL); g_assert_cmpint (memcmp (data, "deadbeef", 8), ==, 0); } static void fu_firmware_ihex_offset_func (void) { const guint8 *data; gboolean ret; gsize len; g_autofree gchar *str = NULL; g_autoptr(FuFirmware) firmware = fu_ihex_firmware_new (); g_autoptr(FuFirmware) firmware_verify = fu_ihex_firmware_new (); g_autoptr(FuFirmwareImage) img_verify = NULL; g_autoptr(FuFirmwareImage) img = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GBytes) data_dummy = NULL; g_autoptr(GBytes) data_verify = NULL; g_autoptr(GError) error = NULL; /* add a 4 byte image in high memory */ data_dummy = g_bytes_new_static ("foo", 4); img = fu_firmware_image_new (data_dummy); fu_firmware_image_set_addr (img, 0x80000000); fu_firmware_add_image (firmware, img); data_bin = fu_firmware_write (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 */ ret = fu_firmware_parse (firmware_verify, data_bin, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); img_verify = fu_firmware_get_image_default (firmware_verify, &error); g_assert_no_error (error); g_assert (img_verify != NULL); g_assert_cmpint (fu_firmware_image_get_addr (img_verify), ==, 0x80000000); data_verify = fu_firmware_image_write (img_verify, &error); g_assert_no_error (error); g_assert (data_verify != NULL); g_assert_cmpint (g_bytes_get_size (data_verify), ==, 0x4); } static void fu_firmware_srec_func (void) { gboolean ret; g_autofree gchar *filename_srec = NULL; g_autofree gchar *filename_ref = NULL; g_autoptr(FuFirmware) firmware = fu_srec_firmware_new (); g_autoptr(GBytes) data_ref = NULL; g_autoptr(GBytes) data_srec = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_bin = NULL; g_autoptr(GFile) file_srec = NULL; filename_srec = g_build_filename (TESTDATADIR_SRC, "firmware.srec", NULL); file_srec = g_file_new_for_path (filename_srec); data_srec = g_file_load_bytes (file_srec, NULL, NULL, &error); g_assert_no_error (error); g_assert (data_srec != NULL); ret = fu_firmware_parse (firmware, data_srec, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); data_bin = fu_firmware_get_image_default_bytes (firmware, &error); g_assert_no_error (error); g_assert_nonnull (data_bin); g_assert_cmpint (g_bytes_get_size (data_bin), ==, 136); /* did we match the reference file? */ filename_ref = g_build_filename (TESTDATADIR_SRC, "firmware.bin", NULL); file_bin = g_file_new_for_path (filename_ref); data_ref = g_file_load_bytes (file_bin, NULL, NULL, &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 fu_firmware_srec_tokenization_func (void) { FuSrecFirmwareRecord *rcd; GPtrArray *records; gboolean ret; g_autoptr(FuFirmware) firmware = fu_srec_firmware_new (); g_autoptr(GBytes) data_srec = NULL; g_autoptr(GError) error = NULL; const gchar *buf = "S3060000001400E5\r\n" "S31000000002281102000000007F0304002C\r\n" "S306000000145095\r\n" "S70500000000FA\r\n"; data_srec = g_bytes_new_static (buf, strlen (buf)); g_assert_no_error (error); g_assert (data_srec != NULL); ret = fu_firmware_tokenize (firmware, data_srec, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); records = fu_srec_firmware_get_records (FU_SREC_FIRMWARE (firmware)); g_assert_nonnull (records); g_assert_cmpint (records->len, ==, 4); rcd = g_ptr_array_index (records, 2); g_assert_nonnull (rcd); g_assert_cmpint (rcd->ln, ==, 0x3); g_assert_cmpint (rcd->kind, ==, 3); g_assert_cmpint (rcd->addr, ==, 0x14); g_assert_cmpint (rcd->buf->len, ==, 0x1); g_assert_cmpint (rcd->buf->data[0], ==, 0x50); } static void fu_firmware_dfu_func (void) { gboolean ret; g_autofree gchar *filename_dfu = NULL; g_autofree gchar *filename_ref = NULL; g_autoptr(FuFirmware) firmware = fu_dfu_firmware_new (); g_autoptr(GBytes) data_ref = NULL; g_autoptr(GBytes) data_dfu = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_bin = NULL; g_autoptr(GFile) file_dfu = NULL; filename_dfu = g_build_filename (TESTDATADIR_SRC, "firmware.dfu", NULL); file_dfu = g_file_new_for_path (filename_dfu); data_dfu = g_file_load_bytes (file_dfu, NULL, NULL, &error); g_assert_no_error (error); g_assert (data_dfu != NULL); ret = fu_firmware_parse (firmware, data_dfu, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fu_dfu_firmware_get_vid (FU_DFU_FIRMWARE (firmware)), ==, 0x1234); g_assert_cmpint (fu_dfu_firmware_get_pid (FU_DFU_FIRMWARE (firmware)), ==, 0x4321); g_assert_cmpint (fu_dfu_firmware_get_release (FU_DFU_FIRMWARE (firmware)), ==, 0xdead); data_bin = fu_firmware_get_image_default_bytes (firmware, &error); g_assert_no_error (error); g_assert_nonnull (data_bin); g_assert_cmpint (g_bytes_get_size (data_bin), ==, 136); /* did we match the reference file? */ filename_ref = g_build_filename (TESTDATADIR_SRC, "firmware.bin", NULL); file_bin = g_file_new_for_path (filename_ref); data_ref = g_file_load_bytes (file_bin, NULL, NULL, &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 fu_firmware_func (void) { g_autoptr(FuFirmware) firmware = fu_firmware_new (); g_autoptr(FuFirmwareImage) img1 = fu_firmware_image_new (NULL); g_autoptr(FuFirmwareImage) img2 = fu_firmware_image_new (NULL); g_autoptr(FuFirmwareImage) img_id = NULL; g_autoptr(FuFirmwareImage) img_idx = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *str = NULL; fu_firmware_image_set_addr (img1, 0x200); fu_firmware_image_set_idx (img1, 13); fu_firmware_image_set_id (img1, "primary"); fu_firmware_add_image (firmware, img1); fu_firmware_image_set_addr (img2, 0x400); fu_firmware_image_set_idx (img2, 23); fu_firmware_image_set_id (img2, "secondary"); fu_firmware_add_image (firmware, img2); img_id = fu_firmware_get_image_by_id (firmware, "NotGoingToExist", &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null (img_id); g_clear_error (&error); img_id = fu_firmware_get_image_by_id (firmware, "primary", &error); g_assert_no_error (error); g_assert_nonnull (img_id); g_assert_cmpint (fu_firmware_image_get_addr (img_id), ==, 0x200); g_assert_cmpint (fu_firmware_image_get_idx (img_id), ==, 13); g_assert_cmpstr (fu_firmware_image_get_id (img_id), ==, "primary"); img_idx = fu_firmware_get_image_by_idx (firmware, 123456, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null (img_idx); g_clear_error (&error); img_idx = fu_firmware_get_image_by_idx (firmware, 23, &error); g_assert_no_error (error); g_assert_nonnull (img_idx); g_assert_cmpint (fu_firmware_image_get_addr (img_idx), ==, 0x400); g_assert_cmpint (fu_firmware_image_get_idx (img_idx), ==, 23); g_assert_cmpstr (fu_firmware_image_get_id (img_idx), ==, "secondary"); str = fu_firmware_to_string (firmware); g_assert_cmpstr (str, ==, "FuFirmware:\n" " FuFirmwareImage:\n" " ID: primary\n" " Index: 0xd\n" " Address: 0x200\n" " FuFirmwareImage:\n" " ID: secondary\n" " Index: 0x17\n" " Address: 0x400\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); 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_OFFLINE_TRIGGER", "/tmp/fwupd-self-test/system-update", TRUE); g_setenv ("FWUPD_LOCALSTATEDIR", "/tmp/fwupd-self-test/var", TRUE); g_test_add_func ("/fwupd/plugin{delay}", fu_plugin_delay_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/chunk", fu_chunk_func); g_test_add_func ("/fwupd/common{string-append-kv}", fu_common_string_append_kv_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); g_test_add_func ("/fwupd/common{kernel-lockdown}", fu_common_kernel_lockdown_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/firmware", fu_firmware_func); g_test_add_func ("/fwupd/firmware{ihex}", fu_firmware_ihex_func); g_test_add_func ("/fwupd/firmware{ihex-offset}", fu_firmware_ihex_offset_func); g_test_add_func ("/fwupd/firmware{ihex-signed}", fu_firmware_ihex_signed_func); g_test_add_func ("/fwupd/firmware{srec-tokenization}", fu_firmware_srec_tokenization_func); g_test_add_func ("/fwupd/firmware{srec}", fu_firmware_srec_func); g_test_add_func ("/fwupd/firmware{dfu}", fu_firmware_dfu_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/device{incorporate}", fu_device_incorporate_func); if (g_test_slow ()) 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); return g_test_run (); } fwupd-1.3.9/libfwupdplugin/fu-smbios-private.h000066400000000000000000000007051362775233600214510ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-smbios.h" 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); fwupd-1.3.9/libfwupdplugin/fu-smbios.c000066400000000000000000000310401362775233600177700ustar00rootroot00000000000000/* * 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-private.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 * * Since: 1.0.0 **/ 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_from_path: * @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 * * Since: 1.0.0 **/ 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 * * Since: 1.0.0 **/ 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 * * Since: 1.0.0 **/ 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 * * Since: 1.0.0 **/ 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 * * Since: 1.0.0 **/ 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 * * Since: 1.0.0 **/ FuSmbios * fu_smbios_new (void) { FuSmbios *self; self = g_object_new (FU_TYPE_SMBIOS, NULL); return FU_SMBIOS (self); } fwupd-1.3.9/libfwupdplugin/fu-smbios.h000066400000000000000000000013511362775233600177770ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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 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); fwupd-1.3.9/libfwupdplugin/fu-srec-firmware.c000066400000000000000000000246241362775233600212540ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-common.h" #include "fu-firmware-common.h" #include "fu-srec-firmware.h" /** * SECTION:fu-srec-firmware * @short_description: SREC firmware image * * An object that represents a SREC firmware image. * * See also: #FuFirmware */ struct _FuSrecFirmware { FuFirmware parent_instance; GPtrArray *records; }; G_DEFINE_TYPE (FuSrecFirmware, fu_srec_firmware, FU_TYPE_FIRMWARE) /** * fu_srec_firmware_get_records: * @self: A #FuSrecFirmware * * Returns the raw records from SREC tokenization. * * This might be useful if the plugin is expecting the SREC file to be a list * of operations, rather than a simple linear image with filled holes. * * Returns: (transfer none) (element-type FuSrecFirmwareRecord): records * * Since: 1.3.2 **/ GPtrArray * fu_srec_firmware_get_records (FuSrecFirmware *self) { g_return_val_if_fail (FU_IS_SREC_FIRMWARE (self), NULL); return self->records; } static void fu_srec_firmware_record_free (FuSrecFirmwareRecord *rcd) { g_byte_array_unref (rcd->buf); g_free (rcd); } /** * fu_srec_firmware_record_new: (skip): * @ln: unsigned integer * @kind: #FuFirmwareSrecRecordKind * @addr: unsigned integer * * Returns a single firmware record * * Returns: (transfer full) (element-type FuSrecFirmwareRecord): records * * Since: 1.3.2 **/ FuSrecFirmwareRecord * fu_srec_firmware_record_new (guint ln, FuFirmareSrecRecordKind kind, guint32 addr) { FuSrecFirmwareRecord *rcd = g_new0 (FuSrecFirmwareRecord, 1); rcd->ln = ln; rcd->kind = kind; rcd->addr = addr; rcd->buf = g_byte_array_new (); return rcd; } static gboolean fu_srec_firmware_tokenize (FuFirmware *firmware, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSrecFirmware *self = FU_SREC_FIRMWARE (firmware); const gchar *data; gboolean got_eof = FALSE; gsize sz = 0; g_auto(GStrv) lines = NULL; /* parse records */ data = g_bytes_get_data (fw, &sz); lines = fu_common_strnsplit (data, sz, "\n", -1); for (guint ln = 0; lines[ln] != NULL; ln++) { FuSrecFirmwareRecord *rcd; 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 + 1); 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 + 1, (guint) linesz); return FALSE; } /* kind, count, address, (data), checksum, linefeed */ rec_kind = line[1] - '0'; rec_count = fu_firmware_strparse_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 + 1, (guint) linesz - 4, (guint) rec_count * 2); return FALSE; } /* checksum check */ if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { guint8 rec_csum = 0; guint8 rec_csum_expected; for (guint8 i = 0; i < rec_count; i++) rec_csum += fu_firmware_strparse_uint8 (line + (i * 2) + 2); rec_csum ^= 0xff; rec_csum_expected = fu_firmware_strparse_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 + 1, rec_csum_expected, rec_csum); return FALSE; } } /* set each command settings */ switch (rec_kind) { case FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER: addrsz = 2; break; case FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16: addrsz = 2; break; case FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24: addrsz = 3; break; case FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32: addrsz = 4; break; case FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16: addrsz = 2; got_eof = TRUE; break; case FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24: addrsz = 3; break; case FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32: addrsz = 4; got_eof = TRUE; break; case FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24: addrsz = 3; got_eof = TRUE; break; case FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16: addrsz = 2; got_eof = TRUE; break; default: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid srec record type S%c at line %u", line[1], ln + 1); return FALSE; } /* parse address */ switch (addrsz) { case 2: rec_addr32 = fu_firmware_strparse_uint16 (line + 4); break; case 3: rec_addr32 = fu_firmware_strparse_uint24 (line + 4); break; case 4: rec_addr32 = fu_firmware_strparse_uint32 (line + 4); break; default: g_assert_not_reached (); } g_debug ("line %03u S%u addr:0x%04x datalen:0x%02x", ln + 1, rec_kind, rec_addr32, (guint) rec_count - addrsz - 1); /* data */ rcd = fu_srec_firmware_record_new (ln + 1, rec_kind, rec_addr32); if (rec_kind == 1 || rec_kind == 2 || rec_kind == 3) { for (guint8 i = 4 + (addrsz * 2); i <= rec_count * 2; i += 2) { guint8 tmp = fu_firmware_strparse_uint8 (line + i); fu_byte_array_append_uint8 (rcd->buf, tmp); } } g_ptr_array_add (self->records, rcd); } /* no EOF */ if (!got_eof) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no EOF, perhaps truncated file"); return FALSE; } return TRUE; } static gboolean fu_srec_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuSrecFirmware *self = FU_SREC_FIRMWARE (firmware); gboolean got_hdr = FALSE; guint16 data_cnt = 0; guint32 addr32_last = 0; guint32 img_address = 0; g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (NULL); g_autoptr(GBytes) img_bytes = NULL; g_autoptr(GByteArray) outbuf = g_byte_array_new (); /* parse records */ for (guint j = 0; j < self->records->len; j++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index (self->records, j); /* header */ if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER) { g_autoptr(GString) modname = g_string_new (NULL); /* check for duplicate */ if (got_hdr) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "duplicate header record at line %u", rcd->ln); return FALSE; } /* could be anything, lets assume text */ for (guint8 i = 0; i < rcd->buf->len; i++) { gchar tmp = rcd->buf->data[i]; if (!g_ascii_isgraph (tmp)) break; g_string_append_c (modname, tmp); } if (modname->len != 0) fu_firmware_image_set_id (img, modname->str); got_hdr = TRUE; continue; } /* verify we got all records */ if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16) { if (rcd->addr != data_cnt) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "count record was not valid, got 0x%02x expected 0x%02x at line %u", (guint) rcd->addr, (guint) data_cnt, rcd->ln); return FALSE; } continue; } /* data */ if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16 || rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24 || rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32) { /* invalid */ if (!got_hdr) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "missing header record at line %u", rcd->ln); return FALSE; } /* does not make sense */ if (rcd->addr < addr32_last) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid address 0x%x, last was 0x%x at line %u", (guint) rcd->addr, (guint) addr32_last, rcd->ln); return FALSE; } if (rcd->addr < addr_start) { g_debug ("ignoring data at 0x%x as before start address 0x%x at line %u", (guint) rcd->addr, (guint) addr_start, rcd->ln); } else { guint32 len_hole = rcd->addr - 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 at line %u", (guint) len_hole, rcd->ln); return FALSE; } if (addr32_last > 0x0 && len_hole > 1) { g_debug ("filling address 0x%08x to 0x%08x at line %u", addr32_last + 1, addr32_last + len_hole - 1, rcd->ln); for (guint i = 0; i < len_hole; i++) fu_byte_array_append_uint8 (outbuf, 0xff); } /* add data */ g_byte_array_append (outbuf, rcd->buf->data, rcd->buf->len); if (img_address == 0x0) img_address = rcd->addr; addr32_last = rcd->addr + rcd->buf->len; } data_cnt++; } } /* add single image */ img_bytes = g_bytes_new (outbuf->data, outbuf->len); fu_firmware_image_set_bytes (img, img_bytes); fu_firmware_image_set_addr (img, img_address); fu_firmware_add_image (firmware, img); return TRUE; } static void fu_srec_firmware_finalize (GObject *object) { FuSrecFirmware *self = FU_SREC_FIRMWARE (object); g_ptr_array_unref (self->records); G_OBJECT_CLASS (fu_srec_firmware_parent_class)->finalize (object); } static void fu_srec_firmware_init (FuSrecFirmware *self) { self->records = g_ptr_array_new_with_free_func ((GFreeFunc) fu_srec_firmware_record_free); } static void fu_srec_firmware_class_init (FuSrecFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); object_class->finalize = fu_srec_firmware_finalize; klass_firmware->parse = fu_srec_firmware_parse; klass_firmware->tokenize = fu_srec_firmware_tokenize; } /** * fu_srec_firmware_new: * * Creates a new #FuFirmware of sub type Srec * * Since: 1.3.2 **/ FuFirmware * fu_srec_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_SREC_FIRMWARE, NULL)); } fwupd-1.3.9/libfwupdplugin/fu-srec-firmware.h000066400000000000000000000022221362775233600212470ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_SREC_FIRMWARE (fu_srec_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuSrecFirmware, fu_srec_firmware, FU, SREC_FIRMWARE, FuFirmware) typedef enum { FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER, FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16, FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24, FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32, FU_FIRMWARE_SREC_RECORD_KIND_S4_RESERVED, FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16, FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24, FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32, FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24, FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16, FU_FIRMWARE_SREC_RECORD_KIND_LAST } FuFirmareSrecRecordKind; typedef struct { guint ln; FuFirmareSrecRecordKind kind; guint32 addr; GByteArray *buf; } FuSrecFirmwareRecord; FuFirmware *fu_srec_firmware_new (void); GPtrArray *fu_srec_firmware_get_records (FuSrecFirmware *self); FuSrecFirmwareRecord *fu_srec_firmware_record_new (guint ln, FuFirmareSrecRecordKind kind, guint32 addr); fwupd-1.3.9/libfwupdplugin/fu-udev-device-private.h000066400000000000000000000003261362775233600223540ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-udev-device.h" void fu_udev_device_emit_changed (FuUdevDevice *self); fwupd-1.3.9/libfwupdplugin/fu-udev-device.c000066400000000000000000000715531362775233600207110ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuUdevDevice" #include "config.h" #include #include #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_IOCTL_H #include #endif #include #include #include #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; guint32 vendor; guint32 model; guint8 revision; gchar *subsystem; gchar *device_file; gint fd; FuUdevDeviceFlags flags; } FuUdevDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuUdevDevice, fu_udev_device, FU_TYPE_DEVICE) enum { PROP_0, PROP_UDEV_DEVICE, PROP_SUBSYSTEM, PROP_DEVICE_FILE, 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 guint32 fu_udev_device_get_sysfs_attr_as_uint32 (GUdevDevice *udev_device, const gchar *name) { #ifdef HAVE_GUDEV guint64 tmp = fu_common_strtoull (g_udev_device_get_sysfs_attr (udev_device, name)); if (tmp > G_MAXUINT32) { g_warning ("reading %s for %s overflowed", name, g_udev_device_get_sysfs_path (udev_device)); return G_MAXUINT32; } return tmp; #else return G_MAXUINT32; #endif } static guint8 fu_udev_device_get_sysfs_attr_as_uint8 (GUdevDevice *udev_device, const gchar *name) { #ifdef HAVE_GUDEV guint64 tmp = fu_common_strtoull (g_udev_device_get_sysfs_attr (udev_device, name)); if (tmp > G_MAXUINT8) { g_warning ("reading %s for %s overflowed", name, g_udev_device_get_sysfs_path (udev_device)); return G_MAXUINT8; } return tmp; #else return G_MAXUINT8; #endif } #ifdef HAVE_GUDEV static void fu_udev_device_to_string_raw (GUdevDevice *udev_device, guint idt, GString *str) { const gchar * const *keys; keys = g_udev_device_get_property_keys (udev_device); for (guint i = 0; keys[i] != NULL; i++) { fu_common_string_append_kv (str, idt, 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++) { fu_common_string_append_kv (str, idt, keys[i], g_udev_device_get_sysfs_attr (udev_device, keys[i])); } } #endif static void fu_udev_device_to_string (FuDevice *device, guint idt, GString *str) { #ifdef HAVE_GUDEV FuUdevDevice *self = FU_UDEV_DEVICE (device); FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GUdevDevice) udev_parent = NULL; if (priv->udev_device == NULL) return; if (g_getenv ("FU_UDEV_DEVICE_DEBUG") == NULL) return; fu_udev_device_to_string_raw (priv->udev_device, idt, str); udev_parent = g_udev_device_get_parent (priv->udev_device); if (udev_parent != NULL) { fu_common_string_append_kv (str, idt, "Parent", NULL); fu_udev_device_to_string_raw (udev_parent, idt + 1, str); } #endif } 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); #ifdef HAVE_GUDEV const gchar *tmp; g_autofree gchar *subsystem = NULL; g_autoptr(GUdevDevice) udev_parent = NULL; g_autoptr(GUdevDevice) parent_i2c = NULL; #endif /* nothing to do */ if (priv->udev_device == NULL) return TRUE; /* set ven:dev:rev */ priv->vendor = fu_udev_device_get_sysfs_attr_as_uint32 (priv->udev_device, "vendor"); priv->model = fu_udev_device_get_sysfs_attr_as_uint32 (priv->udev_device, "device"); priv->revision = fu_udev_device_get_sysfs_attr_as_uint8 (priv->udev_device, "revision"); #ifdef HAVE_GUDEV /* fallback to the parent */ udev_parent = g_udev_device_get_parent (priv->udev_device); if (udev_parent != NULL && priv->flags & FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT && priv->vendor == 0x0 && priv->model == 0x0 && priv->revision == 0x0) { priv->vendor = fu_udev_device_get_sysfs_attr_as_uint32 (udev_parent, "vendor"); priv->model = fu_udev_device_get_sysfs_attr_as_uint32 (udev_parent, "device"); priv->revision = fu_udev_device_get_sysfs_attr_as_uint8 (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 (priv->subsystem, "hidraw") == 0) { tmp = g_udev_device_get_property (udev_parent, "HID_ID"); if (tmp != NULL) { g_auto(GStrv) split = g_strsplit (tmp, ":", -1); if (g_strv_length (split) == 3) { guint64 val = g_ascii_strtoull (split[1], NULL, 16); if (val > G_MAXUINT32) { g_warning ("reading %s for %s overflowed", split[1], g_udev_device_get_sysfs_path (priv->udev_device)); } else { priv->vendor = val; } val = g_ascii_strtoull (split[2], NULL, 16); if (val > G_MAXUINT32) { g_warning ("reading %s for %s overflowed", split[2], g_udev_device_get_sysfs_path (priv->udev_device)); } else { priv->model = val; } } } tmp = g_udev_device_get_property (udev_parent, "HID_NAME"); if (tmp != NULL) { if (fu_device_get_name (device) == NULL) fu_device_set_name (device, tmp); } } /* try harder to find a vendor name the user will recognise */ if (priv->flags & FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT && udev_parent != NULL && fu_device_get_vendor (device) == NULL) { g_autoptr(GUdevDevice) device_tmp = g_object_ref (udev_parent); for (guint i = 0; i < 0xff; i++) { g_autoptr(GUdevDevice) parent = NULL; const gchar *id_vendor; id_vendor = g_udev_device_get_property (device_tmp, "ID_VENDOR_FROM_DATABASE"); if (id_vendor != NULL) { fu_device_set_vendor (device, id_vendor); break; } parent = g_udev_device_get_parent (device_tmp); if (parent == NULL) break; g_set_object (&device_tmp, parent); } } /* 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) tmp = g_udev_device_get_property (priv->udev_device, "ID_PCI_CLASS_FROM_DATABASE"); 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 (g_udev_device_get_subsystem (priv->udev_device), -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) { 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); } /* add subsystem to match in plugins */ if (subsystem != NULL) { fu_device_add_instance_id_full (device, subsystem, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* determine if we're wired internally */ parent_i2c = g_udev_device_get_parent_with_subsystem (priv->udev_device, "i2c", NULL); if (parent_i2c != NULL) fu_device_add_flag (device, FWUPD_DEVICE_FLAG_INTERNAL); #endif /* 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); #ifdef HAVE_GUDEV const gchar *summary; g_autoptr(GUdevDevice) parent = NULL; #endif 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; #ifdef HAVE_GUDEV priv->subsystem = g_strdup (g_udev_device_get_subsystem (priv->udev_device)); priv->device_file = g_strdup (g_udev_device_get_device_file (priv->udev_device)); /* try to get one line summary */ summary = g_udev_device_get_sysfs_attr (priv->udev_device, "description"); if (summary == NULL) { parent = g_udev_device_get_parent (priv->udev_device); if (parent != NULL) summary = g_udev_device_get_sysfs_attr (parent, "description"); } if (summary != NULL) fu_device_set_summary (FU_DEVICE (self), summary); #endif } /** * fu_udev_device_get_slot_depth: * @self: A #FuUdevDevice * @subsystem: a subsystem * * Determine how far up a chain a given device is * * Returns: unsigned integer * * Since: 1.2.4 **/ guint fu_udev_device_get_slot_depth (FuUdevDevice *self, const gchar *subsystem) { #ifdef HAVE_GUDEV 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); } #endif return 0; } static void fu_udev_device_incorporate (FuDevice *self, FuDevice *donor) { FuUdevDevice *uself = FU_UDEV_DEVICE (self); FuUdevDevice *udonor = FU_UDEV_DEVICE (donor); FuUdevDevicePrivate *priv = GET_PRIVATE (uself); g_return_if_fail (FU_IS_UDEV_DEVICE (self)); g_return_if_fail (FU_IS_UDEV_DEVICE (donor)); fu_udev_device_set_dev (uself, fu_udev_device_get_dev (udonor)); if (priv->device_file == NULL) { priv->subsystem = g_strdup (fu_udev_device_get_subsystem (udonor)); priv->device_file = g_strdup (fu_udev_device_get_device_file (udonor)); } } /** * 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 #FuUdevDevice * * 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 priv->subsystem; } /** * fu_udev_device_get_device_file: * @self: A #FuUdevDevice * * Gets the device node. * * Returns: a device file, or NULL if unset * * Since: 1.3.1 **/ const gchar * fu_udev_device_get_device_file (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), NULL); return priv->device_file; } /** * fu_udev_device_get_sysfs_path: * @self: A #FuUdevDevice * * 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) { #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), NULL); if (priv->udev_device != NULL) return g_udev_device_get_sysfs_path (priv->udev_device); #endif return NULL; } /** * fu_udev_device_get_vendor: * @self: A #FuUdevDevice * * Gets the device vendor code. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.1.2 **/ guint32 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 #FuUdevDevice * * Gets the device device code. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.1.2 **/ guint32 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 #FuUdevDevice * * 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; } #ifdef HAVE_GUDEV static gchar * 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 self and all parent devices */ if (priv->subsystem != NULL) g_string_append_printf (str, "%s,", priv->subsystem); 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 g_string_free (str, FALSE); } #endif /** * fu_udev_device_set_physical_id: * @self: A #FuUdevDevice * @subsystems: A subsystem string, e.g. `pci,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 *subsystems, GError **error) { #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE (self); const gchar *subsystem = NULL; const gchar *tmp; g_autofree gchar *physical_id = NULL; g_auto(GStrv) split = NULL; g_autoptr(GUdevDevice) udev_device = NULL; g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), FALSE); g_return_val_if_fail (subsystems != NULL, FALSE); /* nothing to do */ if (priv->udev_device == NULL) return TRUE; /* look for each subsystem in turn */ split = g_strsplit (subsystems, ",", -1); for (guint i = 0; split[i] != NULL; i++) { subsystem = split[i]; if (g_strcmp0 (priv->subsystem, subsystem) == 0) { udev_device = g_object_ref (priv->udev_device); break; } udev_device = g_udev_device_get_parent_with_subsystem (priv->udev_device, subsystem, NULL); if (udev_device != NULL) break; } if (udev_device == NULL) { g_autofree gchar *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 subsystems %s, only got %s", subsystems, 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, "mmc") == 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 if (g_strcmp0 (subsystem, "tpm") == 0 || g_strcmp0 (subsystem, "drm_dp_aux_dev") == 0) { tmp = g_udev_device_get_property (udev_device, "DEVNAME"); 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 ("DEVNAME=%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; #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return FALSE; #endif } /** * fu_udev_device_get_fd: * @self: A #FuUdevDevice * * Gets the file descriptor if the device is open. * * Returns: positive integer, or -1 if the device is not open * * Since: 1.3.3 **/ gint fu_udev_device_get_fd (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), -1); return priv->fd; } /** * fu_udev_device_set_fd: * @self: A #FuUdevDevice * @fd: A valid file descriptor * * Replace the file descriptor to use when the device has already been opened. * This object will automatically close() @fd when fu_device_close() is called. * * Since: 1.3.3 **/ void fu_udev_device_set_fd (FuUdevDevice *self, gint fd) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_UDEV_DEVICE (self)); if (priv->fd > 0) close (priv->fd); priv->fd = fd; } /** * fu_udev_device_set_readonly: * @self: A #FuUdevDevice * @readonly: %TRUE if the device file should be opened readonly * * Sets the open mode to `O_RDONLY` use when opening the device with * fu_device_open(). By default devices are opened with `O_RDWR`. * * Since: 1.3.3 **/ void fu_udev_device_set_readonly (FuUdevDevice *self, gboolean readonly) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_UDEV_DEVICE (self)); priv->flags = readonly ? FU_UDEV_DEVICE_FLAG_OPEN_READ : FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE; } /** * fu_udev_device_set_flags: * @self: A #FuUdevDevice * @flags: a #FuUdevDeviceFlags, e.g. %FU_UDEV_DEVICE_FLAG_OPEN_READ * * Sets the parameters to use when opening the device. * * For example %FU_UDEV_DEVICE_FLAG_OPEN_READ means that fu_device_open() * would use `O_RDONLY` rather than `O_RDWR` which is the default. * * Since: 1.3.6 **/ void fu_udev_device_set_flags (FuUdevDevice *self, FuUdevDeviceFlags flags) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_if_fail (FU_IS_UDEV_DEVICE (self)); priv->flags = flags; } static gboolean fu_udev_device_open (FuDevice *device, GError **error) { FuUdevDevice *self = FU_UDEV_DEVICE (device); FuUdevDevicePrivate *priv = GET_PRIVATE (self); FuUdevDeviceClass *klass = FU_UDEV_DEVICE_GET_CLASS (device); /* open device */ if (priv->device_file != NULL && priv->flags != FU_UDEV_DEVICE_FLAG_NONE) { gint flags; if (priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_READ && priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_WRITE) { flags = O_RDWR; } else if (priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_WRITE) { flags = O_WRONLY; } else { flags = O_RDONLY; } priv->fd = g_open (priv->device_file, flags, 0); if (priv->fd < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to open %s: %s", priv->device_file, strerror (errno)); return FALSE; } } /* subclassed */ if (klass->open != NULL) { if (!klass->open (self, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_udev_device_close (FuDevice *device, GError **error) { FuUdevDevice *self = FU_UDEV_DEVICE (device); FuUdevDevicePrivate *priv = GET_PRIVATE (self); FuUdevDeviceClass *klass = FU_UDEV_DEVICE_GET_CLASS (device); /* subclassed */ if (klass->close != NULL) { if (!klass->close (self, error)) return FALSE; } /* close device */ if (priv->fd > 0) { if (!g_close (priv->fd, error)) return FALSE; priv->fd = 0; } /* success */ return TRUE; } /** * fu_udev_device_ioctl: * @self: A #FuUdevDevice * @request: request number * @buf: A buffer to use, which *must* be large enough for the request * @rc: (out) (allow-none): the raw return value from the ioctl * @error: A #GError, or %NULL * * Control a device using a low-level request. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_udev_device_ioctl (FuUdevDevice *self, gulong request, guint8 *buf, gint *rc, GError **error) { #ifdef HAVE_IOCTL_H FuUdevDevicePrivate *priv = GET_PRIVATE (self); gint rc_tmp; g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), FALSE); g_return_val_if_fail (request != 0x0, FALSE); g_return_val_if_fail (buf != NULL, FALSE); g_return_val_if_fail (priv->fd > 0, FALSE); rc_tmp = ioctl (priv->fd, request, buf); if (rc != NULL) *rc = rc_tmp; if (rc_tmp < 0) { if (rc_tmp == -EPERM) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "permission denied"); return FALSE; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ioctl not supported: %s", strerror (errno)); return FALSE; } return TRUE; #else g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as not found"); return FALSE; #endif } /** * fu_udev_device_pwrite: * @self: A #FuUdevDevice * @port: offset address * @data: value * @error: A #GError, or %NULL * * Write to a file descriptor at a given offset. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_udev_device_pwrite (FuUdevDevice *self, goffset port, guint8 data, GError **error) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), FALSE); g_return_val_if_fail (port != 0x0, FALSE); g_return_val_if_fail (priv->fd > 0, FALSE); #ifdef HAVE_PWRITE if (pwrite (priv->fd, &data, 1, 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; #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as pwrite() is unavailable"); return FALSE; #endif } /** * fu_udev_device_pread: * @self: A #FuUdevDevice * @port: offset address * @data: (out): value * @error: A #GError, or %NULL * * Read from a file descriptor at a given offset. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_udev_device_pread (FuUdevDevice *self, goffset port, guint8 *data, GError **error) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UDEV_DEVICE (self), FALSE); g_return_val_if_fail (port != 0x0, FALSE); g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (priv->fd > 0, FALSE); #ifdef HAVE_PWRITE if (pread (priv->fd, data, 1, 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; #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as pread() is unavailable"); return FALSE; #endif } 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; case PROP_SUBSYSTEM: g_value_set_string (value, priv->subsystem); break; case PROP_DEVICE_FILE: g_value_set_string (value, priv->device_file); 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); FuUdevDevicePrivate *priv = GET_PRIVATE (self); switch (prop_id) { case PROP_UDEV_DEVICE: fu_udev_device_set_dev (self, g_value_get_object (value)); break; case PROP_SUBSYSTEM: priv->subsystem = g_value_dup_string (value); break; case PROP_DEVICE_FILE: priv->device_file = g_value_dup_string (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); g_free (priv->subsystem); g_free (priv->device_file); if (priv->udev_device != NULL) g_object_unref (priv->udev_device); if (priv->fd > 0) g_close (priv->fd, NULL); G_OBJECT_CLASS (fu_udev_device_parent_class)->finalize (object); } static void fu_udev_device_init (FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE (self); priv->flags = FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE; } 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; device_class->open = fu_udev_device_open; device_class->close = fu_udev_device_close; device_class->to_string = fu_udev_device_to_string; 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); pspec = g_param_spec_string ("subsystem", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_SUBSYSTEM, pspec); pspec = g_param_spec_string ("device-file", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property (object_class, PROP_DEVICE_FILE, 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.3.9/libfwupdplugin/fu-udev-device.h000066400000000000000000000053661362775233600207150ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #ifdef HAVE_GUDEV #include #else #define G_UDEV_TYPE_DEVICE G_TYPE_OBJECT #define GUdevDevice GObject #endif #include "fu-plugin.h" #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); gboolean (*open) (FuUdevDevice *device, GError **error); gboolean (*close) (FuUdevDevice *device, GError **error); gpointer __reserved[29]; }; /** * FuUdevDeviceFlags: * @FU_UDEV_DEVICE_FLAG_NONE: No flags set * @FU_UDEV_DEVICE_FLAG_OPEN_READ: Open the device read-only * @FU_UDEV_DEVICE_FLAG_OPEN_WRITE: Open the device write-only * @FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT: Get the vendor ID fallback from the parent * * Flags used when opening the device using fu_device_open(). **/ typedef enum { FU_UDEV_DEVICE_FLAG_NONE = 0, FU_UDEV_DEVICE_FLAG_OPEN_READ = 1 << 0, FU_UDEV_DEVICE_FLAG_OPEN_WRITE = 1 << 1, FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT = 1 << 2, /*< private >*/ FU_UDEV_DEVICE_FLAG_LAST } FuUdevDeviceFlags; FuUdevDevice *fu_udev_device_new (GUdevDevice *udev_device); GUdevDevice *fu_udev_device_get_dev (FuUdevDevice *self); const gchar *fu_udev_device_get_device_file (FuUdevDevice *self); const gchar *fu_udev_device_get_sysfs_path (FuUdevDevice *self); const gchar *fu_udev_device_get_subsystem (FuUdevDevice *self); guint32 fu_udev_device_get_vendor (FuUdevDevice *self); guint32 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 *subsystems, GError **error); void fu_udev_device_set_readonly (FuUdevDevice *self, gboolean readonly) G_GNUC_DEPRECATED_FOR(fu_udev_device_set_flags); void fu_udev_device_set_flags (FuUdevDevice *self, FuUdevDeviceFlags flags); gint fu_udev_device_get_fd (FuUdevDevice *self); void fu_udev_device_set_fd (FuUdevDevice *self, gint fd); gboolean fu_udev_device_ioctl (FuUdevDevice *self, gulong request, guint8 *buf, gint *rc, GError **error); gboolean fu_udev_device_pwrite (FuUdevDevice *self, goffset port, guint8 data, GError **error); gboolean fu_udev_device_pread (FuUdevDevice *self, goffset port, guint8 *data, GError **error); fwupd-1.3.9/libfwupdplugin/fu-usb-device-private.h000066400000000000000000000003271362775233600222030ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-usb-device.h" const gchar *fu_usb_device_get_platform_id (FuUsbDevice *self); fwupd-1.3.9/libfwupdplugin/fu-usb-device.c000066400000000000000000000403621362775233600205310ustar00rootroot00000000000000/* * 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-usb-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_query_hub (FuUsbDevice *self, GError **error) { FuUsbDevicePrivate *priv = GET_PRIVATE (self); gsize sz = 0; guint16 value = 0x29; guint8 data[0x0c] = { 0x0 }; g_autofree gchar *devid = NULL; /* longer descriptor for SuperSpeed */ if (fu_usb_device_get_spec (self) >= 0x0300) value = 0x2a; if (!g_usb_device_control_transfer (priv->usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_DEVICE, 0x06, /* LIBUSB_REQUEST_GET_DESCRIPTOR */ value << 8, 0x00, data, sizeof(data), &sz, 1000, NULL, error)) { g_prefix_error (error, "failed to get USB descriptor: "); return FALSE; } if (g_getenv ("FU_USB_DEVICE_DEBUG") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "HUB_DT", data, sz); /* see http://www.usblyzer.com/usb-hub-class-decoder.htm */ if (sz == 0x09) { devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X&HUB_%02X", g_usb_device_get_vid (priv->usb_device), g_usb_device_get_pid (priv->usb_device), data[7]); fu_device_add_instance_id (FU_DEVICE (self), devid); } else if (sz == 0x0c) { devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X&HUB_%02X%02X", g_usb_device_get_vid (priv->usb_device), g_usb_device_get_pid (priv->usb_device), data[11], data[10]); fu_device_add_instance_id (FU_DEVICE (self), devid); } return TRUE; } 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); } /* get the hub descriptor if this is a hub */ if (g_usb_device_get_device_class (priv->usb_device) == G_USB_DEVICE_CLASS_HUB) { if (!fu_usb_device_query_hub (self, error)) return FALSE; } /* 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_get_spec: * @self: A #FuUsbDevice * * Gets the string USB revision for the device. * * Return value: a specification revision in BCD format, or 0x0 if not supported * * Since: 1.3.4 **/ guint16 fu_usb_device_get_spec (FuUsbDevice *self) { #if G_USB_CHECK_VERSION(0,3,1) FuUsbDevicePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_USB_DEVICE (self), 0x0); if (priv->usb_device == NULL) return 0x0; return g_usb_device_get_spec (priv->usb_device); #else return 0x0; #endif } /** * 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_find_udev_device: * @device: A #FuUsbDevice * @error: A #GError, or %NULL * * Gets the matching #GUdevDevice for the #GUsbDevice. * * Returns: (transfer full): a #GUdevDevice, or NULL if unset or invalid * * Since: 1.3.2 **/ GUdevDevice * fu_usb_device_find_udev_device (FuUsbDevice *device, GError **error) { #ifdef HAVE_GUDEV FuUsbDevicePrivate *priv = GET_PRIVATE (device); 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, "usb"); for (GList *l = devices; l != NULL; l = l->next) { GUdevDevice *dev = G_UDEV_DEVICE (l->data); /* check correct device */ if (g_udev_device_get_sysfs_attr_as_int (dev, "busnum") != g_usb_device_get_bus (priv->usb_device)) continue; if (g_udev_device_get_sysfs_attr_as_int (dev, "devnum") != g_usb_device_get_address (priv->usb_device)) continue; /* success */ g_debug ("USB device %u:%u is %s", g_usb_device_get_bus (priv->usb_device), g_usb_device_get_address (priv->usb_device), g_udev_device_get_sysfs_path (dev)); return g_object_ref (dev); } /* failure */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "could not find sysfs device for %u:%u", g_usb_device_get_bus (priv->usb_device), g_usb_device_get_address (priv->usb_device)); #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); #endif return NULL; } /** * 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.3.9/libfwupdplugin/fu-usb-device.h000066400000000000000000000025771362775233600205440ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-plugin.h" #include "fu-udev-device.h" #define FU_TYPE_USB_DEVICE (fu_usb_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuUsbDevice, fu_usb_device, FU, USB_DEVICE, FuDevice) /* HID */ #define FU_HID_REPORT_GET 0x01 #define FU_HID_REPORT_SET 0x09 #define FU_HID_REPORT_TYPE_INPUT 0x01 #define FU_HID_REPORT_TYPE_OUTPUT 0x02 #define FU_HID_REPORT_TYPE_FEATURE 0x03 #define FU_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); guint16 fu_usb_device_get_spec (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); GUdevDevice *fu_usb_device_find_udev_device (FuUsbDevice *device, GError **error); fwupd-1.3.9/libfwupdplugin/fwupdplugin.h000066400000000000000000000025051362775233600204410ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once /** * SECTION:fwupdplugin * @short_description: Helper objects for plugins interacting with fwupd daemon */ #define __FWUPDPLUGIN_H_INSIDE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FWUPD_DISABLE_DEPRECATED #include #endif #undef __FWUPDPLUGIN_H_INSIDE__ fwupd-1.3.9/libfwupdplugin/fwupdplugin.map000066400000000000000000000313161362775233600207710ustar00rootroot00000000000000# generated automatically, do not edit! LIBFWUPDPLUGIN_0.1.0 { global: fu_device_add_flag; fu_device_get_metadata; fu_device_get_type; fu_device_new; fu_device_set_metadata; local: *; }; LIBFWUPDPLUGIN_0.3.5 { global: fu_common_vercmp; local: *; } LIBFWUPDPLUGIN_0.1.0; LIBFWUPDPLUGIN_0.6.1 { global: fu_device_get_equivalent_id; fu_device_set_equivalent_id; local: *; } LIBFWUPDPLUGIN_0.3.5; LIBFWUPDPLUGIN_0.7.1 { global: fu_device_set_id; fu_device_set_name; local: *; } LIBFWUPDPLUGIN_0.6.1; LIBFWUPDPLUGIN_0.7.2 { global: fu_device_add_guid; fu_device_get_alternate; fu_device_set_alternate; local: *; } LIBFWUPDPLUGIN_0.7.1; LIBFWUPDPLUGIN_0.8.0 { global: fu_plugin_alloc_data; fu_plugin_cache_add; fu_plugin_cache_lookup; fu_plugin_cache_remove; fu_plugin_device_add; fu_plugin_device_remove; fu_plugin_get_data; fu_plugin_get_enabled; fu_plugin_get_name; fu_plugin_get_type; fu_plugin_get_usb_context; fu_plugin_new; fu_plugin_open; fu_plugin_request_recoldplug; fu_plugin_runner_clear_results; fu_plugin_runner_coldplug; fu_plugin_runner_coldplug_cleanup; fu_plugin_runner_coldplug_prepare; fu_plugin_runner_get_results; fu_plugin_runner_startup; fu_plugin_runner_unlock; fu_plugin_runner_update; fu_plugin_runner_verify; fu_plugin_set_coldplug_delay; fu_plugin_set_enabled; fu_plugin_set_name; fu_plugin_set_usb_context; local: *; } LIBFWUPDPLUGIN_0.7.2; LIBFWUPDPLUGIN_0.9.1 { global: fu_plugin_check_hwid; local: *; } LIBFWUPDPLUGIN_0.8.0; LIBFWUPDPLUGIN_0.9.3 { global: fu_hwids_get_guid; fu_hwids_get_guids; fu_hwids_get_replace_keys; fu_hwids_get_replace_values; fu_hwids_get_type; fu_hwids_get_value; fu_hwids_has_guid; fu_hwids_new; fu_hwids_setup; local: *; } LIBFWUPDPLUGIN_0.9.1; LIBFWUPDPLUGIN_0.9.5 { global: fu_common_get_contents_fd; fu_common_set_contents_bytes; local: *; } LIBFWUPDPLUGIN_0.9.3; LIBFWUPDPLUGIN_0.9.7 { global: fu_common_extract_archive; fu_common_firmware_builder; fu_common_get_contents_bytes; fu_common_mkdir_parent; fu_common_rmtree; fu_common_spawn_sync; fu_device_get_metadata_boolean; fu_device_get_metadata_integer; fu_device_set_metadata_boolean; fu_device_set_metadata_integer; fu_plugin_device_register; fu_plugin_get_dmi_value; fu_plugin_runner_device_register; fu_plugin_set_hwids; local: *; } LIBFWUPDPLUGIN_0.9.5; LIBFWUPDPLUGIN_0.9.8 { global: fu_device_to_string; fu_plugin_get_smbios_data; fu_plugin_get_smbios_string; local: *; } LIBFWUPDPLUGIN_0.9.7; LIBFWUPDPLUGIN_1.0.0 { global: fu_device_locker_get_type; fu_device_locker_new; fu_device_locker_new_full; fu_plugin_add_rule; fu_plugin_get_order; fu_plugin_get_rules; fu_plugin_has_rule; fu_plugin_set_order; fu_plugin_set_priority; fu_plugin_set_smbios; fu_smbios_get_data; fu_smbios_get_string; fu_smbios_get_type; fu_smbios_new; fu_smbios_setup; fu_smbios_setup_from_file; fu_smbios_setup_from_path; fu_smbios_to_string; local: *; } LIBFWUPDPLUGIN_0.9.8; LIBFWUPDPLUGIN_1.0.1 { global: fu_chunk_array_to_string; fu_plugin_get_quirks; fu_plugin_lookup_quirk_by_id; fu_plugin_set_quirks; fu_quirks_get_type; fu_quirks_load; fu_quirks_lookup_by_id; fu_quirks_new; local: *; } LIBFWUPDPLUGIN_1.0.0; LIBFWUPDPLUGIN_1.0.2 { global: fu_device_get_remove_delay; fu_device_set_remove_delay; fu_plugin_runner_udev_device_added; fu_plugin_runner_udev_device_changed; fu_plugin_runner_usb_device_added; fu_usb_device_get_dev; fu_usb_device_get_type; fu_usb_device_new; fu_usb_device_set_dev; local: *; } LIBFWUPDPLUGIN_1.0.1; LIBFWUPDPLUGIN_1.0.3 { global: fu_common_read_uint16; fu_common_read_uint32; fu_common_write_uint16; fu_common_write_uint32; fu_device_get_progress; fu_device_get_quirks; fu_device_get_status; fu_device_set_progress; fu_device_set_progress_full; fu_device_set_quirks; fu_device_set_status; fu_usb_device_is_open; local: *; } LIBFWUPDPLUGIN_1.0.2; LIBFWUPDPLUGIN_1.0.4 { global: fu_plugin_add_report_metadata; fu_plugin_get_report_metadata; fu_plugin_runner_recoldplug; local: *; } LIBFWUPDPLUGIN_1.0.3; LIBFWUPDPLUGIN_1.0.5 { global: fu_device_get_release_default; local: *; } LIBFWUPDPLUGIN_1.0.4; LIBFWUPDPLUGIN_1.0.6 { global: fu_common_get_files_recursive; fu_plugin_get_config_value; local: *; } LIBFWUPDPLUGIN_1.0.5; LIBFWUPDPLUGIN_1.0.7 { global: fu_plugin_add_compile_version; fu_plugin_add_runtime_version; fu_plugin_set_compile_versions; fu_plugin_set_runtime_versions; local: *; } LIBFWUPDPLUGIN_1.0.6; LIBFWUPDPLUGIN_1.0.8 { global: fu_common_error_array_get_best; fu_common_get_path; fu_device_add_child; fu_device_add_parent_guid; fu_device_attach; fu_device_detach; fu_device_get_children; fu_device_get_guids_as_str; fu_device_get_order; fu_device_get_parent; fu_device_get_parent_guids; fu_device_has_parent_guid; fu_device_read_firmware; fu_device_set_order; fu_device_set_parent; fu_device_write_firmware; fu_plugin_guess_name_from_fn; fu_plugin_name_compare; fu_plugin_order_compare; local: *; } LIBFWUPDPLUGIN_1.0.7; LIBFWUPDPLUGIN_1.0.9 { global: fu_plugin_runner_composite_cleanup; fu_plugin_runner_composite_prepare; local: *; } LIBFWUPDPLUGIN_1.0.8; LIBFWUPDPLUGIN_1.1.0 { global: fu_device_get_alternate_id; fu_device_get_custom_flags; fu_device_has_custom_flag; fu_device_incorporate; fu_device_set_alternate_id; fu_device_set_custom_flags; local: *; } LIBFWUPDPLUGIN_1.0.9; LIBFWUPDPLUGIN_1.1.1 { global: fu_device_get_priority; fu_device_set_priority; fu_plugin_get_hwids; fu_plugin_get_priority; local: *; } LIBFWUPDPLUGIN_1.1.0; LIBFWUPDPLUGIN_1.1.2 { global: fu_chunk_array_new; fu_chunk_array_new_from_bytes; fu_chunk_new; fu_common_find_program_in_path; fu_common_strstrip; fu_common_strtoull; fu_device_add_counterpart_guid; fu_device_close; fu_device_ensure_id; fu_device_get_logical_id; fu_device_get_physical_id; fu_device_open; fu_device_poll; fu_device_prepare_firmware; fu_device_probe; fu_device_probe_invalidate; fu_device_set_firmware_size_max; fu_device_set_firmware_size_min; fu_device_set_logical_id; fu_device_set_physical_id; fu_device_set_poll_interval; fu_device_setup; fu_plugin_add_udev_subsystem; fu_plugin_lookup_quirk_by_id_as_uint64; fu_plugin_runner_device_removed; fu_plugin_runner_update_attach; fu_plugin_runner_update_cleanup; fu_plugin_runner_update_detach; fu_plugin_runner_update_prepare; fu_plugin_runner_update_reload; fu_plugin_set_udev_subsystems; fu_udev_device_emit_changed; fu_udev_device_get_dev; fu_udev_device_get_model; fu_udev_device_get_revision; fu_udev_device_get_subsystem; fu_udev_device_get_sysfs_path; fu_udev_device_get_type; fu_udev_device_get_vendor; fu_udev_device_new; fu_udev_device_set_physical_id; fu_usb_device_get_pid; fu_usb_device_get_platform_id; fu_usb_device_get_vid; local: *; } LIBFWUPDPLUGIN_1.1.1; LIBFWUPDPLUGIN_1.2.0 { global: fu_common_cab_build_silo; fu_common_string_replace; fu_common_version_from_uint16; fu_common_version_from_uint32; fu_common_version_guess_format; fu_common_version_parse; local: *; } LIBFWUPDPLUGIN_1.1.2; LIBFWUPDPLUGIN_1.2.2 { global: fu_archive_get_type; fu_archive_lookup_by_fn; fu_archive_new; fu_common_dump_bytes; fu_common_dump_raw; fu_device_has_guid; fu_io_channel_get_type; fu_io_channel_new_file; fu_io_channel_read_bytes; fu_io_channel_read_raw; fu_io_channel_shutdown; fu_io_channel_unix_get_fd; fu_io_channel_unix_new; fu_io_channel_write_bytes; fu_io_channel_write_raw; local: *; } LIBFWUPDPLUGIN_1.2.0; LIBFWUPDPLUGIN_1.2.4 { global: fu_common_bytes_align; fu_common_dump_full; fu_common_string_append_kb; fu_common_string_append_ku; fu_common_string_append_kv; fu_common_string_append_kx; fu_device_incorporate_from_component; fu_plugin_get_build_hash; fu_plugin_set_build_hash; fu_udev_device_get_slot_depth; local: *; } LIBFWUPDPLUGIN_1.2.2; LIBFWUPDPLUGIN_1.2.5 { global: fu_common_guid_is_plausible; fu_device_add_instance_id; fu_device_convert_instance_ids; local: *; } LIBFWUPDPLUGIN_1.2.4; LIBFWUPDPLUGIN_1.2.6 { global: fu_common_bytes_compare; fu_common_bytes_is_empty; fu_common_realpath; fu_device_activate; fu_device_get_firmware_size_max; fu_device_get_firmware_size_min; fu_device_set_firmware_size; fu_plugin_runner_activate; local: *; } LIBFWUPDPLUGIN_1.2.5; LIBFWUPDPLUGIN_1.2.9 { global: fu_common_version_ensure_semver; fu_common_version_verify_format; fu_device_add_instance_id_full; fu_device_set_version; local: *; } LIBFWUPDPLUGIN_1.2.6; LIBFWUPDPLUGIN_1.3.1 { global: fu_byte_array_append_uint16; fu_byte_array_append_uint32; fu_byte_array_append_uint8; fu_common_bytes_pad; fu_common_strnsplit; fu_device_rescan; fu_firmware_add_image; fu_firmware_get_image_by_id; fu_firmware_get_image_by_id_bytes; fu_firmware_get_image_by_idx; fu_firmware_get_image_by_idx_bytes; fu_firmware_get_image_default; fu_firmware_get_image_default_bytes; fu_firmware_get_images; fu_firmware_get_type; fu_firmware_image_get_addr; fu_firmware_image_get_id; fu_firmware_image_get_idx; fu_firmware_image_get_type; fu_firmware_image_new; fu_firmware_image_set_addr; fu_firmware_image_set_bytes; fu_firmware_image_set_id; fu_firmware_image_set_idx; fu_firmware_image_to_string; fu_firmware_image_write_chunk; fu_firmware_new; fu_firmware_new_from_bytes; fu_firmware_parse; fu_firmware_parse_full; fu_firmware_strparse_uint16; fu_firmware_strparse_uint24; fu_firmware_strparse_uint32; fu_firmware_strparse_uint4; fu_firmware_strparse_uint8; fu_firmware_to_string; fu_firmware_write; fu_ihex_firmware_get_type; fu_ihex_firmware_new; fu_memcpy_safe; fu_plugin_has_custom_flag; fu_udev_device_get_device_file; local: *; } LIBFWUPDPLUGIN_1.2.9; LIBFWUPDPLUGIN_1.3.2 { global: fu_common_bytes_compare_raw; fu_common_strwidth; fu_firmware_tokenize; fu_io_channel_read_byte_array; fu_io_channel_write_byte_array; fu_srec_firmware_get_records; fu_srec_firmware_get_type; fu_srec_firmware_new; fu_srec_firmware_record_new; fu_usb_device_find_udev_device; local: *; } LIBFWUPDPLUGIN_1.3.1; LIBFWUPDPLUGIN_1.3.3 { global: fu_common_read_uint16_safe; fu_common_read_uint32_safe; fu_common_read_uint8_safe; fu_common_version_parse_from_format; fu_device_cleanup; fu_device_get_possible_plugins; fu_device_get_specialized_gtype; fu_device_prepare; fu_device_reload; fu_device_remove_metadata; fu_dfu_firmware_get_pid; fu_dfu_firmware_get_release; fu_dfu_firmware_get_type; fu_dfu_firmware_get_version; fu_dfu_firmware_get_vid; fu_dfu_firmware_new; fu_dfu_firmware_set_pid; fu_dfu_firmware_set_release; fu_dfu_firmware_set_version; fu_dfu_firmware_set_vid; fu_firmware_get_version; fu_firmware_image_write; fu_firmware_parse_file; fu_firmware_set_version; fu_firmware_write_file; fu_plugin_add_firmware_gtype; fu_plugin_get_hwid_replace_value; fu_plugin_set_device_gtype; fu_quirks_lookup_by_id_iter; fu_udev_device_get_fd; fu_udev_device_ioctl; fu_udev_device_pread; fu_udev_device_pwrite; fu_udev_device_set_fd; fu_udev_device_set_readonly; local: *; } LIBFWUPDPLUGIN_1.3.2; LIBFWUPDPLUGIN_1.3.4 { global: fu_archive_iterate; fu_firmware_image_get_version; fu_firmware_image_set_version; fu_ihex_firmware_get_records; fu_usb_device_get_spec; local: *; } LIBFWUPDPLUGIN_1.3.3; LIBFWUPDPLUGIN_1.3.5 { global: fu_common_fnmatch; fu_device_get_protocol; fu_device_incorporate_flag; fu_device_set_protocol; fu_plugin_is_open; local: *; } LIBFWUPDPLUGIN_1.3.4; LIBFWUPDPLUGIN_1.3.6 { global: fu_common_version_from_uint64; fu_udev_device_set_flags; local: *; } LIBFWUPDPLUGIN_1.3.5; LIBFWUPDPLUGIN_1.3.8 { global: fu_common_kernel_locked_down; local: *; } LIBFWUPDPLUGIN_1.3.6; LIBFWUPDPLUGIN_1.3.9 { global: fu_common_vercmp_full; local: *; } LIBFWUPDPLUGIN_1.3.8; fwupd-1.3.9/libfwupdplugin/meson.build000066400000000000000000000130171362775233600200660ustar00rootroot00000000000000fwupdplugin_src = [ 'fu-archive.c', 'fu-chunk.c', 'fu-common.c', 'fu-common-cab.c', 'fu-common-guid.c', 'fu-common-version.c', 'fu-device-locker.c', 'fu-device.c', 'fu-dfu-firmware.c', 'fu-firmware.c', 'fu-firmware-common.c', 'fu-firmware-image.c', 'fu-hwids.c', 'fu-ihex-firmware.c', 'fu-io-channel.c', 'fu-plugin.c', 'fu-quirks.c', 'fu-smbios.c', 'fu-srec-firmware.c', 'fu-udev-device.c', 'fu-usb-device.c', ] fwupdplugin_headers = [ 'fu-archive.h', 'fu-chunk.h', 'fu-common.h', 'fu-common-cab.h', 'fu-common-guid.h', 'fu-common-version.h', 'fu-device.h', 'fu-device-metadata.h', 'fu-device-locker.h', 'fu-dfu-firmware.h', 'fu-firmware.h', 'fu-firmware-common.h', 'fu-firmware-image.h', 'fu-hwids.h', 'fu-ihex-firmware.h', 'fu-io-channel.h', 'fu-plugin.h', 'fu-quirks.h', 'fu-smbios.h', 'fu-srec-firmware.h', 'fu-udev-device.h', 'fu-usb-device.h', ] install_headers( 'fwupdplugin.h', subdir : 'fwupd-1', ) install_headers([fwupdplugin_headers, 'fu-plugin-vfuncs.h'], subdir : 'fwupd-1/libfwupdplugin', ) fu_hash = custom_target( 'fu-hash.h', input : fwupdplugin_src, output : 'fu-hash.h', command : [python3.path(), join_paths(meson.current_source_dir(), 'fu-hash.py'), '@OUTPUT@', '@INPUT@'] ) fwupdplugin_headers_private = [ fu_hash, 'fu-device-private.h', 'fu-plugin-private.h', 'fu-smbios-private.h', 'fu-usb-device-private.h', ] introspection_deps = [ libxmlb, giounix, gusb, ] if get_option('gudev') fwupdplugin_headers_private += 'fu-udev-device-private.h' introspection_deps += gudev endif library_deps = [ introspection_deps, gmodule, libarchive, libjsonglib, libgcab, valgrind, platform_deps, ] fwupdplugin_mapfile = 'fwupdplugin.map' vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), fwupdplugin_mapfile) fwupdplugin = shared_library( 'fwupdplugin', sources : [ fwupdplugin_src, fwupdplugin_headers ], soversion : libfwupdplugin_lt_current, version : libfwupdplugin_lt_version, include_directories : [ root_incdir, fwupd_incdir, ], dependencies : [ library_deps ], link_with : [ fwupd, ], link_args : vflag, link_depends : fwupdplugin_mapfile, install : true ) fwupdplugin_pkgg = import('pkgconfig') fwupdplugin_pkgg.generate( libraries : fwupdplugin, requires : [ 'gio-2.0', 'gmodule-2.0', 'gobject-2.0', 'gusb', 'fwupd', 'json-glib-1.0', 'libarchive', 'libgcab-1.0', 'libsoup-2.4', 'xmlb', ], subdirs : 'fwupd-1', version : meson.project_version(), name : 'fwupdplugin', filebase : 'fwupdplugin', description : 'library for plugins to use to interact with fwupd daemon', ) if get_option('introspection') gir_dep = declare_dependency(sources: fwupd_gir) fwupdplugin_gir = gnome.generate_gir(fwupd, sources : [ fwupdplugin_src, fwupdplugin_headers, fwupdplugin_headers_private, ], nsversion : '1.0', namespace : 'FwupdPlugin', symbol_prefix : 'fu', identifier_prefix : 'Fu', export_packages : 'fu', include_directories : [ fwupd_incdir, ], dependencies : [ gir_dep, introspection_deps ], link_with : [ fwupdplugin, ], includes : [ 'Gio-2.0', 'GObject-2.0', 'GUsb-1.0', fwupd_gir[0], ], install : true ) gnome.generate_vapi('fwupdplugin', sources : fwupd_gir[0], packages : ['gio-2.0'], 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. fwupdplugin_mapfile_target = custom_target('fwupdplugin_mapfile', input: fwupdplugin_gir[0], output: 'fwupdplugin.map', command: [ join_paths(meson.source_root(), 'contrib', 'generate-version-script.py'), 'LIBFWUPDPLUGIN', '@INPUT@', '@OUTPUT@', ], ) test('fwupdplugin-exported-api', diffcmd, args : [ '-urNp', join_paths(meson.current_source_dir(), 'fwupdplugin.map'), fwupdplugin_mapfile_target, ], ) endif if get_option('tests') test_deps = [ colorhug_test_firmware, builder_test_firmware, hwid_test_firmware, multiple_rels_test_firmware, noreqs_test_firmware, fu_hash, ] if get_option('pkcs7') test_deps += colorhug_pkcs7_signature endif 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( 'fwupdplugin-self-test', test_deps, sources : [ fwupdplugin_src, 'fu-self-test.c' ], include_directories : [ root_incdir, fwupd_incdir, ], dependencies : [ library_deps ], link_with : [ fwupd, fwupdplugin ], c_args : [ '-DTESTDATADIR_SRC="' + testdatadir_src + '"', '-DTESTDATADIR_DST="' + testdatadir_dst + '"', '-DPLUGINBUILDDIR="' + pluginbuilddir + '"', ], ) test('fwupdplugin-self-test', e, is_parallel:false, timeout:180) endif fwupdplugin_incdir = include_directories('.') fwupd-1.3.9/meson.build000066400000000000000000000321361362775233600150360ustar00rootroot00000000000000project('fwupd', 'c', version : run_command('contrib/get-version.py').stdout().strip(), 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].split('-')[0] conf = configuration_data() if varr[2].contains('-') fwupd_dirty_version = varr[2].split('-')[1] fwupd_commit = varr[2].split('-')[2] conf.set('FWUPD_DIRTY_VERSION', fwupd_dirty_version) conf.set_quoted('FWUPD_COMMIT_VERSION', fwupd_commit) endif 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) # 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 libfwupd_lt_current = '2' libfwupd_lt_revision = '0' libfwupd_lt_age = '0' libfwupd_lt_version = '@0@.@1@.@2@'.format(libfwupd_lt_current, libfwupd_lt_age, libfwupd_lt_revision) libfwupdplugin_lt_current = '1' libfwupdplugin_lt_revision = '0' libfwupdplugin_lt_age = '0' libfwupdplugin_lt_version = '@0@.@1@.@2@'.format(libfwupdplugin_lt_current, libfwupdplugin_lt_age, libfwupdplugin_lt_revision) # get supported warning flags warning_flags = [ '-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') if not meson.is_cross_build() add_project_arguments('-fstack-protector-strong', language : 'c') endif # 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') # sanity check if get_option('build') == 'all' build_standalone = true build_daemon = true elif get_option('build') == 'standalone' build_standalone = true build_daemon = false elif get_option('build') == 'library' build_standalone = false build_daemon = false endif prefix = get_option('prefix') bindir = join_paths(prefix, get_option('bindir')) libdir = join_paths(prefix, get_option('libdir')) libexecdir = join_paths(prefix, get_option('libexecdir')) #this ends up in compiled code, ignore prefix if host_machine.system() == 'windows' sysconfdir = get_option('sysconfdir') localstatedir = get_option('localstatedir') datadir = get_option('datadir') else datadir = join_paths(prefix, get_option('datadir')) sysconfdir = join_paths(prefix, get_option('sysconfdir')) localstatedir = join_paths(prefix, get_option('localstatedir')) endif mandir = join_paths(prefix, get_option('mandir')) localedir = join_paths(prefix, get_option('localedir')) diffcmd = find_program('diff') gio = dependency('gio-2.0', version : '>= 2.45.8') giounix = dependency('gio-unix-2.0', version : '>= 2.45.8', required: false) if giounix.found() conf.set('HAVE_GIO_UNIX', '1') endif if gio.version().version_compare ('>= 2.55.0') conf.set('HAVE_GIO_2_55_0', '1') endif if build_standalone gmodule = dependency('gmodule-2.0') if get_option('gudev') gudev = dependency('gudev-1.0', version : '>= 232') conf.set('HAVE_GUDEV', '1') else gudev = dependency('', required : false) endif libxmlb = dependency('xmlb', version : '>= 0.1.13', fallback : ['libxmlb', 'libxmlb_dep']) gusb = dependency('gusb', version : '>= 0.2.9', fallback : ['gusb', 'gusb_dep']) sqlite = dependency('sqlite3') libarchive = dependency('libarchive') endif 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 build_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', fallback : ['gcab', 'gcab_dep']) 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') platform_deps = [] if get_option('default_library') != 'static' if host_machine.system() == 'windows' platform_deps += cc.find_library('shlwapi') endif endif if valgrind.found() conf.set('HAVE_VALGRIND', '1') endif if build_standalone and get_option('plugin_redfish') efivar = dependency('efivar') endif if build_standalone and get_option('plugin_altos') libelf = dependency('libelf') endif if cc.has_header('sys/utsname.h') conf.set('HAVE_UTSNAME_H', '1') endif if cc.has_header('sys/ioctl.h') conf.set('HAVE_IOCTL_H', '1') endif if cc.has_header('sys/errno.h') conf.set('HAVE_ERRNO_H', '1') endif if cc.has_header('poll.h') conf.set('HAVE_POLL_H', '1') endif if cc.has_header('fnmatch.h') conf.set('HAVE_FNMATCH_H', '1') endif if cc.has_function('getuid') conf.set('HAVE_GETUID', '1') endif if cc.has_function('realpath') conf.set('HAVE_REALPATH', '1') endif if cc.has_header_symbol('locale.h', 'LC_MESSAGES') conf.set('HAVE_LC_MESSAGES', '1') endif if cc.has_function('pwrite', args : '-D_XOPEN_SOURCE') conf.set('HAVE_PWRITE', '1') endif if build_standalone and get_option('plugin_tpm') tpm2tss = dependency('tss2-esys', version : '>= 2.0') endif if build_standalone and 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') tpm2tss = dependency('tss2-esys', version : '>= 2.0') genpeimg = find_program ('genpeimg', required: false) efi_app_location = join_paths(libexecdir, 'fwupd', 'efi') conf.set_quoted ('EFI_APP_LOCATION', efi_app_location) 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 build_standalone and 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 build_standalone and 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 build_standalone and get_option('plugin_nvme') if not cc.has_header('linux/nvme_ioctl.h') error('NVMe support requires kernel >= 4.4') endif endif if build_standalone and get_option('plugin_synaptics') conf.set('HAVE_SYNAPTICS', '1') endif if build_standalone and get_option('plugin_thunderbolt') umockdev = dependency('umockdev-1.0', required: false) conf.set('HAVE_THUNDERBOLT', '1') endif if build_standalone and get_option('plugin_flashrom') libflashrom = dependency('flashrom', fallback : ['flashrom', 'flashrom_dep']) endif if build_standalone and get_option('systemd') systemd = dependency('systemd', version : '>= 211') conf.set('HAVE_SYSTEMD' , '1') conf.set('HAVE_LOGIND' , '1') systemdunitdir = get_option('systemdunitdir') if systemdunitdir == '' and get_option('systemd') systemdunitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir') endif systemdsystempresetdir = systemd.get_pkgconfig_variable('systemdsystempresetdir') endif if build_standalone and get_option('elogind') elogind = dependency('libelogind', version : '>= 211') conf.set('HAVE_LOGIND' , '1') endif if build_standalone and get_option('consolekit') conf.set('HAVE_CONSOLEKIT' , '1') endif gnome = import('gnome') i18n = import('i18n') conf.set_quoted('FWUPD_BINDIR', bindir) conf.set_quoted('FWUPD_LIBEXECDIR', libexecdir) conf.set_quoted('FWUPD_DATADIR', datadir) conf.set_quoted('FWUPD_LOCALSTATEDIR', localstatedir) conf.set_quoted('FWUPD_SYSCONFDIR', sysconfdir) conf.set_quoted('FWUPD_LOCALEDIR', localedir) if build_standalone if host_machine.system() == 'windows' plugin_dir = 'fwupd-plugins-3' else plugin_dir = join_paths(libdir, 'fwupd-plugins-3') endif conf.set_quoted('FWUPD_PLUGINDIR', plugin_dir) endif conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) conf.set_quoted('PACKAGE_NAME', meson.project_name()) conf.set_quoted('VERSION', meson.project_version()) motd_file = '85-fwupd' motd_dir = 'motd.d' conf.set_quoted('MOTD_FILE', motd_file) conf.set_quoted('MOTD_DIR', motd_dir) configure_file( output : 'config.h', configuration : conf ) if build_standalone 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 endif root_incdir = include_directories('.') if get_option('gtkdoc') gtkdocscan = find_program('gtkdoc-scan', required : true) subdir('docs') endif subdir('libfwupd') if build_daemon subdir('policy') endif if build_standalone subdir('data') subdir('po') subdir('libfwupdplugin') subdir('src') subdir('plugins') subdir('contrib') endif if get_option('systemd') and build_daemon meson.add_install_script('meson_post_install.sh', systemdunitdir, localstatedir) endif makensis = find_program('makensis', required : false) if makensis.found() run_target( 'makensis', command: [ makensis, join_paths(meson.source_root(), 'contrib', 'setup-win32.nsi'), ]) endif fwupd-1.3.9/meson_options.txt000066400000000000000000000066061362775233600163340ustar00rootroot00000000000000option('build', type : 'combo', choices : ['all', 'standalone', 'library'], value : 'all', description : 'build type') 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 : false, 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('gudev', type : 'boolean', value : true, description : 'enable GUdev support') 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_emmc', type : 'boolean', value : true, description : 'enable eMMC support') 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_tpm', type : 'boolean', value : true, description : 'enable TPM 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('plugin_coreboot', type : 'boolean', value : true, description : 'enable coreboot 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-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 name of OS directory in ESP') fwupd-1.3.9/meson_post_install.sh000077500000000000000000000006311362775233600171420ustar00rootroot00000000000000#!/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.3.9/plugins/000077500000000000000000000000001362775233600143505ustar00rootroot00000000000000fwupd-1.3.9/plugins/README.md000066400000000000000000000022071362775233600156300ustar00rootroot00000000000000Adding 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). You can use the [fwupd developer documentation](https://fwupd.github.io) to assist with APIs available to write the plugin. 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/fwupd/fwupd/blob/master/src/fu-device-metadata.h All interactions between plugins should have the interface defined in that file. fwupd-1.3.9/plugins/altos/000077500000000000000000000000001362775233600154725ustar00rootroot00000000000000fwupd-1.3.9/plugins/altos/README.md000066400000000000000000000036671362775233600167650ustar00rootroot00000000000000Altos 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x1D50` ### 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.3.9/plugins/altos/altos.quirk000066400000000000000000000003671362775233600176770ustar00rootroot00000000000000# ChaosKey [DeviceInstanceId=USB\VID_1D50&PID_60C6] Plugin = altos Flags = none Name = Altos ChaosKey CounterpartGuid = USB\VID_FFFE&PID_000A [DeviceInstanceId=USB\VID_FFFE&PID_000A] Plugin = altos Flags = is-bootloader Name = Altos [bootloader] fwupd-1.3.9/plugins/altos/data/000077500000000000000000000000001362775233600164035ustar00rootroot00000000000000fwupd-1.3.9/plugins/altos/data/lsusb-bootloader.txt000066400000000000000000000056651362775233600224400ustar00rootroot00000000000000Bus 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.3.9/plugins/altos/data/lsusb-runtime.txt000066400000000000000000000037001362775233600217550ustar00rootroot00000000000000Bus 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.3.9/plugins/altos/fu-altos-device.c000066400000000000000000000373651362775233600206430ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-io-channel.h" #include "fu-altos-device.h" #include "fu-altos-firmware.h" struct _FuAltosDevice { FuUsbDevice parent_instance; gchar *tty; guint64 addr_base; guint64 addr_bound; struct termios tty_termios; FuIOChannel *io_channel; }; G_DEFINE_TYPE (FuAltosDevice, fu_altos_device, FU_TYPE_USB_DEVICE) static void fu_altos_device_to_string (FuDevice *device, guint idt, GString *str) { FuAltosDevice *self = FU_ALTOS_DEVICE (device); fu_common_string_append_kv (str, idt, "TTY", self->tty); if (self->addr_base != 0x0) fu_common_string_append_kx (str, idt, "AddrBase", self->addr_base); if (self->addr_bound != 0x0) fu_common_string_append_kx (str, idt, "AddrBound", self->addr_bound); } static void fu_altos_device_finalize (GObject *object) { FuAltosDevice *self = FU_ALTOS_DEVICE (object); g_free (self->tty); G_OBJECT_CLASS (fu_altos_device_parent_class)->finalize (object); } 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); if (g_getenv ("FWUPD_ALTOS_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "write", (const guint8 *) data, (gsize) data_len); 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; if (g_getenv ("FWUPD_ALTOS_VERBOSE") != NULL) fu_common_dump_bytes (G_LOG_DOMAIN, "read", buf); 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 FuFirmware * fu_altos_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_altos_firmware_new (); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; return g_steal_pointer (&firmware); } static gboolean fu_altos_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuAltosDevice *self = FU_ALTOS_DEVICE (device); const gchar *data; const gsize data_len; guint flash_len; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuFirmwareImage) img = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GString) buf = g_string_new (NULL); /* check kind */ if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_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 */ img = fu_firmware_get_image_default (firmware, error); if (img == NULL) return FALSE; /* check the start address */ if (fu_firmware_image_get_addr (img) != self->addr_base) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "start address not correct %" G_GUINT64_FORMAT ":" "%" G_GUINT64_FORMAT, fu_firmware_image_get_addr (img), self->addr_base); return FALSE; } /* check firmware will fit */ fw = fu_firmware_image_write (img, error); if (fw == NULL) return FALSE; data = g_bytes_get_data (fw, (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; fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); 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; if (!fu_memcpy_safe (buf_tmp, sizeof(buf_tmp), 0, /* dst */ (const guint8 *) data, data_len, i, /* src */ chunk_len, error)) return FALSE; } /* 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 FuFirmware * 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(GBytes) fw = NULL; g_autoptr(GString) buf = g_string_new (NULL); /* check kind */ if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_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 */ fw = g_bytes_new (buf->str, buf->len); return fu_firmware_new_from_bytes (fw); } 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) { g_prefix_error (error, "failed to get version information: "); 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)); const gchar *version_prefix = "ChaosKey-hw-1.0-sw-"; guint8 version_idx; g_autofree gchar *version = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* bootloader uses tty */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return fu_altos_device_probe_bootloader (self, error); /* 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; } static void fu_altos_device_init (FuAltosDevice *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_vendor (FU_DEVICE (self), "altusmetrum.org"); fu_device_set_summary (FU_DEVICE (self), "A USB hardware random number generator"); fu_device_set_protocol (FU_DEVICE (self), "org.altusmetrum.altos"); /* requires manual step */ if (!fu_device_has_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); } 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->to_string = fu_altos_device_to_string; klass_device->probe = fu_altos_device_probe; klass_device->prepare_firmware = fu_altos_device_prepare_firmware; 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; } fwupd-1.3.9/plugins/altos/fu-altos-device.h000066400000000000000000000004471362775233600206370ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_ALTOS_DEVICE (fu_altos_device_get_type ()) G_DECLARE_FINAL_TYPE (FuAltosDevice, fu_altos_device, FU, ALTOS_DEVICE, FuUsbDevice) fwupd-1.3.9/plugins/altos/fu-altos-firmware.c000066400000000000000000000055471362775233600212150ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-altos-firmware.h" struct _FuAltosFirmware { FuFirmware parent_instance; }; G_DEFINE_TYPE (FuAltosFirmware, fu_altos_firmware, FU_TYPE_FIRMWARE) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(Elf, elf_end); #pragma clang diagnostic pop static gboolean fu_altos_firmware_parse (FuFirmware *firmware, GBytes *blob, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, 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) { g_autoptr(FuFirmwareImage) img = NULL; g_autoptr(GBytes) bytes = NULL; bytes = g_bytes_new (data->d_buf, data->d_size); img = fu_firmware_image_new (bytes); fu_firmware_image_set_addr (img, shdr.sh_addr); fu_firmware_add_image (firmware, img); } 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_class_init (FuAltosFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->parse = fu_altos_firmware_parse; } static void fu_altos_firmware_init (FuAltosFirmware *self) { } FuFirmware * fu_altos_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_ALTOS_FIRMWARE, NULL)); } fwupd-1.3.9/plugins/altos/fu-altos-firmware.h000066400000000000000000000005401362775233600212060ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_ALTOS_FIRMWARE (fu_altos_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuAltosFirmware, fu_altos_firmware, FU, ALTOS_FIRMWARE, FuFirmware) FuFirmware *fu_altos_firmware_new (void); fwupd-1.3.9/plugins/altos/fu-plugin-altos.c000066400000000000000000000007321362775233600206660ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-altos-device.h" #include "fu-altos-firmware.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_ALTOS_DEVICE); fu_plugin_add_firmware_gtype (plugin, "altos", FU_TYPE_ALTOS_FIRMWARE); } fwupd-1.3.9/plugins/altos/meson.build000066400000000000000000000010221362775233600176270ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ libelf, plugin_deps, ], ) fwupd-1.3.9/plugins/amt/000077500000000000000000000000001362775233600151315ustar00rootroot00000000000000fwupd-1.3.9/plugins/amt/README.md000066400000000000000000000015141362775233600164110ustar00rootroot00000000000000Intel 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. Vendor ID Security ------------------ The device is not upgradable and thus requires no vendor ID set. fwupd-1.3.9/plugins/amt/fu-plugin-amt.c000066400000000000000000000347061362775233600177740ustar00rootroot00000000000000/* * 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" #include "fu-hash.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.3.9/plugins/amt/meson.build000066400000000000000000000005631362775233600172770ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginAmt"'] shared_module('fu_plugin_amt', fu_hash, sources : [ 'fu-plugin-amt.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/ata/000077500000000000000000000000001362775233600151155ustar00rootroot00000000000000fwupd-1.3.9/plugins/ata/README.md000066400000000000000000000027161362775233600164020ustar00rootroot00000000000000ATA === 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. Vendor ID Security ------------------ No vendor ID is set as there is no vendor field in the IDENTIFY response. 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.3.9/plugins/ata/ata.quirk000066400000000000000000000001231362775233600167330ustar00rootroot00000000000000# match all devices with this udev subsystem [DeviceInstanceId=BLOCK] Plugin = ata fwupd-1.3.9/plugins/ata/fu-ata-device.c000066400000000000000000000475771362775233600177170ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #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; guint16 transfer_blocks; guint8 transfer_mode; }; G_DEFINE_TYPE (FuAtaDevice, fu_ata_device, FU_TYPE_UDEV_DEVICE) 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, guint idt, GString *str) { FuAtaDevice *self = FU_ATA_DEVICE (device); fu_common_string_append_kx (str, idt, "TransferMode", self->transfer_mode); fu_common_string_append_kx (str, idt, "TransferBlocks", self->transfer_blocks); fu_common_string_append_ku (str, idt, "PciDepth", self->pci_depth); fu_common_string_append_ku (str, idt, "UsbDepth", 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_probe (FuUdevDevice *device, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE (device); GUdevDevice *udev_device = fu_udev_device_get_dev (device); /* check is valid */ if (g_strcmp0 (g_udev_device_get_devtype (udev_device), "disk") != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct devtype=%s, expected disk", g_udev_device_get_devtype (udev_device)); return FALSE; } if (!g_udev_device_get_property_as_boolean (udev_device, "ID_ATA_SATA") || !g_udev_device_get_property_as_boolean (udev_device, "ID_ATA_DOWNLOAD_MICROCODE")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "has no ID_ATA_DOWNLOAD_MICROCODE"); return FALSE; } /* 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); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); } 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; if (g_getenv ("FWUPD_ATA_VERBOSE") != NULL) { 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 (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self), SG_IO, (guint8 *) &io_hdr, NULL, error)) 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); if (g_getenv ("FWUPD_ATA_VERBOSE") != NULL) 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 (g_getenv ("FWUPD_ATA_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "IDENTIFY", id, sizeof(id)); 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_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, FuFirmware *firmware, 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(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* 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"); fu_device_set_protocol (FU_DEVICE (self), "org.t13.ata"); fu_udev_device_set_flags (FU_UDEV_DEVICE (self), FU_UDEV_DEVICE_FLAG_OPEN_READ); } 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->setup = fu_ata_device_setup; klass_device->activate = fu_ata_device_activate; klass_device->write_firmware = fu_ata_device_write_firmware; klass_udev_device->probe = fu_ata_device_probe; } 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.3.9/plugins/ata/fu-ata-device.h000066400000000000000000000010331362775233600176750ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #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_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); fwupd-1.3.9/plugins/ata/fu-plugin-ata.c000066400000000000000000000006321362775233600177330ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-ata-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_udev_subsystem (plugin, "block"); fu_plugin_set_device_gtype (plugin, FU_TYPE_ATA_DEVICE); } fwupd-1.3.9/plugins/ata/fu-self-test.c000066400000000000000000000024451362775233600176040ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ata-device.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 = g_build_filename (TESTDATADIR, "StarDrive-SBFM61.2.bin", NULL); 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.3.9/plugins/ata/meson.build000066400000000000000000000020531362775233600172570ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with : [ fwupd, fwupdplugin, ], 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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) test('ata-self-test', e) endif fwupd-1.3.9/plugins/ata/tests/000077500000000000000000000000001362775233600162575ustar00rootroot00000000000000fwupd-1.3.9/plugins/ata/tests/StarDrive-SBFM61.2.bin000066400000000000000000000010001362775233600216770ustar00rootroot00000000000000@?7?4AA570A8916800745690BSMF162.ASATS DS @/@??xxxxLDkt}cAitcA@ D@@@)@9fwupd-1.3.9/plugins/colorhug/000077500000000000000000000000001362775233600161725ustar00rootroot00000000000000fwupd-1.3.9/plugins/colorhug/README.md000066400000000000000000000015451362775233600174560ustar00rootroot00000000000000ColorHug 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x273F` fwupd-1.3.9/plugins/colorhug/colorhug.quirk000066400000000000000000000030231362775233600210670ustar00rootroot00000000000000# ColorHug1 [DeviceInstanceId=USB\VID_273F&PID_1000] Plugin = colorhug Flags = is-bootloader,self-recovery 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 = self-recovery 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 = self-recovery 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,self-recovery 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,self-recovery 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,self-recovery Guid = 84f40464-9272-4ef7-9399-cd95f12da696 CounterpartGuid = USB\VID_273F&PID_1007 InstallDuration = 5 fwupd-1.3.9/plugins/colorhug/fu-colorhug-common.c000066400000000000000000000057431362775233600220670ustar00rootroot00000000000000/* * 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.3.9/plugins/colorhug/fu-colorhug-common.h000066400000000000000000000023201362775233600220600ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include 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); fwupd-1.3.9/plugins/colorhug/fu-colorhug-device.c000066400000000000000000000313371362775233600220340ustar00rootroot00000000000000/* * 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) { if (!fu_memcpy_safe (buf, sizeof(buf), 0x1, /* dst */ ibuf, ibufsz, 0x0, /* src */ ibufsz, error)) return FALSE; } /* 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) { if (!fu_memcpy_safe (obuf, obufsz, 0x0, /* dst */ buf, sizeof(buf), 0x2, /* src */ obufsz, error)) return FALSE; } 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; } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); 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; } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static 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_reload (FuDevice *device, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE (device); return fu_colorhug_device_set_flash_success (self, TRUE, error); } 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, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE (device); g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* 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); if (!fu_memcpy_safe (buf, sizeof(buf), 0x4, /* dst */ chk->data, chk->data_sz, 0x0, /* src */ chk->data_sz, error)) return FALSE; 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_protocol (FU_DEVICE (self), "com.hughski.colorhug"); 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->reload = fu_colorhug_device_reload; klass_device->setup = fu_colorhug_device_setup; klass_usb_device->open = fu_colorhug_device_open; klass_usb_device->probe = fu_colorhug_device_probe; } fwupd-1.3.9/plugins/colorhug/fu-colorhug-device.h000066400000000000000000000004661362775233600220400ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_COLORHUG_DEVICE (fu_colorhug_device_get_type ()) G_DECLARE_FINAL_TYPE (FuColorhugDevice, fu_colorhug_device, FU, COLORHUG_DEVICE, FuUsbDevice) fwupd-1.3.9/plugins/colorhug/fu-plugin-colorhug.c000066400000000000000000000005701362775233600220660ustar00rootroot00000000000000/* * Copyright (C) 2015-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-colorhug-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_COLORHUG_DEVICE); } fwupd-1.3.9/plugins/colorhug/meson.build000066400000000000000000000010351362775233600203330ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/coreboot/000077500000000000000000000000001362775233600161645ustar00rootroot00000000000000fwupd-1.3.9/plugins/coreboot/README.md000066400000000000000000000032011362775233600174370ustar00rootroot00000000000000coreboot ======== Introduction ------------ Until now only the version detection has been implemented. System identification --------------------- coreboot can be detected the following ways: 1. by parsing SMBIOS type 0 vendor string. On coreboot enabled platforms it's always `coreboot`. 2. by parsing ACPI tables. An ACPI device with the _HID `BOOT0000` exists. (This HID has been reserved for coreboot enabled platforms) 3. by parsing the devicetree. A node under *firmware/coreboot* with the compatible id `coreboot` exists. coreboot version string ----------------------- The coreboot version string can have an optional prefix (see below). After the optional prefix the *major*, *minor* string follows and finally the *build string*, containing the exact commit and repository state, follows. For example: > 4.10-989-gc8a4e4b9c5-dirty **Exception on Lenovo devices:** The thinkpad_acpi kernel module requires a specific pattern in the DMI version string. To satisfy those requirements coreboot adds the CBETxxxx prefix to the DMI version string on all Lenovo devices. For example: > CBET4000 4.10-989-gc8a4e4b9c5-dirty The coreboot DMI version string always starts with `CBET`. GUID Generation --------------- These device uses hardware ID values which are derived from SMBIOS. The following HWIDs are added on coreboot enabled platforms: * HardwareID-3 * HardwareID-4 * HardwareID-5 * HardwareID-6 * HardwareID-10 They do match the values provided by `fwupdtool hwids` or the `ComputerHardwareIds.exe` Windows utility. Vendor ID Security ------------------ The vendor ID is set from the BIOS vendor, in this instance `DMI:coreboot` fwupd-1.3.9/plugins/coreboot/coreboot.quirk000066400000000000000000000001071362775233600210530ustar00rootroot00000000000000[SmbiosManufacturer=LENOVO] CorebootVersionQuirks = lenovo-cbet-prefix fwupd-1.3.9/plugins/coreboot/fu-coreboot-common.c000066400000000000000000000056371362775233600220550ustar00rootroot00000000000000/* * Copyright (C) 2019 9elements Agency GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-plugin-coreboot.h" /** * FU_QUIRKS_COREBOOT_VERSION: * @key: The SMBIOS manufacturer name * @value: One of the following: "lenovo-cbet-prefix" * * "lenovo-cbet-prefix" quirk: * The thinkpad_acpi kernel module requires a specific pattern * in the DMI version string. To satisfy those requirements * coreboot adds the CBETxxxx prefix to the DMI version string * on all Lenovo devices. The prefix isn't present in the * version string found in coreboot tables, or on other * coreboot enabled devices. * * Since: 1.3.5 */ #define FU_QUIRKS_COREBOOT_VERSION "CorebootVersionQuirks" #define FU_QUIRK_CBET_PREFIX "lenovo-cbet-prefix" /* Tries to convert the coreboot version string to a triplet string. * Returns NULL on error. */ gchar * fu_plugin_coreboot_version_string_to_triplet (const gchar *coreboot_version, GError **error) { guint cb_major = 0; guint cb_minor = 0; guint cb_build = 0; gint rc; rc = sscanf (coreboot_version, "%u.%u-%u", &cb_major, &cb_minor, &cb_build); if (rc < 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Failed to parse firmware version"); return NULL; } /* Sanity check */ if (cb_major == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid firmware version"); return NULL; } return g_strdup_printf ("%u.%u.%u", cb_major, cb_minor, cb_build); } /* convert firmware type to user friendly string representation */ gchar * fu_plugin_coreboot_get_name_for_type (FuPlugin *plugin, const gchar *vboot_partition) { GString *display_name; if (vboot_partition != NULL) { display_name = g_string_new (vboot_partition); g_string_prepend (display_name, ", VBOOT partition "); } else { display_name = g_string_new (""); } g_string_prepend (display_name, "coreboot System Firmware"); return g_string_free (display_name, FALSE); } /* Returns the version string with possible quirks applied */ const gchar * fu_plugin_coreboot_get_version_string (FuPlugin *plugin) { const gchar *version; const gchar *manufacturer; const gchar *quirk = NULL; version = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BIOS_VERSION); if (version == NULL) return NULL; manufacturer = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER); if (manufacturer != NULL) { g_autofree gchar *group = NULL; /* any quirks match */ group = g_strdup_printf ("SmbiosManufacturer=%s", manufacturer); quirk = fu_plugin_lookup_quirk_by_id (plugin, group, FU_QUIRKS_COREBOOT_VERSION); } if (quirk == NULL) return version; if (g_strcmp0(quirk, FU_QUIRK_CBET_PREFIX) == 0) { if (strlen (version) > 9 && g_str_has_prefix (version, "CBET")) version += 9; return version; } return version; } fwupd-1.3.9/plugins/coreboot/fu-plugin-coreboot.c000066400000000000000000000074751362775233600220650ustar00rootroot00000000000000/* * Copyright (C) 2019 9elements Agency GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-device-metadata.h" #include "fu-device-private.h" #include "fu-plugin-coreboot.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { const gchar *major; const gchar *minor; const gchar *version; GBytes *bios_table; gboolean updatable = FALSE; /* TODO: Implement update support */ g_autofree gchar *name = NULL; g_autofree gchar *triplet = NULL; g_autoptr(FuDevice) dev = NULL; /* don't include FU_HWIDS_KEY_BIOS_VERSION */ static const gchar *hwids[] = { "HardwareID-3", "HardwareID-4", "HardwareID-5", "HardwareID-6", "HardwareID-10", }; version = fu_plugin_coreboot_get_version_string (plugin); if (version != NULL) triplet = fu_plugin_coreboot_version_string_to_triplet (version, error); if (version == NULL || triplet == NULL) { major = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BIOS_MAJOR_RELEASE); if (major == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Missing BIOS major release"); return FALSE; } minor = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BIOS_MINOR_RELEASE); if (minor == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Missing BIOS minor release"); return FALSE; } triplet = g_strdup_printf ("%s.%s.0", major, minor); } if (triplet == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "No version string found"); return FALSE; } dev = fu_device_new (); fu_device_set_version (dev, triplet, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_summary (dev, "Open Source system boot firmware"); fu_device_set_id (dev, "coreboot"); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon (dev, "computer"); name = fu_plugin_coreboot_get_name_for_type (plugin, NULL); if (name != NULL) { fu_device_set_name (dev, name); } else { 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_instance_id (dev, "main-system-firmware"); fu_device_set_vendor_id (dev, "DMI:coreboot"); for (guint i = 0; i < G_N_ELEMENTS (hwids); i++) { char *str; str = fu_plugin_get_hwid_replace_value (plugin, hwids[i], NULL); if (str != NULL) fu_device_add_instance_id (dev, str); } bios_table = fu_plugin_get_smbios_data (plugin, FU_SMBIOS_STRUCTURE_TYPE_BIOS); if (bios_table != NULL) { guint32 bios_characteristics; gsize len; const guint8 *value = g_bytes_get_data (bios_table, &len); if (len >= 0x9) { gint firmware_size = (value[0x9] + 1) * 64 * 1024; fu_device_set_firmware_size_max (dev, firmware_size); } if (len >= (0xa + sizeof(guint32))) { memcpy (&bios_characteristics, &value[0xa], sizeof (guint32)); /* Read the "BIOS is upgradeable (Flash)" flag */ if (!(bios_characteristics & (1 << 11))) updatable = FALSE; } } if (updatable) fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); /* convert instances to GUID */ fu_device_convert_instance_ids (dev); fu_plugin_device_add (plugin, dev); return TRUE; } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { const gchar *vendor; vendor = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BIOS_VENDOR); if (g_strcmp0 (vendor, "coreboot") != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No coreboot detected on this machine."); return FALSE; } return TRUE; } fwupd-1.3.9/plugins/coreboot/fu-plugin-coreboot.h000066400000000000000000000007321362775233600220570ustar00rootroot00000000000000/* * Copyright (C) 2019 9elements Agency GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-device.h" gchar *fu_plugin_coreboot_version_string_to_triplet (const gchar *coreboot_version, GError **error); gchar *fu_plugin_coreboot_get_name_for_type (FuPlugin *plugin, const gchar *vboot_partition); const gchar *fu_plugin_coreboot_get_version_string (FuPlugin *plugin); fwupd-1.3.9/plugins/coreboot/meson.build000066400000000000000000000010051362775233600203220ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginCoreboot"'] install_data(['coreboot.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_coreboot', fu_hash, sources : [ 'fu-plugin-coreboot.c', 'fu-coreboot-common.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, ], dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/csr/000077500000000000000000000000001362775233600151375ustar00rootroot00000000000000fwupd-1.3.9/plugins/csr/README.md000066400000000000000000000022641362775233600164220ustar00rootroot00000000000000CSR 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x0A12` fwupd-1.3.9/plugins/csr/csr-aiaiai.quirk000066400000000000000000000005211362775233600202140ustar00rootroot00000000000000[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.3.9/plugins/csr/data/000077500000000000000000000000001362775233600160505ustar00rootroot00000000000000fwupd-1.3.9/plugins/csr/data/lsusb.txt000066400000000000000000000036411362775233600177450ustar00rootroot00000000000000Bus 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.3.9/plugins/csr/data/upgrade.tdc.gz000066400000000000000000000163611362775233600206210ustar00rootroot00000000000000*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.3.9/plugins/csr/fu-csr-device.c000066400000000000000000000373761362775233600177570ustar00rootroot00000000000000/* * 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 "fu-ihex-firmware.h" #include "dfu-common.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, guint idt, GString *str) { FuCsrDevice *self = FU_CSR_DEVICE (device); fu_common_string_append_kv (str, idt, "State", dfu_state_to_string (self->dfu_state)); fu_common_string_append_ku (str, idt, "DownloadTimeout", 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, FU_HID_REPORT_SET, /* bRequest */ FU_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, FU_HID_REPORT_GET, /* bRequest */ FU_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, FU_HID_REPORT_SET, /* bRequest */ FU_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, FU_HID_REPORT_GET, /* bRequest */ FU_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 FuFirmware * fu_csr_device_upload (FuDevice *device, GError **error) { FuCsrDevice *self = FU_CSR_DEVICE (device); g_autoptr(GPtrArray) chunks = NULL; g_autoptr(GBytes) fw = 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 */ fw = dfu_utils_bytes_join_array (chunks); return fu_firmware_new_from_bytes (fw); } 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); if (!fu_memcpy_safe (buf, sizeof(buf), FU_CSR_COMMAND_HEADER_SIZE, /* dst */ chunk_data, chunk_sz, 0x0, /* src */ chunk_sz, error)) return FALSE; /* 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, FU_HID_REPORT_SET, /* bRequest */ FU_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 FuFirmware * fu_csr_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_ihex_firmware_new (); /* parse the file */ if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; if (g_getenv ("FWUPD_CSR_VERBOSE") != NULL) { g_autofree gchar *fw_str = NULL; fw_str = fu_firmware_to_string (firmware); g_debug ("%s", fw_str); } /* success */ return g_steal_pointer (&firmware); } static gboolean fu_csr_device_download (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuCsrDevice *self = FU_CSR_DEVICE (device); guint16 idx; g_autoptr(GBytes) blob_empty = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) chunks = NULL; /* get default image */ blob = fu_firmware_get_image_default_bytes (firmware, error); if (blob == NULL) return FALSE; /* 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 *self) { fu_device_set_protocol (FU_DEVICE (self), "com.qualcomm.dfu"); } 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; } fwupd-1.3.9/plugins/csr/fu-csr-device.h000066400000000000000000000004351362775233600177460ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_CSR_DEVICE (fu_csr_device_get_type ()) G_DECLARE_FINAL_TYPE (FuCsrDevice, fu_csr_device, FU, CSR_DEVICE, FuUsbDevice) fwupd-1.3.9/plugins/csr/fu-plugin-csr.c000066400000000000000000000005511362775233600177770ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-csr-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_CSR_DEVICE); } fwupd-1.3.9/plugins/csr/meson.build000066400000000000000000000007741362775233600173110ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, plugindfu_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ fwupdplugin, dfu, ], ) fwupd-1.3.9/plugins/dell-dock/000077500000000000000000000000001362775233600162065ustar00rootroot00000000000000fwupd-1.3.9/plugins/dell-dock/README.md000066400000000000000000000065071362775233600174750ustar00rootroot00000000000000Dell 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x413C` 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.3.9/plugins/dell-dock/dell-dock.quirk000066400000000000000000000066201362775233600211250ustar00rootroot00000000000000# # 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 # # 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,dual-image,usable-during-update 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,dual-image,usable-during-update 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. VendorId = USB:0x413C Icon = dock-usb FirmwareSizeMin = 0x1FFC0 FirmwareSizeMax = 0x20000 Flags = require-ac,dual-image,self-recovery,usable-during-update 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. Flags = self-recovery,usable-during-update 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 = synaptics_mst ParentGuid = USB\VID_413C&PID_B06E&hub&embedded Flags = skip-restart,require-ac,dual-image,usable-during-update 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. VendorId = TBT:0x00D4 ParentGuid = USB\VID_413C&PID_B06E&hub&embedded FirmwareSizeMin=0x40000 FirmwareSizeMax=0x80000 Flags = require-ac,dual-image Icon = thunderbolt InstallDuration = 22 DellDockInstallDurationI2C = 181 DellDockUnlockTarget = 10 DellDockHubVersionLowest = 1.31 DellDockBlobMajorOffset = 0x400a DellDockBlobMinorOffset = 0x4009 fwupd-1.3.9/plugins/dell-dock/fu-dell-dock-common.c000066400000000000000000000041051362775233600221060ustar00rootroot00000000000000/* * 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.3.9/plugins/dell-dock/fu-dell-dock-common.h000066400000000000000000000021211362775233600221070ustar00rootroot00000000000000/* * 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.3.9/plugins/dell-dock/fu-dell-dock-hid.c000066400000000000000000000346741362775233600214000ustar00rootroot00000000000000/* * 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, FU_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); g_usleep (G_USEC_PER_SEC); } } 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, FU_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.3.9/plugins/dell-dock/fu-dell-dock-hid.h000066400000000000000000000046671362775233600214040ustar00rootroot00000000000000/* * 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.3.9/plugins/dell-dock/fu-dell-dock-hub.c000066400000000000000000000140551362775233600214010ustar00rootroot00000000000000/* * 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); fu_device_set_protocol (device, "com.dell.dock"); return TRUE; } static gboolean fu_dell_dock_hub_write_fw (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuDellDockHub *self = FU_DELL_DOCK_HUB (device); gsize fw_size = 0; const guint8 *data; gsize write_size; gsize nwritten = 0; guint32 address = 0; gboolean result = FALSE; g_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (FU_IS_FIRMWARE (firmware), FALSE); /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; data = g_bytes_get_data (fw, &fw_size); write_size = (fw_size / HIDI2C_MAX_WRITE) >= 1 ? HIDI2C_MAX_WRITE : fw_size; 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_status (device, FWUPD_STATUS_DEVICE_RESTART); 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.3.9/plugins/dell-dock/fu-dell-dock-hub.h000066400000000000000000000014021362775233600213760ustar00rootroot00000000000000/* * 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" #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); fwupd-1.3.9/plugins/dell-dock/fu-dell-dock-i2c-ec.c000066400000000000000000000733701362775233600216720ustar00rootroot00000000000000/* * 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_full (self->ec_version, self->ec_minimum_version, FWUPD_VERSION_FORMAT_QUAD) < 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_full (hub_version, "1.42", FWUPD_VERSION_FORMAT_PAIR) >= 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, "An update is pending " "next time the dock is " "unplugged"); } } 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, guint idt, GString *str) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); gchar service_tag[8] = {0x00}; fu_common_string_append_ku (str, idt, "BoardId", self->data->board_id); fu_common_string_append_ku (str, idt, "PowerSupply", self->data->power_supply_wattage); fu_common_string_append_kx (str, idt, "StatusPort0", self->data->port0_dock_status); fu_common_string_append_kx (str, idt, "StatusPort1", self->data->port1_dock_status); memcpy (service_tag, self->data->service_tag, 7); fu_common_string_append_kv (str, idt, "ServiceTag", service_tag); fu_common_string_append_ku (str, idt, "Configuration", self->data->dock_configuration); fu_common_string_append_kx (str, idt, "PackageFirmwareVersion", self->data->dock_firmware_pkg_ver); fu_common_string_append_ku (str, idt, "ModuleSerial", self->data->module_serial); fu_common_string_append_ku (str, idt, "OriginalModuleSerial", self->data->original_module_serial); fu_common_string_append_ku (str, idt, "Type", self->data->dock_type); fu_common_string_append_kx (str, idt, "ModuleType", self->data->module_type); fu_common_string_append_kv (str, idt, "MinimumEc", self->ec_minimum_version); fu_common_string_append_ku (str, idt, "PassiveFlow", 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, FuFirmware *firmware, 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; gsize write_size = 0; gsize nwritten = 0; guint32 address = 0 | 0xff << 24; g_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (FU_IS_FIRMWARE (firmware), FALSE); /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; data = g_bytes_get_data (fw, &fw_size); write_size = (fw_size / HIDI2C_MAX_WRITE) >= 1 ? HIDI2C_MAX_WRITE : fw_size; 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; } } fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); 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); fu_device_set_protocol (FU_DEVICE (self), "com.dell.dock"); } 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.3.9/plugins/dell-dock/fu-dell-dock-i2c-ec.h000066400000000000000000000027021362775233600216660ustar00rootroot00000000000000/* * 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" #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); 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.3.9/plugins/dell-dock/fu-dell-dock-i2c-mst.c000066400000000000000000000633211362775233600221010ustar00rootroot00000000000000/* * 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); g_return_val_if_fail (symbiote != NULL, FALSE); g_return_val_if_fail (data != NULL, FALSE); memcpy (buffer, &address, 4); memcpy (buffer + 4, data, length); /* 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, FuFirmware *firmware, 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_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (FU_IS_FIRMWARE (firmware), FALSE); g_return_val_if_fail (self->symbiote != NULL, FALSE); /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; data = g_bytes_get_data (fw, NULL); 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, 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, fw, order[phase], error)) return FALSE; if (!fu_dell_dock_mst_checksum_bank (self->symbiote, 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_status (device, FWUPD_STATUS_DEVICE_RESTART); 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) { fu_device_set_protocol (FU_DEVICE (self), "com.synaptics.mst"); } 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.3.9/plugins/dell-dock/fu-dell-dock-i2c-mst.h000066400000000000000000000013531362775233600221030ustar00rootroot00000000000000/* * 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" #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); fwupd-1.3.9/plugins/dell-dock/fu-dell-dock-i2c-tbt.c000066400000000000000000000214711362775233600220670ustar00rootroot00000000000000/* * 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, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT (device); guint32 start_offset = 0; gsize image_size = 0; const guint8 *buffer; guint16 target_system = 0; g_autoptr(GTimer) timer = g_timer_new (); g_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (FU_IS_FIRMWARE (firmware), FALSE); /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; buffer = g_bytes_get_data (fw, &image_size); 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_status (device, FWUPD_STATUS_DEVICE_RESTART); 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_full (version, MIN_NVM, FWUPD_VERSION_FORMAT_PAIR) < 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_full (hub_version, self->hub_minimum_version, FWUPD_VERSION_FORMAT_PAIR) < 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); /* this is true only when connected to non-thunderbolt port */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); 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 *self) { fu_device_set_protocol (FU_DEVICE (self), "com.intel.thunderbolt"); } 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.3.9/plugins/dell-dock/fu-dell-dock-i2c-tbt.h000066400000000000000000000014241362775233600220700ustar00rootroot00000000000000/* * 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" #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); fwupd-1.3.9/plugins/dell-dock/fu-dell-dock-status.c000066400000000000000000000110721362775233600221420ustar00rootroot00000000000000/* * 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, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuDellDockStatus *self = FU_DELL_DOCK_STATUS (device); FuDevice *parent; gsize length = 0; guint32 status_version = 0; const guint8 *data; g_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (FU_IS_FIRMWARE (firmware), FALSE); /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; data = g_bytes_get_data (fw, &length); if (!fu_memcpy_safe ((guint8 *) &status_version, sizeof(status_version), 0x0, /* dst */ data, length, self->blob_version_offset, /* src */ sizeof(status_version), error)) return FALSE; 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, fw, error)) return FALSE; /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); 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) { fu_device_set_protocol (FU_DEVICE (self), "com.dell.dock"); } 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.3.9/plugins/dell-dock/fu-dell-dock-status.h000066400000000000000000000013771362775233600221560ustar00rootroot00000000000000/* * 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" #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); fwupd-1.3.9/plugins/dell-dock/fu-plugin-dell-dock.c000066400000000000000000000127631362775233600221250ustar00rootroot00000000000000/* * 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-hash.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 */ 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, "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; } /* 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); } fwupd-1.3.9/plugins/dell-dock/meson.build000066400000000000000000000012621362775233600203510ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, gudev, ], ) fwupd-1.3.9/plugins/dell-esrt/000077500000000000000000000000001362775233600162435ustar00rootroot00000000000000fwupd-1.3.9/plugins/dell-esrt/README.md000066400000000000000000000023121362775233600175200ustar00rootroot00000000000000Dell 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`. Vendor ID Security ------------------ The vendor ID is hardcoded to `PCI:0x1028`. 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.3.9/plugins/dell-esrt/dell-esrt.conf000066400000000000000000000003661362775233600210120ustar00rootroot00000000000000[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.3.9/plugins/dell-esrt/fu-plugin-dell-esrt.c000066400000000000000000000113531362775233600222110ustar00rootroot00000000000000/* * 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 "fu-plugin-vfuncs.h" #include "fu-hash.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); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_BETTER_THAN, "uefi"); } 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"); fu_device_set_name (dev, "Dell UEFI updates"); fu_device_set_summary (dev, "Enable UEFI Update Functionality"); fu_device_set_vendor_id (dev, "PCI:0x1028"); fu_device_add_instance_id (dev, "main-system-firmware"); 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.3.9/plugins/dell-esrt/meson.build000066400000000000000000000014101362775233600204010ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, ], link_with : [ fwupd, fwupdplugin, ], 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.3.9/plugins/dell-esrt/metadata.xml000066400000000000000000000016461362775233600205540ustar00rootroot00000000000000 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.3.9/plugins/dell/000077500000000000000000000000001362775233600152705ustar00rootroot00000000000000fwupd-1.3.9/plugins/dell/README.md000066400000000000000000000147041362775233600165550ustar00rootroot00000000000000Dell 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. TPM GUIDs are also built using the TSS properties `TPM2_PT_FAMILY_INDICATOR`, `TPM2_PT_MANUFACTURER`, and `TPM2_PT_VENDOR_STRING_*` These are built hierarchically with more parts for each GUID: * `DELL-TPM-$FAMILY-$MANUFACTURER-$VENDOR_STRING_1` * `DELL-TPM-$FAMILY-$MANUFACTURER-$VENDOR_STRING_1$VENDOR_STRING_2` * `DELL-TPM-$FAMILY-$MANUFACTURER-$VENDOR_STRING_1$VENDOR_STRING_2$VENDOR_STRING_3` * `DELL-TPM-$FAMILY-$MANUFACTURER-$VENDOR_STRING_1$VENDOR_STRING_2$VENDOR_STRING_3$VENDOR_STRING_4` If there are non-ASCII values in any vendor string or any vendor is missing that octet will be skipped. Example resultant GUIDs from a real system containing a TPM from Nuvoton: ``` Guid: 7d65b10b-bb24-552d-ade5-590b3b278188 <- DELL-TPM-2.0-NTC-NPCT Guid: 6f5ddd3a-8339-5b2a-b9a6-cf3b92f6c86d <- DELL-TPM-2.0-NTC-NPCT75x Guid: fe462d4a-e48f-5069-9172-47330fc5e838 <- DELL-TPM-2.0-NTC-NPCT75xrls ``` Vendor ID Security ------------------ The vendor ID is hardcoded to `TPM:DELL`. 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.3.9/plugins/dell/dell.quirk000066400000000000000000000017421362775233600172710ustar00rootroot00000000000000# 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 = synaptics_mst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 # Dell TB16 dock [Guid=MST-tb16-vmm3320-274] Plugin = synaptics_mst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 [Guid=MST-tb16-vmm3330-274] Plugin = synaptics_mst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 #Dell TB18 dock [Guid=MST-tb18-vmm3320-274] Plugin = synaptics_mst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 [Guid=MST-tb18-vmm3330-274] Plugin = synaptics_mst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 [SmbiosManufacturer=Dell Inc.] UefiVersionFormat = dell-bios [SmbiosManufacturer=Alienware] UefiVersionFormat = dell-bios fwupd-1.3.9/plugins/dell/fu-dell-smi.c000066400000000000000000000135461362775233600175630ustar00rootroot00000000000000/* * 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.3.9/plugins/dell/fu-dell-smi.h000066400000000000000000000055571362775233600175730ustar00rootroot00000000000000/* * 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.3.9/plugins/dell/fu-plugin-dell.c000066400000000000000000000716741362775233600202770ustar00rootroot00000000000000/* * 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 #include "fwupd-common.h" #include "fu-plugin-dell.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.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" /** * 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 */}; static guint16 fu_dell_get_system_id (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); const gchar *system_id_str = NULL; guint16 system_id = 0; gchar *endptr = NULL; /* don't care for test suite */ if (data->smi_obj->fake_smbios) return 0; 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_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_vendor_id (dev, "PCI:0x1028"); 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; } 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; } static void Esys_Finalize_autoptr_cleanup (ESYS_CONTEXT *esys_context) { Esys_Finalize (&esys_context); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (ESYS_CONTEXT, Esys_Finalize_autoptr_cleanup) static gchar * fu_plugin_dell_get_tpm_capability (ESYS_CONTEXT *ctx, guint32 query) { TSS2_RC rc; guint32 val; gchar result[5] = {'\0'}; g_autofree TPMS_CAPABILITY_DATA *capability = NULL; rc = Esys_GetCapability (ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_TPM_PROPERTIES, query, 1, NULL, &capability); if (rc != TSS2_RC_SUCCESS) { g_debug ("capability request failed for query %x", query); return NULL; } if (capability->data.tpmProperties.count == 0) { g_debug ("no properties returned for query %x", query); return NULL; } if (capability->data.tpmProperties.tpmProperty[0].property != query) { g_debug ("wrong query returned (got %x expected %x)", capability->data.tpmProperties.tpmProperty[0].property, query); return NULL; } val = GUINT32_FROM_BE (capability->data.tpmProperties.tpmProperty[0].value); memcpy (result, (gchar *) &val, 4); /* convert non-ASCII into spaces */ for (guint i = 0; i < 4; i++) { if (!g_ascii_isgraph (result[i]) && result[i] != '\0') result[i] = 0x20; } return fu_common_strstrip (result); } static gboolean fu_plugin_dell_add_tpm_model (FuDevice *dev, GError **error) { TSS2_RC rc; const gchar *base = "DELL-TPM"; g_autoptr(ESYS_CONTEXT) ctx = NULL; g_autofree gchar *family = NULL; g_autofree gchar *manufacturer = NULL; g_autofree gchar *vendor1 = NULL; g_autofree gchar *vendor2 = NULL; g_autofree gchar *vendor3 = NULL; g_autofree gchar *vendor4 = NULL; g_autofree gchar *v1 = NULL; g_autofree gchar *v1_v2 = NULL; g_autofree gchar *v1_v2_v3 = NULL; g_autofree gchar *v1_v2_v3_v4 = NULL; rc = Esys_Initialize (&ctx, NULL, NULL); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to initialize TPM library"); return FALSE; } rc = Esys_Startup (ctx, TPM2_SU_CLEAR); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to initialize TPM"); return FALSE; } /* lookup guaranteed details from TPM */ family = fu_plugin_dell_get_tpm_capability (ctx, TPM2_PT_FAMILY_INDICATOR); if (family == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to read TPM family"); return FALSE; } manufacturer = fu_plugin_dell_get_tpm_capability (ctx, TPM2_PT_MANUFACTURER); if (manufacturer == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to read TPM manufacturer"); return FALSE; } vendor1 = fu_plugin_dell_get_tpm_capability (ctx, TPM2_PT_VENDOR_STRING_1); if (vendor1 == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to read TPM vendor string"); return FALSE; } /* these are not guaranteed by spec and may be NULL */ vendor2 = fu_plugin_dell_get_tpm_capability (ctx, TPM2_PT_VENDOR_STRING_2); vendor3 = fu_plugin_dell_get_tpm_capability (ctx, TPM2_PT_VENDOR_STRING_3); vendor4 = fu_plugin_dell_get_tpm_capability (ctx, TPM2_PT_VENDOR_STRING_4); /* add GUIDs to daemon */ v1 = g_strjoin ("-", base, family, manufacturer, vendor1, NULL); v1_v2 = g_strconcat (v1, vendor2, NULL); v1_v2_v3 = g_strconcat (v1_v2, vendor3, NULL); v1_v2_v3_v4 = g_strconcat (v1_v2_v3, vendor4, NULL); fu_device_add_instance_id (dev, v1); fu_device_add_instance_id (dev, v1_v2); fu_device_add_instance_id (dev, v1_v2_v3); fu_device_add_instance_id (dev, v1_v2_v3_v4); 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 *version_str = NULL; struct tpm_status *out = NULL; g_autoptr (FuDevice) dev_alt = NULL; g_autoptr (FuDevice) dev = NULL; g_autoptr(GError) error_tss = NULL; 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_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 */ pretty_tpm_name = g_strdup_printf ("TPM %s", tpm_mode); pretty_tpm_name_alt = g_strdup_printf ("TPM %s", tpm_mode_alt); /* build Standard device nodes */ dev = fu_device_new (); fu_device_set_physical_id (dev, "DEVNAME=/dev/tpm0"); fu_device_add_instance_id (dev, tpm_guid_raw); fu_device_add_instance_id (dev, "system-tpm"); fu_device_set_vendor (dev, "Dell Inc."); fu_device_set_vendor_id (dev, "PCI:0x1028"); 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 { fu_device_set_update_error (dev, "Updating disabled due to TPM ownership"); } /* build GUIDs from TSS strings */ if (!fu_plugin_dell_add_tpm_model (dev, &error_tss)) g_debug ("could not build instances: %s", error_tss->message); if (!fu_device_setup (dev, error)) return FALSE; 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_instance_id (dev_alt, tpm_guid_raw_alt); fu_device_set_vendor (dev, "Dell Inc."); fu_device_set_vendor_id (dev, "PCI:0x1028"); 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 { fu_device_set_update_error (dev_alt, "mode switch disabled due to TPM ownership"); } if (!fu_device_setup (dev_alt, error)) return FALSE; 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); } } } 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); else g_setenv ("TSS2_LOG", "esys+error,tcti+none", FALSE); 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; /* make sure that UEFI plugin is ready to receive devices */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "uefi"); /* our TPM device is upgradable! */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_BETTER_THAN, "tpm"); } 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); } fwupd-1.3.9/plugins/dell/fu-plugin-dell.h000066400000000000000000000015501362775233600202660ustar00rootroot00000000000000/* * 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.3.9/plugins/dell/fu-self-test.c000066400000000000000000000425401362775233600177570ustar00rootroot00000000000000/* * 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" #include "fu-hash.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; const gchar *tpm_server_running = g_getenv ("TPM_SERVER_RUNNING"); g_autofree gchar *pluginfn_uefi = NULL; g_autofree gchar *pluginfn_dell = NULL; 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; pluginfn_uefi = g_build_filename (PLUGINBUILDDIR, "..", "uefi", "libfu_plugin_uefi." G_MODULE_SUFFIX, NULL); pluginfn_dell = g_build_filename (PLUGINBUILDDIR, "libfu_plugin_dell." G_MODULE_SUFFIX, NULL); memset (&tpm_out, 0x0, sizeof(tpm_out)); plugin_uefi = fu_plugin_new (); ret = fu_plugin_open (plugin_uefi, pluginfn_uefi, &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, pluginfn_dell, &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); #ifdef HAVE_GETUID if (tpm_server_running == NULL && (getuid () != 0 || geteuid () != 0)) { g_test_skip ("TPM tests require simulated TPM2.0 running or need root access with physical TPM"); return; } #endif /* 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, "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, "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, "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, "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, "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, "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, "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, "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 */ g_test_expect_message ("FuPluginUefi", G_LOG_LEVEL_WARNING, "missing or invalid embedded capsule header"); ret = fu_plugin_runner_update (plugin_uefi, device_v20, blob_fw, FWUPD_INSTALL_FLAG_FORCE, &error); g_test_assert_expected_messages (); 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_autofree gchar *pluginfn_uefi = NULL; g_autofree gchar *pluginfn_dell = NULL; 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 (); pluginfn_uefi = g_build_filename (PLUGINBUILDDIR, "..", "uefi", "libfu_plugin_uefi." G_MODULE_SUFFIX, NULL); pluginfn_dell = g_build_filename (PLUGINBUILDDIR, "libfu_plugin_dell." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open (plugin_uefi, pluginfn_uefi, &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, pluginfn_dell, &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_UEFI_TEST", "1", 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.3.9/plugins/dell/meson.build000066400000000000000000000023331362775233600174330ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, ], dependencies : [ plugin_deps, efivar, libsmbios_c, tpm2tss, ], ) if get_option('tests') testdatadir = join_paths(meson.current_source_dir(), 'tests') cargs += '-DTESTDATADIR="' + testdatadir + '"' 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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, efivar, sqlite, libsmbios_c, valgrind, tpm2tss, ], link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, ], ) test('dell-self-test', e) endif fwupd-1.3.9/plugins/dell/tests000077700000000000000000000000001362775233600206572../uefi/tests/ustar00rootroot00000000000000fwupd-1.3.9/plugins/dfu/000077500000000000000000000000001362775233600151265ustar00rootroot00000000000000fwupd-1.3.9/plugins/dfu/README.md000066400000000000000000000023641362775233600164120ustar00rootroot00000000000000DFU 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, for example `USB:0x0A12` 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| fwupd-1.3.9/plugins/dfu/contrib/000077500000000000000000000000001362775233600165665ustar00rootroot00000000000000fwupd-1.3.9/plugins/dfu/contrib/parse-avrdude-conf.py000077500000000000000000000113271362775233600226340ustar00rootroot00000000000000#!/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.3.9/plugins/dfu/dfu-common.c000066400000000000000000000074121362775233600173420ustar00rootroot00000000000000/* * 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_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); } fwupd-1.3.9/plugins/dfu/dfu-common.h000066400000000000000000000102551362775233600173460ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include /** * 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; /** * 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; const gchar *dfu_state_to_string (DfuState state); const gchar *dfu_status_to_string (DfuStatus status); const gchar *dfu_version_to_string (DfuVersion version); /* helpers */ GBytes *dfu_utils_bytes_join_array (GPtrArray *chunks); fwupd-1.3.9/plugins/dfu/dfu-device.c000066400000000000000000001447471362775233600173260ustar00rootroot00000000000000/* * 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 * * `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 * * `detach-for-attach`: Requires a DFU_REQUEST_DETACH to attach * * Default value: `none` * * Since: 1.0.1 */ #define FU_QUIRKS_DFU_FLAGS "Flags" /** * 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" #include "config.h" #include #include "dfu-common.h" #include "dfu-device.h" #include "dfu-target-avr.h" #include "dfu-target-private.h" #include "dfu-target-stm.h" #include "fu-device-locker.h" #include "fu-firmware-common.h" #include "fwupd-error.h" static void dfu_device_finalize (GObject *object); typedef struct { DfuDeviceAttributes attributes; DfuState state; DfuStatus status; GPtrArray *targets; gboolean done_upload_or_download; gboolean claimed_interface; gchar *chip_id; 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; G_DEFINE_TYPE_WITH_PRIVATE (DfuDevice, dfu_device, FU_TYPE_USB_DEVICE) #define GET_PRIVATE(o) (dfu_device_get_instance_private (o)) static void dfu_device_set_state (DfuDevice *device, DfuState state); static void dfu_device_to_string (FuDevice *device, guint idt, GString *str) { DfuDevice *self = DFU_DEVICE (device); DfuDevicePrivate *priv = GET_PRIVATE (self); fu_common_string_append_kv (str, idt, "State", dfu_state_to_string (priv->state)); fu_common_string_append_kv (str, idt, "Status", dfu_status_to_string (priv->status)); fu_common_string_append_kb (str, idt, "DoneUploadOrDownload", priv->done_upload_or_download); fu_common_string_append_kb (str, idt, "ClaimedInterface", priv->claimed_interface); if (priv->chip_id != NULL) fu_common_string_append_kv (str, idt, "ChipId", priv->chip_id); fu_common_string_append_kx (str, idt, "Version", priv->version); fu_common_string_append_kx (str, idt, "Force_version", priv->force_version); fu_common_string_append_kx (str, idt, "RuntimePid", priv->runtime_pid); fu_common_string_append_kx (str, idt, "RuntimeVid", priv->runtime_vid); fu_common_string_append_kx (str, idt, "RuntimeRelease", priv->runtime_release); fu_common_string_append_kx (str, idt, "TransferSize", priv->transfer_size); fu_common_string_append_kx (str, idt, "IfaceNumber", priv->iface_number); fu_common_string_append_kx (str, idt, "DnloadTimeout", priv->dnload_timeout); fu_common_string_append_kx (str, idt, "TimeoutMs", priv->timeout_ms); } /** * 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) { /* some devices use the wrong interface */ if (fu_device_has_custom_flag (FU_DEVICE (device), "force-dfu-mode")) { g_debug ("quirking device into DFU mode"); dfu_device_set_state (device, DFU_STATE_DFU_IDLE); return; } /* runtime */ if (g_usb_interface_get_protocol (iface) == 0x01) { dfu_device_set_state (device, DFU_STATE_APP_IDLE); return; } /* DFU */ if (g_usb_interface_get_protocol (iface) == 0x02) { dfu_device_set_state (device, 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 (!fu_device_has_custom_flag (FU_DEVICE (device), "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; } /* set expected protocol */ if (priv->version == DFU_VERSION_DFUSE) { fu_device_set_protocol (FU_DEVICE (device), "com.st.dfuse"); } else { fu_device_set_protocol (FU_DEVICE (device), "org.usb.dfu"); } /* 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 || fu_device_has_custom_flag (FU_DEVICE (device), "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 && fu_device_has_custom_flag (FU_DEVICE (device), "no-dfu-runtime")) { g_debug ("no DFU runtime, so faking device"); dfu_device_set_state (device, 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 (fu_device_has_custom_flag (FU_DEVICE (device), "ignore-upload")) priv->attributes &= ~DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD; return TRUE; } /** * 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_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; } /** * 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; } 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; /* set bootloader status */ if (state == DFU_STATE_APP_IDLE || state == DFU_STATE_APP_DETACH) { fu_device_remove_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } 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; } } static void dfu_device_set_status (DfuDevice *device, DfuStatus status) { DfuDevicePrivate *priv = GET_PRIVATE (device); if (priv->status == status) return; priv->status = 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 they 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 && fu_device_has_custom_flag (FU_DEVICE (device), "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 (fu_device_has_custom_flag (FU_DEVICE (device), "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 (fu_device_has_custom_flag (FU_DEVICE (device), "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 gboolean dfu_device_request_detach (DfuDevice *self, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (self); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); const guint16 timeout_reset_ms = 1000; g_autoptr(GError) error_local = 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, 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 (self, &error_local); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot detach device: %s", error_local->message); return FALSE; } } return TRUE; } static gboolean dfu_device_detach (FuDevice *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)); g_return_val_if_fail (DFU_IS_DEVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* already in DFU mode */ if (!dfu_device_refresh_and_clear (self, error)) return FALSE; if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* 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 (self)); return FALSE; } /* the device has no DFU runtime, so cheat */ if (priv->state == DFU_STATE_APP_IDLE && fu_device_has_custom_flag (FU_DEVICE (self), "no-dfu-runtime")) return TRUE; /* ensure interface is claimed */ if (!dfu_device_ensure_interface (self, error)) return FALSE; /* inform UI there's going to be a detach:attach */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!dfu_device_request_detach (self, error)) 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 (self, error)) return FALSE; } /* success */ priv->force_version = 0x0; fu_device_set_status (device, FWUPD_STATUS_IDLE); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); 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 && fu_device_has_custom_flag (FU_DEVICE (device), "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 && fu_device_has_custom_flag (FU_DEVICE (device), "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 && fu_device_has_custom_flag (FU_DEVICE (self), "no-dfu-runtime")) { dfu_device_set_state (self, 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)) { g_warning ("%04x:%04x is missing download capability", g_usb_device_get_vid (usb_device), g_usb_device_get_pid (usb_device)); } /* hardware rom Jabra literally reboots if you try to retry a failed * write -- there's no way to avoid blocking the daemon like this... */ if (fu_device_has_custom_flag (FU_DEVICE (device), "attach-extra-reset")) g_usleep (10 * G_USEC_PER_SEC); /* 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; } static gboolean dfu_device_attach (FuDevice *device, GError **error) { DfuDevice *self = DFU_DEVICE (device); DfuDevicePrivate *priv = GET_PRIVATE (self); 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_refresh_and_clear (self, error)) return FALSE; if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* inform UI there's going to be a re-attach */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); /* handle weirdness */ if (fu_device_has_custom_flag (device, "detach-for-attach")) { if (!dfu_device_request_detach (self, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_IDLE); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } /* handle m-stack DFU bootloaders */ if (!priv->done_upload_or_download && fu_device_has_custom_flag (FU_DEVICE (self), "attach-upload-download")) { 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 (self, 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 (self, 0, error); if (target == NULL) return FALSE; /* normal DFU mode just needs a bus reset */ if (!dfu_target_attach (target, error)) return FALSE; /* success */ priv->force_version = 0x0; fu_device_set_status (device, FWUPD_STATUS_IDLE); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); 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 (); fu_dfu_firmware_set_vid (FU_DFU_FIRMWARE (firmware), priv->runtime_vid); fu_dfu_firmware_set_pid (FU_DFU_FIRMWARE (firmware), priv->runtime_pid); fu_dfu_firmware_set_release (FU_DFU_FIRMWARE (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; fu_firmware_add_image (FU_FIRMWARE (firmware), FU_FIRMWARE_IMAGE (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; } static gboolean dfu_device_download (DfuDevice *device, DfuFirmware *firmware, DfuTargetTransferFlags flags, GError **error) { DfuDevicePrivate *priv = GET_PRIVATE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); gboolean ret; g_autoptr(GPtrArray) images = NULL; /* 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 (fu_dfu_firmware_get_vid (FU_DFU_FIRMWARE (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 (fu_dfu_firmware_get_pid (FU_DFU_FIRMWARE (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 (fu_dfu_firmware_get_vid (FU_DFU_FIRMWARE (firmware)), priv->runtime_vid, fu_usb_device_get_vid (FU_USB_DEVICE (device)))) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "vendor ID incorrect, expected 0x%04x " "got 0x%04x and 0x%04x\n", fu_dfu_firmware_get_vid (FU_DFU_FIRMWARE (firmware)), priv->runtime_vid, fu_usb_device_get_vid (FU_USB_DEVICE (device))); return FALSE; } } /* check product matches */ if (priv->runtime_pid != 0xffff) { if (!dfu_device_id_compatible (fu_dfu_firmware_get_pid (FU_DFU_FIRMWARE (firmware)), priv->runtime_pid, fu_usb_device_get_pid (FU_USB_DEVICE (device)))) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "product ID incorrect, expected 0x%04x " "got 0x%04x and 0x%04x", fu_dfu_firmware_get_pid (FU_DFU_FIRMWARE (firmware)), priv->runtime_pid, fu_usb_device_get_pid (FU_USB_DEVICE (device))); return FALSE; } } /* download each target */ images = fu_firmware_get_images (FU_FIRMWARE (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++) { 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 */ 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); /* 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; } } static FuFirmware * dfu_device_read_firmware (FuDevice *device, GError **error) { DfuDevice *self = DFU_DEVICE (device); g_autoptr(DfuFirmware) dfu_firmware = NULL; g_autoptr(GBytes) fw = NULL; /* get data from hardware */ g_debug ("uploading from device->host"); if (!dfu_device_refresh_and_clear (self, error)) return NULL; dfu_firmware = dfu_device_upload (self, DFU_TARGET_TRANSFER_FLAG_NONE, error); if (dfu_firmware == NULL) return NULL; /* get the checksum */ fw = dfu_firmware_write_data (dfu_firmware, error); return fu_firmware_new_from_bytes (fw); } static gboolean dfu_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { DfuDevice *self = DFU_DEVICE (device); DfuTargetTransferFlags transfer_flags = DFU_TARGET_TRANSFER_FLAG_VERIFY; g_autoptr(DfuFirmware) dfu_firmware = NULL; g_autoptr(GBytes) blob_fw = NULL; /* open it */ blob_fw = fu_firmware_get_image_default_bytes (firmware, error); if (blob_fw == NULL) return FALSE; if (!dfu_device_refresh_and_clear (self, error)) return FALSE; if (flags & FWUPD_INSTALL_FLAG_FORCE) { transfer_flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID; transfer_flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID; } /* hit hardware */ dfu_firmware = dfu_firmware_new (); if (!dfu_firmware_parse_data (dfu_firmware, blob_fw, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; return dfu_device_download (self, dfu_firmware, transfer_flags, error); } 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_FORCE_VERSION) == 0) { if (value != NULL && strlen (value) == 4) { priv->force_version = fu_firmware_strparse_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); g_free (priv->chip_id); 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_device->to_string = dfu_device_to_string; klass_device->read_firmware = dfu_device_read_firmware; klass_device->write_firmware = dfu_device_write_firmware; klass_device->attach = dfu_device_attach; klass_device->detach = dfu_device_detach; klass_usb_device->open = dfu_device_open; klass_usb_device->close = dfu_device_close; klass_usb_device->probe = dfu_device_probe; 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; fu_device_add_icon (FU_DEVICE (device), "drive-harddisk-usb"); 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); } fwupd-1.3.9/plugins/dfu/dfu-device.h000066400000000000000000000071041362775233600173140ustar00rootroot00000000000000/* * 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" #define DFU_TYPE_DEVICE (dfu_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuDevice, dfu_device, DFU, DEVICE, FuUsbDevice) /** * 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; }; 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); gboolean dfu_device_reset (DfuDevice *device, GError **error); DfuFirmware *dfu_device_upload (DfuDevice *device, 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_abort (DfuDevice *device, GError **error); gboolean dfu_device_clear_status (DfuDevice *device, GError **error); guint8 dfu_device_get_interface (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); void dfu_device_set_transfer_size (DfuDevice *device, guint16 transfer_size); void dfu_device_set_timeout (DfuDevice *device, guint timeout_ms); void dfu_device_error_fixup (DfuDevice *device, GError **error); guint dfu_device_get_download_timeout (DfuDevice *device); gchar *dfu_device_get_attributes_as_string (DfuDevice *device); gboolean dfu_device_ensure_interface (DfuDevice *device, GError **error); fwupd-1.3.9/plugins/dfu/dfu-element.c000066400000000000000000000072421362775233600175040ustar00rootroot00000000000000/* * 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 address; } 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 representation 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->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); } fwupd-1.3.9/plugins/dfu/dfu-element.h000066400000000000000000000013211362775233600175010ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #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); void dfu_element_set_contents (DfuElement *element, GBytes *contents); void dfu_element_set_address (DfuElement *element, guint32 address); gchar *dfu_element_to_string (DfuElement *element); fwupd-1.3.9/plugins/dfu/dfu-firmware.c000066400000000000000000000211041362775233600176600ustar00rootroot00000000000000/* * 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 "fu-firmware.h" #include "dfu-common.h" #include "dfu-firmware.h" #include "dfu-format-dfu.h" #include "dfu-format-raw.h" #include "dfu-image.h" #include "fwupd-error.h" typedef struct { DfuFirmwareFormat format; } DfuFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE (DfuFirmware, dfu_firmware, FU_TYPE_DFU_FIRMWARE) #define GET_PRIVATE(o) (dfu_firmware_get_instance_private (o)) static void dfu_firmware_init (DfuFirmware *firmware) { } static void dfu_firmware_finalize (GObject *object) { 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_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) { guint32 length = 0; g_autoptr(GPtrArray) images = fu_firmware_get_images (FU_FIRMWARE (firmware)); g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0); for (guint i = 0; i < images->len; i++) { DfuImage *image = g_ptr_array_index (images, i); length += dfu_image_get_size (image); } return length; } /** * 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_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. %FWUPD_INSTALL_FLAG_FORCE * @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, FwupdInstallFlags 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); /* try to get format if not already set */ 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_FORMAT_RAW; /* handled easily */ switch (priv->format) { 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. %FWUPD_INSTALL_FLAG_FORCE * @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, FwupdInstallFlags flags, GError **error) { gchar *contents = NULL; 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); 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); } static gboolean dfu_firmware_check_acceptable_for_format (DfuFirmware *firmware, GError **error) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); g_autoptr(GPtrArray) images = fu_firmware_get_images (FU_FIRMWARE (firmware)); /* always okay */ if (images->len <= 1) return TRUE; if (priv->format == DFU_FIRMWARE_FORMAT_DFUSE) return TRUE; /* unsupported */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "multiple images (%u) not supported for %s", 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_autoptr(GPtrArray) images = fu_firmware_get_images (FU_FIRMWARE (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 (images->len == 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); /* 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_format_to_string: * @format: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_DFU * * Returns a string representation 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"; 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; return DFU_FIRMWARE_FORMAT_UNKNOWN; } static void dfu_firmware_class_init (DfuFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = dfu_firmware_finalize; } fwupd-1.3.9/plugins/dfu/dfu-firmware.h000066400000000000000000000034071362775233600176730ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-dfu-firmware.h" #include "dfu-common.h" #include "dfu-image.h" #include "fwupd-enums.h" #define DFU_TYPE_FIRMWARE (dfu_firmware_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuFirmware, dfu_firmware, DFU, FIRMWARE, FuDfuFirmware) struct _DfuFirmwareClass { FuDfuFirmwareClass parent_class; }; /** * 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 * * 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, /*< 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); guint16 dfu_firmware_get_format (DfuFirmware *firmware); guint32 dfu_firmware_get_size (DfuFirmware *firmware); void dfu_firmware_set_format (DfuFirmware *firmware, DfuFirmwareFormat format); gboolean dfu_firmware_parse_data (DfuFirmware *firmware, GBytes *bytes, FwupdInstallFlags flags, GError **error); gboolean dfu_firmware_parse_file (DfuFirmware *firmware, GFile *file, FwupdInstallFlags flags, GError **error); GBytes *dfu_firmware_write_data (DfuFirmware *firmware, GError **error); gboolean dfu_firmware_write_file (DfuFirmware *firmware, GFile *file, GError **error); fwupd-1.3.9/plugins/dfu/dfu-format-dfu.c000066400000000000000000000107231362775233600201150ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-dfu-firmware.h" #include "dfu-element.h" #include "dfu-format-dfu.h" #include "dfu-format-dfuse.h" #include "dfu-format-raw.h" #include "dfu-image.h" #include "fwupd-error.h" /** * 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) { g_autoptr(FuFirmware) firmware = fu_dfu_firmware_new (); /* check versions */ if (!fu_firmware_parse (firmware, bytes, FWUPD_INSTALL_FLAG_NONE, NULL)) return DFU_FIRMWARE_FORMAT_UNKNOWN; switch (fu_dfu_firmware_get_version (FU_DFU_FIRMWARE (firmware))) { 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; } /** * dfu_firmware_from_dfu: (skip) * @firmware: a #DfuFirmware * @bytes: data to parse * @flags: some #FwupdInstallFlags * @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, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) native = fu_dfu_firmware_new (); g_autoptr(GBytes) contents = NULL; if (!fu_firmware_parse (native, bytes, flags, error)) return FALSE; fu_dfu_firmware_set_vid (FU_DFU_FIRMWARE (firmware), fu_dfu_firmware_get_vid (FU_DFU_FIRMWARE (native))); fu_dfu_firmware_set_pid (FU_DFU_FIRMWARE (firmware), fu_dfu_firmware_get_pid (FU_DFU_FIRMWARE (native))); fu_dfu_firmware_set_release (FU_DFU_FIRMWARE (firmware), fu_dfu_firmware_get_release (FU_DFU_FIRMWARE (native))); /* parse DfuSe prefix */ contents = fu_firmware_get_image_default_bytes (native, error); if (contents == NULL) return FALSE; 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) { g_autoptr(FuFirmware) native = fu_dfu_firmware_new (); g_autoptr(FuFirmwareImage) image = fu_firmware_image_new (contents); fu_dfu_firmware_set_vid (FU_DFU_FIRMWARE (native), fu_dfu_firmware_get_vid (FU_DFU_FIRMWARE (firmware))); fu_dfu_firmware_set_pid (FU_DFU_FIRMWARE (native), fu_dfu_firmware_get_pid (FU_DFU_FIRMWARE (firmware))); fu_dfu_firmware_set_release (FU_DFU_FIRMWARE (native), fu_dfu_firmware_get_release (FU_DFU_FIRMWARE (firmware))); fu_dfu_firmware_set_version (FU_DFU_FIRMWARE (native), dfu_convert_version (dfu_firmware_get_format (firmware))); fu_firmware_add_image (native, image); return fu_firmware_write (native, error); } /** * 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; g_autoptr(DfuImage) image = NULL; image = DFU_IMAGE (fu_firmware_get_image_default (FU_FIRMWARE (firmware), error)); if (image == NULL) return 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.3.9/plugins/dfu/dfu-format-dfu.h000066400000000000000000000007401362775233600201200ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-firmware.h" 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, FwupdInstallFlags flags, GError **error); fwupd-1.3.9/plugins/dfu/dfu-format-dfuse.c000066400000000000000000000240061362775233600204440ustar00rootroot00000000000000/* * 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-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); g_autoptr(GError) error = NULL; if (!fu_memcpy_safe (buf, length_total + sizeof (DfuSeImagePrefix), offset, /* dst */ data, length, 0x0, /* src */ length, &error)) { g_critical ("failed to pack buffer: %s", error->message); continue; } 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; guint32 image_size_total = 0; guint32 offset = sizeof (DfuSePrefix); g_autofree guint8 *buf = NULL; g_autoptr(GPtrArray) dfuse_images = NULL; g_autoptr(GPtrArray) images = NULL; /* get all the image data */ dfuse_images = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); images = fu_firmware_get_images (FU_FIRMWARE (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); if (!fu_memcpy_safe (buf, sizeof (DfuSePrefix) + image_size_total, offset, /* dst */ data, length, 0x0, /* src */ length, error)) return NULL; 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 #FwupdInstallFlags * @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, FwupdInstallFlags 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; fu_firmware_add_image (FU_FIRMWARE (firmware), FU_FIRMWARE_IMAGE (image)); offset += consumed; len -= consumed; } return TRUE; } fwupd-1.3.9/plugins/dfu/dfu-format-dfuse.h000066400000000000000000000007511362775233600204520ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-firmware.h" 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, FwupdInstallFlags flags, GError **error); fwupd-1.3.9/plugins/dfu/dfu-format-raw.c000066400000000000000000000032161362775233600201270ustar00rootroot00000000000000/* * 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_from_raw: (skip) * @firmware: a #DfuFirmware * @bytes: data to parse * @flags: some #FwupdInstallFlags * @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, FwupdInstallFlags 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); fu_firmware_add_image (FU_FIRMWARE (firmware), FU_FIRMWARE_IMAGE (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_IMAGE (fu_firmware_get_image_default (FU_FIRMWARE (firmware), error)); if (image == NULL) 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.3.9/plugins/dfu/dfu-format-raw.h000066400000000000000000000006431362775233600201350ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-firmware.h" GBytes *dfu_firmware_to_raw (DfuFirmware *firmware, GError **error); gboolean dfu_firmware_from_raw (DfuFirmware *firmware, GBytes *bytes, FwupdInstallFlags flags, GError **error); fwupd-1.3.9/plugins/dfu/dfu-image.c000066400000000000000000000140301362775233600171260ustar00rootroot00000000000000/* * 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 "fu-common.h" #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]; } DfuImagePrivate; G_DEFINE_TYPE_WITH_PRIVATE (DfuImage, dfu_image, FU_TYPE_FIRMWARE_IMAGE) #define GET_PRIVATE(o) (dfu_image_get_instance_private (o)) 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) { g_return_val_if_fail (DFU_IS_IMAGE (image), 0xff); return fu_firmware_image_get_idx (FU_FIRMWARE_IMAGE (image)); } /** * 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) { fu_firmware_image_set_idx (FU_FIRMWARE_IMAGE (image), 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); } static void dfu_image_to_string (FuFirmwareImage *self, guint idt, GString *str) { DfuImage *image = DFU_IMAGE (self); DfuImagePrivate *priv = GET_PRIVATE (image); if (priv->name[0] != '\0') fu_common_string_append_kv (str, idt, "Name", priv->name); fu_common_string_append_ku (str, idt, "Elements", 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); } } static void dfu_image_class_init (DfuImageClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuFirmwareImageClass *firmware_image_class = FU_FIRMWARE_IMAGE_CLASS (klass); object_class->finalize = dfu_image_finalize; firmware_image_class->to_string = dfu_image_to_string; } fwupd-1.3.9/plugins/dfu/dfu-image.h000066400000000000000000000020041362775233600171310ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-firmware-image.h" #include "dfu-element.h" #define DFU_TYPE_IMAGE (dfu_image_get_type ()) G_DECLARE_DERIVABLE_TYPE (DfuImage, dfu_image, DFU, IMAGE, FuFirmwareImage) struct _DfuImageClass { FuFirmwareImageClass 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); fwupd-1.3.9/plugins/dfu/dfu-sector.c000066400000000000000000000124171362775233600173520ustar00rootroot00000000000000/* * 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.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 representation 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.3.9/plugins/dfu/dfu-sector.h000066400000000000000000000026521362775233600173570ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #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; DfuSector *dfu_sector_new (guint32 address, guint32 size, guint32 size_left, guint16 zone, guint16 number, DfuSectorCap cap); 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); fwupd-1.3.9/plugins/dfu/dfu-self-test.c000066400000000000000000000301561362775233600177610ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "dfu-common.h" #include "dfu-device.h" #include "dfu-firmware.h" #include "dfu-sector.h" #include "dfu-target-private.h" #include "fu-common.h" #include "fwupd-error.h" static gchar * dfu_test_get_filename (const gchar *filename) { g_autofree gchar *path = NULL; path = g_build_filename (TESTDATADIR, filename, NULL); return fu_common_realpath (path, NULL); } 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 gboolean fu_test_compare_lines (const gchar *txt1, const gchar *txt2, GError **error) { g_autofree gchar *output = NULL; if (g_strcmp0 (txt1, txt2) == 0) return TRUE; if (fu_common_fnmatch (txt2, txt1)) return TRUE; 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; g_set_error_literal (error, 1, 0, output); return FALSE; } static void dfu_firmware_raw_func (void) { DfuElement *element; GBytes *no_suffix_contents; gchar buf[256]; gboolean ret; g_autoptr(DfuFirmware) firmware = NULL; g_autoptr(DfuImage) image_tmp = 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, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fu_dfu_firmware_get_vid (FU_DFU_FIRMWARE (firmware)), ==, 0xffff); g_assert_cmpint (fu_dfu_firmware_get_pid (FU_DFU_FIRMWARE (firmware)), ==, 0xffff); g_assert_cmpint (fu_dfu_firmware_get_release (FU_DFU_FIRMWARE (firmware)), ==, 0xffff); g_assert_cmpint (dfu_firmware_get_format (firmware), ==, DFU_FIRMWARE_FORMAT_RAW); image_tmp = DFU_IMAGE (fu_firmware_get_image_by_idx (FU_FIRMWARE (firmware), 0xfe, NULL)); g_assert (image_tmp == NULL); image_tmp = DFU_IMAGE (fu_firmware_get_image_by_idx (FU_FIRMWARE (firmware), 0, NULL)); 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) firmware1 = dfu_firmware_new (); g_autoptr(DfuFirmware) firmware2 = dfu_firmware_new (); g_autoptr(DfuFirmware) firmware3 = dfu_firmware_new (); 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 */ dfu_firmware_set_format (firmware1, DFU_FIRMWARE_FORMAT_DFU); fu_dfu_firmware_set_vid (FU_DFU_FIRMWARE (firmware1), 0x1234); fu_dfu_firmware_set_pid (FU_DFU_FIRMWARE (firmware1), 0x5678); fu_dfu_firmware_set_release (FU_DFU_FIRMWARE (firmware1), 0xfedc); image = dfu_image_new (); element = dfu_element_new (); dfu_element_set_contents (element, fw); dfu_image_add_element (image, element); fu_firmware_add_image (FU_FIRMWARE (firmware1), FU_FIRMWARE_IMAGE (image)); g_assert_cmpint (dfu_firmware_get_size (firmware1), ==, 256); data = dfu_firmware_write_data (firmware1, &error); g_assert_no_error (error); g_assert (data != NULL); /* can we load it again? */ ret = dfu_firmware_parse_data (firmware2, data, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fu_dfu_firmware_get_vid (FU_DFU_FIRMWARE (firmware2)), ==, 0x1234); g_assert_cmpint (fu_dfu_firmware_get_pid (FU_DFU_FIRMWARE (firmware2)), ==, 0x5678); g_assert_cmpint (fu_dfu_firmware_get_release (FU_DFU_FIRMWARE (firmware2)), ==, 0xfedc); g_assert_cmpint (dfu_firmware_get_format (firmware2), ==, DFU_FIRMWARE_FORMAT_DFU); g_assert_cmpint (dfu_firmware_get_size (firmware2), ==, 256); /* load a real firmware */ filename = dfu_test_get_filename ("kiibohd.dfu.bin"); g_assert (filename != NULL); file = g_file_new_for_path (filename); ret = dfu_firmware_parse_file (firmware3, file, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fu_dfu_firmware_get_vid (FU_DFU_FIRMWARE (firmware3)), ==, 0x1c11); g_assert_cmpint (fu_dfu_firmware_get_pid (FU_DFU_FIRMWARE (firmware3)), ==, 0xb007); g_assert_cmpint (fu_dfu_firmware_get_release (FU_DFU_FIRMWARE (firmware3)), ==, 0xffff); g_assert_cmpint (dfu_firmware_get_format (firmware3), ==, DFU_FIRMWARE_FORMAT_DFU); g_assert_cmpint (dfu_firmware_get_size (firmware3), ==, 0x8eB4); /* 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 (firmware3, &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, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); g_assert_cmpint (fu_dfu_firmware_get_vid (FU_DFU_FIRMWARE (firmware)), ==, 0x0483); g_assert_cmpint (fu_dfu_firmware_get_pid (FU_DFU_FIRMWARE (firmware)), ==, 0x0000); g_assert_cmpint (fu_dfu_firmware_get_release (FU_DFU_FIRMWARE (firmware)), ==, 0x0000); g_assert_cmpint (dfu_firmware_get_format (firmware), ==, DFU_FIRMWARE_FORMAT_DFUSE); g_assert_cmpint (dfu_firmware_get_size (firmware), ==, 0x168d5); /* 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 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); } 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/enums", dfu_enums_func); g_test_add_func ("/dfu/target(DfuSe}", dfu_target_dfuse_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); return g_test_run (); } fwupd-1.3.9/plugins/dfu/dfu-target-avr.c000066400000000000000000000540461362775233600201330ustar00rootroot00000000000000/* * 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.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 (fu_device_has_custom_flag (FU_DEVICE (dfu_target_get_device (target)), "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 (fu_device_has_custom_flag (FU_DEVICE (dfu_target_get_device (target)), "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 (fu_device_has_custom_flag (FU_DEVICE (dfu_target_get_device (target)), "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 (fu_device_has_custom_flag (FU_DEVICE (dfu_target_get_device (target)), "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 (fu_device_has_custom_flag (FU_DEVICE (dfu_target_get_device (target)), "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.3.9/plugins/dfu/dfu-target-avr.h000066400000000000000000000006671362775233600201400ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-target.h" #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); fwupd-1.3.9/plugins/dfu/dfu-target-private.h000066400000000000000000000031021362775233600210050ustar00rootroot00000000000000/* * 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" 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); fwupd-1.3.9/plugins/dfu/dfu-target-stm.c000066400000000000000000000261351362775233600201440ustar00rootroot00000000000000/* * 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.3.9/plugins/dfu/dfu-target-stm.h000066400000000000000000000006671362775233600201530ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "dfu-target.h" #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); fwupd-1.3.9/plugins/dfu/dfu-target.c000066400000000000000000001067251362775233600173470ustar00rootroot00000000000000/* * 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.h" #include "dfu-sector.h" #include "dfu-target-private.h" #include "fwupd-error.h" static void dfu_target_finalize (GObject *object); typedef struct { DfuDevice *device; /* not refcounted */ 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; /* 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 (fu_device_has_flag (FU_DEVICE (priv->device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { 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; } fwupd-1.3.9/plugins/dfu/dfu-target.h000066400000000000000000000053461362775233600173510ustar00rootroot00000000000000/* * 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" #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_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_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); fwupd-1.3.9/plugins/dfu/dfu-tool.c000066400000000000000000001137201362775233600170270ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #ifdef HAVE_GIO_UNIX #include #endif #include "dfu-device.h" #include "dfu-sector.h" #include "fu-device-locker.h" #include "fwupd-error.h" typedef struct { GCancellable *cancellable; GPtrArray *cmd_array; gboolean force; gchar *device_vid_pid; guint16 transfer_size; 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->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); 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); 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_device_wait_for_replug (DfuToolPrivate *priv, DfuDevice *device, guint timeout, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); g_autoptr(GUsbDevice) usb_device2 = NULL; g_autoptr(GUsbContext) usb_context = NULL; /* get all the DFU devices */ usb_context = g_usb_context_new (error); if (usb_context == NULL) return FALSE; /* close */ fu_device_close (FU_DEVICE (device), NULL); /* watch the device disappear and re-appear */ usb_device2 = g_usb_context_wait_for_replug (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; } 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, FWUPD_INSTALL_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; } fu_dfu_firmware_set_vid (FU_DFU_FIRMWARE (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, FWUPD_INSTALL_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; } fu_dfu_firmware_set_pid (FU_DFU_FIRMWARE (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, FWUPD_INSTALL_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; } fu_dfu_firmware_set_release (FU_DFU_FIRMWARE (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_replace_data (DfuToolPrivate *priv, gchar **values, GError **error) { 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; g_autoptr(GPtrArray) images = 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, FWUPD_INSTALL_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 = fu_firmware_get_images (FU_FIRMWARE (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_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, FWUPD_INSTALL_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 = fu_firmware_to_string (FU_FIRMWARE (firmware)); g_debug ("DFU: %s", str_debug); /* write out new file */ return dfu_firmware_write_file (firmware, file_out, error); } static void fu_tool_action_changed_cb (FuDevice *device, GParamSpec *pspec, DfuToolPrivate *priv) { g_print ("%s:\t%u%%\n", fwupd_status_to_string (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 (!fu_device_has_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug ("detaching"); if (!fu_device_detach (FU_DEVICE (device), error)) return FALSE; if (!dfu_device_wait_for_replug (priv, 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 (!fu_device_attach (FU_DEVICE (device), error)) return FALSE; if (!dfu_device_wait_for_replug (priv, 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); fu_dfu_firmware_set_vid (FU_DFU_FIRMWARE (firmware), dfu_device_get_runtime_vid (device)); fu_dfu_firmware_set_pid (FU_DFU_FIRMWARE (firmware), dfu_device_get_runtime_pid (device)); fu_firmware_add_image (FU_FIRMWARE (firmware), FU_FIRMWARE_IMAGE (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 = fu_firmware_to_string (FU_FIRMWARE (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 { 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 (!fu_device_has_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!fu_device_detach (FU_DEVICE (device), error)) return FALSE; if (!dfu_device_wait_for_replug (priv, 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 (!fu_device_attach (FU_DEVICE (device), error)) return FALSE; if (!dfu_device_wait_for_replug (priv, 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 = fu_firmware_to_string (FU_FIRMWARE (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]", fu_usb_device_get_vid (FU_USB_DEVICE (device)), fu_usb_device_get_pid (FU_USB_DEVICE (device)), error->message); } if (!dfu_device_refresh (device, &error)) return NULL; } return g_strdup_printf ("%04x:%04x [%s:%s]", fu_usb_device_get_vid (FU_USB_DEVICE (device)), fu_usb_device_get_pid (FU_USB_DEVICE (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 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) { FwupdInstallFlags flags = FWUPD_INSTALL_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 |= FWUPD_INSTALL_FLAG_FORCE; /* 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 = fu_firmware_to_string (FU_FIRMWARE (firmware)); g_print ("%s\n", tmp); } return TRUE; } static gboolean dfu_tool_write_alt (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(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 " "[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, FWUPD_INSTALL_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 (!fu_device_has_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug ("detaching"); if (!fu_device_detach (FU_DEVICE (device), error)) return FALSE; if (!dfu_device_wait_for_replug (priv, device, 5000, error)) return FALSE; } /* print the new object */ str_debug = fu_firmware_to_string (FU_FIRMWARE (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_IMAGE (fu_firmware_get_image_by_id (FU_FIRMWARE (firmware), values[2], NULL)); 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_IMAGE (fu_firmware_get_image_by_idx (FU_FIRMWARE (firmware), tmp, error)); if (image == NULL) return FALSE; } } else { g_print ("WARNING: Using default firmware image\n"); image = DFU_IMAGE (fu_firmware_get_image_default (FU_FIRMWARE (firmware), error)); if (image == NULL) return FALSE; } /* transfer */ if (!dfu_target_download (target, image, flags, error)) return FALSE; /* do host reset */ if (!fu_device_attach (FU_DEVICE (device), error)) return FALSE; if (!dfu_device_wait_for_replug (priv, 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) { FwupdInstallFlags flags = FWUPD_INSTALL_FLAG_NONE; g_autoptr(DfuDevice) device = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuDeviceLocker) locker = 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 */ fw = fu_common_get_contents_bytes (values[0], error); if (fw == NULL) 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 (!fu_device_has_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!fu_device_detach (FU_DEVICE (device), error)) return FALSE; if (!dfu_device_wait_for_replug (priv, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) { return FALSE; } } /* allow wildcards */ if (priv->force) flags |= FWUPD_INSTALL_FLAG_FORCE; /* 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 (!fu_device_write_firmware (FU_DEVICE (device), fw, flags, error)) return FALSE; /* do host reset */ if (!fu_device_attach (FU_DEVICE (device), error)) return FALSE; if (!dfu_device_wait_for_replug (priv, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* success */ g_print ("%u bytes successfully downloaded to device\n", (guint) g_bytes_get_size (fw)); return TRUE; } #ifdef HAVE_GIO_UNIX 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; } #endif 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, FWUPD_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, "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-release", "FILE RELEASE", /* TRANSLATORS: command description */ _("Set release version on firmware file"), dfu_tool_set_release); 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, "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, "replace-data", NULL, /* TRANSLATORS: command description */ _("Replace data in an existing firmware file"), dfu_tool_replace_data); /* use quirks */ priv->quirks = fu_quirks_new (); if (!fu_quirks_load (priv->quirks, FU_QUIRKS_LOAD_FLAG_NONE, &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 (); #ifdef HAVE_GIO_UNIX g_unix_signal_add_full (G_PRIORITY_DEFAULT, SIGINT, dfu_tool_sigint_cb, priv, NULL); #endif /* 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.3.9/plugins/dfu/dfu-tool.h2m000066400000000000000000000041671362775233600172770ustar00rootroot00000000000000[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.3.9/plugins/dfu/dfu.quirk000066400000000000000000000203121362775233600167570ustar00rootroot00000000000000# All DFU devices [DeviceInstanceId=USB\CLASS_FE&SUBCLASS_01] Plugin = dfu # Realtek USB camera [DeviceInstanceId=USB\VID_0BDA&PID_5850] CounterpartGuid = USB\VID_0BDA&PID_5800 [DeviceInstanceId=USB\VID_0BDA&PID_5855] CounterpartGuid = USB\VID_0BDA&PID_5800 [DeviceInstanceId=USB\VID_0BDA&PID_58FE] CounterpartGuid = USB\VID_0BDA&PID_5800 [DeviceInstanceId=USB\VID_0BDA&PID_5800] Flags = detach-for-attach # 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] Flags = only-supported # Openmoko Freerunner / GTA02 [DeviceInstanceId=USB\VID_1D50&PID_5119] Plugin = dfu Flags = ignore-polltimeout,no-pid-change,no-dfu-runtime,needs-bootloader,no-get-status-upload # OpenPCD Reader [DeviceInstanceId=USB\VID_16C0&PID_076B] Plugin = dfu Flags = ignore-polltimeout # SIMtrace [DeviceInstanceId=USB\VID_16C0&PID_0762] Plugin = dfu Flags = ignore-polltimeout # OpenPICC [DeviceInstanceId=USB\VID_16C0&PID_076C] Plugin = dfu Flags = 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] Flags = ignore-polltimeout [DeviceInstanceId=USB\VID_0908&PID_02C5&REV_0000] Flags = ignore-polltimeout # Midiman M-Audio Transit [DeviceInstanceId=USB\VID_0763&PID_2806] Plugin = dfu Flags = ignore-polltimeout # LPC DFU bootloader [DeviceInstanceId=USB\VID_1FC9&PID_000C] Plugin = dfu Flags = force-dfu-mode # m-stack DFU [DeviceInstanceId=USB\VID_273F&PID_1003] Flags = attach-upload-download [DeviceInstanceId=USB\VID_273F&PID_100A] Flags = attach-upload-download # HydraBus [DeviceInstanceId=USB\VID_1D50&PID_60A7] Plugin = dfu Flags = no-dfu-runtime,needs-bootloader # Jabra 410 [appIDLE & dfuIDLE] [DeviceInstanceId=USB\VID_0B0E&PID_0411] Plugin = dfu Flags = no-pid-change,ignore-upload,attach-extra-reset # Jabra 510 [appIDLE & dfuIDLE] [DeviceInstanceId=USB\VID_0B0E&PID_0421] Plugin = dfu Flags = no-pid-change,ignore-upload,attach-extra-reset # Jabra 710 [appIDLE & dfuIDLE] [DeviceInstanceId=USB\VID_0B0E&PID_0982] Plugin = dfu Flags = no-pid-change,ignore-upload,attach-extra-reset # Jabra 810 [appIDLE & dfuIDLE] [DeviceInstanceId=USB\VID_0B0E&PID_0971] Plugin = dfu Flags = no-pid-change,ignore-upload,attach-extra-reset # Atmel AT90USB Bootloader [DeviceInstanceId=USB\VID_03EB&PID_2FF7] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FF9] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FFA] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FFB] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode # Atmel ATMEGA Bootloader [DeviceInstanceId=USB\VID_03EB&PID_2FEE] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FEF] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FF0] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FF2] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FF3] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [DeviceInstanceId=USB\VID_03EB&PID_2FF4] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode # Atmel XMEGA Bootloader [DeviceInstanceId=USB\VID_03EB&PID_2FE2] Plugin = dfu Flags = 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.3.9/plugins/dfu/fu-plugin-dfu.c000066400000000000000000000005431362775233600177560ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "dfu-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, DFU_TYPE_DEVICE); } fwupd-1.3.9/plugins/dfu/fuzzing.md000066400000000000000000000002541362775233600171450ustar00rootroot00000000000000Fuzzing ======= 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 @@ fwupd-1.3.9/plugins/dfu/fuzzing/000077500000000000000000000000001362775233600166225ustar00rootroot00000000000000fwupd-1.3.9/plugins/dfu/fuzzing/example.dfu000066400000000000000000000000341362775233600207520ustar00rootroot00000000000000hello world UFDfd-fwupd-1.3.9/plugins/dfu/fuzzing/example.dfuse000066400000000000000000000005011362775233600213010ustar00rootroot00000000000000DfuSe1TargetST hello world UFDw~fwupd-1.3.9/plugins/dfu/fuzzing/metadata-multiple.dfu000066400000000000000000000001151362775233600227300ustar00rootroot00000000000000amaliaMDkeyvalue CopyrightRichard HughesLicenseGPL-2.0+UFDGZ+@fwupd-1.3.9/plugins/dfu/fuzzing/metadata.dfu000066400000000000000000000000431362775233600210770ustar00rootroot00000000000000amaliaMDkeyvalueUFDXfwupd-1.3.9/plugins/dfu/meson.build000066400000000000000000000047071362775233600173000ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginDfu"'] install_data(['dfu.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) dfu = static_library( 'dfu', fu_hash, sources : [ 'dfu-common.c', 'dfu-device.c', 'dfu-element.c', 'dfu-firmware.c', 'dfu-format-dfu.c', 'dfu-format-dfuse.c', 'dfu-format-raw.c', 'dfu-image.c', 'dfu-sector.c', 'dfu-target.c', 'dfu-target-stm.c', 'dfu-target-avr.c', ], dependencies : [ giounix, libm, gusb, gudev, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs, include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], ) shared_module('fu_plugin_dfu', fu_hash, sources : [ 'fu-plugin-dfu.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, dfu, ], ) dfu_tool = executable( 'dfu-tool', fu_hash, sources : [ 'dfu-tool.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ libxmlb, giounix, libm, gusb, gudev, ], link_with : [ dfu, fwupd, fwupdplugin, ], 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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ libxmlb, gio, gusb, gudev, libm, ], link_with : [ dfu, fwupd, fwupdplugin, ], c_args : cargs ) test('dfu-self-test', e) endif plugindfu_incdir = include_directories('.') fwupd-1.3.9/plugins/dfu/tests/000077500000000000000000000000001362775233600162705ustar00rootroot00000000000000fwupd-1.3.9/plugins/dfu/tests/dev_VRBRAIN.dfu000066400000000000000000002650221362775233600207400ustar00rootroot00000000000000DfuSejTargetST...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.3.9/plugins/dfu/tests/example.bin000066400000000000000000000000141362775233600204100ustar00rootroot00000000000000hello world fwupd-1.3.9/plugins/dfu/tests/example.dfu000066400000000000000000000000341362775233600204200ustar00rootroot00000000000000hello world UFDfd-fwupd-1.3.9/plugins/dfu/tests/example.xdfu000066400000000000000000000000341362775233600206100ustar00rootroot00000000000000iy_fs5UFDM3fwupd-1.3.9/plugins/dfu/tests/kiibohd.dfu.bin000066400000000000000000001073041362775233600211550ustar00rootroot00000000000000 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.3.9/plugins/dfu/tests/metadata.dfu000066400000000000000000000000431362775233600205450ustar00rootroot00000000000000amaliaMDkeyvalueUFDXfwupd-1.3.9/plugins/ebitdo/000077500000000000000000000000001362775233600156165ustar00rootroot00000000000000fwupd-1.3.9/plugins/ebitdo/README.md000066400000000000000000000022371362775233600171010ustar00rootroot000000000000008Bitdo 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, which is set to various different values depending on the model and device mode. The list of USB VIDs used is: * `USB:0x2DC8` * `USB:0x0483` * `USB:0x1002` * `USB:0x1235` * `USB:0x2002` * `USB:0x8000` fwupd-1.3.9/plugins/ebitdo/data/000077500000000000000000000000001362775233600165275ustar00rootroot00000000000000fwupd-1.3.9/plugins/ebitdo/data/m30.txt000066400000000000000000000175211362775233600176750ustar00rootroot00000000000000Bus 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.3.9/plugins/ebitdo/data/nes30pro.txt000066400000000000000000000107671362775233600207540ustar00rootroot00000000000000Bus 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.3.9/plugins/ebitdo/data/scf30.txt000066400000000000000000000106001362775233600202030ustar00rootroot00000000000000 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.3.9/plugins/ebitdo/data/sf30pro.txt000066400000000000000000000303401362775233600205640ustar00rootroot00000000000000Start + 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.3.9/plugins/ebitdo/data/update.csv000066400000000000000000032101711362775233600205330ustar00rootroot00000000000000# 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.3.9/plugins/ebitdo/data/update.tdc000066400000000000000000017532571362775233600205310ustar00rootroot00000000000000TPDC?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.3.9/plugins/ebitdo/ebitdo.quirk000066400000000000000000000037311362775233600201450ustar00rootroot00000000000000# 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,will-disappear 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 = will-disappear [DeviceInstanceId=USB\VID_2DC8&PID_9001] Plugin = ebitdo Flags = will-disappear 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 = will-disappear [DeviceInstanceId=USB\VID_2DC8&PID_6001] Plugin = ebitdo Flags = will-disappear InstallDuration = 120 # SN30 PRO+ ## Dinput mode (Start + B) [DeviceInstanceId=USB\VID_2DC8&PID_6002] Plugin = ebitdo Flags = will-disappear InstallDuration = 120 # M30 [DeviceInstanceId=USB\VID_2DC8&PID_5006] Plugin = ebitdo Flags = none InstallDuration = 120 fwupd-1.3.9/plugins/ebitdo/fu-ebitdo-common.c000066400000000000000000000044721362775233600211350ustar00rootroot00000000000000/* * 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)); } fwupd-1.3.9/plugins/ebitdo/fu-ebitdo-common.h000066400000000000000000000045161362775233600211410ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* 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_pkt (FuEbitdoPkt *hdr); fwupd-1.3.9/plugins/ebitdo/fu-ebitdo-device.c000066400000000000000000000423401362775233600211000ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-chunk.h" #include "fu-ebitdo-common.h" #include "fu-ebitdo-device.h" #include "fu-ebitdo-firmware.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); if (!fu_memcpy_safe (packet, sizeof(packet), 0x08, /* dst */ in, in_len, 0x0, /* src */ in_len, error)) return FALSE; 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) 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; } if (!fu_memcpy_safe (out, out_len, 0x0, /* dst */ packet, sizeof(packet), sizeof(FuEbitdoPkt), /* src */ hdr->payload_len, error)) return FALSE; } 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; } if (!fu_memcpy_safe (out, out_len, 0x0, /* dst */ packet, sizeof(packet), 0x1, /* src */ 4, error)) return FALSE; } 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; } if (!fu_memcpy_safe (out, out_len, 0x0, /* dst */ packet, sizeof(packet), sizeof(FuEbitdoPkt) - 3, /* src */ hdr->cmd_len, error)) return FALSE; } 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); fu_device_set_version_raw (FU_DEVICE (self), version); } 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; } static gboolean fu_ebitdo_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuEbitdoDevice *self = FU_EBITDO_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); const guint8 *buf; gsize bufsz = 0; guint32 serial_new[3]; g_autoptr(GBytes) fw_hdr = NULL; g_autoptr(GBytes) fw_payload = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) chunks = 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)) { 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 0x6002: /* 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_NEEDS_USER_ACTION, msg->str); return FALSE; } /* get header and payload */ fw_hdr = fu_firmware_get_image_by_id_bytes (firmware, FU_FIRMWARE_IMAGE_ID_HEADER, error); if (fw_hdr == NULL) return FALSE; fw_payload = fu_firmware_get_image_by_id_bytes (firmware, FU_FIRMWARE_IMAGE_ID_PAYLOAD, error); if (fw_payload == NULL) return FALSE; /* set up the firmware header */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); buf = g_bytes_get_data (fw_hdr, &bufsz); 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, buf, bufsz, error)) { g_prefix_error (error, "failed to set up firmware header:"); return FALSE; } if (!fu_ebitdo_device_receive (self, NULL, 0, error)) { g_prefix_error (error, "failed to get ACK for fw update header: "); return FALSE; } /* flash the firmware in 32 byte blocks */ chunks = fu_chunk_array_new_from_bytes (fw_payload, 0x0, 0x0, 32); for (guint i = 0; i < chunks->len; i++) { FuChunk *chunk = g_ptr_array_index (chunks, i); if (g_getenv ("FWUPD_EBITDO_VERBOSE") != NULL) { g_debug ("writing %u bytes to 0x%04x of 0x%04x", chunk->data_sz, chunk->address, chunk->data_sz); } 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, chunk->data, chunk->data_sz, error)) { g_prefix_error (error, "failed to write firmware @0x%04x: ", chunk->address); return FALSE; } if (!fu_ebitdo_device_receive (self, NULL, 0, error)) { g_prefix_error (error, "failed to get ACK for write firmware @0x%04x: ", chunk->address); return FALSE; } fu_device_set_progress_full (device, chunk->idx, chunks->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)) { g_prefix_error (error, "failed to set encoding ID: "); 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)) { g_prefix_error (error, "failed to mark firmware as successful: "); return FALSE; } if (!fu_ebitdo_device_receive (self, NULL, 0, &error_local)) { g_prefix_error (&error_local, "failed to get ACK for mark firmware as successful: "); if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { fu_device_set_remove_delay (device, 0); g_debug ("%s", error_local->message); return TRUE; } g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } /* when doing a soft-reboot the device does not re-enumerate properly * so manually reboot the GUsbDevice */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!g_usb_device_reset (usb_device, &error_local)) { g_prefix_error (&error_local, "failed to force-reset device: "); if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { fu_device_set_remove_delay (device, 0); g_debug ("%s", error_local->message); return TRUE; } g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } /* not all 8bito devices come back in the right mode */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) fu_device_set_remove_delay (device, 0); else fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* 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 FuFirmware * fu_ebitdo_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_ebitdo_firmware_new (); fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; return g_steal_pointer (&firmware); } static void fu_ebitdo_device_init (FuEbitdoDevice *self) { fu_device_set_protocol (FU_DEVICE (self), "com.8bitdo"); } 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; klass_device->prepare_firmware = fu_ebitdo_device_prepare_firmware; } fwupd-1.3.9/plugins/ebitdo/fu-ebitdo-device.h000066400000000000000000000004471362775233600211070ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_EBITDO_DEVICE (fu_ebitdo_device_get_type ()) G_DECLARE_FINAL_TYPE (FuEbitdoDevice, fu_ebitdo_device, FU, EBITDO_DEVICE, FuUsbDevice) fwupd-1.3.9/plugins/ebitdo/fu-ebitdo-firmware.c000066400000000000000000000060431362775233600214550ustar00rootroot00000000000000/* * Copyright (C) 2016-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ebitdo-firmware.h" struct _FuEbitdoFirmware { FuFirmwareClass parent_instance; }; G_DEFINE_TYPE (FuEbitdoFirmware, fu_ebitdo_firmware, FU_TYPE_FIRMWARE) /* little endian */ typedef struct __attribute__((packed)) { guint32 version; guint32 destination_addr; guint32 destination_len; guint32 reserved[4]; } FuEbitdoFirmwareHeader; static gboolean fu_ebitdo_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuEbitdoFirmwareHeader *hdr; guint32 payload_len; g_autofree gchar *version = NULL; g_autoptr(FuFirmwareImage) img_hdr = fu_firmware_image_new (NULL); g_autoptr(FuFirmwareImage) img_payload = fu_firmware_image_new (NULL); g_autoptr(GBytes) fw_hdr = NULL; g_autoptr(GBytes) fw_payload = NULL; /* 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; } /* check the file size */ hdr = (FuEbitdoFirmwareHeader *) g_bytes_get_data (fw, NULL); 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; } } /* parse version */ version = g_strdup_printf ("%.2f", GUINT32_FROM_LE(hdr->version) / 100.f); fu_firmware_set_version (firmware, version); /* add header */ fw_hdr = g_bytes_new_from_bytes (fw, 0x0, sizeof(FuEbitdoFirmwareHeader)); fu_firmware_image_set_id (img_hdr, FU_FIRMWARE_IMAGE_ID_HEADER); fu_firmware_image_set_bytes (img_hdr, fw_hdr); fu_firmware_add_image (firmware, img_hdr); /* add payload */ fw_payload = g_bytes_new_from_bytes (fw, sizeof(FuEbitdoFirmwareHeader), payload_len); fu_firmware_image_set_id (img_payload, FU_FIRMWARE_IMAGE_ID_PAYLOAD); fu_firmware_image_set_addr (img_payload, GUINT32_FROM_LE(hdr->destination_addr)); fu_firmware_image_set_bytes (img_payload, fw_payload); fu_firmware_add_image (firmware, img_payload); return TRUE; } static void fu_ebitdo_firmware_init (FuEbitdoFirmware *self) { } static void fu_ebitdo_firmware_class_init (FuEbitdoFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->parse = fu_ebitdo_firmware_parse; } FuFirmware * fu_ebitdo_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_EBITDO_FIRMWARE, NULL)); } fwupd-1.3.9/plugins/ebitdo/fu-ebitdo-firmware.h000066400000000000000000000005471362775233600214650ustar00rootroot00000000000000/* * Copyright (C) 2016-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_EBITDO_FIRMWARE (fu_ebitdo_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuEbitdoFirmware, fu_ebitdo_firmware, FU, EBITDO_FIRMWARE, FuFirmware) FuFirmware *fu_ebitdo_firmware_new (void); fwupd-1.3.9/plugins/ebitdo/fu-plugin-ebitdo.c000066400000000000000000000007321362775233600211360ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ebitdo-device.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-ebitdo-firmware.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_EBITDO_DEVICE); fu_plugin_add_firmware_gtype (plugin, "8bitdo", FU_TYPE_EBITDO_FIRMWARE); } fwupd-1.3.9/plugins/ebitdo/meson.build000066400000000000000000000010461362775233600177610ustar00rootroot00000000000000cargs = ['-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', 'fu-ebitdo-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/emmc/000077500000000000000000000000001362775233600152715ustar00rootroot00000000000000fwupd-1.3.9/plugins/emmc/README.md000066400000000000000000000010211362775233600165420ustar00rootroot00000000000000eMMC Support ================= Introduction ------------ This plugin reads the sysfs attributes corresponding to eMMC devices. It uses the kernel MMC API for flashing devices. Protocol -------- eMMC devices support the `org.jedec.mmc` protocol. GUID Generation --------------- These devices use the following instance values: * `EMMC\%NAME%` * `EMMC\%MANFID%&%OEMID%` * `EMMC\%MANFID%&%OEMID%&%NAME%` Vendor ID Security ------------------ The vendor ID is set from the EMMC vendor, for example set to `EMMC:{$manfid}` fwupd-1.3.9/plugins/emmc/emmc.quirk000066400000000000000000000001241362775233600172640ustar00rootroot00000000000000# match all devices with this udev subsystem [DeviceInstanceId=BLOCK] Plugin = emmc fwupd-1.3.9/plugins/emmc/fu-emmc-device.c000066400000000000000000000354501362775233600202320ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: GPL-2+ */ #include "config.h" #include #include #include "fu-chunk.h" #include "fu-emmc-device.h" /* From kernel linux/major.h */ #define MMC_BLOCK_MAJOR 179 /* From kernel linux/mmc/mmc.h */ #define MMC_SWITCH 6 /* ac [31:0] See below R1b */ #define MMC_SEND_EXT_CSD 8 /* adtc R1 */ #define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */ #define MMC_WRITE_BLOCK 24 /* adtc [31:0] data addr R1 */ /* From kernel linux/mmc/core.h */ #define MMC_RSP_PRESENT (1 << 0) #define MMC_RSP_CRC (1 << 2) /* expect valid crc */ #define MMC_RSP_BUSY (1 << 3) /* card may send busy */ #define MMC_RSP_OPCODE (1 << 4) /* response contains opcode */ #define MMC_RSP_SPI_S1 (1 << 7) /* one status byte */ #define MMC_CMD_AC (0 << 5) #define MMC_CMD_ADTC (1 << 5) #define MMC_RSP_SPI_BUSY (1 << 10) /* card may send busy */ #define MMC_RSP_SPI_R1 (MMC_RSP_SPI_S1) #define MMC_RSP_SPI_R1B (MMC_RSP_SPI_S1|MMC_RSP_SPI_BUSY) #define MMC_RSP_R1 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) #define MMC_RSP_R1B (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY) /* EXT_CSD fields */ #define EXT_CSD_SUPPORTED_MODES 493 /* RO */ #define EXT_CSD_FFU_FEATURES 492 /* RO */ #define EXT_CSD_FFU_ARG_3 490 /* RO */ #define EXT_CSD_FFU_ARG_2 489 /* RO */ #define EXT_CSD_FFU_ARG_1 488 /* RO */ #define EXT_CSD_FFU_ARG_0 487 /* RO */ #define EXT_CSD_NUM_OF_FW_SEC_PROG_3 305 /* RO */ #define EXT_CSD_NUM_OF_FW_SEC_PROG_2 304 /* RO */ #define EXT_CSD_NUM_OF_FW_SEC_PROG_1 303 /* RO */ #define EXT_CSD_NUM_OF_FW_SEC_PROG_0 302 /* RO */ #define EXT_CSD_REV 192 #define EXT_CSD_FW_CONFIG 169 /* R/W */ #define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */ #define EXT_CSD_MODE_CONFIG 30 #define EXT_CSD_MODE_OPERATION_CODES 29 /* W */ #define EXT_CSD_FFU_STATUS 26 /* R */ #define EXT_CSD_REV_V5_1 8 #define EXT_CSD_REV_V5_0 7 /* EXT_CSD field definitions */ #define EXT_CSD_NORMAL_MODE (0x00) #define EXT_CSD_FFU_MODE (0x01) #define EXT_CSD_FFU_INSTALL (0x01) #define EXT_CSD_FFU (1<<0) #define EXT_CSD_UPDATE_DISABLE (1<<0) #define EXT_CSD_CMD_SET_NORMAL (1<<0) struct _FuEmmcDevice { FuUdevDevice parent_instance; guint32 sect_size; }; G_DEFINE_TYPE (FuEmmcDevice, fu_emmc_device, FU_TYPE_UDEV_DEVICE) static void fu_emmc_device_to_string (FuDevice *device, guint idt, GString *str) { FuEmmcDevice *self = FU_EMMC_DEVICE (device); fu_common_string_append_ku (str, idt, "SectorSize", self->sect_size); } static const gchar * fu_emmc_device_get_manufacturer (guint64 mmc_id) { switch (mmc_id) { case 0x00: case 0x44: return "SanDisk"; case 0x02: return "Kingston/Sandisk"; case 0x03: case 0x11: return "Toshiba"; case 0x13: return "Micron"; case 0x15: return "Samsung/Sandisk/LG"; case 0x37: return "Kingmax"; case 0x70: case 0x2c: return "Kingston"; default: return NULL; } return NULL; } static gboolean fu_emmc_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 %s for %s", name, sysfs); return FALSE; } *val_out = g_ascii_strtoull (sysfs, NULL, 16); return TRUE; } static gboolean fu_emmc_device_probe (FuUdevDevice *device, GError **error) { GUdevDevice *udev_device = fu_udev_device_get_dev (device); guint64 flag; guint64 oemid = 0; guint64 manfid = 0; const gchar *tmp; g_autoptr(GUdevDevice) udev_parent = NULL; g_autofree gchar *name_only = NULL; g_autofree gchar *man_oem = NULL; g_autofree gchar *man_oem_name = NULL; g_autofree gchar *vendor_id = NULL; udev_parent = g_udev_device_get_parent_with_subsystem (udev_device, "mmc", NULL); if (udev_parent == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no MMC parent"); return FALSE; } /* look for only the parent node */ if (g_strcmp0 (g_udev_device_get_devtype (udev_device), "disk") != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct devtype=%s, expected disk", g_udev_device_get_devtype (udev_device)); return FALSE; } /* doesn't support FFU */ if (!fu_emmc_device_get_sysattr_guint64 (udev_parent, "ffu_capable", &flag, error)) return FALSE; if (flag == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s does not support field firmware updates", fu_device_get_name (FU_DEVICE (device))); return FALSE; } /* add instance IDs */ tmp = g_udev_device_get_property (udev_device, "ID_NAME"); if (tmp == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "has no ID_NAME"); return FALSE; } /* name */ fu_device_set_name (FU_DEVICE (device), tmp); name_only = g_strdup_printf ("EMMC\\%s", fu_device_get_name (device)); fu_device_add_instance_id (FU_DEVICE (device), name_only); /* manfid + oemid, manfid + oemid + name */ if (!fu_emmc_device_get_sysattr_guint64 (udev_parent, "manfid", &manfid, error)) return FALSE; if (!fu_emmc_device_get_sysattr_guint64 (udev_parent, "oemid", &oemid, error)) return FALSE; man_oem = g_strdup_printf ("EMMC\\%04" G_GUINT64_FORMAT "&%04" G_GUINT64_FORMAT, manfid, oemid); fu_device_add_instance_id (FU_DEVICE (device), man_oem); man_oem_name = g_strdup_printf ("EMMC\\%04" G_GUINT64_FORMAT "&%04" G_GUINT64_FORMAT "&%s", manfid, oemid, fu_device_get_name (device)); fu_device_add_instance_id (FU_DEVICE (device), man_oem_name); /* set the vendor */ tmp = g_udev_device_get_sysfs_attr (udev_parent, "manfid"); vendor_id = g_strdup_printf ("EMMC:%s", tmp); fu_device_set_vendor_id (FU_DEVICE (device), vendor_id); fu_device_set_vendor (FU_DEVICE (device), fu_emmc_device_get_manufacturer (manfid)); /* set the physical ID */ if (!fu_udev_device_set_physical_id (device, "mmc", error)) return FALSE; /* internal */ if (!fu_emmc_device_get_sysattr_guint64 (udev_device, "removable", &flag, error)) return FALSE; if (flag == 0) fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_INTERNAL); /* firwmare version */ tmp = g_udev_device_get_sysfs_attr (udev_parent, "fwrev"); if (tmp != NULL) fu_device_set_version (FU_DEVICE (device), tmp, FWUPD_VERSION_FORMAT_NUMBER); return TRUE; } static gboolean fu_emmc_read_extcsd (FuEmmcDevice *self, guint8 *ext_csd, gsize ext_csd_sz, GError **error) { struct mmc_ioc_cmd idata = { 0x0 }; idata.write_flag = 0; idata.opcode = MMC_SEND_EXT_CSD; idata.arg = 0; idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; idata.blksz = 512; idata.blocks = 1; mmc_ioc_cmd_set_data (idata, ext_csd); return fu_udev_device_ioctl (FU_UDEV_DEVICE (self), MMC_IOC_CMD, (guint8 *) &idata, NULL, error); } static gboolean fu_emmc_validate_extcsd (FuDevice *device, GError **error) { FuEmmcDevice *self = FU_EMMC_DEVICE (device); guint8 ext_csd[512] = { 0x0 }; if (!fu_emmc_read_extcsd (FU_EMMC_DEVICE (device), ext_csd, sizeof (ext_csd), error)) return FALSE; if (ext_csd[EXT_CSD_REV] < EXT_CSD_REV_V5_0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "FFU is only available on devices >= " "MMC 5.0, not supported in %s", fu_device_get_name (device)); return FALSE; } if ((ext_csd[EXT_CSD_SUPPORTED_MODES] & EXT_CSD_FFU) == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "FFU is not supported in %s", fu_device_get_name (device)); return FALSE; } if (ext_csd[EXT_CSD_FW_CONFIG] & EXT_CSD_UPDATE_DISABLE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware update was disabled in %s", fu_device_get_name (device)); return FALSE; } self->sect_size = (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] == 0) ? 512 : 4096; return TRUE; } static gboolean fu_emmc_device_setup (FuDevice *device, GError **error) { g_autoptr(GError) error_validate = NULL; if (!fu_emmc_validate_extcsd (device, &error_validate)) g_debug ("%s", error_validate->message); else fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE); return TRUE; } static FuFirmware * fu_emmc_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuEmmcDevice *self = FU_EMMC_DEVICE (device); gsize fw_size = g_bytes_get_size (fw); /* check alignment */ if ((fw_size % self->sect_size) > 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware data size (%" G_GSIZE_FORMAT ") is not aligned", fw_size); return NULL; } return fu_firmware_new_from_bytes (fw); } static gboolean fu_emmc_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuEmmcDevice *self= FU_EMMC_DEVICE (device); gsize fw_size = 0; gsize total_done; guint32 arg; guint32 sect_done = 0; guint8 ext_csd[512]; guint failure_cnt = 0; g_autofree struct mmc_ioc_multi_cmd *multi_cmd = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; if (!fu_emmc_read_extcsd (FU_EMMC_DEVICE (device), ext_csd, sizeof (ext_csd), error)) return FALSE; fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; fw_size = g_bytes_get_size (fw); /* set CMD ARG */ arg = ext_csd[EXT_CSD_FFU_ARG_0] | ext_csd[EXT_CSD_FFU_ARG_1] << 8 | ext_csd[EXT_CSD_FFU_ARG_2] << 16 | ext_csd[EXT_CSD_FFU_ARG_3] << 24; /* prepare multi_cmd to be sent */ multi_cmd = g_malloc0 (sizeof(struct mmc_ioc_multi_cmd) + 3 * sizeof(struct mmc_ioc_cmd)); multi_cmd->num_of_cmds = 3; /* put device into ffu mode */ multi_cmd->cmds[0].opcode = MMC_SWITCH; multi_cmd->cmds[0].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (EXT_CSD_MODE_CONFIG << 16) | (EXT_CSD_FFU_MODE << 8) | EXT_CSD_CMD_SET_NORMAL; multi_cmd->cmds[0].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; multi_cmd->cmds[0].write_flag = 1; /* send image chunk */ multi_cmd->cmds[1].opcode = MMC_WRITE_BLOCK; multi_cmd->cmds[1].blksz = self->sect_size; multi_cmd->cmds[1].blocks = 1; multi_cmd->cmds[1].arg = arg; multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; multi_cmd->cmds[1].write_flag = 1; /* return device into normal mode */ multi_cmd->cmds[2].opcode = MMC_SWITCH; multi_cmd->cmds[2].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (EXT_CSD_MODE_CONFIG << 16) | (EXT_CSD_NORMAL_MODE << 8) | EXT_CSD_CMD_SET_NORMAL; multi_cmd->cmds[2].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; multi_cmd->cmds[2].write_flag = 1; /* build packets */ chunks = fu_chunk_array_new_from_bytes (fw, 0x00, /* start addr */ 0x00, /* page_sz */ self->sect_size); while (sect_done == 0) { for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); mmc_ioc_cmd_set_data (multi_cmd->cmds[1], chk->data); if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self), MMC_IOC_MULTI_CMD, (guint8 *) multi_cmd, NULL, error)) { g_prefix_error (error, "multi-cmd failed: "); /* multi-cmd ioctl failed before exiting from ffu mode */ fu_udev_device_ioctl (FU_UDEV_DEVICE (self), MMC_IOC_CMD, (guint8 *) &multi_cmd->cmds[2], NULL, NULL); return FALSE; } if (!fu_emmc_read_extcsd (self, ext_csd, sizeof (ext_csd), error)) return FALSE; /* if we need to restart the download */ sect_done = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_0] | ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_1] << 8 | ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_2] << 16 | ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_3] << 24; if (sect_done == 0) { if (failure_cnt >= 3) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "programming failed"); return FALSE; } failure_cnt++; g_debug ("programming failed: retrying (%u)", failure_cnt); break; } /* update progress */ fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len - 1); } } /* sanity check */ total_done = (gsize) sect_done * (gsize) self->sect_size; if (total_done != fw_size) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "firmware size and number of sectors written " "mismatch (%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT "):", total_done, fw_size); return FALSE; } /* check mode operation for ffu install*/ if (!ext_csd[EXT_CSD_FFU_FEATURES]) { fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } else { /* re-enter ffu mode and install the firmware */ multi_cmd->num_of_cmds = 2; /* set ext_csd to install mode */ multi_cmd->cmds[1].opcode = MMC_SWITCH; multi_cmd->cmds[1].blksz = 0; multi_cmd->cmds[1].blocks = 0; multi_cmd->cmds[1].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (EXT_CSD_MODE_OPERATION_CODES << 16) | (EXT_CSD_FFU_INSTALL << 8) | EXT_CSD_CMD_SET_NORMAL; multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; multi_cmd->cmds[1].write_flag = 1; /* send ioctl with multi-cmd */ if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self), MMC_IOC_MULTI_CMD, (guint8 *) multi_cmd, NULL, error)) { /* In case multi-cmd ioctl failed before exiting from ffu mode */ g_prefix_error (error, "multi-cmd failed setting install mode: "); fu_udev_device_ioctl (FU_UDEV_DEVICE (self), MMC_IOC_CMD, (guint8 *) &multi_cmd->cmds[2], NULL, NULL); return FALSE; } /* return status */ if (!fu_emmc_read_extcsd (self, ext_csd, sizeof (ext_csd), error)) return FALSE; if (ext_csd[EXT_CSD_FFU_STATUS] != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "FFU install failed: %d", ext_csd[EXT_CSD_FFU_STATUS]); return FALSE; } } return TRUE; } static void fu_emmc_device_init (FuEmmcDevice *self) { fu_device_set_protocol (FU_DEVICE (self), "org.jedec.mmc"); fu_device_add_icon (FU_DEVICE (self), "media-memory"); } static void fu_emmc_device_finalize (GObject *object) { G_OBJECT_CLASS (fu_emmc_device_parent_class)->finalize (object); } static void fu_emmc_device_class_init (FuEmmcDeviceClass *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_emmc_device_finalize; klass_device->setup = fu_emmc_device_setup; klass_device->to_string = fu_emmc_device_to_string; klass_device->prepare_firmware = fu_emmc_device_prepare_firmware; klass_udev_device->probe = fu_emmc_device_probe; klass_device->write_firmware = fu_emmc_device_write_firmware; } fwupd-1.3.9/plugins/emmc/fu-emmc-device.h000066400000000000000000000004501362775233600202270ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_EMMC_DEVICE (fu_emmc_device_get_type ()) G_DECLARE_FINAL_TYPE (FuEmmcDevice, fu_emmc_device, FU, EMMC_DEVICE, FuUdevDevice) fwupd-1.3.9/plugins/emmc/fu-plugin-emmc.c000066400000000000000000000006461362775233600202700ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-emmc-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_udev_subsystem (plugin, "block"); fu_plugin_set_device_gtype (plugin, FU_TYPE_EMMC_DEVICE); } fwupd-1.3.9/plugins/emmc/meson.build000066400000000000000000000007451362775233600174410ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginEmmc"'] install_data(['emmc.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_emmc', fu_hash, sources : [ 'fu-plugin-emmc.c', 'fu-emmc-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/fastboot/000077500000000000000000000000001362775233600161715ustar00rootroot00000000000000fwupd-1.3.9/plugins/fastboot/README.md000066400000000000000000000024251362775233600174530ustar00rootroot00000000000000Fastboot 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 | Vendor ID Security ------------------ The vendor ID is set from the USB vendor, for example `USB:0x18D1` fwupd-1.3.9/plugins/fastboot/data/000077500000000000000000000000001362775233600171025ustar00rootroot00000000000000fwupd-1.3.9/plugins/fastboot/data/android/000077500000000000000000000000001362775233600205225ustar00rootroot00000000000000fwupd-1.3.9/plugins/fastboot/data/android/flashfile.xml000066400000000000000000000003701362775233600232010ustar00rootroot00000000000000 fwupd-1.3.9/plugins/fastboot/data/lsusb.txt000066400000000000000000000036661362775233600210060ustar00rootroot00000000000000Bus 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.3.9/plugins/fastboot/data/qfil/000077500000000000000000000000001362775233600200355ustar00rootroot00000000000000fwupd-1.3.9/plugins/fastboot/data/qfil/partition_nand.xml000066400000000000000000000011751362775233600235740ustar00rootroot00000000000000 0xAA7D1B9A 0x1F7D48BC 0x4 0:SBL 0x8 0x2 0 0xFF 0x01 0x00 0xFE sbl1.mbn fwupd-1.3.9/plugins/fastboot/fastboot.quirk000066400000000000000000000001351362775233600210660ustar00rootroot00000000000000# All fastboot devices [DeviceInstanceId=USB\CLASS_FF&SUBCLASS_42&PROT_03] Plugin = fastboot fwupd-1.3.9/plugins/fastboot/fu-fastboot-device.c000066400000000000000000000471501362775233600220320ustar00rootroot00000000000000/* * 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, guint idt, GString *str) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device); fu_common_string_append_kx (str, idt, "InterfaceNumber", self->intf_nr); fu_common_string_append_kx (str, idt, "BlockSize", self->blocksz); fu_common_string_append_kb (str, idt, "Secure", self->secure); } 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, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* 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); if (!fu_fastboot_device_cmd (device, "reboot", FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error)) return FALSE; fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_fastboot_device_init (FuFastbootDevice *self) { /* this is a safe default, even using USBv1 */ self->blocksz = 512; fu_device_set_protocol (FU_DEVICE (self), "com.google.fastboot"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); 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; } fwupd-1.3.9/plugins/fastboot/fu-fastboot-device.h000066400000000000000000000004611362775233600220310ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_FASTBOOT_DEVICE (fu_fastboot_device_get_type ()) G_DECLARE_FINAL_TYPE (FuFastbootDevice, fu_fastboot_device, FU, FASTBOOT_DEVICE, FuUsbDevice) fwupd-1.3.9/plugins/fastboot/fu-plugin-fastboot.c000066400000000000000000000005631362775233600220660ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-fastboot-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_FASTBOOT_DEVICE); } fwupd-1.3.9/plugins/fastboot/meson.build000066400000000000000000000007721362775233600203410ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/flashrom/000077500000000000000000000000001362775233600161635ustar00rootroot00000000000000fwupd-1.3.9/plugins/flashrom/README.md000066400000000000000000000012701362775233600174420ustar00rootroot00000000000000Flashrom ======== 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. Vendor ID Security ------------------ The vendor ID is set from the BIOS vendor, for example `DMI:Google` fwupd-1.3.9/plugins/flashrom/example/000077500000000000000000000000001362775233600176165ustar00rootroot00000000000000fwupd-1.3.9/plugins/flashrom/example/build.sh000077500000000000000000000003241362775233600212530ustar00rootroot00000000000000#!/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.3.9/plugins/flashrom/example/com.Flashrom.Laptop.metainfo.xml000066400000000000000000000026311362775233600257310ustar00rootroot00000000000000 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.3.9/plugins/flashrom/example/random-tool000077500000000000000000000000501362775233600217720ustar00rootroot00000000000000#!/bin/sh echo "hello from the sandbox" fwupd-1.3.9/plugins/flashrom/example/startup.sh000077500000000000000000000003021362775233600216520ustar00rootroot00000000000000#!/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.3.9/plugins/flashrom/flashrom.quirk000066400000000000000000000002461362775233600210550ustar00rootroot00000000000000# Purism [HwId=a0ce5085-2dea-5086-ae72-45810a186ad0] DeviceId=librem15v3 # Libretrend [HwId=52b68c34-6b31-5ecc-8a5c-de37e666ccd5] DeviceId=LT1000 VersionFormat=quad fwupd-1.3.9/plugins/flashrom/fu-plugin-flashrom.c000066400000000000000000000200251362775233600220450ustar00rootroot00000000000000/* -*- 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 "fu-hash.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)); } 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); const gchar *dmi_vendor; g_autoptr(GPtrArray) devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); dmi_vendor = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BIOS_VENDOR); 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_set_protocol (dev, "org.flashrom"); 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); if (dmi_vendor != NULL) { g_autofree gchar *vendor_id = g_strdup_printf ("DMI:%s", dmi_vendor); fu_device_set_vendor_id (FU_DEVICE (dev), vendor_id); } 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 (FWUPD_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.3.9/plugins/flashrom/meson.build000066400000000000000000000010671362775233600203310ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], dependencies : [ plugin_deps, libflashrom, ], ) fwupd-1.3.9/plugins/fresco-pd/000077500000000000000000000000001362775233600162325ustar00rootroot00000000000000fwupd-1.3.9/plugins/fresco-pd/README.md000066400000000000000000000013261362775233600175130ustar00rootroot00000000000000Fresco PD Support ================= Introduction ------------ This plugin is used to update Power Devlivery devices by Fresco. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an unspecifed binary format. This plugin supports the following protocol ID: * com.frescologic.pd GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1D5C&PID_7102&REV_0001` * `USB\VID_1D5C&PID_7102` * `USB\VID_1D5C` These devices also use custom GUID values, e.g. * `USB\VID_1D5C&PID_7102&CID_01` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x1D5C` fwupd-1.3.9/plugins/fresco-pd/fresco-pd.quirk000066400000000000000000000001631362775233600211710ustar00rootroot00000000000000# FL7102 [Guid=USB\VID_1D5C&PID_7102] Plugin = fresco_pd # FL7112 [Guid=USB\VID_1D5C&PID_7112] Plugin = fresco_pd fwupd-1.3.9/plugins/fresco-pd/fu-fresco-pd-common.c000066400000000000000000000006721362775233600221630ustar00rootroot00000000000000/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fresco-pd-common.h" gchar * fu_fresco_pd_version_from_buf (const guint8 ver[4]) { if (ver[3] == 1 || ver[3] == 2) return g_strdup_printf ("%u.%u.%u.%u", ver[0], ver[1], ver[2], ver[3]); return g_strdup_printf ("%u.%u.%u.%u", ver[3], ver[1], ver[2], ver[0]); } fwupd-1.3.9/plugins/fresco-pd/fu-fresco-pd-common.h000066400000000000000000000003631362775233600221650ustar00rootroot00000000000000/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" gchar *fu_fresco_pd_version_from_buf (const guint8 ver[4]); fwupd-1.3.9/plugins/fresco-pd/fu-fresco-pd-device.c000066400000000000000000000310751362775233600221330ustar00rootroot00000000000000/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fresco-pd-common.h" #include "fu-fresco-pd-device.h" #include "fu-fresco-pd-firmware.h" struct _FuFrescoPdDevice { FuUsbDevice parent_instance; guint8 customer_id; }; G_DEFINE_TYPE (FuFrescoPdDevice, fu_fresco_pd_device, FU_TYPE_USB_DEVICE) static void fu_fresco_pd_device_to_string (FuDevice *device, guint idt, GString *str) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE (device); fu_common_string_append_ku (str, idt, "CustomerID", self->customer_id); } static gboolean fu_fresco_pd_device_transfer_read (FuFrescoPdDevice *self, guint16 offset, guint8 *buf, guint16 bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_length = 0; g_return_val_if_fail (buf != NULL, FALSE); g_return_val_if_fail (bufsz != 0, FALSE); /* to device */ if (g_getenv ("FWUPD_FRESCO_PD_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "read", buf, bufsz); 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, 0x40, 0x0, offset, buf, bufsz, &actual_length, 5000, NULL, error)) { g_prefix_error (error, "failed to read from offset 0x%x: ", offset); return FALSE; } if (bufsz != actual_length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "read 0x%x bytes of 0x%x", (guint) actual_length, bufsz); return FALSE; } /* success */ return TRUE; } static gboolean fu_fresco_pd_device_transfer_write (FuFrescoPdDevice *self, guint16 offset, guint8 *buf, guint16 bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_length = 0; g_return_val_if_fail (buf != NULL, FALSE); g_return_val_if_fail (bufsz != 0, FALSE); /* to device */ if (g_getenv ("FWUPD_FRESCO_PD_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "write", buf, bufsz); 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, 0x41, 0x0, offset, buf, bufsz, &actual_length, 5000, NULL, error)) { g_prefix_error (error, "failed to write offset 0x%x: ", offset); return FALSE; } if (bufsz != actual_length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "wrote 0x%x bytes of 0x%x", (guint) actual_length, bufsz); return FALSE; } /* success */ return TRUE; } static gboolean fu_fresco_pd_device_read_byte (FuFrescoPdDevice *self, guint16 offset, guint8 *buf, GError **error) { return fu_fresco_pd_device_transfer_read (self, offset, buf, 1, error); } static gboolean fu_fresco_pd_device_write_byte (FuFrescoPdDevice *self, guint16 offset, guint8 buf, GError **error) { return fu_fresco_pd_device_transfer_write (self, offset, &buf, 1, error); } static gboolean fu_fresco_pd_device_set_byte (FuFrescoPdDevice *self, guint16 offset, guint8 val, GError **error) { guint8 buf = 0x0; if (!fu_fresco_pd_device_read_byte (self, offset, &buf, error)) return FALSE; if (buf == val) return TRUE; return fu_fresco_pd_device_write_byte (self, offset, val, error); } static gboolean fu_fresco_pd_device_and_byte (FuFrescoPdDevice *self, guint16 offset, guint8 val, GError **error) { guint8 buf = 0xff; if (!fu_fresco_pd_device_read_byte (self, offset, &buf, error)) return FALSE; buf &= val; return fu_fresco_pd_device_write_byte (self, offset, buf, error); } static gboolean fu_fresco_pd_device_or_byte (FuFrescoPdDevice *self, guint16 offset, guint8 val, GError **error) { guint8 buf; if (!fu_fresco_pd_device_read_byte (self, offset, &buf, error)) return FALSE; buf |= val; return fu_fresco_pd_device_write_byte (self, offset, buf, error); } static gboolean fu_fresco_pd_device_setup (FuDevice *device, GError **error) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE (device); FuUsbDevice *usb_device = FU_USB_DEVICE (device); guint8 ver[4] = { 0x0 }; g_autofree gchar *instance_id = NULL; g_autofree gchar *version = NULL; /* read existing device version */ for (guint i = 0; i < 4; i++) { if (!fu_fresco_pd_device_transfer_read (self, 0x3000 + i, &ver[i], 1, error)) { g_prefix_error (error, "failed to read device version [%u]: ", i); return FALSE; } } version = fu_fresco_pd_version_from_buf (ver); fu_device_set_version (FU_DEVICE (self), version, FWUPD_VERSION_FORMAT_QUAD); /* get customer ID */ self->customer_id = ver[1]; instance_id = g_strdup_printf ("USB\\VID_%04X&PID_%04X&CID_%02X", fu_usb_device_get_vid (usb_device), fu_usb_device_get_pid (usb_device), self->customer_id); fu_device_add_instance_id (device, instance_id); /* success */ return TRUE; } static FuFirmware * fu_fresco_pd_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE (device); guint8 customer_id; g_autoptr(FuFirmware) firmware = fu_fresco_pd_firmware_new (); /* check size */ if (g_bytes_get_size (fw) < fu_device_get_firmware_size_min (device)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too small, got 0x%x, expected >= 0x%x", (guint) g_bytes_get_size (fw), (guint) fu_device_get_firmware_size_min (device)); return NULL; } /* check firmware is suitable */ fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; customer_id = fu_fresco_pd_firmware_get_customer_id (FU_FRESCO_PD_FIRMWARE (firmware)); if (customer_id != self->customer_id) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "device is incompatible with firmware x.%u.x.x", customer_id); return NULL; } return g_steal_pointer (&firmware); } static gboolean fu_fresco_pd_device_panther_reset_device (FuFrescoPdDevice *self, GError **error) { g_autoptr(GError) error_local = NULL; g_debug ("resetting target device"); fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_RESTART); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* ignore when the device reset before completing the transaction */ if (!fu_fresco_pd_device_or_byte (self, 0xA003, 1 << 3, &error_local)) { if (g_error_matches (error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { g_debug ("ignoring %s", error_local->message); return TRUE; } g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to reset device [%i]", error_local->code); return FALSE; } return TRUE; } static gboolean fu_fresco_pd_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE (device); const guint8 *buf; gsize bufsz = 0x0; guint16 begin_addr = 0x6420; guint8 config[3] = { 0x0 }; guint8 start_symbols[2] = { 0x0 }; g_autoptr(GBytes) fw = NULL; /* get default blob, which we know is already bigger than FirmwareMin */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; buf = g_bytes_get_data (fw, &bufsz); /* get start symbols, and be slightly paranoid */ if (!fu_memcpy_safe (start_symbols, sizeof(start_symbols), 0x0, /* dst */ buf, bufsz, 0x4000, /* src */ sizeof(start_symbols), error)) return FALSE; /* 0xA001 = b'0 * 0x6C00 = b'0 * 0x6C04 = 0x08 */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY); g_debug ("disable MCU, and enable mtp write"); if (!fu_fresco_pd_device_and_byte (self, 0xa001, ~(1 << 2), error)) { g_prefix_error (error, "failed to disable MCU bit 2: "); return FALSE; } if (!fu_fresco_pd_device_and_byte (self, 0x6c00, ~(1 << 1), error)) { g_prefix_error (error, "failed to disable MCU bit 1: "); return FALSE; } if (!fu_fresco_pd_device_write_byte (self, 0x6c04, 0x08, error)) { g_prefix_error (error, "failed to disable MCU: "); return FALSE; } /* fill safe code in the boot code */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint16 i = 0; i < 0x400; i += 3) { for (guint j = 0; j < 3; j++) { if (!fu_fresco_pd_device_read_byte (self, begin_addr + i + j, &config[j], error)) { g_prefix_error (error, "failed to read config byte %u: ", j); return FALSE; } } if (config[0] == start_symbols[0] && config[1] == start_symbols[1]) { begin_addr = 0x6420 + i; break; } if (config[0] == 0 && config[1] == 0 && config[2] == 0) break; } g_debug ("begin_addr: 0x%04x", begin_addr); for (guint16 i = begin_addr + 3; i < begin_addr + 0x400; i += 3) { for (guint j = 0; j < 3; j++) { if (!fu_fresco_pd_device_read_byte (self, i + j, &config[j], error)) { g_prefix_error (error, "failed to read config byte %u: ", j); return FALSE; } } if (config[0] == 0x74 && config[1] == 0x06 && config[2] != 0x22) { if (!fu_fresco_pd_device_write_byte (self, i + 2, 0x22, error)) return FALSE; } else if (config[0] == 0x6c && config[1] == 0x00 && config[2] != 0x01) { if (!fu_fresco_pd_device_write_byte (self, i + 2, 0x01, error)) return FALSE; } else if (config[0] == 0x00 && config[1] == 0x00 && config[2] != 0x00) break; } /* copy buf offset [0 - 0x3FFFF] to mmio address [0x2000 - 0x5FFF] */ g_debug ("fill firmware body"); for (guint16 byte_index = 0; byte_index < 0x4000; byte_index++) { if (!fu_fresco_pd_device_set_byte (self, byte_index + 0x2000, buf[byte_index], error)) return FALSE; fu_device_set_progress_full (device, (gsize) byte_index, 0x4000); } /* write file buf 0x4200 ~ 0x4205, 6 bytes to internal address 0x6600 ~ 0x6605 * write file buf 0x4210 ~ 0x4215, 6 bytes to internal address 0x6610 ~ 0x6615 * write file buf 0x4220 ~ 0x4225, 6 bytes to internal address 0x6620 ~ 0x6625 * write file buf 0x4230, 1 byte, to internal address 0x6630 */ g_debug ("update customize data"); for (guint16 byte_index = 0; byte_index < 6; byte_index++) { if (!fu_fresco_pd_device_set_byte (self, 0x6600 + byte_index, buf[0x4200 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte (self, 0x6610 + byte_index, buf[0x4210 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte (self, 0x6620 + byte_index, buf[0x4220 + byte_index], error)) return FALSE; } if (!fu_fresco_pd_device_set_byte (self, 0x6630, buf[0x4230], error)) return FALSE; /* overwrite firmware file's boot code area (0x4020 ~ 0x41ff) to the area on the device marked by begin_addr * example: if the begin_addr = 0x6420, then copy file buf [0x4020 ~ 0x41ff] to device offset[0x6420 ~ 0x65ff] */ g_debug ("write boot configuration area"); for (guint16 byte_index = 0; byte_index < 0x1e0; byte_index += 3) { if (!fu_fresco_pd_device_set_byte (self, begin_addr + byte_index + 0, buf[0x4020 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte (self, begin_addr + byte_index + 1, buf[0x4021 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte (self, begin_addr + byte_index + 2, buf[0x4022 + byte_index], error)) return FALSE; } /* reset the device */ return fu_fresco_pd_device_panther_reset_device (self, error); } static void fu_fresco_pd_device_init (FuFrescoPdDevice *self) { fu_device_add_icon (FU_DEVICE (self), "audio-card"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_protocol (FU_DEVICE (self), "com.frescologic.pd"); fu_device_set_install_duration (FU_DEVICE (self), 15); fu_device_set_remove_delay (FU_DEVICE (self), 20000); fu_device_set_firmware_size (FU_DEVICE (self), 0x4400); } static void fu_fresco_pd_device_class_init (FuFrescoPdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->to_string = fu_fresco_pd_device_to_string; klass_device->setup = fu_fresco_pd_device_setup; klass_device->write_firmware = fu_fresco_pd_device_write_firmware; klass_device->prepare_firmware = fu_fresco_pd_device_prepare_firmware; } fwupd-1.3.9/plugins/fresco-pd/fu-fresco-pd-device.h000066400000000000000000000005711362775233600221350ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_FRESCO_PD_DEVICE (fu_fresco_pd_device_get_type ()) G_DECLARE_FINAL_TYPE (FuFrescoPdDevice, fu_fresco_pd_device, FU, FRESCO_PD_DEVICE, FuUsbDevice) struct _FuFrescoPdDeviceClass { FuUsbDeviceClass parent_class; }; fwupd-1.3.9/plugins/fresco-pd/fu-fresco-pd-firmware.c000066400000000000000000000040171362775233600225040ustar00rootroot00000000000000/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fresco-pd-common.h" #include "fu-fresco-pd-firmware.h" struct _FuFrescoPdFirmware { FuFirmwareClass parent_instance; guint8 customer_id; }; G_DEFINE_TYPE (FuFrescoPdFirmware, fu_fresco_pd_firmware, FU_TYPE_FIRMWARE) guint8 fu_fresco_pd_firmware_get_customer_id (FuFrescoPdFirmware *self) { return self->customer_id; } static void fu_fresco_pd_firmware_to_string (FuFirmware *firmware, guint idt, GString *str) { FuFrescoPdFirmware *self = FU_FRESCO_PD_FIRMWARE (firmware); fu_common_string_append_ku (str, idt, "CustomerID", self->customer_id); } static gboolean fu_fresco_pd_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuFrescoPdFirmware *self = FU_FRESCO_PD_FIRMWARE (firmware); guint8 ver[4] = { 0x0 }; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data (fw, &bufsz); g_autofree gchar *version = NULL; g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (fw); /* read version block */ if (!fu_memcpy_safe (ver, sizeof(ver), 0x0, /* dst */ buf, bufsz, 0x1000, /* src */ sizeof(ver), error)) return FALSE; /* customer ID is always the 2nd byte */ self->customer_id = ver[1]; /* set version number */ version = fu_fresco_pd_version_from_buf (ver); fu_firmware_image_set_version (img, version); fu_firmware_add_image (firmware, img); return TRUE; } static void fu_fresco_pd_firmware_init (FuFrescoPdFirmware *self) { } static void fu_fresco_pd_firmware_class_init (FuFrescoPdFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->parse = fu_fresco_pd_firmware_parse; klass_firmware->to_string = fu_fresco_pd_firmware_to_string; } FuFirmware * fu_fresco_pd_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_FRESCO_PD_FIRMWARE, NULL)); } fwupd-1.3.9/plugins/fresco-pd/fu-fresco-pd-firmware.h000066400000000000000000000007421362775233600225120ustar00rootroot00000000000000/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_FRESCO_PD_FIRMWARE (fu_fresco_pd_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuFrescoPdFirmware, fu_fresco_pd_firmware, FU, FRESCO_PD_FIRMWARE, FuFirmware) FuFirmware *fu_fresco_pd_firmware_new (void); guint8 fu_fresco_pd_firmware_get_customer_id (FuFrescoPdFirmware *self); fwupd-1.3.9/plugins/fresco-pd/fu-plugin-fresco-pd.c000066400000000000000000000007511362775233600221670ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-fresco-pd-device.h" #include "fu-fresco-pd-firmware.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_FRESCO_PD_DEVICE); fu_plugin_add_firmware_gtype (plugin, "fresco-pd", FU_TYPE_FRESCO_PD_FIRMWARE); } fwupd-1.3.9/plugins/fresco-pd/lsusb.txt000066400000000000000000000047141362775233600201310ustar00rootroot00000000000000Bus 003 Device 002: ID 1d5c:7102 Fresco Logic Generic Billboard Device Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.01 bDeviceClass 17 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x1d5c Fresco Logic idProduct 0x7102 bcdDevice 1.00 iManufacturer 1 Fresco Logic, Inc iProduct 2 Generic Billboard Device iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0012 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 0mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 0 bInterfaceClass 17 bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 Binary Object Store Descriptor: bLength 5 bDescriptorType 15 wTotalLength 0x0050 bNumDeviceCaps 3 USB 2.0 Extension Device Capability: bLength 7 bDescriptorType 16 bDevCapabilityType 2 bmAttributes 0x00000006 BESL Link Power Management (LPM) Supported Container ID Device Capability: bLength 20 bDescriptorType 16 bDevCapabilityType 4 bReserved 0 ContainerID {00000000-0000-0000-0000-000000000000} Billboard Capability: bLength 48 bDescriptorType 16 bDevCapabilityType 13 iAddtionalInfoURL 4 www.frescologic.com bNumberOfAlternateModes 1 bPreferredAlternateMode 0 VCONN Power 0 1W bmConfigured 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 bcdVersion 1.10 bAdditionalFailureInfo 0 bReserved 0 Alternate Modes supported by Device Container: Alternate Mode 0 : Alternate Mode configuration successful wSVID[0] 0x0000 bAlternateMode[0] 0 iAlternateModeString[0] 0 Device Status: 0x0001 Self Powered fwupd-1.3.9/plugins/fresco-pd/meson.build000066400000000000000000000010721362775233600203740ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginFrescoPd"'] install_data(['fresco-pd.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_fresco_pd', fu_hash, sources : [ 'fu-plugin-fresco-pd.c', 'fu-fresco-pd-common.c', 'fu-fresco-pd-device.c', 'fu-fresco-pd-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/jabra/000077500000000000000000000000001362775233600154275ustar00rootroot00000000000000fwupd-1.3.9/plugins/jabra/README.md000066400000000000000000000012531362775233600167070ustar00rootroot00000000000000Jabra Support ============= Introduction ------------ This plugin is used to detach the Jabra device to DFU mode. GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0B0E&PID_0412` Quirk use --------- This plugin uses the following plugin-specific quirks: | Quirk | Description | fwupd version | |---------------|----------------------------------------------|---------------| |`JabraMagic` | Two magic bytes sent to detach into DFU mode.|1.3.3 | Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x0A12` fwupd-1.3.9/plugins/jabra/fu-jabra-device.c000066400000000000000000000107751362775233600205310ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-firmware-common.h" #include "fu-jabra-device.h" struct _FuJabraDevice { FuUsbDevice parent_instance; gchar *magic; }; G_DEFINE_TYPE (FuJabraDevice, fu_jabra_device, FU_TYPE_USB_DEVICE) static void fu_jabra_device_to_string (FuDevice *device, guint idt, GString *str) { FuJabraDevice *self = FU_JABRA_DEVICE (device); fu_common_string_append_kv (str, idt, "Magic", self->magic); } 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; } /* slightly weirdly, this magic turns the device into appIDLE, so we * need the DFU plugin to further detach us into dfuIDLE */ static gboolean fu_jabra_device_prepare (FuDevice *device, FwupdInstallFlags flags, GError **error) { FuJabraDevice *self = FU_JABRA_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); guint8 adr = 0x00; guint8 rep = 0x00; guint8 iface_hid; guint8 buf[33] = { 0x00 }; g_autoptr(GError) error_local = NULL; /* parse string and create magic packet */ rep = fu_firmware_strparse_uint8 (self->magic + 0); adr = fu_firmware_strparse_uint8 (self->magic + 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_local)) { g_debug ("whilst sending magic: %s, ignoring", error_local->message); } /* wait for device to re-appear and be added to the dfu plugin */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_jabra_device_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuJabraDevice *self = FU_JABRA_DEVICE (device); if (g_strcmp0 (key, "JabraMagic") == 0) { if (value != NULL && strlen (value) == 4) { self->magic = g_strdup (value); return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unsupported jabra quirk format"); return FALSE; } /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_jabra_device_init (FuJabraDevice *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_remove_delay (FU_DEVICE (self), 20000); /* 10+10s! */ } static void fu_jabra_device_finalize (GObject *object) { FuJabraDevice *self = FU_JABRA_DEVICE (object); g_free (self->magic); G_OBJECT_CLASS (fu_jabra_device_parent_class)->finalize (object); } static void fu_jabra_device_class_init (FuJabraDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); object_class->finalize = fu_jabra_device_finalize; klass_device->to_string = fu_jabra_device_to_string; klass_device->prepare = fu_jabra_device_prepare; klass_device->set_quirk_kv = fu_jabra_device_set_quirk_kv; } fwupd-1.3.9/plugins/jabra/fu-jabra-device.h000066400000000000000000000004421362775233600205240ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_JABRA_DEVICE (fu_jabra_device_get_type ()) G_DECLARE_FINAL_TYPE (FuJabraDevice, fu_jabra_device, FU, JABRA_DEVICE, FuUsbDevice) fwupd-1.3.9/plugins/jabra/fu-plugin-jabra.c000066400000000000000000000030261362775233600205570ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-jabra-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_JABRA_DEVICE); } /* slightly weirdly, this takes us from appIDLE back into the actual * runtime mode where the device actually works */ gboolean fu_plugin_update_cleanup (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *device, GError **error) { GUsbDevice *usb_device; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* check for a property on the *dfu* FuDevice, which is also why we * can't just rely on using FuDevice->cleanup() */ if (!fu_device_has_custom_flag (device, "attach-extra-reset")) return TRUE; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; g_debug ("performing extra reset into firmware mode"); usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); 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; } /* wait for device to re-appear */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } fwupd-1.3.9/plugins/jabra/jabra.quirk000066400000000000000000000010431362775233600175610ustar00rootroot00000000000000# Jabra 410 [runtime] [DeviceInstanceId=USB\VID_0B0E&PID_0412] Plugin = jabra JabraMagic = 0201 CounterpartGuid = USB\VID_0B0E&PID_0411 # Jabra 510 [runtime] [DeviceInstanceId=USB\VID_0B0E&PID_0420] Plugin = jabra JabraMagic = 0201 CounterpartGuid = USB\VID_0B0E&PID_0421 # Jabra 710 [runtime] [DeviceInstanceId=USB\VID_0B0E&PID_2475] Plugin = jabra JabraMagic = 0508 CounterpartGuid = USB\VID_0B0E&PID_0982 # Jabra 810 [runtime] [DeviceInstanceId=USB\VID_0B0E&PID_2456] Plugin = jabra JabraMagic = 0508 CounterpartGuid = USB\VID_0B0E&PID_0971 fwupd-1.3.9/plugins/jabra/meson.build000066400000000000000000000007531362775233600175760ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginJabra"'] install_data(['jabra.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_jabra', fu_hash, sources : [ 'fu-plugin-jabra.c', 'fu-jabra-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/logind/000077500000000000000000000000001362775233600156245ustar00rootroot00000000000000fwupd-1.3.9/plugins/logind/README.md000066400000000000000000000004361362775233600171060ustar00rootroot00000000000000logind Support ============== Introduction ------------ This plugin is used to ensure that the machine does not enter a low power mode when updates are being performed. Vendor ID Security ------------------ This protocol does not create a device and thus requires no vendor ID set. fwupd-1.3.9/plugins/logind/fu-plugin-logind.c000066400000000000000000000064001362775233600211500ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-plugin-vfuncs.h" #include "fu-hash.h" struct FuPluginData { GDBusProxy *logind_proxy; gint logind_fd; }; 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->logind_fd != 0) g_close (data->logind_fd, NULL); if (data->logind_proxy != NULL) g_object_unref (data->logind_proxy); } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); data->logind_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", NULL, error); if (data->logind_proxy == NULL) { g_prefix_error (error, "failed to connect to logind: "); return FALSE; } if (g_dbus_proxy_get_name_owner (data->logind_proxy) == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no owner for %s", g_dbus_proxy_get_name (data->logind_proxy)); return FALSE; } return TRUE; } gboolean fu_plugin_update_prepare (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *device, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autoptr(GError) error_local = NULL; g_autoptr(GUnixFDList) out_fd_list = NULL; g_autoptr(GVariant) res = NULL; const gchar *what = "shutdown:sleep:idle:handle-power-key:handle-suspend-key:" "handle-hibernate-key:handle-lid-switch"; /* already inhibited */ if (data->logind_fd != 0) return TRUE; /* not yet connected */ if (data->logind_proxy == NULL) { g_warning ("no logind connection to use"); return TRUE; } /* block shutdown and idle */ res = g_dbus_proxy_call_with_unix_fd_list_sync (data->logind_proxy, "Inhibit", g_variant_new ("(ssss)", what, PACKAGE_NAME, "Firmware Update in Progress", "block"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, /* fd_list */ &out_fd_list, NULL, /* GCancellable */ &error_local); if (res == NULL) { g_warning ("failed to Inhibit using logind: %s", error_local->message); return TRUE; } /* keep fd as cookie */ if (g_unix_fd_list_get_length (out_fd_list) != 1) { g_warning ("invalid response from logind"); return TRUE; } data->logind_fd = g_unix_fd_list_get (out_fd_list, 0, NULL); g_debug ("opened logind fd %i", data->logind_fd); return TRUE; } gboolean fu_plugin_update_cleanup (FuPlugin *plugin, FwupdInstallFlags flags, FuDevice *device, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); if (data->logind_fd == 0) return TRUE; g_debug ("closed logind fd %i", data->logind_fd); if (!g_close (data->logind_fd, error)) return FALSE; data->logind_fd = 0; return TRUE; } fwupd-1.3.9/plugins/logind/meson.build000066400000000000000000000005741362775233600177740ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginLogind"'] shared_module('fu_plugin_logind', fu_hash, sources : [ 'fu-plugin-logind.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/logitech-hidpp/000077500000000000000000000000001362775233600172505ustar00rootroot00000000000000fwupd-1.3.9/plugins/logitech-hidpp/README.md000066400000000000000000000033461362775233600205350ustar00rootroot00000000000000Logitech HID Support ================ Introduction ------------ This plugin can flash the firmware on Logitech Unifying dongles, both the Nordic (U0007) device and the Texas Instruments (U0008) version. This plugin will not work with the different "Nano" dongle (U0010) as it does not use the Unifying protocol. Some bootloader protocol information was taken from the Mousejack[1] project, specifically logitech-usb-restore.py and unifying.py. Other documentation was supplied by Logitech. Additional constants were taken from the Solaar[2] project. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in a vendor-specific format that appears to be a subset of the Intel HEX format. This plugin supports the following protocol IDs: * com.logitech.unifying * com.logitech.unifyingsigned GUID Generation --------------- These devices use the standard USB DeviceInstanceId values when in DFU mode: * `USB\VID_046D&PID_AAAA&REV_0001` * `USB\VID_046D&PID_AAAA` * `USB\VID_046D` When in runtime mode, the HID raw DeviceInstanceId values are used: * `HIDRAW\VEN_046D&DEV_C52B` * `HIDRAW\VEN_046D` Vendor ID Security ------------------ The vendor ID is set from the vendor ID, in this instance set to `USB:0x046D` in bootloader and `HIDRAW:0x046D` in runtime mode. Design Notes ------------ When a dongle is detected in bootloader mode we detach the hidraw driver from the kernel and use raw control transfers. This ensures that we don't accidentally corrupt the uploading firmware. For application firmware we use hidraw which means the hardware keeps working while probing, and also allows us to detect paired devices. [1] https://www.mousejack.com/ [2] https://pwr-Solaar.github.io/Solaar/ fwupd-1.3.9/plugins/logitech-hidpp/data/000077500000000000000000000000001362775233600201615ustar00rootroot00000000000000fwupd-1.3.9/plugins/logitech-hidpp/data/dump.csv.gz000066400000000000000000003104571362775233600222740ustar00rootroot00000000000000ɺIXdump.csv]n]S,7-GDA18I^>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.3.9/plugins/logitech-hidpp/data/dump.tdc000066400000000000000000025021401362775233600216260ustar00rootroot00000000000000TPDC?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.3.9/plugins/logitech-hidpp/data/lsusb-U0007-bootloader.txt000066400000000000000000000034761362775233600247250ustar00rootroot00000000000000Bus 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.3.9/plugins/logitech-hidpp/data/lsusb-U0007.txt000066400000000000000000000101341362775233600225620ustar00rootroot00000000000000Bus 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.3.9/plugins/logitech-hidpp/data/lsusb-U0008-bootloader-old.txt000066400000000000000000000057551362775233600255040ustar00rootroot00000000000000 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.3.9/plugins/logitech-hidpp/data/lsusb-U0008-bootloader.txt000066400000000000000000000057551362775233600247300ustar00rootroot00000000000000 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.3.9/plugins/logitech-hidpp/data/lsusb-U0008-old.txt000066400000000000000000000426521362775233600233510ustar00rootroot00000000000000 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.3.9/plugins/logitech-hidpp/data/lsusb-U0008.txt000066400000000000000000000426511362775233600225740ustar00rootroot00000000000000Bus 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.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-nordic.c000066400000000000000000000213301362775233600263270ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-bootloader-nordic.h" struct _FuLogitechHidPpBootloaderNordic { FuLogitechHidPpBootloader parent_instance; }; G_DEFINE_TYPE (FuLogitechHidPpBootloaderNordic, fu_logitech_hidpp_bootloader_nordic, FU_TYPE_UNIFYING_BOOTLOADER) static gchar * fu_logitech_hidpp_bootloader_nordic_get_hw_platform_id (FuLogitechHidPpBootloader *self, GError **error) { g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_GET_HW_PLATFORM_ID; if (!fu_logitech_hidpp_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_logitech_hidpp_bootloader_nordic_get_fw_version (FuLogitechHidPpBootloader *self, GError **error) { guint16 micro; g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_GET_FW_VERSION; if (!fu_logitech_hidpp_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to get firmware version: "); return NULL; } /* RRRxx.yy_Bzzzz * 012345678901234*/ micro = (guint16) fu_logitech_hidpp_buffer_read_uint8 ((const gchar *) req->data + 10) << 8; micro += fu_logitech_hidpp_buffer_read_uint8 ((const gchar *) req->data + 12); return fu_logitech_hidpp_format_version ("RQR", fu_logitech_hidpp_buffer_read_uint8 ((const gchar *) req->data + 3), fu_logitech_hidpp_buffer_read_uint8 ((const gchar *) req->data + 6), micro); } static gboolean fu_logitech_hidpp_bootloader_nordic_setup (FuLogitechHidPpBootloader *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_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_bootloader_nordic_write_signature (FuLogitechHidPpBootloader *self, guint16 addr, guint8 len, const guint8 *data, GError **error) { g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = 0xC0; req->addr = addr; req->len = len; memcpy (req->data, data, req->len); if (!fu_logitech_hidpp_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_logitech_hidpp_bootloader_nordic_write (FuLogitechHidPpBootloader *self, guint16 addr, guint8 len, const guint8 *data, GError **error) { g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_bootloader_nordic_erase (FuLogitechHidPpBootloader *self, guint16 addr, GError **error) { g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_ERASE_PAGE; req->addr = addr; req->len = 0x01; if (!fu_logitech_hidpp_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_logitech_hidpp_bootloader_nordic_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuLogitechHidPpBootloader *self = FU_UNIFYING_BOOTLOADER (device); const FuLogitechHidPpBootloaderRequest *payload; guint16 addr; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) reqs = NULL; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* erase firmware pages up to the bootloader */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); for (addr = fu_logitech_hidpp_bootloader_get_addr_lo (self); addr < fu_logitech_hidpp_bootloader_get_addr_hi (self); addr += fu_logitech_hidpp_bootloader_get_blocksize (self)) { if (!fu_logitech_hidpp_bootloader_nordic_erase (self, addr, error)) return FALSE; } /* transfer payload */ reqs = fu_logitech_hidpp_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_logitech_hidpp_bootloader_nordic_write_signature (self, payload->addr, payload->len, payload->data, error); } else { res = fu_logitech_hidpp_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_logitech_hidpp_bootloader_nordic_write (self, payload->addr + 1, payload->len - 1, payload->data + 1, error)) return FALSE; if (!fu_logitech_hidpp_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_logitech_hidpp_bootloader_nordic_class_init (FuLogitechHidPpBootloaderNordicClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuLogitechHidPpBootloaderClass *klass_device_bootloader = FU_UNIFYING_BOOTLOADER_CLASS (klass); klass_device->write_firmware = fu_logitech_hidpp_bootloader_nordic_write_firmware; klass_device_bootloader->setup = fu_logitech_hidpp_bootloader_nordic_setup; } static void fu_logitech_hidpp_bootloader_nordic_init (FuLogitechHidPpBootloaderNordic *self) { } fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-nordic.h000066400000000000000000000006361362775233600263420ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-logitech-hidpp-bootloader.h" #define FU_TYPE_UNIFYING_BOOTLOADER_NORDIC (fu_logitech_hidpp_bootloader_nordic_get_type ()) G_DECLARE_FINAL_TYPE (FuLogitechHidPpBootloaderNordic, fu_logitech_hidpp_bootloader_nordic, FU, UNIFYING_BOOTLOADER_NORDIC, FuLogitechHidPpBootloader) fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-texas.c000066400000000000000000000164661362775233600262130ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-bootloader-texas.h" struct _FuLogitechHidPpBootloaderTexas { FuLogitechHidPpBootloader parent_instance; }; G_DEFINE_TYPE (FuLogitechHidPpBootloaderTexas, fu_logitech_hidpp_bootloader_texas, FU_TYPE_UNIFYING_BOOTLOADER) static gboolean fu_logitech_hidpp_bootloader_texas_erase_all (FuLogitechHidPpBootloader *self, GError **error) { g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM; req->len = 0x01; /* magic number */ req->data[0] = 0x00; /* magic number */ if (!fu_logitech_hidpp_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to erase all pages: "); return FALSE; } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_texas_compute_and_test_crc (FuLogitechHidPpBootloader *self, GError **error) { g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM; req->len = 0x01; /* magic number */ req->data[0] = 0x03; /* magic number */ if (!fu_logitech_hidpp_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_logitech_hidpp_bootloader_texas_flash_ram_buffer (FuLogitechHidPpBootloader *self, guint16 addr, GError **error) { g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_bootloader_texas_clear_ram_buffer (FuLogitechHidPpBootloader *self, GError **error) { g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_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_logitech_hidpp_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to clear ram buffer @%04x: ", req->addr); return FALSE; } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_texas_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuLogitechHidPpBootloader *self = FU_UNIFYING_BOOTLOADER (device); const FuLogitechHidPpBootloaderRequest *payload; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) reqs = NULL; g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new (); /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* transfer payload */ reqs = fu_logitech_hidpp_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_logitech_hidpp_bootloader_texas_erase_all (self, error)) return FALSE; /* set existing RAM buffer to 0xff's */ if (!fu_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_bootloader_texas_setup (FuLogitechHidPpBootloader *self, GError **error) { fu_device_set_version (FU_DEVICE (self), "RQR24.00_B0000", FWUPD_VERSION_FORMAT_PLAIN); return TRUE; } static void fu_logitech_hidpp_bootloader_texas_class_init (FuLogitechHidPpBootloaderTexasClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuLogitechHidPpBootloaderClass *klass_device_bootloader = FU_UNIFYING_BOOTLOADER_CLASS (klass); klass_device->write_firmware = fu_logitech_hidpp_bootloader_texas_write_firmware; klass_device_bootloader->setup = fu_logitech_hidpp_bootloader_texas_setup; } static void fu_logitech_hidpp_bootloader_texas_init (FuLogitechHidPpBootloaderTexas *self) { } fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-texas.h000066400000000000000000000006311362775233600262030ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-logitech-hidpp-bootloader.h" #define FU_TYPE_UNIFYING_BOOTLOADER_TEXAS (fu_logitech_hidpp_bootloader_texas_get_type ()) G_DECLARE_FINAL_TYPE (FuLogitechHidPpBootloaderTexas, fu_logitech_hidpp_bootloader_texas, FU, UNIFYING_BOOTLOADER_TEXAS, FuLogitechHidPpBootloader) fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader.c000066400000000000000000000341521362775233600250610ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-firmware-common.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-bootloader.h" #include "fu-logitech-hidpp-hidpp.h" typedef struct { guint16 flash_addr_lo; guint16 flash_addr_hi; guint16 flash_blocksize; } FuLogitechHidPpBootloaderPrivate; #define FU_UNIFYING_DEVICE_EP1 0x81 #define FU_UNIFYING_DEVICE_EP3 0x83 G_DEFINE_TYPE_WITH_PRIVATE (FuLogitechHidPpBootloader, fu_logitech_hidpp_bootloader, FU_TYPE_USB_DEVICE) #define GET_PRIVATE(o) (fu_logitech_hidpp_bootloader_get_instance_private (o)) static void fu_logitech_hidpp_bootloader_to_string (FuDevice *device, guint idt, GString *str) { FuLogitechHidPpBootloader *self = FU_UNIFYING_BOOTLOADER (device); FuLogitechHidPpBootloaderPrivate *priv = GET_PRIVATE (self); fu_common_string_append_kx (str, idt, "FlashAddrHigh", priv->flash_addr_hi); fu_common_string_append_kx (str, idt, "FlashAddrLow", priv->flash_addr_lo); fu_common_string_append_kx (str, idt, "FlashBlockSize", priv->flash_blocksize); } FuLogitechHidPpBootloaderRequest * fu_logitech_hidpp_bootloader_request_new (void) { FuLogitechHidPpBootloaderRequest *req = g_new0 (FuLogitechHidPpBootloaderRequest, 1); return req; } GPtrArray * fu_logitech_hidpp_bootloader_parse_requests (FuLogitechHidPpBootloader *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(FuLogitechHidPpBootloaderRequest) payload = NULL; guint8 rec_type = 0x00; guint16 offset = 0x0000; gboolean exit = FALSE; /* skip empty lines */ tmp = lines[i]; if (strlen (tmp) < 5) continue; payload = fu_logitech_hidpp_bootloader_request_new (); payload->len = fu_logitech_hidpp_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 = fu_firmware_strparse_uint16 (tmp + 0x03); payload->cmd = FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER; rec_type = fu_logitech_hidpp_buffer_read_uint8 (tmp + 0x07); switch (rec_type) { case 0x00: /* data */ break; case 0x01: /* EOF */ exit = TRUE; break; case 0x03: /* start segment address */ /* this is used to specify the start address, it is doesn't matter in this context so we can safely ignore it */ continue; case 0x04: /* extended linear address */ offset = fu_firmware_strparse_uint16 (tmp + 0x09); if (offset != 0x0000) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "extended linear addresses with offset different from 0 are not supported"); return NULL; } continue; case 0x05: /* start linear address */ /* this is used to specify the start address, it is doesn't matter in this context so we can safely ignore it */ continue; case 0xFD: /* custom - vendor */ /* record type of 0xFD indicates signature data */ payload->cmd = FU_UNIFYING_BOOTLOADER_CMD_WRITE_SIGNATURE; break; default: g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "intel hex file record type %02x not supported", rec_type); return NULL; } if (exit) break; /* 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_logitech_hidpp_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_logitech_hidpp_bootloader_get_addr_hi (self)) { g_debug ("skipping write @ %04x", payload->addr); continue; } /* skip the header */ if (payload->addr < fu_logitech_hidpp_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_logitech_hidpp_bootloader_get_addr_lo (FuLogitechHidPpBootloader *self) { FuLogitechHidPpBootloaderPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UNIFYING_BOOTLOADER (self), 0x0000); return priv->flash_addr_lo; } guint16 fu_logitech_hidpp_bootloader_get_addr_hi (FuLogitechHidPpBootloader *self) { FuLogitechHidPpBootloaderPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UNIFYING_BOOTLOADER (self), 0x0000); return priv->flash_addr_hi; } guint16 fu_logitech_hidpp_bootloader_get_blocksize (FuLogitechHidPpBootloader *self) { FuLogitechHidPpBootloaderPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (FU_IS_UNIFYING_BOOTLOADER (self), 0x0000); return priv->flash_blocksize; } static gboolean fu_logitech_hidpp_bootloader_attach (FuDevice *device, GError **error) { FuLogitechHidPpBootloader *self = FU_UNIFYING_BOOTLOADER (device); g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new (); req->cmd = FU_UNIFYING_BOOTLOADER_CMD_REBOOT; if (!fu_logitech_hidpp_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_logitech_hidpp_bootloader_set_bl_version (FuLogitechHidPpBootloader *self, GError **error) { guint16 build; g_autofree gchar *version = NULL; g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new (); /* call into hardware */ req->cmd = FU_UNIFYING_BOOTLOADER_CMD_GET_BL_VERSION; if (!fu_logitech_hidpp_bootloader_request (self, req, error)) { g_prefix_error (error, "failed to get firmware version: "); return FALSE; } /* BOTxx.yy_Bzzzz * 012345678901234 */ build = (guint16) fu_logitech_hidpp_buffer_read_uint8 ((const gchar *) req->data + 10) << 8; build += fu_logitech_hidpp_buffer_read_uint8 ((const gchar *) req->data + 12); version = fu_logitech_hidpp_format_version ("BOT", fu_logitech_hidpp_buffer_read_uint8 ((const gchar *) req->data + 3), fu_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_bootloader_setup (FuDevice *device, GError **error) { FuLogitechHidPpBootloaderClass *klass = FU_UNIFYING_BOOTLOADER_GET_CLASS (device); FuLogitechHidPpBootloader *self = FU_UNIFYING_BOOTLOADER (device); FuLogitechHidPpBootloaderPrivate *priv = GET_PRIVATE (self); g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new (); /* get memory map */ req->cmd = FU_UNIFYING_BOOTLOADER_CMD_GET_MEMINFO; if (!fu_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_bootloader_request (FuLogitechHidPpBootloader *self, FuLogitechHidPpBootloaderRequest *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; if (!fu_memcpy_safe (buf_request, sizeof(buf_request), 0x04, /* dst */ req->data, sizeof(req->data), 0x0, /* src */ sizeof(req->data), error)) return FALSE; /* 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, FU_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_logitech_hidpp_bootloader_init (FuLogitechHidPpBootloader *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); 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_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_remove_delay (FU_DEVICE (self), FU_UNIFYING_DEVICE_TIMEOUT_MS); } static void fu_logitech_hidpp_bootloader_class_init (FuLogitechHidPpBootloaderClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->to_string = fu_logitech_hidpp_bootloader_to_string; klass_device->attach = fu_logitech_hidpp_bootloader_attach; klass_device->setup = fu_logitech_hidpp_bootloader_setup; klass_usb_device->open = fu_logitech_hidpp_bootloader_open; klass_usb_device->close = fu_logitech_hidpp_bootloader_close; } fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader.h000066400000000000000000000057771362775233600251010ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-usb-device.h" #define FU_TYPE_UNIFYING_BOOTLOADER (fu_logitech_hidpp_bootloader_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuLogitechHidPpBootloader, fu_logitech_hidpp_bootloader, FU, UNIFYING_BOOTLOADER, FuUsbDevice) struct _FuLogitechHidPpBootloaderClass { FuUsbDeviceClass parent_class; gboolean (*setup) (FuLogitechHidPpBootloader *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 } FuLogitechHidPpBootloaderCmd; /* packet to and from device */ typedef struct __attribute__((packed)) { guint8 cmd; guint16 addr; guint8 len; guint8 data[28]; } FuLogitechHidPpBootloaderRequest; FuLogitechHidPpBootloaderRequest *fu_logitech_hidpp_bootloader_request_new (void); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuLogitechHidPpBootloaderRequest, g_free); #pragma clang diagnostic pop GPtrArray *fu_logitech_hidpp_bootloader_parse_requests (FuLogitechHidPpBootloader *self, GBytes *fw, GError **error); gboolean fu_logitech_hidpp_bootloader_request (FuLogitechHidPpBootloader *self, FuLogitechHidPpBootloaderRequest *req, GError **error); guint16 fu_logitech_hidpp_bootloader_get_addr_lo (FuLogitechHidPpBootloader *self); guint16 fu_logitech_hidpp_bootloader_get_addr_hi (FuLogitechHidPpBootloader *self); guint16 fu_logitech_hidpp_bootloader_get_blocksize (FuLogitechHidPpBootloader *self); fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-common.c000066400000000000000000000017521362775233600242170ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-logitech-hidpp-common.h" guint8 fu_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_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.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-common.h000066400000000000000000000015141362775233600242200ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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 30000 guint8 fu_logitech_hidpp_buffer_read_uint8 (const gchar *str); guint16 fu_logitech_hidpp_buffer_read_uint16 (const gchar *str); gchar *fu_logitech_hidpp_format_version (const gchar *name, guint8 major, guint8 minor, guint16 build); fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp-msg.c000066400000000000000000000273661362775233600246300ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-hidpp-hidpp.h" #include "fu-logitech-hidpp-hidpp-msg.h" FuLogitechHidPpHidppMsg * fu_logitech_hidpp_msg_new (void) { return g_new0 (FuLogitechHidPpHidppMsg, 1); } const gchar * fu_logitech_hidpp_msg_dev_id_to_string (FuLogitechHidPpHidppMsg *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_logitech_hidpp_msg_rpt_id_to_string (FuLogitechHidPpHidppMsg *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_logitech_hidpp_msg_get_payload_length (FuLogitechHidPpHidppMsg *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_logitech_hidpp_msg_fcn_id_to_string (FuLogitechHidPpHidppMsg *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_logitech_hidpp_msg_sub_id_to_string (FuLogitechHidPpHidppMsg *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_logitech_hidpp_msg_is_reply (FuLogitechHidPpHidppMsg *msg1, FuLogitechHidPpHidppMsg *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_logitech_hidpp_msg_is_error (FuLogitechHidPpHidppMsg *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_logitech_hidpp_msg_copy (FuLogitechHidPpHidppMsg *msg_dst, const FuLogitechHidPpHidppMsg *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_logitech_hidpp_msg_is_hidpp10_compat (FuLogitechHidPpHidppMsg *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_logitech_hidpp_msg_verify_swid (FuLogitechHidPpHidppMsg *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.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp-msg.h000066400000000000000000000040031362775233600246140ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include 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 } FuLogitechHidPpHidppMsgFlags; 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; } FuLogitechHidPpHidppMsg; /* 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(FuLogitechHidPpHidppMsg, g_free); #pragma clang diagnostic pop FuLogitechHidPpHidppMsg *fu_logitech_hidpp_msg_new (void); void fu_logitech_hidpp_msg_copy (FuLogitechHidPpHidppMsg *msg_dst, const FuLogitechHidPpHidppMsg *msg_src); gsize fu_logitech_hidpp_msg_get_payload_length (FuLogitechHidPpHidppMsg *msg); gboolean fu_logitech_hidpp_msg_is_reply (FuLogitechHidPpHidppMsg *msg1, FuLogitechHidPpHidppMsg *msg2); gboolean fu_logitech_hidpp_msg_is_hidpp10_compat (FuLogitechHidPpHidppMsg *msg); gboolean fu_logitech_hidpp_msg_is_error (FuLogitechHidPpHidppMsg *msg, GError **error); gboolean fu_logitech_hidpp_msg_verify_swid (FuLogitechHidPpHidppMsg *msg); const gchar *fu_logitech_hidpp_msg_dev_id_to_string (FuLogitechHidPpHidppMsg *msg); const gchar *fu_logitech_hidpp_msg_rpt_id_to_string (FuLogitechHidPpHidppMsg *msg); const gchar *fu_logitech_hidpp_msg_sub_id_to_string (FuLogitechHidPpHidppMsg *msg); const gchar *fu_logitech_hidpp_msg_fcn_id_to_string (FuLogitechHidPpHidppMsg *msg); fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp.c000066400000000000000000000143571362775233600240400ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-hidpp.h" static gchar * fu_logitech_hidpp_msg_to_string (FuLogitechHidPpHidppMsg *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_logitech_hidpp_msg_rpt_id_to_string (msg)); tmp = fu_logitech_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_logitech_hidpp_msg_sub_id_to_string (msg)); g_string_append_printf (str, "function-id: %02x [%s]\n", msg->function_id, fu_logitech_hidpp_msg_fcn_id_to_string (msg)); if (!fu_logitech_hidpp_msg_is_error (msg, &error)) { g_string_append_printf (str, "error: %s\n", error->message); } return g_string_free (str, FALSE); } gboolean fu_logitech_hidpp_send (FuIOChannel *io_channel, FuLogitechHidPpHidppMsg *msg, guint timeout, GError **error) { gsize len = fu_logitech_hidpp_msg_get_payload_length (msg); FuIOChannelFlags write_flags = FU_IO_CHANNEL_FLAG_FLUSH_INPUT; /* 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_logitech_hidpp_msg_to_string (msg); fu_common_dump_raw (G_LOG_DOMAIN, "host->device", (guint8 *) msg, len); g_print ("%s", str); } /* only use blocking IO when it will be a short timeout for reboot */ if ((msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT) == 0) write_flags |= FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO; /* HID */ if (!fu_io_channel_write_raw (io_channel, (guint8 *) msg, len, timeout, write_flags, error)) { g_prefix_error (error, "failed to send: "); return FALSE; } /* success */ return TRUE; } gboolean fu_logitech_hidpp_receive (FuIOChannel *io_channel, FuLogitechHidPpHidppMsg *msg, guint timeout, GError **error) { gsize read_size = 0; if (!fu_io_channel_read_raw (io_channel, (guint8 *) msg, sizeof(FuLogitechHidPpHidppMsg), &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_logitech_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_logitech_hidpp_msg_get_payload_length (msg)); return FALSE; } /* detailed debugging */ if (g_getenv ("FWUPD_UNIFYING_VERBOSE") != NULL) { g_autofree gchar *str = fu_logitech_hidpp_msg_to_string (msg); g_print ("%s", str); } /* success */ return TRUE; } gboolean fu_logitech_hidpp_transfer (FuIOChannel *io_channel, FuLogitechHidPpHidppMsg *msg, GError **error) { guint timeout = FU_UNIFYING_DEVICE_TIMEOUT_MS; guint ignore_cnt = 0; g_autoptr(FuLogitechHidPpHidppMsg) msg_tmp = fu_logitech_hidpp_msg_new (); /* increase timeout for some operations */ if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT) timeout *= 10; /* send request */ if (!fu_logitech_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_logitech_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_logitech_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_logitech_hidpp_msg_is_error (msg_tmp, error)) return FALSE; /* is valid reply */ if (fu_logitech_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_logitech_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_logitech_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_logitech_hidpp_msg_copy (msg, msg_tmp); return TRUE; } fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp.h000066400000000000000000000125531362775233600240410ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-io-channel.h" /* * 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-logitech-hidpp-hidpp-msg.h" gboolean fu_logitech_hidpp_send (FuIOChannel *self, FuLogitechHidPpHidppMsg *msg, guint timeout, GError **error); gboolean fu_logitech_hidpp_receive (FuIOChannel *self, FuLogitechHidPpHidppMsg *msg, guint timeout, GError **error); gboolean fu_logitech_hidpp_transfer (FuIOChannel *self, FuLogitechHidPpHidppMsg *msg, GError **error); fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-peripheral.c000066400000000000000000000751711362775233600250700ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-peripheral.h" #include "fu-logitech-hidpp-hidpp.h" struct _FuLogitechHidPpPeripheral { 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 FuLogitechHidPpHidppMap */ }; typedef struct { guint8 idx; guint16 feature; } FuLogitechHidPpHidppMap; G_DEFINE_TYPE (FuLogitechHidPpPeripheral, fu_logitech_hidpp_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 } FuLogitechHidPpPeripheralKind; static const gchar * fu_logitech_hidpp_peripheral_get_icon (FuLogitechHidPpPeripheralKind 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_logitech_hidpp_peripheral_get_summary (FuLogitechHidPpPeripheralKind 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_logitech_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_logitech_hidpp_peripheral_refresh_updatable (FuLogitechHidPpPeripheral *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_logitech_hidpp_peripheral_ping (FuLogitechHidPpPeripheral *self, GError **error) { gdouble version; g_autoptr(GError) error_local = NULL; g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_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_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_peripheral_close (FuDevice *device, GError **error) { FuLogitechHidPpPeripheral *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_logitech_hidpp_peripheral_poll (FuDevice *device, GError **error) { FuLogitechHidPpPeripheral *self = FU_UNIFYING_PERIPHERAL (device); const guint timeout = 1; /* ms */ g_autoptr(GError) error_local = NULL; g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_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_logitech_hidpp_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_logitech_hidpp_peripheral_open (FuDevice *device, GError **error) { FuLogitechHidPpPeripheral *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_logitech_hidpp_map_to_string (FuLogitechHidPpHidppMap *map, guint idt, GString *str) { g_autofree gchar *title = g_strdup_printf ("Feature%02x", map->idx); g_autofree gchar *tmp = g_strdup_printf ("%s [0x%04x]", fu_logitech_hidpp_feature_to_string (map->feature), map->feature); fu_common_string_append_kv (str, idt, title, tmp); } static void fu_logitech_hidpp_peripheral_to_string (FuDevice *device, guint idt, GString *str) { FuLogitechHidPpPeripheral *self = FU_UNIFYING_PERIPHERAL (device); fu_common_string_append_ku (str, idt, "HidppVersion", self->hidpp_version); fu_common_string_append_kx (str, idt, "HidppId", self->hidpp_id); fu_common_string_append_ku (str, idt, "BatteryLevel", self->battery_level); fu_common_string_append_kb (str, idt, "IsUpdatable", self->is_updatable); fu_common_string_append_kb (str, idt, "IsActive", self->is_active); for (guint i = 0; i < self->feature_index->len; i++) { FuLogitechHidPpHidppMap *map = g_ptr_array_index (self->feature_index, i); fu_logitech_hidpp_map_to_string (map, idt, str); } } static guint8 fu_logitech_hidpp_peripheral_feature_get_idx (FuLogitechHidPpPeripheral *self, guint16 feature) { for (guint i = 0; i < self->feature_index->len; i++) { FuLogitechHidPpHidppMap *map = g_ptr_array_index (self->feature_index, i); if (map->feature == feature) return map->idx; } return 0x00; } static gboolean fu_logitech_hidpp_peripheral_fetch_firmware_info (FuLogitechHidPpPeripheral *self, GError **error) { guint8 idx; guint8 entity_count; g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new (); /* get the feature index */ idx = fu_logitech_hidpp_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_logitech_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_logitech_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_logitech_hidpp_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_logitech_hidpp_peripheral_fetch_battery_level (FuLogitechHidPpPeripheral *self, GError **error) { /* try using HID++2.0 */ if (self->hidpp_version >= 2.f) { guint8 idx; idx = fu_logitech_hidpp_peripheral_feature_get_idx (self, HIDPP_FEATURE_BATTERY_LEVEL_STATUS); if (idx != 0x00) { g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_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(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_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_logitech_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_logitech_hidpp_feature_search (FuDevice *device, guint16 feature, GError **error) { FuLogitechHidPpPeripheral *self = FU_UNIFYING_PERIPHERAL (device); FuLogitechHidPpHidppMap *map; g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to get idx for feature %s [0x%04x]: ", fu_logitech_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_logitech_hidpp_feature_to_string (feature), feature); return FALSE; } /* add to map */ map = g_new0 (FuLogitechHidPpHidppMap, 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_logitech_hidpp_feature_to_string (feature), feature, map->idx); return TRUE; } static gboolean fu_logitech_hidpp_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_logitech_hidpp_peripheral_setup (FuDevice *device, GError **error) { FuLogitechHidPpPeripheral *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_logitech_hidpp_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) { FuLogitechHidPpHidppMap *map = g_new0 (FuLogitechHidPpHidppMap, 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_logitech_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_logitech_hidpp_peripheral_fetch_firmware_info (self, error)) return FALSE; /* get the battery level */ if (!fu_logitech_hidpp_peripheral_fetch_battery_level (self, error)) return FALSE; /* try using HID++2.0 */ idx = fu_logitech_hidpp_peripheral_feature_get_idx (self, HIDPP_FEATURE_GET_DEVICE_NAME_TYPE); if (idx != 0x00) { const gchar *tmp; g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_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_logitech_hidpp_peripheral_get_summary (msg->data[0]); if (tmp != NULL) fu_device_set_summary (FU_DEVICE (device), tmp); tmp = fu_logitech_hidpp_peripheral_get_icon (msg->data[0]); if (tmp != NULL) fu_device_add_icon (FU_DEVICE (device), tmp); } idx = fu_logitech_hidpp_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_logitech_hidpp_peripheral_feature_get_idx (self, HIDPP_FEATURE_DFU_CONTROL_SIGNED); if (idx != 0x00) { /* check the feature is available */ g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_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); } fu_device_set_protocol (FU_DEVICE (device), "com.logitech.unifyingsigned"); } idx = fu_logitech_hidpp_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_logitech_hidpp_peripheral_refresh_updatable (self); /* poll for pings to track active state */ fu_device_set_poll_interval (device, 30000); return TRUE; } static gboolean fu_logitech_hidpp_peripheral_detach (FuDevice *device, GError **error) { FuLogitechHidPpPeripheral *self = FU_UNIFYING_PERIPHERAL (device); guint8 idx; g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new (); /* this requires user action */ idx = fu_logitech_hidpp_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_logitech_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); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION, "%s needs to be manually restarted to complete the update." "Please unplug and reconnect the device and re-run the update", fu_device_get_name (device)); return FALSE; } /* this can reboot all by itself */ idx = fu_logitech_hidpp_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_logitech_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to put device into DFU mode: "); return FALSE; } return fu_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_peripheral_write_firmware_pkt (FuLogitechHidPpPeripheral *self, guint8 idx, guint8 cmd, const guint8 *data, GError **error) { guint32 packet_cnt; g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_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_logitech_hidpp_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(FuLogitechHidPpHidppMsg) msg2 = fu_logitech_hidpp_msg_new (); msg2->flags = FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_FNCT_ID; if (!fu_logitech_hidpp_receive (self->io_channel, msg2, 15000, error)) return FALSE; if (fu_logitech_hidpp_msg_is_reply (msg, msg2)) { g_autoptr(GError) error2 = NULL; if (!fu_logitech_hidpp_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_logitech_hidpp_peripheral_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuLogitechHidPpPeripheral *self = FU_UNIFYING_PERIPHERAL (device); gsize sz = 0; const guint8 *data; guint8 cmd = 0x04; guint8 idx; g_autoptr(GBytes) fw = NULL; /* if we're in bootloader mode, we should be able to get this feature */ idx = fu_logitech_hidpp_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; } /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) 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_logitech_hidpp_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_logitech_hidpp_peripheral_attach (FuDevice *device, GError **error) { FuLogitechHidPpPeripheral *self = FU_UNIFYING_PERIPHERAL (device); guint8 idx; g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new (); /* if we're in bootloader mode, we should be able to get this feature */ idx = fu_logitech_hidpp_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_logitech_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to restart device: "); return FALSE; } /* reprobe */ if (!fu_logitech_hidpp_peripheral_setup (device, error)) return FALSE; /* success */ return TRUE; } static void fu_logitech_hidpp_peripheral_finalize (GObject *object) { FuLogitechHidPpPeripheral *self = FU_UNIFYING_PERIPHERAL (object); g_ptr_array_unref (self->feature_index); G_OBJECT_CLASS (fu_logitech_hidpp_peripheral_parent_class)->finalize (object); } static void fu_logitech_hidpp_peripheral_class_init (FuLogitechHidPpPeripheralClass *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_logitech_hidpp_peripheral_finalize; klass_device->setup = fu_logitech_hidpp_peripheral_setup; klass_device->open = fu_logitech_hidpp_peripheral_open; klass_device->close = fu_logitech_hidpp_peripheral_close; klass_device->write_firmware = fu_logitech_hidpp_peripheral_write_firmware; klass_device->attach = fu_logitech_hidpp_peripheral_attach; klass_device->detach = fu_logitech_hidpp_peripheral_detach; klass_device->poll = fu_logitech_hidpp_peripheral_poll; klass_device->to_string = fu_logitech_hidpp_peripheral_to_string; klass_device_udev->probe = fu_logitech_hidpp_peripheral_probe; } static void fu_logitech_hidpp_peripheral_init (FuLogitechHidPpPeripheral *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); fu_device_set_protocol (FU_DEVICE (self), "com.logitech.unifying"); /* there are a lot of unifying peripherals, but not all respond * well to opening -- so limit to ones with issued updates */ fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_ONLY_SUPPORTED); } fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-peripheral.h000066400000000000000000000005411362775233600250620ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-udev-device.h" #define FU_TYPE_UNIFYING_PERIPHERAL (fu_logitech_hidpp_peripheral_get_type ()) G_DECLARE_FINAL_TYPE (FuLogitechHidPpPeripheral, fu_logitech_hidpp_peripheral, FU, UNIFYING_PERIPHERAL, FuUdevDevice) fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.c000066400000000000000000000243431362775233600244130ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-runtime.h" #include "fu-logitech-hidpp-hidpp.h" struct _FuLogitechHidPpRuntime { FuUdevDevice parent_instance; guint8 version_bl_major; gboolean signed_firmware; FuIOChannel *io_channel; }; G_DEFINE_TYPE (FuLogitechHidPpRuntime, fu_logitech_hidpp_runtime, FU_TYPE_UDEV_DEVICE) static void fu_logitech_hidpp_runtime_to_string (FuDevice *device, guint idt, GString *str) { FuLogitechHidPpRuntime *self = FU_UNIFYING_RUNTIME (device); fu_common_string_append_kb (str, idt, "SignedFirmware", self->signed_firmware); } static gboolean fu_logitech_hidpp_runtime_enable_notifications (FuLogitechHidPpRuntime *self, GError **error) { g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_hidpp_transfer (self->io_channel, msg, error); } static gboolean fu_logitech_hidpp_runtime_close (FuDevice *device, GError **error) { FuLogitechHidPpRuntime *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_logitech_hidpp_runtime_poll (FuDevice *device, GError **error) { FuLogitechHidPpRuntime *self = FU_UNIFYING_RUNTIME (device); const guint timeout = 1; /* ms */ g_autoptr(GError) error_local = NULL; g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_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_logitech_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_logitech_hidpp_runtime_open (FuDevice *device, GError **error) { FuLogitechHidPpRuntime *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_logitech_hidpp_runtime_probe (FuUdevDevice *device, GError **error) { FuLogitechHidPpRuntime *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_logitech_hidpp_runtime_setup_internal (FuDevice *device, GError **error) { FuLogitechHidPpRuntime *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(FuLogitechHidPpHidppMsg) msg = fu_logitech_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_logitech_hidpp_transfer (self->io_channel, msg, error)) { g_prefix_error (error, "failed to read device config: "); return FALSE; } if (!fu_memcpy_safe (config, sizeof(config), i * 2, /* dst */ msg->data, sizeof(msg->data), 0x1, /* src */ 2, error)) return FALSE; } /* get firmware version */ version_fw = fu_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_runtime_enable_notifications (self, error)) { g_prefix_error (error, "failed to enable notifications: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_hidpp_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_logitech_hidpp_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_logitech_hidpp_runtime_detach (FuDevice *device, GError **error) { FuLogitechHidPpRuntime *self = FU_UNIFYING_RUNTIME (device); g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new (); g_autoptr(GError) error_local = NULL; 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_logitech_hidpp_send (self->io_channel, msg, FU_UNIFYING_DEVICE_TIMEOUT_MS, &error_local)) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_WRITE)) { g_debug ("failed to detach to bootloader: %s", error_local->message); } else { g_prefix_error (&error_local, "failed to detach to bootloader: "); g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_logitech_hidpp_runtime_finalize (GObject *object) { G_OBJECT_CLASS (fu_logitech_hidpp_runtime_parent_class)->finalize (object); } static void fu_logitech_hidpp_runtime_class_init (FuLogitechHidPpRuntimeClass *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_logitech_hidpp_runtime_finalize; klass_device->open = fu_logitech_hidpp_runtime_open; klass_device_udev->probe = fu_logitech_hidpp_runtime_probe; klass_device->setup = fu_logitech_hidpp_runtime_setup; klass_device->close = fu_logitech_hidpp_runtime_close; klass_device->detach = fu_logitech_hidpp_runtime_detach; klass_device->poll = fu_logitech_hidpp_runtime_poll; klass_device->to_string = fu_logitech_hidpp_runtime_to_string; } static void fu_logitech_hidpp_runtime_init (FuLogitechHidPpRuntime *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); fu_device_set_protocol (FU_DEVICE (self), "com.logitech.unifying"); } fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.h000066400000000000000000000005221362775233600244110ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-udev-device.h" #define FU_TYPE_UNIFYING_RUNTIME (fu_logitech_hidpp_runtime_get_type ()) G_DECLARE_FINAL_TYPE (FuLogitechHidPpRuntime, fu_logitech_hidpp_runtime, FU, UNIFYING_RUNTIME, FuUdevDevice) fwupd-1.3.9/plugins/logitech-hidpp/fu-logitech-hidpp-self-test.c000066400000000000000000000016171362775233600246350ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-logitech-hidpp-common.h" static void fu_logitech_hidpp_common (void) { guint8 u8; guint16 u16; g_autofree gchar *ver1 = NULL; u8 = fu_logitech_hidpp_buffer_read_uint8 ("12"); g_assert_cmpint (u8, ==, 0x12); u16 = fu_logitech_hidpp_buffer_read_uint16 ("1234"); g_assert_cmpint (u16, ==, 0x1234); ver1 = fu_logitech_hidpp_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_logitech_hidpp_common); return g_test_run (); } fwupd-1.3.9/plugins/logitech-hidpp/fu-plugin-logitech-hidpp.c000066400000000000000000000022651362775233600242250ustar00rootroot00000000000000/* * Copyright (C) 2016-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-logitech-hidpp-bootloader-nordic.h" #include "fu-logitech-hidpp-bootloader-texas.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-peripheral.h" #include "fu-logitech-hidpp-runtime.h" 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_udev_subsystem (plugin, "hidraw"); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_CONFLICTS, "unifying"); /* register the custom types */ g_type_ensure (FU_TYPE_UNIFYING_BOOTLOADER_NORDIC); g_type_ensure (FU_TYPE_UNIFYING_BOOTLOADER_TEXAS); g_type_ensure (FU_TYPE_UNIFYING_PERIPHERAL); g_type_ensure (FU_TYPE_UNIFYING_RUNTIME); } fwupd-1.3.9/plugins/logitech-hidpp/logitech-hidpp.quirk000066400000000000000000000021341362775233600232250ustar00rootroot00000000000000# Unifying Receiver [DeviceInstanceId=HIDRAW\VEN_046D&DEV_C52B] Plugin = logitech_hidpp GType = FuLogitechHidPpRuntime VendorId=USB:0x046D InstallDuration = 30 # Nordic [DeviceInstanceId=USB\VID_046D&PID_AAAA] Plugin = logitech_hidpp GType = FuLogitechHidPpBootloaderNordic FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 30 # Nordic Pico [DeviceInstanceId=USB\VID_046D&PID_AAAE] Plugin = logitech_hidpp GType = FuLogitechHidPpBootloaderNordic FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 30 # Texas [DeviceInstanceId=USB\VID_046D&PID_AAAC] Plugin = logitech_hidpp GType = FuLogitechHidPpBootloaderTexas FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 18 # Texas Pico [DeviceInstanceId=USB\VID_046D&PID_AAAD] Plugin = logitech_hidpp GType = FuLogitechHidPpBootloaderTexas FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 18 # Possible HID++ v2.0 peripheral device [DeviceInstanceId=HIDRAW\VEN_046D] Plugin = logitech_hidpp GType = FuLogitechHidPpPeripheral fwupd-1.3.9/plugins/logitech-hidpp/meson.build000066400000000000000000000023661362775233600214210ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginLogitechHidPp"'] install_data([ 'logitech-hidpp.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_logitech_hidpp', fu_hash, sources : [ 'fu-plugin-logitech-hidpp.c', 'fu-logitech-hidpp-bootloader.c', 'fu-logitech-hidpp-bootloader-nordic.c', 'fu-logitech-hidpp-bootloader-texas.c', 'fu-logitech-hidpp-common.c', 'fu-logitech-hidpp-hidpp.c', 'fu-logitech-hidpp-hidpp-msg.c', 'fu-logitech-hidpp-peripheral.c', 'fu-logitech-hidpp-runtime.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) if get_option('tests') e = executable( 'logitech-hidpp-self-test', fu_hash, sources : [ 'fu-logitech-hidpp-self-test.c', 'fu-logitech-hidpp-common.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs, ) test('logitech-hidpp-self-test', e) endif fwupd-1.3.9/plugins/meson.build000066400000000000000000000030371362775233600165150ustar00rootroot00000000000000subdir('dfu') subdir('colorhug') subdir('ebitdo') subdir('fastboot') subdir('fresco-pd') subdir('jabra') subdir('steelseries') subdir('dell-dock') subdir('nitrokey') subdir('rts54hid') subdir('rts54hub') subdir('solokey') subdir('synaptics-cxaudio') subdir('synaptics-prometheus') subdir('test') subdir('upower') subdir('wacom-usb') subdir('vli') if get_option('gudev') subdir('ata') subdir('logitech-hidpp') subdir('optionrom') subdir('superio') subdir('synaptics-rmi') subdir('thelio-io') subdir('wacom-raw') endif if get_option('systemd') subdir('logind') endif # depends on dfu subdir('csr') if get_option('plugin_tpm') and get_option('gudev') subdir('tpm') subdir('tpm-eventlog') endif if get_option('plugin_emmc') and get_option('gudev') subdir('emmc') endif if get_option('plugin_nvme') and get_option('gudev') subdir('nvme') endif if get_option('plugin_modem_manager') subdir('modem-manager') endif if get_option('plugin_altos') and get_option('gudev') subdir('altos') endif if get_option('plugin_amt') subdir('amt') endif if get_option('plugin_thunderbolt') and get_option('gudev') 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') and get_option('gudev') subdir('synaptics-mst') endif if get_option('plugin_uefi') subdir('uefi') subdir('uefi-recovery') endif if get_option('plugin_flashrom') subdir('flashrom') endif if get_option('plugin_coreboot') subdir('coreboot') endif fwupd-1.3.9/plugins/modem-manager/000077500000000000000000000000001362775233600170615ustar00rootroot00000000000000fwupd-1.3.9/plugins/modem-manager/README.md000066400000000000000000000027201362775233600203410ustar00rootroot00000000000000ModemManager ============ 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, for example `USB:0x413C` 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.3.9/plugins/modem-manager/fu-mm-device.c000066400000000000000000000626231362775233600215140ustar00rootroot00000000000000/* * 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, guint idt, GString *str) { FuMmDevice *self = FU_MM_DEVICE (device); if (self->port_at != NULL) fu_common_string_append_kv (str, idt, "AtPort", self->port_at); if (self->port_qmi != NULL) fu_common_string_append_kv (str, idt, "QmiPort", 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_PLAIN); 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 gboolean fu_mm_qmi_pdc_archive_iterate_mcfg (FuArchive *archive, const gchar *filename, GBytes *bytes, gpointer user_data, GError **error) { 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 TRUE; 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); return TRUE; } 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 */ if (!fu_archive_iterate (archive, fu_mm_qmi_pdc_archive_iterate_mcfg, &archive_context, error)) return FALSE; 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, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) array = NULL; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* 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_PLAIN); 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.3.9/plugins/modem-manager/fu-mm-device.h000066400000000000000000000032661362775233600215170ustar00rootroot00000000000000/* * 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" #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); #endif /* __FU_MM_DEVICE_H */ fwupd-1.3.9/plugins/modem-manager/fu-mm-utils.c000066400000000000000000000042631362775233600214110ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-udev-device.h" #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.3.9/plugins/modem-manager/fu-mm-utils.h000066400000000000000000000010241362775233600214060ustar00rootroot00000000000000/* * 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 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); #endif /* __FU_MM_UTILS_H */ fwupd-1.3.9/plugins/modem-manager/fu-plugin-modem-manager.c000066400000000000000000000302151362775233600236430ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-plugin-vfuncs.h" #include "fu-hash.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_full (version, MM_REQUIRED_VERSION, FWUPD_VERSION_FORMAT_TRIPLET) < 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; } 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.3.9/plugins/modem-manager/fu-qmi-pdc-updater.c000066400000000000000000000512131362775233600226330ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #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; g_autoptr(GError) error = NULL; 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); if (!fu_memcpy_safe ((guint8 *)chunk->data, chunk_size, 0x0, /* dst */ (const guint8 *)g_bytes_get_data (ctx->blob, NULL), /* src */ g_bytes_get_size (ctx->blob), ctx->offset, chunk_size, &error)) { g_critical ("failed to copy chunk: %s", error->message); } 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 * reset 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.3.9/plugins/modem-manager/fu-qmi-pdc-updater.h000066400000000000000000000015651362775233600226450ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #ifndef __FU_QMI_PDC_UPDATER_H #define __FU_QMI_PDC_UPDATER_H #include #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); #endif /* __FU_QMI_PDC_UPDATER_H */ fwupd-1.3.9/plugins/modem-manager/meson.build000066400000000000000000000011311362775233600212170ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, ], link_with : [ fwupd, fwupdplugin, ], dependencies : [ plugin_deps, libmm_glib, libqmi_glib, ], ) fwupd-1.3.9/plugins/modem-manager/modem-manager.quirk000066400000000000000000000022141362775233600226460ustar00rootroot00000000000000 # 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 # T77W968 [DeviceInstanceId=USB\VID_0489&PID_E0B4] Summary = Foxconn T77w968 LTE modem CounterpartGuid = USB\VID_0489&PID_E0B7 # T77W968 in fastboot mode [DeviceInstanceId=USB\VID_0489&PID_E0B7] Summary = Foxconn T77w968 LTE modem (fastboot) CounterpartGuid = USB\VID_0489&PID_E0B4 # T77W968/eSIM [DeviceInstanceId=USB\VID_0489&PID_E0B5] Summary = Foxconn T77w968/eSIM LTE modem CounterpartGuid = USB\VID_0489&PID_E0B8 # T77W968/eSIM in fastboot mode [DeviceInstanceId=USB\VID_0489&PID_E0B8] Summary = Foxconn T77w968/eSIM LTE modem (fastboot) CounterpartGuid = USB\VID_0489&PID_E0B5 fwupd-1.3.9/plugins/nitrokey/000077500000000000000000000000001362775233600162145ustar00rootroot00000000000000fwupd-1.3.9/plugins/nitrokey/README.md000066400000000000000000000013341362775233600174740ustar00rootroot00000000000000Nitrokey 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x20A0` in runtime mode and `USB:0x03EB` in bootloader mode. fwupd-1.3.9/plugins/nitrokey/fu-nitrokey-common.c000066400000000000000000000015611362775233600221250ustar00rootroot00000000000000/* * 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.3.9/plugins/nitrokey/fu-nitrokey-common.h000066400000000000000000000031641362775233600221330ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include 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; fwupd-1.3.9/plugins/nitrokey/fu-nitrokey-device.c000066400000000000000000000147641362775233600221050ustar00rootroot00000000000000/* * 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 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 */ if (g_getenv ("FWUPD_NITROKEY_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "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; } if (g_getenv ("FWUPD_NITROKEY_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "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; } if (g_getenv ("FWUPD_NITROKEY_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "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; } fwupd-1.3.9/plugins/nitrokey/fu-nitrokey-device.h000066400000000000000000000005711362775233600221010ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #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; }; fwupd-1.3.9/plugins/nitrokey/fu-plugin-nitrokey.c000066400000000000000000000006231362775233600221310ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.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_set_device_gtype (plugin, FU_TYPE_NITROKEY_DEVICE); } fwupd-1.3.9/plugins/nitrokey/fu-self-test.c000066400000000000000000000060311362775233600206760ustar00rootroot00000000000000/* * 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.3.9/plugins/nitrokey/lsusb.txt000066400000000000000000000165571362775233600201230ustar00rootroot00000000000000Bus 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.3.9/plugins/nitrokey/meson.build000066400000000000000000000016421362775233600203610ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupdplugin, ], 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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, valgrind, ], link_with : [ fwupdplugin, ], ) test('nitrokey-self-test', e) endif fwupd-1.3.9/plugins/nitrokey/nitrokey.quirk000066400000000000000000000003321362775233600211330ustar00rootroot00000000000000# 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.3.9/plugins/nvme/000077500000000000000000000000001362775233600153155ustar00rootroot00000000000000fwupd-1.3.9/plugins/nvme/README.md000066400000000000000000000033451362775233600166010ustar00rootroot00000000000000NVMe ==== 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 | Vendor ID Security ------------------ The vendor ID is set from the udev vendor, for example set to `NVME:0x1179` fwupd-1.3.9/plugins/nvme/fu-nvme-common.c000066400000000000000000000123521362775233600203270ustar00rootroot00000000000000/* * 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.3.9/plugins/nvme/fu-nvme-common.h000066400000000000000000000061131362775233600203320ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include 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); fwupd-1.3.9/plugins/nvme/fu-nvme-device.c000066400000000000000000000321651362775233600203020ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #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; guint64 write_block_size; }; G_DEFINE_TYPE (FuNvmeDevice, fu_nvme_device, FU_TYPE_UDEV_DEVICE) static void fu_nvme_device_to_string (FuDevice *device, guint idt, GString *str) { FuNvmeDevice *self = FU_NVME_DEVICE (device); fu_common_string_append_ku (str, idt, "PciDepth", 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 = 0; guint32 err; /* submit admin command */ if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self), NVME_IOCTL_ADMIN_CMD, (guint8 *) cmd, &rc, error)) { g_prefix_error (error, "failed to issue admin command 0x%02x: ", cmd->opcode); 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_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); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); } /* 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_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE (device); g_autoptr(GBytes) fw2 = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; guint64 block_size = self->write_block_size > 0 ? self->write_block_size : 0x1000; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* 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"); fu_device_set_protocol (FU_DEVICE (self), "org.nvmexpress"); fu_udev_device_set_flags (FU_UDEV_DEVICE (self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT); } 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->setup = fu_nvme_device_setup; klass_device->write_firmware = fu_nvme_device_write_firmware; klass_udev_device->probe = fu_nvme_device_probe; } 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.3.9/plugins/nvme/fu-nvme-device.h000066400000000000000000000006151362775233600203020ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #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_from_blob (const guint8 *buf, gsize sz, GError **error); fwupd-1.3.9/plugins/nvme/fu-plugin-nvme.c000066400000000000000000000006331362775233600203340ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-nvme-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_udev_subsystem (plugin, "nvme"); fu_plugin_set_device_gtype (plugin, FU_TYPE_NVME_DEVICE); } fwupd-1.3.9/plugins/nvme/fu-self-test.c000066400000000000000000000050341362775233600200010ustar00rootroot00000000000000/* * 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" 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 = g_build_filename (TESTDATADIR, "TOSHIBA_THNSN5512GPU7.bin", NULL); 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 = g_build_filename (TESTDATADIR, "blobs", NULL); if (!g_file_test (path, G_FILE_TEST_EXISTS)) 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.3.9/plugins/nvme/meson.build000066400000000000000000000021451362775233600174610ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with : [ fwupd, fwupdplugin, ], 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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) test('nvme-self-test', e) endif fwupd-1.3.9/plugins/nvme/nvme.quirk000066400000000000000000000002411362775233600173340ustar00rootroot00000000000000# match all devices with this udev subsystem [DeviceInstanceId=NVME] Plugin = nvme # Phison [DeviceInstanceId=NVME\VEN_1987] Flags = force-align,needs-shutdown fwupd-1.3.9/plugins/nvme/tests/000077500000000000000000000000001362775233600164575ustar00rootroot00000000000000fwupd-1.3.9/plugins/nvme/tests/.gitignore000066400000000000000000000000061362775233600204430ustar00rootroot00000000000000blobs fwupd-1.3.9/plugins/nvme/tests/TOSHIBA_THNSN5512GPU7.bin000066400000000000000000000100001362775233600221630ustar00rootroot00000000000000yy 37RSDEADBEEFTHNSN5512GPU7 TOSHIBA 410557LA _cfD x'<Pfwupd-1.3.9/plugins/optionrom/000077500000000000000000000000001362775233600163765ustar00rootroot00000000000000fwupd-1.3.9/plugins/optionrom/README.md000066400000000000000000000012751362775233600176620ustar00rootroot00000000000000OptionROM 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. Vendor ID Security ------------------ The device is not upgradable and thus requires no vendor ID set. fwupd-1.3.9/plugins/optionrom/fu-optionrom-device.c000066400000000000000000000064701362775233600224440ustar00rootroot00000000000000/* * Copyright (C) 2015-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-rom.h" #include "fu-optionrom-device.h" struct _FuOptionromDevice { FuUdevDevice parent_instance; }; G_DEFINE_TYPE (FuOptionromDevice, fu_optionrom_device, FU_TYPE_UDEV_DEVICE) static gboolean fu_optionrom_device_probe (FuUdevDevice *device, GError **error) { GUdevDevice *udev_device = fu_udev_device_get_dev (device); const gchar *guid = NULL; guid = g_udev_device_get_property (udev_device, "FWUPD_GUID"); if (guid == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no FWUPD_GUID property"); return FALSE; } /* set the physical ID */ if (!fu_udev_device_set_physical_id (device, "pci", error)) return FALSE; return TRUE; } static FuFirmware * fu_optionrom_device_read_firmware (FuDevice *device, GError **error) { FuUdevDevice *udev_device = FU_UDEV_DEVICE (device); g_autofree gchar *guid = NULL; g_autofree gchar *rom_fn = NULL; g_autoptr(FuRom) rom = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GFile) file = NULL; /* open the file */ rom_fn = g_build_filename (fu_udev_device_get_sysfs_path (udev_device), "rom", NULL); if (rom_fn == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Unable to read firmware from device"); return NULL; } 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 NULL; /* 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 */ /* update guid */ guid = g_strdup_printf ("PCI\\VEN_%04X&DEV_%04X", fu_rom_get_vendor (rom), fu_rom_get_model (rom)); fu_device_add_guid (device, guid); /* get new data */ fw = fu_rom_get_data (rom); return fu_firmware_new_from_bytes (fw); } static void fu_optionrom_device_init (FuOptionromDevice *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon (FU_DEVICE (self), "audio-card"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_udev_device_set_flags (FU_UDEV_DEVICE (self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT); } static void fu_optionrom_device_finalize (GObject *object) { G_OBJECT_CLASS (fu_optionrom_device_parent_class)->finalize (object); } static void fu_optionrom_device_class_init (FuOptionromDeviceClass *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_optionrom_device_finalize; klass_device->read_firmware = fu_optionrom_device_read_firmware; klass_udev_device->probe = fu_optionrom_device_probe; } fwupd-1.3.9/plugins/optionrom/fu-optionrom-device.h000066400000000000000000000004671362775233600224510ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_OPTIONROM_DEVICE (fu_optionrom_device_get_type ()) G_DECLARE_FINAL_TYPE (FuOptionromDevice, fu_optionrom_device, FU, OPTIONROM_DEVICE, FuUdevDevice) fwupd-1.3.9/plugins/optionrom/fu-plugin-optionrom.c000066400000000000000000000007511362775233600224770ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-optionrom-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_udev_subsystem (plugin, "pci"); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_CONFLICTS, "udev"); fu_plugin_set_device_gtype (plugin, FU_TYPE_OPTIONROM_DEVICE); } fwupd-1.3.9/plugins/optionrom/fu-rom-tool.c000066400000000000000000000133141362775233600207240ustar00rootroot00000000000000/* * 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) { 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)); 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.3.9/plugins/optionrom/fu-rom.c000066400000000000000000000522441362775233600177560ustar00rootroot00000000000000/* * 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; FuRomKind kind; gchar *version; 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_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); /* 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(GInputStream) stream = NULL; g_return_val_if_fail (FU_IS_ROM (self), FALSE); /* open file */ stream = G_INPUT_STREAM (g_file_read (file, cancellable, &error_local)); if (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")) { g_autoptr(GFileOutputStream) output_stream = NULL; 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 (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 (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; } 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; } GBytes * fu_rom_get_data (FuRom *self) { GByteArray *buf = g_byte_array_new (); for (guint i = 0; i < self->hdrs->len; i++) { FuRomPciHeader *hdr = g_ptr_array_index (self->hdrs, i); g_byte_array_append (buf, hdr->rom_data, hdr->rom_len); } return g_byte_array_free_to_bytes (buf); } 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->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_ptr_array_unref (self->hdrs); 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.3.9/plugins/optionrom/fu-rom.h000066400000000000000000000023571362775233600177630ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); GBytes *fu_rom_get_data (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); fwupd-1.3.9/plugins/optionrom/fu-self-test.c000066400000000000000000000066471362775233600210750ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-plugin-private.h" #include "fu-rom.h" static void fu_rom_func (void) { struct { FuRomKind kind; const gchar *fn; const gchar *ver; guint16 vendor; guint16 model; } data[] = { { FU_ROM_KIND_ATI, "Asus.9800PRO.256.unknown.031114.rom", "008.015.041.001", 0x1002, 0x4e48 }, { FU_ROM_KIND_ATI, /* atombios */ "Asus.R9290X.4096.131014.rom", "015.039.000.006.003515", 0x1002, 0x67b0 }, { FU_ROM_KIND_ATI, /* atombios, with serial */ "Asus.HD7970.3072.121018.rom", "015.023.000.002.000000", 0x1002, 0x6798 }, { FU_ROM_KIND_NVIDIA, "Asus.GTX480.1536.100406_1.rom", "70.00.1A.00.02", 0x10de, 0x06c0 }, { FU_ROM_KIND_NVIDIA, /* nvgi */ "Asus.GTX980.4096.140905.rom", "84.04.1F.00.02", 0x10de, 0x13c0 }, { FU_ROM_KIND_NVIDIA, /* nvgi, with serial */ "Asus.TitanBlack.6144.140212.rom", "80.80.4E.00.01", 0x10de, 0x100c }, { FU_ROM_KIND_UNKNOWN, 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 = g_build_filename (TESTDATADIR, data[i].fn, NULL); if (!g_file_test (filename, G_FILE_TEST_EXISTS)) 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_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 = g_build_filename (TESTDATADIR, "roms", NULL); if (!g_file_test (path, G_FILE_TEST_EXISTS)) 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_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.3.9/plugins/optionrom/fuzzing.md000066400000000000000000000001661362775233600204170ustar00rootroot00000000000000CC=afl-gcc ./configure --disable-shared AFL_HARDEN=1 make afl-fuzz -m 300 -i fuzzing -o findings ./fu-rom-tool rom @@ fwupd-1.3.9/plugins/optionrom/fuzzing/000077500000000000000000000000001362775233600200725ustar00rootroot00000000000000fwupd-1.3.9/plugins/optionrom/fuzzing/header-data-payload.rom000066400000000000000000000010001362775233600243660ustar00rootroot00000000000000U K7hdr-data-payload PCIRVersion 1.0\fwupd-1.3.9/plugins/optionrom/fuzzing/header-no-data.rom000066400000000000000000000010001362775233600233510ustar00rootroot00000000000000U K7hdr-no-data fwupd-1.3.9/plugins/optionrom/fuzzing/ifr-header-data-payload.rom000066400000000000000000000012001362775233600251460ustar00rootroot00000000000000NVGIU K7ifr-hdr-data-payld PCIRVersion 1.0\fwupd-1.3.9/plugins/optionrom/fuzzing/naked-ifr.rom000066400000000000000000000002001362775233600224410ustar00rootroot00000000000000NVGIfwupd-1.3.9/plugins/optionrom/meson.build000066400000000000000000000026311362775233600205420ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginOptionrom"'] install_data(['optionrom.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_optionrom', fu_hash, sources : [ 'fu-plugin-optionrom.c', 'fu-optionrom-device.c', 'fu-rom.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) executable( 'fu-rom-tool', fu_hash, sources : [ 'fu-rom-tool.c', 'fu-rom.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, libjsonglib, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs, ) if get_option('tests') cargs += '-DPLUGINBUILDDIR="' + meson.current_build_dir() + '"' testdatadir = join_paths(meson.current_source_dir(), 'tests') cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'optionrom-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-rom.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) test('optionrom-self-test', e) endif fwupd-1.3.9/plugins/optionrom/optionrom.quirk000066400000000000000000000001271362775233600215010ustar00rootroot00000000000000# match all devices with this udev subsystem [DeviceInstanceId=PCI] Plugin = optionrom fwupd-1.3.9/plugins/optionrom/tests/000077500000000000000000000000001362775233600175405ustar00rootroot00000000000000fwupd-1.3.9/plugins/optionrom/tests/get-nonfree.sh000077500000000000000000000007251362775233600223140ustar00rootroot00000000000000rm *.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.3.9/plugins/redfish/000077500000000000000000000000001362775233600157745ustar00rootroot00000000000000fwupd-1.3.9/plugins/redfish/README.md000066400000000000000000000043111362775233600172520ustar00rootroot00000000000000Redfish 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. Vendor ID Security ------------------ No vendor ID is set as there is no vendor field in the schema. 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.3.9/plugins/redfish/fu-plugin-redfish.c000066400000000000000000000067711362775233600215030ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.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_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.3.9/plugins/redfish/fu-redfish-client.c000066400000000000000000000571251362775233600214620ustar00rootroot00000000000000/* * 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_set_protocol (dev, "org.dmtf.redfish"); 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.3.9/plugins/redfish/fu-redfish-client.h000066400000000000000000000024231362775233600214560ustar00rootroot00000000000000 /* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); fwupd-1.3.9/plugins/redfish/fu-redfish-common.c000066400000000000000000000024641362775233600214700ustar00rootroot00000000000000/* * 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.3.9/plugins/redfish/fu-redfish-common.h000066400000000000000000000027051362775233600214730ustar00rootroot00000000000000 /* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include /* 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); fwupd-1.3.9/plugins/redfish/fu-self-test.c000066400000000000000000000016071362775233600204620ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-plugin-private.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.3.9/plugins/redfish/meson.build000066400000000000000000000020061362775233600201340ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], 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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, efivar, libjsonglib, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) test('redfish-self-test', e) endif fwupd-1.3.9/plugins/redfish/redfish.conf000066400000000000000000000004561362775233600202740ustar00rootroot00000000000000[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.3.9/plugins/rts54hid/000077500000000000000000000000001362775233600160165ustar00rootroot00000000000000fwupd-1.3.9/plugins/rts54hid/README.md000066400000000000000000000030241362775233600172740ustar00rootroot00000000000000Realtek 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x0BDA` 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.3.9/plugins/rts54hid/fu-plugin-rts54hid.c000066400000000000000000000007371362775233600215430ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.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_set_device_gtype (plugin, FU_TYPE_RTS54HID_DEVICE); /* register the custom types */ g_type_ensure (FU_TYPE_RTS54HID_MODULE); } fwupd-1.3.9/plugins/rts54hid/fu-rts54hid-common.h000066400000000000000000000030461362775233600215360ustar00rootroot00000000000000/* * 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_RTS54FU_HID_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.3.9/plugins/rts54hid/fu-rts54hid-device.c000066400000000000000000000253001362775233600214750ustar00rootroot00000000000000/* * 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, guint idt, GString *str) { FuRts54HidDevice *self = FU_RTS54HID_DEVICE (device); fu_common_string_append_kb (str, idt, "FwAuth", self->fw_auth); fu_common_string_append_kb (str, idt, "DualBank", 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, FU_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, FU_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_RTS54FU_HID_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_RTS54FU_HID_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_RTS54FU_HID_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)); if (!fu_memcpy_safe (buf, sizeof(buf), FU_RTS54HID_CMD_BUFFER_OFFSET_DATA, /* dst */ data, data_sz, 0x0, /* src */ data_sz, error)) return FALSE; 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_RTS54FU_HID_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_RTS54FU_HID_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_RTS54FU_HID_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, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuRts54HidDevice *self = FU_RTS54HID_DEVICE (device); g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* 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) { fu_device_set_protocol (FU_DEVICE (self), "com.realtek.rts54"); } 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; } fwupd-1.3.9/plugins/rts54hid/fu-rts54hid-device.h000066400000000000000000000011151362775233600215000ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_RTS54HID_DEVICE (fu_rts54hid_device_get_type ()) G_DECLARE_FINAL_TYPE (FuRts54HidDevice, fu_rts54hid_device, FU, RTS54HID_DEVICE, FuUsbDevice) 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); fwupd-1.3.9/plugins/rts54hid/fu-rts54hid-module.c000066400000000000000000000166611362775233600215350ustar00rootroot00000000000000/* * 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, guint idt, GString *str) { FuRts54HidModule *self = FU_RTS54HID_MODULE (module); fu_common_string_append_kx (str, idt, "SlaveAddr", self->slave_addr); fu_common_string_append_kx (str, idt, "I2cSpeed", self->i2c_speed); fu_common_string_append_kx (str, idt, "RegisterAddrLen", 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_RTS54FU_HID_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)); if (!fu_memcpy_safe (buf, sizeof(buf), FU_RTS54HID_CMD_BUFFER_OFFSET_DATA, /* dst */ data, data_sz, 0x0, /* src */ data_sz, error)) return FALSE; 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_RTS54FU_HID_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; return fu_memcpy_safe (data, data_sz, 0x0, buf, sizeof(buf), FU_RTS54HID_CMD_BUFFER_OFFSET_DATA, data_sz, error); } 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, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuRts54HidModule *self = FU_RTS54HID_MODULE (module); g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* 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.3.9/plugins/rts54hid/fu-rts54hid-module.h000066400000000000000000000005411362775233600215300ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #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); fwupd-1.3.9/plugins/rts54hid/meson.build000066400000000000000000000010351362775233600201570ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/rts54hid/rts54hid.quirk000066400000000000000000000006351362775233600205450ustar00rootroot00000000000000# 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.3.9/plugins/rts54hub/000077500000000000000000000000001362775233600160305ustar00rootroot00000000000000fwupd-1.3.9/plugins/rts54hub/README.md000066400000000000000000000015461362775233600173150ustar00rootroot00000000000000Realtek 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x0BDA` fwupd-1.3.9/plugins/rts54hub/data/000077500000000000000000000000001362775233600167415ustar00rootroot00000000000000fwupd-1.3.9/plugins/rts54hub/data/lsusb.txt000066400000000000000000000111441362775233600206330ustar00rootroot00000000000000Bus 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.3.9/plugins/rts54hub/fu-plugin-rts54hub.c000066400000000000000000000005631362775233600215640ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-rts54hub-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_RTS54HUB_DEVICE); } fwupd-1.3.9/plugins/rts54hub/fu-rts54hub-device.c000066400000000000000000000310671362775233600215300ustar00rootroot00000000000000/* * 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, guint idt, GString *str) { FuRts54HubDevice *self = FU_RTS54HUB_DEVICE (device); fu_common_string_append_kb (str, idt, "FwAuth", self->fw_auth); fu_common_string_append_kb (str, idt, "DualBank", self->dual_bank); fu_common_string_append_kb (str, idt, "RunningOnFlash", 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, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuRts54HubDevice *self = FU_RTS54HUB_DEVICE (device); g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* 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 FuFirmware * 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 fu_firmware_new_from_bytes (fw); } static void fu_rts54hub_device_init (FuRts54HubDevice *self) { fu_device_set_protocol (FU_DEVICE (self), "com.realtek.rts54"); 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; } fwupd-1.3.9/plugins/rts54hub/fu-rts54hub-device.h000066400000000000000000000004611362775233600215270ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_RTS54HUB_DEVICE (fu_rts54hub_device_get_type ()) G_DECLARE_FINAL_TYPE (FuRts54HubDevice, fu_rts54hub_device, FU, RTS54HUB_DEVICE, FuUsbDevice) fwupd-1.3.9/plugins/rts54hub/meson.build000066400000000000000000000010011362775233600201620ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/rts54hub/rts54hub.quirk000066400000000000000000000002131362775233600205610ustar00rootroot00000000000000# RTS5423 Development Board [DeviceInstanceId=USB\VID_0BDA&PID_5423] Plugin = rts54hub FirmwareSizeMin = 0x20000 FirmwareSizeMax = 0x40000 fwupd-1.3.9/plugins/solokey/000077500000000000000000000000001362775233600160355ustar00rootroot00000000000000fwupd-1.3.9/plugins/solokey/README.md000066400000000000000000000014201362775233600173110ustar00rootroot00000000000000SoloKey Support =============== Introduction ------------ The SoloKey Secure and Hacker is an affordable open source FIDO2 security key. All hardware supports the U2F HID flashing protocol. The Hacker version is not supported and the existing DFU update procedure should be used. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in a JSON wrapped, base-64, Intel hex file. This plugin supports the following protocol ID: * com.solokeys GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0483&PID_A2CA&REV_0001` * `USB\VID_0483&PID_A2CA` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x0483` fwupd-1.3.9/plugins/solokey/data/000077500000000000000000000000001362775233600167465ustar00rootroot00000000000000fwupd-1.3.9/plugins/solokey/data/bootloader-version-hid.tdc000066400000000000000000000044101362775233600240200ustar00rootroot00000000000000TPDCI]?] *   $0 1 MZ?]X ' l @@.   `  9!-} -`-  > @`83@ D8  l@@@=+3@ `v`v@k`-kx`kZk2r@ǀ kA@`--֎-@,y<A 5=C G k`u)  @@3a@ ~ @A`6@@`@Q @C  tQ %wl@GA @J`@ A᩠FgB@YB) @@@ADA@Ā3"zEb&=@@=ay =`N!&=G)@/ / ! @H {! @J@`_@߀t  @  co;NAB\[ `iC@aDBA)b@ADAX`ELADcS@\ =a"S <@$@ bKA{b[+-A瀂 S >@JB S@-@ADA@B @ˠaLSEHcS 9@=@S@ #S @ = `{"S@5ր@ Sa! C,6A ;@BՀ S B@`BB S@x@ADA @`E aLSILcS : = `="S <@Ā@ CSD C'# AIfà@8aS B~ S@x@ADA!DE| aLM&P@=" 9@ S# &z`{"S$@᱀@ SG*҂A% 9F S'`aDB| S@x@ADA' @EkaLgQ J(@: =j) @-`k 4I8*@: =- 6=2!I+$7!kI,=2ju- @k-k`k.@: =i;Cmk/--z"9vD6m20 9@`k`@=2B@<``` `@(@0B @ `x5!` @ @<@$;`L<no e- ,@B]@$c MG@@(ʢ  `" 3` `@ VH%܇@@@,+@  @w )'f2 WD !""@@  DyS l K ysH B ot`"ad $r 2"33 0kk2 6 3 9D 04 :@!%"  @A!AV@@  @@ @MC@5a 1 `7fwupd-1.3.9/plugins/solokey/data/bootloader-version.tdc000066400000000000000000000040631362775233600232620ustar00rootroot00000000000000TPDCI>] 6   O  >]  l @@.   `  9!-} -`-  > @`8Oh@ D8@ lu@@@=`g@ `v`v@k`--׃D-kw@0vA@Z=7a4G = `u  @@3 T @ `@@F`6@@`@Q@ C  Ƒ|4J %@G!< z쀂 ` @Ag0F*BA @'@x@ADA@.@@4A3"EA@==`=!&= 1G   !B.䀌 @N@ۀ@@t `@K!!AY av.A +ڀ `iȡBA@x@ADAH@?cSCL`= S@@ɀ@ @A{b[+A : GS +@DB~A S@x@ADA@=@@\@?cS +@= S@ #S @ =Q`{H =% S )@@ % @A! C,XA ;@B S +@`BBA DS@x@ADA @`Epp@?cS +s `="S@٥@SadA[BAra< GS +@DBS@x@ADA@B E^ aL'1# = `=G #!IL@3h2! )@ @@ &i @d`D 'A lC" Q # +@B@x@ADA$EL aL@=%<=ѣ'{ @!(X`{"'@聀@ a  C CX # A( 2@B?O ) B@`BByA D @x@ADA*@B E:@?K+ +@=9 z, @-]o`k(- J8- @=Ll k!h>`7uBi`@/=@yEd|A« `f@(/ <@ `q@@@ @@0l<no e. +$-l b@@, LK@`- @ & `fwupd-1.3.9/plugins/solokey/data/firmware-update.tdc000066400000000000000000004644731362775233600225600ustar00rootroot00000000000000TPDCI?] r  WgWN% ?] $< l @`.   `  9!-} -`-  > @`8ǎ` D8  lD@@@=ݍ`@v`v@k`--ظ -k + $A@ZA`kQ@G = `G2  @@3 T @ @I@A{`6@@`@Q`C  ,U %Z@G!<sH `a AgpF*BA2 @'@x@A3A@.@@@3"Ea%(@==D1`=!&= 1G2   !! s=?`=H { `=! @N@7@ @  c`UxA +h6 `iȡBA`@2@x@ADA),cSC-a"S@@]%@ bKA{b[+-AC$ S~DB S@x@ADA@=@@݀-0cS`;=X.= S@CS @ {a"S@S@ Sa! C,6A <@B S B@`BB S@x@ADA @`Eˀ14cS ; a"S@@ CSD C'A A # cC@`DB~ S@x@ADA A@58cS =}S!a"S"@z@ SAS*҂A# : S$ B@aDBz S@x@ADA% @EaLg9jg(X`=-Fg'-`kdJ!%*8( @=ُ k!)`57I KI* 9@uCFR + @-Yڀ,`;=H k - 3@u$`.`;=ZEn /koـ0=Eހk 1$`uъ7EnG2 ="F=FX0 3 .@ky@ M @Pe$e p9q  #D04 x  aC5 "@DB 0@x@ADD06E1aLa7 =q`="$@h@ s A[ !s;A/Q  : +@DB@x@ADA;EaLaP<^`="= ~@U@A[b@gWA> d ? +@DB@x@ADA@EaLaI-L`="B ~@C@"s O`  "AQB + K@DB %@x@ADAE`CE$a1R:'A1@ s@+ [lAK @'I@, B@x@ADAJ@B E8 aK :g('L@@ gks@@e[ c=AM !0  N +@DB@x@ADAO`CEL؁ 6aP 9|'AZ@ @ W  `#@AR D  S +@B@x@ADAT`CE`Ɓ aU 9 'V <@`@  Kh pG2\ J0C   @K@Jh@!/AWV$X B@B@x@ADAY`CEtL.FnZ 9IaIEQ70 W[ )@ @B`_K!amaao #C@1@#˶A\m适$] B@BW@x@ADA^`CEaLc_ 9I`="`@׀@ !c!c4Cc`S#/ 0#@cC#!yAa]$b B@B@x@ADAcEaL>ad =Χ`=He )@5ƀ@  b!{B! !yp@JlCdl%Afŀ$g B@DB@x@ADAh`CE~aLai 9I`="j@@  @$D JmCem!knCfnzAk~$l B@B@x@ADAm`CElaLan 9⪭`="o@8@" fJS#CSD#;'GD{Ap$q B@B@x@ADAr`CEZaLas 9`="t@I@"+ !W#!gF3cw`'8DLmCSe $Au$v B@B+@x@ADAw`CEHaLax 9(`="y@V~@ + AKh&%+Ш!>HOpj @ '(Az}${ B@B@x@ADA|`CE7aL1;} 9 5u`="~ <@l@'&++![1A[ 5":  ?h @  O`P=&H2A kk$B @x@ADAE%aLa =Jc`="@Z@! #H (FAO#dk$sck#` KZh"bZ`Zf+Y$ B@B@x@ADA`CE(aLaC=9Q`="@@H@"+!WB!W @RBR@  B;BB@ "BZ` |RBR`pC G@ +@B+@x@fADA`CE<aL  a 9d?`="@6@!aK#8L[0& 6`81(IgOA A# +@B@x@ADA`CEP  a 9-'@$@ q9S>@Wu`e?!'&1 &3 )f= h }P PJB +@B@x@ ;A @Ed݁ a ; '@@ ABA$ B@B@x@ADAEaL a = =>`=" <@<݀@ H[L!Q!mLQVh F; -DTH$A܀$ B@B@x@ADA`CEaL!$M 9I`="@Hˀ@ + &&P!w$ä5iS6n$Aʀ$ B@B@x@ADA`CEȃaL%(c 96`="@{@ ((pG@  JIShCcS`2 LJ ce F"A޸$ B@B@x@ADA`CEqaL),a 9`="@H@   FI G,W p@D ? . OD%FU2$zw%Wg`$ B@B@x@ADA`CE_`-0a 9`="@c@"a FuPF#> #`h#3ZQ &` `#!@+ 6gzCǔ$W B@B@x@ADA`CENaL1/4'F+ 9+`="@@" ji!O#$a OqAo f]{7'{! QQ@mA䂀$ B@B@x@ADA`CEEFaL?a = =xFH`="@=@=@ \!i^,;9bVl8S 90KZi*3A  <$@? B@x@yADAEa = G4K' = @+@  J`2`pG[;#gEJKI( /F j `Br iӵA  *$@? B@x@ADAE䀈w y* = 9"N' = @@ Ki[A K`D3` KbZiB2Zai!u BZ/ A;5A  $ @? B@x@ADA EҀ1u! = ;Q' = @@  *rI i!w i aJi"b"JaKiCCK@ 3KaKi A $ B@B+@x@ADAE00A = QS' = @@!+BiZiAu&_! I F i! i aMiEMa`C`#AOA  $ 7@B@x@ADA :@EDTaL a = =V`=G ). = @ @  #i##aci#3c$3a-AFJAu#-q + ,FFO+B!  pဂ mi( 331@? DB@x@ADAEXWaL 4{C = Y`=" = @р@" ,"F("hF ,!  3@F)g<oC  S$ @? B@x@ADA!ElZaL@u&+" = @ \`="# = @@". J$oL6DB;ebhբhbhf |BA$  f$%@? B.@x@ADA&Ex]aLA'Ҷ_`="(@@ A B{ B3V (K@\*"F@FoGA)x$* B@B@x@ADA+Ef`aLA, = b`=@+- = @(@"W 9 FȲd#XC2sAZC cCzp czsO`zrc dkA.  $/@? B@x@ADA0ETcaL 8A1 = e`="2 = @;@  9 CiCpG"v0 r0  Kp#t #H3A3$4 B@B@x@ADA5EBfaL!$A6 = [h`="7 = @v@ AxC KJ[jhZ @x K"a ;347 KhZ(NA8$A9 B@B@x@ADA:E0iaL%(A; = nk`="< = @Vf@"  Kh@ +ѽ@ս,e | p 87?r&A=e$> B@B@x@ADA?ElaL),A@ =  ]n`="$A = @QT@ K`bM,+hB`!h,`8*&Q B(B  S$C@? B@x@ADADE oaL-0 FE = Kq`="F = @eB@ + L K#`K =[/ &'hp&'C Kx jG  A$H@? BW@x@ADAIE 14AJ = 99t'K = @w0@ pGb)IL `#h L+#p K J&) c rL/$M B@B+@x@ADANE 逈5)@AOID'w'P@@ XL!##p8BACC]_< @ bF+Q$R B@B@x@ADASE4׀9<AT = Xz'U = @ @"W@"! H H(H(O*)I* IF 0sAV$W B@BW@x@ADAXEHŁ =.AY = }'Z = @|`@" 0+@#!Hih@?C!K "`JZU  8 K LhA[A$\ B@B @x@ADA]E\LADA^ = aI"_ = @@"G J!hh9; -`ٱI +!`#`L#hI ,Q x(uA`  0$a@? B@x@ADAbEpaLE *)Ac = ߂`="d = @ր@" K`hB/mp '#h;#*3+#h F82 HCe  D$f@? B@x@ADAgEaLILAh = ͅ`="i = @ ŀ@"m5G $ ć  "I N H"IKAjmĀ$k B@B@x@ADAlE}aLMPAm = 滈`="n = @!@ AX@XBXA]AsI7I)]H"!#Iq~ $Ao  $)`B@x@ADAqEkaLQ8TAr = ة`="s = @@"+AaHBA 7=M+hZF8lX#| "iFA4$u B@B @x@ADAvEYaLU(XAw嗎`="3@0@"W(0!F"Fcz3hX; 2} }! &HAy$z B@B@x@ADA{EGaLY0\A| = `='$P.} = @A}@ m | 3Bq!HB#+04FO3 B| A~  |$@? B@x@ADA30`E5aL]0`A = =bt`=" = @k@ !+AX"iF-Ay 3I` }&5 A$pz:  j$.9`DB+@x@ADAE#aLadA = ?b`=" = @Y@"A"z@Dq|KhB`&?th cpG'i pX$ B@B:@x@ADAEaL3b)@sehA = EP`=" = @G@"A KpFh(05s 3LF#"hKB.D#hG5 D\D  F$@? B@x@ADAE$aLilA = l>`=" = @5@ + BU6Ao (OP2$( B p(iA  4$@? B@x@ADAE8mpA = ,' = @#@ $+!MDo*u 5=8M^r$ F4|, r/`0$ B@B@x@ADAEL܁ { @sq @===:r=$ҀGY=@16 = a ) = @u@ S! @%:  ܵ @? aB| @x@ADAEpaLS A = ழ`=" 8@4@"`2 pGOrPC +3r3`$:D    @? aB@x@ADAE^aLA = 뜷`=" = @G@ c+ F as!qar3$!19K @pG-A!w -mFA  $@? B @x@ADAELaLA =  `=!I@]@ i0r0Ob!FR#:FAFhD r&e~@#iF .A A$! B@x@ADA@CE;aLA 2 4y`=" = @p@"b"M.hc.F O HO`Exρ A = = ' = @@ M#,#c\A  a$@?  B@x@ADAE A =  a" = @:@"JJ hF@! !0"@p#L)[ H X \:g  $@? B@x@ADAEaLA`="@7@"A K`'_*KI "F JK` "`# ' EC$ B@B@x@ADAEaLA*8`="@%π@"W :OFI% Hh Gk0AAK '( C΀$ B@B@x@ADA+q`EȇaL A = =`=" = @S@! )ZCAoAk!kk L(-FFIH A  $.R`DB@x@ADAEuaLA = &`=" = @q@" "!FH@ 𷿀!$xB hC!F"H @ A  Ԫ$@? B@x@ADAEcaLA = `=" = @\@"+m /G!wD@-5F!FFF"F'AF8FHA  $@? B)@x@ADAERaLA = F`="@q@ W (F M1!a )K!"!w  A#3)S+AԆ$ B@B@x@ADAE@aLA = <~`=" = @u@" LK`KI/T$GI+pF#F!,!A  t$@? B@x@ADAE,.aLA = l`=" = @c@ ! EK h0F FΖB(`i`3F#FF̬B`Y`"F A  ;$@? B@x@ADAE@aLAZ`="> CQ@ m-',7 ##H3FpGK,Y#-p"ppG '1A8$ B@B$"@x 9ADA @ET aLA =IH`="@?@"knDkB!c 4O%2G$ 3`FFFg($ B@DB+@x@ADAEh A =6' @-@ PQ!NaAF !C+cU 8DE"Fq2 Q$ B@BP@x@ADA E| A =$'@+@ P $K ;BF2`i*  *X r8 FF! -)> ( !0 8YH F$ B@B@x@ADAEԀ A ='@@ @  @p$}0#F00F$"FhD uc4D,DhF+ $ B@B@x@ADAE€A = '@*`@"2 hF ,lCp*տ pG" F*ο&1OF-1~A$ B@B@x@ADAEL8A =aI"@$@ T,F "! p,pD "FAF <1ɲ $*JA倂$ B@B@x@ADA!E͞aLA" =6 "#@DԀ@ +% F cSESFFXFA-. DBEA$Ӏ$% B@B}@x@ADA&EaL( A' =`="(@M€@  , BB!{' m,0C-#-) "d )(-A)$* B@B@x@ADA+Ez(n  A, =`="-@z@ gН RoBѝ!K@[&)ХB۫!J#pJ GA.ݯ$/ B@Bg@x@ADA0E iaLAA1 =( `="2@s@"J`pG /?   YK`'/@ pNK4x)g׳ABu֝$4 @B@x@ADA5EW- a6 =G-V "7@@"m M+xC!hF$5,p F )A$; #3p+#30+  A8틀$9 B@Bm@x@ADA:E1E aLa; =z`="<@z@"0+,pF$ #$)C' KR} Kx3@C*F/\ZA=*$> B@B@x@ADA?EE3aL  a@ =wq`="A@h@"W'!wAA8M,h\tB '$(`8PAB$C B@B@x@ADADEY!aL )aE =m_`="F@V@" fK- Zp$5B#F#OsKh Z   hFz$AG&$H B@B@x@ADAIEmaLaJ =M`="K@D@ A! ! 0BXX2u2 $,( " " W#a9,AL=@!A ` vM B@B. 5@ADAN @E ,@=O=<=:r P =B)a! )Q@׀@dOR S B@aBy @x@ADATE*aLS-0K U =",`="SV@hǀ@"6hF' 0F " F7M! K 2$ Jx[ FpD`"Wƀ$X B@aB ?@x@ADAYE -aL14aZ =$/`="[@z@ i5h5@r 0D D FBFx* ,ݜB"8TD\ݴ$] B@Bi@x@ADA^E!n0aL58C_ =c2`="`@@ 6F]K 漜B"F @pG 8J#F # Kp8#J)@Aa$b B@Bb@x@ADAcE5\3aL9`="t@[@ +Op? F($.@ S7Au[$v B@B@x@ADAwE?aLILax =RA`="y@J@"b 3  maa33 R2s3`D! *zI${ B@B@x@ADA|EBaLMPC} =@D`="~@ 8@ KW(F x OC"@I!]5+Y$ A8KCm7$ B@B@x@ADAEQTa =.G'@7&@ +%! $G2f@0@+FMAn(A%$ B@B@x@ADAEހUXC =J'@1@"+ A(5Cd x8 Kx(`!IA$ B@B+@x@ADAÈY\a = M'@[@"m A@0A3B#K x x++F!SA$ B@B+@x@ADAE麀]`a =O'@T@ + +(" I&CҲeUam "I-LR,7)JA$ B@B@x@ADAEPaLada = R`="@iހ@"A (L|H!Ob` һ < -CF)vx+q+O8FcRA݀$ B@B@x@ADAES @Teha =7U`="@̀@" 6MF,F oOA8FC#B *H"FHAˀ$ B@B@x@ADAE%VaLila =|X`="@Һ@ DLD v,`!uB ЁB!O *FAMD _%`%hIA6$ B@B+@x@ADAE9sYaLm)p!IA =b[`="@@ +*F fAF2FH aqH!`@5+)W!8F+0aI"S @@ K pʌuCM#Y++#L7g#QK5h`HCS   B@aB @x@ADAEAaL , ={ߧ`=G@ @ր@ `@FR@ IX( :k,(  #b #Ap M+x+:J#.\*  B@B@x@ADAEUaLA =ͪ`="@Ā@ ho$I/  ` Oŀ!&[ %tQ$ B@B@x@ADAEi}aLa =`="@ڲ@ 1 C`i+ @(pha@@!  `p mD=$ B@B@x@ADAE}kaLa =`="@@  ,j,q4&! K[XCP1 H KBB J#B;KB o`A x$! B@B @x@ADA"EY @TD# =`="$@@   3@6vFp f  J0Cp  K B'tA%f$& B@B+@x@ADA'EGaLa( = ΅`=")@*}@ )% "R""`O4[@ 2 L-AF(iА)4]*A*|$+ B@B@x@ADA,E5aLa- =t`=".@Pk@"b($'%F#U !i*nF+h3` h !@>C/jA[A0 B@B@x@ADA1E#aLa2 = b`="3@|Y@ $!W"F#F&F(ن1))`+d #F(23+ A4X$5 B@B@x@ADA6EaL3_@@sF+7 = P`="8@lG@E+)r+rw@r  #$pC/ i+ FkAA9F$: B@B5Y@x@ADA;Ea< =>'=@k5@"A #j[w h  (4+F #$4 #j U>4$? B@BA@x@ADA@E CA =\,'B@#@  % (,-!y$*Ay "!y$# A}M!'p3C$D B@B"@x@ADAEE܁ aF ='G@@ K%D FOFF%D( !D) aJxpp΁+# q#+oDH$I B@B@x@ADAJE1ʁ aK =H'L@`@ Ds"4Ip! q C'!CAFpAM$N B@B@x@ADAOEEL  aP =aI"Q@@  "DC#!D)0p&ė +A+2ARi퀂$S B@B@x@ADATEYaL aU =`="V@ۀ@"Abp" AL%(!sD<"@2DR)R&Y(Fp@1gWB$X B@B@x@ADAYEmaLaZ =`="[@ʀ@ a L+d&#,UC%Dcnd&+0A} 0Fp-OF@ C\w`$] B@B@x@ADA^E`a_ =!`="`@t@ +T  hD0ˀF9A"h  #c F >! FLAa׸$b B@B@x@ADAcEpaLAad =`="e@@ 8 $wCyCAyAw@SAfh$g B@B@x@ADAhE^aL ai =`="j@@!mak&a@0#k@0`* !`Aky$l B@BP@x@ADAmELaL!$an =`="o@D@"WOc{ * a^D@0C! D#Ap$q B@BW@x@ADArE:aL%(as =x`="t@>p@ $+f!wkakEAD=&#hD "L~uo$v B@BA@x@ADAwE(aL),ax = f`="y@Q^@" ![$y $*@@:   T0!<DdgCz]${ B@B}@x@ADA|EaL-0a} = U`="~@fL@"4D"B f1$}s1$$cAK$ B@B@x@ADAEaL14a =5C`="@:@ Aq&@' P#= (0@Dk0-A9$ B@B@x@ADAE"W58a =b1'@(@ + 30 cdDcBY$0+? "l*@ !hCL05 曲V(A!$ B@B@x@ADAE6 90.C'$ B@B+@x@ADAEaLILa =`="A@΀@"&O=0&0 OG  42+S&SPS+D3AZ } B@B. 5@ADA @EaLMPa = ?=`="@@"+ 2P 5&<+$FA$*D   8'}P$ B@B@x@ADAEu!@TQTa =`="@E@ + 8B^ҠD F:1y?=#h3& # GP( IiC$ B@B+@x@ADAEcaLUXa =`="@3@ $+ F) rdk!iDc3l Fۻf$20#P$ 0mDDճKA$ B@B @x@ADAEQaLY\a = `="@g@ !Tğ0"+F$#$ "h(2!1CFC"a9 ʆ$ B@B@x@ADAE? aL]t@== ="`{ `3@@ ! @K I`-aCSz  B@aBy i@x@ADAEbԀSuxcS =a"S@ @" @x!0ce -+F2"$ FL>xnAr  B@aB @x@ADAEv€y|a = '@`@ #,8ChBF-(O E!(3)"qAs$ B@B@x@ADAEL}a ="aI"@@ pp2 BЄ($FWm%R@ t8A" s#A[$ B@BF@x@ADAE#aLa =%`="@ Ԁ@ /-)"  8@chB$CCF"%p!qAmӀ$ B@B/-@x@ADAE&aLa =(`="@7€@ (4+J#T(F8J!!A$gjA$ B@B@x@ADAEz)aLa =ո+`="@2@" $q@(0 ps pG"s`h"l$X==X TA$ B@B@x@ADAEh,aLa =.`="@G@!A[a[A{hF!h `T C @ )T!pG- A$ B@ B~@x@ADAEV/aLa =1`="@\@ +O hMh;aLa =M=`="@D@  $ C b4Gj !wC% $0O A$ B@B@x@ADAER a = l;@'@2@ T '  G GbJhBаO LhUBC `J A%$ B@B@x@ADAEf av)C'@ @!+ BbBh#@sC`hb b KBS 'cB |cCiCKA7$ B@Bb`x@9 A @Ezف a =F'@/@" Ca 4,@@@0);)s 0 ?  A$ B@DB@x@ADA Eǀa =I' @H`@"_j#SbjDhi!q!AA!i#SC3U`#@ M#-A _$ B@B@x@ADAELa =KaI"@=@ +U$}eBѕi$DDhD`ad6a'jAh@!4$s5Aꀂ$ B@B\@x@ADAELaLa = N`="@Kـ@ A,C"+C"!o+CzbU %C '  Aؠ e: B@B@x@ADAEʑOaLa =Q`="@6ǀ@ !Yi!q)C  qAEhA`aBc*  t$DDAƠA[F B@B@x@ADAERaLa =мT`="@3@ $C "C` [ c CA $! B@B@x@ADA"EmUaLa# =+W`="$@@ @Y#aAc ţcs++IA%񢀂$& B@B@x$ 9ADA' @E\XaLa( =6Z`=")@@ +sC##!q@ >cC##A*$+ B@DB@x@ADA,EJ[aLa- =]`=".@@"+YCQA E&1ac#!y3c m!1!p Zw$/~A[+0 B@B@x@ADA1E.8^aLa2 =;v``="3@m@"CB3CCQao@ !I,7QB aBAha1A,mA`h&C4$5 B@B@x@ADA6EB&aaLa7 =bdc`=H QrB8@[@ +DeeYm-$a!Sa` qA9,$: B@B@x@ADA;EVdaLa< =Rf`="=@I@ + !AbDee,Q =Q@0WA>RA[NW? B@B@x@ADA@EjgaLaA =@i`="B@8@ gC?![c#~S[[ @!"#@AC|7$D B@B@x@ADAEE~aF =.l'G@%@"+@!W D P BF!4 x0$ Fx"B"pfAHP$I B@B@x@ADAJEށ aK =o'L@@ $(LHEBr #!q xL53% "@2C BB#  { x-|ΈOAMd$N B@B@x@ADAOÈaP =!A r'Q@@ $ g 4xv%v2gPP*Dd xAV@ @DDD! @x x*33ARx$S B@B$@x@ADATEaU =t'V@)@"+$J $ BbBb ! @"D id xAW$X B@B@x@ADAYEΨuaLaZ =w`=%$P2` [@Bހ@"?P`CBT[$@d oDDoTDS=,&eH5S #3$ WA\݀$] B@B@x@ADA^ExaLa_ =z`="`@Ǹ@"R $$ "gB@$ !q! `@RB5! Aaˀ$b B@B@x@ADAcE{aL<ad =}`="e@g@"A [Md E$' "P%@EEE#  x$`"55D&&K Afʹ$g B@B@x@ADAhE s~aLAi =`=G@d@j@|@ A!PP2*DM%& T xMx-CAoG"P,'@''"@`qAkߧ@!l B@B@x@ADAmEaaL@=n=`=ao =C`=6 6p@G;@ @`-q: r B@aBy 2 @'@x@ADAsEScSt =3a"Su@,+@ S ʇV{&{,$ !"ZCSv*!6 Sw B@AbBL@x@ADAxE〈 ay =!'z@@ 'i '%!iHG"C,H@ A{~$| B@B@x@ADA}Eр!$a~ = '@/@"!@DFIm QR 'A i B@B@x@ADAEҿ%(a ='@A@  wB  xx$O$P JI6A$ B@B@x@ADAE歞aL),a =!`="+@U@ +Br$D珏  {Jx x0#c% A $ B@B2@x@ADAEaL-0a =6ڣ`="@р@"  "eC 3 !k 5"7iC#@"  ! T.W)Ѐ$ B@B A@x@ADAEaL14a =)Ȧ`="@@"LAW!p*D@+O@ 3`C⾀$ B@BL@x@ADAE"xaL58a =,`="@@"+q \'0##c%C3qA񬀂$ B@B@x@ADAE6faL9* $ B@B@x@ADAEOkaLqta =p`="@@ AMLK kKq C#&" " A!0`8VJ D] F.F$ B@B@x@ADAEcYaLuxa =`="@Ҏ@"5FG:46D00 , ,>OzsK"8X``"C5$ B@B5@x@ADAEwGaLy|a =`="@ }@  `pGK @ h[p&63ChMih j3Ci 3Ci+C3Ci$0zo|$ B@B@x@ADAE5aL}a =s`="@#k@"D3C$C#C`Dhh$pdC #C_C`ыa /!3JmCXCj$ B@B@x@ADAE#aLa = a`="@Y@ lcei+2hC#`Kh")(!+O3:"fAsX$ B@BH@x@ADAEaLa =O`="@GG@ -dAC@J۲C3#D@# apA<"@ F4AF$ B@B@x@ADAEa>'@^5@"++, F@aAsAmkR@ Թf! pA4$ B@B+`x@9 A @E퀈a =,'@s#@  pGpG*JSh$cS`F(J(H##pD#"  bA"$ B@DB@x@ADA Eۀa =.' @p@"A3bM!H"F#!u X#"!   ]} $ B@B@x@ADAEʀa ="'@p`@ !]]Os"    `k$ B@B@x@ADAELa =caI"@@ A &p@h@xC _)# w F@F"F D$ B@B@x@ADAE+aLa =}`=v v@ۀ@  q!]M! `  !2EA댛A+$ B@B@x@ADAE?aLa =`="@ɀ@ !A*X =7V a9z~ M$! B@BW@x@ADA"ESaL#=%4$ = Àe " S S%@|\@d(&[ ` @ ipF' B@aB{ S@x@ADA(E aLScS) =T `="S*@@@!6 L11!*p$ i#t"*јGC3 +D@#-D?$@ B@B ?@x@ADAAE/ aB =q'C@@"+i+s!s # FFF)Hl"*=#RB٪bF[ZAD($E B@B@x@ADAFECaLaG =`=G@lFH@@ \A3FF$Ix28 фxpi5eB 5"B*#Cm:AI@$J B@B@x@ADAKEWaLaL = !`="M@(π@" 'R+F$h+d7sG!AFEs2р!AN΀$O B@B @x@ADAPEk"aLaQ =$`="R@@ ![i3$ $$"FF%@&@#3F!DQPa" gAS`$T B@Bx@x@ADAUEu%aLaV = '`=G W@@ \A$2a!fbtR*a`|#C+ FKpt ,c9rAXx$Y B@B@x@ADAZEc(aLa[ =*`="\@@  '2iCUF"'3 F$U  GA]b$^ B@B@x@ADA_EQ+aLa` =-`="a@.@"b __;l2#G`K{Ab$c B@B@x@ADAdE?.aL/-ae =}0`="f@'u@  !!smG!]g7 x` +F F@++@ Kx +agt$h B@B@x@ADAiE-1aLaj = l3`="k@fc@ + !: } F#)FhIG 0K :*o#nvqlb$m B@B@x@ADAnE4aLao = Z6`=G@p@{Q@  nCL2h | G *O+C*|FS2j %G#CDqP$r B@B@x@ADAsE 7aLat = OH9`="u@?@"m pj۲+G"%9 A[h02h`h`i`,\Av$w B@B@x@ADAxE Ay =A6<'z@-@"W[iaWi|C32B[kG   #B(FF Za{$| B@BW@x@ADA}E a~ =g$?'@@ +F$5DѮ狈Cˈ3M-,A5dV鲀v FC$ B@B@x@ADAE3ԁ a =B'@ @"7#-2v#x5M))pa ++9)xF9 b)?|m2$ B@B@x@ADAEG +  a =D'@@ $!#A`]1Ag (q1}GA`@gAh! dɲ)xa`Y Cw$ B@B@x@ADAE[EaL a =G`="@@ Aʈ*D+++F#A?A,@# ;+ʈ*AC$ B@B@x@ADAEoHaLa = J`="@Ӏ@  Q l@C K 0|2!s+ay ?K+22/؀ # @GC$ B@B@x@ADAEKaLIA = M`="@@")=KW@+Ы7+c+y+  .C<U$ B@B. 5@ADA @EzNaLa =ԸP`="@!@"-2Q8D8 x` *F F@*rZx$ !*6*$pC$ B@DB@x@ADAEhQaL a =֦S`="@@ +t=/'![ A""=30GAZ*Jۈ @'l6A}$ B@B@x@ADAEVTaL!$a =V`=G @+@ +A ![0M*K f5+*G/A$ B@B@x@ADAEDWaL%(a =Y`="@Az@"#Вi*5D+`")F J+LUy$ B@Bg@x@ADAE2ZaL),a ="q\`="@nh@"+q# !iDu D!*a u@ #(`7?pKx*M)g$ B@B}@x@ADAE ]aL-0a =__`="@iV@"WCpxx5iJy y@ y` ÀpG&%~0wl!U$ B@B(@x@ADAE`aL14a =eMb`="@D@ $+*"8UD]\Ҳ2R#Kp p#_0*FZҲT3۲T#A#$ B@B$@x@ADAE 58a = P;e'@2@ A  HpGt)# LH F0"!* FG EA$ B@B@x@ADAE7 9A$ B@BL@x@ =A @EraLa =`="@a@  ,$o&\&#a0G!&QCSs0f+ħ`$ B@DBW 5@ADA @E``a =`="@]@" FFBp F!FFFj*_ !L(F !H!j PC$ B@DB @x@ =A @EOaLa = ?=I`=" @@!A k,%F̬B`Q`#F8 !KphAR[ FYBL!$" B@B=@x@ADA#E=aLa$ =\{`="%@r@ W  <*FF[DSI= l Kh@ pFF0 !#->"vC&$' B@B@x@ADA(E,+aLa) =yi`="*@`@ +F1%?  *F0!B!y!{j2w"i % T@ 4+iA+'$, B@B@x@ADA-E@aLa. = sX`="/@O@ "y !hF0AH]R=} -GF$F ΩA0#$1 B@BW@x@ADA2ETaLWa3 = E`="4@<@ FJ0`F+@BF)FPFaOFB К`.A5K$6 B@B@x@ADA7Eh ma8 =3'9@*@"U.%![ OD A}@!m# !&##h 2b8®A:_$; B@B@x@ADA<E| a= =!'>@@ ! . ODA@@!c@ $ 0Сg(Q&UJA?L$@ B@B@x@ADAAEс aB ='C@@"W!F `0JR1Fj lCf !()/b0! ^  9YA;Dz$E B@BA@x@ADAFEaG ='H@@"F!aU Z*!@ #!$$A ) 0$A @VCIx$J B@B@x@ADAKEaLaL =*`="M@T@"^ɃKHKO&('&!zFO@#,(MAN $O B@B@x@ADAPE̛aLaQ =`="R@hр@"+0 % A)+fOD,,ѕ@0 -A 11FoAS`$T B@B+@x@ADAUE`aV = U`="W@@ W8- (#FAF8&',4#"0IS O !0 F9AX쾀$Y B@B@x@ADAZEwaLa[ =A`="\@d@!m!F/!gG@ F3@ oAw@'gA]Ǭ$^ B@B@x@ADA_EfaLa` =`="a@@ \ LC+J(* $DOAb$c B@B@x@ADAdETaLae =t`="f@`@ JWD!UD s;&"07=FFFh!y F81g$h B@B@xf 9ADAi @E0Ba aj =i`="k@w@"m;F20 @n0yCyyB"*C F04 J0#$LuCl$m B@DB@x@ADAnED0aLao =fn`="p@e@ XB 0t03 +O0p7' J#J` pG? $?EAq$r B@B@x@ADAsEXaLat =\`="u@S@ + (!W K(KhB   s J8F#|\"SCPAv@$w B@B@x@ADAxEl aLay = J`="z@A@"+#+t( F8ccC8 L"#)YB cYA{@$| B@B+@x@ADA}E a~ = 8'@/@  "!tA2 *ağXF'%OO O BcO$ B@B@x@ADAE a =&'@@ + 6#bbh ù"xbv" RbhQ`"Dx]Pu3@)#aCd$ B@B@x@ADAEրa ='@Y @ !5e#`5"}bvQ!u!h[iKCh0xCv[Ca9-wA @! @ `  B@B@x@ADAEĀ@==o =}a"@垀@ !K!=9L S B@aB@x@ADAE4YaLSA =H`="@@ S @#`h+ @!I F,|FCS S B@aB@x@ADAEHGaL  a =#@"@|@!X"89#" 0 22 2(M\MA$ B@Bi@x@ADAE\5aL a =xs`="@j@"p9$@*O 0ӱ12!aEE!1Y# ) A1$ B@B r@x@ADAEp#aLa =a`="@Y@"  +hB4) 4 ,ѽ.LX"$? .E?&AkX$ B@B @x@ =A @EaLa =O `="@F@"!J"#?duD$ Cyh]*&-^AU$ B@DB/-@x@ADAE a == '@55@"Ѱ0o@-݁hxѫKh6r20F`0$'|4$ B@B+@x@ADAE퀈 a =+'@#@ A(5,D!F@]3F4G !XBTk\7C"$ B@B;@x@ADAEۀ!$a = '@V@ \,W$"KLWIA)AT#ah0`#"`#$ t(C<$ B@B2C@x@ADAEɀ%(a ='@D`@" 0# a$iI# FXB C tC;~FC$ B@B@x@ADAE跁L),a =aI"@S@ !h!w"$0X$T=ڔ+ ڴ&Q[APB $@A쀂$ B@BIh@x@ADAEaL-0a =;`="@ۀ@ + (j*BiKiJhhO3cE>h`@?eH;*0D Aڀ$ B@BW@x@ADAEaL14a =_`="@ɀ@  \H_K^HXK9*`ZK(9"`WKO1` hGF@?zwb$m B@B @x@ADAE$aL58a = `="@ҷ@  ;";6>`KKNKx ((?>(:iGx,< ؃,𐀆,ް`C6$ B@B"@x@ADAE8p!aL9FF'A'$ B@B@x@ADAEY\a = ;'@G@ m(OJhB+$6 $4FF/F+FG C0CbA$ B@B@x@ADA'@[@"pG "F F"0"1FpF:FF @\B\A F$pA$ B@DB@x` =A @Eada = 5=>@'@@ 8FF &e!F:;8@ :A0@F cFw$$ B@DB@x@ADA EAaLeha =YC`=" @@"A_!W!  "  # $0F->x.> FF@q|C  $ B@B$"@x@ADAE(DaLila =F`="@̀@"mIh0 !  @7 !yHF @AT$ B@B@x@ADAE#J/w/weA:>$; B@Bm@x@ADA<Ea= =)6a'>@|-@ :-A/CiF"F)+ IJF!]Y@"#F%rB a9A?,$@ B@B@x@ADAAE倈{ STy  B=:r=G2 (  AC =ra ^D@@ @Q@x C 9E F B@aB@x@ADAGEhzsaL+! A$cSH = `x`="I@@  @#ECExCe<#C `kXAJe K B@B@x@ADALEVyaLaM =Ȕ{`="N@@A 5!i!iai^:FDK/JF  /RO2 3@=( AOa$P B@B@x@ADAQED|aL8oaR =~`="S@y!ij!i`"yI Ur m @}A|=$} B@B+@x@ADA~EYaLa =`="@؀@"A ؀#l!iq!iAi {`e t `"fImAT$ B@BA@x@ADAEmaL)a =ϛ`="@ǀ@"W $s?_!iw!iai?Y z `!ZI@&RJ8Aƀ$ B@BW@x@ADAEaLa = `="@@ A F~!i}aiL! q`#Oa F  d G Ah$ B@B@x@ADAEmaLa =֫`="@1@" c@!e!eY!et "Da}:  M h@KYxKYBYA_A$ B@B@x@ADAE[aLa =י`="@-@"1Do2!o!o=!oXц!-  2 MS' !A$ B@B@x@ADAEIaLa =և`="@,@ m&!gAObC !i! 4   M" -C~$ B@B@x@ADAE7aLa =v`="@am@ +i!!e!e!{ =y   )  IAl$ B@B@x@ADAE%aLAa =d`="@W[@"AMUU!c S UR) -*  Z$ B@BA@x@ADAEaLa =]R`="@I@"W![  5wp FF! "77! !0Fx(F!C$ B@BW@x@ADAE aLA =>`="@5@ +t! os#FZv`Y`"F p!F^JA9$ B@B@x@ADAE! a =?.'@%@"H,!H@C-OI}FFOr F!#%@!g$$ B@B@x@AD>E5ހa =u'@@  2}Kn'3h$+ J($+ 0 FA0VrC$ B@ B@x@ADAEÍ   a =X '@@"UFFTJSxYKBKAE۲FG 0%!1` #!C@@2h?$ B@BU@x@ADAE]  a ='@@  }#}:)Fh${EK%S B BF"!7 6 5PFGsCE$ B@B@x@ADAEqaLa =`="@݀@ $W x!X$y!eY cFsE`Q`b*SFBFYF F: (AY$ B@B+@x@ADAEaLa =`="@ˀ@ (1sF"Q'.'r9F 8'J$EE< 0 3 AV$ B@BA@x@ADAEaLa =`="@/@ J@# n-n_`"1$A+ A$ B@BA@x@ADAEraL a =`="@F@ PF!P!} #&/ JOqI}1k4' i- g( e/*$+ B@Bi@x@ADAE9 9P@==uaz2u"ş)  7b =`=H@=s@ @3@bπ@ i! L9΀ S `@  ipF B@aB@x@ADAEaLQTcS =AV``="S@>@ '/ao(e0#GO, R.c_*!FD vZ၊B@x@ADAB LwaLUXa =`="@3@ # KWbag!g!gF)F2.%i#?@~$-9"aq` jA$ B@DBFR@x@ADAEeaLY\a = 2`="@u@ %u'Ns!gO!! KI2D @!$ ؚ$ B@B@x@ADA ESaL]`a =(`=" @@" p ae!eUFeJ!{   F )C戀$ B@B@x@ADAEBaLada =!`="@sw@ !S " ;&0 xA+F FB+"+(C kF1%)`טAv$ B@B+@x@ADAE0aLeha =Wn`="@e@"w 0 }"h,'!uJHAS\ Ad$ B@B@x@ADAE)$@Tila =f\`="@S@"L H d MFF FFW Fx3 (zyA$ B@B@x@ADAE= $@Tmpa =[J`="!@A@ AK[x+ 3$5$ F !W+@rJ3Rx*+E6$ +AѝSP"$# B@B@x@ADA$EQ qta% =k8'&@/@ #+-@рkOBF"0Ff'O?)K,Z"#q``@q[' l$$( B@B r@x@ADA)Ee uxa* =& '+@@"x ,M]I;Z!g@1- AOs ` !VQ`! !AD,8$- B@B@x@ADA.Eyց y|a/ ='0@ @  !q2䲧X 0[Jam* Й &7.(F5:F@A1 lK$2 B@B@x@ADA3Eā }a4 ='5@`@" !q!q B0(q-:5@ ! !$vs A6\$7 B@B r@x@ADA8ELa9 =aI":@@"m D$yo/# OD&!wAD b D7W 3#A;u瀂$< B@Bm@x@ADA=EaLa> =`="?@<ր@" !@%{! G OG!n  !#XsA@Հ$A B@B@x@ADABEɎaLaC =&`="D@aĀ@" F!FFG;aI!U!&S q'!H-?@x@ADA @Eͥ=aLa = ?$@L"@fۀ@ + ,Q"Pe!i@?)W D bI=Gs_`C  71FSDDڀ$ B@DB@x@ADAE@aLa = *B`="@uɀ@  ;A+UB+UY2S\A{LTa{G!{-SaQN OSq[AȀ$ Bm@x@ADA @ECaLa =E`="@H@  "NagEH!g@O!T+`B OU$$>4Ao($m DB@x@ADA @E pFaLa =;H`="U@x@") F$q\$#(F;8!y @A{\:FAu2` o,/C_Hۤ$!@DB@x@ADAE^IaLaH?K`=" ~@@"$B0"F"3xA7'B+?+d\t# F)IH MIF 7% cA𒀂$ B@B@x 9ADAk E1LLaLa IIVN`="@@ +CF,9F@FF7@,#</"FIcCiA$` DB@x@ADA @EE:OaLa =xQ`="@o@ + .!i$j ,і0S$ F%O 0!F!H$,}(! 5 5TF@l:$, DB@x@ADA! @EY(RaLa =sfT`="J@]@c!!TT4 4A/O=FF͐ĕ`aD)$ B@DBm@x@ADAEmUaL{O@=h Hȟ=:r!: @  2O {/e`{H@43 j@@mp@!>O `?i?h aDBz (@x@ADA[ `EfaL@&cS8h`="S@@J$A|CD X!p=cq`="@@"0$G\AH ' O1}F&*1* A( aA$ B@B +bV@x@ADAE6craL6a =ut`="@@H*!Z1+q' L#p#)цJAA$ B@BL@x@ADAEJQuaL  a =iw`="@@mp+&8 p+0@ &$FC@iosGCD;D(PHA$ B@B @x@ADAE^?xaL a =x}z`="@t@ +0""2@'qc#?#@.l1K C\+A"!f.$ B@B@x@ADAEr-{aLa =k}`="@b@HW 0 <u#F!F' p0  #@0dCB$ B@B@x@ADAE~aLa =Y`="@"Q@  bл@ TI#BT=$w a qP$ B@B@x@ADAE aLa =G`="@"?@VxJI"Ay #\CB uD->d$,BC>$ B@B@x@ADAE a =5'@-@{S )!  5JTF/#F˫B`Q`8z{x},$ B@B/-@x@ADAE倈!$a = $'@H@PA -L!P F:F!a'%'|CIFBJ~C$ B@B)@x@ADAEӀ%(a =#'@o @"+ [$q$q.܉IZd  ?$9 TEA$ B@B@x@ADAE),a = '@Y`@  ao!oz!o2FAi` m '$$.$eA$ B@B+@x@ADAEL-0a =0aI"@{@Y+ PPA!SOR8 L F9 8#@A䀂$ B@Bx@x@ADAEaL14a = Uܔ`="@Ӏ@  x+ =FAi2!@FH,Ccx+Hal !OA l oҀ$B B@|@x@ADA@/ E&aL58a =bʗ`="@@T!W( ,Q)FA(!<(н8@ڽ8=  FfA$ B@DB@@x@ADA@/ E:z$@ 9 =_aI"?@@"+(B!#L;L_pF  *F ؙJHWa@$A B@DB$@x@ADABE*$@TilaC J`="D@؀@"m *@![- ,@ *@+;Q+ Ixh(E$F B@Bm@x@ADAGE>aLmpaH = {M`="I@ƀ@+" =z+QA#1!1A/u#;}J&DJ$K B@B@@x@ADAL@/ ERaLqtaM =x`="N@γ@" 4F@aia{v&!0ua Y`-ǀ*NO1$P B@DBJ@x@ADAQEfmaLuxaR =`="S@@ Omao!oIao $+5*Fqd`!#-8 !v<#h[CT]$U B@B]w@x@ADAVEz[aLy|aW =ə`="X@@ k   ^ai!i+!ii[Jo  V`   Fjp8AYz$Z B@B@x@ADA[EIaL+}a\ =`="]@@ !NK[/id $7'Н| # } eCJa9CA^g~$_ B@B@x@ADA`E7aL{@=a=h:r"  b =d`{H@73 G.  c@@ @SK ^9Od2 e B@aB@x@ADAfÉ Ag =3 a"Sh@@"  @($ ,?yAyCpA"R F"2F!! [fRAENq$ @DB/-@x@ADAE*$ a =i`="@:`@ WFRg&Ao/A0ȱu$ApARh_$ B@x@ADA! @EaLa =W`="@fN@"L 0+AR')Y)Y'$)Y\!՝AM$ B@DBAjPW@x@ADAE$ ?aE1E% <" ~@<@  i;{+KJHOqm3# p +9*4  {YADG;$0 Bg@x@ADAF E aIU3%@K"@*@ + a!pG8{* F K J !!L{ BB(` O_AH $ @BB@x@ADA  E AgeA!' @@  `8 $wN+A@(!L~$ B@DBe@x@ =A @E2рa =Iw '@@"+ ! D8$@+3@WK'$ B@DBg@x@ADAn EF a n '@@ g#3P!@&[emD$ B@DBF@x@ADAEZ aLa =`="+@@!+ KJH@$ #@!w CA{`!{+ +v"JsQ$ @B@@x@ADA@/ EnaLa =`="@р@ {"m{p -A{+F F @H8 m}CeЀ$ B@DBSA@x@ADAE% a =`=!@@ A!aO(HNHO(FB#hC#` 0`+ #jF ;A}$ B@BOM@x@ADAEwaLa =`="@.@"AO (э/8(1F ȹA@+jF=$ B@B@x@ADAEeaLa =`="@A@"qa 1(A!=G9aa`+A\$ B@B@x@ADAESaLa =`="@Y@ +$kw0@фAa}} @-塽D$ B@Bm+@x@ADAEA%@Ta =.!`="@jw@ weQ@ `A} C#1IDs(D`+рaG@1@h@Gv$ B@B@@x@ADA@/ E/"aLa =n$`="@ke@"+ i)R  p#)CZ5F*II Fr=Ad$ B@DB@@x@ 'A @E%aLa =$\'`="@iS@ ँ#&0ӹ x7 I '.#) 4?#3p)DAR$ B@DBS@x@ADAE (aLmR =(J*`="@~A@" ,) p  2#  p{+=QJ>I 3b@$ B@B{@x@ADAE"A =F8-'@/@ !!_(FB Ӂs7(&  )C.$ B@B@x@ =A @E6耈-5 =Y&0'@@ A$u$(4m$ * l2+hj"C%A$ B@DB@x@ADAEJց  A =c3'@ @"+`A#! sF C30B A$ B@B^@x@ADAE^ā  A =6'@5`@ ! &&)/3FF!$O P-1AFU AX$ B@B[@x@ADAErL@u$ =8aI"@@" G F#' FSs R*2ŦAZ$B X@x@ADAE9aLA =;`=H m @@3%@ Հ@ #&'"+=/ 4-T jF>C)2(KAW$ B@B@x 9ADA @E`="@2Ā@ A3F+&+,YA9FRH`u'gr '&S4|A À$ B@DB@x@ADA E|?aL A =A`=" @D@"A 2iAs6(rcquRх#&$GF_$ B@BA@x@ADAEjBaL!$A =D`="@3@ !PzFQf! F!f!hf!Ȁ  a9yC$ B@B@x@ADAEXEaL%<`== =;V`{i i@3@ ! @L9f2  B@aBnFJ`2 @'@x@ADAEO퀈=@CS =!A+Ya"S@"@" @' '&#;:GF>CSN  B@AbB@x@ADA Ecہ ADA! =\'"@@ # ('WW'' &0+"AFH  1)1)$ !0+pA#y$$ B@B@x@ADA%EwɀED,& =_''@^`@"*)Y)O"Ia}P}(a}3p !Q`!!A(e$) B@B @x@ADA*ELILA+ =aaI",@@"!sas;p)m5 +*0# pH!5 FV/A-쀂$. B@B @x@ADA/EbaLMPA0 =d`="1@7ۀ@  +IIq#F 0L@.#F;aI0+$A##p(!A2ڀ$3 B@B@x@ADA4EeaLQTA5 =g`="6@8ɀ@" I(1$#' !O'Fg?!I5 [ȹ#x˹"A7Ȁ$8 B@B @x@ADA9EǁhaLUX&+: =ܿj`=";@2@  "pF+B!k jFOs IX JAST(бA ##gA<$= B@B@x@ADA>EokaLY\A? = m`="@@`@ A&[[d H iFF[`7OTK`/ZjzxAä$B B@B@x@ADACE]naL]` D =2p`="E@t@  *z$E )S= ؿ pGp g0OFג$G B@B@x@ADAHELq%@TadAI =s`="J@o@0+ &'5@ y#C A$ B@B@x@ADAE/?aLA =M}`="@t@ + ;+RU ',@LVs "! ,Gq!} #hC#`A ^5 t$ B@B@x@ADAEC-aLA = k`="@b@ 6$!oao!eOu*У A,q ;8$ B@B@x@ADAEWaLA =zY`="@P@{^;!!]!!]$Иd+6 (` 'Ѕ#"rD($ B@B@x@ADAEk aLA =G`="@>@"aga{@+ &C#2*@(/wcA=$ B@B@x@ADAE A =5'@-@ Ak2a#1!ou T/sFFj r OKl1)q%h,$ B@B@x@ADAE倈A =#'@)@ +PJ &!)c&91Io()o$ B@B@x@ADAEӀA ='@[ @"A*Sʿ[>*㪿4&Ij io+{<$ B@BA@x@ADAEA =aF@u@F@ %+5R"f#$0D5#()k б  F D$ B@B@x@ADAEϯaLA =`="@=@ o+P-a{p1ga sQ`!fS@+g:A䀂$ B@B'8@x@ADAE㝷aLA =%ܹ`="@cӀ@  )--d1-j-Gc1a +|1FQAҀ$ B@B@x@ADAEaLA =ʼ`="@h@ !D$u maH S5r])1a9BA$ B@B@x@ADAE zaL`==r =\`{H@ i 4@4T@ @Q@Z *?OS  B@aBx @x@ADAEaLSCS =L`="S@!D@"  @#' 1#w K AC  B@aBS@x@ADAEA =:'@2@"5+ y !  + 4{qAj1$ B@B@x@ADAEꀈA =('@4 @ A $0MZ_!orF!K  !1C@A$ B@B 2 @'@x@ADAE؀'6A =% "@D@ "k P0){QAg4U`a0a@+r!ѧA $ B@AbB@x@ADAEƀA ='@Y`@  @*!+Osb N&~Al!wKA$ B@B@x@ADAE紁LA =aI"@R@"x19$1F7 i@"!h2$(QA适$ B@B@x@ADAEaL3A =0`="@l؀@"  Fh #hFr"_)"IDXA׀$ B@B@x@AD>EaLA&+ =*`="@~ƀ@"+@  +K`K"pKppG? f -CF#0+xAŀ$ B@B r@x@ADAE#aLA =`="@@ +;O+FT@#/F@+`'`#?@ `$A$$ B@B@x@ADAE7maLA =l`="@@ A 8CD C! |!  w@ r2F)FwH!!A  $ B@B@x@ADAEK[aL  a =`="@ΐ@!A g0ñ~ayA]X!H"*00H!K\2$  B@B@x@ADAE_IaL a =}`=" @~@"+ HO c4Q"8F FDI8F")F`< p 8&]5C 4$ B@B+@x@ADA Es7aLa =u`="@l@ + !{G'ACx+! ё@PA0$Qx+(FO@lAC$ B@B@x@ADAE%aLa = c`="@Z@ + x3F*F  F!_AZ!wJ@ GAyO@  @8 FG!2AY$ B@B@x@ADAEaLa =Q`="@3I@"@& " 6+x++D{#"#pbpp G! )U F@( ! #COѧAH$ B@BN @x@ADAEaL a =?`="@87@ @FKxxCcx3Cx1 #BD;$kFOCq #qjQ)A6$ B@BC<@x@ADA E!$J! =-& ""@G%@9{+6hR_1D DOA4 D@,cmmnCnnA#$@+j`` $ B@B~A@x@ADA%E݀%(A& =''@E@"n{Y+KtFO-S+# #qjS(0D iA($) B@B@x@ADA*Eˀ),a+ = ',@r@  '  #t:ZD  #@  ZD@ FRA-$. B@BQ@x@ADA/E-0a0 = '1@m@"DFrDe"DBe2De*DeDf DCfcDf;DfILgFD02$3 B@B@x@ADA4E aL14a5 =S `="+6@ހ@"+ 5FF&FFF4#"d## K J I@   KfC7A[f!@+8 B@BhB@x@ADA9E' aL58a: =c`=";@ˀ@ AtS#CfpGgg jrn<:OhكRQF F'B.A<$= B@B@x@ADA>E;aL9]tP$Q B@B@x@ADARE<aLILDS =z`=G@:T@#r@ W@mp3$[ @ʭ G@ uA qCUq$AV B@B@x@ADAWE*aLMPaX =h `= 4 4Y@P`@ !i_dA Fp)a !6AZ_@(N[ B@B@x@ADA\E!aLFRQTa] =V#`=`^@DN@"vAA_M$` B@B@x@ADAaE$aLUXDb =D&`="c@5<@"AcodA@ C+-O +C+j ꁽ/m֠Ad;$e B@BA@x@b =Af @EY\ag =2)'h@*@ +Q3@* b  b  b b@+ bAi)Aj B@DB@x@g =Ak @E ]`al = ,'m@\@ +aA{AwAsonAo B@DB@x@ADApEрadaq =A/'r@@ Bo@0zs A[t B@B@x@ADAuE eh!yv ="1'w@@  $    F $a! ndndiDx$y B@B@x@ADAzE,2aLila{ =e4`="|@@!!nAs!knawAsAoa9U.$$~ B@B X@x@ADAE@5& ?m@==)M =~F`{ @hu@ @Q l@C  `-a@Gt  B@aBy @x@ADAE/GaL D =mI`="S@@e@ SпE q(Ad$S B@aB S@x@ADAEJaLa = [L`="@9S@  7AR$ B@B@x@ADAE MaLa = JO`="@fA@ U!_pg& ƏOPA@ &q @  B@B @x@ADAEa =!8R'@y/@ WGAso @&. B@B @x@ADAE耈a =&U'@{@ D} gA[< B@B@x@ADAEրa =PX'@ @ ALpD$ B@B@x@ADAE0ā a =F['@Z`@ C@& A$ B@BA@x@ADAEDLa =Y]aI"@@ g!A$ B@B@x@ADAEX^aLa =f``="@Հ@"})OOq'$ B@B@x@ADAElaaLa =c`="@Ā@ AO0~àA[ B@B@x@ADAE|daLa = f`="@@ + GmBDU B@B@x@ADAEjgaLa =i`= @@"A 5 gI7mAg$ B@BA@x@ADAEXjaLa = іl`="@/@"+ /5  cA $ B@B+@x@ADAEFmaLa =o`="@S|@ + + 7>;A{$ B@B@x@ADAE4paLa = sr`="@ej@ + 3 7Ai $m B@B @x@ADAE"saLa = au`="@iX@ 9@=vAW$ B@B@x@ADAEvaLa =Ox`="@}F@"AU ,/ J$}Iw$EA[hz B@BA@x@ADAE a = @={'@4@"Aa{$mxAC$ B@BA@x@ADAE a =P+~'@"@ P+k-A$ B@B@x@ADAE4ہ a =c'@@",A)$ B@B@x@ADAEHɁ a =]'@`@!' M* **_ k/$ B@B5@x@ADAE\La =aI"@@ AAk ^ 'i# i4 iE jcj4IRAvA[AoF$ B@BA@x@ADAEpaLa =`="@ڀ@ $+ x!g I  xWAX` jA J ) kkEO8D?$ B@B$@x@ADAEaLA =ь`="@ɀ@"+l%_Y IAdARAmAAk:  jfAZ @jn  {AzȀ$ B@B+@x@ADAEaLa = `="@ @"?A]D"$SA G(VA l$ B@B?@x@ADAEoaLa =׭`="@2@ +$h$@DhV$` !q HD@99$ A$ B@B@x@ADAE]aLa = ߛ`=" @2@"iQ!q '9j&&@ &k k kQ[$ Y #iom $ B@B@x@ADA EKaLa =艘`="@F@"k8 #!l4l!labb@  fA;G$ B@BA@x@ADAE9aL9a =w`="@Zo@"dbmA[ IABl XY `   6Dn$m B@B@x@ADAE'aLa =%f`="@]@"!o)!_!_ao_lkhlkhlkhEv'\$ B@B@x@ADAEaLJ =CT`="@K@"+$hV is-4-E0MA[ALWgm0' ETA}A00CJ$ B@B+@x@ADA E$aLa! =@B`=""@9@"A0/MAm!*'&'j@'%'@ &;v_XI,5A#8$$ B@BA@x@ADA%E8a& = d0a"g'@'@ mL'0J)G$  Y   -$DA("$) B@B@x@ADA*EL   a+ =\a",@@"+ !_!_a_\ f#f##D##d[!m-$. B@B+@x@ADA/E`΁  a0 =  '1@@ !$O92w)7##''` #f) Ma@C2s$3 B@B@x@ADA4Et(@=5=?n=O6 =6a 7@@ ! @Qa0A?O8 9 B@aBz @x@ADA:EPaLS),cS; =`="<@W@ i6 @ؿ '/D'#_ 4m&4A= > B@aBi@x@ADA?E?aL-0a@ = 5B}`="A@t@"x$ĽQ:u h遑 jg]!)kg - /ABs$C B@B @x@ADADE-aL14aE =Ik`="F@b@ l!cl!clAc nn n ng_Z O\ Xn & OG 0H B@B*@x@ADAIE(aL58aJ =Y`="K@P@"Bbb!bacc@ iAdAAmAA'CL9$M B@B@x@ADANE< aL9F2RbgF]tDOZ )A$ B@DB @x@ADAE1IaLa =@`=J }@~@ WFD;@&Ah$$ B@B@x@ADAE݀=Z= =j6a @ѷ@ S$ @0O9O9  B@aBy 6@x@ADAE!r7aLScS =Z9`="@@"  @3rJ 2!(  5n%eF/5cCS$S B@aB @x@ADAE5`:aLa =<`="@㕀@ #,a[܀*CF@RFI* KE ]4sT] ۝AF$ B@B@x@ADAEIN=aLa =?`="@Ⴠ@  hhB&Y&Y !F FYMFYB&Y3dFCF2F!F FAE  B@B 2 @'@x@ =A @@]<@aLa =lzB`=" @q@ $C @΀BL#'u (?hICA-$ B@DB@x@ADAEq*CaLa = PhE`="@_@ C!Y`z2JE $ <Z$ B@B@x@ADAEFaLa =UH`=" @qL@ c @  CC`Fn K$ B@B@x@ADA EIaLa =DK`="@!<@"+-!U?JFQFP'{S' h ]C;$ B@B+@x@ADAEa =2N'@A*@" 'G"F12M,f]br)$ B@BL@x@ADAE a = Q'@/@ +`;B#ǫpR&99BOC$ B@B@x@ADAEЀa =T'@Z@ +l#Ga!iAF3bIhA[vP@Z B@BU@x@ADA!E龀a" =V'#@V@" `GCF l1J?+4LI yC$$% B@B@x@ADA&EWaL3a' =Y`="(@@  auKTakDآ`A6)E$* B@B@x@ADA+EZaL+A, =2\`=G@n-@Ѐ@!m ظu#$ ; ;$,+\%Բ:_OFC.π$/ B@Bx@x@ADA0E%]aLa1 = H_`="2@@  +F!Q# *@!}/@#F]lg+@9A3$4 B@B@x@ADA5E9w`aLa6 =b`="7@Ҭ@") J&' F̲F/7'SA86$9 B@B)@x@ADA:EMecaL  a; = e`="<@䚀@"mXD!COR  d X rF0%(FA=G$> B@B@x@ADA?EaSfaL a@ =ˏh`=H +A@*@ W@'G(P00. >ݺ@򧀻c5"FF0AB$C B@B@x@ADADEuAiaLaE =k`="F@v@ +@3sEBA C1BtFB ,U!˲YIBB@mGH$H B@B@x@ADAIE/laLaJ =mn`=G@K@d@"+_ 3[B!eȿU#0BȿC*Z?BKh(FCA fLX$M B@B+@x@ADANEoaLaO =[q`="P@S@ }'*3F*F  =5 D $c=E=FDQjR$R B@B@x@ADASE raL aT =It`=G@U@"A@"Rd ۲Q  3 C9C/+AV@$W B@BC<@x@ADAXE!$aY =7w'Z@5/@"S <COL * Ҳ*Fc7Fo7c7:[.$\ B@B@x@ADA]E瀈%(a^ =%z'_@H@ g gK  "FD q]C;-0+"[ EC`$a B@Bg@x@ADAbEՀ),ac ="}'d@u @"F+?"u? 0A`F#F2F'3  3FxAe $f B@B@x@ADAgEĀ-0ah =$'i@o`@ 2s 0 FG3jF/d(;BF0!!@+)sAj$k B@B@xi 9ADAl @EL14am =+aI"n@@ AZ2}*W"T" B;[ZD"D U RЕAo怂$p B@ B@x@ADAqE)aL58ar =Sޅ`="s@Հ@!B 33F:c)G$GFRtԀ$u B@B@x@ADAvE=aL9A@$ B@B@x@ADAEYaLa = |`="@߀@ 0 &R! QC" 4&?Ay@F#C7y?PA*$ B@B@x@ADAEmaLa =`="@΀@"m O03O0+K+0 0s  0[  %< %0Ah̀$ B@BU@x@ADAEaLa =`="@0@ ! 38!W034OWqFFF   dG@ A$AA[s$ B@B@x@ADAEtaLa =۲`="@)@"_ @A(?L"l ? oA$ B@B+@x@ADAEbaLa =`="@@ 2g# $w $ $ $ $ $A{$ B@B@x@ADAEPaLa =`="@Q@ A!W!W0,I} U@`A$ B@B@x@ADAE>aLa =|`="@dt@"+ʭu5WAs!& + B@B+@x@ADAE,aLa =0k`=" @~b@"+AwA߹_Z\"P" B ;[\# #  Ih a$ B@B+@x@ADA EaLa =GY`="@P@"Wsؓ@8Op/@o_ 4,@O$ B@BW@x@ADAE aLa =CG`="@>@"+ .dh,_  _OYOFT6FEED D $ B@B@x@ADAE" Aa =o5'@,@"L DELO_nR+vB!{R& oom$ B@B@x@ADAE6 a =t#'@@ PA5mB!e`ȿR%PDȿ,C,FFF:F,O0G.C-$ B@B}@x@ADA!EJӁ a" ='#@@" 0)!gAc G  n}"! 0I -h -[$@$% B@B@x@ADA&E^ a' = '(@@LuA}HF}!!eW A [F",@ !F @ la)q$* B@B@x@ADA+EraLa, =(@"-@@ m;ak ! (@̀"FB)#8"A(FA*!a{D.^$/ B@B@x@ADA0EaLa1 =`="2@Ҁ@"+[!)F(>+hV-CR3`B! aA3Y$4 B@B+@x@ADA5EaLa6 = `="7@$@"D;F D}C` CE #58$9 B@B@x@ADA:EyaLWa; = `="<@5@ +aa "dD;$!y )l C=$> B@B@x< 9ADA? @Eg aL6a@ = `="A@0@ +B8BW>E)''#+/OZ  +F=AB$C B@DB@x@ADADEU aLaE =`="F@C@"JWHcE SET2iE  #EAI!!&&&.oAG$H B@BJ@x@ADAIECaLaJ =E`="K@y@"W /m""#"XY!D 2+ALx$M B@BW@x@ADANE1aLaO =)p`="P@ng@"!G%Z? F;FZl7 ,'ZgG a9AQf$R B@B@x@ADASE aL{c_@s@=T=rU ='`{b V@:&`@ @L~9W `A?  iX B@aBy @x@ADAYELAZ =)aI"S[@ @ SpC5 ` P` ),B8 >CS\适$] B@aBS@x@ADA^E*aLa_ = ,`="`@.؀@")cB3`OsXCsP#=#S/E Bт7Aa׀$b B@B @x@ADAcE-aL ad /`="e@zĀ@ $Fs!m< oDo*|F2FFK"2AfÀ g B@B@x@ADAhE~0aL!$ai = 2`="j@^@"C<F1F.O"/5"@F9Ak³$l B@B + r@x@ADAmEl3aL%(an =:5`="o@~@ g1!gg0FGSFJF9!#` 8 #a Ap⡀$q B@B@x@ADArEZ6aL),as =>8`="t@@ 6"aa 8#+5,A2FAF ao # gu䏀$v B@B@x@ADAwEI9aL-0ax =*;`="y@o~@"A9!ssC #F 1F0!v!2F!Dn pa 7Cz}${ B@B@x@ADA|E7`="~@l@!"8"#Z!wU'nA|&;#F Bk$ B@BL@x@ADAE*%?aL58A =[cA`="@Z@"L~"G!u;'^ !?hb !g !#! $,xCY$ B@B@x@ADAE>BaL94,AS$ B@B@x@ADAEVkaLqta =Vm`="@M@ W /I /A#45 /S ! Fd#EIE#E^AQ$ B@B@x@ADAEjnaLuxa =Dp`="@;@ $I+}Ag)CF";FELAa$ B@B@x@ADAE~ y|a =2s'@,*@ 3C{ 0 G(@F, 4#*0;۲+A)$ B@B@x@ADAE }a = v'@@ @݀&0O  _OZ &* 5Fs4EFb$ B@B@x@ADAEЁ a =y'@0@ E 5CDSEEO_\^)`dBE^$`C$ B@B@x@ADAE"dA ={'@@@ __B!e@ȿ^#0AȿC-`G @GH>v>A$ B@B@x@ADAEά|aLa = 2~`="@x@ $3uF,+FrFF0 -D:E 3۲+@OY "KAဂ$ B@B+@x@ADAEaLa =؁`="@PЀ@"m,&Pv3EBA $K ^0< 'S@s[BBgπ$ B@Bm@x@ADAEaLa =DŽ`=G@p4`@c@ !i<06!gqi$? B@B@x@ADA@E"aLaA =``=- .`@3B @  X@   *egS<+]ѩ[3w}S ( FTaHz<! CnW@ `(ÿ\D B@Bm@x@ADAEEaLaF =IN`="G@F@ A R!JvLDGIE P?S-IM! FnTCHjE$I B@B@x@ADAJEaK =<'L@F4@  DG1R,2q@B+Y)M  8jZG# &AM3$N B@B@x@ADAOE쀈aP =+'Q@Z"@  "FFGFˡmP)Qy*F`FxK`pG[L~R!$S B@B@x@ADATEڀaU ='V@E@  ? HpG,-OFF$ O@ 0H>BGIFrW$X B@B@x@ADAYEȀAZ ='[@m`@ 9@!oP} X3FJF8F3(@𬀵=k\$] B@BA@x@ADA^EL3a_ =IaI"`@@  k.7S}.]\A9F@Fc[ZF+a뀂$Wb B@B@x@ADAcEaLCd =K`="e@ڀ@"3*F#DY#x[۲Pt!A Fe341@BAf $g B@B@x@ADAhE&aLai =h`="j@Ȁ@ Pb$02q +O1FMQ.0icP 1P!Ak"$l B@BѬ@x@ADAmE:aLan =w`="o@Ҷ@ + 0BBFܔ`.:l;wU.@BAp6$q B@B+@x@ADArENoaL  as ={`="mt@Ҥ@"5L;!P`-!+DFo F$C  "bcL@kAu5$v B@BW@x@ADAwEb]aL ax =`="y@Ғ@"m ,O ˲" W!--/. 1=FhEFftxRAz6${ B@BW@x@ADA|EwKaLDa} =`="~@倀@"FH 0أZ*#9 FFZ,AH$ B@B@x@ADAE9aLa =w`="@o@ +0/_g>#'81XQ8 Aun$ B@B@x@ADAE'aLa =e`="@ ]@"1PGH-N;ڲ2 F!O 88cF Am\$ B@B@x@ADAEaL a =S`="@#K@""b  @_CBbPO- ݝ 2+lAJ$ B@B@x@ADAEaL!$a =A`="@69@"O;"[8FTLM L]92,O]QB9PqA8$ B@B*N@x@ADAE%(a =/0'@u'@ +=Ob >DG $#F:F1F0F(tѹDtOi(O{  S A&$ B@BAs@x@ADAE߀),a ='@[@ mq F#!#!:F/Khf(:aSl 0FwA$ B@B@x@ADAE΀-0a =N '@@ (Fк/q(2X,!> F[-^Mx'8$ B@B@x@ADAE 14a =^'@@"+K( B RҲS+ؿ 4;C$ B@B+@x@ADAE+aL58a =a`="@߀@ $FP4BC C )osO#)FgZ_poA$ B@B$@x@ADAE?aL9mO Ac$ B@Bm@x@ADAEgtaLADa =`="@٩@ !@/A9_@πo" #F`A`!A<$ B@B@x@ADAE{baLE\@==* =( S B@aBy . @'@x@ADAE ]`cS =#5a"S@,@ \ @ '% _BBCu. ۲XQ@E@ cOCS+ W\ B@AbBE@x@ADAEaLada =!`=G@!J@B@"3=c[EAeAe/S<C*R $F"/C/$ B@B@x 9ADA @EӀeha =@ )@LG@@@ i  K@}NaoC"&c.pcfc#5UE!3C$ B@DB @x@ADAE/ila = q"'@@ 0O$oDQ@ YI!k.@7c5GA2$ B@B +6@x@ADAEC#aLmpa =r%`=G@@@ @$ON%݁*dD"$$CA+$ B@B@x@AD>EW&)@Tqta =x(`="@Ҁ@ /}{ 8EG X !F+F@FYйE()$ B@B@x@ADAEk)aLuxa =+`="@@"{Z%%.ZLYFD!AFHFC?$ B@B@x@ADAEy,aLy|a =.`="@@"A65 "F D#05zG4C2@6AZ$ B@BA@x@ =A @Eg/aL}a =1`="@@ +4COTC ЛP-:2nT^  Ah  B@DB@x@ADAEU2aLa =4`="@@ +7II!@`,!#DFg F% Aw$ B@B@x@ADAEC5aLa =7`="@Ay@" R@LFF%7YGFFFFAx$ B@B@x@ADAE18aLa =o:`="@Ag@" $@$P& <е1FBG:FAF(XAf$ B@B@x@ADAE;aLaW\=`="@S@ + Z] F xpLx\pxpxp1B JO&j $ B@B`x@9 A @E >aLa =K@`="@KC@"W E ]T\\\  0Cx]@x!P\@ "@CB$ B@DBW@x@ADA E a =8Ca$ $ @G0@"+ 6Y@<.#\U]]Q\o.d-p 1#FxA /$ B@BA@x@ADAEꀈa =2(Fa"@@ 2Caxkp@ BBp ZD @pG'OF FF <A$ B@B@x@ADAE3؀a =jIa"@ @! ## bzc{bsbybrbxbqzcpxp{ryqysxpzM^A,$ B@B@x@ADAEGƁ a =La"@K`@  q{rsF/g#$gHA\ 3+2B!)a &F7xsx8FA@$ B@B@x@ADAE[La = N) #@ @!   x!} F(  + l适$! B@B@x@ADA"EoOaLa'+Q`="$@׀@ `FLQ !c  2 )C    T`C%?$& B@B`@x@ADA'ERaLa( =iT`=")@ŀ@"]!] Y@* Q@0 !  Q@A@y@i@ qp{*2A[sh9@  '8+ B@B@x@ADA,E~UaLa- =ȼW`=".@.@ AoJ@ J@B@z@J!u  S@   FR/A[0 B@B@x@ADA1ElXaLa2 =Z`="3@A@ bK^ G@O@o@ p6Bw ;B[+4$5 B@B@x@ADA6EZ[aLa7 =]`="8@E@"zci[ Js#)QQ\ IW)W  3F+9$: B@BD@x@ADA;EH^aLa< =``="=@C~@"[EJ!F ROo/[M F-- #l{'F#UA>}$? B@B$@x@ADA@E6aaLAA =Lsc`="B@j@ +F "H,M 2*3B"bycxbpbzbqb{br-sbAC$D B@B. 5@ADAE @E$daLaF =!cf`="G@wZ@ +oA{Asy-q!F xNxx X@?HY$I B@DB@x@ADAJEgaLaK =@Qi`="L@H@ pp@ HpF!qh`p !  ! CMG$N B@B@x@ADAOE#jaLaP = B?l`="Q@6@ $1B8F7RF0 m/J#'Q\   &_$oAR5$S B@B@x@ADATE7aU =f-o'V@$@"AS!FR5qZaW%$X B@B@x@ADAYEK݁ aZ =]r'[@@ s`!!tuCu$ B@B@x@ADAEaLa =`="@P@ Aq-$KDB٠#$K|O`@ pG h`hK/Aဂ$ B@B k@x@ADAEaL a =ئ`="@"Ѐ@  hC`R #G&9&9 h**@0! @p+ NGAπ$ B@B@x@ADAELjaL!$a =Ʃ`="@5@ @(Ѐ8 pG `9h!psDhhBF! p"xཱུA$ B@B@x@ADAEvaL%(a = E`=G _i`3N @@  s*"s*@@)1)F&G "s)@G}A$ B@B@x@ADAEdaL),a =`="@@  Dwp*٢ @0-BH ( Oqف**!bx(?A_$ B@B@x@ADAESaL-0a =C`="@@ cV :*  "s#"xs A*"sJ*A$ B@B@x 9ADA @EAaL14a ==`="@v@  Ѵ R{BshS7O hBx)3C`#s`FpU"u$ B@DB@x@ADAE+/aLx58a =[m`="@d@" GhZЂ{*R] "s!mG@ sh3FF:u +`Db$ B@B@x@ADAE?aL9mO#l_mA대$ B@B+@x@ADAE0FaL3" * i@* ila !O`="@{@ '8![ F!D4#`]~F{( K #q H'A$P B@B D ` @x@ =A @ED4aLmpa ] =|s`="@j@"; C @h"F@ /_c  FpA%$ HB`x |ADA@ EX"aLqta =t``="@W@"A {+ P(#{2{ +F FK !M!&AiȄA%$ B@DBA@x@ADA ElaLuxa =N`=" @F@ A{[O31`a0 &!Oe- K!V!Oi #BB`z+A gE$ B@B$@x@ADAEWy|a =<'@ 4@ ++?khc`,9`{)*Q`C#s*$DA3$ B@B+@x@ADAE쀈}a = *'@("@"+!WUs)? #~! $ {+K !KhC`GA!$ B@B+@x@ADAEڀa ='@,@ 'BB! !F F38 JH@]1L {t$ B@B@x@ADAEȀa ='@8`@ + oFO Џ&B C(6!D$!*51;C $! B@B@x@ADA"EжLa# =aI"$@g@ k@!0 L M),F,Q o(0FK! @0  `XA%뀂$& B@B @x@ADA'EaL{WG ((=V`=t:r=G2^) =* ."*@ @dO+t~ , B@aBO@x@ADA-E\9aLScS. =w `="S/@n@"0&aF&eF KCOK5jF#޾CS0E$S1 B@aB@x@ADA2Ep' aLa3 =e `="4@\@"FAk祱i` .#=#f)Fh! <5@?`A5X$6 B@B@x@ADA7EaLa8 =S`="9@J@ i } {*K J )k !wh+'a=Oe A:Y$; B@Bi@x@ADA<EaLa= =! #@`=">@`7@G / `# F"-AFFFF{+15 K!J!!QK(F! -YA?6 +k@ B@B @x@ADAAEaB = /'C@4'@ Fth+O(F$lD {`+)H 1$xtD&$E B@B@x@ADAFE߀aG = -'H@X@!* 0Cg`FA@@ &%A, 2,0lCI J B@B@x@ADAKÈaL = 'M@i@"x!S- Q9 2 _F KhFhRK2I+F"$"AN$O B@Bx@x@ADAPE軀aQ ='R@U@  KF" 7"D [)AKF!KhjG F1jAS$T B@B?@x@ADAUEaLaV = !`="W@n߀@ + }t!{FHS+h]pGAp M Ld&B AXހ$Y B@B@x@ADAZE"aLa[ =$`="\@b̀@   M Lva pU;G6 #50O! ]̀$^ B@B@x@ADA_E$%aLa` =T'`="a@@"+ 9$B ]4 ]BX0 DB/YpGKOBBCb $c B@B+@x@ADAdE8t(aLae =|*`="f@ɩ@" كBFBBAs!sB!y!DF pGXAg-$Wh B@B@x@ADAiELb+aLaj =v-`= kk@ї@ + h;+`ڔiB ;]KhX`pF@pGF.]Al5$m B@BF@x@ADAnE`P.aLao = 0`="p@ͅ@ + d:F"0/EC-O FFFFi lKBnAq0$r B@B@x@ADAsEt>1aLat =|3`="u@s@  }hknԫԨm+i)F8,C kn5 0wKvE$w B@B@x@ADAxE,4aLay =j6`="z@a@ #hu" h竉a# #)00 *0O #FC{]$| B@B?@x@ADA}E7aLa~ =X9`="@P@!+F+ %*Ѷ [/MC 00 [D 3x+##2AyO$ B@B@x@ADAE:aLa =F<`="@>@ :#6S04F"F1UH D "  D+` 2x**iA=$+ B@B:@x@ADAEa = 4?'@\,@ D+4F &!F*;0; +Kp C&Fh*RBCtC+$ B@Bve@x@ADAE䀈a = #B'@a@ f!Y#x.+ cx*+3h+$ 345N!x"0'8@#!AA[2 B@B@x@ADAEҀa =E'@9@ C4D-H("(?*K3#3 C&3 n2 A$+ B@B@x@ADAEa = 5rG'@@ F)%"F & F' 0: *+!w!#F*FK8FUA0$ B@B@x@ADAEHaL5A =?J`="@@" ?F],][?  a kA。$ B@B@x@ADAE(KaLa =YM`="@Ҁ@"7  x ~   /oGFFh i BF0KA$ B@B@x@ =A @ECi$ B@B @x@ADAE$aLUXa =b`="@>Z@ &+ +FFFFG(!F8F ڨ D!()E#yJ8/QeQ#$ B@DB@x` =A @E݀eha =^'@@"F FHFE2"A{FQ X!{u C %;h :`_q$ B@DB@x@ADA E0ˁ ila =; ' @@ +?FFMF#E9a}D. A#=!*` 1%D#$D $ B@B 2@x@ADAED mpa ]~am)O@@" D2FFFQ)?,` Yf+% a3c2`A`A/$ B@B@x@ADA`DEXaLqta =l`="@܀@"m  #EWc 0# 3#C A1FD @ Q'_A*$ B@Bm@x@ADAElaLuxa =Ӣ`="@ʀ@ cV $y02h$y 3`c? ;$2h0AX$ B@B@x@ADAEaLy|a = `=" @@ + ;,+E)M-"$E ;SCE^A!W$" B@B)@x@ADA#EqaL+}a$ =`="%@ @" 8F<+Ы JEJF*CFF= B(2 OA&n$' B@B@x@ADA(E_aLa) =`="*@q@"+ CFF!o NEF;)U FNFFBٶ!+C!CA+Ԓ$, B@B+@x@ADA-EMaLa. =`="/@Y@ +@|C<,3F)8F F#`O08BlR!0K F1*F2A0$1 B@B@x@ADA2E;aLa3 =y`="4@ q@\+8@+ YA  $B%@()O KFhҿ%8A5mp$6 B@B@x@ADA7E)aL+a8 =h`=G@  9@V_@"L#FFF#`C#h+`8f ּ+7*(BpGF5}:^$; B@B@x9 9ADA< @EaLa=V`=">@cM@!!e*8pGj9!{;!{/BѠB+јF7U7UFC?L$@ B@DB@x@ADAAE aLaB =]D`="C@;@ + !KB+lhi`/#ik#i hciB7F8]e&0hTD#$E B@B@x@ADAFE aG = 2'H@*@ +;`#hZ"`pci0BУ .сy8:E !Ѭh cIf)$J B@B@x@ADAKE4 aL = W 'M@@"hq ()37=c2KphF F%iC}W$DN$O B@B@x@ADAPEHЁ aQ ='R@@ ."$ 0, "2`C@!0>% %C$ !QviAS=$T B@B@x@ADAUE\ aV ='W@`@ t+akADB0,7 #cc#$! `#i#&?C  #iK swCXVA[}Y B@Bt@x@ADAZEqa L~a[ =`="\@@"!FA #`ci[Ba#iSC1 pXc''8]~ဂ$^ B@B@x@ADA_EaLa` =`=G QWa@π@" & -AF FXKh+ l+ j.2/CbS$c B@B @x@ADAdEaLae =`="f@@ +4R/h+`2`mZcJk #l#Fj!j(FGC)h)I*JʠOAgv Nh B@B~@x@ADAiEvaLaj =ݴ`="k@C@ +@E"b`"i"`B8 `eak/`)GI7 ) `c!j#{lA[m B@B@x@ADAnEdaL+ao =`="p@T@"+BA!}+++/`'ICi/ГhKi`#Cq$r B@B+@x@ADAsERaLat =`="u@B`@ `]5CF:F!jd(JD&W@4  iF^4Av$w B@B@x@ADAxE@a ay = %`="z@v@ +  F%(F81,;k+bnԙԠm(ArYJnA{u`$| B@B @x@ADA}E.`Wa~ ="m`="@xd@ cnFԣak!0 Mc#pAc$ B@B@x@ADAEaLa ==[`="@R@"F3`Cf3aF"\0;KcbK  $b#c7;!$C$&\AQ$ B@B@x@ADAE% aLa =ZI`="@@@"+ {!W IpJh%UCFtF@!!  0`h$ B@B+@x@ADAE9 :a =O7'@.@ + !p FpH!f @  ͠ a9DC$ B@B@x@ADAEM  ]w@s=a = 5`=\ \@z@ ! @Fx9 !J B@aB@x@ADAE{aLScS =`="@7@"b @#F'Fi@ 3#eKJhbB#a 4s CS$S B@aB4@x@ADAEiaLa =`="@M@ # ``AW`"`!`h" ! }"! xA,t2q6$ B@B@x@ADAEWaLa = +@"@X@  #Kh:Y0 H64;3h 6h5 PչK`XC$ B@B@x@ADAEF? a =`="@y@ ;efF%`UUa")F\ U =h4!8!bA1 + B@B@x@ADAE4aL  zA' =q`="@Qh@"F0`(D{ #;`d{+-CFFH&#+u?$Ag$ B@BQ@x@ADAE)"aL=A =>` `="@W@"6h竉+ٵ03)F@FGCh5pG`F)FF#+`1iAV$ B@B6@x@ADAE= aL0# =}N `="@E@ + Ocj)(pBSZBZA*#)@# #`psF A6$ B@B@x@ADAEQ  A =<'@3@"FG#`#a#ca!mj& }H} #C?!z'AM$ B@B@x@ADAEe  .@# =*'@"@   Kb `C!_ca a[0#'( !C)wAc!$ B@B@x@ADAEyڀ@u! =!A@ '@@ A%)*+;:B-@ BA!  A"AH$ B@BA@x@ADAEȁ Q>! ='@`@ D #V<GGA2nAi$ B@B@x@ADAELA =aI"@<@"+ pG8pG-5F880@?0!@% 8F)EЋA뀂$ B@B @x@ADAEaLU# =`="@Pڀ@"A Q< +- JhF3c``(,G8@AB !hb2 h[hc`RIHAـ$ B@BA@x@ADAEɒaL!$A = `="@3Ȁ@ + "``FZh BhXB hDXB`hRhZ`D` ,U hAǀ$ B@BW@x@ADAE݀!aL%(A =#`="@J@ + !B!kb" !`\`8;? %5 -8 %$F۩B!3UA$ B@B@x@ADAEn$aL),A =(&`="@v@"+`!o!I hF O;h#!lQ<8`)` 7C#!`J#h؉A٣$ B@B+@x 9ADA @E]'aL-0A =s)`="@ΐ@ [ +#`D%`chB `Sas # 9_P"Fdh mA1Az `  B@DB@x@ADAEK*aL14A =>,`="@@ + $B!B04D$8L#`#A$ B@B@x@ADAE-9-aL58A =@w/`="@n@ 8> (cm#Sce-AF F FFAm$ B@BW@x@ADAEA'0aL9<A = [e2`="@\@ #"uuhPA2F;FJAU#`e#z $ B@B@x@ADAEU3aL=@A =jS5`="@J@"+SC"B8L""`F!ěcKC($ B@B+@x@ADA  Ei6aLADA =}A8`="@8@RXg)@3 Zo +Q!`>$ B@DBW@x` =A @E} EHA =/;'@'@"+BjPq@!FOA'yCd&$ B@DB@x@ADA E߀ILA =>' @@"ANsH!#@ A a$ B@B@x@ADAÉ MPA = A'@;@" 8KX"%pGCA$ B@B @x@ADAEQTA =C'@g@"o 5d/4AA$ B@BW@x@ADAEͩDaLUXA =F`="@J߀@ WJIh+ FD`FpG-$Aހ$ B@B@x@ADAEGaLY\A =I`=" @Ò@""/Wsrc/init.c0 0 *H=01 0 UUS1)A!̀$" B@B"@x@ADA#EJaL]`A$ =XL`="%@@"U0A\ Maryland10@ Solo Keys10@ Root CA10a&! O' B@BU@x@ADA(E tMaLadA) =>O`="*@@ WAa s!uk!t .com1!0 #  hello@"0  C+A[, B@B@x@ADA-EbPaLehA. =#R`="/@@ +181@25200Z206810290DF0얠A[1 B@B@x@ADA2E1PSaLilA3 =>U`="4@@ "0 C2 Authenticator Attestat-C5$ 46 B@B@x@ADA7EE>VaLmpA8 =L|X`="9@s@ ion%,FA:A[; B@B}@x@ADA<EY,YaLqtA= =lj[`=">@a@  %Y!&=0B"*xE7(WCIoMN%u0=D?,$@ B@B@x@ADAAEm\aLux :B =}X^`="C@O@"! i,ݲ\9#ƬJ'T? ţ00$;a9yCD;$E B@B@x@ADAFE_aLOy`=G=I=H =Co`{4h@&I@@ $K f9J ` u@ ipF B@aBz 2 O@x@ADALEpaLSCSM =r`="SN@kҀ@ S @(o|( 0CI #0'RTCSOр ၊P B@aBS@x@ADAQE saLAR =u`="S@~@"w'ATῠ ZU B@B "@x@ADAVE!yvaLAW =Ex`="X@@ w+rYA[Z B@B @x@ADA[E5gyaLA\ =>{`="]@@ &+f+/ Gc0R00 D2 G0D q&^A[_ B@B@x@ADA`EIU|aLAa =~`="b@㊀@" F,UdP;iq֦=f;a ^N:dx5c.2n)EDcF$d B@B @x@ADAeE]CaLAf =`="g@x@  ȑKqR#G/ v   TZ4Y tapU2F_uhX$i B@B@x@ADAjEq1aLAk = o`="l@f@ +V2!klib/usbd`_conf7rJ@ʢ'3s!Cm^ n B@B+@x@ADAoEaLAp =]`="q@ U@ lo 2.4.20123456789ABCDEF algsigx5cidi! namedispArmT$s B@B@x@ADAtE aLAu =K`="v@/C@ layNA[ typepublic-7jFIDO_2_0vcԠBWsyrkupplaAwB$x B@B@x@ADAyEAz =9'{@1@ tcli5 Pinpacked../ fido2/c&$ hmac-secre / bor_valuA|0A[} B@B@x@ADA~E逈A =''@8@ e_get_booleanf string_lengthcopy_A$b B@B@x@ADAE׀A ='@( @" texrkbyteAarraɬA  B@B@x@ADAEŀA ="'@`@! y Akmap ismƶ?C$ B@B@x@ADAEaZaLA = {`="@я@"?1'Y GQcg)) '8!.m,M 8STs e jv.,r=C4$ B@ B! @x@ADAEuHaLA =`="@}@"+迢KfpK£Ql$օ5pjl7LwH'4 9JNOʜ[+CE$ B@B+@x@ADAE6aLA =t`="@l@!Wo.htocxxȄnjlPxq = a7Avk$ B@B$@x@ADAE$aLA =c`="@"Z@ A@AkQ%cʹ@@ "`˜E93-}6AY$ B@@B@x@ADAEaLA =P`="@H@""w@cGB,kQ7h@^1kW3+|J玛BOK`'><;!YAG$ B@B)@x@ADAEaLA =>`="@J6@"+S̰evU:5ZUQ @6R j068@IA5`$ B@B+@x@ADAEA =,`"@J$@ m|9/4CDT{2#=L BN.f($v[Im%rdhOx#$ B@B@x@ADAE܀A =U'@@ +Ԥ\]elpHP^FWث XE,?k:0C$ B@B+@x@ADAEˁ FRA =; '@@ AOgst"57unGq)ʼnobV>Ky xͅA`$ B@B@x@ADAE L~AA =5`"@@ Zݨ31Y'_`QJ -zɜ;M*aLa =\`="@ʀ@"'u ,nZR;ֳ)/S [j˾9JLXCM3EPfHa5Wi R:A[: B@B:@x@ADAEz_aLa =`="@@ َU(ߌ BhA-Tadvanc:0 nternal_c;EdecDf$ B@B@x@ADAEM+@TWa = 0`="@!@" odak64qpfixedAwA  B@B@x@ADAE;aLD =x`="@jo@ M!"beC _containerleavea9dAn@! B@B@x@ADAE)aL{ O4@==mr`= =x `="@@ !a9F a aB@x@ADA@> @. S58cS 6Qa"S@@ S@0|DgetD_check$i&pa_c!CSAS B@@BS@x@ADA@> @BaL9 @'uaLADa 6`="@@  oDoubleAp(*ptr#SmallaMaA`A B@@B@x@ADA@> @~vaLEHa 6 >  `=" 9@@ sk)Ba]32BitC / Aw  B@@B@x@ADA@> @daLILa 6I`="@Z@"e64!errAC& NoError parser.c@&(size_1A A B@@B%@x@ADA@> @RaLMPa 6`="!@@"t)a(it fMUnknownLcG0 A"v # B@@B@x@ADA$@> @@aLQTa% 6`="&@gv@"6Ag!In&idf'is)K/_@ a@)o?A'u$( B@@BL@x@ADA)@> @.,@ UXa* 6l`="+@Id@")3_!r!qki_ recu$d-A,cA- B@@B+@x@ADA.@> @aLY\a/ 6Z`="0@NR@"+bEF&!,m( ), function:A1Q$2 B@@B@x@ADA3@> @ aL]`a4 6_H`="5@?@" as'A[ "%s" failed: f `, l2 %d%s%s 9 #-0+ 1N 6(A7 B@@B@x@ADA8@> @ ada9 6 >  l5 ': 9@,@" hlLefgEFGabcdef ;6$< B@@B}+@x@ADA=@> @ eha> 6I$%'?@@" @.6|@ A B@@B~@x@ADAB@> @2ՀilaC 6S'D@ @ ; @` FpG@ Pes%[F+E$F B@@B@x@ADAG@> @FÁ mpXH 6x'I@`@" ` Usoloke8t/@ =ݎ  -  n|AJA  K B@@B@x@ADAL@> @ZLqt rM 6jaI"N@@A*   &u@ !    2`}AO*$P B@@B(@x@ADAQ@> @n,@ u4!R 6`="S@Ԁ@ F"1_@   ! ""@ 'J AT?$U B@@BJ@x@ADAV@> @aLy|aW 6 Ӡ=`= 3X@À@  )2  !yay @@   !jtAYx  %Z B@@B-d@x@ADA[@> @{aL}C\ 6@  `="] 9 <  @ "2 F!=^ {g$_ B@@B0@x@ADA`@> @i aLaa 6"`="b@@"M,)tc{@Rd B@@B@x@ADAe@> @W#aLS.@,f@6 @Ck@=G2Jz  &ig 6X^.`{S h@U@ #S @K 9i& `#`E&8oj B@@B@x@ADAk@> @/aLScSl 6GN1`= im@E@D ` ! @ J"@97DXUa=6B_b?': Yx4ģ6õ2Fin So B@@B@x@ADAp@> @" aq 6:4a"r@1@ i`=;U OT~ Q]sN$t B@@Bi@x@ADAu@> @6 '$ D @=v@6 @"ԣ =  "w 6 .@H@{ ` =Nx@}@ S!>y뀂 z B@@B@x@ADA{@> @̦ aLdL@,!@=|@6 @`=]= Ȃ9}@6 @DK`=`a{RA`8~@1 @s8 @,@q@1 @AO@q@1 @gUw8q>@1 @~8/8;q<@,Lť 6@  w`v#1`G#@@9K 9@#v@@`6@@`@ @AnÀ@;@ݔFJuJJ `J -F B@@B}D @ @x@A C > E}U@z@ `@# l@ K 6^;8ATCT  `Ti B@@BA@ @x@AA ><3C@@ ! @"r@BBBB B@@B  /E"%@'@x@AC@ @;`Eb@1 @8cy` 8@1 @=5`83A 6 Π=`=!}@@@S"3E 3 {7AJsJ  ģ B@@B i@x@A EC@> @ @`@  @ (. 6@  =.%C.# 9@a@@# @ h@C!@BBȧ J B@@B~@ @x@AAJ@> @i(b_?(ENXb 6@p  h0 3$ 9@g@@~@`@ ARJffJ J `J-38F B@@B~3b @x@A B_ >@  nE3"& 9@k@@ `@T)T  `Ti B@@BB3q@x@AA >q3#~@]@ ! @ɣ~Bp BB B@@B}  +@x@AA >@2  &} 9@{@!@+_qJzJ @ B@@B@@x@A A >, b@E@@ B B@@BbÃBS@x@@ : $"a@j@,JΌJ B@@B@x@A A >$0@@$ B@@B @x@2(A ʠ@0@,JJ  B@@B@x@A A >飀 @P@  B@@B@x@@ :@鰀@!(l FJLJ B@@B@x@A A >J1 L@@@Sr')2.`JmK znKK  &0v B@@B!z@x@A A >@   !( 9@@ ! @ B_B  B@@BW@x@AA > (@@ !)AWJTJ  B@@B@x@A M\@> @€@Χ3 6R ΀=ul@fȀ@ ~!"_#u: @YuDAskǀk s B@@B*@x@A-B >  Ѐ cTvZ@ 9@7@$Bπ E1 B Ί@x@AA9%} K@@K@K. 9A @A^ D@  $ր ^ 9@Հ@ " BB ^F B@@B@x@AA >ހ $@&݀@á /z!J܀J W B@@B@x@A A > !<@@@S  w@ K  xCF;F  B@@B @x@AA >@2  !  9 -  @$ ;BB 2D BEMDE@x@A K@@K@A^ DU ^@@ BB ^D B@@B^O@x@AHq@> @ A8 6@  {=J$ 9@@ #CJBJ  B@@B*)H@x@A AR@> @S 6p* Y@G~E%")' @W)KSo* o 2.4 2<=YXX ` B@@B< @x@AC >" "u-G@o@ !" RBB B@@B@ @x@AA >@2   # 9@K@ AWJ J W B@@BR@x@A C@> @W 6=9@@ Keys0A\(T \~ B@ #~B ~@x@ABq Eq#~I@L@ ~ B~ B@a B?@x@AA  E) #~@(@ ~c~ AWJy'J ~ B@B@x@A C~ E#, =Rt5=~] @1@ "_'+ K2&6 3&9D 0'B \\ [ B@adB@x@ABy E E8y#@*@ .!& B7 B B@a B٬%@x@ABqE*  =JisJ#@@ @{'%AJJ  B@RB@x@A AREC =@ր=R#Ub@@@" @ BfB J B@`B 1@x@AC E - #i "_@6,@ $&J+J _ B@aB&@x@A B_!EI0_" =R 9=$-&#@5@F$ &$XX `u% B@a`B= @x@ABu& E": uF'@@ #" (Bg B RF) B &@x@AF* K@aXK@K @A^+ K K@^F,@t?@ ^-B>B ^RF. B@`B @x@AA / E S k $0@ R@ 1JoQJ W2 B@aB@x@A D3EV4 =R<_=+5@a[@$6\Z\ d7 B@adB@x@ABy8 E&by+9@a@ :BXB; B@a B@@x@ABq<Ei E k= =@pt=J#>@hr@! @ A?JqJ @ B@B@x@A B_A Ew"_B@v@ "_ CB[B lD B@ B @x@AA E E }"!F@{@@S " AWGJTJ W!H B@B@x@A AI E@E!eJ@@ !c% K%1\Kd d$CL B@B C@x@A&A.M E E舀C#PN@N@ !. OB P B@B P@x@AEQE4%pyD> .R=J$5`@M>S -kn`kT=a#u;`g@ kU--Aͯ-`&@Ho_@V=$`k= @$8`36 Bz+`@#nʢ  `"@3@ @@ VH%܇@@s @$`@@ @@0 <n;un$! @@`$ `@ x5`` ` AA4x #Dw@ '@8O @ !T@ ) ^! %VHp# @A!; l @ "t @ A@$A4@@@B @`!@`@E@5@@1h @Mc+@@M( @b @LM ܠfwupd-1.3.9/plugins/solokey/data/firmware-version.tdc000066400000000000000000000053641362775233600227510ustar00rootroot00000000000000TPDCI`?]     9{S `?]  l @@.   `  9!-} -`-  > @`8D@  D8@ l+#@@@=C@ `v`v@k`--o-k@^DA@ZA`k:la4G O= F`u,  @@3 T @ @@@F`6 8@`@Q @C  δ' %o@G!< ` @A`F*BA, @'@x@ADA@.@@3"EaEH@==EAC!&=G,   !BJn`= @N@@@Ft ~ `@K!!AY aA + `iȡBA,@x@ADAoAILcSCHq`= @@@@F! @A{b[+A : S +@DB~ S@x@ADA@=@@-rAMP@= +@=@IS@ #S @ =Ft`{ S@π@@F~S% @ÁVfU2F_V2hFIDO_2_0khmac-secretPvcԠBWsybrk A `A{@@}SŗaB S@x@ADA @`EAuAQ/T'A +^w`=H I&!I@3F )@ ~Ľ@@F#S @cSbupdplaticlientPin@YA !@(+$ B@B{@x@ADA@B EUvxAUXd z`=$!@쫀@ # @cdB[BArA"O  # +@DB@x@ADA$Eid{aL2Y\a% <Q}`="([@@@F `@'AA iA'  zd$( >@@|B i@x@% :A) @E}R~A]`@=* +@=)Ii @!+ `{",@@"iP@ CX ؘcבKhY{` XOtж _! (? [  *BA- @Bu ea i͡`BB i@x@ADA/ @`E@aLad?A0 :~`="1@u@ iX!c AkG7 QA2 (a$3 B@DB S@x@ADA4 @`E.goe4O5 +@=-=S6 @-fcaȓ"gM87@: ==`? k"~ :``7b)ٍCF5w@0d`=@=@7kդ6 @@` 0B @ `x5!` @ @<@$;`N<no e0 .@EE@$c MK@`@ ʢ  `" 3` `@ VH%܇@@@,/@  @w )2 WH  !""@@  B/S l K ysLA B ot`"a(er 2$3 0o2 6 3 9D 0#B r&DA&`𥁙aa+ ` `A4x@@]  #@@ @@0 <  b@ "B@`<  @@ @ME@5a 1K@B@a#bh@@ RQ@@7A]fwupd-1.3.9/plugins/solokey/data/lsusb.txt000066400000000000000000000111341362775233600206370ustar00rootroot00000000000000Bus 001 Device 011: ID 0483:a2ca STMicroelectronics Solo 2.3.0 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x0483 STMicroelectronics idProduct 0xa2ca bcdDevice 1.00 iManufacturer 1 SoloKeys iProduct 2 Solo 2.3.0 iSerial 3 2060329D304B bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0029 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 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 2 Solo 2.3.0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 34 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 0x01 EP 1 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Device Status: 0x0000 (Bus Powered) Bus 001 Device 013: ID 0483:a2ca STMicroelectronics Solo Bootloader 2.3.0 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x0483 STMicroelectronics idProduct 0xa2ca bcdDevice 1.00 iManufacturer 1 SoloKeys iProduct 2 Solo Bootloader 2.3.0 iSerial 3 2060329D304B bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0029 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 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 2 Solo Bootloader 2.3.0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 34 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 0x01 EP 1 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Device Status: 0x0000 (Bus Powered) fwupd-1.3.9/plugins/solokey/fu-plugin-solokey.c000066400000000000000000000007371362775233600216010ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-solokey-device.h" #include "fu-solokey-firmware.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_SOLOKEY_DEVICE); fu_plugin_add_firmware_gtype (plugin, "solokey", FU_TYPE_SOLOKEY_FIRMWARE); } fwupd-1.3.9/plugins/solokey/fu-solokey-device.c000066400000000000000000000357101362775233600215410ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-chunk.h" #include "fu-solokey-device.h" #include "fu-solokey-firmware.h" struct _FuSolokeyDevice { FuUsbDevice parent_instance; guint32 cid; }; G_DEFINE_TYPE (FuSolokeyDevice, fu_solokey_device, FU_TYPE_USB_DEVICE) #define SOLO_EXTENSION_VERSION 0x14 #define SOLO_BOOTLOADER_WRITE 0x40 #define SOLO_BOOTLOADER_DONE 0x41 #define SOLO_BOOTLOADER_VERSION 0x44 #define SOLO_BOOTLOADER_HID_CMD_BOOT 0x50 #define SOLO_USB_TIMEOUT 5000 /* ms */ #define SOLO_USB_HID_EP 0x0001 #define SOLO_USB_HID_EP_IN (SOLO_USB_HID_EP | 0x80) #define SOLO_USB_HID_EP_OUT (SOLO_USB_HID_EP | 0x00) #define SOLO_USB_HID_EP_SIZE 64 static void fu_solokey_device_exchange (GByteArray *req, guint8 cmd, guint32 addr, GByteArray *ibuf) { guint8 buf_addr[4] = { 0x00 }; guint8 buf_len[2] = { 0x00 }; /* command */ fu_byte_array_append_uint8 (req, cmd); /* first *3* bytes of the LE address */ fu_common_write_uint32 (buf_addr, addr, G_LITTLE_ENDIAN); g_byte_array_append (req, buf_addr, 3); /* "random" number :/ */ g_byte_array_append (req, (const guint8 *) "\x8C\x27\x90\xf6", 4); /* uint16 length then optional (or dummy) data */ if (ibuf != NULL) { fu_common_write_uint16 (buf_len, ibuf->len, G_BIG_ENDIAN); g_byte_array_append (req, buf_len, sizeof(buf_len)); g_byte_array_append (req, ibuf->data, ibuf->len); return; } /* dummy data */ fu_common_write_uint16 (buf_len, 16, G_BIG_ENDIAN); g_byte_array_append (req, buf_len, sizeof(buf_len)); for (guint i = 0; i < 16; i++) fu_byte_array_append_uint8 (req, 'A'); } static gboolean fu_solokey_device_probe (FuUsbDevice *device, GError **error) { /* always disregard the bcdVersion */ fu_device_set_version (FU_DEVICE (device), NULL, FWUPD_VERSION_FORMAT_UNKNOWN); return TRUE; } static gboolean fu_solokey_device_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); g_autofree gchar *product = NULL; g_auto(GStrv) split = NULL; /* got the version using the HID API */ if (!g_usb_device_set_configuration (usb_device, 0x0001, error)) return FALSE; if (!g_usb_device_claim_interface (usb_device, 0x0000, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { return FALSE; } /* parse product ID */ product = g_usb_device_get_string_descriptor (usb_device, g_usb_device_get_product_index (usb_device), error); if (product == NULL) return FALSE; split = g_strsplit (product, " ", -1); if (g_strv_length (split) < 2) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "product not parsable, got '%s'", product); return FALSE; } if (g_strcmp0 (split[0], "Solo") != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "product not expected format, got '%s'", product); return FALSE; } if (g_strcmp0 (split[1], "Hacker") == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Only Solo Secure supported"); return FALSE; } if (g_strcmp0 (split[1], "Bootloader") == 0) { fu_device_set_version_bootloader (FU_DEVICE (device), split[2]); fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_remove_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); } else if (g_strcmp0 (split[1], "Keys") == 0 && g_strcmp0 (split[2], "Solo") == 0) { fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_remove_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); } else { fu_device_set_version (FU_DEVICE (device), split[1], FWUPD_VERSION_FORMAT_TRIPLET); fu_device_remove_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); } /* success */ return TRUE; } static gboolean fu_solokey_device_close (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* rebind kernel driver so it works as a security key again... */ if (!g_usb_device_release_interface (usb_device, 0x0000, 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_solokey_device_packet_tx (FuSolokeyDevice *self, GByteArray *req, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_length = 0; /* round up to the endpoint size */ for (guint i = req->len; i < SOLO_USB_HID_EP_SIZE; i++) fu_byte_array_append_uint8 (req, 0x0); /* request */ if (g_getenv ("FWUPD_SOLOKEY_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "REQ", req->data, req->len, 16, FU_DUMP_FLAGS_SHOW_ADDRESSES); } /* do not hit hardware */ if (g_getenv ("FWUPD_SOLOKEY_EMULATE") != NULL) return TRUE; if (!g_usb_device_interrupt_transfer (usb_device, SOLO_USB_HID_EP_OUT, req->data, req->len, &actual_length, SOLO_USB_TIMEOUT, NULL, /* cancellable */ error)) { g_prefix_error (error, "failed to send request: "); return FALSE; } if (actual_length != req->len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "request not all sent, got %" G_GSIZE_FORMAT, actual_length); return FALSE; } /* success */ return TRUE; } static GByteArray * fu_solokey_device_packet_rx (FuSolokeyDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_length = 0; g_autoptr(GByteArray) res = g_byte_array_new (); guint8 buf[SOLO_USB_HID_EP_SIZE] = { 0x00 }; /* return anything */ if (g_getenv ("FWUPD_SOLOKEY_EMULATE") != NULL) return g_steal_pointer (&res); /* read reply */ if (!g_usb_device_interrupt_transfer (usb_device, SOLO_USB_HID_EP_IN, buf, sizeof(buf), &actual_length, SOLO_USB_TIMEOUT, NULL, /* cancellable */ error)) { g_prefix_error (error, "failed to get reply: "); return NULL; } if (g_getenv ("FWUPD_SOLOKEY_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "RES", buf, actual_length); /* copy back optional buf */ g_byte_array_append (res, buf, actual_length); return g_steal_pointer (&res); } static GByteArray * fu_solokey_device_packet (FuSolokeyDevice *self, guint8 cmd, GByteArray *payload, GError **error) { guint8 buf_cid[4] = { 0x00 }; guint8 buf_len[2] = { 0x00 }; guint8 cmd_id = cmd | 0x80; guint16 first_chunk_size; g_autoptr(GByteArray) req = g_byte_array_new (); g_autoptr(GByteArray) res = NULL; /* U2F header */ fu_common_write_uint32 (buf_cid, self->cid, G_LITTLE_ENDIAN); g_byte_array_append (req, buf_cid, sizeof(buf_cid)); g_byte_array_append (req, &cmd_id, sizeof(cmd_id)); /* no payload */ if (payload == NULL) { if (!fu_solokey_device_packet_tx (self, req, error)) return NULL; return fu_solokey_device_packet_rx (self, error); } /* sanity check */ if (payload->len > 7609) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "payload impossible size, got %x", payload->len); return NULL; } /* initialization packet */ first_chunk_size = payload->len; if (payload->len > SOLO_USB_HID_EP_SIZE - 7) first_chunk_size = SOLO_USB_HID_EP_SIZE - 7; fu_common_write_uint16 (buf_len, payload->len, G_BIG_ENDIAN); g_byte_array_append (req, buf_len, sizeof(buf_len)); g_byte_array_append (req, payload->data, first_chunk_size); if (!fu_solokey_device_packet_tx (self, req, error)) return NULL; /* continuation packets */ if (payload->len > first_chunk_size) { g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_new (payload->data + first_chunk_size, payload->len - first_chunk_size, 0x00, /* addr start */ 0x00, /* page_sz */ SOLO_USB_HID_EP_SIZE - 5); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); guint8 seq = chk->idx; g_autoptr(GByteArray) req2 = g_byte_array_new (); g_byte_array_append (req2, buf_cid, sizeof(buf_cid)); g_byte_array_append (req2, &seq, sizeof(seq)); g_byte_array_append (req2, chk->data, chk->data_sz); if (!fu_solokey_device_packet_tx (self, req2, error)) return NULL; } } /* do not hit hardware */ if (g_getenv ("FWUPD_SOLOKEY_EMULATE") != NULL) return g_byte_array_new (); /* success */ res = fu_solokey_device_packet_rx (self, error); if (res == NULL) return NULL; if (res->len != SOLO_USB_HID_EP_SIZE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "result invalid size, got %x", res->len); return NULL; } if (memcmp (res->data + 0, buf_cid, sizeof(buf_cid)) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "CID invalid, got %x", fu_common_read_uint32 (res->data + 0, G_BIG_ENDIAN)); return NULL; } if (res->data[4] != cmd_id) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "command ID invalid, got %x", res->data[4]); return NULL; } return g_steal_pointer (&res); } static gboolean fu_solokey_device_setup_cid (FuSolokeyDevice *self, GError **error) { g_autoptr(GByteArray) nonce = g_byte_array_new (); g_autoptr(GByteArray) res = NULL; /* do not hit hardware */ if (g_getenv ("FWUPD_SOLOKEY_EMULATE") != NULL) return TRUE; /* get a channel ID */ for (guint i = 0; i < 8; i++) fu_byte_array_append_uint8 (nonce, g_random_int_range (0x00, 0xff)); res = fu_solokey_device_packet (self, 0x06, nonce, error); if (res == NULL) return FALSE; if (fu_common_read_uint16 (res->data + 5, G_LITTLE_ENDIAN) < 0x11) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "INIT length invalid"); return FALSE; } if (memcmp (res->data + 7, nonce->data, 8) != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "nonce invalid"); return FALSE; } self->cid = fu_common_read_uint32 (res->data + 7 + 8, G_LITTLE_ENDIAN); g_debug ("CID to use for device: %04x", self->cid); return TRUE; } static gboolean fu_solokey_device_get_version_bl (FuSolokeyDevice *self, GError **error) { g_autofree gchar *version = NULL; g_autoptr(GByteArray) req = g_byte_array_new (); g_autoptr(GByteArray) res = NULL; /* pass through data */ fu_solokey_device_exchange (req, SOLO_BOOTLOADER_VERSION, 0x00, NULL); res = fu_solokey_device_packet (self, SOLO_BOOTLOADER_HID_CMD_BOOT, req, error); if (res == NULL) return FALSE; version = g_strdup_printf ("%u.%u.%u", res->data[8], res->data[9], res->data[10]); fu_device_set_version_bootloader (FU_DEVICE (self), version); return TRUE; } static gboolean fu_solokey_device_setup (FuDevice *device, GError **error) { FuSolokeyDevice *self = FU_SOLOKEY_DEVICE (device); /* get channel ID */ if (!fu_solokey_device_setup_cid (self, error)) return FALSE; /* verify version */ if (fu_device_has_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!fu_solokey_device_get_version_bl (self, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_solokey_device_verify (FuSolokeyDevice *self, GBytes *fw_sig, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new (); g_autoptr(GByteArray) res = NULL; g_autoptr(GByteArray) sig = g_byte_array_new (); fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY); g_byte_array_append (sig, g_bytes_get_data (fw_sig, NULL), g_bytes_get_size (fw_sig)); fu_solokey_device_exchange (req, SOLO_BOOTLOADER_DONE, 0x00, sig); res = fu_solokey_device_packet (self, SOLO_BOOTLOADER_HID_CMD_BOOT, req, error); if (res == NULL) return FALSE; return TRUE; } static FuFirmware * fu_solokey_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_solokey_firmware_new (); fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; return g_steal_pointer (&firmware); } static gboolean fu_solokey_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuSolokeyDevice *self = FU_SOLOKEY_DEVICE (device); g_autoptr(FuFirmwareImage) img = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw_sig = NULL; g_autoptr(GPtrArray) chunks = NULL; /* get main image */ img = fu_firmware_get_image_by_id (firmware, NULL, error); if (img == NULL) return FALSE; /* build packets */ fw = fu_firmware_image_write (img, error); if (fw == NULL) return FALSE; chunks = fu_chunk_array_new_from_bytes (fw, fu_firmware_image_get_addr (img), 0x00, /* page_sz */ 2048); /* 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); g_autoptr(GByteArray) buf = g_byte_array_new (); g_autoptr(GByteArray) req = g_byte_array_new (); g_autoptr(GByteArray) res = NULL; g_autoptr(GError) error_local = NULL; g_byte_array_append (buf, chk->data, chk->data_sz); fu_solokey_device_exchange (req, SOLO_BOOTLOADER_WRITE, chk->address, buf); res = fu_solokey_device_packet (self, SOLO_BOOTLOADER_HID_CMD_BOOT, req, &error_local); if (res == NULL) { 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); } /* verify the signature and reboot back to runtime */ fw_sig = fu_firmware_get_image_by_id_bytes (firmware, FU_FIRMWARE_IMAGE_ID_SIGNATURE, error); if (fw_sig == NULL) return FALSE; return fu_solokey_device_verify (self, fw_sig, error); } static void fu_solokey_device_init (FuSolokeyDevice *self) { self->cid = 0xffffffff; fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG); fu_device_set_protocol (FU_DEVICE (self), "com.solokeys"); fu_device_set_name (FU_DEVICE (self), "Solo Secure"); fu_device_set_summary (FU_DEVICE (self), "An open source FIDO2 security key"); fu_device_add_icon (FU_DEVICE (self), "applications-internet"); } static void fu_solokey_device_class_init (FuSolokeyDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->write_firmware = fu_solokey_device_write_firmware; klass_device->prepare_firmware = fu_solokey_device_prepare_firmware; klass_device->setup = fu_solokey_device_setup; klass_usb_device->open = fu_solokey_device_open; klass_usb_device->close = fu_solokey_device_close; klass_usb_device->probe = fu_solokey_device_probe; } fwupd-1.3.9/plugins/solokey/fu-solokey-device.h000066400000000000000000000004541362775233600215430ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_SOLOKEY_DEVICE (fu_solokey_device_get_type ()) G_DECLARE_FINAL_TYPE (FuSolokeyDevice, fu_solokey_device, FU, SOLOKEY_DEVICE, FuUsbDevice) fwupd-1.3.9/plugins/solokey/fu-solokey-firmware.c000066400000000000000000000060731362775233600221160ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "fu-ihex-firmware.h" #include "fu-solokey-firmware.h" struct _FuSolokeyFirmware { FuFirmware parent_instance; }; G_DEFINE_TYPE (FuSolokeyFirmware, fu_solokey_firmware, FU_TYPE_FIRMWARE) static GBytes * _g_base64_decode_to_bytes (const gchar *text) { gsize out_len = 0; guchar *out = g_base64_decode (text, &out_len); return g_bytes_new_take ((guint8 *) out, out_len); } static gboolean fu_solokey_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { JsonNode *json_root; JsonObject *json_obj; const gchar *base64; g_autoptr(FuFirmware) ihex_firmware = fu_ihex_firmware_new (); g_autoptr(FuFirmwareImage) img = NULL; g_autoptr(FuFirmwareImage) img_sig = fu_firmware_image_new (NULL); g_autoptr(GBytes) fw_ihex = NULL; g_autoptr(GBytes) fw_sig = NULL; g_autoptr(GString) base64_websafe = NULL; g_autoptr(JsonParser) parser = json_parser_new (); /* parse JSON */ if (!json_parser_load_from_data (parser, (const gchar *) g_bytes_get_data (fw, NULL), (gssize) g_bytes_get_size (fw), error)) { g_prefix_error (error, "firmware not in JSON format: "); return FALSE; } json_root = json_parser_get_root (parser); json_obj = json_node_get_object (json_root); if (!json_object_has_member (json_obj, "firmware")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "JSON invalid as has no 'firmware'"); return FALSE; } if (!json_object_has_member (json_obj, "signature")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "JSON invalid as has no 'signature'"); return FALSE; } /* decode */ base64 = json_object_get_string_member (json_obj, "firmware"); fw_ihex = _g_base64_decode_to_bytes (base64); if (!fu_firmware_parse (ihex_firmware, fw_ihex, flags, error)) return FALSE; img = fu_firmware_get_image_default (ihex_firmware, error); if (img == NULL) return FALSE; fu_firmware_add_image (firmware, img); /* signature */ base64_websafe = g_string_new (json_object_get_string_member (json_obj, "signature")); fu_common_string_replace (base64_websafe, "-", "+"); fu_common_string_replace (base64_websafe, "_", "/"); g_string_append (base64_websafe, "=="); fw_sig = _g_base64_decode_to_bytes (base64_websafe->str); fu_firmware_image_set_bytes (img_sig, fw_sig); fu_firmware_image_set_id (img_sig, FU_FIRMWARE_IMAGE_ID_SIGNATURE); fu_firmware_add_image (firmware, img_sig); return TRUE; } static void fu_solokey_firmware_init (FuSolokeyFirmware *self) { } static void fu_solokey_firmware_class_init (FuSolokeyFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->parse = fu_solokey_firmware_parse; } FuFirmware * fu_solokey_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_SOLOKEY_FIRMWARE, NULL)); } fwupd-1.3.9/plugins/solokey/fu-solokey-firmware.h000066400000000000000000000005451362775233600221210ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_SOLOKEY_FIRMWARE (fu_solokey_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuSolokeyFirmware, fu_solokey_firmware, FU, SOLOKEY_FIRMWARE, FuFirmware) FuFirmware *fu_solokey_firmware_new (void); fwupd-1.3.9/plugins/solokey/meson.build000066400000000000000000000010521362775233600201750ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginSoloKey"'] install_data([ 'solokey.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_solokey', fu_hash, sources : [ 'fu-solokey-device.c', 'fu-solokey-firmware.c', 'fu-plugin-solokey.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, libjsonglib, ], ) fwupd-1.3.9/plugins/solokey/solokey.quirk000066400000000000000000000001321362775233600205730ustar00rootroot00000000000000# SoloKey1 [DeviceInstanceId=USB\VID_0483&PID_A2CA] Plugin = solokey InstallDuration = 20 fwupd-1.3.9/plugins/steelseries/000077500000000000000000000000001362775233600166775ustar00rootroot00000000000000fwupd-1.3.9/plugins/steelseries/README.md000066400000000000000000000010141362775233600201520ustar00rootroot00000000000000SteelSeries 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` Vendor ID Security ------------------ The device is not upgradable and thus requires no vendor ID set. fwupd-1.3.9/plugins/steelseries/fu-plugin-steelseries.c000066400000000000000000000005761362775233600233060ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-steelseries-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_STEELSERIES_DEVICE); } fwupd-1.3.9/plugins/steelseries/fu-steelseries-device.c000066400000000000000000000063751362775233600232520ustar00rootroot00000000000000/* * 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; } fwupd-1.3.9/plugins/steelseries/fu-steelseries-device.h000066400000000000000000000006201362775233600232420ustar00rootroot00000000000000/* * Copyright (C) 2016-2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #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; }; fwupd-1.3.9/plugins/steelseries/meson.build000066400000000000000000000007761362775233600210530ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/steelseries/steelseries.quirk000066400000000000000000000001771362775233600223100ustar00rootroot00000000000000# Rival 100 [DeviceInstanceId=USB\VID_1038&PID_1702] Plugin = steelseries Summary = An optical gaming mouse Icon = input-mouse fwupd-1.3.9/plugins/superio/000077500000000000000000000000001362775233600160365ustar00rootroot00000000000000fwupd-1.3.9/plugins/superio/README.md000066400000000000000000000016621362775233600173220ustar00rootroot00000000000000SuperIO ======= 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` Vendor ID Security ------------------ The vendor ID is set from the baseboard vendor, for example `DMI:Star Labs` fwupd-1.3.9/plugins/superio/fu-plugin-superio.c000066400000000000000000000064271362775233600216050ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.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) { const gchar *dmi_vendor; 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, "device-file", "/dev/port", "chipset", chipset, "id", id, "port", port, NULL); } else if (id >> 8 == 0x89) { dev = g_object_new (FU_TYPE_SUPERIO_IT89_DEVICE, "device-file", "/dev/port", "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; } /* set vendor ID as the motherboard vendor */ dmi_vendor = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BASEBOARD_MANUFACTURER); if (dmi_vendor != NULL) { g_autofree gchar *vendor_id = g_strdup_printf ("DMI:%s", dmi_vendor); fu_device_set_vendor_id (FU_DEVICE (dev), vendor_id); } /* 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); } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { GPtrArray *hwids; if (fu_common_kernel_locked_down ()) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported when kernel locked down"); return FALSE; } 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; } fwupd-1.3.9/plugins/superio/fu-superio-common.c000066400000000000000000000025161362775233600215720ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-superio-common.h" 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.3.9/plugins/superio/fu-superio-common.h000066400000000000000000000075131362775233600216010ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" /* 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); fwupd-1.3.9/plugins/superio/fu-superio-device.c000066400000000000000000000314621362775233600215430ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-superio-common.h" #include "fu-superio-device.h" #define FU_PLUGIN_SUPERIO_TIMEOUT 0.25 /* s */ typedef struct { gchar *chipset; guint16 port; guint16 pm1_iobad0; guint16 pm1_iobad1; guint16 id; } FuSuperioDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuSuperioDevice, fu_superio_device, FU_TYPE_UDEV_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_udev_device_pwrite (FU_UDEV_DEVICE (self), priv->port, addr, error)) return FALSE; if (!fu_udev_device_pread (FU_UDEV_DEVICE (self), 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_udev_device_pwrite (FU_UDEV_DEVICE (self), priv->port, addr, error)) return FALSE; if (!fu_udev_device_pwrite (FU_UDEV_DEVICE (self), 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, guint idt, GString *str) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); FuSuperioDevicePrivate *priv = GET_PRIVATE (self); fu_common_string_append_kv (str, idt, "Chipset", priv->chipset); fu_common_string_append_kx (str, idt, "Id", priv->id); fu_common_string_append_kx (str, idt, "Port", priv->port); fu_common_string_append_kx (str, idt, "PM1_IOBAD0", priv->pm1_iobad0); fu_common_string_append_kx (str, idt, "PM1_IOBAD1", 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_udev_device_pread (FU_UDEV_DEVICE (self), 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_udev_device_pread (FU_UDEV_DEVICE (self), 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_udev_device_pwrite (FU_UDEV_DEVICE (self), 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_udev_device_pwrite (FU_UDEV_DEVICE (self), 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_udev_device_pread (FU_UDEV_DEVICE (self), priv->pm1_iobad1, &status, error)) return FALSE; if ((status & SIO_STATUS_EC_OBF) == 0) break; if (!fu_udev_device_pread (FU_UDEV_DEVICE (self), 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_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); /* 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 FuFirmware * 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 fu_firmware_new_from_bytes (fw); } } g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "did not detect signature in firmware image"); return NULL; } 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_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_protocol (FU_DEVICE (self), "tw.com.ite.superio"); 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->probe = fu_superio_device_probe; klass_device->setup = fu_superio_device_setup; klass_device->prepare_firmware = fu_superio_device_prepare_firmware; } fwupd-1.3.9/plugins/superio/fu-superio-device.h000066400000000000000000000024361362775233600215470ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_SUPERIO_DEVICE (fu_superio_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuSuperioDevice, fu_superio_device, FU, SUPERIO_DEVICE, FuUdevDevice) struct _FuSuperioDeviceClass { FuUdevDeviceClass 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); fwupd-1.3.9/plugins/superio/fu-superio-it85-device.c000066400000000000000000000037701362775233600223330ustar00rootroot00000000000000/* * 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.3.9/plugins/superio/fu-superio-it85-device.h000066400000000000000000000005251362775233600223330ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-superio-device.h" #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) fwupd-1.3.9/plugins/superio/fu-superio-it89-device.c000066400000000000000000000462741362775233600223450ustar00rootroot00000000000000/* * 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 FuFirmware * 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; g_autoptr(GBytes) fw = 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); fw = fu_plugin_superio_fix_signature (self, blob, error); return fu_firmware_new_from_bytes (fw); } 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 (device, 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, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE (device); guint8 id[4] = { 0x0 }; g_autoptr(GBytes) fw_fixed = NULL; g_autoptr(GBytes) fw = 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; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) 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.3.9/plugins/superio/fu-superio-it89-device.h000066400000000000000000000005251362775233600223370ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-superio-device.h" #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) fwupd-1.3.9/plugins/superio/meson.build000066400000000000000000000011201362775233600201720ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/superio/superio.quirk000066400000000000000000000012311362775233600205760ustar00rootroot00000000000000# 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.3.9/plugins/synaptics-cxaudio/000077500000000000000000000000001362775233600200175ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-cxaudio/README.md000066400000000000000000000026231362775233600213010ustar00rootroot00000000000000Conexant Audio Support ====================== Introduction ------------ This plugin is used to update a small subset of Conexant (now owned by Synaptics) audio devices. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in a modified SREC file format. This plugin supports the following protocol ID: * com.synaptics.synaptics-cxaudio GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_17EF&PID_3083&REV_0001` * `USB\VID_17EF&PID_3083` * `USB\VID_17EF` These devices also use custom GUID values, e.g. * `SYNAPTICS_CXAUDIO\CX2198X` * `SYNAPTICS_CXAUDIO\CX21985` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x17EF` Quirk Use --------- This plugin uses the following plugin-specific quirks: | Quirk | Description | Minimum fwupd version | |----------------------------|----------------------------------|-----------------------| | `ChipIdBase` | Base integer for ChipID | 1.3.2 | | `IsSoftwareResetSupported` | If the chip supports self-reset | 1.3.2 | | `EepromPatchValidAddr` | Address of patch location #1 | 1.3.2 | | `EepromPatch2ValidAddr` | Address of patch location #2 | 1.3.2 | fwupd-1.3.9/plugins/synaptics-cxaudio/fu-plugin-synaptics-cxaudio.c000066400000000000000000000010101362775233600255260ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-synaptics-cxaudio-device.h" #include "fu-synaptics-cxaudio-firmware.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_SYNAPTICS_CXAUDIO_DEVICE); fu_plugin_add_firmware_gtype (plugin, "conexant", FU_TYPE_SYNAPTICS_CXAUDIO_FIRMWARE); } fwupd-1.3.9/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-common.h000066400000000000000000000105371362775233600255430ustar00rootroot00000000000000/* * Copyright (C) 2005-2019 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" /* usb */ #define FU_SYNAPTICS_CXAUDIO_INPUT_REPORT_SIZE 35 #define FU_SYNAPTICS_CXAUDIO_OUTPUT_REPORT_SIZE 39 #define FU_SYNAPTICS_CXAUDIO_HID_INTERFACE 0x03 #define FU_SYNAPTICS_CXAUDIO_USB_TIMEOUT 2000 /* ms */ /* commands */ #define FU_SYNAPTICS_CXAUDIO_MEM_WRITEID 0x4 #define FU_SYNAPTICS_CXAUDIO_MEM_READID 0x5 typedef enum { FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_UNKNOWN, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX20562 = 20562, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070x = 20700, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2077x = 20770, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2076x = 20760, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2085x = 20850, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2089x = 20890, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2098x = 20980, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2198x = 21980, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_LAST } FuSynapticsCxaudioDeviceKind; typedef enum { FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_ROM, FU_SYNAPTICS_CXAUDIO_MEM_KIND_LAST } FuSynapticsCxaudioMemKind; /* EEPROM */ #define FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET 0x0000 #define FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET 0x0020 #define FU_SYNAPTICS_CXAUDIO_EEPROM_CPX_PATCH_VERSION_ADDRESS 0x0022 #define FU_SYNAPTICS_CXAUDIO_EEPROM_CPX_PATCH2_VERSION_ADDRESS 0x0176 #define FU_SYNAPTICS_CXAUDIO_EEPROM_STORAGE_SIZE_ADDRESS 0x0005 #define FU_SYNAPTICS_CXAUDIO_EEPROM_STORAGE_PADDING_SIZE 0x4 /* bytes */ #define FU_SYNAPTICS_CXAUDIO_DEVICE_CAPABILITIES_STRIDX 50 #define FU_SYNAPTICS_CXAUDIO_DEVICE_CAPABILITIES_BYTE 0x03 #define FU_SYNAPTICS_CXAUDIO_MAGIC_BYTE 'L' #define FU_SYNAPTICS_CXAUDIO_SIGNATURE_BYTE 'S' #define FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE 'P' #define FU_SYNAPTICS_CXAUDIO_REG_FIRMWARE_PARK_ADDR 0x1000 #define FU_SYNAPTICS_CXAUDIO_REG_FIRMWARE_VERSION_ADDR 0x1001 #define FU_SYNAPTICS_CXAUDIO_REG_RESET_ADDR 0x0400 #define FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE (8 * 1024) typedef guint16 FuSynapticsCxaudioEepromPtr; typedef struct __attribute__ ((packed)) { FuSynapticsCxaudioEepromPtr PatchVersionStringAddress; guint8 CpxPatchVersion[3]; guint8 SpxPatchVersion[4]; guint8 LayoutSignature; guint8 LayoutVersion; guint8 ApplicationStatus; guint16 VendorID; guint16 ProductID; guint16 RevisionID; FuSynapticsCxaudioEepromPtr LanguageStringAddress; FuSynapticsCxaudioEepromPtr ManufacturerStringAddress; FuSynapticsCxaudioEepromPtr ProductStringAddress; FuSynapticsCxaudioEepromPtr SerialNumberStringAddress; } FuSynapticsCxaudioEepromCustomInfo; #define FU_SYNAPTICS_CXAUDIO_EEPROM_APP_STATUS_ADDRESS (FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + G_STRUCT_OFFSET(FuSynapticsCxaudioEepromCustomInfo, ApplicationStatus)) #define FU_SYNAPTICS_CXAUDIO_EEPROM_LAYOUT_SIGNATURE_ADDRESS (FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + G_STRUCT_OFFSET(FuSynapticsCxaudioEepromCustomInfo, LayoutSignature)) #define FU_SYNAPTICS_CXAUDIO_EEPROM_LAYOUT_VERSION_ADDRESS (FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + G_STRUCT_OFFSET(FuSynapticsCxaudioEepromCustomInfo, LayoutVersion)) typedef struct __attribute__ ((packed)) { guint8 Length; guint8 Type; } FuSynapticsCxaudioEepromStringHeader; typedef struct __attribute__ ((packed)) { guint8 PatchSignature; FuSynapticsCxaudioEepromPtr PatchAddress; } FuSynapticsCxaudioEepromPatchInfo; typedef struct __attribute__ ((packed)) { guint8 MagicByte; guint8 EeepromSizeCode; } FuSynapticsCxaudioEepromValiditySignature; #define FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET 0x0014 #define FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_SIZE (sizeof(FuSynapticsCxaudioEepromPatchInfo)) #define FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_SIGNATURE_ADDRESS (FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET + G_STRUCT_OFFSET(FuSynapticsCxaudioEepromPatchInfo, PatchSignature)) #define FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_PTR_ADDRESS (FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET + G_STRUCT_OFFSET(FuSynapticsCxaudioEepromPatchInfo, PatchAddress)) #define FU_SYNAPTICS_CXAUDIO_FIRMWARE_SIGNATURE_OFFSET (FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET + sizeof(FuSynapticsCxaudioEepromValiditySignature)) fwupd-1.3.9/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-device.c000066400000000000000000000710001362775233600254750ustar00rootroot00000000000000/* * Copyright (C) 2005-2019 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-chunk.h" #include "fu-srec-firmware.h" #include "fu-synaptics-cxaudio-common.h" #include "fu-synaptics-cxaudio-device.h" #include "fu-synaptics-cxaudio-firmware.h" struct _FuSynapticsCxaudioDevice { FuUsbDevice parent_instance; guint32 chip_id_base; guint32 chip_id; gboolean serial_number_set; gboolean sw_reset_supported; guint32 eeprom_layout_version; guint32 eeprom_patch2_valid_addr; guint32 eeprom_patch_valid_addr; guint32 eeprom_storage_address; guint32 eeprom_storage_sz; guint32 eeprom_sz; guint8 patch_level; }; G_DEFINE_TYPE (FuSynapticsCxaudioDevice, fu_synaptics_cxaudio_device, FU_TYPE_USB_DEVICE) static void fu_synaptics_cxaudio_device_to_string (FuDevice *device, guint idt, GString *str) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE (device); fu_common_string_append_ku (str, idt, "ChipIdBase", self->chip_id_base); fu_common_string_append_ku (str, idt, "ChipId", self->chip_id); fu_common_string_append_kx (str, idt, "EepromLayoutVersion", self->eeprom_layout_version); fu_common_string_append_kx (str, idt, "EepromStorageAddress", self->eeprom_storage_address); fu_common_string_append_kx (str, idt, "EepromStorageSz", self->eeprom_storage_sz); fu_common_string_append_kx (str, idt, "EepromSz", self->eeprom_sz); fu_common_string_append_kb (str, idt, "SwResetSupported", self->sw_reset_supported); fu_common_string_append_kb (str, idt, "SerialNumberSet", self->serial_number_set); } static gboolean fu_synaptics_cxaudio_device_open (FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (device); /* get firmware version */ if (!g_usb_device_claim_interface (usb_device, FU_SYNAPTICS_CXAUDIO_HID_INTERFACE, 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_synaptics_cxaudio_device_output_report (FuSynapticsCxaudioDevice *self, guint8 *buf, guint16 bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); guint16 report_number = buf[0]; gsize actual_length = 0; g_return_val_if_fail (buf != NULL, FALSE); g_return_val_if_fail (bufsz != 0, FALSE); /* weird */ if (report_number == 0x0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "report 0 not supported"); return FALSE; } /* to device */ if (g_getenv ("FWUPD_SYNAPTICS_CXAUDIO_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "HID::WRITE", buf, bufsz); 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, FU_HID_REPORT_SET, (FU_HID_REPORT_TYPE_OUTPUT << 8) | report_number, FU_SYNAPTICS_CXAUDIO_HID_INTERFACE, buf, bufsz, &actual_length, FU_SYNAPTICS_CXAUDIO_USB_TIMEOUT, NULL, error)) { return FALSE; } if (bufsz != actual_length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "wrote 0x%x bytes of 0x%x", (guint) actual_length, bufsz); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_cxaudio_device_input_report (FuSynapticsCxaudioDevice *self, guint8 ReportID, guint8 *buf, guint16 bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_length = 0; g_return_val_if_fail (buf != NULL, FALSE); g_return_val_if_fail (bufsz != 0, FALSE); /* from device */ 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, FU_HID_REPORT_GET, (FU_HID_REPORT_TYPE_INPUT << 8) | ReportID, FU_SYNAPTICS_CXAUDIO_HID_INTERFACE, buf, bufsz, &actual_length, FU_SYNAPTICS_CXAUDIO_USB_TIMEOUT, NULL, error)) { return FALSE; } if (g_getenv ("FWUPD_SYNAPTICS_CXAUDIO_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "HID::READ", buf, bufsz); if (bufsz != actual_length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "read 0x%x bytes of expected 0x%x", (guint) actual_length, bufsz); return FALSE; } /* success */ return TRUE; } typedef enum { FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_OPERATION_LAST } FuSynapticsCxaudioOperation; typedef enum { FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE = 0, FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_VERIFY = (1 << 4), } FuSynapticsCxaudioOperationFlags; static gboolean fu_synaptics_cxaudio_device_operation (FuSynapticsCxaudioDevice *self, FuSynapticsCxaudioOperation operation, FuSynapticsCxaudioMemKind mem_kind, guint32 addr, guint8 *buf, guint32 bufsz, FuSynapticsCxaudioOperationFlags flags, GError **error) { const guint32 idx_read = 0x1; const guint32 idx_write = 0x5; const guint32 payload_max = 0x20; guint32 size = 0x02800; g_autoptr(GPtrArray) chunks = NULL; g_return_val_if_fail (bufsz > 0, FALSE); g_return_val_if_fail (buf != NULL, FALSE); /* check if memory operation is supported by device */ if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE && mem_kind == FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_ROM) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "trying to write unwritable section %u", mem_kind); return FALSE; } /* check memory address - should be within valid range */ if (mem_kind == FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM) size = 0x20000; if (addr > size) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "address out of range 0x%x < 0x%x", addr, size); return FALSE; } /* send to hardware */ chunks = fu_chunk_array_new (buf, bufsz, addr, 0x0, payload_max); for (guint i = 0; i < chunks->len; i++) { FuChunk *chunk = g_ptr_array_index (chunks, i); guint8 inbuf[FU_SYNAPTICS_CXAUDIO_INPUT_REPORT_SIZE] = { 0 }; guint8 outbuf[FU_SYNAPTICS_CXAUDIO_OUTPUT_REPORT_SIZE] = { 0 }; /* first byte is always report ID */ outbuf[0] = FU_SYNAPTICS_CXAUDIO_MEM_WRITEID; /* set memory address and payload length (if relevant) */ if (chunk->address >= 64 * 1024) outbuf[1] |= 1 << 4; outbuf[2] = chunk->data_sz; fu_common_write_uint16 (outbuf + 3, chunk->address, G_BIG_ENDIAN); /* set memtype */ if (mem_kind == FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM) outbuf[1] |= 1 << 5; /* fill the report payload part */ if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE) { outbuf[1] |= 1 << 6; if (!fu_memcpy_safe (outbuf, sizeof(outbuf), idx_write, /* dst */ chunk->data, chunk->data_sz, 0x0, /* src */ chunk->data_sz, error)) return FALSE; } if (!fu_synaptics_cxaudio_device_output_report (self, outbuf, sizeof(outbuf), error)) return FALSE; /* issue additional write directive to read */ if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE && flags & FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_VERIFY) { outbuf[1] &= ~(1 << 6); if (!fu_synaptics_cxaudio_device_output_report (self, outbuf, sizeof(outbuf), error)) return FALSE; } if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_READ || flags & FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_VERIFY) { if (!fu_synaptics_cxaudio_device_input_report (self, FU_SYNAPTICS_CXAUDIO_MEM_READID, inbuf, sizeof(inbuf), error)) return FALSE; } if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE && flags & FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_VERIFY) { if (!fu_common_bytes_compare_raw (outbuf + idx_write, payload_max, inbuf + idx_read, payload_max, error)) { g_prefix_error (error, "failed to verify on packet %u @0x%x: ", chunk->idx, chunk->address); return FALSE; } } if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_READ) { if (!fu_memcpy_safe ((guint8 *) chunk->data, chunk->data_sz, 0x0, /* dst */ inbuf, sizeof(inbuf), idx_read, /* src */ chunk->data_sz, error)) return FALSE; } } /* success */ return TRUE; } static gboolean fu_synaptics_cxaudio_device_register_clear_bit (FuSynapticsCxaudioDevice *self, guint32 address, guint8 bit_position, GError **error) { guint8 tmp = 0x0; if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, address, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) return FALSE; tmp &= ~(1 << bit_position); return fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, address, &tmp, sizeof(guint8), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error); } static gboolean fu_synaptics_cxaudio_device_register_set_bit (FuSynapticsCxaudioDevice *self, guint32 address, guint8 bit_position, GError **error) { guint8 tmp = 0x0; if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, address, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) return FALSE; tmp |= 1 << bit_position; return fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, address, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error); } static gchar * fu_synaptics_cxaudio_device_eeprom_read_string (FuSynapticsCxaudioDevice *self, guint32 address, GError **error) { FuSynapticsCxaudioEepromStringHeader header = { 0 }; g_autofree gchar *str = NULL; /* read header */ if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, address, (guint8 *) &header, sizeof(header), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read EEPROM string header @0x%x: ", address); return NULL; } /* sanity check */ if (header.Type != FU_SYNAPTICS_CXAUDIO_DEVICE_CAPABILITIES_BYTE) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM string header type invalid"); return NULL; } if (header.Length < sizeof(header)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM string header length invalid"); return NULL; } /* allocate buffer + NUL terminator */ str = g_malloc0 (header.Length - sizeof(header) + 1); if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, address + sizeof(header), (guint8 *) str, header.Length - sizeof(header), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read EEPROM string @0x%x: ", address); return NULL; } return g_steal_pointer (&str); } static gboolean fu_synaptics_cxaudio_device_reset (FuSynapticsCxaudioDevice *self, GError **error) { guint8 tmp = 1 << 6; g_autoptr(GError) error_local = NULL; /* is disabled on EVK board using jumper */ if (!self->sw_reset_supported) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "software reset is not supported"); return FALSE; } /* this fails on success */ if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, FU_SYNAPTICS_CXAUDIO_REG_RESET_ADDR, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { if (g_error_matches (error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { return TRUE; } g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } return TRUE; } static gboolean fu_synaptics_cxaudio_device_ensure_patch_level (FuSynapticsCxaudioDevice *self, GError **error) { guint8 tmp = 0x0; if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, self->eeprom_patch_valid_addr, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read EEPROM patch validation byte: "); return FALSE; } if (tmp == FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE) { self->patch_level = 1; return TRUE; } if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, self->eeprom_patch2_valid_addr, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read EEPROM patch validation byte: "); return FALSE; } if (tmp == FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE) { self->patch_level = 2; return TRUE; } /* not sure what to do here */ g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM patch version undiscoverable"); return FALSE; } static gboolean fu_synaptics_cxaudio_device_setup (FuDevice *device, GError **error) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE (device); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); FuSynapticsCxaudioEepromCustomInfo cinfo = { 0x0 }; guint32 addr; guint8 chip_id_offset = 0x0; guint8 sigbuf[2] = { 0x0 }; guint8 verbuf_fw[4] = { 0x0 }; guint8 verbuf_patch[3] = { 0x0 }; g_autofree gchar *cap_str = NULL; g_autofree gchar *chip_id = NULL; g_autofree gchar *summary = NULL; g_autofree gchar *version_fw = NULL; g_autofree gchar *version_patch = NULL; /* get the ChipID */ if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, 0x1005, &chip_id_offset, sizeof(chip_id_offset), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read ChipID: "); return FALSE; } self->chip_id = self->chip_id_base + chip_id_offset; chip_id = g_strdup_printf ("SYNAPTICS_CXAUDIO\\CX%u", self->chip_id); fu_device_add_instance_id (device, chip_id); /* set summary */ summary = g_strdup_printf ("CX%u USB audio device", self->chip_id); fu_device_set_summary (device, summary); /* read the EEPROM validity signature */ if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET, sigbuf, sizeof(sigbuf), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read EEPROM signature bytes: "); return FALSE; } /* blank EEPROM */ if (sigbuf[0] == 0xff && sigbuf[1] == 0xff) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM is missing or blank"); return FALSE; } /* is disabled on EVK board using jumper */ if ((sigbuf[0] == 0x00 && sigbuf[1] == 0x00) || (sigbuf[0] == 0xff && sigbuf[1] == 0x00)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM has been disabled using a jumper"); return FALSE; } /* check magic byte */ if (sigbuf[0] != FU_SYNAPTICS_CXAUDIO_MAGIC_BYTE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM magic byte invalid, got 0x%02x expected 0x%02x", sigbuf[0], (guint) FU_SYNAPTICS_CXAUDIO_MAGIC_BYTE); return FALSE; } /* calculate EEPROM size */ self->eeprom_sz = (guint32) 1 << (sigbuf[1] + 8); if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_STORAGE_SIZE_ADDRESS, sigbuf, sizeof(sigbuf), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read EEPROM signature bytes: "); return FALSE; } self->eeprom_storage_sz = fu_common_read_uint16 (sigbuf, G_LITTLE_ENDIAN); if (self->eeprom_storage_sz < self->eeprom_sz - FU_SYNAPTICS_CXAUDIO_EEPROM_STORAGE_PADDING_SIZE) { self->eeprom_storage_address = self->eeprom_sz - \ self->eeprom_storage_sz - \ FU_SYNAPTICS_CXAUDIO_EEPROM_STORAGE_PADDING_SIZE; } /* get EEPROM custom info */ if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET, (guint8 *) &cinfo, sizeof(cinfo), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read EEPROM custom info: "); return FALSE; } if (cinfo.LayoutSignature == FU_SYNAPTICS_CXAUDIO_SIGNATURE_BYTE) self->eeprom_layout_version = cinfo.LayoutVersion; g_debug ("CpxPatchVersion: %u.%u.%u", cinfo.CpxPatchVersion[0], cinfo.CpxPatchVersion[1], cinfo.CpxPatchVersion[2]); g_debug ("SpxPatchVersion: %u.%u.%u.%u", cinfo.SpxPatchVersion[0], cinfo.SpxPatchVersion[1], cinfo.SpxPatchVersion[2], cinfo.SpxPatchVersion[3]); g_debug ("VendorID: 0x%04x", cinfo.VendorID); g_debug ("ProductID: 0x%04x", cinfo.ProductID); g_debug ("RevisionID: 0x%04x", cinfo.RevisionID); g_debug ("ApplicationStatus: 0x%02x", cinfo.ApplicationStatus); /* serial number, which also allows us to recover it after write */ if (self->eeprom_layout_version >= 0x01) { self->serial_number_set = cinfo.SerialNumberStringAddress != 0x0; if (self->serial_number_set) { g_autofree gchar *tmp = NULL; tmp = fu_synaptics_cxaudio_device_eeprom_read_string (self, cinfo.SerialNumberStringAddress, error); if (tmp == NULL) return FALSE; fu_device_set_serial (device, tmp); } } /* read fw version */ if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, FU_SYNAPTICS_CXAUDIO_REG_FIRMWARE_VERSION_ADDR, verbuf_fw, sizeof(verbuf_fw), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read EEPROM firmware version: "); return FALSE; } version_fw = g_strdup_printf ("%02X.%02X.%02X.%02X", verbuf_fw[1], verbuf_fw[0], verbuf_fw[3], verbuf_fw[2]); fu_device_set_version_bootloader (device, version_fw); /* use a different address if a patch is in use */ if (self->eeprom_patch_valid_addr != 0x0) { if (!fu_synaptics_cxaudio_device_ensure_patch_level (self, error)) return FALSE; } addr = self->patch_level == 0 ? FU_SYNAPTICS_CXAUDIO_EEPROM_CPX_PATCH_VERSION_ADDRESS : FU_SYNAPTICS_CXAUDIO_EEPROM_CPX_PATCH2_VERSION_ADDRESS; if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, addr, verbuf_patch, sizeof(verbuf_patch), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read EEPROM patch version: "); return FALSE; } version_patch = g_strdup_printf ("%02X-%02X-%02X", verbuf_patch[0], verbuf_patch[1], verbuf_patch[2]); fu_device_set_version (device, version_patch, FWUPD_VERSION_FORMAT_PLAIN); /* find out if patch supports additional capabilities (optional) */ cap_str = g_usb_device_get_string_descriptor (usb_device, FU_SYNAPTICS_CXAUDIO_DEVICE_CAPABILITIES_STRIDX, NULL); if (cap_str != NULL) { g_auto(GStrv) split = g_strsplit (cap_str, ";", -1); for (guint i = 0; split[i] != NULL; i++) { g_debug ("capability: %s", split[i]); if (g_strcmp0 (split[i], "RESET") == 0) self->sw_reset_supported = TRUE; } } /* success */ return TRUE; } static gboolean fu_synaptics_cxaudio_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, FU_SYNAPTICS_CXAUDIO_HID_INTERFACE, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error (error, "failed to release interface: "); return FALSE; } /* success */ return TRUE; } static FuFirmware * fu_synaptics_cxaudio_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE (device); guint32 chip_id_base; g_autoptr(FuFirmware) firmware = fu_synaptics_cxaudio_firmware_new (); fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; chip_id_base = fu_synaptics_cxaudio_firmware_get_devtype (FU_SYNAPTICS_CXAUDIO_FIRMWARE (firmware)); if (chip_id_base != self->chip_id_base) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "device 0x%04u is incompatible with firmware 0x%04u", self->chip_id_base, chip_id_base); return NULL; } return g_steal_pointer (&firmware); } static gboolean fu_synaptics_cxaudio_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE (device); GPtrArray *records = fu_srec_firmware_get_records (FU_SREC_FIRMWARE (firmware)); FuSynapticsCxaudioFileKind file_kind; /* check if a patch file fits completely into the EEPROM */ for (guint i = 0; i < records->len; i++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index (records, i); if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16) continue; if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_LAST) continue; if (rcd->addr > self->eeprom_sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM address 0x%02x is bigger than size 0x%02x", rcd->addr, self->eeprom_sz); return FALSE; } } /* park the FW: run only the basic functionality until the upgrade is over */ if (!fu_synaptics_cxaudio_device_register_set_bit (self, FU_SYNAPTICS_CXAUDIO_REG_FIRMWARE_PARK_ADDR, 7, error)) return FALSE; g_usleep (10 * 1000); /* initialize layout signature and version to 0 if transitioning from * EEPROM layout version 1 => 0 */ file_kind = fu_synaptics_cxaudio_firmware_get_file_type (FU_SYNAPTICS_CXAUDIO_FIRMWARE (firmware)); if (file_kind == FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_FW && self->eeprom_layout_version >= 1 && fu_synaptics_cxaudio_firmware_get_layout_version (FU_SYNAPTICS_CXAUDIO_FIRMWARE (firmware)) == 0) { guint8 value = 0; if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_LAYOUT_SIGNATURE_ADDRESS, &value, sizeof(value), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to initialize layout signature "); return FALSE; } if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_LAYOUT_VERSION_ADDRESS, &value, sizeof(value), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to initialize layout signature "); return FALSE; } g_debug ("initialized layout signature"); } /* perform the actual write */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < records->len; i++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index (records, i); if (rcd->kind != FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32) continue; g_debug ("writing @0x%04x len:0x%02x", rcd->addr, rcd->buf->len); if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, rcd->addr, rcd->buf->data, rcd->buf->len, FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_VERIFY, error)) { g_prefix_error (error, "failed to write @0x%04x len:0x%02x: ", rcd->addr, rcd->buf->len); return FALSE; } fu_device_set_progress_full (device, (gsize) i, (gsize) records->len); } /* in case of a full FW upgrade invalidate the old FW patch (if any) * as it may have not been done by the S37 file */ if (file_kind == FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_FW) { FuSynapticsCxaudioEepromPatchInfo pinfo = { 0 }; if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET, (guint8 *) &pinfo, sizeof(pinfo), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to read EEPROM patch info: "); return FALSE; } if (pinfo.PatchSignature == FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE) { memset (&pinfo, 0x0, sizeof(pinfo)); if (!fu_synaptics_cxaudio_device_operation (self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET, (guint8 *) &pinfo, sizeof(pinfo), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error (error, "failed to write empty EEPROM patch info"); return FALSE; } g_debug ("invalidated old FW patch for CX2070x (RAM) device"); } } /* unpark the FW */ if (!fu_synaptics_cxaudio_device_register_clear_bit (self, FU_SYNAPTICS_CXAUDIO_REG_FIRMWARE_PARK_ADDR, 7, error)) return FALSE; /* if supported, self reset */ if (self->sw_reset_supported) { fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); return fu_synaptics_cxaudio_device_reset (self, error); } /* success */ return TRUE; } static gboolean fu_synaptics_cxaudio_device_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE (device); if (g_strcmp0 (key, "ChipIdBase") == 0) { self->chip_id_base = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "IsSoftwareResetSupported") == 0) { self->sw_reset_supported = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "EepromPatchValidAddr") == 0) { self->eeprom_patch_valid_addr = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "EepromPatch2ValidAddr") == 0) { self->eeprom_patch2_valid_addr = fu_common_strtoull (value); return TRUE; } g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_synaptics_cxaudio_device_init (FuSynapticsCxaudioDevice *self) { self->sw_reset_supported = TRUE; fu_device_add_icon (FU_DEVICE (self), "audio-card"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_install_duration (FU_DEVICE (self), 3); /* seconds */ fu_device_set_protocol (FU_DEVICE (self), "com.synaptics.cxaudio"); fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_synaptics_cxaudio_device_class_init (FuSynapticsCxaudioDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->to_string = fu_synaptics_cxaudio_device_to_string; klass_device->set_quirk_kv = fu_synaptics_cxaudio_device_set_quirk_kv; klass_device->setup = fu_synaptics_cxaudio_device_setup; klass_device->write_firmware = fu_synaptics_cxaudio_device_write_firmware; klass_device->prepare_firmware = fu_synaptics_cxaudio_device_prepare_firmware; klass_usb_device->open = fu_synaptics_cxaudio_device_open; klass_usb_device->close = fu_synaptics_cxaudio_device_close; } fwupd-1.3.9/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-device.h000066400000000000000000000006511362775233600255060ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_SYNAPTICS_CXAUDIO_DEVICE (fu_synaptics_cxaudio_device_get_type ()) G_DECLARE_FINAL_TYPE (FuSynapticsCxaudioDevice, fu_synaptics_cxaudio_device, FU, SYNAPTICS_CXAUDIO_DEVICE, FuUsbDevice) struct _FuSynapticsCxaudioDeviceClass { FuUsbDeviceClass parent_class; }; fwupd-1.3.9/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-firmware.c000066400000000000000000000254601362775233600260630ustar00rootroot00000000000000/* * Copyright (C) 2005-2019 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-synaptics-cxaudio-firmware.h" struct _FuSynapticsCxaudioFirmware { FuSrecFirmwareClass parent_instance; FuSynapticsCxaudioFileKind file_kind; FuSynapticsCxaudioDeviceKind device_kind; FuSynapticsCxaudioEepromCustomInfo cinfo; }; G_DEFINE_TYPE (FuSynapticsCxaudioFirmware, fu_synaptics_cxaudio_firmware, FU_TYPE_SREC_FIRMWARE) FuSynapticsCxaudioFileKind fu_synaptics_cxaudio_firmware_get_file_type (FuSynapticsCxaudioFirmware *self) { g_return_val_if_fail (FU_IS_SYNAPTICS_CXAUDIO_FIRMWARE (self), 0); return self->file_kind; } FuSynapticsCxaudioDeviceKind fu_synaptics_cxaudio_firmware_get_devtype (FuSynapticsCxaudioFirmware *self) { g_return_val_if_fail (FU_IS_SYNAPTICS_CXAUDIO_FIRMWARE (self), 0); return self->device_kind; } guint8 fu_synaptics_cxaudio_firmware_get_layout_version (FuSynapticsCxaudioFirmware *self) { g_return_val_if_fail (FU_IS_SYNAPTICS_CXAUDIO_FIRMWARE (self), 0); return self->cinfo.LayoutVersion; } static void fu_synaptics_cxaudio_firmware_to_string (FuFirmware *firmware, guint idt, GString *str) { FuSynapticsCxaudioFirmware *self = FU_SYNAPTICS_CXAUDIO_FIRMWARE (firmware); fu_common_string_append_kx (str, idt, "FileKind", self->file_kind); fu_common_string_append_kx (str, idt, "DeviceKind", self->device_kind); fu_common_string_append_kx (str, idt, "LayoutSignature", self->cinfo.LayoutSignature); fu_common_string_append_kx (str, idt, "LayoutVersion", self->cinfo.LayoutVersion); if (self->cinfo.LayoutVersion >= 1) { fu_common_string_append_kx (str, idt, "VendorID", self->cinfo.VendorID); fu_common_string_append_kx (str, idt, "ProductID", self->cinfo.ProductID); fu_common_string_append_kx (str, idt, "RevisionID", self->cinfo.RevisionID); } } typedef struct { const gchar *str; guint32 addr; guint32 len; } FuSynapticsCxaudioFirmwareBadblock; static void fu_synaptics_cxaudio_firmware_badblock_add (GPtrArray *badblocks, const gchar *str, guint32 addr, guint32 len) { FuSynapticsCxaudioFirmwareBadblock *bb = g_new0 (FuSynapticsCxaudioFirmwareBadblock, 1); g_debug ("created reserved range @0x%04x len:0x%x: %s", addr, len, str); bb->str = str; bb->addr = addr; bb->len = len; g_ptr_array_add (badblocks, bb); } static gboolean fu_synaptics_cxaudio_firmware_is_addr_valid (GPtrArray *badblocks, guint32 addr, guint32 len) { for (guint j = 0; j < badblocks->len; j++) { FuSynapticsCxaudioFirmwareBadblock *bb = g_ptr_array_index (badblocks, j); if (addr <= bb->addr + bb->len - 1 && addr + len - 1 >= bb->addr) { g_debug ("addr @0x%04x len:0x%x invalid " "as 0x%02x->0x%02x protected: %s", addr, len, bb->addr, bb->addr + bb->len - 1, bb->str); return FALSE; } } return TRUE; } static gboolean fu_synaptics_cxaudio_firmware_is_record_valid (GPtrArray *badblocks, FuSrecFirmwareRecord *rcd) { /* the entire record is not within an ignored range */ return fu_synaptics_cxaudio_firmware_is_addr_valid (badblocks, rcd->addr, rcd->buf->len); } static void fu_synaptics_cxaudio_firmware_avoid_badblocks (GPtrArray *badblocks, GPtrArray *records) { g_autoptr(GPtrArray) records_new = g_ptr_array_new (); /* find records that include addresses with blocks we want to avoid */ for (guint i = 0; i < records->len; i++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index (records, i); FuSrecFirmwareRecord *rcd1; if (rcd->kind != FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32) continue; if (fu_synaptics_cxaudio_firmware_is_record_valid (badblocks, rcd)) { rcd1 = fu_srec_firmware_record_new (rcd->ln, rcd->kind, rcd->addr); g_byte_array_append (rcd1->buf, rcd->buf->data, rcd->buf->len); g_ptr_array_add (records_new, rcd1); continue; } g_debug ("splitting record @0x%04x len:0x%x as protected", rcd->addr, rcd->buf->len); for (guint j = 0; j < rcd->buf->len; j++) { if (!fu_synaptics_cxaudio_firmware_is_addr_valid (badblocks, rcd->addr + j, 0x1)) continue; rcd1 = fu_srec_firmware_record_new (rcd->ln, rcd->kind, rcd->addr + j); g_byte_array_append (rcd1->buf, rcd->buf->data + j, 0x1); g_ptr_array_add (records_new, rcd1); } } /* swap the old set of records with the new records */ g_ptr_array_set_size (records, 0); for (guint i = 0; i < records_new->len; i++) { FuSrecFirmwareRecord *rcd1 = g_ptr_array_index (records_new, i); g_ptr_array_add (records, rcd1); } } static gboolean fu_synaptics_cxaudio_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuSynapticsCxaudioFirmware *self = FU_SYNAPTICS_CXAUDIO_FIRMWARE (firmware); GPtrArray *records = fu_srec_firmware_get_records (FU_SREC_FIRMWARE (firmware)); guint8 dev_kind_candidate = G_MAXUINT8; g_autofree guint8 *shadow = g_malloc0 (FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE); g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (fw); /* copy shadow EEPROM */ for (guint i = 0; i < records->len; i++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index (records, i); if (rcd->kind != FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32) continue; if (rcd->addr > FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE) continue; if (!fu_memcpy_safe (shadow, FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE, rcd->addr, /* dst */ rcd->buf->data, rcd->buf->len, 0x0, /* src */ rcd->buf->len, error)) return FALSE; } /* parse EEPROM map */ if (!fu_memcpy_safe ((guint8 *) &self->cinfo, sizeof(FuSynapticsCxaudioEepromCustomInfo), 0x0, /* dst */ shadow, FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE, FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET, /* src */ sizeof(FuSynapticsCxaudioEepromCustomInfo), error)) return FALSE; /* just layout version byte is not enough in case of old CX20562 patch * files that could have non-zero value of the Layout version */ if (shadow[FU_SYNAPTICS_CXAUDIO_FIRMWARE_SIGNATURE_OFFSET] == FU_SYNAPTICS_CXAUDIO_SIGNATURE_BYTE) { self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070x; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_FW; g_debug ("FileKind: CX2070x (FW)"); } else if (shadow[FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_SIGNATURE_ADDRESS] == FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE) { self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070x; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_PATCH; g_debug ("FileKind: CX2070x (Patch)"); } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "CX20562 is not supported"); return FALSE; } for (guint i = records->len - 3; i < records->len; i++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index (records, i); if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16) continue; if (rcd->buf->len < 2) continue; if (memcmp (rcd->buf->data, "CX", 2) == 0) { dev_kind_candidate = rcd->buf->data[2]; g_debug ("DeviceKind signature suspected 0x%0x", dev_kind_candidate); break; } } /* check the signature character to see if it defines the device */ switch (dev_kind_candidate) { case '2': /* fallthrough */ /* CX2070x */ case '4': /* CX2070x-21Z */ case '6': /* CX2070x-21Z */ self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070x; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_PATCH; g_debug ("FileKind: CX2070x overwritten from signature"); break; case '3': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2077x; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2077X_PATCH; g_debug ("FileKind: CX2077x overwritten from signature"); break; case '5': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2076x; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2076X_PATCH; g_debug ("FileKind: CX2076x overwritten from signature"); break; case '7': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2085x; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2085X_PATCH; g_debug ("FileKind: CX2085x overwritten from signature"); break; case '8': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2089x; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2089X_PATCH; g_debug ("FileKind: CX2089x overwritten from signature"); break; case '9': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2098x; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2098X_PATCH; g_debug ("FileKind: CX2098x overwritten from signature"); break; case 'A': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2198x; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2198X_PATCH; g_debug ("FileKind: CX2198x overwritten from signature"); break; default: /* probably future devices */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "DeviceKind signature invalid 0x%x", dev_kind_candidate); return FALSE; } /* ignore records with protected content */ if (self->cinfo.LayoutVersion >= 1) { g_autoptr(GPtrArray) badblocks = g_ptr_array_new_with_free_func (g_free); /* add standard ranges to ignore */ fu_synaptics_cxaudio_firmware_badblock_add (badblocks, "test mark", 0x00BC, 0x02); fu_synaptics_cxaudio_firmware_badblock_add (badblocks, "application status", FU_SYNAPTICS_CXAUDIO_EEPROM_APP_STATUS_ADDRESS, 1); fu_synaptics_cxaudio_firmware_badblock_add (badblocks, "boot bytes", FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET, sizeof(FuSynapticsCxaudioEepromValiditySignature) + 1); /* serial number address and also string pointer itself if set */ if (self->cinfo.SerialNumberStringAddress != 0x0) { FuSynapticsCxaudioEepromPtr addr_tmp; FuSynapticsCxaudioEepromPtr addr_str; addr_tmp = FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + G_STRUCT_OFFSET(FuSynapticsCxaudioEepromCustomInfo, SerialNumberStringAddress); fu_synaptics_cxaudio_firmware_badblock_add (badblocks, "serial number", addr_tmp, sizeof(FuSynapticsCxaudioEepromPtr)); memcpy (&addr_str, shadow + addr_tmp, sizeof(addr_str)); fu_synaptics_cxaudio_firmware_badblock_add (badblocks, "serial number data", addr_str, shadow[addr_str]); } fu_synaptics_cxaudio_firmware_avoid_badblocks (badblocks, records); } /* this isn't used, but it seems a good thing to add */ fu_firmware_add_image (firmware, img); return TRUE; } static void fu_synaptics_cxaudio_firmware_init (FuSynapticsCxaudioFirmware *self) { } static void fu_synaptics_cxaudio_firmware_class_init (FuSynapticsCxaudioFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->parse = fu_synaptics_cxaudio_firmware_parse; klass_firmware->to_string = fu_synaptics_cxaudio_firmware_to_string; } FuFirmware * fu_synaptics_cxaudio_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_SYNAPTICS_CXAUDIO_FIRMWARE, NULL)); } fwupd-1.3.9/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-firmware.h000066400000000000000000000024621362775233600260650ustar00rootroot00000000000000/* * Copyright (C) 2005-2019 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-srec-firmware.h" #include "fu-synaptics-cxaudio-common.h" #define FU_TYPE_SYNAPTICS_CXAUDIO_FIRMWARE (fu_synaptics_cxaudio_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuSynapticsCxaudioFirmware, fu_synaptics_cxaudio_firmware, FU, SYNAPTICS_CXAUDIO_FIRMWARE, FuSrecFirmware) typedef enum { FU_SYNAPTICS_CXAUDIO_FILE_KIND_UNKNOWN, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_FW, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2077X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2076X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2085X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2089X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2098X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2198X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_LAST } FuSynapticsCxaudioFileKind; FuFirmware *fu_synaptics_cxaudio_firmware_new (void); FuSynapticsCxaudioFileKind fu_synaptics_cxaudio_firmware_get_file_type (FuSynapticsCxaudioFirmware *self); FuSynapticsCxaudioDeviceKind fu_synaptics_cxaudio_firmware_get_devtype (FuSynapticsCxaudioFirmware *self); guint8 fu_synaptics_cxaudio_firmware_get_layout_version (FuSynapticsCxaudioFirmware *self); fwupd-1.3.9/plugins/synaptics-cxaudio/meson.build000066400000000000000000000011151362775233600221570ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsCxaudio"'] install_data(['synaptics-cxaudio.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_synaptics_cxaudio', fu_hash, sources : [ 'fu-plugin-synaptics-cxaudio.c', 'fu-synaptics-cxaudio-device.c', 'fu-synaptics-cxaudio-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/synaptics-cxaudio/synaptics-cxaudio.quirk000066400000000000000000000015271362775233600245500ustar00rootroot00000000000000# ThinkPad TBT3-TR Gen 2 dock [DeviceInstanceId=USB\VID_17EF&PID_3083] Guid = SYNAPTICS_CXAUDIO\CX2098X ParentGuid = TBT-01081720 # ThinkPad TBT3-MS Gen 2 dock [DeviceInstanceId=USB\VID_17EF&PID_3092] Guid = SYNAPTICS_CXAUDIO\CX2198X ParentGuid = USB\VID_17EF&PID_308F # ThinkPad USB-C Dock Gen2 Audio [DeviceInstanceId=USB\VID_17EF&PID_A396] Guid = SYNAPTICS_CXAUDIO\CX2198X ParentGuid = USB\VID_17EF&PID_1039 # Google Pixel USB-C headphones [DeviceInstanceId=USB\VID_18D1&PID_5033] Guid = SYNAPTICS_CXAUDIO\CX2198X # Google Pixel USB-C <-> 3.5mm adapter [DeviceInstanceId=USB\VID_18D1&PID_5034] Guid = SYNAPTICS_CXAUDIO\CX2198X [Guid=SYNAPTICS_CXAUDIO\CX2098X] Plugin = synaptics_cxaudio ChipIdBase = 20980 [Guid=SYNAPTICS_CXAUDIO\CX2198X] Plugin = synaptics_cxaudio ChipIdBase = 21980 EepromPatchValidAddr = 0x0014 EepromPatch2ValidAddr = 0x0171 fwupd-1.3.9/plugins/synaptics-mst/000077500000000000000000000000001362775233600171665ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-mst/README.md000066400000000000000000000046261362775233600204550ustar00rootroot00000000000000# 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)` * `MST-$(device_kind)-$(board-ID)` * `MST-$(device_kind)` Please refer to the plugin source for more details about how the GUID is constructed for specific hardware. Vendor ID Security ------------------ The vendor ID is set from the PCI vendor, for example set to `DRM_DP_AUX_DEV:0x$(vid)` ## 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: synaptics_mst 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.3.9/plugins/synaptics-mst/fu-plugin-synaptics-mst.c000066400000000000000000000120651362775233600240600ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * Copyright (C) 2017 Peichen Huang * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-synaptics-mst-common.h" #include "fu-synaptics-mst-device.h" #define FU_SYNAPTICS_MST_DRM_REPLUG_DELAY 5 /* s */ struct FuPluginData { GPtrArray *devices; guint drm_changed_id; }; /* see https://github.com/hughsie/fwupd/issues/1121 for more details */ static gboolean fu_synaptics_mst_check_amdgpu_safe (GError **error) { gsize bufsz = 0; g_autofree gchar *buf = NULL; g_auto(GStrv) lines = NULL; /* no module support in the kernel, we can't test for amdgpu module */ if (!g_file_test ("/proc/modules", G_FILE_TEST_EXISTS)) return TRUE; 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_NOT_SUPPORTED, "amdgpu has known issues with synaptics_mst"); return FALSE; } } return TRUE; } static void fu_plugin_synaptics_mst_device_rescan (FuPlugin *plugin, FuDevice *device) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* open fd */ locker = fu_device_locker_new (device, &error_local); if (locker == NULL) { g_debug ("failed to open device %s: %s", fu_device_get_logical_id (device), error_local->message); return; } if (!fu_device_rescan (device, &error_local)) { g_debug ("no device found on %s: %s", fu_device_get_logical_id (device), error_local->message); if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REGISTERED)) fu_plugin_device_remove (plugin, device); } else { fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_plugin_device_add (plugin, device); } } /* reprobe all existing devices added by this plugin */ static void fu_plugin_synaptics_mst_rescan (FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data (plugin); for (guint i = 0; i < priv->devices->len; i++) { FuDevice *device = FU_DEVICE (g_ptr_array_index (priv->devices, i)); fu_plugin_synaptics_mst_device_rescan (plugin, device); } } static gboolean fu_plugin_synaptics_mst_rescan_cb (gpointer user_data) { FuPlugin *plugin = FU_PLUGIN (user_data); FuPluginData *priv = fu_plugin_get_data (plugin); fu_plugin_synaptics_mst_rescan (plugin); priv->drm_changed_id = 0; return FALSE; } gboolean fu_plugin_udev_device_changed (FuPlugin *plugin, FuUdevDevice *device, GError **error) { FuPluginData *priv = fu_plugin_get_data (plugin); /* interesting device? */ if (g_strcmp0 (fu_udev_device_get_subsystem (device), "drm") != 0) return TRUE; /* recoldplug all drm_dp_aux_dev devices after a *long* delay */ if (priv->drm_changed_id != 0) g_source_remove (priv->drm_changed_id); priv->drm_changed_id = g_timeout_add_seconds (FU_SYNAPTICS_MST_DRM_REPLUG_DELAY, fu_plugin_synaptics_mst_rescan_cb, plugin); return TRUE; } gboolean fu_plugin_udev_device_added (FuPlugin *plugin, FuUdevDevice *device, GError **error) { FuPluginData *priv = fu_plugin_get_data (plugin); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuSynapticsMstDevice) dev = NULL; dev = fu_synaptics_mst_device_new (device); locker = fu_device_locker_new (dev, error); if (locker == NULL) return FALSE; /* for DeviceKind=system devices */ fu_synaptics_mst_device_set_system_type (FU_SYNAPTICS_MST_DEVICE (dev), fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_SKU)); /* this might fail if there is nothing connected */ fu_plugin_synaptics_mst_device_rescan (plugin, FU_DEVICE (dev)); g_ptr_array_add (priv->devices, g_steal_pointer (&dev)); return TRUE; } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { return fu_synaptics_mst_check_amdgpu_safe (error); } 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; if (!fu_device_write_firmware (device, blob_fw, flags, error)) return FALSE; if (!fu_device_has_custom_flag (device, "skip-restart")) fu_plugin_device_remove (plugin, device); return TRUE; } void fu_plugin_init (FuPlugin *plugin) { FuPluginData *priv = fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); /* devices added by this plugin */ priv->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_udev_subsystem (plugin, "drm"); /* used for uevent only */ fu_plugin_add_udev_subsystem (plugin, "drm_dp_aux_dev"); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data (plugin); if (priv->drm_changed_id != 0) g_source_remove (priv->drm_changed_id); g_ptr_array_unref (priv->devices); } fwupd-1.3.9/plugins/synaptics-mst/fu-self-test.c000066400000000000000000000100101362775233600216400ustar00rootroot00000000000000/* * 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, g_object_ref (device)); } static void _test_add_fake_devices_from_dir (FuPlugin *plugin, const gchar *path) { const gchar *basename; gboolean ret; g_autoptr(FuQuirks) quirks = fu_quirks_new (); g_autoptr(GError) error = NULL; g_autoptr(GDir) dir = g_dir_open (path, 0, &error); g_assert_no_error (error); g_assert_nonnull (dir); while ((basename = g_dir_read_name (dir)) != NULL) { g_autofree gchar *fn = g_build_filename (path, basename, NULL); g_autoptr(FuUdevDevice) dev = NULL; if (!g_str_has_prefix (basename, "drm_dp_aux")) continue; dev = g_object_new (FU_TYPE_UDEV_DEVICE, "quirks", quirks, "physical-id", "PCI_SLOT_NAME=0000:3e:00.0", "logical-id", basename, "subsystem", "drm_dp_aux_dev", "device-file", fn, NULL); g_debug ("creating drm_dp_aux_dev object backed by %s", fn); ret = fu_plugin_runner_udev_device_added (plugin, dev, &error); g_assert_no_error (error); g_assert (ret); } } /* test with no Synaptics MST devices */ static void fu_plugin_synaptics_mst_none_func (void) { gboolean ret; g_autoptr(FuPlugin) plugin = fu_plugin_new (); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_autofree gchar *pluginfn = NULL; g_signal_connect (plugin, "device-added", G_CALLBACK (_plugin_device_added_cb), &devices); pluginfn = g_build_filename (PLUGINBUILDDIR, "libfu_plugin_synaptics_mst." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open (plugin, pluginfn, &error); g_assert_no_error (error); g_assert (ret); ret = fu_plugin_runner_startup (plugin, &error); if (!ret && g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip ("Skipping tests due to unsupported configuration"); return; } g_assert_no_error (error); g_assert (ret); _test_add_fake_devices_from_dir (plugin, SOURCEDIR "/tests/no_devices"); g_assert_cmpint (devices->len, ==, 0); } /* emulate adding/removing a Dell TB16 dock */ static void fu_plugin_synaptics_mst_tb16_func (void) { gboolean ret; g_autoptr(FuPlugin) plugin = fu_plugin_new (); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_autofree gchar *pluginfn = NULL; g_signal_connect (plugin, "device-added", G_CALLBACK (_plugin_device_added_cb), &devices); pluginfn = g_build_filename (PLUGINBUILDDIR, "libfu_plugin_synaptics_mst." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open (plugin, pluginfn, &error); g_assert_no_error (error); g_assert (ret); ret = fu_plugin_runner_startup (plugin, &error); if (!ret && g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip ("Skipping tests due to unsupported configuration"); return; } g_assert_no_error (error); g_assert (ret); _test_add_fake_devices_from_dir (plugin, SOURCEDIR "/tests/tb16_dock"); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index (devices, i); g_autofree gchar *tmp = fu_device_to_string (device); g_debug ("%s", tmp); } g_assert_cmpint (devices->len, ==, 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); 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/synaptics_mst{none}", fu_plugin_synaptics_mst_none_func); g_test_add_func ("/fwupd/plugin/synaptics_mst{tb16}", fu_plugin_synaptics_mst_tb16_func); return g_test_run (); } fwupd-1.3.9/plugins/synaptics-mst/fu-synaptics-mst-common.c000066400000000000000000000021371362775233600240510ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-mst-common.h" const gchar * fu_synaptics_mst_mode_to_string (FuSynapticsMstMode mode) { if (mode == FU_SYNAPTICS_MST_MODE_DIRECT) return "DIRECT"; if (mode == FU_SYNAPTICS_MST_MODE_REMOTE) return "REMOTE"; return NULL; } const gchar * fu_synaptics_mst_family_to_string (FuSynapticsMstFamily family) { if (family == FU_SYNAPTICS_MST_FAMILY_TESLA) return "tesla"; if (family == FU_SYNAPTICS_MST_FAMILY_LEAF) return "leaf"; if (family == FU_SYNAPTICS_MST_FAMILY_PANAMERA) return "panamera"; return NULL; } FuSynapticsMstFamily fu_synaptics_mst_family_from_chip_id (guint16 chip_id) { if (chip_id >= 0x5000 && chip_id < 0x6000) return FU_SYNAPTICS_MST_FAMILY_PANAMERA; if (chip_id >= 0x3000 && chip_id < 0x4000) return FU_SYNAPTICS_MST_FAMILY_LEAF; if (chip_id >= 0x2000 && chip_id < 0x3000) return FU_SYNAPTICS_MST_FAMILY_TESLA; return FU_SYNAPTICS_MST_FAMILY_UNKNOWN; } fwupd-1.3.9/plugins/synaptics-mst/fu-synaptics-mst-common.h000066400000000000000000000022561362775233600240600ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Peichen Huang * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define SYNAPTICS_FLASH_MODE_DELAY 3 /* seconds */ /** * FuSynapticsMstMode: * @FU_SYNAPTICS_MST_MODE_UNKNOWN: Type invalid or not known * @FU_SYNAPTICS_MST_MODE_DIRECT: Directly addressable * @FU_SYNAPTICS_MST_MODE_REMOTE: Requires remote register work * * The device type. **/ typedef enum { FU_SYNAPTICS_MST_MODE_UNKNOWN, FU_SYNAPTICS_MST_MODE_DIRECT, FU_SYNAPTICS_MST_MODE_REMOTE, /*< private >*/ FU_SYNAPTICS_MST_MODE_LAST } FuSynapticsMstMode; typedef enum { FU_SYNAPTICS_MST_FAMILY_UNKNOWN, FU_SYNAPTICS_MST_FAMILY_TESLA, FU_SYNAPTICS_MST_FAMILY_LEAF, FU_SYNAPTICS_MST_FAMILY_PANAMERA, /**/ FU_SYNAPTICS_MST_FAMILY_LAST } FuSynapticsMstFamily; const gchar *fu_synaptics_mst_mode_to_string (FuSynapticsMstMode mode); const gchar *fu_synaptics_mst_family_to_string (FuSynapticsMstFamily family); FuSynapticsMstFamily fu_synaptics_mst_family_from_chip_id (guint16 chip_id); fwupd-1.3.9/plugins/synaptics-mst/fu-synaptics-mst-connection.c000066400000000000000000000303721362775233600247220ustar00rootroot00000000000000/* * 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 "fu-synaptics-mst-connection.h" #include "fu-synaptics-mst-common.h" #define UNIT_SIZE 32 #define MAX_WAIT_TIME 3 /* unit : second */ struct _FuSynapticsMstConnection { GObject parent_instance; gint fd; /* not owned by the connection */ guint8 layer; guint8 remain_layer; guint8 rad; }; G_DEFINE_TYPE (FuSynapticsMstConnection, fu_synaptics_mst_connection, G_TYPE_OBJECT) static void fu_synaptics_mst_connection_init (FuSynapticsMstConnection *self) { } static void fu_synaptics_mst_connection_class_init (FuSynapticsMstConnectionClass *klass) { } static gboolean fu_synaptics_mst_connection_aux_node_read (FuSynapticsMstConnection *self, guint32 offset, guint8 *buf, gint length, GError **error) { if (lseek (self->fd, offset, SEEK_SET) != offset) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to lseek to 0x%x on layer:%u, rad:0x%x", offset, self->layer, self->rad); return FALSE; } if (read (self->fd, buf, length) != length) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to read 0x%x bytes on layer:%u, rad:0x%x", (guint) length, self->layer, self->rad); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_connection_aux_node_write (FuSynapticsMstConnection *self, guint32 offset, const guint8 *buf, gint length, GError **error) { if (lseek (self->fd, offset, SEEK_SET) != offset) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to lseek to 0x%x on layer:%u, rad:0x%x", offset, self->layer, self->rad); return FALSE; } if (write (self->fd, buf, length) != length) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to write 0x%x bytes on layer:%u, rad:0x%x", (guint) length, self->layer, self->rad); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_connection_bus_read (FuSynapticsMstConnection *self, guint32 offset, guint8 *buf, guint32 length, GError **error) { return fu_synaptics_mst_connection_aux_node_read (self, offset, buf, length, error); } static gboolean fu_synaptics_mst_connection_bus_write (FuSynapticsMstConnection *self, guint32 offset, const guint8 *buf, guint32 length, GError **error) { return fu_synaptics_mst_connection_aux_node_write (self, offset, buf, length, error); } FuSynapticsMstConnection * fu_synaptics_mst_connection_new (gint fd, guint8 layer, guint rad) { FuSynapticsMstConnection *self = g_object_new (FU_TYPE_SYNAPTICS_MST_CONNECTION, NULL); self->fd = fd; self->layer = layer; self->remain_layer = layer; self->rad = rad; return self; } gboolean fu_synaptics_mst_connection_read (FuSynapticsMstConnection *self, guint32 offset, guint8 *buf, guint32 length, GError **error) { if (self->layer && self->remain_layer) { guint8 node; gboolean result; self->remain_layer--; node = (self->rad >> self->remain_layer * 2) & 0x03; result = fu_synaptics_mst_connection_rc_get_command (self, UPDC_READ_FROM_TX_DPCD + node, length, offset, (guint8 *)buf, error); self->remain_layer++; return result; } return fu_synaptics_mst_connection_bus_read (self, offset, buf, length, error); } gboolean fu_synaptics_mst_connection_write (FuSynapticsMstConnection *self, guint32 offset, const guint8 *buf, guint32 length, GError **error) { if (self->layer && self->remain_layer) { guint8 node; gboolean result; self->remain_layer--; node = (self->rad >> self->remain_layer * 2) & 0x03; result = fu_synaptics_mst_connection_rc_set_command (self, UPDC_WRITE_TO_TX_DPCD + node, length, offset, (guint8 *)buf, error); self->remain_layer++; return result; } return fu_synaptics_mst_connection_bus_write (self, offset, buf, length, error); } gboolean fu_synaptics_mst_connection_rc_set_command (FuSynapticsMstConnection *self, 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 (!fu_synaptics_mst_connection_write (self, REG_RC_DATA, buf, cur_length, error)) { g_prefix_error (error, "failure writing data register: "); return FALSE; } /* write offset */ if (!fu_synaptics_mst_connection_write (self, REG_RC_OFFSET, (guint8 *)&cur_offset, 4, error)) { g_prefix_error (error, "failure writing offset register: "); return FALSE; } /* write length */ if (!fu_synaptics_mst_connection_write (self, 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 (!fu_synaptics_mst_connection_write (self, 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 (!fu_synaptics_mst_connection_read (self, 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 fu_synaptics_mst_connection_rc_get_command (FuSynapticsMstConnection *self, 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 (!fu_synaptics_mst_connection_write (self, REG_RC_OFFSET, (guint8 *)&cur_offset, 4, error)) { g_prefix_error (error, "failed to write offset: "); return FALSE; } /* write length */ if (!fu_synaptics_mst_connection_write (self, 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 (!fu_synaptics_mst_connection_write (self, 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 (!fu_synaptics_mst_connection_read (self, 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 (!fu_synaptics_mst_connection_read (self, 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 fu_synaptics_mst_connection_rc_special_get_command (FuSynapticsMstConnection *self, 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 (!fu_synaptics_mst_connection_write (self, REG_RC_DATA, cmd_data, cmd_length, error)) { g_prefix_error (error, "Failed to write command data: "); return FALSE; } } /* write offset */ if (!fu_synaptics_mst_connection_write (self, REG_RC_OFFSET, (guint8 *)&cmd_offset, 4, error)) { g_prefix_error (error, "failed to write offset: "); return FALSE; } /* write length */ if (!fu_synaptics_mst_connection_write (self, 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 (!fu_synaptics_mst_connection_write (self, 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 (!fu_synaptics_mst_connection_read (self, 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 (!fu_synaptics_mst_connection_read (self, REG_RC_DATA, buf, length, error)) { g_prefix_error (error, "failed to read length: "); } } return TRUE; } gboolean fu_synaptics_mst_connection_enable_rc (FuSynapticsMstConnection *self, GError **error) { const gchar *sc = "PRIUS"; for (gint i = 0; i <= self->layer; i++) { g_autoptr(FuSynapticsMstConnection) connection_tmp = NULL; connection_tmp = fu_synaptics_mst_connection_new (self->fd, i, self->rad); if (!fu_synaptics_mst_connection_rc_set_command (connection_tmp, UPDC_ENABLE_RC, 5, 0, (guint8*)sc, error)) { g_prefix_error (error, "failed to enable remote control: "); return FALSE; } } return TRUE; } gboolean fu_synaptics_mst_connection_disable_rc (FuSynapticsMstConnection *self, GError **error) { for (gint i = self->layer; i >= 0; i--) { g_autoptr(FuSynapticsMstConnection) connection_tmp = NULL; connection_tmp = fu_synaptics_mst_connection_new (self->fd, i, self->rad); if (!fu_synaptics_mst_connection_rc_set_command (connection_tmp, UPDC_DISABLE_RC, 0, 0, NULL, error)) { g_prefix_error (error, "failed to disable remote control: "); return FALSE; } } return TRUE; } fwupd-1.3.9/plugins/synaptics-mst/fu-synaptics-mst-connection.h000066400000000000000000000057011362775233600247250ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Peichen Huang * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #define FU_TYPE_SYNAPTICS_MST_CONNECTION (fu_synaptics_mst_connection_get_type ()) G_DECLARE_FINAL_TYPE (FuSynapticsMstConnection, fu_synaptics_mst_connection, FU, SYNAPTICS_MST_CONNECTION, GObject) #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; FuSynapticsMstConnection *fu_synaptics_mst_connection_new (gint fd, guint8 layer, guint rad); gboolean fu_synaptics_mst_connection_read (FuSynapticsMstConnection *self, guint32 offset, guint8 *buf, guint32 length, GError **error); gboolean fu_synaptics_mst_connection_write (FuSynapticsMstConnection *self, guint32 offset, const guint8 *buf, guint32 length, GError **error); gboolean fu_synaptics_mst_connection_rc_set_command (FuSynapticsMstConnection *self, guint32 rc_cmd, guint32 length, guint32 offset, const guint8 *buf, GError **error); gboolean fu_synaptics_mst_connection_rc_get_command (FuSynapticsMstConnection *self, guint32 rc_cmd, guint32 length, guint32 offset, guint8 *buf, GError **error); gboolean fu_synaptics_mst_connection_rc_special_get_command(FuSynapticsMstConnection *self, guint32 rc_cmd, guint32 cmd_length, guint32 cmd_offset, guint8 *cmd_data, guint32 length, guint8 *buf, GError **error); gboolean fu_synaptics_mst_connection_enable_rc (FuSynapticsMstConnection *self, GError **error); gboolean fu_synaptics_mst_connection_disable_rc (FuSynapticsMstConnection *self, GError **error); fwupd-1.3.9/plugins/synaptics-mst/fu-synaptics-mst-device.c000066400000000000000000001126531362775233600240250ustar00rootroot00000000000000/* * Copyright (C) 2015-2019 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 #include "fu-synaptics-mst-common.h" #include "fu-synaptics-mst-connection.h" #include "fu-synaptics-mst-device.h" #define FU_SYNAPTICS_MST_ID_CTRL_SIZE 0x1000 #define SYNAPTICS_UPDATE_ENUMERATE_TRIES 3 #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 */ struct _FuSynapticsMstDevice { FuUdevDevice parent_instance; gchar *system_type; guint64 write_block_size; FuSynapticsMstFamily family; FuSynapticsMstMode mode; guint8 active_bank; guint8 layer; guint16 rad; /* relative address */ guint32 board_id; guint16 chip_id; }; G_DEFINE_TYPE (FuSynapticsMstDevice, fu_synaptics_mst_device, FU_TYPE_UDEV_DEVICE) static void fu_synaptics_mst_device_finalize (GObject *object) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE (object); g_free (self->system_type); G_OBJECT_CLASS (fu_synaptics_mst_device_parent_class)->finalize (object); } static void fu_synaptics_mst_device_init (FuSynapticsMstDevice *self) { fu_device_set_protocol (FU_DEVICE (self), "com.synaptics.mst"); fu_device_set_vendor (FU_DEVICE (self), "Synaptics"); fu_device_set_vendor_id (FU_DEVICE (self), "DRM_DP_AUX_DEV:0x06CB"); fu_device_set_summary (FU_DEVICE (self), "Multi-Stream Transport Device"); fu_device_add_icon (FU_DEVICE (self), "video-display"); fu_udev_device_set_flags (FU_UDEV_DEVICE (self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT); } static void fu_synaptics_mst_device_to_string (FuDevice *device, guint idt, GString *str) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE (device); if (self->mode != FU_SYNAPTICS_MST_MODE_UNKNOWN) { fu_common_string_append_kv (str, idt, "Mode", fu_synaptics_mst_mode_to_string (self->mode)); } if (self->family == FU_SYNAPTICS_MST_FAMILY_PANAMERA) fu_common_string_append_kx (str, idt, "ActiveBank", self->active_bank); fu_common_string_append_kx (str, idt, "Layer", self->layer); fu_common_string_append_kx (str, idt, "Rad", self->rad); if (self->board_id != 0x0) fu_common_string_append_ku (str, idt, "BoardId", self->board_id); if (self->chip_id != 0x0) fu_common_string_append_kx (str, idt, "ChipId", self->chip_id); } static gboolean fu_synaptics_mst_device_enable_rc (FuSynapticsMstDevice *self, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; /* in test mode */ if (fu_udev_device_get_dev (FU_UDEV_DEVICE (self)) == NULL) return TRUE; connection = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), self->layer, self->rad); return fu_synaptics_mst_connection_enable_rc (connection, error); } static gboolean fu_synaptics_mst_device_disable_rc (FuSynapticsMstDevice *self, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; /* in test mode */ if (fu_udev_device_get_dev (FU_UDEV_DEVICE (self)) == NULL) return TRUE; connection = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), self->layer, self->rad); return fu_synaptics_mst_connection_disable_rc (connection, error); } static gboolean fu_synaptics_mst_device_probe (FuUdevDevice *device, GError **error) { g_autofree gchar *logical_id = NULL; logical_id = g_path_get_basename (fu_udev_device_get_sysfs_path(device)); fu_device_set_logical_id (FU_DEVICE (device), logical_id); if (!fu_udev_device_set_physical_id (device, "pci,drm_dp_aux_dev", error)) return FALSE; return TRUE; } static gboolean fu_synaptics_mst_device_get_flash_checksum (FuSynapticsMstDevice *self, guint32 length, guint32 offset, guint32 *checksum, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; connection = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), self->layer, self->rad); if (!fu_synaptics_mst_connection_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 fu_synaptics_mst_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 fu_synaptics_mst_device_set_flash_sector_erase (FuSynapticsMstDevice *self, guint16 rc_cmd, guint16 offset, GError **error) { guint16 us_data; g_autoptr(FuSynapticsMstConnection) connection = NULL; connection = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), self->layer, self->rad); /* Need to add Wp control ? */ us_data = rc_cmd + offset; if (!fu_synaptics_mst_connection_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 fu_synaptics_mst_device_update_esm (FuSynapticsMstDevice *self, const guint8 *payload_data, GError **error) { guint32 checksum = 0; guint32 esm_sz = ESM_CODE_SIZE; guint32 flash_checksum = 0; guint32 unit_sz = BLOCK_UNIT; guint32 write_loops = 0; g_autoptr(FuSynapticsMstConnection) connection = NULL; connection = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), self->layer, self->rad); for (guint32 i = 0; i < esm_sz; i++) checksum += *(payload_data + EEPROM_ESM_OFFSET +i); if (!fu_synaptics_mst_device_get_flash_checksum (self, 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 (!fu_synaptics_mst_device_set_flash_sector_erase (self, 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 (!fu_synaptics_mst_connection_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; fu_device_set_progress_full (FU_DEVICE (self), (goffset) i * 100, (goffset) (write_loops -1) * 100); } /* check ESM checksum */ checksum = 0; flash_checksum = 0; for (guint32 i = 0; i < esm_sz; i++) checksum += *(payload_data + EEPROM_ESM_OFFSET +i); if (!fu_synaptics_mst_device_get_flash_checksum (self, 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 fu_synaptics_mst_device_update_tesla_leaf_firmware (FuSynapticsMstDevice *self, guint32 payload_len, const guint8 *payload_data, GError **error) { g_autoptr(FuSynapticsMstConnection) 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 = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), self->layer, self->rad); for (guint32 retries_cnt = 0; ; retries_cnt++) { guint32 checksum = 0; guint32 flash_checksum = 0; if (!fu_synaptics_mst_device_set_flash_sector_erase (self, 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 (!fu_synaptics_mst_connection_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 (!fu_synaptics_mst_connection_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; fu_device_set_progress_full (FU_DEVICE (self), (goffset) i * 100, (goffset) (write_loops -1) * 100); } /* check data just written */ for (guint32 i = 0; i < payload_len; i++) checksum += *(payload_data + i); if (!fu_synaptics_mst_device_get_flash_checksum (self, 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 fu_synaptics_mst_device_get_active_bank_panamera (FuSynapticsMstDevice *self, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; guint32 buf[16]; /* get used bank */ connection = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), self->layer, self->rad); if (!fu_synaptics_mst_connection_rc_get_command (connection, UPDC_READ_FROM_MEMORY, ((sizeof(buf)/sizeof(buf[0]))*4), (gint) 0x20010c, (guint8*) buf, error)) { g_prefix_error (error, "get active bank failed: "); return FALSE; } if ((buf[0] & BIT(7)) || (buf[0] & BIT(30))) self->active_bank = BANKTAG_1; else self->active_bank = BANKTAG_0; return TRUE; } static gboolean fu_synaptics_mst_device_update_panamera_firmware (FuSynapticsMstDevice *self, guint32 payload_len, const guint8 *payload_data, GError **error) { guint16 crc_tmp = 0; guint32 fw_size; guint32 unit_sz = BLOCK_UNIT; guint32 write_loops = 0; guint8 bank_to_update = BANKTAG_1; guint8 readBuf[256]; guint8 tagData[16]; struct tm *pTM; time_t timeptr; g_autoptr(FuSynapticsMstConnection) connection = NULL; /* get used bank */ if (!fu_synaptics_mst_device_get_active_bank_panamera (self, error)) return FALSE; if (self->active_bank == 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 (!fu_synaptics_mst_device_set_flash_sector_erase (self, FLASH_SECTOR_ERASE_64K, erase_offset++, error)) return FALSE; if (!fu_synaptics_mst_device_set_flash_sector_erase (self, 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 = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), self->layer, self->rad); for (guint32 i = 0; i < write_loops ; i++ ) { g_autoptr(GError) error_local = NULL; if (!fu_synaptics_mst_connection_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 (!fu_synaptics_mst_connection_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; fu_device_set_progress_full (FU_DEVICE (self), (goffset) i * 100, (goffset) (write_loops -1) * 100); } /* verify CRC */ checksum = fu_synaptics_mst_device_get_crc (0, 16, fw_size, payload_data ); for (guint32 i = 0; i < 4; i++) { g_usleep (1000); /* wait crc calculation */ if (!fu_synaptics_mst_connection_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 = fu_synaptics_mst_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) fu_synaptics_mst_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 (!fu_synaptics_mst_connection_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 (!fu_synaptics_mst_connection_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 (!fu_synaptics_mst_connection_rc_get_command (connection, UPDC_READ_FROM_EEPROM, 1, (EEPROM_BANK_OFFSET * self->active_bank + 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 * self->active_bank + EEPROM_BANK_OFFSET - 0x1000) / 0x1000; if (!fu_synaptics_mst_device_set_flash_sector_erase (self, FLASH_SECTOR_ERASE_4K, erase_offset, error)) return FALSE; /* CRC8 is 0xff, set it to 0x00 */ } else { tagData[1] = 0x00; if (!fu_synaptics_mst_connection_rc_set_command (connection, UPDC_WRITE_TO_EEPROM, 1, (EEPROM_BANK_OFFSET * self->active_bank + EEPROM_TAG_OFFSET + 15), &tagData[1], error)) { g_prefix_error (error, "failed to clear CRC: "); return FALSE; } } if (!fu_synaptics_mst_connection_rc_get_command (connection, UPDC_READ_FROM_EEPROM, 1, (EEPROM_BANK_OFFSET * self->active_bank + 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 fu_synaptics_mst_device_panamera_prepare_write (FuSynapticsMstDevice *self, GError **error) { guint32 buf[4] = {0}; g_autoptr(FuSynapticsMstConnection) connection = NULL; /* Need to detect flash mode and ESM first ? */ /* disable flash Quad mode and ESM/HDCP2.2*/ connection = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), self->layer, self->rad); /* disable ESM first */ buf[0] = 0x21; if (!fu_synaptics_mst_connection_rc_set_command (connection, UPDC_WRITE_TO_MEMORY, 4, (gint)REG_ESM_DISABLE, (guint8*)buf, error)) { g_prefix_error (error, "ESM disable failed: "); return FALSE; } /* wait for ESM exit */ g_usleep (200); /* disable QUAD mode */ if (!fu_synaptics_mst_connection_rc_get_command (connection, UPDC_READ_FROM_MEMORY, ((sizeof(buf)/sizeof(buf[0]))*4), (gint)REG_QUAD_DISABLE, (guint8*)buf, error)) { g_prefix_error (error, "quad query failed: "); return FALSE; } buf[0] = 0x00; if (!fu_synaptics_mst_connection_rc_set_command (connection, UPDC_WRITE_TO_MEMORY, 4, (gint)REG_QUAD_DISABLE, (guint8*)buf, error)) { g_prefix_error (error, "quad disable failed: "); return FALSE; } /* disable HDCP2.2 */ if (!fu_synaptics_mst_connection_rc_get_command (connection, UPDC_READ_FROM_MEMORY, 4, (gint)REG_HDCP22_DISABLE, (guint8*)buf, error)) { g_prefix_error (error, "HDCP query failed: "); return FALSE; } buf[0] = buf[0] & (~BIT(2)); if (!fu_synaptics_mst_connection_rc_set_command (connection, UPDC_WRITE_TO_MEMORY, 4, (gint)REG_HDCP22_DISABLE, (guint8*)buf, error)) { g_prefix_error (error, "HDCP disable failed: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_device_restart (FuSynapticsMstDevice *self, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; guint8 buf[4] = {0xF5, 0, 0 ,0}; g_autoptr(GError) error_local = NULL; /* issue the reboot command, ignore return code (triggers before returning) */ connection = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), self->layer, self->rad); if (!fu_synaptics_mst_connection_rc_set_command (connection, UPDC_WRITE_TO_MEMORY, 4, (gint) 0x2000FC, (guint8*) &buf, &error_local)) g_debug ("failed to restart: %s", error_local->message); return TRUE; } static FuFirmware * fu_synaptics_mst_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE (device); /* check firmware and board ID match */ if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && !fu_device_has_custom_flag (device, "ignore-board-id")) { const guint8 *buf; gsize len; guint16 board_id; buf = g_bytes_get_data (fw, &len); board_id = fu_common_read_uint16 (buf + ADDR_CUSTOMER_ID, G_BIG_ENDIAN); if (board_id != self->board_id) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "board ID mismatch, got 0x%04x, expected 0x%04x", board_id, self->board_id); return NULL; } } return fu_firmware_new_from_bytes (fw); } static gboolean fu_synaptics_mst_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE (device); g_autoptr(GBytes) fw = NULL; const guint8 *payload_data; gsize payload_len; g_autoptr(FuDeviceLocker) locker = NULL; fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; payload_data = g_bytes_get_data (fw, &payload_len); /* enable remote control and disable on exit */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); if (!fu_device_has_custom_flag (device, "skip-restart")) { locker = fu_device_locker_new_full (self, (FuDeviceLockerFunc) fu_synaptics_mst_device_enable_rc, (FuDeviceLockerFunc) fu_synaptics_mst_device_restart, error); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fu_device_set_remove_delay (FU_DEVICE (self), 10000); /* a long time */ } else { locker = fu_device_locker_new_full (self, (FuDeviceLockerFunc) fu_synaptics_mst_device_enable_rc, (FuDeviceLockerFunc) fu_synaptics_mst_device_disable_rc, error); } if (locker == NULL) return FALSE; /* update firmware */ if (self->family == FU_SYNAPTICS_MST_FAMILY_PANAMERA) { if (!fu_synaptics_mst_device_panamera_prepare_write (self, error)) { g_prefix_error (error, "Failed to prepare for write: "); return FALSE; } if (!fu_synaptics_mst_device_update_esm (self, payload_data, error)) { g_prefix_error (error, "ESM update failed: "); return FALSE; } if (!fu_synaptics_mst_device_update_panamera_firmware (self, payload_len, payload_data, error)) { g_prefix_error (error, "Firmware update failed: "); return FALSE; } } else { if (!fu_synaptics_mst_device_update_tesla_leaf_firmware (self, payload_len, payload_data, error)) { g_prefix_error (error, "Firmware update failed: "); return FALSE; } } fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); return TRUE; } FuSynapticsMstDevice * fu_synaptics_mst_device_new (FuUdevDevice *device) { FuSynapticsMstDevice *self = g_object_new (FU_TYPE_SYNAPTICS_MST_DEVICE, NULL); fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device)); return self; } static gboolean fu_synaptics_mst_device_read_board_id (FuSynapticsMstDevice *self, FuSynapticsMstConnection *connection, guint8 *byte, GError **error) { /* in test mode we need to open a different file node instead */ if (fu_udev_device_get_dev (FU_UDEV_DEVICE (self)) == NULL) { g_autofree gchar *filename = NULL; g_autofree gchar *dirname = NULL; gint fd; dirname = g_path_get_dirname (fu_udev_device_get_device_file (FU_UDEV_DEVICE (self))); filename = g_strdup_printf ("%s/remote/%s_eeprom", dirname, fu_device_get_logical_id (FU_DEVICE (self))); 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); return TRUE; } /* get board ID via MCU address 0x170E instead of flash access due to HDCP2.2 running */ if (!fu_synaptics_mst_connection_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 fu_synaptics_mst_device_scan_cascade (FuSynapticsMstDevice *self, guint8 layer, GError **error) { /* in test mode we skip this */ if (fu_udev_device_get_dev (FU_UDEV_DEVICE (self)) == NULL) return TRUE; /* test each relative address in this layer */ for (guint16 rad = 0; rad <= 2; rad++) { guint8 byte[4]; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuSynapticsMstConnection) connection = NULL; g_autoptr(FuSynapticsMstDevice) device_tmp = NULL; g_autoptr(GError) error_local = NULL; /* enable remote control and disable on exit */ device_tmp = fu_synaptics_mst_device_new (FU_UDEV_DEVICE (self)); device_tmp->layer = layer; device_tmp->rad = rad; locker = fu_device_locker_new_full (device_tmp, (FuDeviceLockerFunc) fu_synaptics_mst_device_enable_rc, (FuDeviceLockerFunc) fu_synaptics_mst_device_disable_rc, &error_local); if (locker == NULL) { g_debug ("no cascade device found: %s", error_local->message); continue; } connection = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), layer + 1, rad); if (!fu_synaptics_mst_connection_read (connection, REG_RC_CAP, byte, 1, &error_local)) { g_debug ("no valid cascade device: %s", error_local->message); continue; } /* check recursively for more devices */ g_clear_object (&locker); self->mode = FU_SYNAPTICS_MST_MODE_REMOTE; self->layer = layer + 1; self->rad = rad; if (!fu_synaptics_mst_device_scan_cascade (self, layer + 1, error)) return FALSE; } return TRUE; } void fu_synaptics_mst_device_set_system_type (FuSynapticsMstDevice *self, const gchar *system_type) { g_return_if_fail (FU_IS_SYNAPTICS_MST_DEVICE (self)); self->system_type = g_strdup (system_type); } static gboolean fu_synaptics_mst_device_rescan (FuDevice *device, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE (device); FuQuirks *quirks; guint8 buf_vid[4]; g_autoptr(FuSynapticsMstConnection) connection = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autofree gchar *version = NULL; g_autofree gchar *guid1 = NULL; g_autofree gchar *guid2 = NULL; g_autofree gchar *guid3 = NULL; g_autofree gchar *group = NULL; g_autofree gchar *name = NULL; const gchar *guid_template; const gchar *name_parent; const gchar *name_family; guint8 buf_ver[16]; /* read vendor ID */ connection = fu_synaptics_mst_connection_new (fu_udev_device_get_fd (FU_UDEV_DEVICE (self)), 0, 0); if (!fu_synaptics_mst_connection_read (connection, REG_RC_CAP, buf_vid, 1, error)) { g_prefix_error (error, "failed to read device: "); return FALSE; } if (buf_vid[0] & 0x04) { if (!fu_synaptics_mst_connection_read (connection, REG_VENDOR_ID, buf_vid, 3, error)) { g_prefix_error (error, "failed to read vendor ID: "); return FALSE; } /* not a correct device */ if (buf_vid[0] != 0x90 || buf_vid[1] != 0xCC || buf_vid[2] != 0x24) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no device"); return FALSE; } } /* direct */ self->mode = FU_SYNAPTICS_MST_MODE_DIRECT; self->layer = 0; self->rad = 0; /* enable remote control and disable on exit */ locker = fu_device_locker_new_full (self, (FuDeviceLockerFunc) fu_synaptics_mst_device_enable_rc, (FuDeviceLockerFunc) fu_synaptics_mst_device_disable_rc, error); if (locker == NULL) return FALSE; /* read firmware version */ if (!fu_synaptics_mst_connection_read (connection, REG_FIRMWARE_VERSION, buf_ver, 3, error)) return FALSE; version = g_strdup_printf ("%1d.%02d.%02d", buf_ver[0], buf_ver[1], buf_ver[2]); fu_device_set_version (FU_DEVICE (self), version, FWUPD_VERSION_FORMAT_TRIPLET); /* read board ID */ if (!fu_synaptics_mst_device_read_board_id (self, connection, buf_ver, error)) return FALSE; self->board_id = fu_common_read_uint16 (buf_ver, G_BIG_ENDIAN); /* read board chip_id */ if (!fu_synaptics_mst_connection_read (connection, REG_CHIP_ID, buf_ver, 2, error)) { g_prefix_error (error, "failed to read chip id: "); return FALSE; } self->chip_id = (buf_ver[0] << 8) | (buf_ver[1]); if (self->chip_id == 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid chip ID"); return FALSE; } self->family = fu_synaptics_mst_family_from_chip_id (self->chip_id); /* check the active bank for debugging */ if (self->family == FU_SYNAPTICS_MST_FAMILY_PANAMERA) { if (!fu_synaptics_mst_device_get_active_bank_panamera (self, error)) return FALSE; } /* recursively look for cascade devices */ g_clear_object (&locker); if (!fu_synaptics_mst_device_scan_cascade (self, 0, error)) return FALSE; /* set up the device name via quirks */ group = g_strdup_printf ("SynapticsMSTBoardID=%u", self->board_id); quirks = fu_device_get_quirks (FU_DEVICE (self)); name_parent = fu_quirks_lookup_by_id (quirks, group, FU_QUIRKS_NAME); if (name_parent != NULL) { name = g_strdup_printf ("VMM%04x inside %s", self->chip_id, name_parent); } else { name = g_strdup_printf ("VMM%04x", self->chip_id); } fu_device_set_name (FU_DEVICE (self), name); /* this is a host system, use system ID */ guid_template = fu_quirks_lookup_by_id (quirks, group, "DeviceKind"); name_family = fu_synaptics_mst_family_to_string (self->family); if (g_strcmp0 (guid_template, "system") == 0) { g_autofree gchar *guid = NULL; guid = g_strdup_printf ("MST-%s-%s-%u", name_family, self->system_type, self->board_id); fu_device_add_instance_id (FU_DEVICE (self), guid); /* docks or something else */ } else if (guid_template != NULL) { g_auto(GStrv) templates = NULL; templates = g_strsplit (guid_template, ",", -1); for (guint i = 0; templates[i] != NULL; i++) { g_autofree gchar *dock_id1 = NULL; g_autofree gchar *dock_id2 = NULL; dock_id1 = g_strdup_printf ("MST-%s-%u", templates[i], self->board_id); fu_device_add_instance_id (FU_DEVICE (self), dock_id1); dock_id2 = g_strdup_printf ("MST-%s-vmm%04x-%u", templates[i], self->chip_id, self->board_id); fu_device_add_instance_id (FU_DEVICE (self), dock_id2); } } /* detect chip family */ switch (self->family) { case FU_SYNAPTICS_MST_FAMILY_TESLA: fu_device_set_firmware_size_max (device, 0x10000); fu_device_add_instance_id (device, "MST-tesla"); break; case FU_SYNAPTICS_MST_FAMILY_LEAF: fu_device_set_firmware_size_max (device, 0x10000); fu_device_add_instance_id (device, "MST-leaf"); break; case FU_SYNAPTICS_MST_FAMILY_PANAMERA: fu_device_set_firmware_size_max (device, 0x80000); fu_device_add_instance_id (device, "MST-panamera"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); break; default: break; } /* add non-standard GUIDs */ guid1 = g_strdup_printf ("MST-%s-vmm%04x-%u", name_family, self->chip_id, self->board_id); fu_device_add_instance_id (FU_DEVICE (self), guid1); guid2 = g_strdup_printf ("MST-%s-%u", name_family, self->board_id); fu_device_add_instance_id (FU_DEVICE (self), guid2); guid3 = g_strdup_printf ("MST-%s", name_family); fu_device_add_instance_id (FU_DEVICE (self), guid3); /* success */ return TRUE; } static void fu_synaptics_mst_device_class_init (FuSynapticsMstDeviceClass *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_synaptics_mst_device_finalize; klass_device->to_string = fu_synaptics_mst_device_to_string; klass_device->rescan = fu_synaptics_mst_device_rescan; klass_device->write_firmware = fu_synaptics_mst_device_write_firmware; klass_device->prepare_firmware = fu_synaptics_mst_device_prepare_firmware; klass_udev_device->probe = fu_synaptics_mst_device_probe; } fwupd-1.3.9/plugins/synaptics-mst/fu-synaptics-mst-device.h000066400000000000000000000010041362775233600240150ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_SYNAPTICS_MST_DEVICE (fu_synaptics_mst_device_get_type ()) G_DECLARE_FINAL_TYPE (FuSynapticsMstDevice, fu_synaptics_mst_device, FU, SYNAPTICS_MST_DEVICE, FuUdevDevice) FuSynapticsMstDevice *fu_synaptics_mst_device_new (FuUdevDevice *device); void fu_synaptics_mst_device_set_system_type (FuSynapticsMstDevice *self, const gchar *system_type); fwupd-1.3.9/plugins/synaptics-mst/meson.build000066400000000000000000000025141362775233600213320ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsMST"'] install_data(['synaptics-mst.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_synaptics_mst', fu_hash, sources : [ 'fu-plugin-synaptics-mst.c', 'fu-synaptics-mst-common.c', 'fu-synaptics-mst-connection.c', 'fu-synaptics-mst-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, ], link_with : [ fwupd, fwupdplugin, ], dependencies : [ plugin_deps, ], ) if get_option('tests') cargs += '-DPLUGINBUILDDIR="' + meson.current_build_dir() + '"' cargs += '-DSOURCEDIR="' + meson.current_source_dir() + '"' e = executable( 'synaptics-mst-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-synaptics-mst-common.c', 'fu-synaptics-mst-connection.c', 'fu-synaptics-mst-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, sqlite, valgrind, ], link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, ], ) test('synaptics-mst-self-test', e, env: ['FWUPD_LOCALSTATEDIR=/tmp/fwupd-self-test/var']) endif fwupd-1.3.9/plugins/synaptics-mst/synaptics-mst-evb.quirk000066400000000000000000000011711362775233600236330ustar00rootroot00000000000000# 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.3.9/plugins/synaptics-mst/synaptics-mst.quirk000066400000000000000000000026001362775233600230570ustar00rootroot00000000000000# match all devices with this udev subsystem [DeviceInstanceId=DRM_DP_AUX_DEV] Plugin = synaptics_mst # 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 delimited 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=259] Name = Dell dock # ThinkPad Workstation Dock [DeviceInstanceId=MST-tesla-vmm2322-513] ParentGuid = USB\VID_17EF&PID_305A # ThinkPad Thunderbolt 3 Workstation Dock [DeviceInstanceId=MST-panamera-vmm5322-595] ParentGuid = TBT-01081720 [SynapticsMSTBoardID=596] Name = ThinkPad USB-C Dock Gen2 fwupd-1.3.9/plugins/synaptics-mst/tests/000077500000000000000000000000001362775233600203305ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-mst/tests/no_devices/000077500000000000000000000000001362775233600224465ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-mst/tests/no_devices/drm_dp_aux0000066400000000000000000003720001362775233600245750ustar00rootroot00000000000000 A   wU@fwupd-1.3.9/plugins/synaptics-mst/tests/no_devices/drm_dp_aux1000066400000000000000000000000001362775233600245620ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-mst/tests/no_devices/drm_dp_aux2000066400000000000000000000000001362775233600245630ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-mst/tests/tb16_dock/000077500000000000000000000000001362775233600221045ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-mst/tests/tb16_dock/drm_dp_aux0000066400000000000000000003720001362775233600242330ustar00rootroot00000000000000 A   wU@fwupd-1.3.9/plugins/synaptics-mst/tests/tb16_dock/drm_dp_aux1000066400000000000000000011610001362775233600242310ustar00rootroot00000000000000wO wO  ?TESLA$SYNA30 ,] Non-PnP fwupd-1.3.9/plugins/synaptics-mst/tests/tb16_dock/drm_dp_aux2000066400000000000000000011610001362775233600242320ustar00rootroot00000000000000|O |O  ?TESLA$SYNA3  G0fwupd-1.3.9/plugins/synaptics-mst/tests/tb16_dock/remote/000077500000000000000000000000001362775233600233775ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-mst/tests/tb16_dock/remote/drm_dp_aux0000066400000000000000000007640001362775233600255330ustar00rootroot00000000000000 A   wU@fwupd-1.3.9/plugins/synaptics-mst/tests/tb16_dock/remote/drm_dp_aux1000066400000000000000000003720001362775233600255270ustar00rootroot00000000000000* *  ?TESLAIUS$SYNA30 ,] Non-PnP fwupd-1.3.9/plugins/synaptics-mst/tests/tb16_dock/remote/drm_dp_aux1_eeprom000066400000000000000000000000021362775233600270640ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-mst/tests/tb16_dock/remote/drm_dp_aux2000066400000000000000000007640001362775233600255350ustar00rootroot000000000000002 , 2 ,  ?TESLAPRIUS$SYNA3  ,] Non-PnP fwupd-1.3.9/plugins/synaptics-mst/tests/tb16_dock/remote/drm_dp_aux2_eeprom000066400000000000000000000000021362775233600270650ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-prometheus/000077500000000000000000000000001362775233600205565ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-prometheus/README.md000066400000000000000000000013501362775233600220340ustar00rootroot00000000000000Synaptics 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x06CB` fwupd-1.3.9/plugins/synaptics-prometheus/data/000077500000000000000000000000001362775233600214675ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-prometheus/data/lsusb.txt000066400000000000000000000044351362775233600233660ustar00rootroot00000000000000Bus 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.3.9/plugins/synaptics-prometheus/data/test.pkg000066400000000000000000000004461362775233600231550ustar00rootroot00000000000000A RHfwupd-1.3.9/plugins/synaptics-prometheus/fu-dump.c000066400000000000000000000025761362775233600223110ustar00rootroot00000000000000/* * 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(FuFirmware) firmware = fu_synaprom_firmware_new (); if (!g_file_get_contents (filename, &data, &len, error)) return FALSE; blob = g_bytes_new_take (data, len); return fu_firmware_parse (firmware, blob, 0, error); } static gboolean fu_dump_generate (const gchar *filename, GError **error) { const gchar *data; gsize len = 0; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) firmware = fu_synaprom_firmware_new (); blob = fu_firmware_write (firmware, error); if (blob == NULL) return FALSE; 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.3.9/plugins/synaptics-prometheus/fu-plugin-synaptics-prometheus.c000066400000000000000000000007441362775233600270410ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaprom-device.h" #include "fu-synaprom-firmware.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_SYNAPROM_DEVICE); fu_plugin_add_firmware_gtype (plugin, "synaprom", FU_TYPE_SYNAPROM_FIRMWARE); } fwupd-1.3.9/plugins/synaptics-prometheus/fu-self-test.c000066400000000000000000000047511362775233600232470ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-plugin-private.h" #include "fu-synaprom-device.h" #include "fu-synaprom-firmware.h" static void fu_test_synaprom_firmware_func (void) { const guint8 *buf; gboolean ret; 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(FuFirmware) firmware2 = NULL; g_autoptr(FuFirmware) firmware = fu_synaprom_firmware_new (); filename = g_build_filename (TESTDATADIR, "test.pkg", NULL); 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); ret = fu_firmware_parse (firmware, fw, 0, &error); g_assert_no_error (error); g_assert_true (ret); /* does not exist */ blob1 = fu_firmware_get_image_by_id_bytes (firmware, "NotGoingToExist", NULL); g_assert_null (blob1); blob1 = fu_firmware_get_image_by_id_bytes (firmware, "cfg-update-header", NULL); g_assert_null (blob1); /* header needs to exist */ blob1 = fu_firmware_get_image_by_id_bytes (firmware, "mfw-update-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); firmware2 = fu_synaprom_device_prepare_fw (FU_DEVICE (device), fw, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert_nonnull (firmware2); blob2 = fu_firmware_get_image_by_id_bytes (firmware2, "mfw-update-payload", &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.3.9/plugins/synaptics-prometheus/fu-synaprom-common.c000066400000000000000000000055561362775233600245030ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-common.h" #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 (); fu_byte_array_append_uint8 (blob, cmd); 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.3.9/plugins/synaptics-prometheus/fu-synaprom-common.h000066400000000000000000000006441362775233600245010ustar00rootroot00000000000000/* * 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.3.9/plugins/synaptics-prometheus/fu-synaprom-config.c000066400000000000000000000222441362775233600244510ustar00rootroot00000000000000/* * 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; 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) static gboolean fu_synaprom_config_setup (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); 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 (FU_SYNAPROM_DEVICE (parent), 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; } if (!fu_memcpy_safe ((guint8 *) &cfg, sizeof(cfg), 0x0, /* dst */ reply->data, reply->len, sizeof(hdr), /* src */ sizeof(cfg), error)) return FALSE; 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 FuFirmware * 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(FuFirmware) firmware = fu_synaprom_firmware_new (); guint32 product; guint32 id1; /* parse the firmware */ fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; /* check the update header product and version */ blob = fu_firmware_get_image_by_id_bytes (firmware, "cfg-update-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; } } /* success */ return g_steal_pointer (&firmware); } static gboolean fu_synaprom_config_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent (device); g_autoptr(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_image_by_id_bytes (firmware, "cfg-update-payload", error); if (fw == NULL) return FALSE; /* I assume the CFG/MFW difference is detected in the device...*/ return fu_synaprom_device_write_fw (FU_SYNAPROM_DEVICE (parent), fw, error); } static void fu_synaprom_config_init (FuSynapromConfig *self) { fu_device_set_protocol (FU_DEVICE (self), "com.synaptics.prometheus.config"); 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_constructed (GObject *obj) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (obj); FuDevice *parent = fu_device_get_parent (FU_DEVICE (self)); 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 (parent)), fu_usb_device_get_pid (FU_USB_DEVICE (parent))); fu_device_add_instance_id (FU_DEVICE (self), devid); G_OBJECT_CLASS (fu_synaprom_config_parent_class)->constructed (obj); } static gboolean fu_synaprom_config_open (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); return fu_device_open (parent, error); } static gboolean fu_synaprom_config_close (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); return fu_device_close (parent, error); } static gboolean fu_synaprom_config_attach (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); return fu_device_attach (parent, error); } static gboolean fu_synaprom_config_detach (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); return fu_device_detach (parent, error); } static void fu_synaprom_config_flags_notify_cb (FuDevice *parent, GParamSpec *pspec, FuDevice *device) { fu_device_incorporate_flag (device, parent, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } static void fu_synaprom_config_class_init (FuSynapromConfigClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = fu_synaprom_config_constructed; 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->reload = fu_synaprom_config_setup; klass_device->attach = fu_synaprom_config_attach; klass_device->detach = fu_synaprom_config_detach; } FuSynapromConfig * fu_synaprom_config_new (FuSynapromDevice *device) { FuSynapromConfig *self; self = g_object_new (FU_TYPE_SYNAPROM_CONFIG, "parent", device, NULL); /* mirror the bootloader flag on the parent to the child */ if (fu_device_has_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); g_signal_connect (device, "notify::flags", G_CALLBACK (fu_synaprom_config_flags_notify_cb), self); return FU_SYNAPROM_CONFIG (self); } fwupd-1.3.9/plugins/synaptics-prometheus/fu-synaprom-config.h000066400000000000000000000006711362775233600244560ustar00rootroot00000000000000/* * 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" #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); fwupd-1.3.9/plugins/synaptics-prometheus/fu-synaprom-device.c000066400000000000000000000333561362775233600244510ustar00rootroot00000000000000/* * 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; /* 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 (fu_device_get_children (device)->len == 0 && !fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER) && 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); } FuFirmware * fu_synaprom_device_prepare_fw (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapromFirmwareMfwHeader hdr; guint32 product; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) firmware = fu_synaprom_firmware_new (); /* parse the firmware */ fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; /* check the update header product and version */ blob = fu_firmware_get_image_by_id_bytes (firmware, "mfw-update-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; } } /* success */ return g_steal_pointer (&firmware); } 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, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuSynapromDevice *self = FU_SYNAPROM_DEVICE (device); g_autoptr(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_image_by_id_bytes (firmware, "mfw-update-payload", error); if (fw == NULL) return FALSE; 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; } fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); 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; } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static void fu_synaprom_device_init (FuSynapromDevice *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY); fu_device_set_protocol (FU_DEVICE (self), "com.synaptics.prometheus"); 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->reload = 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.3.9/plugins/synaptics-prometheus/fu-synaprom-device.h000066400000000000000000000024201362775233600244420ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #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); FuFirmware *fu_synaprom_device_prepare_fw (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error); fwupd-1.3.9/plugins/synaptics-prometheus/fu-synaprom-firmware.c000066400000000000000000000104151362775233600250150ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "fu-synaprom-firmware.h" struct _FuSynapromFirmware { FuFirmware parent_instance; }; G_DEFINE_TYPE (FuSynapromFirmware, fu_synaprom_firmware, FU_TYPE_FIRMWARE) typedef struct __attribute__((packed)) { guint16 tag; guint32 bufsz; } FuSynapromFirmwareHdr; /* 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 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; } static gboolean fu_synaprom_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { const guint8 *buf; gsize bufsz = 0; gsize offset = 0; g_return_val_if_fail (fw != NULL, FALSE); buf = g_bytes_get_data (fw, &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 FALSE; } bufsz -= FU_SYNAPROM_FIRMWARE_SIGSIZE; /* parse each chunk */ while (offset != bufsz) { FuSynapromFirmwareHdr header; guint32 hdrsz; guint32 tag; g_autoptr(GBytes) bytes = NULL; g_autoptr(FuFirmwareImage) img = NULL; /* verify item header */ memcpy (&header, buf, sizeof(header)); tag = GUINT16_FROM_LE(header.tag); if (tag >= FU_SYNAPROM_FIRMWARE_TAG_MAX) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "tag 0x%04x is too large", tag); return FALSE; } 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 FALSE; } /* move pointer to data */ buf += sizeof(header); bytes = g_bytes_new (buf, hdrsz); g_debug ("adding 0x%04x (%s) with size 0x%04x", tag, fu_synaprom_firmware_tag_to_string (tag), hdrsz); img = fu_firmware_image_new (bytes); fu_firmware_image_set_idx (img, tag); fu_firmware_image_set_id (img, fu_synaprom_firmware_tag_to_string (tag)); fu_firmware_add_image (firmware, img); /* next item */ buf += hdrsz; } return TRUE; } static GBytes * fu_synaprom_firmware_write (FuFirmware *self, GError **error) { 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, }; /* add header */ fu_byte_array_append_uint16 (blob, FU_SYNAPROM_FIRMWARE_TAG_MFW_HEADER, G_LITTLE_ENDIAN); fu_byte_array_append_uint32 (blob, sizeof(hdr), G_LITTLE_ENDIAN); g_byte_array_append (blob, (const guint8 *) &hdr, sizeof(hdr)); /* add payload */ fu_byte_array_append_uint16 (blob, FU_SYNAPROM_FIRMWARE_TAG_MFW_PAYLOAD, G_LITTLE_ENDIAN); fu_byte_array_append_uint32 (blob, sizeof(data), G_LITTLE_ENDIAN); g_byte_array_append (blob, data, sizeof(data)); /* add signature */ for (guint i = 0; i < FU_SYNAPROM_FIRMWARE_SIGSIZE; i++) fu_byte_array_append_uint8 (blob, 0xff); return g_byte_array_free_to_bytes (blob); } static void fu_synaprom_firmware_init (FuSynapromFirmware *self) { } static void fu_synaprom_firmware_class_init (FuSynapromFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->parse = fu_synaprom_firmware_parse; klass_firmware->write = fu_synaprom_firmware_write; } FuFirmware * fu_synaprom_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_SYNAPROM_FIRMWARE, NULL)); } fwupd-1.3.9/plugins/synaptics-prometheus/fu-synaprom-firmware.h000066400000000000000000000023541362775233600250250ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #include "fu-synaprom-firmware.h" #define FU_TYPE_SYNAPROM_FIRMWARE (fu_synaprom_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuSynapromFirmware, fu_synaprom_firmware, FU, SYNAPROM_FIRMWARE, FuFirmware) #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; FuFirmware *fu_synaprom_firmware_new (void); fwupd-1.3.9/plugins/synaptics-prometheus/meson.build000066400000000000000000000031571362775233600227260ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], 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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], 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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ gio, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) endif fwupd-1.3.9/plugins/synaptics-prometheus/synaptics-prometheus.quirk000066400000000000000000000002671362775233600260460ustar00rootroot00000000000000[DeviceInstanceId=USB\VID_06CB&PID_00A9] Plugin = synaptics_prometheus InstallDuration = 2 [DeviceInstanceId=USB\VID_06CB&PID_00BD] Plugin = synaptics_prometheus InstallDuration = 2 fwupd-1.3.9/plugins/synaptics-rmi/000077500000000000000000000000001362775233600171525ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-rmi/README.md000066400000000000000000000013551362775233600204350ustar00rootroot00000000000000Synaptics RMI4 Support ====================== Introduction ------------ This plugin updates integrated Synaptics RMI4 devices, typically touchpads. GUID Generation --------------- The HID DeviceInstanceId values are used, e.g. `HIDRAW\VEN_06CB&DEV_4875`. These devices also use custom GUID values constructed using the board ID, e.g. * `SYNAPTICS_RMI\TM3038-002` * `SYNAPTICS_RMI\TM3038` Vendor ID Security ------------------ The vendor ID is set from the udev vendor, in this instance set to `HIDRAW:0x06CB` Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in a proprietary (but docucumented) file format. This plugin supports the following protocol ID: * com.synaptics.rmi fwupd-1.3.9/plugins/synaptics-rmi/fu-dump.c000066400000000000000000000052611362775233600206770ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-rmi-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(FuFirmware) firmware = fu_synaptics_rmi_firmware_new (); g_autofree gchar *str = NULL; if (!g_file_get_contents (filename, &data, &len, error)) return FALSE; blob = g_bytes_new_take (data, len); if (!fu_firmware_parse (firmware, blob, FWUPD_INSTALL_FLAG_FORCE, error)) return FALSE; str = fu_firmware_to_string (firmware); g_print ("%s", str); return TRUE; } static gboolean fu_dump_generate_v0x (const gchar *filename, GError **error) { const gchar *data; gsize len = 0; g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) fw = fu_synaptics_rmi_firmware_generate_v0x (); g_autoptr(FuFirmware) firmware = fu_synaptics_rmi_firmware_new (); g_autoptr(FuFirmwareImage) image = fu_firmware_image_new (fw); fu_firmware_add_image (firmware, image); blob = fu_firmware_write (firmware, error); if (blob == NULL) return FALSE; data = g_bytes_get_data (blob, &len); return g_file_set_contents (filename, data, len, error); } static gboolean fu_dump_generate_v10 (const gchar *filename, GError **error) { const gchar *data; gsize len = 0; g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) fw = fu_synaptics_rmi_firmware_generate_v10 (); g_autoptr(FuFirmware) firmware = fu_synaptics_rmi_firmware_new (); g_autoptr(FuFirmwareImage) image = fu_firmware_image_new (fw); fu_firmware_add_image (firmware, image); blob = fu_firmware_write (firmware, error); if (blob == NULL) return FALSE; data = g_bytes_get_data (blob, &len); return g_file_set_contents (filename, data, len, error); } int main (int argc, char **argv) { gint rc = 0; /* no args */ if (argc <= 1) { g_printerr ("firmware filename required\n"); return 2; } /* tool gen fn */ if (argc == 3 && g_strcmp0 (argv[1], "gen0x") == 0) { g_autoptr(GError) error = NULL; if (!fu_dump_generate_v0x (argv[2], &error)) { g_printerr ("generate failed: %s\n", error->message); return 1; } return 0; } if (argc == 3 && g_strcmp0 (argv[1], "gen10") == 0) { g_autoptr(GError) error = NULL; if (!fu_dump_generate_v10 (argv[2], &error)) { g_printerr ("generate failed: %s\n", error->message); return 1; } return 0; } /* tool fn [fn2] [fn3] */ for (gint i = 1; i < argc; i++) { g_autoptr(GError) error = NULL; if (!fu_dump_parse (argv[i], &error)) { g_printerr ("parse failed: %s\n", error->message); rc = 1; } if (rc != 0) return rc; } /* success */ g_print ("OK!\n"); return rc; } fwupd-1.3.9/plugins/synaptics-rmi/fu-plugin-synaptics-rmi.c000066400000000000000000000010451362775233600240240ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-synaptics-rmi-device.h" #include "fu-synaptics-rmi-firmware.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_udev_subsystem (plugin, "hidraw"); fu_plugin_set_device_gtype (plugin, FU_TYPE_SYNAPTICS_RMI_DEVICE); fu_plugin_add_firmware_gtype (plugin, "rmi", FU_TYPE_SYNAPTICS_RMI_FIRMWARE); } fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-common.c000066400000000000000000000056541362775233600240300ustar00rootroot00000000000000/* * Copyright (C) 2012-2014 Andrew Duggan * Copyright (C) 2012-2019 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-common.h" #include "fu-io-channel.h" #include "fu-synaptics-rmi-common.h" #include "fwupd-error.h" #define RMI_FUNCTION_QUERY_OFFSET 0 #define RMI_FUNCTION_COMMAND_OFFSET 1 #define RMI_FUNCTION_CONTROL_OFFSET 2 #define RMI_FUNCTION_DATA_OFFSET 3 #define RMI_FUNCTION_INTERRUPT_SOURCES_OFFSET 4 #define RMI_FUNCTION_NUMBER 5 #define RMI_FUNCTION_VERSION_MASK 0x60 #define RMI_FUNCTION_INTERRUPT_SOURCES_MASK 0x7 guint32 fu_synaptics_rmi_generate_checksum (const guint8 *data, gsize len) { guint32 lsw = 0xffff; guint32 msw = 0xffff; for (gsize i = 0; i < len / 2; i++) { lsw += fu_common_read_uint16 (&data[i * 2], G_LITTLE_ENDIAN); msw += lsw; lsw = (lsw & 0xffff) + (lsw >> 16); msw = (msw & 0xffff) + (msw >> 16); } return msw << 16 | lsw; } FuSynapticsRmiFunction * fu_synaptics_rmi_function_parse (GByteArray *buf, guint16 page_base, guint interrupt_count, GError **error) { FuSynapticsRmiFunction *func; guint8 interrupt_offset; const guint8 *data = buf->data; /* not expected */ if (buf->len != RMI_DEVICE_PDT_ENTRY_SIZE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "PDT entry buffer invalid size %u != %i", buf->len, RMI_DEVICE_PDT_ENTRY_SIZE); return NULL; } func = g_new0 (FuSynapticsRmiFunction, 1); func->query_base = data[RMI_FUNCTION_QUERY_OFFSET] + page_base; func->command_base = data[RMI_FUNCTION_COMMAND_OFFSET] + page_base; func->control_base = data[RMI_FUNCTION_CONTROL_OFFSET] + page_base; func->data_base = data[RMI_FUNCTION_DATA_OFFSET] + page_base; func->interrupt_source_count = data[RMI_FUNCTION_INTERRUPT_SOURCES_OFFSET] & RMI_FUNCTION_INTERRUPT_SOURCES_MASK; func->function_number = data[RMI_FUNCTION_NUMBER]; func->function_version = (data[RMI_FUNCTION_INTERRUPT_SOURCES_OFFSET] & RMI_FUNCTION_VERSION_MASK) >> 5; if (func->interrupt_source_count > 0) { func->interrupt_reg_num = (interrupt_count + 8) / 8 - 1; /* set an enable bit for each data source */ interrupt_offset = interrupt_count % 8; func->interrupt_mask = 0; for (guint8 i = interrupt_offset; i < (func->interrupt_source_count + interrupt_offset); i++) func->interrupt_mask |= 1 << i; } return func; } gboolean fu_synaptics_rmi_device_writeln (const gchar *fn, const gchar *buf, GError **error) { int fd; g_autoptr(FuIOChannel) io = NULL; fd = open (fn, O_WRONLY); if (fd < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "could not open %s", fn); return FALSE; } io = fu_io_channel_unix_new (fd); return fu_io_channel_write_raw (io, (const guint8 *) buf, strlen (buf), 1000, FU_IO_CHANNEL_FLAG_NONE, error); } fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-common.h000066400000000000000000000016661362775233600240340ustar00rootroot00000000000000/* * Copyright (C) 2012-2014 Andrew Duggan * Copyright (C) 2012-2019 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define RMI_PRODUCT_ID_LENGTH 10 #define RMI_DEVICE_PDT_ENTRY_SIZE 6 typedef struct { guint16 query_base; guint16 command_base; guint16 control_base; guint16 data_base; guint8 interrupt_source_count; guint8 function_number; guint8 function_version; guint8 interrupt_reg_num; guint8 interrupt_mask; } FuSynapticsRmiFunction; guint32 fu_synaptics_rmi_generate_checksum (const guint8 *data, gsize len); FuSynapticsRmiFunction *fu_synaptics_rmi_function_parse (GByteArray *buf, guint16 page_base, guint interrupt_count, GError **error); gboolean fu_synaptics_rmi_device_writeln (const gchar *fn, const gchar *buf, GError **error); fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-device.c000066400000000000000000001005341362775233600237700ustar00rootroot00000000000000/* * Copyright (C) 2012-2014 Andrew Duggan * Copyright (C) 2012-2019 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-io-channel.h" #include "fu-synaptics-rmi-common.h" #include "fu-synaptics-rmi-firmware.h" #include "fu-synaptics-rmi-v5-device.h" #include "fu-synaptics-rmi-v6-device.h" #include "fu-synaptics-rmi-v7-device.h" #define RMI_WRITE_REPORT_ID 0x9 /* output report */ #define RMI_READ_ADDR_REPORT_ID 0xa /* output report */ #define RMI_READ_DATA_REPORT_ID 0xb /* input report */ #define RMI_ATTN_REPORT_ID 0xc /* input report */ #define RMI_SET_RMI_MODE_REPORT_ID 0xf /* feature report */ #define RMI_DEVICE_DEFAULT_TIMEOUT 2000 #define HID_RMI4_REPORT_ID 0 #define HID_RMI4_READ_INPUT_COUNT 1 #define HID_RMI4_READ_INPUT_DATA 2 #define HID_RMI4_READ_OUTPUT_ADDR 2 #define HID_RMI4_READ_OUTPUT_COUNT 4 #define HID_RMI4_WRITE_OUTPUT_COUNT 1 #define HID_RMI4_WRITE_OUTPUT_ADDR 2 #define HID_RMI4_WRITE_OUTPUT_DATA 4 #define HID_RMI4_FEATURE_MODE 1 #define HID_RMI4_ATTN_INTERUPT_SOURCES 1 #define HID_RMI4_ATTN_DATA 2 #define RMI_DEVICE_PAGE_SELECT_REGISTER 0xff #define RMI_DEVICE_MAX_PAGE 0xff #define RMI_DEVICE_PAGE_SIZE 0x100 #define RMI_DEVICE_PAGE_SCAN_START 0x00e9 #define RMI_DEVICE_PAGE_SCAN_END 0x0005 #define RMI_DEVICE_F01_BASIC_QUERY_LEN 11 #define RMI_DEVICE_F01_LTS_RESERVED_SIZE 19 #define RMI_DEVICE_F01_QRY1_HAS_LTS (1 << 2) #define RMI_DEVICE_F01_QRY1_HAS_SENSOR_ID (1 << 3) #define RMI_DEVICE_F01_QRY1_HAS_PROPS_2 (1 << 7) #define RMI_DEVICE_F01_QRY42_DS4_QUERIES (1 << 0) #define RMI_DEVICE_F01_QRY43_01_PACKAGE_ID (1 << 0) #define RMI_DEVICE_F01_QRY43_01_BUILD_ID (1 << 1) #define RMI_F34_COMMAND_MASK 0x0f #define RMI_F34_STATUS_MASK 0x07 #define RMI_F34_STATUS_SHIFT 4 #define RMI_F34_ENABLED_MASK 0x80 #define RMI_F34_COMMAND_V1_MASK 0x3f #define RMI_F34_STATUS_V1_MASK 0x3f #define RMI_F34_ENABLED_V1_MASK 0x80 #define RMI_F01_CMD_DEVICE_RESET 1 #define RMI_F01_DEFAULT_RESET_DELAY_MS 100 /* * msleep mode controls power management on the device and affects all * functions of the device. */ #define RMI_F01_CTRL0_SLEEP_MODE_MASK 0x03 #define RMI_SLEEP_MODE_NORMAL 0x00 #define RMI_SLEEP_MODE_SENSOR_SLEEP 0x01 /* * This bit disables whatever sleep mode may be selected by the sleep_mode * field and forces the device to run at full power without sleeping. */ #define RMI_F01_CRTL0_NOSLEEP_BIT (1 << 2) typedef struct { FuSynapticsRmiFlash flash; GPtrArray *functions; FuIOChannel *io_channel; FuSynapticsRmiFunction *f01; FuSynapticsRmiFunction *f34; } FuSynapticsRmiDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuSynapticsRmiDevice, fu_synaptics_rmi_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_synaptics_rmi_device_get_instance_private (o)) FuSynapticsRmiFlash * fu_synaptics_rmi_device_get_flash (FuSynapticsRmiDevice *self) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); return &priv->flash; } static void fu_synaptics_rmi_flash_to_string (FuSynapticsRmiFlash *flash, guint idt, GString *str) { if (flash->bootloader_id[0] != 0x0) { g_autofree gchar *tmp = g_strdup_printf ("%02x.%02x", flash->bootloader_id[0], flash->bootloader_id[1]); fu_common_string_append_kv (str, idt, "BootloaderId", tmp); } fu_common_string_append_kx (str, idt, "BlockSize", flash->block_size); fu_common_string_append_kx (str, idt, "BlockCountFw", flash->block_count_fw); fu_common_string_append_kx (str, idt, "BlockCountCfg", flash->block_count_cfg); fu_common_string_append_kx (str, idt, "FlashConfigLength", flash->config_length); fu_common_string_append_kx (str, idt, "PayloadLength", flash->payload_length); fu_common_string_append_kx (str, idt, "BuildID", flash->build_id); } static void fu_synaptics_rmi_device_to_string (FuDevice *device, guint idt, GString *str) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); fu_common_string_append_kx (str, idt, "BlVer", priv->f34->function_version + 0x5); fu_synaptics_rmi_flash_to_string (&priv->flash, idt, str); } FuSynapticsRmiFunction * fu_synaptics_rmi_device_get_function (FuSynapticsRmiDevice *self, guint8 function_number, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); if (priv->functions->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no RMI functions, perhaps read the PDT?"); return NULL; } for (guint i = 0; i < priv->functions->len; i++) { FuSynapticsRmiFunction *func = g_ptr_array_index (priv->functions, i); if (func->function_number == function_number) return func; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get RMI function 0x%02x", function_number); return NULL; } GByteArray * fu_synaptics_rmi_device_read (FuSynapticsRmiDevice *self, guint16 addr, gsize req_sz, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GByteArray) buf = g_byte_array_new (); g_autoptr(GByteArray) req = g_byte_array_new (); /* maximum size */ if (req_sz > 0xffff) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "data to read was too long"); return NULL; } /* report then old 1 byte read count */ fu_byte_array_append_uint8 (req, RMI_READ_ADDR_REPORT_ID); fu_byte_array_append_uint8 (req, 0x0); /* address */ fu_byte_array_append_uint16 (req, addr, G_LITTLE_ENDIAN); /* read output count */ fu_byte_array_append_uint16 (req, req_sz, G_LITTLE_ENDIAN); /* request */ for (guint j = req->len; j < 21; j++) fu_byte_array_append_uint8 (req, 0x0); if (g_getenv ("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "ReportWrite", req->data, req->len, 80, FU_DUMP_FLAGS_NONE); } if (!fu_io_channel_write_byte_array (priv->io_channel, req, RMI_DEVICE_DEFAULT_TIMEOUT, FU_IO_CHANNEL_FLAG_SINGLE_SHOT | FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO, error)) return NULL; /* keep reading responses until we get enough data */ while (buf->len < req_sz) { guint8 input_count_sz = 0; g_autoptr(GByteArray) res = NULL; res = fu_io_channel_read_byte_array (priv->io_channel, req_sz, RMI_DEVICE_DEFAULT_TIMEOUT, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error); if (res == NULL) return NULL; if (res->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "response zero sized"); return NULL; } if (g_getenv ("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "ReportRead", res->data, res->len, 80, FU_DUMP_FLAGS_NONE); } /* ignore non data report events */ if (res->data[HID_RMI4_REPORT_ID] != RMI_READ_DATA_REPORT_ID) { g_debug ("ignoring report with ID 0x%02x", res->data[HID_RMI4_REPORT_ID]); continue; } if (res->len < HID_RMI4_READ_INPUT_DATA) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "response too small: 0x%02x", res->len); return NULL; } input_count_sz = res->data[HID_RMI4_READ_INPUT_COUNT]; if (input_count_sz == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "input count zero"); return NULL; } if (input_count_sz + (guint) HID_RMI4_READ_INPUT_DATA > res->len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "underflow 0x%02x from expected 0x%02x", res->len, (guint) input_count_sz + HID_RMI4_READ_INPUT_DATA); return NULL; } g_byte_array_append (buf, res->data + HID_RMI4_READ_INPUT_DATA, input_count_sz); } if (g_getenv ("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "DeviceRead", buf->data, buf->len, 80, FU_DUMP_FLAGS_NONE); } return g_steal_pointer (&buf); } gboolean fu_synaptics_rmi_device_write (FuSynapticsRmiDevice *self, guint16 addr, GByteArray *req, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); guint8 len = 0x0; g_autoptr(GByteArray) buf = g_byte_array_new (); /* check size */ if (req != NULL) { if (req->len > 0xff) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "data to write was too long"); return FALSE; } len = req->len; } /* report */ fu_byte_array_append_uint8 (buf, RMI_WRITE_REPORT_ID); /* length */ fu_byte_array_append_uint8 (buf, len); /* address */ fu_byte_array_append_uint16 (buf, addr, G_LITTLE_ENDIAN); /* optional data */ if (req != NULL) g_byte_array_append (buf, req->data, req->len); /* pad out to 21 bytes for some reason */ for (guint i = buf->len; i < 21; i++) fu_byte_array_append_uint8 (buf, 0x0); if (g_getenv ("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "DeviceWrite", buf->data, buf->len, 80, FU_DUMP_FLAGS_NONE); } return fu_io_channel_write_byte_array (priv->io_channel, buf, RMI_DEVICE_DEFAULT_TIMEOUT, FU_IO_CHANNEL_FLAG_SINGLE_SHOT | FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO, error); } static gboolean fu_synaptics_rmi_device_set_rma_page (FuSynapticsRmiDevice *self, guint8 page, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new (); fu_byte_array_append_uint8 (req, page); if (!fu_synaptics_rmi_device_write (self, RMI_DEVICE_PAGE_SELECT_REGISTER, req, error)) { g_prefix_error (error, "failed to set RMA page 0x%x", page); return FALSE; } return TRUE; } gboolean fu_synaptics_rmi_device_reset (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GByteArray) req = g_byte_array_new (); fu_byte_array_append_uint8 (req, RMI_F01_CMD_DEVICE_RESET); if (!fu_synaptics_rmi_device_write (self, priv->f01->command_base, req, error)) return FALSE; g_usleep (1000 * RMI_F01_DEFAULT_RESET_DELAY_MS); return TRUE; } static gboolean fu_synaptics_rmi_device_scan_pdt (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); guint interrupt_count = 0; /* clear old list */ g_ptr_array_set_size (priv->functions, 0); /* scan pages */ for (guint page = 0; page < RMI_DEVICE_MAX_PAGE; page++) { gboolean found = FALSE; guint32 page_start = RMI_DEVICE_PAGE_SIZE * page; guint32 pdt_start = page_start + RMI_DEVICE_PAGE_SCAN_START; guint32 pdt_end = page_start + RMI_DEVICE_PAGE_SCAN_END; /* set page */ if (!fu_synaptics_rmi_device_set_rma_page (self, page, error)) return FALSE; /* read out functions */ for (guint addr = pdt_start; addr >= pdt_end; addr -= RMI_DEVICE_PDT_ENTRY_SIZE) { g_autofree FuSynapticsRmiFunction *func = NULL; g_autoptr(GByteArray) res = NULL; res = fu_synaptics_rmi_device_read (self, addr, RMI_DEVICE_PDT_ENTRY_SIZE, error); if (res == NULL) { g_prefix_error (error, "failed to read PDT entry @ 0x%04x: ", addr); return FALSE; } func = fu_synaptics_rmi_function_parse (res, page_start, interrupt_count, error); if (func == NULL) return FALSE; if (func->function_number == 0) break; interrupt_count += func->interrupt_source_count; g_ptr_array_add (priv->functions, g_steal_pointer (&func)); found = TRUE; } if (!found) break; } /* for debug */ if (g_getenv ("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { for (guint i = 0; i < priv->functions->len; i++) { FuSynapticsRmiFunction *func = g_ptr_array_index (priv->functions, i); g_debug ("PDT-%02u fn:0x%02x vr:%d sc:%d ms:0x%x " "db:0x%02x cb:0x%02x cm:0x%02x qb:0x%02x", i, func->function_number, func->function_version, func->interrupt_source_count, func->interrupt_mask, func->data_base, func->control_base, func->command_base, func->query_base); } } /* success */ return TRUE; } typedef enum { HID_RMI4_MODE_MOUSE = 0, HID_RMI4_MODE_ATTN_REPORTS = 1, HID_RMI4_MODE_NO_PACKED_ATTN_REPORTS = 2, } FuSynapticsRmiHidMode; static gboolean fu_synaptics_rmi_device_set_mode (FuSynapticsRmiDevice *self, FuSynapticsRmiHidMode mode, GError **error) { const guint8 data[] = { 0x0f, mode }; if (g_getenv ("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "SetMode", data, sizeof(data)); return fu_udev_device_ioctl (FU_UDEV_DEVICE (self), HIDIOCSFEATURE(sizeof(data)), (guint8 *) data, NULL, error); } static void fu_synaptics_rmi_device_set_product_id (FuSynapticsRmiDevice *self, const gchar *product_id) { g_autofree gchar *instance_id = NULL; g_auto(GStrv) product_id_split = g_strsplit (product_id, "-", 2); /* use the product ID as an instance ID */ instance_id = g_strdup_printf ("SYNAPTICS_RMI\\%s", product_id); fu_device_add_instance_id (FU_DEVICE (self), instance_id); /* also add the product ID without the sub-number */ if (g_strv_length (product_id_split) == 2) { g_autofree gchar *instance_id_major = NULL; instance_id_major = g_strdup_printf ("SYNAPTICS_RMI\\%s", product_id_split[0]); fu_device_add_instance_id (FU_DEVICE (self), instance_id_major); } } static gboolean fu_synaptics_rmi_device_setup (FuDevice *device, GError **error) { FuDeviceClass *klass_device = FU_DEVICE_GET_CLASS (device); FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS (device); FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); guint16 addr; guint16 prod_info_addr; guint8 ds4_query_length = 0; gboolean has_build_id_query = FALSE; gboolean has_dds4_queries = FALSE; gboolean has_lts; gboolean has_package_id_query = FALSE; gboolean has_query42; gboolean has_sensor_id; g_autofree gchar *bl_ver = NULL; g_autofree gchar *fw_ver = NULL; g_autofree gchar *product_id = NULL; g_autoptr(GByteArray) f01_basic = NULL; g_autoptr(GByteArray) f01_product_id = NULL; g_autoptr(GByteArray) f01_ds4 = NULL; /* read PDT */ if (!fu_synaptics_rmi_device_scan_pdt (self, error)) return FALSE; priv->f01 = fu_synaptics_rmi_device_get_function (self, 0x01, error); if (priv->f01 == NULL) return FALSE; addr = priv->f01->query_base; f01_basic = fu_synaptics_rmi_device_read (self, addr, RMI_DEVICE_F01_BASIC_QUERY_LEN, error); if (f01_basic == NULL) { g_prefix_error (error, "failed to read the basic query: "); return FALSE; } has_lts = (f01_basic->data[1] & RMI_DEVICE_F01_QRY1_HAS_LTS) > 0; has_sensor_id = (f01_basic->data[1] & RMI_DEVICE_F01_QRY1_HAS_SENSOR_ID) > 0; has_query42 = (f01_basic->data[1] & RMI_DEVICE_F01_QRY1_HAS_PROPS_2) > 0; /* get the product ID */ addr += 11; f01_product_id = fu_synaptics_rmi_device_read (self, addr, RMI_PRODUCT_ID_LENGTH, error); if (f01_product_id == NULL) { g_prefix_error (error, "failed to read the product id: "); return FALSE; } product_id = g_strndup ((const gchar *) f01_product_id->data, f01_product_id->len); if (product_id != NULL) fu_synaptics_rmi_device_set_product_id (self, product_id); /* skip */ prod_info_addr = addr + 6; addr += 10; if (has_lts) addr++; if (has_sensor_id) addr++; if (has_lts) addr += RMI_DEVICE_F01_LTS_RESERVED_SIZE; /* read package ids */ if (has_query42) { g_autoptr(GByteArray) f01_tmp = NULL; f01_tmp = fu_synaptics_rmi_device_read (self, addr++, 1, error); if (f01_tmp == NULL) { g_prefix_error (error, "failed to read query 42: "); return FALSE; } has_dds4_queries = (f01_tmp->data[0] & RMI_DEVICE_F01_QRY42_DS4_QUERIES) > 0; } if (has_dds4_queries) { g_autoptr(GByteArray) f01_tmp = NULL; f01_tmp = fu_synaptics_rmi_device_read (self, addr++, 1, error); if (f01_tmp == NULL) { g_prefix_error (error, "failed to read DS4 query length: "); return FALSE; } ds4_query_length = f01_tmp->data[0]; } f01_ds4 = fu_synaptics_rmi_device_read (self, addr, 0x1, error); if (f01_ds4 == NULL) { g_prefix_error (error, "failed to read F01 Query43: "); return FALSE; } has_package_id_query = (f01_ds4->data[0] & RMI_DEVICE_F01_QRY43_01_PACKAGE_ID) > 0; has_build_id_query = (f01_ds4->data[0] & RMI_DEVICE_F01_QRY43_01_BUILD_ID) > 0; addr += ds4_query_length; if (has_package_id_query) prod_info_addr++; if (has_build_id_query) { g_autoptr(GByteArray) f01_tmp = NULL; guint8 buf32[4] = { 0x0 }; f01_tmp = fu_synaptics_rmi_device_read (self, prod_info_addr, 0x3, error); if (f01_tmp == NULL) { g_prefix_error (error, "failed to read build ID bytes: "); return FALSE; } if (!fu_memcpy_safe (buf32, sizeof(buf32), 0x0, /* dst */ f01_tmp->data, f01_tmp->len, 0x0, /* src */ f01_tmp->len, error)) return FALSE; priv->flash.build_id = fu_common_read_uint32 (buf32, G_LITTLE_ENDIAN); } priv->f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (priv->f34 == NULL) return FALSE; /* set up vfuncs for each bootloader protocol version */ if (priv->f34->function_version == 0x0) { klass_rmi->setup = fu_synaptics_rmi_v5_device_setup; klass_rmi->query_status = fu_synaptics_rmi_v5_device_query_status; klass_device->detach = fu_synaptics_rmi_v5_device_detach; klass_device->write_firmware = fu_synaptics_rmi_v5_device_write_firmware; } else if (priv->f34->function_version == 0x1) { klass_rmi->setup = fu_synaptics_rmi_v6_device_setup; klass_rmi->query_status = fu_synaptics_rmi_v5_device_query_status; klass_device->detach = fu_synaptics_rmi_v5_device_detach; klass_device->write_firmware = fu_synaptics_rmi_v5_device_write_firmware; } else if (priv->f34->function_version == 0x2) { klass_rmi->setup = fu_synaptics_rmi_v7_device_setup; klass_rmi->query_status = fu_synaptics_rmi_v7_device_query_status; klass_device->detach = fu_synaptics_rmi_v7_device_detach; klass_device->write_firmware = fu_synaptics_rmi_v7_device_write_firmware; } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "f34 function version 0x%02x unsupported", priv->f34->function_version); return FALSE; } /* get Function34_Query0,1 */ if (!klass_rmi->setup (self, error)) { g_prefix_error (error, "failed to read f34 queries: "); return FALSE; } if (!klass_rmi->query_status (self, error)) { g_prefix_error (error, "failed to read bootloader status: "); return FALSE; } /* set versions */ fw_ver = g_strdup_printf ("%u.%u.%u", f01_basic->data[2], f01_basic->data[3], priv->flash.build_id); fu_device_set_version (device, fw_ver, FWUPD_VERSION_FORMAT_TRIPLET); bl_ver = g_strdup_printf ("%u.0", priv->flash.bootloader_id[1]); fu_device_set_version_bootloader (device, bl_ver); /* success */ return TRUE; } static gboolean fu_synaptics_rmi_device_open (FuUdevDevice *device, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); /* set up touchpad so we can query it */ priv->io_channel = fu_io_channel_unix_new (fu_udev_device_get_fd (device)); if (!fu_synaptics_rmi_device_set_mode (self, HID_RMI4_MODE_ATTN_REPORTS, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_synaptics_rmi_device_close (FuUdevDevice *device, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GError) error_local = NULL; /* turn it back to mouse mode */ if (!fu_synaptics_rmi_device_set_mode (self, HID_RMI4_MODE_MOUSE, &error_local)) { /* if just detached for replug, swallow error */ if (!g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED)) { g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } g_debug ("ignoring: %s", error_local->message); } fu_udev_device_set_fd (device, -1); g_clear_object (&priv->io_channel); return TRUE; } static gboolean fu_synaptics_rmi_device_probe (FuUdevDevice *device, GError **error) { return fu_udev_device_set_physical_id (device, "hid", error); } static FuFirmware * fu_synaptics_rmi_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(FuFirmware) firmware = fu_synaptics_rmi_firmware_new (); g_autoptr(GBytes) bytes_cfg = NULL; g_autoptr(GBytes) bytes_bin = NULL; gsize size_expected; if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; /* check sizes */ bytes_bin = fu_firmware_get_image_by_id_bytes (firmware, "ui", error); if (bytes_bin == NULL) return NULL; size_expected = (gsize) priv->flash.block_count_fw * (gsize) priv->flash.block_size; if (g_bytes_get_size (bytes_bin) != size_expected) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "file firmware invalid size 0x%04x, expected 0x%04x", (guint) g_bytes_get_size (bytes_bin), (guint) size_expected); return NULL; } bytes_cfg = fu_firmware_get_image_by_id_bytes (firmware, "config", error); if (bytes_cfg == NULL) return NULL; size_expected = (gsize) priv->flash.block_count_cfg * (gsize) priv->flash.block_size; if (g_bytes_get_size (bytes_cfg) != size_expected) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "file config invalid size 0x%04x, expected 0x%04x", (guint) g_bytes_get_size (bytes_cfg), (guint) size_expected); return NULL; } return g_steal_pointer (&firmware); } static gboolean fu_synaptics_rmi_device_poll (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GByteArray) f34_db = NULL; /* get if the last flash read completed successfully */ f34_db = fu_synaptics_rmi_device_read (self, priv->f34->data_base, 0x1, error); if (f34_db == NULL) { g_prefix_error (error, "failed to read f34_db: "); return FALSE; } if ((f34_db->data[0] & 0x1f) != 0x0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "flash status invalid: 0x%x", (guint) (f34_db->data[0] & 0x1f)); return FALSE; } /* success */ return TRUE; } gboolean fu_synaptics_rmi_device_poll_wait (FuSynapticsRmiDevice *self, GError **error) { g_autoptr(GError) error_local = NULL; /* try to poll every 20ms for up to 400ms */ for (guint i = 0; i < 20; i++) { g_usleep (1000 * 20); g_clear_error (&error_local); if (fu_synaptics_rmi_device_poll (self, &error_local)) return TRUE; g_debug ("failed: %s", error_local->message); } /* proxy the last error */ g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } static gboolean fu_synaptics_rmi_device_wait_for_attr (FuSynapticsRmiDevice *self, guint8 source_mask, guint timeout_ms, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GTimer) timer = g_timer_new (); /* wait for event from hardware */ while (g_timer_elapsed (timer, NULL) * 1000.f < timeout_ms) { g_autoptr(GByteArray) res = NULL; g_autoptr(GError) error_local = NULL; /* read from fd */ res = fu_io_channel_read_byte_array (priv->io_channel, HID_RMI4_ATTN_INTERUPT_SOURCES + 1, timeout_ms, FU_IO_CHANNEL_FLAG_NONE, &error_local); if (res == NULL) { if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) break; g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } if (g_getenv ("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "ReportRead", res->data, res->len, 80, FU_DUMP_FLAGS_NONE); } if (res->len < HID_RMI4_ATTN_INTERUPT_SOURCES + 1) { g_debug ("attr: ignoring small read of %u", res->len); continue; } if (res->data[HID_RMI4_REPORT_ID] != RMI_ATTN_REPORT_ID) { g_debug ("attr: ignoring invalid report ID 0x%x", res->data[HID_RMI4_REPORT_ID]); continue; } /* success */ if (source_mask & res->data[HID_RMI4_ATTN_INTERUPT_SOURCES]) return TRUE; /* wrong mask */ g_debug ("source mask did not match: 0x%x", res->data[HID_RMI4_ATTN_INTERUPT_SOURCES]); } /* urgh */ g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no attr report, timed out"); return FALSE; } gboolean fu_synaptics_rmi_device_wait_for_idle (FuSynapticsRmiDevice *self, guint timeout_ms, RmiDeviceWaitForIdleFlags flags, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); guint8 f34_command; guint8 f34_enabled; guint8 f34_status; g_autoptr(GByteArray) res = NULL; g_autoptr(GError) error_local = NULL; /* try to get report without requesting */ if (timeout_ms > 0 && !fu_synaptics_rmi_device_wait_for_attr (self, priv->f34->interrupt_mask, timeout_ms, &error_local)) { if (!g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to wait for attr: "); return FALSE; } } else if ((flags & RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34) == 0) { /* device reported idle via an event */ return TRUE; } /* if for some reason we are not getting attention reports for HID devices * then we can still continue after the timeout and read F34 status * but if we have to wait for the timeout to ellapse every time then this * will be slow */ if (priv->f34->function_version == 0x1) { res = fu_synaptics_rmi_device_read (self, priv->flash.status_addr, 0x2, error); if (res == NULL) return FALSE; f34_command = res->data[0] & RMI_F34_COMMAND_V1_MASK; f34_status = res->data[1] & RMI_F34_STATUS_V1_MASK; f34_enabled = !!(res->data[1] & RMI_F34_ENABLED_MASK); } else { res = fu_synaptics_rmi_device_read (self, priv->flash.status_addr, 0x1, error); if (res == NULL) return FALSE; f34_command = res->data[0] & RMI_F34_COMMAND_MASK; f34_status = (res->data[0] >> RMI_F34_STATUS_SHIFT) & RMI_F34_STATUS_MASK; f34_enabled = !!(res->data[0] & RMI_F34_ENABLED_MASK); } /* is idle */ if (f34_status == 0x0 && f34_command == 0x0) { if (f34_enabled == 0x0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "idle but enabled unset"); return FALSE; } return TRUE; } /* failed */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "timed out waiting for idle [cmd:0x%x, sta:0x%x, ena:0x%x]", f34_command, f34_status, f34_enabled); return FALSE; } gboolean fu_synaptics_rmi_device_disable_sleep (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GByteArray) f01_control0 = NULL; f01_control0 = fu_synaptics_rmi_device_read (self, priv->f01->control_base, 0x1, error); if (f01_control0 == NULL) { g_prefix_error (error, "failed to write get f01_control0: "); return FALSE; } f01_control0->data[0] |= RMI_F01_CRTL0_NOSLEEP_BIT; f01_control0->data[0] = (f01_control0->data[0] & ~RMI_F01_CTRL0_SLEEP_MODE_MASK) | RMI_SLEEP_MODE_NORMAL; if (!fu_synaptics_rmi_device_write (self, priv->f01->control_base, f01_control0, error)) { g_prefix_error (error, "failed to write f01_control0: "); return FALSE; } /* success */ return TRUE; } gboolean fu_synaptics_rmi_device_rebind_driver (FuSynapticsRmiDevice *self, GError **error) { GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (self)); const gchar *hid_id; const gchar *driver; const gchar *subsystem; g_autofree gchar *fn_rebind = NULL; g_autofree gchar *fn_unbind = NULL; g_autoptr(GUdevDevice) parent_hid = NULL; g_autoptr(GUdevDevice) parent_i2c = NULL; /* get actual HID node */ parent_hid = g_udev_device_get_parent_with_subsystem (udev_device, "hid", NULL); if (parent_hid == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no HID parent device for %s", g_udev_device_get_sysfs_path (udev_device)); return FALSE; } /* find the physical ID to use for the rebind */ hid_id = g_udev_device_get_property (parent_hid, "HID_PHYS"); if (hid_id == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no HID_PHYS in %s", g_udev_device_get_sysfs_path (parent_hid)); return FALSE; } g_debug ("HID_PHYS: %s", hid_id); /* build paths */ parent_i2c = g_udev_device_get_parent_with_subsystem (udev_device, "i2c", NULL); if (parent_i2c == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no I2C parent device for %s", g_udev_device_get_sysfs_path (udev_device)); return FALSE; } driver = g_udev_device_get_driver (parent_i2c); subsystem = g_udev_device_get_subsystem (parent_i2c); fn_rebind = g_build_filename ("/sys/bus/", subsystem, "drivers", driver, "bind", NULL); fn_unbind = g_build_filename ("/sys/bus/", subsystem, "drivers", driver, "unbind", NULL); /* unbind hidraw, then bind it again to get a replug */ fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); if (!fu_synaptics_rmi_device_writeln (fn_unbind, hid_id, error)) return FALSE; if (!fu_synaptics_rmi_device_writeln (fn_rebind, hid_id, error)) return FALSE; /* success */ return TRUE; } gboolean fu_synaptics_rmi_device_write_bootloader_id (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); gint block_data_offset = RMI_F34_BLOCK_DATA_OFFSET; g_autoptr(GByteArray) bootloader_id_req = g_byte_array_new (); if (priv->f34->function_version == 0x1) block_data_offset = RMI_F34_BLOCK_DATA_V1_OFFSET; /* write bootloader_id into F34_Flash_Data0,1 */ g_byte_array_append (bootloader_id_req, priv->flash.bootloader_id, sizeof(priv->flash.bootloader_id)); if (!fu_synaptics_rmi_device_write (self, priv->f34->data_base + block_data_offset, bootloader_id_req, error)) { g_prefix_error (error, "failed to write bootloader_id: "); return FALSE; } /* success */ return TRUE; } gboolean fu_synaptics_rmi_device_disable_irqs (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); g_autoptr(GByteArray) interrupt_disable_req = g_byte_array_new (); fu_byte_array_append_uint8 (interrupt_disable_req, priv->f34->interrupt_mask | priv->f01->interrupt_mask); if (!fu_synaptics_rmi_device_write (self, priv->f01->control_base + 1, interrupt_disable_req, error)) { g_prefix_error (error, "failed to disable interrupts: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_rmi_device_attach (FuDevice *device, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device); /* reset device */ if (!fu_synaptics_rmi_device_reset (self, error)) return FALSE; /* rebind to rescan PDT with new firmware running */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); return fu_synaptics_rmi_device_rebind_driver (self, error); } static void fu_synaptics_rmi_device_init (FuSynapticsRmiDevice *self) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); fu_device_set_protocol (FU_DEVICE (self), "com.synaptics.rmi"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_name (FU_DEVICE (self), "Touchpad"); fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); priv->functions = g_ptr_array_new_with_free_func (g_free); } static void fu_synaptics_rmi_device_finalize (GObject *object) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (object); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE (self); g_ptr_array_unref (priv->functions); G_OBJECT_CLASS (fu_synaptics_rmi_device_parent_class)->finalize (object); } static void fu_synaptics_rmi_device_class_init (FuSynapticsRmiDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUdevDeviceClass *klass_device_udev = FU_UDEV_DEVICE_CLASS (klass); object_class->finalize = fu_synaptics_rmi_device_finalize; klass_device->to_string = fu_synaptics_rmi_device_to_string; klass_device->prepare_firmware = fu_synaptics_rmi_device_prepare_firmware; klass_device->attach = fu_synaptics_rmi_device_attach; klass_device->setup = fu_synaptics_rmi_device_setup; klass_device_udev->probe = fu_synaptics_rmi_device_probe; klass_device_udev->open = fu_synaptics_rmi_device_open; klass_device_udev->close = fu_synaptics_rmi_device_close; } fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-device.h000066400000000000000000000050751362775233600240010ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-synaptics-rmi-common.h" #include "fu-udev-device.h" #define FU_TYPE_SYNAPTICS_RMI_DEVICE (fu_synaptics_rmi_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuSynapticsRmiDevice, fu_synaptics_rmi_device, FU, SYNAPTICS_RMI_DEVICE, FuUdevDevice) struct _FuSynapticsRmiDeviceClass { FuUdevDeviceClass parent_class; gboolean (*setup) (FuSynapticsRmiDevice *self, GError **error); gboolean (*query_status) (FuSynapticsRmiDevice *self, GError **error); }; typedef struct { guint16 block_count_cfg; guint16 block_count_fw; guint16 block_size; guint16 config_length; guint16 payload_length; guint32 build_id; guint8 bootloader_id[2]; guint8 status_addr; } FuSynapticsRmiFlash; #define RMI_F34_HAS_NEW_REG_MAP (1 << 0) #define RMI_F34_HAS_CONFIG_ID (1 << 2) #define RMI_F34_BLOCK_DATA_OFFSET 2 #define RMI_F34_BLOCK_DATA_V1_OFFSET 1 #define RMI_F34_ENABLE_WAIT_MS 300 /* ms */ #define RMI_F34_IDLE_WAIT_MS 500 /* ms */ typedef enum { RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE = 0, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34 = (1 << 0), } RmiDeviceWaitForIdleFlags; gboolean fu_synaptics_rmi_device_write_bootloader_id (FuSynapticsRmiDevice *self, GError **error); gboolean fu_synaptics_rmi_device_disable_irqs (FuSynapticsRmiDevice *self, GError **error); GByteArray *fu_synaptics_rmi_device_read (FuSynapticsRmiDevice *self, guint16 addr, gsize req_sz, GError **error); gboolean fu_synaptics_rmi_device_write (FuSynapticsRmiDevice *self, guint16 addr, GByteArray *req, GError **error); gboolean fu_synaptics_rmi_device_reset (FuSynapticsRmiDevice *self, GError **error); gboolean fu_synaptics_rmi_device_wait_for_idle (FuSynapticsRmiDevice *self, guint timeout_ms, RmiDeviceWaitForIdleFlags flags, GError **error); gboolean fu_synaptics_rmi_device_disable_sleep (FuSynapticsRmiDevice *self, GError **error); FuSynapticsRmiFlash *fu_synaptics_rmi_device_get_flash (FuSynapticsRmiDevice *self); FuSynapticsRmiFunction *fu_synaptics_rmi_device_get_function (FuSynapticsRmiDevice *self, guint8 function_number, GError **error); gboolean fu_synaptics_rmi_device_rebind_driver (FuSynapticsRmiDevice *self, GError **error); gboolean fu_synaptics_rmi_device_poll_wait (FuSynapticsRmiDevice *self, GError **error); fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-firmware.c000066400000000000000000000414121362775233600243440ustar00rootroot00000000000000/* * Copyright (C) 2012-2014 Andrew Duggan * Copyright (C) 2012-2019 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "fu-synaptics-rmi-common.h" #include "fu-synaptics-rmi-firmware.h" struct _FuSynapticsRmiFirmware { FuFirmware parent_instance; guint32 checksum; guint8 io; guint8 bootloader_version; guint32 build_id; guint16 package_id; guint16 product_info; gchar *product_id; }; G_DEFINE_TYPE (FuSynapticsRmiFirmware, fu_synaptics_rmi_firmware, FU_TYPE_FIRMWARE) #define RMI_IMG_CHECKSUM_OFFSET 0x00 #define RMI_IMG_IO_OFFSET 0x06 #define RMI_IMG_BOOTLOADER_VERSION_OFFSET 0x07 #define RMI_IMG_IMAGE_SIZE_OFFSET 0x08 #define RMI_IMG_CONFIG_SIZE_OFFSET 0x0c #define RMI_IMG_PACKAGE_ID_OFFSET 0x1a #define RMI_IMG_FW_BUILD_ID_OFFSET 0x50 #define RMI_IMG_PRODUCT_ID_OFFSET 0x10 #define RMI_IMG_PRODUCT_INFO_OFFSET 0x1e #define RMI_IMG_FW_OFFSET 0x100 #define RMI_IMG_V10_CNTR_ADDR_OFFSET 0x0c typedef struct __attribute__((packed)) { guint32 content_checksum; guint16 container_id; guint8 minor_version; guint8 major_version; guint8 reserved_08; guint8 reserved_09; guint8 reserved_0a; guint8 reserved_0b; guint32 container_option_flags; guint32 content_options_length; guint32 content_options_address; guint32 content_length; guint32 content_address; } RmiFirmwareContainerDescriptor; typedef enum { RMI_FIRMWARE_CONTAINER_ID_TOP_LEVEL = 0, RMI_FIRMWARE_CONTAINER_ID_UI, RMI_FIRMWARE_CONTAINER_ID_UI_CONFIG, RMI_FIRMWARE_CONTAINER_ID_BL, RMI_FIRMWARE_CONTAINER_ID_BL_IMAGE, RMI_FIRMWARE_CONTAINER_ID_BL_CONFIG, RMI_FIRMWARE_CONTAINER_ID_BL_LOCKDOWN_INFO, RMI_FIRMWARE_CONTAINER_ID_PERMANENT_CONFIG, RMI_FIRMWARE_CONTAINER_ID_GUEST_CODE, RMI_FIRMWARE_CONTAINER_ID_BL_PROTOCOL_DESCRIPTOR, RMI_FIRMWARE_CONTAINER_ID_UI_PROTOCOL_DESCRIPTOR, RMI_FIRMWARE_CONTAINER_ID_RMI_SELF_DISCOVERY, RMI_FIRMWARE_CONTAINER_ID_RMI_PAGE_CONTENT, RMI_FIRMWARE_CONTAINER_ID_GENERAL_INFORMATION, RMI_FIRMWARE_CONTAINER_ID_DEVICE_CONFIG, RMI_FIRMWARE_CONTAINER_ID_FLASH_CONFIG, RMI_FIRMWARE_CONTAINER_ID_GUEST_SERIALIZATION, RMI_FIRMWARE_CONTAINER_ID_GLOBAL_PARAMETERS, RMI_FIRMWARE_CONTAINER_ID_CORE_CODE, RMI_FIRMWARE_CONTAINER_ID_CORE_CONFIG, RMI_FIRMWARE_CONTAINER_ID_DISPLAY_CONFIG, RMI_FIRMWARE_CONTAINER_ID_EXTERNAL_TOUCH_AFE_CONFIG, RMI_FIRMWARE_CONTAINER_ID_UTILITY, RMI_FIRMWARE_CONTAINER_ID_UTILITY_PARAMETER, } RmiFirmwareContainerId; static const gchar * rmi_firmware_container_id_to_string (RmiFirmwareContainerId container_id) { if (container_id == RMI_FIRMWARE_CONTAINER_ID_TOP_LEVEL) return "top-level"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_UI) return "ui"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_UI_CONFIG) return "ui-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_BL) return "bl"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_BL_IMAGE) return "bl-image"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_BL_CONFIG) return "bl-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_BL_LOCKDOWN_INFO) return "bl-lockdown-info"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_PERMANENT_CONFIG) return "permanent-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_GUEST_CODE) return "guest-code"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_BL_PROTOCOL_DESCRIPTOR) return "bl-protocol-descriptor"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_UI_PROTOCOL_DESCRIPTOR) return "ui-protocol-descriptor"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_RMI_SELF_DISCOVERY) return "rmi-self-discovery"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_RMI_PAGE_CONTENT) return "rmi-page-content"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_GENERAL_INFORMATION) return "general-information"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_DEVICE_CONFIG) return "device-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_FLASH_CONFIG) return "flash-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_GUEST_SERIALIZATION) return "guest-serialization"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_GLOBAL_PARAMETERS) return "global-parameters"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_CORE_CODE) return "core-code"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_CORE_CONFIG) return "core-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_DISPLAY_CONFIG) return "display-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_EXTERNAL_TOUCH_AFE_CONFIG) return "external-touch-afe-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_UTILITY) return "utility"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_UTILITY_PARAMETER) return "utility-parameter"; return NULL; } static void fu_synaptics_rmi_firmware_add_image (FuFirmware *firmware, const gchar *id, const guint8 *data, gsize sz) { g_autoptr(GBytes) bytes = g_bytes_new (data, sz); g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (bytes); fu_firmware_image_set_id (img, id); fu_firmware_add_image (firmware, img); } static void fu_synaptics_rmi_firmware_to_string (FuFirmware *firmware, guint idt, GString *str) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE (firmware); fu_common_string_append_kv (str, idt, "ProductId", self->product_id); fu_common_string_append_kx (str, idt, "BootloaderVersion", self->bootloader_version); fu_common_string_append_kx (str, idt, "IO", self->io); fu_common_string_append_kx (str, idt, "Checksum", self->checksum); fu_common_string_append_kx (str, idt, "BuildId", self->build_id); fu_common_string_append_kx (str, idt, "PackageId", self->package_id); fu_common_string_append_kx (str, idt, "ProductInfo", self->product_info); } static gboolean fu_synaptics_rmi_firmware_parse_v10 (FuFirmware *firmware, GBytes *fw, GError **error) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE (firmware); RmiFirmwareContainerDescriptor desc = { 0x0 }; guint16 container_id; guint32 cntrs_len; guint32 offset; guint32 cntr_addr; gsize sz = 0; const guint8 *data = g_bytes_get_data (fw, &sz); cntr_addr = fu_common_read_uint32 (data + RMI_IMG_V10_CNTR_ADDR_OFFSET, G_LITTLE_ENDIAN); g_debug ("v10 RmiFirmwareContainerDescriptor at 0x%x", cntr_addr); if (!fu_memcpy_safe ((guint8 *) &desc, sizeof(desc), 0x0, /* dst */ data, sz, cntr_addr, /* src */ sizeof(desc), error)) { g_prefix_error (error, "RmiFirmwareContainerDescriptor invalid: "); return FALSE; } container_id = GUINT16_FROM_LE(desc.container_id); if (container_id != RMI_FIRMWARE_CONTAINER_ID_TOP_LEVEL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "toplevel container_id invalid, got 0x%x expected 0x%x", (guint) container_id, (guint) RMI_FIRMWARE_CONTAINER_ID_TOP_LEVEL); return FALSE; } offset = GUINT32_FROM_LE(desc.content_address); if (offset > sz - sizeof(guint32) - sizeof(desc)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "image offset invalid, got 0x%x, size 0x%x", (guint) offset, (guint) sz); return FALSE; } cntrs_len = GUINT32_FROM_LE(desc.content_length) / 4; g_debug ("offset=0x%x (cntrs_len=%u)", offset, cntrs_len); for (guint32 i = 0; i < cntrs_len; i++) { guint32 content_addr; guint32 addr = fu_common_read_uint32 (data + offset, G_LITTLE_ENDIAN); guint32 length; g_debug ("parsing RmiFirmwareContainerDescriptor at 0x%x", addr); if (!fu_memcpy_safe ((guint8 *) &desc, sizeof(desc), 0x0, /* dst */ data, sz, addr, /* src */ sizeof(desc), error)) return FALSE; container_id = GUINT16_FROM_LE(desc.container_id); content_addr = GUINT32_FROM_LE(desc.content_address); length = GUINT32_FROM_LE(desc.content_length); g_debug ("RmiFirmwareContainerDescriptor 0x%02x @ 0x%x (len 0x%x)", container_id, content_addr, length); if (length > sz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "length invalid, length 0x%x, size 0x%x", (guint) length, (guint) sz); return FALSE; } if (content_addr > sz - length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "address invalid, got 0x%x (length 0x%x), size 0x%x", (guint) content_addr, (guint) length, (guint) sz); return FALSE; } switch (container_id) { case RMI_FIRMWARE_CONTAINER_ID_BL: self->bootloader_version = data[content_addr]; break; case RMI_FIRMWARE_CONTAINER_ID_UI: case RMI_FIRMWARE_CONTAINER_ID_CORE_CODE: fu_synaptics_rmi_firmware_add_image (firmware, "ui", data + content_addr, length); break; case RMI_FIRMWARE_CONTAINER_ID_FLASH_CONFIG: fu_synaptics_rmi_firmware_add_image (firmware, "flash-config", data + content_addr, length); break; case RMI_FIRMWARE_CONTAINER_ID_UI_CONFIG: case RMI_FIRMWARE_CONTAINER_ID_CORE_CONFIG: fu_synaptics_rmi_firmware_add_image (firmware, "config", data + content_addr, length); break; case RMI_FIRMWARE_CONTAINER_ID_GENERAL_INFORMATION: if (length < 0x18 + RMI_PRODUCT_ID_LENGTH) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "content_addr invalid, got 0x%x (length 0x%x)", content_addr, (guint) length); return FALSE; } self->io = 1; self->package_id = fu_common_read_uint32 (data + content_addr, G_LITTLE_ENDIAN); self->build_id = fu_common_read_uint32 (data + content_addr + 4, G_LITTLE_ENDIAN); self->product_id = g_strndup ((const gchar *) data + content_addr + 0x18, RMI_PRODUCT_ID_LENGTH); break; default: g_debug ("unsupported container %s [0x%02x]", rmi_firmware_container_id_to_string (container_id), container_id); break; } offset += 4; } return TRUE; } static gboolean fu_synaptics_rmi_firmware_parse_v0x (FuFirmware *firmware, GBytes *fw, GError **error) { guint32 cfg_sz; guint32 img_sz; gsize sz = 0; const guint8 *data = g_bytes_get_data (fw, &sz); /* main firmware */ img_sz = fu_common_read_uint32 (data + RMI_IMG_IMAGE_SIZE_OFFSET, G_LITTLE_ENDIAN); if (img_sz > 0) { if (img_sz > sz - RMI_IMG_FW_OFFSET) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "img_sz offset invalid, got 0x%x, size 0x%x", (guint) img_sz, (guint) sz - RMI_IMG_FW_OFFSET); return FALSE; } fu_synaptics_rmi_firmware_add_image (firmware, "ui", data + RMI_IMG_FW_OFFSET, img_sz); } /* config */ cfg_sz = fu_common_read_uint32 (data + RMI_IMG_CONFIG_SIZE_OFFSET, G_LITTLE_ENDIAN); if (cfg_sz > 0) { if (cfg_sz > sz - RMI_IMG_FW_OFFSET) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cfg_sz offset invalid, got 0x%x, size 0x%x", (guint) cfg_sz, (guint) sz - RMI_IMG_FW_OFFSET); return FALSE; } fu_synaptics_rmi_firmware_add_image (firmware, "config", data + RMI_IMG_FW_OFFSET + img_sz, cfg_sz); } return TRUE; } static gboolean fu_synaptics_rmi_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE (firmware); gsize sz = 0; guint32 checksum_calculated; const guint8 *data = g_bytes_get_data (fw, &sz); /* check minimum size */ if (sz < RMI_IMG_FW_OFFSET) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "not enough data to parse header"); return FALSE; } if (sz % 2 != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "data not aligned to 16 bits"); return FALSE; } /* verify checksum */ self->checksum = fu_common_read_uint32 (data + RMI_IMG_CHECKSUM_OFFSET, G_LITTLE_ENDIAN); checksum_calculated = fu_synaptics_rmi_generate_checksum (data + 4, sz - 4); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { if (self->checksum != checksum_calculated) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum verification failed, got 0x%08x, actual 0x%08x", (guint) self->checksum, (guint) checksum_calculated); return FALSE; } } /* parse legacy image */ self->io = data[RMI_IMG_IO_OFFSET]; self->bootloader_version = data[RMI_IMG_BOOTLOADER_VERSION_OFFSET]; if (self->io == 1) { self->build_id = fu_common_read_uint32 (data + RMI_IMG_FW_BUILD_ID_OFFSET, G_LITTLE_ENDIAN); self->package_id = fu_common_read_uint32 (data + RMI_IMG_PACKAGE_ID_OFFSET, G_LITTLE_ENDIAN); } self->product_id = g_strndup ((const gchar *) data + RMI_IMG_PRODUCT_ID_OFFSET, RMI_PRODUCT_ID_LENGTH); self->product_info = fu_common_read_uint16 (data + RMI_IMG_PRODUCT_INFO_OFFSET, G_LITTLE_ENDIAN); /* parse partitions, but ignore lockdown */ switch (self->bootloader_version) { case 2: case 3: case 4: case 5: case 6: if (!fu_synaptics_rmi_firmware_parse_v0x (firmware, fw, error)) return FALSE; break; case 16: if (!fu_synaptics_rmi_firmware_parse_v10 (firmware, fw, error)) return FALSE; break; default: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unsupported image version 0x%02x", self->bootloader_version); return FALSE; } /* success */ return TRUE; } GBytes * fu_synaptics_rmi_firmware_generate_v0x (void) { GByteArray *buf = g_byte_array_new (); /* create empty block */ g_byte_array_set_size (buf, RMI_IMG_FW_OFFSET + 0x4 + 0x4); buf->data[RMI_IMG_IO_OFFSET] = 0x0; /* no build_id or package_id */ buf->data[RMI_IMG_BOOTLOADER_VERSION_OFFSET] = 0x2; /* not hierarchical */ memcpy (buf->data + RMI_IMG_PRODUCT_ID_OFFSET, "Example", 7); fu_common_write_uint16 (buf->data + RMI_IMG_PRODUCT_INFO_OFFSET, 0x1234, G_LITTLE_ENDIAN); fu_common_write_uint32 (buf->data + RMI_IMG_IMAGE_SIZE_OFFSET, 0x4, G_LITTLE_ENDIAN); fu_common_write_uint32 (buf->data + RMI_IMG_CONFIG_SIZE_OFFSET, 0x4, G_LITTLE_ENDIAN); fu_common_write_uint32 (buf->data + RMI_IMG_FW_OFFSET + 0x0, 0xdead, G_LITTLE_ENDIAN); /* img */ fu_common_write_uint32 (buf->data + RMI_IMG_FW_OFFSET + 0x4, 0xbeef, G_LITTLE_ENDIAN); /* config */ fu_common_dump_full (G_LOG_DOMAIN, "v0x", buf->data, buf->len, 0x20, FU_DUMP_FLAGS_SHOW_ADDRESSES); return g_byte_array_free_to_bytes (buf); } GBytes * fu_synaptics_rmi_firmware_generate_v10 (void) { GByteArray *buf = g_byte_array_new (); /* header | desc_hdr | offset_table | desc | flash_config | * \0x0 \0x20 \0x24 \0x44 |0x48 */ RmiFirmwareContainerDescriptor desc_hdr = { .container_id = GUINT16_TO_LE(RMI_FIRMWARE_CONTAINER_ID_TOP_LEVEL), .content_length = GUINT32_TO_LE(0x1 * 4), /* size of offset table in bytes */ .content_address = GUINT32_TO_LE(RMI_IMG_FW_OFFSET + 0x20), /* offset to table */ }; guint32 offset_table[] = { RMI_IMG_FW_OFFSET + 0x24 }; /* offset to first RmiFirmwareContainerDescriptor */ RmiFirmwareContainerDescriptor desc = { .container_id = GUINT16_TO_LE(RMI_FIRMWARE_CONTAINER_ID_FLASH_CONFIG), .content_length = GUINT32_TO_LE(0x4), .content_address = GUINT32_TO_LE(RMI_IMG_FW_OFFSET + 0x44), }; /* create empty block */ g_byte_array_set_size (buf, RMI_IMG_FW_OFFSET + 0x48); buf->data[RMI_IMG_IO_OFFSET] = 0x1; buf->data[RMI_IMG_BOOTLOADER_VERSION_OFFSET] = 16; /* hierarchical */ memcpy (buf->data + RMI_IMG_PRODUCT_ID_OFFSET, "Example", 7); fu_common_write_uint32 (buf->data + RMI_IMG_FW_BUILD_ID_OFFSET, 0x1234, G_LITTLE_ENDIAN); fu_common_write_uint32 (buf->data + RMI_IMG_PACKAGE_ID_OFFSET, 0x4321, G_LITTLE_ENDIAN); fu_common_write_uint16 (buf->data + RMI_IMG_PRODUCT_INFO_OFFSET, 0x3456, G_LITTLE_ENDIAN); fu_common_write_uint32 (buf->data + RMI_IMG_IMAGE_SIZE_OFFSET, 0x4, G_LITTLE_ENDIAN); fu_common_write_uint32 (buf->data + RMI_IMG_CONFIG_SIZE_OFFSET, 0x4, G_LITTLE_ENDIAN); fu_common_write_uint32 (buf->data + RMI_IMG_V10_CNTR_ADDR_OFFSET, RMI_IMG_FW_OFFSET, G_LITTLE_ENDIAN); /* hierarchical section */ memcpy (buf->data + RMI_IMG_FW_OFFSET + 0x00, &desc_hdr, sizeof(desc_hdr)); memcpy (buf->data + RMI_IMG_FW_OFFSET + 0x20, offset_table, sizeof(offset_table)); memcpy (buf->data + RMI_IMG_FW_OFFSET + 0x24, &desc, sizeof(desc)); fu_common_write_uint32 (buf->data + RMI_IMG_FW_OFFSET + 0x44, 0xfeed, G_LITTLE_ENDIAN); /* flash_config */ fu_common_dump_full (G_LOG_DOMAIN, "v10", buf->data, buf->len, 0x20, FU_DUMP_FLAGS_SHOW_ADDRESSES); return g_byte_array_free_to_bytes (buf); } static void fu_synaptics_rmi_firmware_init (FuSynapticsRmiFirmware *self) { } static void fu_synaptics_rmi_firmware_class_init (FuSynapticsRmiFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->parse = fu_synaptics_rmi_firmware_parse; klass_firmware->to_string = fu_synaptics_rmi_firmware_to_string; } FuFirmware * fu_synaptics_rmi_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_SYNAPTICS_RMI_FIRMWARE, NULL)); } fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-firmware.h000066400000000000000000000007731362775233600243560ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_SYNAPTICS_RMI_FIRMWARE (fu_synaptics_rmi_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuSynapticsRmiFirmware, fu_synaptics_rmi_firmware, FU, SYNAPTICS_RMI_FIRMWARE, FuFirmware) FuFirmware *fu_synaptics_rmi_firmware_new (void); GBytes *fu_synaptics_rmi_firmware_generate_v0x (void); GBytes *fu_synaptics_rmi_firmware_generate_v10 (void); fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-v5-device.c000066400000000000000000000220031362775233600243120ustar00rootroot00000000000000/* * Copyright (C) 2012-2014 Andrew Duggan * Copyright (C) 2012-2019 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-chunk.h" #include "fu-common.h" #include "fu-synaptics-rmi-v5-device.h" #include "fwupd-error.h" #define RMI_F34_WRITE_FW_BLOCK 0x02 #define RMI_F34_ERASE_ALL 0x03 #define RMI_F34_WRITE_LOCKDOWN_BLOCK 0x04 #define RMI_F34_WRITE_CONFIG_BLOCK 0x06 #define RMI_F34_ENABLE_FLASH_PROG 0x0f #define RMI_F34_BLOCK_SIZE_OFFSET 1 #define RMI_F34_FW_BLOCKS_OFFSET 3 #define RMI_F34_CONFIG_BLOCKS_OFFSET 5 #define RMI_F34_ERASE_WAIT_MS (5 * 1000) /* ms */ gboolean fu_synaptics_rmi_v5_device_detach (FuDevice *device, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device); FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); g_autoptr(GByteArray) enable_req = g_byte_array_new (); /* disable interrupts */ if (!fu_synaptics_rmi_device_disable_irqs (self, error)) return FALSE; /* unlock bootloader and rebind kernel driver */ if (!fu_synaptics_rmi_device_write_bootloader_id (self, error)) return FALSE; fu_byte_array_append_uint8 (enable_req, RMI_F34_ENABLE_FLASH_PROG); if (!fu_synaptics_rmi_device_write (self, flash->status_addr, enable_req, error)) { g_prefix_error (error, "failed to enable programming: "); return FALSE; } fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); g_usleep (1000 * RMI_F34_ENABLE_WAIT_MS); return fu_synaptics_rmi_device_rebind_driver (self, error); } static gboolean fu_synaptics_rmi_v5_device_erase_all (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFunction *f34; FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); g_autoptr(GByteArray) erase_cmd = g_byte_array_new (); /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; /* all other versions */ fu_byte_array_append_uint8 (erase_cmd, RMI_F34_ERASE_ALL); if (!fu_synaptics_rmi_device_write (self, flash->status_addr, erase_cmd, error)) { g_prefix_error (error, "failed to erase core config: "); return FALSE; } g_usleep (1000 * RMI_F34_ENABLE_WAIT_MS); if (!fu_synaptics_rmi_device_wait_for_idle (self, RMI_F34_ERASE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34, error)) { g_prefix_error (error, "failed to wait for idle for erase: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_rmi_v5_device_write_block (FuSynapticsRmiDevice *self, guint8 cmd, guint32 address, const guint8 *data, gsize datasz, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new (); g_byte_array_append (req, data, datasz); fu_byte_array_append_uint8 (req, cmd); if (!fu_synaptics_rmi_device_write (self, address, req, error)) { g_prefix_error (error, "failed to write block @0x%x: ", address); return FALSE; } if (!fu_synaptics_rmi_device_wait_for_idle (self, RMI_F34_IDLE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE, error)) { g_prefix_error (error, "failed to wait for idle @0x%x: ", address); return FALSE; } return TRUE; } gboolean fu_synaptics_rmi_v5_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device); FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); FuSynapticsRmiFunction *f34; guint32 address; g_autoptr(GBytes) bytes_bin = NULL; g_autoptr(GBytes) bytes_cfg = NULL; g_autoptr(GPtrArray) chunks_bin = NULL; g_autoptr(GPtrArray) chunks_cfg = NULL; g_autoptr(GByteArray) req_addr = g_byte_array_new (); /* we should be in bootloader mode now, but check anyway */ if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not bootloader, perhaps need detach?!"); return FALSE; } /* check is idle */ if (!fu_synaptics_rmi_device_wait_for_idle (self, 0, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34, error)) { g_prefix_error (error, "not idle: "); return FALSE; } /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; /* get both images */ bytes_bin = fu_firmware_get_image_by_id_bytes (firmware, "ui", error); if (bytes_bin == NULL) return FALSE; bytes_cfg = fu_firmware_get_image_by_id_bytes (firmware, "config", error); if (bytes_cfg == NULL) return FALSE; /* disable powersaving */ if (!fu_synaptics_rmi_device_disable_sleep (self, error)) { g_prefix_error (error, "failed to disable sleep: "); return FALSE; } /* unlock again */ if (!fu_synaptics_rmi_device_write_bootloader_id (self, error)) { g_prefix_error (error, "failed to unlock again: "); return FALSE; } /* erase all */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_synaptics_rmi_v5_device_erase_all (self, error)) { g_prefix_error (error, "failed to erase all: "); return FALSE; } /* write initial address */ fu_byte_array_append_uint16 (req_addr, 0x0, G_LITTLE_ENDIAN); fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); if (!fu_synaptics_rmi_device_write (self, f34->data_base, req_addr, error)) { g_prefix_error (error, "failed to write 1st address zero: "); return FALSE; } /* write each block */ if (f34->function_version == 0x01) address = f34->data_base + RMI_F34_BLOCK_DATA_V1_OFFSET; else address = f34->data_base + RMI_F34_BLOCK_DATA_OFFSET; chunks_bin = fu_chunk_array_new_from_bytes (bytes_bin, 0x00, /* start addr */ 0x00, /* page_sz */ flash->block_size); chunks_cfg = fu_chunk_array_new_from_bytes (bytes_cfg, 0x00, /* start addr */ 0x00, /* page_sz */ flash->block_size); for (guint i = 0; i < chunks_bin->len; i++) { FuChunk *chk = g_ptr_array_index (chunks_bin, i); if (!fu_synaptics_rmi_v5_device_write_block (self, RMI_F34_WRITE_FW_BLOCK, address, chk->data, chk->data_sz, error)) { g_prefix_error (error, "failed to write bin block %u: ", chk->idx); return FALSE; } fu_device_set_progress_full (device, (gsize) i, (gsize) chunks_bin->len + chunks_cfg->len); } /* program the configuration image */ if (!fu_synaptics_rmi_device_write (self, f34->data_base, req_addr, error)) { g_prefix_error (error, "failed to 2nd write address zero: "); return FALSE; } for (guint i = 0; i < chunks_cfg->len; i++) { FuChunk *chk = g_ptr_array_index (chunks_cfg, i); if (!fu_synaptics_rmi_v5_device_write_block (self, RMI_F34_WRITE_CONFIG_BLOCK, address, chk->data, chk->data_sz, error)) { g_prefix_error (error, "failed to write cfg block %u: ", chk->idx); return FALSE; } fu_device_set_progress_full (device, (gsize) chunks_bin->len + i, (gsize) chunks_bin->len + chunks_cfg->len); } /* success */ return TRUE; } gboolean fu_synaptics_rmi_v5_device_setup (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFunction *f34; FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); g_autoptr(GByteArray) f34_data0 = NULL; g_autoptr(GByteArray) f34_data2 = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; /* get bootloader ID */ f34_data0 = fu_synaptics_rmi_device_read (self, f34->query_base, 0x2, error); if (f34_data0 == NULL) { g_prefix_error (error, "failed to read bootloader ID: "); return FALSE; } flash->bootloader_id[0] = f34_data0->data[0]; flash->bootloader_id[1] = f34_data0->data[1]; /* get flash properties */ f34_data2 = fu_synaptics_rmi_device_read (self, f34->query_base + 0x2, 0x7, error); if (f34_data2 == NULL) return FALSE; flash->block_size = fu_common_read_uint16 (f34_data2->data + RMI_F34_BLOCK_SIZE_OFFSET, G_LITTLE_ENDIAN); flash->block_count_fw = fu_common_read_uint16 (f34_data2->data + RMI_F34_FW_BLOCKS_OFFSET, G_LITTLE_ENDIAN); flash->block_count_cfg = fu_common_read_uint16 (f34_data2->data + RMI_F34_CONFIG_BLOCKS_OFFSET, G_LITTLE_ENDIAN); flash->status_addr = f34->data_base + RMI_F34_BLOCK_DATA_OFFSET + flash->block_size; return TRUE; } gboolean fu_synaptics_rmi_v5_device_query_status (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFunction *f01; g_autoptr(GByteArray) f01_db = NULL; /* f01 */ f01 = fu_synaptics_rmi_device_get_function (self, 0x01, error); if (f01 == NULL) return FALSE; f01_db = fu_synaptics_rmi_device_read (self, f01->data_base, 0x1, error); if (f01_db == NULL) { g_prefix_error (error, "failed to read the f01 data base: "); return FALSE; } if (f01_db->data[0] & 0x40) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } return TRUE; } fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-v5-device.h000066400000000000000000000012101362775233600243140ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaptics-rmi-device.h" gboolean fu_synaptics_rmi_v5_device_detach (FuDevice *device, GError **error); gboolean fu_synaptics_rmi_v5_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error); gboolean fu_synaptics_rmi_v5_device_setup (FuSynapticsRmiDevice *self, GError **error); gboolean fu_synaptics_rmi_v5_device_query_status (FuSynapticsRmiDevice *self, GError **error); fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-v6-device.c000066400000000000000000000032251362775233600243200ustar00rootroot00000000000000/* * Copyright (C) 2012-2014 Andrew Duggan * Copyright (C) 2012-2019 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-synaptics-rmi-v6-device.h" #include "fwupd-error.h" #define RMI_F34_CONFIG_BLOCKS_OFFSET 2 gboolean fu_synaptics_rmi_v6_device_setup (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); FuSynapticsRmiFunction *f34; g_autoptr(GByteArray) f34_data0 = NULL; g_autoptr(GByteArray) f34_data2 = NULL; g_autoptr(GByteArray) f34_data3 = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; /* get bootloader ID */ f34_data0 = fu_synaptics_rmi_device_read (self, f34->query_base, 0x2, error); if (f34_data0 == NULL) { g_prefix_error (error, "failed to read bootloader ID: "); return FALSE; } flash->bootloader_id[0] = f34_data0->data[0]; flash->bootloader_id[1] = f34_data0->data[1]; /* get flash properties */ f34_data2 = fu_synaptics_rmi_device_read (self, f34->query_base + 0x02, 2, error); if (f34_data2 == NULL) return FALSE; flash->block_size = fu_common_read_uint16 (f34_data2->data, G_LITTLE_ENDIAN); f34_data3 = fu_synaptics_rmi_device_read (self, f34->query_base + 0x03, 8, error); if (f34_data3 == NULL) return FALSE; flash->block_count_fw = fu_common_read_uint16 (f34_data3->data, G_LITTLE_ENDIAN); flash->block_count_cfg = fu_common_read_uint16 (f34_data3->data + RMI_F34_CONFIG_BLOCKS_OFFSET, G_LITTLE_ENDIAN); flash->status_addr = f34->data_base + 2; return TRUE; } fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-v6-device.h000066400000000000000000000004371362775233600243270ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaptics-rmi-device.h" gboolean fu_synaptics_rmi_v6_device_setup (FuSynapticsRmiDevice *self, GError **error); fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-v7-device.c000066400000000000000000000461671362775233600243350ustar00rootroot00000000000000/* * Copyright (C) 2012-2014 Andrew Duggan * Copyright (C) 2012-2019 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-chunk.h" #include "fu-synaptics-rmi-v7-device.h" #include "fwupd-error.h" #define RMI_F34_ERASE_WAIT_MS 10000 /* ms */ typedef enum { RMI_FLASH_CMD_IDLE = 0x00, RMI_FLASH_CMD_ENTER_BL, RMI_FLASH_CMD_READ, RMI_FLASH_CMD_WRITE, RMI_FLASH_CMD_ERASE, RMI_FLASH_CMD_ERASE_AP, RMI_FLASH_CMD_SENSOR_ID, } RmiFlashCommand; typedef enum { RMI_PARTITION_ID_NONE = 0x00, RMI_PARTITION_ID_BOOTLOADER = 0x01, RMI_PARTITION_ID_DEVICE_CONFIG, RMI_PARTITION_ID_FLASH_CONFIG, RMI_PARTITION_ID_MANUFACTURING_BLOCK, RMI_PARTITION_ID_GUEST_SERIALIZATION, RMI_PARTITION_ID_GLOBAL_PARAMETERS, RMI_PARTITION_ID_CORE_CODE, RMI_PARTITION_ID_CORE_CONFIG, RMI_PARTITION_ID_GUEST_CODE, RMI_PARTITION_ID_DISPLAY_CONFIG, RMI_PARTITION_ID_EXTERNAL_TOUCH_AFE_CONFIG, RMI_PARTITION_ID_UTILITY_PARAMETER, } RmiPartitionId; static const gchar * rmi_firmware_partition_id_to_string (RmiPartitionId partition_id) { if (partition_id == RMI_PARTITION_ID_NONE) return "none"; if (partition_id == RMI_PARTITION_ID_BOOTLOADER) return "bootloader"; if (partition_id == RMI_PARTITION_ID_DEVICE_CONFIG) return "device-config"; if (partition_id == RMI_PARTITION_ID_FLASH_CONFIG) return "flash-config"; if (partition_id == RMI_PARTITION_ID_MANUFACTURING_BLOCK) return "manufacturing-block"; if (partition_id == RMI_PARTITION_ID_GUEST_SERIALIZATION) return "guest-serialization"; if (partition_id == RMI_PARTITION_ID_GLOBAL_PARAMETERS) return "global-parameters"; if (partition_id == RMI_PARTITION_ID_CORE_CODE) return "core-code"; if (partition_id == RMI_PARTITION_ID_CORE_CONFIG) return "core-config"; if (partition_id == RMI_PARTITION_ID_GUEST_CODE) return "guest-code"; if (partition_id == RMI_PARTITION_ID_DISPLAY_CONFIG) return "display-config"; if (partition_id == RMI_PARTITION_ID_EXTERNAL_TOUCH_AFE_CONFIG) return "external-touch-afe-config"; if (partition_id == RMI_PARTITION_ID_UTILITY_PARAMETER) return "utility-parameter"; return NULL; } gboolean fu_synaptics_rmi_v7_device_detach (FuDevice *device, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device); g_autoptr(GByteArray) enable_req = g_byte_array_new (); FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); FuSynapticsRmiFunction *f34; /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; /* disable interrupts */ if (!fu_synaptics_rmi_device_disable_irqs (self, error)) return FALSE; /* enter BL */ fu_byte_array_append_uint8 (enable_req, RMI_PARTITION_ID_BOOTLOADER); fu_byte_array_append_uint32 (enable_req, 0x0, G_LITTLE_ENDIAN); fu_byte_array_append_uint8 (enable_req, RMI_FLASH_CMD_ENTER_BL); fu_byte_array_append_uint8 (enable_req, flash->bootloader_id[0]); fu_byte_array_append_uint8 (enable_req, flash->bootloader_id[1]); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 1, enable_req, error)) { g_prefix_error (error, "failed to enable programming: "); return FALSE; } /* wait for idle */ if (!fu_synaptics_rmi_device_wait_for_idle (self, RMI_F34_ENABLE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE, error)) return FALSE; if (!fu_synaptics_rmi_device_poll_wait (self, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); g_usleep (1000 * RMI_F34_ENABLE_WAIT_MS); return fu_synaptics_rmi_device_rebind_driver (self, error); } static gboolean fu_synaptics_rmi_v7_device_erase_all (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFunction *f34; FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); g_autoptr(GByteArray) erase_cmd = g_byte_array_new (); /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; fu_byte_array_append_uint8 (erase_cmd, RMI_PARTITION_ID_CORE_CODE); fu_byte_array_append_uint32 (erase_cmd, 0x0, G_LITTLE_ENDIAN); if (flash->bootloader_id[1] == 8) { /* For bootloader v8 */ fu_byte_array_append_uint8 (erase_cmd, RMI_FLASH_CMD_ERASE_AP); } else { /* For bootloader v7 */ fu_byte_array_append_uint8 (erase_cmd, RMI_FLASH_CMD_ERASE); } fu_byte_array_append_uint8 (erase_cmd, flash->bootloader_id[0]); fu_byte_array_append_uint8 (erase_cmd, flash->bootloader_id[1]); /* for BL8 device, we need hold 1 seconds after querying F34 status to * avoid not get attention by following giving erase command */ if (flash->bootloader_id[1] == 8) g_usleep (1000 * 1000); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 1, erase_cmd, error)) { g_prefix_error (error, "failed to unlock erasing: "); return FALSE; } g_usleep (1000 * 100); if (flash->bootloader_id[1] == 8){ /* wait for ATTN */ if (!fu_synaptics_rmi_device_wait_for_idle (self, RMI_F34_ERASE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE, error)) { g_prefix_error (error, "failed to wait for idle: "); return FALSE; } } if (!fu_synaptics_rmi_device_poll_wait (self, error)) { g_prefix_error (error, "failed to get flash success: "); return FALSE; } /* for BL7, we need erase config partition */ if (flash->bootloader_id[1] == 7) { g_autoptr(GByteArray) erase_config_cmd = g_byte_array_new (); fu_byte_array_append_uint8 (erase_config_cmd, RMI_PARTITION_ID_CORE_CONFIG); fu_byte_array_append_uint32 (erase_config_cmd, 0x0, G_LITTLE_ENDIAN); fu_byte_array_append_uint8 (erase_config_cmd, RMI_FLASH_CMD_ERASE); g_usleep (1000 * 100); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 1, erase_config_cmd, error)) { g_prefix_error (error, "failed to erase core config: "); return FALSE; } /* wait for ATTN */ g_usleep (1000 * 100); if (!fu_synaptics_rmi_device_wait_for_idle (self, RMI_F34_ERASE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34, error)) { g_prefix_error (error, "failed to wait for idle: "); return FALSE; } if (!fu_synaptics_rmi_device_poll_wait (self, error)) { g_prefix_error (error, "failed to get flash success: "); return FALSE; } } return TRUE; } static gboolean fu_synaptics_rmi_v7_device_write_blocks (FuSynapticsRmiDevice *self, guint32 address, const guint8 *data, guint32 datasz, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); g_autoptr(GPtrArray) chunks = NULL; /* write FW blocks */ chunks = fu_chunk_array_new (data, datasz, 0x00, /* start addr */ 0x00, /* page_sz */ flash->block_size); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); g_autoptr(GByteArray) req = g_byte_array_new (); g_byte_array_append (req, chk->data, chk->data_sz); if (!fu_synaptics_rmi_device_write (self, address, req, error)) { g_prefix_error (error, "failed to write block @0x%x:%x ", address, chk->address); return FALSE; } } /* wait for idle */ if (!fu_synaptics_rmi_device_wait_for_idle (self, RMI_F34_IDLE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE, error)) { g_prefix_error (error, "failed to wait for idle @0x%x: ", address); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_rmi_v7_device_write_partition (FuSynapticsRmiDevice *self, RmiPartitionId partition_id, GBytes *bytes, GError **error) { FuSynapticsRmiFunction *f34; FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); g_autoptr(GByteArray) req_offset = g_byte_array_new (); g_autoptr(GByteArray) req_partition_id = g_byte_array_new (); g_autoptr(GPtrArray) chunks = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; /* write partition id */ g_debug ("writing partition %s…", rmi_firmware_partition_id_to_string (partition_id)); fu_byte_array_append_uint8 (req_partition_id, partition_id); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 0x1, req_partition_id, error)) { g_prefix_error (error, "failed to write flash partition: "); return FALSE; } fu_byte_array_append_uint16 (req_offset, 0x0, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 0x2, req_offset, error)) { g_prefix_error (error, "failed to write offset: "); return FALSE; } /* write partition */ chunks = fu_chunk_array_new_from_bytes (bytes, 0x00, /* start addr */ 0x00, /* page_sz */ (gsize) flash->payload_length * (gsize) flash->block_size); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); g_autoptr(GByteArray) req_trans_sz = g_byte_array_new (); g_autoptr(GByteArray) req_cmd = g_byte_array_new (); fu_byte_array_append_uint16 (req_trans_sz, chk->data_sz / flash->block_size, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 0x3, req_trans_sz, error)) { g_prefix_error (error, "failed to write transfer length: "); return FALSE; } fu_byte_array_append_uint8 (req_cmd, RMI_FLASH_CMD_WRITE); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 0x4, req_cmd, error)) { g_prefix_error (error, "failed to flash command: "); return FALSE; } if (!fu_synaptics_rmi_v7_device_write_blocks (self, f34->data_base + 0x5, chk->data, chk->data_sz, error)) return FALSE; fu_device_set_progress_full (FU_DEVICE (self), (gsize) i, (gsize) chunks->len); } return TRUE; } gboolean fu_synaptics_rmi_v7_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device); FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); FuSynapticsRmiFunction *f34; g_autoptr(GBytes) bytes_bin = NULL; g_autoptr(GBytes) bytes_cfg = NULL; g_autoptr(GBytes) bytes_flashcfg = NULL; /* we should be in bootloader mode now, but check anyway */ if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not bootloader, perhaps need detach?!"); return FALSE; } /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; /* get both images */ bytes_bin = fu_firmware_get_image_by_id_bytes (firmware, "ui", error); if (bytes_bin == NULL) return FALSE; bytes_cfg = fu_firmware_get_image_by_id_bytes (firmware, "config", error); if (bytes_cfg == NULL) return FALSE; if (flash->bootloader_id[1] == 8) { bytes_flashcfg = fu_firmware_get_image_by_id_bytes (firmware, "flash-config", error); if (bytes_flashcfg == NULL) return FALSE; } /* disable powersaving */ if (!fu_synaptics_rmi_device_disable_sleep (self, error)) return FALSE; /* erase all */ g_debug ("erasing…"); if (!fu_synaptics_rmi_v7_device_erase_all (self, error)) { g_prefix_error (error, "failed to erase all: "); return FALSE; } /* write flash config for v8 */ if (bytes_flashcfg != NULL) { if (!fu_synaptics_rmi_v7_device_write_partition (self, RMI_PARTITION_ID_FLASH_CONFIG, bytes_flashcfg, error)) return FALSE; } /* write core code */ if (!fu_synaptics_rmi_v7_device_write_partition (self, RMI_PARTITION_ID_CORE_CODE, bytes_bin, error)) return FALSE; /* write core config */ if (!fu_synaptics_rmi_v7_device_write_partition (self, RMI_PARTITION_ID_CORE_CONFIG, bytes_cfg, error)) return FALSE; /* success */ return TRUE; } typedef struct __attribute__((packed)) { guint16 partition_id; guint16 partition_len; guint16 partition_addr; guint16 partition_prop; } RmiPartitionTbl; G_STATIC_ASSERT(sizeof(RmiPartitionTbl) == 8); static gboolean fu_synaptics_rmi_device_read_flash_config_v7 (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); FuSynapticsRmiFunction *f34; g_autoptr(GByteArray) req_addr_zero = g_byte_array_new (); g_autoptr(GByteArray) req_cmd = g_byte_array_new (); g_autoptr(GByteArray) req_partition_id = g_byte_array_new (); g_autoptr(GByteArray) req_transfer_length = g_byte_array_new (); g_autoptr(GByteArray) res = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; /* set partition id for bootloader 7 */ fu_byte_array_append_uint8 (req_partition_id, RMI_PARTITION_ID_FLASH_CONFIG); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 0x1, req_partition_id, error)) { g_prefix_error (error, "failed to write flash partition id: "); return FALSE; } fu_byte_array_append_uint16 (req_addr_zero, 0x0, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 0x2, req_addr_zero, error)) { g_prefix_error (error, "failed to write flash config address: "); return FALSE; } /* set transfer length */ fu_byte_array_append_uint16 (req_transfer_length, flash->config_length, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 0x3, req_transfer_length, error)) { g_prefix_error (error, "failed to set transfer length: "); return FALSE; } /* set command to read */ fu_byte_array_append_uint8 (req_cmd, RMI_FLASH_CMD_READ); if (!fu_synaptics_rmi_device_write (self, f34->data_base + 0x4, req_cmd, error)) { g_prefix_error (error, "failed to write command to read: "); return FALSE; } if (!fu_synaptics_rmi_device_poll_wait (self, error)) { g_prefix_error (error, "failed to wait: "); return FALSE; } /* read back entire buffer in blocks */ res = fu_synaptics_rmi_device_read (self, f34->data_base + 0x5, (guint32) flash->block_size * (guint32) flash->config_length, error); if (res == NULL) { g_prefix_error (error, "failed to read: "); return FALSE; } /* debugging */ if (g_getenv ("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "FlashConfig", res->data, res->len, 80, FU_DUMP_FLAGS_NONE); } /* parse the config length */ for (guint i = 0x2; i < res->len; i += sizeof(RmiPartitionTbl)) { RmiPartitionTbl tbl; if (!fu_memcpy_safe ((guint8 *) &tbl, sizeof(tbl), 0x0, /* dst */ res->data, res->len, i, /* src */ sizeof(tbl), error)) return FALSE; g_debug ("found partition %s (0x%02x)", rmi_firmware_partition_id_to_string (tbl.partition_id), tbl.partition_id); if (tbl.partition_id == RMI_PARTITION_ID_CORE_CONFIG) { flash->block_count_cfg = tbl.partition_len; continue; } if (tbl.partition_id == RMI_PARTITION_ID_CORE_CODE) { flash->block_count_fw = tbl.partition_len; continue; } if (tbl.partition_id == RMI_PARTITION_ID_NONE) break; } /* success */ return TRUE; } gboolean fu_synaptics_rmi_v7_device_setup (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self); FuSynapticsRmiFunction *f34; guint8 offset; g_autoptr(GByteArray) f34_data0 = NULL; g_autoptr(GByteArray) f34_dataX = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; f34_data0 = fu_synaptics_rmi_device_read (self, f34->query_base, 1, error); if (f34_data0 == NULL) { g_prefix_error (error, "failed to read bootloader ID: "); return FALSE; } offset = (f34_data0->data[0] & 0b00000111) + 1; f34_dataX = fu_synaptics_rmi_device_read (self, f34->query_base + offset, 21, error); if (f34_dataX == NULL) return FALSE; flash->bootloader_id[0] = f34_dataX->data[0x0]; flash->bootloader_id[1] = f34_dataX->data[0x1]; flash->block_size = fu_common_read_uint16 (f34_dataX->data + 0x07, G_LITTLE_ENDIAN); flash->config_length = fu_common_read_uint16 (f34_dataX->data + 0x0d, G_LITTLE_ENDIAN); flash->payload_length = fu_common_read_uint16 (f34_dataX->data + 0x0f, G_LITTLE_ENDIAN); flash->build_id = fu_common_read_uint32 (f34_dataX->data + 0x02, G_LITTLE_ENDIAN); /* sanity check */ if ((guint32) flash->block_size * (guint32) flash->config_length > G_MAXUINT16) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "block size 0x%x or config length 0x%x invalid", flash->block_size, flash->config_length); return FALSE; } /* read flash config */ return fu_synaptics_rmi_device_read_flash_config_v7 (self, error); } gboolean fu_synaptics_rmi_v7_device_query_status (FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFunction *f34; guint8 status; g_autoptr(GByteArray) f34_data = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error); if (f34 == NULL) return FALSE; f34_data = fu_synaptics_rmi_device_read (self, f34->data_base, 0x1, error); if (f34_data == NULL) { g_prefix_error (error, "failed to read the f01 data base: "); return FALSE; } status = f34_data->data[0]; if (status & 0x80) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } if (status == 0x01) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "operation only supported in bootloader mode"); return FALSE; } if (status == 0x02) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "partition ID is not supported by the bootloader"); return FALSE; } if (status == 0x03) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "partition supported, but command not supported"); return FALSE; } if (status == 0x04) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid block offset"); return FALSE; } if (status == 0x05) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid transfer"); return FALSE; } if (status == 0x06) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "partition has not been erased"); return FALSE; } if (status == 0x07) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "flash programming key incorrect"); return FALSE; } if (status == 0x08) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "bad partition table"); return FALSE; } if (status == 0x09) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "transfer checksum failed"); return FALSE; } if (status == 0x1f) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "flash hardware failure"); return FALSE; } return TRUE; } fwupd-1.3.9/plugins/synaptics-rmi/fu-synaptics-rmi-v7-device.h000066400000000000000000000012101362775233600243160ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaptics-rmi-device.h" gboolean fu_synaptics_rmi_v7_device_detach (FuDevice *device, GError **error); gboolean fu_synaptics_rmi_v7_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error); gboolean fu_synaptics_rmi_v7_device_setup (FuSynapticsRmiDevice *self, GError **error); gboolean fu_synaptics_rmi_v7_device_query_status (FuSynapticsRmiDevice *self, GError **error); fwupd-1.3.9/plugins/synaptics-rmi/fuzzing/000077500000000000000000000000001362775233600206465ustar00rootroot00000000000000fwupd-1.3.9/plugins/synaptics-rmi/fuzzing/meson.build000066400000000000000000000011051362775233600230050ustar00rootroot00000000000000synaptics_example0x = custom_target('example0x.img', output: 'example0x.img', command: [synaptics_rmi_dump, 'gen0x', '@OUTPUT@'], ) synaptics_example10 = custom_target('example10.img', output: 'example10.img', command: [synaptics_rmi_dump, 'gen10', '@OUTPUT@'], ) run_target('fuzz-synaptics-rmi', command: [ join_paths(meson.source_root(), 'contrib/afl-fuzz.py'), '-i', meson.current_build_dir(), '-o', join_paths(meson.current_build_dir(), '..', 'findings'), synaptics_rmi_dump, ], depends: [ synaptics_example0x, synaptics_example10, ], ) fwupd-1.3.9/plugins/synaptics-rmi/meson.build000066400000000000000000000022161362775233600213150ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsRmi"'] install_data(['synaptics-rmi.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_synaptics_rmi', fu_hash, sources : [ 'fu-plugin-synaptics-rmi.c', 'fu-synaptics-rmi-common.c', 'fu-synaptics-rmi-device.c', 'fu-synaptics-rmi-v5-device.c', 'fu-synaptics-rmi-v6-device.c', 'fu-synaptics-rmi-v7-device.c', 'fu-synaptics-rmi-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], ) if get_option('tests') # for fuzzing synaptics_rmi_dump = executable( 'synaptics-rmi-dump', sources : [ 'fu-dump.c', 'fu-synaptics-rmi-common.c', 'fu-synaptics-rmi-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ gio, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) subdir('fuzzing') endif fwupd-1.3.9/plugins/synaptics-rmi/synaptics-rmi.quirk000066400000000000000000000001151362775233600230260ustar00rootroot00000000000000[DeviceInstanceId=HIDRAW\VEN_06CB] Plugin = synaptics_rmi Vendor = Synaptics fwupd-1.3.9/plugins/test/000077500000000000000000000000001362775233600153275ustar00rootroot00000000000000fwupd-1.3.9/plugins/test/README.md000066400000000000000000000006121362775233600166050ustar00rootroot00000000000000Test 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. Vendor ID Security ------------------ The fake device is only for local testing and thus requires no vendor ID set. fwupd-1.3.9/plugins/test/fu-plugin-invalid.c000066400000000000000000000003721362775233600210270ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, "invalid"); } fwupd-1.3.9/plugins/test/fu-plugin-test.c000066400000000000000000000173421362775233600203650ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" struct FuPluginData { GMutex mutex; }; void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); 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_add_flag (device, FWUPD_DEVICE_FLAG_CAN_VERIFY); fu_device_set_protocol (device, "com.acme.test"); 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_vendor_id (child1, "USB:FFFF"); fu_device_set_protocol (child1, "com.acme"); 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_vendor_id (child2, "USB:FFFF"); fu_device_set_protocol (child2, "com.acme"); 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.2") == 0) { fu_device_add_checksum (device, "90d0ad436d21e0687998cd2127b2411135e1f730"); fu_device_add_checksum (device, "921631916a60b295605dbae6a0309f9b64e2401b3de8e8506e109fc82c586e3a"); return TRUE; } 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; } static gchar * fu_plugin_test_get_version (GBytes *blob_fw) { const gchar *str = g_bytes_get_data (blob_fw, NULL); guint64 val = 0; if (str == NULL) return NULL; val = fu_common_strtoull (str); if (val == 0x0) return NULL; return fu_common_version_from_uint32 (val, FWUPD_VERSION_FORMAT_TRIPLET); } 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 { g_autofree gchar *ver = fu_plugin_test_get_version (blob_fw); if (ver != NULL) { fu_device_set_version (device, ver, FWUPD_VERSION_FORMAT_TRIPLET); } 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.3.9/plugins/test/meson.build000066400000000000000000000014261362775233600174740ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : install_dummy, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) shared_module('fu_plugin_invalid', sources : [ 'fu-plugin-invalid.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : install_dummy, install_dir: plugin_dir, link_with : [ fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/thelio-io/000077500000000000000000000000001362775233600162415ustar00rootroot00000000000000fwupd-1.3.9/plugins/thelio-io/README.md000066400000000000000000000007201362775233600175170ustar00rootroot00000000000000Thelio IO Support ================= Introduction ------------ This plugin is used to detach the Thelio IO device to DFU mode. To switch to this mode `1` has to be written to the `bootloader` file in sysfs. GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1209&PID_1776&REV_0001` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x1209` fwupd-1.3.9/plugins/thelio-io/fu-plugin-thelio-io.c000066400000000000000000000006571362775233600222120ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-thelio-io-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_THELIO_IO_DEVICE); } fwupd-1.3.9/plugins/thelio-io/fu-thelio-io-device.c000066400000000000000000000053751362775233600221550ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-io-channel.h" #include "fu-thelio-io-device.h" struct _FuThelioIoDevice { FuUsbDevice parent_instance; }; G_DEFINE_TYPE (FuThelioIoDevice, fu_thelio_io_device, FU_TYPE_USB_DEVICE) static gboolean fu_thelio_io_device_probe (FuDevice *device, GError **error) { const gchar *devpath; g_autofree gchar *fn = NULL; g_autofree gchar *buf = NULL; g_autoptr(GUdevDevice) udev_device = NULL; fu_device_add_instance_id (device, "USB\\VID_03EB&PID_2FF4"); /* convert GUsbDevice to GUdevDevice */ udev_device = fu_usb_device_find_udev_device (FU_USB_DEVICE (device), error); if (udev_device == NULL) return FALSE; devpath = g_udev_device_get_sysfs_path (udev_device); if (G_UNLIKELY (devpath == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Could not determine sysfs path for device"); return FALSE; } fn = g_build_filename (devpath, "revision", NULL); if (!g_file_get_contents(fn, &buf, NULL, error)) return FALSE; fu_device_set_version (device, (const gchar *) buf, FWUPD_VERSION_FORMAT_TRIPLET); return TRUE; } static gboolean fu_thelio_io_device_detach (FuDevice *device, GError **error) { const gchar *devpath; g_autofree gchar *fn = NULL; g_autoptr(FuIOChannel) io_channel = NULL; g_autoptr(GUdevDevice) udev_device = NULL; const guint8 buf[] = { '1', '\n' }; /* convert GUsbDevice to GUdevDevice */ udev_device = fu_usb_device_find_udev_device (FU_USB_DEVICE (device), error); if (udev_device == NULL) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); devpath = g_udev_device_get_sysfs_path (udev_device); if (G_UNLIKELY (devpath == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Could not determine sysfs path for device"); return FALSE; } fn = g_build_filename (devpath, "bootloader", NULL); io_channel = fu_io_channel_new_file (fn, error); if (io_channel == NULL) return FALSE; if (!fu_io_channel_write_raw (io_channel, buf, sizeof(buf), 500, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error)) return FALSE; fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_thelio_io_device_init (FuThelioIoDevice *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); } static void fu_thelio_io_device_class_init (FuThelioIoDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->probe = fu_thelio_io_device_probe; klass_device->detach = fu_thelio_io_device_detach; } fwupd-1.3.9/plugins/thelio-io/fu-thelio-io-device.h000066400000000000000000000005571362775233600221570ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_THELIO_IO_DEVICE (fu_thelio_io_device_get_type ()) G_DECLARE_FINAL_TYPE (FuThelioIoDevice, fu_thelio_io_device, FU, THELIO_IO_DEVICE, FuUsbDevice) fwupd-1.3.9/plugins/thelio-io/meson.build000066400000000000000000000007761362775233600204150ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginThelioIo"'] install_data(['thelio-io.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_thelio_io', fu_hash, sources : [ 'fu-plugin-thelio-io.c', 'fu-thelio-io-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/thelio-io/thelio-io.quirk000066400000000000000000000001321362775233600212030ustar00rootroot00000000000000# System76 Thelio IO [DeviceInstanceId=USB\VID_1209&PID_1776&REV_0001] Plugin = thelio_io fwupd-1.3.9/plugins/thunderbolt-power/000077500000000000000000000000001362775233600200345ustar00rootroot00000000000000fwupd-1.3.9/plugins/thunderbolt-power/fu-plugin-thunderbolt-power.c000066400000000000000000000302521362775233600255720ustar00rootroot00000000000000/* * 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-hash.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" /* 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, 0); 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.3.9/plugins/thunderbolt-power/meson.build000066400000000000000000000006651362775233600222050ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginThunderbolt"'] fu_plugin_thunderbolt_power = shared_module('fu_plugin_thunderbolt_power', fu_hash, sources : [ 'fu-plugin-thunderbolt-power.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/thunderbolt/000077500000000000000000000000001362775233600167025ustar00rootroot00000000000000fwupd-1.3.9/plugins/thunderbolt/README.md000066400000000000000000000062771362775233600201750ustar00rootroot00000000000000Thunderbolt™ 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. Additionally for host system thunderbolt controllers another GUID is added containing the PCI slot designation of the controller. This is intended to be used for systems with multiple host controllers to disambiguiate between controllers. * `TBT-$(vid)$(pid)-native-$(slot)` Vendor ID Security ------------------ The vendor ID is set from the udev vendor, for example set to `TBT:0x$(vid)` 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.3.9/plugins/thunderbolt/fu-plugin-thunderbolt.c000066400000000000000000000537531362775233600233210ustar00rootroot00000000000000/* * Copyright (C) 2017 Christian J. Kellner * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-device-metadata.h" #include "fu-thunderbolt-image.h" #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 gboolean fu_plugin_thunderbolt_safe_kernel (FuPlugin *plugin, GError **error) { g_autofree gchar *minimum_kernel = NULL; struct utsname name_tmp; memset (&name_tmp, 0, sizeof(struct utsname)); if (uname (&name_tmp) < 0) { g_debug ("Failed to read current kernel version"); return TRUE; } minimum_kernel = fu_plugin_get_config_value (plugin, "MinimumKernelVersion"); if (minimum_kernel == NULL) { g_debug ("Ignoring kernel safety checks"); return TRUE; } if (fu_common_vercmp_full (name_tmp.release, minimum_kernel, FWUPD_VERSION_FORMAT_TRIPLET) < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "kernel %s may not have full Thunderbolt support", name_tmp.release); return FALSE; } g_debug ("Using kernel %s (minimum %s)", name_tmp.release, minimum_kernel); return TRUE; } 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, "missing sysfs attribute %s", name); 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_uint16 (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, "%s overflows", name); 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 = NULL; 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; guint16 gen; g_autofree gchar *id = NULL; g_autofree gchar *version = NULL; g_autofree gchar *vendor_id = NULL; g_autofree gchar *device_id = NULL; g_autofree gchar *device_id_with_path = NULL; g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_vid = NULL; g_autoptr(GError) error_did = NULL; g_autoptr(GError) error_gen = 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; } /* these may be missing on ICL or later */ vid = fu_plugin_thunderbolt_udev_get_uint16 (device, "vendor", &error_vid); if (vid == 0x0) g_debug ("failed to get Vendor ID: %s", error_vid->message); did = fu_plugin_thunderbolt_udev_get_uint16 (device, "device", &error_did); if (did == 0x0) g_debug ("failed to get Device ID: %s", error_did->message); /* requires kernel 5.5 or later, non-fatal if not available */ gen = fu_plugin_thunderbolt_udev_get_uint16 (device, "generation", &error_gen); if (gen == 0) g_debug ("Unable to read generation: %s", error_gen->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)) { /* USB4 controllers don't have a concept of legacy vs native * so don't try to read a native attribute from their NVM */ if (is_host && gen < 4) { g_autoptr(GError) native_error = NULL; g_autoptr(GUdevDevice) udev_parent = 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"); udev_parent = g_udev_device_get_parent_with_subsystem (device, "pci", NULL); if (udev_parent != NULL) device_id_with_path = g_strdup_printf ("TBT-%04x%04x%s-%s", (guint) vid, (guint) did, is_native ? "-native" : "", g_udev_device_get_property (udev_parent, "PCI_SLOT_NAME")); } 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); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_DUAL_IMAGE); } 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); if (!is_host) name = g_udev_device_get_sysfs_attr (device, "device_name"); if (name == NULL) { if (gen == 4) name = "USB4 Controller"; else name = "Thunderbolt Controller"; } 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_protocol (dev, "com.intel.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 (device_id_with_path != NULL) fu_device_add_instance_id (dev, device_id_with_path); 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); 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_startup (FuPlugin *plugin, GError **error) { return fu_plugin_thunderbolt_safe_kernel (plugin, error); } 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/fwupd/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.3.9/plugins/thunderbolt/fu-self-test.c000066400000000000000000000766461362775233600214070ustar00rootroot00000000000000/* * 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 "fu-plugin-private.h" #include "fu-thunderbolt-image.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 = g_build_filename (TESTDATADIR, "thunderbolt/minimal-fw-controller.bin", NULL); 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 *pluginfn = NULL; 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); pluginfn = g_build_filename (PLUGINBUILDDIR, "libfu_plugin_thunderbolt." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open (tt->plugin, pluginfn, &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 = g_build_filename (TESTDATADIR, "thunderbolt/minimal-fw.bin", NULL); 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 (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 = g_build_filename (TESTDATADIR, "thunderbolt/minimal-fw-controller.bin", NULL); 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 = g_build_filename (TESTDATADIR, "thunderbolt/minimal-fw.bin", NULL); 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 = g_build_filename (TESTDATADIR, "colorhug/firmware.bin", NULL); 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.3.9/plugins/thunderbolt/fu-thunderbolt-image.c000066400000000000000000000612341362775233600230760ustar00rootroot00000000000000/* * 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.3.9/plugins/thunderbolt/fu-thunderbolt-image.h000066400000000000000000000011141362775233600230720ustar00rootroot00000000000000/* * 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.3.9/plugins/thunderbolt/fu-thunderbolt-tool.c000066400000000000000000000041561362775233600227710ustar00rootroot00000000000000/* * 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-hash.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.3.9/plugins/thunderbolt/meson.build000066400000000000000000000035011362775233600210430ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) cargs += '-DTESTDATADIR="' + join_paths(meson.source_root(), 'data', 'tests') + '"' executable('tbtfwucli', fu_hash, sources : [ 'fu-thunderbolt-tool.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], c_args : cargs, link_with : [ fu_plugin_thunderbolt, fwupd, fwupdplugin, ], dependencies : [ plugin_deps, ], ) install_data(['thunderbolt.conf'], install_dir: join_paths(sysconfdir, 'fwupd') ) # we use functions from 2.52 in the tests if get_option('tests') and umockdev.found() and gio.version().version_compare('>= 2.52') 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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, umockdev, ], link_with : [ fwupd, fwupdplugin, ], 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.3.9/plugins/thunderbolt/thunderbolt.conf000066400000000000000000000002771362775233600221110ustar00rootroot00000000000000[thunderbolt] # Minimum kernel version to allow use of this plugin # It's important that all backports from this kernel have been # made if using an older kernel MinimumKernelVersion=4.13.0 fwupd-1.3.9/plugins/tpm-eventlog/000077500000000000000000000000001362775233600167715ustar00rootroot00000000000000fwupd-1.3.9/plugins/tpm-eventlog/README.md000066400000000000000000000007741362775233600202600ustar00rootroot00000000000000TPM Event Log Support ===================== Introduction ------------ The TPM Event Log records which events are registered for the PCR0 hash, which may help in explaining why PCR0 values are differing for some firmware. The device exposed is not upgradable in any way and is just for debugging. The created device will be a child device of the system TPM device, which may or may not be upgradable. Vendor ID Security ------------------ The device is not upgradable and thus requires no vendor ID set. fwupd-1.3.9/plugins/tpm-eventlog/fu-plugin-tpm-eventlog.c000066400000000000000000000055641362775233600234740ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-hash.h" #include "fu-plugin-vfuncs.h" #include "fu-tpm-eventlog-device.h" struct FuPluginData { GPtrArray *pcr0s; }; void fu_plugin_init (FuPlugin *plugin) { fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_BEFORE, "uefi"); fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); if (data->pcr0s != NULL) g_ptr_array_unref (data->pcr0s); } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); gsize bufsz = 0; const gchar *fn = "/sys/kernel/security/tpm0/binary_bios_measurements"; g_autofree gchar *str = NULL; g_autofree guint8 *buf = NULL; g_autoptr(FuTpmEventlogDevice) dev = NULL; if (!g_file_get_contents (fn, (gchar **) &buf, &bufsz, error)) return FALSE; if (bufsz == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to read data from %s", fn); return FALSE; } dev = fu_tpm_eventlog_device_new (buf, bufsz, error); if (dev == NULL) return FALSE; if (!fu_device_setup (FU_DEVICE (dev), error)) return FALSE; /* save this so we can compare against system-firmware */ data->pcr0s = fu_tpm_eventlog_device_get_checksums (dev, 0, error); if (data->pcr0s == NULL) return FALSE; for (guint i = 0; i < data->pcr0s->len; i++) { const gchar *csum = g_ptr_array_index (data->pcr0s, i); fu_device_add_checksum (FU_DEVICE (dev), csum); } /* add optional report metadata */ str = fu_tpm_eventlog_device_report_metadata (dev); g_debug ("using TPM event log report data of:\n%s", str); fu_plugin_add_report_metadata (plugin, "TpmEventLog", str); fu_plugin_device_add (plugin, FU_DEVICE (dev)); return TRUE; } void fu_plugin_device_registered (FuPlugin *plugin, FuDevice *device) { FuPluginData *data = fu_plugin_get_data (plugin); GPtrArray *checksums; /* only care about UEFI devices from ESRT */ if (g_strcmp0 (fu_device_get_plugin (device), "uefi") != 0) return; /* only the system-firmware device gets checksums */ checksums = fu_device_get_checksums (device); if (checksums->len == 0) return; for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index (checksums, i); for (guint j = 0; j < data->pcr0s->len; j++) { const gchar *checksum_tmp = g_ptr_array_index (data->pcr0s, j); if (g_strcmp0 (checksum, checksum_tmp) == 0) { g_debug ("TPM reconstructed event log matched PCR0 reading"); return; } } } /* urgh, this is unexpected */ fu_device_set_update_error (device, "TPM PCR0 differs from reconstruction, " "please see https://github.com/fwupd/fwupd/wiki/TPM-PCR0-differs-from-reconstruction"); } fwupd-1.3.9/plugins/tpm-eventlog/fu-self-test.c000066400000000000000000000055041362775233600214570ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-tpm-eventlog-common.h" #include "fu-tpm-eventlog-device.h" static void fu_test_tpm_eventlog_parse_v1_func (void) { const gchar *tmp; gboolean ret; gsize bufsz = 0; g_autofree gchar *fn = NULL; g_autofree guint8 *buf = NULL; g_autofree gchar *str = NULL; g_autoptr(FuTpmEventlogDevice) dev = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) pcr0s = NULL; fn = g_build_filename (TESTDATADIR, "binary_bios_measurements-v1", NULL); ret = g_file_get_contents (fn, (gchar **) &buf, &bufsz, &error); g_assert_no_error (error); g_assert_true (ret); dev = fu_tpm_eventlog_device_new (buf, bufsz, &error); g_assert_no_error (error); g_assert_nonnull (dev); str = fu_device_to_string (FU_DEVICE (dev)); g_print ("%s\n", str); g_assert_nonnull (g_strstr_len (str, -1, "231f248f12ef9f38549f1bda7a859b781b5caab0")); g_assert_nonnull (g_strstr_len (str, -1, "9069ca78e7450a285173431b3e52c5c25299e473")); pcr0s = fu_tpm_eventlog_device_get_checksums (dev, 0, &error); g_assert_no_error (error); g_assert_nonnull (pcr0s); g_assert_cmpint (pcr0s->len, ==, 1); tmp = g_ptr_array_index (pcr0s, 0); g_assert_cmpstr (tmp, ==, "543ae96e57b6fc4003531cd0dab1d9ba7f8166e0"); } static void fu_test_tpm_eventlog_parse_v2_func (void) { const gchar *tmp; gboolean ret; gsize bufsz = 0; g_autofree gchar *fn = NULL; g_autofree guint8 *buf = NULL; g_autofree gchar *str = NULL; g_autoptr(FuTpmEventlogDevice) dev = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) pcr0s = NULL; fn = g_build_filename (TESTDATADIR, "binary_bios_measurements-v2", NULL); ret = g_file_get_contents (fn, (gchar **) &buf, &bufsz, &error); g_assert_no_error (error); g_assert_true (ret); dev = fu_tpm_eventlog_device_new (buf, bufsz, &error); g_assert_no_error (error); g_assert_nonnull (dev); str = fu_device_to_string (FU_DEVICE (dev)); g_print ("%s\n", str); g_assert_nonnull (g_strstr_len (str, -1, "19ce8e1347a709d2b485d519695e3ce10b939485")); g_assert_nonnull (g_strstr_len (str, -1, "9069ca78e7450a285173431b3e52c5c25299e473")); g_assert_nonnull (g_strstr_len (str, -1, "Boot Guard Measured")); pcr0s = fu_tpm_eventlog_device_get_checksums (dev, 0, &error); g_assert_no_error (error); g_assert_nonnull (pcr0s); g_assert_cmpint (pcr0s->len, ==, 1); tmp = g_ptr_array_index (pcr0s, 0); g_assert_cmpstr (tmp, ==, "ebead4b31c7c49e193c440cd6ee90bc1b61a3ca6"); } 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 ("/tpm-eventlog/parse{v1}", fu_test_tpm_eventlog_parse_v1_func); g_test_add_func ("/tpm-eventlog/parse{v2}", fu_test_tpm_eventlog_parse_v2_func); return g_test_run (); } fwupd-1.3.9/plugins/tpm-eventlog/fu-tpm-eventlog-common.c000066400000000000000000000153271362775233600234640ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-tpm-eventlog-common.h" const gchar * fu_tpm_eventlog_pcr_to_string (gint pcr) { if (pcr == 0) return "BIOS"; if (pcr == 1) return "BIOS Configuration"; if (pcr == 2) return "Option ROMs"; if (pcr == 3) return "Option ROM configuration"; if (pcr == 4) return "Initial program loader code"; if (pcr == 5) return "Initial program loader code configuration"; if (pcr == 6) return "State transitions and wake events"; if (pcr == 7) return "Platform manufacturer specific measurements"; if (pcr >= 8 && pcr <= 15) return "Static operating system"; if (pcr == 16) return "Debug"; if (pcr == 17) return "Dynamic root of trust measurement and launch control policy"; if (pcr >= 18 && pcr <= 22) return "Trusted OS"; if (pcr == 23) return "Application support"; return "Undefined"; } const gchar * fu_tpm_eventlog_hash_to_string (TPM2_ALG_ID hash_kind) { if (hash_kind == TPM2_ALG_SHA1) return "SHA1"; if (hash_kind == TPM2_ALG_SHA256) return "SHA256"; if (hash_kind == TPM2_ALG_SHA384) return "SHA384"; if (hash_kind == TPM2_ALG_SHA512) return "SHA512"; return NULL; } guint32 fu_tpm_eventlog_hash_get_size (TPM2_ALG_ID hash_kind) { if (hash_kind == TPM2_ALG_SHA1) return TPM2_SHA1_DIGEST_SIZE; if (hash_kind == TPM2_ALG_SHA256) return TPM2_SHA256_DIGEST_SIZE; if (hash_kind == TPM2_ALG_SHA384) return TPM2_SHA384_DIGEST_SIZE; if (hash_kind == TPM2_ALG_SHA512) return TPM2_SHA512_DIGEST_SIZE; return 0; } const gchar * fu_tpm_eventlog_item_kind_to_string (FuTpmEventlogItemKind event_type) { if (event_type == EV_PREBOOT_CERT) return "EV_PREBOOT_CERT"; if (event_type == EV_POST_CODE) return "EV_POST_CODE"; if (event_type == EV_NO_ACTION) return "EV_NO_ACTION"; if (event_type == EV_SEPARATOR) return "EV_SEPARATOR"; if (event_type == EV_ACTION) return "EV_ACTION"; if (event_type == EV_EVENT_TAG) return "EV_EVENT_TAG"; if (event_type == EV_S_CRTM_CONTENTS) return "EV_S_CRTM_CONTENTS"; if (event_type == EV_S_CRTM_VERSION) return "EV_S_CRTM_VERSION"; if (event_type == EV_CPU_MICROCODE) return "EV_CPU_MICROCODE"; if (event_type == EV_PLATFORM_CONFIG_FLAGS) return "EV_PLATFORM_CONFIG_FLAGS"; if (event_type == EV_TABLE_OF_DEVICES) return "EV_TABLE_OF_DEVICES"; if (event_type == EV_COMPACT_HASH) return "EV_COMPACT_HASH"; if (event_type == EV_NONHOST_CODE) return "EV_NONHOST_CODE"; if (event_type == EV_NONHOST_CONFIG) return "EV_NONHOST_CONFIG"; if (event_type == EV_NONHOST_INFO) return "EV_NONHOST_INFO"; if (event_type == EV_OMIT_BOOT_DEVICE_EVENTS) return "EV_OMIT_BOOT_DEVICE_EVENTS"; if (event_type == EV_EFI_EVENT_BASE) return "EV_EFI_EVENT_BASE"; if (event_type == EV_EFI_VARIABLE_DRIVER_CONFIG) return "EV_EFI_VARIABLE_DRIVER_CONFIG"; if (event_type == EV_EFI_VARIABLE_BOOT) return "EV_EFI_VARIABLE_BOOT"; if (event_type == EV_EFI_BOOT_SERVICES_APPLICATION) return "EV_BOOT_SERVICES_APPLICATION"; if (event_type == EV_EFI_BOOT_SERVICES_DRIVER) return "EV_EFI_BOOT_SERVICES_DRIVER"; if (event_type == EV_EFI_RUNTIME_SERVICES_DRIVER) return "EV_EFI_RUNTIME_SERVICES_DRIVER"; if (event_type == EV_EFI_GPT_EVENT) return "EV_EFI_GPT_EVENT"; if (event_type == EV_EFI_ACTION) return "EV_EFI_ACTION"; if (event_type == EV_EFI_PLATFORM_FIRMWARE_BLOB) return "EV_EFI_PLATFORM_FIRMWARE_BLOB"; if (event_type == EV_EFI_HANDOFF_TABLES) return "EV_EFI_HANDOFF_TABLES"; if (event_type == EV_EFI_HCRTM_EVENT) return "EV_EFI_HCRTM_EVENT"; if (event_type == EV_EFI_VARIABLE_AUTHORITY) return "EV_EFI_EFI_VARIABLE_AUTHORITY"; return NULL; } gchar * fu_tpm_eventlog_strhex (GBytes *blob) { GString *csum = g_string_new (NULL); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data (blob, &bufsz); for (guint i = 0; i < bufsz; i++) g_string_append_printf (csum, "%02x", buf[i]); return g_string_free (csum, FALSE); } gchar * fu_tpm_eventlog_blobstr (GBytes *blob) { gboolean has_printable = FALSE; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data (blob, &bufsz); g_autoptr(GString) str = g_string_new (NULL); for (gsize i = 0; i < bufsz; i++) { gchar chr = buf[i]; if (g_ascii_isprint (chr)) { g_string_append_c (str, chr); has_printable = TRUE; } else { g_string_append_c (str, '.'); } } if (!has_printable) return NULL; return g_string_free (g_steal_pointer (&str), FALSE); } GPtrArray * fu_tpm_eventlog_calc_checksums (GPtrArray *items, guint8 pcr, GError **error) { guint cnt_sha1 = 0; guint cnt_sha256 = 0; guint8 digest_sha1[TPM2_SHA1_DIGEST_SIZE] = { 0x0 }; guint8 digest_sha256[TPM2_SHA256_DIGEST_SIZE] = { 0x0 }; gsize digest_sha1_len = sizeof(digest_sha1); gsize digest_sha256_len = sizeof(digest_sha256); g_autoptr(GPtrArray) csums = g_ptr_array_new_with_free_func (g_free); /* sanity check */ if (items->len == 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no event log data"); return NULL; } /* take existing PCR hash, append new measurement to that, * hash that with the same algorithm */ for (guint i = 0; i < items->len; i++) { FuTpmEventlogItem *item = g_ptr_array_index (items, i); if (item->pcr != pcr) continue; if (item->checksum_sha1 != NULL) { g_autoptr(GChecksum) csum_sha1 = g_checksum_new (G_CHECKSUM_SHA1); g_checksum_update (csum_sha1, (const guchar *) digest_sha1, digest_sha1_len); g_checksum_update (csum_sha1, (const guchar *) g_bytes_get_data (item->checksum_sha1, NULL), g_bytes_get_size (item->checksum_sha1)); g_checksum_get_digest (csum_sha1, digest_sha1, &digest_sha1_len); cnt_sha1++; } if (item->checksum_sha256 != NULL) { g_autoptr(GChecksum) csum_sha256 = g_checksum_new (G_CHECKSUM_SHA256); g_checksum_update (csum_sha256, (const guchar *) digest_sha256, digest_sha256_len); g_checksum_update (csum_sha256, (const guchar *) g_bytes_get_data (item->checksum_sha256, NULL), g_bytes_get_size (item->checksum_sha256)); g_checksum_get_digest (csum_sha256, digest_sha256, &digest_sha256_len); cnt_sha256++; } } if (cnt_sha1 == 0 && cnt_sha256 == 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no SHA1 or SHA256 data"); return NULL; } if (cnt_sha1 > 0) { g_autoptr(GBytes) blob_sha1 = NULL; blob_sha1 = g_bytes_new_static (digest_sha1, sizeof(digest_sha1)); g_ptr_array_add (csums, fu_tpm_eventlog_strhex (blob_sha1)); } if (cnt_sha256 > 0) { g_autoptr(GBytes) blob_sha256 = NULL; blob_sha256 = g_bytes_new_static (digest_sha256, sizeof(digest_sha256)); g_ptr_array_add (csums, fu_tpm_eventlog_strhex (blob_sha256)); } return g_steal_pointer (&csums); } fwupd-1.3.9/plugins/tpm-eventlog/fu-tpm-eventlog-common.h000066400000000000000000000036101362775233600234610ustar00rootroot00000000000000 /* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-plugin.h" typedef enum { EV_PREBOOT_CERT = 0x00000000, EV_POST_CODE = 0x00000001, EV_NO_ACTION = 0x00000003, EV_SEPARATOR = 0x00000004, EV_ACTION = 0x00000005, EV_EVENT_TAG = 0x00000006, EV_S_CRTM_CONTENTS = 0x00000007, EV_S_CRTM_VERSION = 0x00000008, EV_CPU_MICROCODE = 0x00000009, EV_PLATFORM_CONFIG_FLAGS = 0x0000000a, EV_TABLE_OF_DEVICES = 0x0000000b, EV_COMPACT_HASH = 0x0000000c, EV_NONHOST_CODE = 0x0000000f, EV_NONHOST_CONFIG = 0x00000010, EV_NONHOST_INFO = 0x00000011, EV_OMIT_BOOT_DEVICE_EVENTS = 0x00000012, EV_EFI_EVENT_BASE = 0x80000000, EV_EFI_VARIABLE_DRIVER_CONFIG = 0x80000001, EV_EFI_VARIABLE_BOOT = 0x80000002, EV_EFI_BOOT_SERVICES_APPLICATION = 0x80000003, EV_EFI_BOOT_SERVICES_DRIVER = 0x80000004, EV_EFI_RUNTIME_SERVICES_DRIVER = 0x80000005, EV_EFI_GPT_EVENT = 0x80000006, EV_EFI_ACTION = 0x80000007, EV_EFI_PLATFORM_FIRMWARE_BLOB = 0x80000008, EV_EFI_HANDOFF_TABLES = 0x80000009, EV_EFI_HCRTM_EVENT = 0x80000010, EV_EFI_VARIABLE_AUTHORITY = 0x800000e0 } FuTpmEventlogItemKind; typedef struct { guint8 pcr; FuTpmEventlogItemKind kind; GBytes *checksum_sha1; GBytes *checksum_sha256; GBytes *blob; } FuTpmEventlogItem; const gchar *fu_tpm_eventlog_pcr_to_string (gint pcr); const gchar *fu_tpm_eventlog_hash_to_string (TPM2_ALG_ID hash_kind); guint32 fu_tpm_eventlog_hash_get_size (TPM2_ALG_ID hash_kind); const gchar *fu_tpm_eventlog_item_kind_to_string (FuTpmEventlogItemKind event_type); gchar *fu_tpm_eventlog_strhex (GBytes *blob); gchar *fu_tpm_eventlog_blobstr (GBytes *blob); GPtrArray *fu_tpm_eventlog_calc_checksums (GPtrArray *items, guint8 pcr, GError **error); fwupd-1.3.9/plugins/tpm-eventlog/fu-tpm-eventlog-device.c000066400000000000000000000065261362775233600234340ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "fu-tpm-eventlog-device.h" #include "fu-tpm-eventlog-parser.h" struct _FuTpmEventlogDevice { FuDevice parent_instance; GPtrArray *items; }; G_DEFINE_TYPE (FuTpmEventlogDevice, fu_tpm_eventlog_device, FU_TYPE_DEVICE) GPtrArray * fu_tpm_eventlog_device_get_checksums (FuTpmEventlogDevice *self, guint8 pcr, GError **error) { return fu_tpm_eventlog_calc_checksums (self->items, pcr, error); } static void fu_tpm_eventlog_device_to_string (FuDevice *device, guint idt, GString *str) { FuTpmEventlogDevice *self = FU_TPM_EVENTLOG_DEVICE (device); if (self->items->len > 0) { fu_common_string_append_kv (str, idt, "Items", NULL); for (guint i = 0; i < self->items->len; i++) { FuTpmEventlogItem *item = g_ptr_array_index (self->items, i); fu_tpm_eventlog_item_to_string (item, idt + 1, str); } } } gchar * fu_tpm_eventlog_device_report_metadata (FuTpmEventlogDevice *self) { GString *str = g_string_new (""); g_autoptr(GPtrArray) pcrs = NULL; for (guint i = 0; i < self->items->len; i++) { FuTpmEventlogItem *item = g_ptr_array_index (self->items, i); g_autofree gchar *blobstr = fu_tpm_eventlog_blobstr (item->blob); g_autofree gchar *checksum = fu_tpm_eventlog_strhex (item->checksum_sha1); g_string_append_printf (str, "0x%08x %s", item->kind, checksum); if (blobstr != NULL) g_string_append_printf (str, " [%s]", blobstr); g_string_append (str, "\n"); } pcrs = fu_tpm_eventlog_calc_checksums (self->items, 0, NULL); if (pcrs != NULL) { for (guint j = 0; j < pcrs->len; j++) { const gchar *csum = g_ptr_array_index (pcrs, j); g_string_append_printf (str, "PCR0: %s\n", csum); } } if (str->len > 0) g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } static void fu_tpm_eventlog_device_init (FuTpmEventlogDevice *self) { fu_device_set_name (FU_DEVICE (self), "Event Log"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_physical_id (FU_DEVICE (self), "DEVNAME=/dev/tpm0"); fu_device_set_logical_id (FU_DEVICE (self), "eventlog"); fu_device_add_parent_guid (FU_DEVICE (self), "system-tpm"); fu_device_add_instance_id (FU_DEVICE (self), "system-tpm-eventlog"); } static void fu_tpm_eventlog_device_finalize (GObject *object) { FuTpmEventlogDevice *self = FU_TPM_EVENTLOG_DEVICE (object); g_ptr_array_unref (self->items); G_OBJECT_CLASS (fu_tpm_eventlog_device_parent_class)->finalize (object); } static void fu_tpm_eventlog_device_class_init (FuTpmEventlogDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); object_class->finalize = fu_tpm_eventlog_device_finalize; klass_device->to_string = fu_tpm_eventlog_device_to_string; } FuTpmEventlogDevice * fu_tpm_eventlog_device_new (const guint8 *buf, gsize bufsz, GError **error) { g_autoptr(FuTpmEventlogDevice) self = NULL; g_return_val_if_fail (buf != NULL, NULL); /* create object */ self = g_object_new (FU_TYPE_TPM_EVENTLOG_DEVICE, NULL); self->items = fu_tpm_eventlog_parser_new (buf, bufsz, FU_TPM_EVENTLOG_PARSER_FLAG_NONE, error); if (self->items == NULL) return NULL; return FU_TPM_EVENTLOG_DEVICE (g_steal_pointer (&self)); } fwupd-1.3.9/plugins/tpm-eventlog/fu-tpm-eventlog-device.h000066400000000000000000000012001362775233600234210ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_TPM_EVENTLOG_DEVICE (fu_tpm_eventlog_device_get_type ()) G_DECLARE_FINAL_TYPE (FuTpmEventlogDevice, fu_tpm_eventlog_device, FU, TPM_EVENTLOG_DEVICE, FuDevice) FuTpmEventlogDevice *fu_tpm_eventlog_device_new (const guint8 *buf, gsize bufsz, GError **error); gchar *fu_tpm_eventlog_device_report_metadata (FuTpmEventlogDevice *self); GPtrArray *fu_tpm_eventlog_device_get_checksums (FuTpmEventlogDevice *self, guint8 pcr, GError **error); fwupd-1.3.9/plugins/tpm-eventlog/fu-tpm-eventlog-parser.c000066400000000000000000000207151362775233600234650ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "fu-tpm-eventlog-parser.h" #define FU_TPM_EVENTLOG_V1_IDX_PCR 0x00 #define FU_TPM_EVENTLOG_V1_IDX_TYPE 0x04 #define FU_TPM_EVENTLOG_V1_IDX_DIGEST 0x08 #define FU_TPM_EVENTLOG_V1_IDX_EVENT_SIZE 0x1c #define FU_TPM_EVENTLOG_V1_SIZE 0x20 #define FU_TPM_EVENTLOG_V2_HDR_IDX_SIGNATURE 0x00 #define FU_TPM_EVENTLOG_V2_HDR_IDX_PLATFORM_CLASS 0x10 #define FU_TPM_EVENTLOG_V2_HDR_IDX_SPEC_VERSION_MINOR 0x14 #define FU_TPM_EVENTLOG_V2_HDR_IDX_SPEC_VERSION_MAJOR 0X15 #define FU_TPM_EVENTLOG_V2_HDR_IDX_SPEC_ERRATA 0x16 #define FU_TPM_EVENTLOG_V2_HDR_IDX_UINTN_SIZE 0x17 #define FU_TPM_EVENTLOG_V2_HDR_IDX_NUMBER_OF_ALGS 0x18 #define FU_TPM_EVENTLOG_V2_HDR_SIGNATURE "Spec ID Event03" #define FU_TPM_EVENTLOG_V2_IDX_PCR 0x00 #define FU_TPM_EVENTLOG_V2_IDX_TYPE 0x04 #define FU_TPM_EVENTLOG_V2_IDX_DIGEST_COUNT 0x08 #define FU_TPM_EVENTLOG_V2_SIZE 0x0c static void fu_tpm_eventlog_parser_item_free (FuTpmEventlogItem *item) { g_bytes_unref (item->blob); if (item->checksum_sha1 != NULL) g_bytes_unref (item->checksum_sha1); if (item->checksum_sha256 != NULL) g_bytes_unref (item->checksum_sha256); g_free (item); } void fu_tpm_eventlog_item_to_string (FuTpmEventlogItem *item, guint idt, GString *str) { const gchar *tmp; g_autofree gchar *blobstr = fu_tpm_eventlog_blobstr (item->blob); g_autofree gchar *pcrstr = g_strdup_printf ("%s (%u)", fu_tpm_eventlog_pcr_to_string (item->pcr), item->pcr); fu_common_string_append_kv (str, idt, "PCR", pcrstr); fu_common_string_append_kx (str, idt, "Type", item->kind); tmp = fu_tpm_eventlog_item_kind_to_string (item->kind); if (tmp != NULL) fu_common_string_append_kv (str, idt, "Description", tmp); if (item->checksum_sha1 != NULL) { g_autofree gchar *csum = fu_tpm_eventlog_strhex (item->checksum_sha1); fu_common_string_append_kv (str, idt, "ChecksumSha1", csum); } if (item->checksum_sha256 != NULL) { g_autofree gchar *csum = fu_tpm_eventlog_strhex (item->checksum_sha256); fu_common_string_append_kv (str, idt, "ChecksumSha256", csum); } if (blobstr != NULL) fu_common_string_append_kv (str, idt, "BlobStr", blobstr); } static GPtrArray * fu_tpm_eventlog_parser_parse_blob_v2 (const guint8 *buf, gsize bufsz, FuTpmEventlogParserFlags flags, GError **error) { guint32 hdrsz = 0x0; g_autoptr(GPtrArray) items = NULL; /* advance over the header block */ if (!fu_common_read_uint32_safe (buf, bufsz, FU_TPM_EVENTLOG_V1_IDX_EVENT_SIZE, &hdrsz, G_LITTLE_ENDIAN, error)) return NULL; items = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_tpm_eventlog_parser_item_free); for (gsize idx = FU_TPM_EVENTLOG_V1_SIZE + hdrsz; idx < bufsz;) { guint32 pcr = 0; guint32 event_type = 0; guint32 digestcnt = 0; guint32 datasz = 0; g_autoptr(GBytes) checksum_sha1 = NULL; g_autoptr(GBytes) checksum_sha256 = NULL; /* read entry */ if (!fu_common_read_uint32_safe (buf, bufsz, idx + FU_TPM_EVENTLOG_V2_IDX_PCR, &pcr, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_uint32_safe (buf, bufsz, idx + FU_TPM_EVENTLOG_V2_IDX_TYPE, &event_type, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_uint32_safe (buf, bufsz, idx + FU_TPM_EVENTLOG_V2_IDX_DIGEST_COUNT, &digestcnt, G_LITTLE_ENDIAN, error)) return NULL; /* read checksum block */ idx += FU_TPM_EVENTLOG_V2_SIZE; for (guint i = 0; i < digestcnt; i++) { guint16 alg_type = 0; guint32 alg_size = 0; /* get checksum type */ if (!fu_common_read_uint16_safe (buf, bufsz, idx, &alg_type, G_LITTLE_ENDIAN, error)) return NULL; alg_size = fu_tpm_eventlog_hash_get_size (alg_type); if (alg_size == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "hash algorithm 0x%x size not known", alg_type); return NULL; } /* build checksum */ idx += sizeof(alg_type); if (alg_type == TPM2_ALG_SHA1 || flags & FU_TPM_EVENTLOG_PARSER_FLAG_ALL_ALGS) { g_autofree guint8 *digest = g_malloc0 (alg_size); /* copy hash */ if (!fu_memcpy_safe (digest, alg_size, 0x0, /* dst */ buf, bufsz, idx, /* src */ alg_size, error)) return NULL; /* save this for analysis */ if (alg_type == TPM2_ALG_SHA1) checksum_sha1 = g_bytes_new_take (g_steal_pointer (&digest), alg_size); else if (alg_type == TPM2_ALG_SHA256) checksum_sha1 = g_bytes_new_take (g_steal_pointer (&digest), alg_size); } /* next block */ idx += alg_size; } /* read data block */ if (!fu_common_read_uint32_safe (buf, bufsz, idx, &datasz, G_LITTLE_ENDIAN, error)) return NULL; if (datasz > 1024 * 1024) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "event log item too large"); return NULL; } /* save blob if PCR=0 */ idx += sizeof(datasz); if (pcr == ESYS_TR_PCR0 || flags & FU_TPM_EVENTLOG_PARSER_FLAG_ALL_PCRS) { FuTpmEventlogItem *item; g_autofree guint8 *data = NULL; /* build item */ data = g_malloc0 (datasz); if (!fu_memcpy_safe (data, datasz, 0x0, /* dst */ buf, bufsz, idx, datasz, /* src */ error)) return NULL; /* not normally required */ if (g_getenv ("FWUPD_TPM_EVENTLOG_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "Event Data", data, datasz, 20, FU_DUMP_FLAGS_SHOW_ASCII); } item = g_new0 (FuTpmEventlogItem, 1); item->pcr = pcr; item->kind = event_type; item->checksum_sha1 = g_steal_pointer (&checksum_sha1); item->checksum_sha256 = g_steal_pointer (&checksum_sha256); item->blob = g_bytes_new_take (g_steal_pointer (&data), datasz); g_ptr_array_add (items, item); } /* next entry */ idx += datasz; } /* success */ return g_steal_pointer (&items); } GPtrArray * fu_tpm_eventlog_parser_new (const guint8 *buf, gsize bufsz, FuTpmEventlogParserFlags flags, GError **error) { gchar sig[] = FU_TPM_EVENTLOG_V2_HDR_SIGNATURE; g_autoptr(GPtrArray) items = NULL; g_return_val_if_fail (buf != NULL, NULL); /* look for TCG v2 signature */ if (!fu_memcpy_safe ((guint8 *) sig, sizeof(sig), 0x0, /* dst */ buf, bufsz, FU_TPM_EVENTLOG_V1_SIZE, /* src */ sizeof(sig), error)) return NULL; if (g_strcmp0 (sig, FU_TPM_EVENTLOG_V2_HDR_SIGNATURE) == 0) return fu_tpm_eventlog_parser_parse_blob_v2 (buf, bufsz, flags, error); /* assume v1 structure */ items = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_tpm_eventlog_parser_item_free); for (gsize idx = 0; idx < bufsz; idx += FU_TPM_EVENTLOG_V1_SIZE) { guint32 datasz = 0; guint32 pcr = 0; guint32 event_type = 0; if (!fu_common_read_uint32_safe (buf, bufsz, idx + FU_TPM_EVENTLOG_V1_IDX_PCR, &pcr, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_uint32_safe (buf, bufsz, idx + FU_TPM_EVENTLOG_V1_IDX_TYPE, &event_type, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_uint32_safe (buf, bufsz, idx + FU_TPM_EVENTLOG_V1_IDX_EVENT_SIZE, &datasz, G_LITTLE_ENDIAN, error)) return NULL; if (datasz > 1024 * 1024) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "event log item too large"); return NULL; } if (pcr == ESYS_TR_PCR0 || flags & FU_TPM_EVENTLOG_PARSER_FLAG_ALL_PCRS) { FuTpmEventlogItem *item; guint8 digest[TPM2_SHA1_DIGEST_SIZE] = { 0x0 }; g_autofree guint8 *data = NULL; /* copy hash */ if (!fu_memcpy_safe (digest, sizeof(digest), 0x0, /* dst */ buf, bufsz, idx + FU_TPM_EVENTLOG_V1_IDX_DIGEST, /* src */ sizeof(digest), error)) return NULL; /* build item */ data = g_malloc0 (datasz); if (!fu_memcpy_safe (data, datasz, 0x0, /* dst */ buf, bufsz, idx + FU_TPM_EVENTLOG_V1_SIZE, /* src */ datasz, error)) return NULL; item = g_new0 (FuTpmEventlogItem, 1); item->pcr = pcr; item->kind = event_type; item->checksum_sha1 = g_bytes_new (digest, sizeof(digest)); item->blob = g_bytes_new_take (g_steal_pointer (&data), datasz); g_ptr_array_add (items, item); /* not normally required */ if (g_getenv ("FWUPD_TPM_EVENTLOG_VERBOSE") != NULL) fu_common_dump_bytes (G_LOG_DOMAIN, "Event Data", item->blob); } idx += datasz; } return g_steal_pointer (&items); } fwupd-1.3.9/plugins/tpm-eventlog/fu-tpm-eventlog-parser.h000066400000000000000000000012111362775233600234600ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-tpm-eventlog-common.h" typedef enum { FU_TPM_EVENTLOG_PARSER_FLAG_NONE = 0, FU_TPM_EVENTLOG_PARSER_FLAG_ALL_PCRS = 1 << 0, FU_TPM_EVENTLOG_PARSER_FLAG_ALL_ALGS = 1 << 1, FU_TPM_EVENTLOG_PARSER_FLAG_LAST } FuTpmEventlogParserFlags; GPtrArray *fu_tpm_eventlog_parser_new (const guint8 *buf, gsize bufsz, FuTpmEventlogParserFlags flags, GError **error); void fu_tpm_eventlog_item_to_string (FuTpmEventlogItem *item, guint idt, GString *str); fwupd-1.3.9/plugins/tpm-eventlog/fu-tpm-eventlog.c000066400000000000000000000074431362775233600221760ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuTpmEventlog" #include "config.h" #include #include #include #include #include #include #include "fu-tpm-eventlog-parser.h" static gint fu_tmp_eventlog_sort_cb (gconstpointer a, gconstpointer b) { FuTpmEventlogItem *item_a = *((FuTpmEventlogItem **) a); FuTpmEventlogItem *item_b = *((FuTpmEventlogItem **) b); if (item_a->pcr > item_b->pcr) return 1; if (item_a->pcr < item_b->pcr) return -1; return 0; } static gboolean fu_tmp_eventlog_process (const gchar *fn, gint pcr, GError **error) { gsize bufsz = 0; g_autofree guint8 *buf = NULL; g_autoptr(GPtrArray) items = NULL; g_autoptr(GString) str = g_string_new (NULL); /* parse this */ if (!g_file_get_contents (fn, (gchar **) &buf, &bufsz, error)) return FALSE; items = fu_tpm_eventlog_parser_new (buf, bufsz, FU_TPM_EVENTLOG_PARSER_FLAG_ALL_ALGS | FU_TPM_EVENTLOG_PARSER_FLAG_ALL_PCRS, error); if (items == NULL) return FALSE; g_ptr_array_sort (items, fu_tmp_eventlog_sort_cb); for (guint i = 0; i < items->len; i++) { FuTpmEventlogItem *item = g_ptr_array_index (items, i); if (pcr >= 0 && item->pcr != pcr) continue; fu_tpm_eventlog_item_to_string (item, 0, str); g_string_append (str, "\n"); } fu_common_string_append_kv (str, 0, "PCRs", NULL); for (guint8 i = 0; i < 10; i++) { g_autoptr(GPtrArray) pcrs = fu_tpm_eventlog_calc_checksums (items, i, NULL); if (pcrs == NULL) continue; for (guint j = 0; j < pcrs->len; j++) { const gchar *csum = g_ptr_array_index (pcrs, j); g_autofree gchar *title = g_strdup_printf ("%u", i); fu_common_string_append_kv (str, 1, title, csum); } } /* success */ g_print ("%s", str->str); return TRUE; } int main (int argc, char *argv[]) { const gchar *fn; gboolean verbose = FALSE; gboolean interactive = isatty (fileno (stdout)) != 0; gint pcr = -1; g_autoptr(GError) error = NULL; g_autoptr(GOptionContext) context = g_option_context_new (NULL); const GOptionEntry options[] = { { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: command line option */ _("Show extra debugging information"), NULL }, { "pcr", 'p', 0, G_OPTION_ARG_INT, &pcr, /* TRANSLATORS: command line option */ _("Only show single PCR value"), NULL }, { NULL} }; #ifdef HAVE_GETUID /* ensure root user */ if (argc < 2 && 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")); #endif setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); /* TRANSLATORS: program name */ g_set_application_name (_("fwupd TPM event log utility")); g_option_context_add_main_entries (context, options, NULL); g_option_context_set_description (context, "This tool will read and parse the TPM event log " "from the system firwmare."); if (!g_option_context_parse (context, &argc, &argv, &error)) { /* 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); g_setenv ("FWUPD_TPM_EVENTLOG_VERBOSE", "1", FALSE); } /* allow user to chose a local file */ fn = argc <= 1 ? "/sys/kernel/security/tpm0/binary_bios_measurements" : argv[1]; if (!fu_tmp_eventlog_process (fn, pcr, &error)) { /* TRANSLATORS: failed to read measurements file */ g_printerr ("%s: %s\n", _("Failed to parse file"), error->message); return EXIT_FAILURE; } return EXIT_SUCCESS; } fwupd-1.3.9/plugins/tpm-eventlog/meson.build000066400000000000000000000042451362775233600211400ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginTpmEventlog"'] shared_module('fu_plugin_tpm_eventlog', fu_hash, sources : [ 'fu-plugin-tpm-eventlog.c', 'fu-tpm-eventlog-common.c', 'fu-tpm-eventlog-device.c', 'fu-tpm-eventlog-parser.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupdplugin, fwupd, ], c_args : cargs, dependencies : [ plugin_deps, tpm2tss, ], ) if get_option('tests') testdatadir = join_paths(meson.current_source_dir(), 'tests') cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'tpm-eventlog-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-tpm-eventlog-common.c', 'fu-tpm-eventlog-device.c', 'fu-tpm-eventlog-parser.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], link_with : [ fwupdplugin, fwupd, ], dependencies : [ plugin_deps, tpm2tss, ], c_args : cargs ) test('tpm-eventlog-self-test', e) endif fwupdtpmevlog = executable( 'fwupdtpmevlog', sources : [ 'fu-tpm-eventlog.c', 'fu-tpm-eventlog-common.c', 'fu-tpm-eventlog-parser.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, tpm2tss, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : bindir ) if get_option('man') custom_target('fwupdtpmevlog-man', input : fwupdtpmevlog, output : 'fwupdtpmevlog.1', command : [ help2man, '@INPUT@', '--no-info', '--output', '@OUTPUT@', '--name', 'Debugging utility for the TPM event log', '--manual', 'User Commands', '--version-string', fwupd_version, ], install : true, install_dir : join_paths(mandir, 'man1'), ) endif run_target('fuzz-tpm-eventlog', command: [ join_paths(meson.source_root(), 'contrib/afl-fuzz.py'), '-i', join_paths(meson.current_source_dir(), 'tests'), '-o', join_paths(meson.current_build_dir(), 'findings'), fwupdtpmevlog, ], ) fwupd-1.3.9/plugins/tpm-eventlog/tests/000077500000000000000000000000001362775233600201335ustar00rootroot00000000000000fwupd-1.3.9/plugins/tpm-eventlog/tests/binary_bios_measurements-v1000066400000000000000000000243041362775233600254750ustar00rootroot00000000000000#$8Tzx\N1EET86W 5CMLi暏͓aD:)uL%ʾPsy10 Yfo*]C٫?߁#DWMBGZ'CHO;B5aʓ + SecureBoot0nwV6fQn$aʓ +PKAp!g˔X&aʓ +KEK[x33)$˲:=EgeodbsD$ Ls;^&˲:=EgeodbxixE (QsC>RRsRh^q<P̫3LH@ ,",JF]֗x1ͦ(IN>ABlA߉#\1޵A$/L+I,",JF]֗xYF͖Hˁc4Ĉs LH>,",JF]֗x B/ê̸bhF8ciLH ,",JF]֗x OGz{1:_VDOf9OT WszL@H`,,",JF]֗xB؁&4 M6(QurQLzH@,",JF]֗xc:JhugT9g( Tfsa5L@H@/,",JF]֗xW|~O~-gӌB&AVgFLЎH;,",JF]֗x[A9mʁ*M*AEo-4e LenovoConfigB<>!|^1nׄO LAӝILenovoSecurityConfig @I>UœLy 1--'?M NQ:_\S J0Jaʓ + BootOrder 9דjRcJ_\aʓ +,Boot0001tWindows Boot Manager* 6vBVF\EFI\Microsoft\Boot\bootmgfw.efiWINDOWSxBCDOBJECT={9dea862c-5cdd-4e70-acc1-f32b344d4795}a0q~Ɗc{Yaʓ +vBoot0000bFedora*@10"`H$+N44\EFI\fedora\shimx64.efiK =7m8URlaʓ +<Boot0017(USB CD $8x`Mhy[pZxHlԝӺjUS`|_%mMXhTnaʓ +>Boot0018(USB FDD $8x`Mhy[o0Cd FI&'CfH9O Zkaʓ +;Boot0019)NVMe0 %8x`Mhy[2LN鎸P=Y-OoNtܿjtg2 kaʓ +;Boot001A)NVMe1 %8x`Mhy[2LN鎸uڡ OQ'9F(`qaʓ +ABoot001B)ATA HDD2 %8x`Mhy[bYVDAO*u[,Ay#3qaʓ +ABoot001C)ATA HDD3 %8x`Mhy[bYVDAO*Zl:LRqaʓ +ABoot001D)ATA HDD0 %8x`Mhy[bYVDAO*Ʃ/ h1 >Boot001F(USB HDD $8x`Mhy[3!3GAȩG<9O[) Unaʓ +>Boot0020(PCI LAN $8x`Mhy[xJ+*N̏=8E1A'S&7V(Calling EFI Application from Boot OptionixE (QsC>RRsixE (QsC>RRsixE (QsC>RRsixE (QsC>RRsixE (QsC>RRsixE (QsC>RRsixE (QsC>RRsgQXb\0徿Х;iKEFI PART\O1;"; rhBNZ:c(s*K>;10"`H$+N4GEFI System Partition=rGy=iG}[pr-|NSHG&=rGy=iG}m18]@EFH&GmWC 3KOOQ CQ{zeH?\=rGy=iG}yUSjMLp@\;r]kA%CEy A  !z*@10"`H$+N44\EFI\fedora\shimx64.efi׎<O \-)"" Џ1​Xuɉgrub_cmd set pager=1 ѩCCkuF0].grub_cmd [ -f (hd2,gpt1)/EFI/fedora/grubenv ] G;(ޡӋ91=13grub_cmd load_env -f (hd2,gpt1)/EFI/fedora/grubenv Kv;BhӽOgrub_cmd [ ] 3dJ>N^?>wMgrub_cmd set default=5a1a3ae8d9d34584935a7c12f0c7ac70-5.3.12-300.fc31.x86_64 > 8O-C~\PnVgrub_cmd [ xy = xy ] ~b)_+mߖʇ]c"grub_cmd menuentry_id_option=--id 3.C$grub_cmd export menuentry_id_option Kv;BhӽOgrub_cmd [ ] c"0̟yR.!grub_cmd terminal_output console > 8O-C~\PnVgrub_cmd [ xy = xy ] ^6(Q#Dk grub_cmd set timeout_style=menu qL :Ua3I 8guigrub_cmd set timeout=5 71x* ^E6ˉgrub_cmd insmod increment <^ %8Ld남grub_cmd [ -n -a 1 = 0 ] ]8F!.(^惧 ORݪgrub_cmd [ 1 = 1 -o 0 = 1 ] PO3%U ~˖<grub_cmd set last_boot_ok=1 )s4Mӑ 8O-C~\PnVgrub_cmd [ xy = xy ] Kv;BhӽOgrub_cmd [ ] StsSHƶ.grub_cmd [ 1 -a 1 = 1 ] (L{=#Fu%grub_cmd set orig_timeout_style=menu yG:4خ/KRgrub_cmd set orig_timeout=5 `[bM@ ,Qgrub_cmd [ = 1 ] wX@vI|{ǎA"grub_cmd set timeout_style=hidden };7K=`Rgxgrub_cmd set timeout=1 o9a*PΗ.ѿ/grub_cmd [ -f (hd2,gpt1)/EFI/fedora/user.cfg ] *M%3_0Vzgrub_cmd insmod part_gpt m7u5ITI#P.;=1@grub_cmd insmod ext2 > 8O-C~\PnVgrub_cmd [ xy = xy ] AHjO ypnU?0Vgrub_cmd search --no-floppy --fs-uuid --set=root c80fb3c4-2fd9-4503-9ebf-1f57305a29f4 *M%3_0Vzgrub_cmd insmod part_gpt 9E:@AVgrub_cmd insmod fat > 8O-C~\PnVgrub_cmd [ xy = xy ] vkS=n7Q ;grub_cmd search --no-floppy --fs-uuid --set=boot 8130-852B Ҏr=GTY>grub_cmd set default_kernelopts=root=UUID=f6af2f01-aeda-4a22-92db-f899fea6df51 ro resume=UUID=2a16ee05-78c0-46a7-b7f5-30add9ad6c44 rhgb quiet  M}N3<_Ngrub_cmd insmod blscfg ~JI%~Zq\igrub_cmd blscfg bxہԊkhv{b.grub_cmd [ -s (hd2,gpt1)/EFI/fedora/grubenv ] ~y^eR X݀Fgrub_cmd load_env pTɬbVDgrub_cmd menuentry Windows Boot Manager (on /dev/sda2) --class windows --class os --id osprober-efi-1C0D-118C { insmod part_gpt insmod fat set root='hd0,gpt2' if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 1C0D-118C else search --no-floppy --fs-uuid --set=root 1C0D-118C fi chainloader /EFI/Microsoft/Boot/bootmgfw.efi } :&JrJiqMxgrub_cmd [ menu -a 1 != 2 ] ^6(Q#Dk grub_cmd set timeout_style=menu qL :Ua3I 8guigrub_cmd set timeout=5 * 0?E-H@grub_cmd menuentry System setup --id uefi-firmware { fwsetup } exnݓfi1grub_cmd [ -f (hd2,gpt1)/EFI/fedora/custom.cfg ] fP2}#oMgrub_cmd [ -z (hd2,gpt1)/EFI/fedora -a -f (hd2,gpt1)/EFI/fedora/custom.cfg ] WK$#>20`Ogrub_cmd load_video > 8O-C~\PnVgrub_cmd [ xy = xy ] {BpH^hO|grub_cmd insmod all_video  ߭5Iƥ ȑgrub_cmd set gfx_payload=keep ErP$K6ދ$grub_cmd insmod gzio Q˜ɚhHr grub_cmd linux (hd2,gpt2)/vmlinuz-5.3.12-300.fc31.x86_64 root=UUID=f6af2f01-aeda-4a22-92db-f899fea6df51 ro resume=UUID=2a16ee05-78c0-46a7-b7f5-30add9ad6c44 rhgb quiet \z{[d9,g¿egrub_linuxefi Kernel?n`in ׂ~y)GI 3ա@6x1jQȬgrub_kernel_cmdline (hd2,gpt2)/vmlinuz-5.3.12-300.fc31.x86_64 root=UUID=f6af2f01-aeda-4a22-92db-f899fea6df51 ro resume=UUID=2a16ee05-78c0-46a7-b7f5-30add9ad6c44 rhgb quiet gpϔyʚ3ˡzZZf@grub_cmd initrd (hd2,gpt2)/initramfs-5.3.12-300.fc31.x86_64.img )x6 @hgrub_linuxefi Initrdfwupd-1.3.9/plugins/tpm-eventlog/tests/binary_bios_measurements-v2000066400000000000000000001165671362775233600255130ustar00rootroot00000000000000%Spec ID Event03 ΎG Ҵi^< ^6qtI2|%ebݔKоBoot Guard Measured S-CRTM#ܧ)>23EP) ${0Wڣ]Ÿ~A xc .06r ?G*izApvc}NAPP@GAߗ1oD.r nx/ uϩacvV_|h}5 WI;Y@Gn2 U^ + (s"aXR5I ΅NQ!5x=@G=c%b# n 2%@Ïтߡi]'TQ̪N@G@H>EzfpktX tcyiDVޏc` :I6@G -'!}) +nx Uq [.WJzB[~}{@GOx9 ,/2T =[ſ7BlvyyҢ`GP넓pr4gvQ BrK3*l̩;X׸-< G m WP3 [2^d +{,•fQTFGT}%mYRbUEFI Debug ModeM@AIMɐSC'} K(EںU+b}4vv1A@5aʓ + SecureBootn^ ŏY *ZeA/-@=!'aʓ +PKY䔧J\+rMVpNI\00P綼@-xh0  *H  0g1 0 UUS10 U Texas10U Round Rock10U Dell Inc.10U Dell Inc. Platform Key0 160601202007Z 310601203006Z0g1 0 UUS10 U Texas10U Round Rock10U Dell Inc.10U Dell Inc. Platform Key0"0  *H 0  :@gX#D D6mKXN,"[.Ŝ(}p'8κ$® 7S6@vvd'&? E)x7^9V136Ә9'gcXDHi_TOD4zW,3JN]=ʤdJ<&{i_C_9 $q޽Bs<>TuSIT dqGo.pC7od^cqCE0C0U0U00UFo RV9HHu0  *H  RyrIi%\j2t)iF`Rz3 hs8äggqhq@nYR R'l҉4Y*5&ܹ70(wS._@6cTc2yب&*k(&Zʿ`9k|Ӯ7R(;7 ~ 1ױ@3y$,C[B,llH#8>UI9̲e>$E[(: Kl!Uî.__9c? c%KBkByO-2r#0 aʓ + KEKY䔧J\+rMVpNI\00'R]L6wB0  *H  0g1 0 UUS10 U Texas10U Round Rock10U Dell Inc.10U Dell Inc. Platform Key0 160601202248Z 230601203247Z0k1 0 UUS10 U Texas10U Round Rock10U Dell Inc.1#0!U Dell Inc. Key Exchange Key0"0  *H 0 Ѝ;A)LBf-3I}c␰f0[ Ug[U{̶_0 +7  SubCA0 U0U00U#0EfRC~XN#U;:"j0\UU0S0QOMKhttp://crl.microsoft.com/pki/crl/products/MicCorThiParMarRoo_2010-10-05.crl0`+T0R0P+0Dhttp://www.microsoft.com/pki/certs/MicCorThiParMarRoo_2010-10-05.crt0  *H  Ԅ*<* נRfuz-vZy7jQ{ddgxΈXd W_iHK2]0x+4VʮA%pkז* K(){|vyo~l{E4Q9^VBwqV̟#˦X~ig~ <νC-j+Z|DR-R=`3e |N8/ o.9'B)FA;gCYe Ou;$PA@y-O j'vnRi{E­S076aJi4hl l"yF`!y2`ج"KK}?W5Ou`"Sy֛ATp 5|4r`;y뢲]%o8yi uk4`\WN625oJANK>} Xo+piaT<}͚"˲:=EgeodbY䔧J\+rMVpNI\00mԀO90  *H  0i1 0 UUSA1 0 U TX10U Round Rock10U Dell Inc.1#0!U Dell Bios Key Exchange Key0 180809230436Z 280809231436Z0_1 0 UUSA1 0 U TX10U Round Rock10U Dell Inc.10U Dell Bios DB Key0"0  *H 0 Gs ={-TYCV(>$OY#/= FmFe#q̩dzQskXnY )Gb`iUHFA.qnD]jfdTF4FZഀ)74/\7v-3t&BZYʺ\%f"QVlBi\Q RHQxǀZy<junQ;iR0P0U0U#0 E}:x~ZVjq?_0UcDqUpqAT0  *H  w$mY~T<٪Y5kZa+ fQ VNṈ]kR &O_q9̇߈";pL9_ Yv1SElv}W}ߥYRtIJIʴ/ ck>kj' } 2S<ߚ䓕/0;kc\'7C&Mxo$`e")09әګz--<3Y䔧J\+rMVpNI\00"XM{UL0  *H  0i1 0 UUSA1 0 U TX10U Round Rock10U Dell Inc.1#0!U Dell Bios Key Exchange Key0 181211215923Z 281211220923Z0n1 0 UUSA1 0 U TX10U Round Rock10U Dell Inc.1(0&U Dell Bios FW Aux Authority 20180"0  *H 0 ˑ<1bNQo@P1ʺ-Q$uwD!Х8DG UlI: $ 'h 2I9&=QtˆC'"k \T*Ws_U t.Z;qOقi\g~t ^8EP8Uҽ %c0~[Nֵ) ;z5U;NVIW;g RMXNG,"-?Aqέ_R0P0U0U#0 E}:x~ZVjq?_0UM~Zwy{k0  *H  i#g$vI ~nѪSenX^AvK`g8G C a Oj6:Bf`http://www.microsoft.com/pki/certs/MicRooCerAut_2010-06-23.crt0  *H  |qQyn9>\` QfG=*hwLb{Ǻz4KbzJ7-W|=ܸZij:ni!7ށugӓW^)9-Es[zFX^gl5?$5 uVx,Јߺ~,c#!xlX6+̤-@EΊ\k>p* j_Gc 26*pZBYqKW~!<ŹE ŕ]b֠c uw}=EWo3wbY~Y䔧J\+r@$wY2M`(xK00 a0  *H  01 0 UUS10U Washington10URedmond10U Microsoft Corporation1;09U2Microsoft Corporation Third Party Marketplace Root0 110627212245Z 260627213245Z01 0 UUS10U Washington10URedmond10U Microsoft Corporation1+0)U"Microsoft Corporation UEFI CA 20110"0  *H 0 lLE jK u CTd} s JEa-+MIA#/^Pƍ_A. lui!Mڭ,wS%27lRr5aj;PV2-B'UZ0TG%/&A\?[<>?GrU%"{*F 5'bq'Y7`8xpLEe¶~iuYXC5.Ao?՘UM IƢpf]l8 `Ef [XM˲:=Egeo'dbxY䔧J\+r0k0S a j0  *H  01 0 UUS10U Washington10URedmond10U Microsoft Corporation1200U)Microsoft Root Certificate Authority 20100 100706204023Z 250706205023Z0y1 0 UUS10U Washington10URedmond10U Microsoft Corporation1#0!UMicrosoft Windows PCA 20100"0  *H 0 y:d*usл> 䱟Ȕ8|3U#f.(HSQ~&tYƢRpZ,}fobnmK/5lޤjcZ_ Ma ~1l M8FsivUiL4뺱)~OrXbVldwFe)L#_o,r(k>|yO~:p'k (smT(Lk]]37%a4jB|:YBm:P[H00 +70UO$y70 +7  SubCA0 U0U00U#0Vˏ\bh=[Κ0VUO0M0KIGEhttp://crl.microsoft.com/pki/crl/products/MicRooCerAut_2010-06-23.crl0Z+N0L0J+0>http://www.microsoft.com/pki/certs/MicRooCerAut_2010-06-23.crt0U 00 +7.00=+1http://www.microsoft.com/PKI/docs/CPS/default.htm0@+042 Legal_Policy_Statement. 0  *H  .Ao;9”pdVmoSZFlTV<qQT~B3 $$ܖ9Vs~P#V$ʻ/'硶 bAHtP)+)y$.@_3T]@bMHnAGRW:V&`ubE>[D#c%6wo["#jAB%{Җ7b95[Ì7_Sѣ7Okv丏u,AYmNNrVH#шO p_kr Iw ]8B7P)ƚ%U@ʭd1Ty֫3&maI%‹tݔ3BPPb즂|f왰X]aљ/I>>   |Zɕχ ]r}*81(LRj}q  p?VL&LP@A6C(0wY2M`(xKi1 ORm@`MAe wY2M`(xK/֒r($E4[$k;}nzwY2M`(xKء- *o.s >d,NgyjwY2M`(xK63M. xbdYWC&`HXšvwY2M`(xK세Kle qR0! b h2۲ '%'߶=IҕrLwY2M`(xK^T`< k覃R8wY2M`(xKƨXdoy(#g+69ОwY2M`(xK _NQxmЁ%orxRYe&wY2M`(xK Cڬz0eu1{ 될ctwY2M`(xK 9v-6=cqZ9ϰF\`lk׽wY2M`(xK o)o3}rK H:*?OwY2M`(xK !Hʃ62u> [1R*[wY2M`(xKoN0;t􀠀Ѐ+ot!hwY2M`(xKN: [CƦ@O4=9bgΔ.#ڒ wY2M`(xK34)bퟗ>H-.ImTdwY2M`(xK+&B.6_K 'lKzoD/ki9wY2M`(xK+,'R*]IZ+R]fbUwY2M`(xK,s3%mԤ<[UYPPR}wY2M`(xK.pgsQpW2.#ӹ+Q}wY2M`(xK0f(Tw0W(JF}8zTiv^uҍwY2M`(xK6Awz/^g4g^Ù^i5 ҽwY2M`(xK8A!6\ !`9MlN g`b[wY2M`(xK?Λ>TR^·mt:syqUpj>swY2M`(xKCʃc| C-/&zKuwY2M`(xKGa':k,Zmk6!h,*Z߽wY2M`(xKQ1s>!"Ty 0a5wY2M`(xKZIU9[.B,/gg6A+\wY2M`(xKkxA{^`Gr̴/fwY2M`(xKlTGYQ&l+585rѓ.wY2M`(xKo(qկ.{˫d|eͶ& :x^wY2M`(xKqo"I~TFb$ whٿcuwY2M`(xKrk>Tj0=ppq-ĝ,#wY2M`(xKrg]V;ݼ2ت^/m(ؽwY2M`(xKx'6,q}䱿CqZH[ʤKŽwY2M`(xKeӇk)T̕SϪȣ;3佚wY2M`(xK;δCΝч͛YA=Xo+V7W_gwY2M`(xKZ~OG q"8b:ߒ=wY2M`(xKHY jagznFdr!YEwY2M`(xK4͐e;=<5P_{c!wY2M`(xK se(Q$Q?eYW5)@νwY2M`(xK5g+6~OIia]JlrMBwY2M`(xK,";VB\GYG8DoYwY2M`(xKn=)t=J2@ؽwY2M`(xKcOx,7`XbfnmwY2M`(xKϲ2.KmH],qgrRY\u"6wY2M`(xKaJ~UәnE AR'[wY2M`(xKU =HZ7?=|cwY2M`(xKw ^; b x  S^ˇ k/wY2M`(xK<9"`tFu7̔ܭZ˦G/4q9脽wY2M`(xK;S> #Aryę-æ6wY2M`(xKQ3@HΈrRjRç`IwY2M`(xKdW[x.V4Rk DxYuN-dEwY2M`(xKEȮu ϻH7R}ddMؑ<͊$MigߎixE (QsC>RRs ?a/@W-=HwRI$ #e#GPQ~ y] aWgcɪ7@Y'Ff Dell Configuration Information 1 tớuCY NO?cv-uؘe6C2,p Dell Configuration Information 1 6:ܮVȩL `Y`|T o'bGtnvRF Dell Configuration Information 2  ";]vEٱtuapp W'lE;uRVi/At1 ? boD|˟yV aPH5E/r J.c 5fcV} R)D2K\pu}.V-k0ALL[,+u`LV]LUqw7&)IY!p?r9~_jAH 4 D epS3(x k3B޿0 _q:aʓ + BootOrderHN{TWR%i ILq(Xmdu5 Ta4Caʓ +vBoot0005bubuntu*@}彳zBWcll4\EFI\ubuntu\shimx64.efiEm6n'ÚFq%? LV&XkFp{ s\aʓ +,Boot0004tWindows Boot Manager*@}彳zBWcllF\EFI\Microsoft\Boot\bootmgfw.efiWINDOWSxBCDOBJECT={9dea862c-5cdd-4e70-acc1-f32b344d4795}*,F?]2z.5 U] M*TD aYT:}<2aʓ +Boot0000UEFI PC601 NVMe SK hynix 512GB SI94N052311304S07 1 A .1*@}彳zBWcll0\EFI\Boot\BootX64.efiNYMR,Y+E]䲴خ ni RF]M;i6 SetupE1A'S&7V =grNu*,L_rPo*ƝV3(Calling EFI Application from Boot OptionixE (QsC>RRs ?a/@W-=HwRI$ixE (QsC>RRs ?a/@W-=HwRI$ixE (QsC>RRs ?a/@W-=HwRI$ixE (QsC>RRs ?a/@W-=HwRI$ixE (QsC>RRs ?a/@W-=HwRI$ixE (QsC>RRs ?a/@W-=HwRI$ixE (QsC>RRs ?a/@W-=HwRI$XfL g A' MJ,t;۵u6`C.H˲:=Egeo$dbwY2M`(xK00 a0  *H  01 0 UUS10U Washington10URedmond10U Microsoft Corporation1;09U2Microsoft Corporation Third Party Marketplace Root0 110627212245Z 260627213245Z01 0 UUS10U Washington10URedmond10U Microsoft Corporation1+0)U"Microsoft Corporation UEFI CA 20110"0  *H 0 lLE jK u CTd} s JEa-+MIA#/^Pƍ_A. lui!Mڭ,wS%27lRr5aj;PV2-B'UZ0TG%/&A\?[<>?GrU%"{*F 5'bq'Y7`8xpLEe¶~iuYXCrʹM#mv 75ARqfϜtl[ s}rdEFI PART\_n-;";`K/4kL(s*K>;}彳zBWcll@EFI system partition\ M}-.sN-{!J"@DMicrosoft reserved partition3Dh&hibWM FiD4#Basic data partition=rGy=iG}fJ[nCLx4#;90J_4gs+> LW >!f>-# X3#6ت`[ ^ A .1*@}彳zBWcll4\EFI\ubuntu\shimx64.efi$督,lF t eb%HOɋK6:?uF)`p]zD('Rfl pܠ@>.g+C00U *#eZ&4Zc0U#0 *#eZ&4Zc0U00 U0CU<0:08642http://www.canonical.com/secure-boot-master-ca.crl0  *H  ?}v+zmRPGwҮW2:UVv Qۚ\?sڔj8m9qtv>V#5UG[AL b s^ֵz~>~f[9HQS1S;upLF=hG}QĚϣ]풻3Qs fm'wBj \\u y+C,= MNsMp[d"LW_e}W4(hd0,gpt1)/EFI/ubuntu/grub.cfg 49ɰZsr p/ǔiImMEͼJɂ6Cgrub_cmd: search.fs_uuid 1f5f8d86-98e7-4c93-ab73-891051ab9a59 root FzO iHҠEՎ J`i"dA^XY;$ +.6*grub_cmd: set prefix=(hd0,gpt4)/boot/grub `0_QOReHGj ^d=X O5Na\ V'_ ,(hd0,gpt4)/boot/grub/x86_64-efi/command.lst e\z1N!Gbb 2] ~ 1~GU9zECܡlW'(hd0,gpt4)/boot/grub/x86_64-efi/fs.lst ҁ9R2: vo8I'{ 6>w'.uC+(hd0,gpt4)/boot/grub/x86_64-efi/crypto.lst f&K Ӕ(ꔓ F/6-`Bj1JZjCb%ߜ-(hd0,gpt4)/boot/grub/x86_64-efi/terminal.lst g(_q)9&J WԞ= Br>.:j/>>=O2gN3grub_cmd: configfile (hd0,gpt4)/boot/grub/grub.cfg 3W 蛝x '6׈:;i<F<#Eǣ(hd0,gpt4)/boot/grub/grub.cfg QЪX*\ tg f!bZ˰[~a)y#.grub_cmd: [ -s (hd0,gpt4)/boot/grub/grubenv ] ~U1dZBD^nM .B4QQ5k{߆V?(hd0,gpt4)/boot/grub/grubenv k :@&u}6\uo2>uѼXgrub_cmd: set default=0 g?go複 Eh6X1-dZ40/B7R!]iYgrub_cmd: [ xy = xy ] R e*'~C# }MKbFc,!!%\46U#grub_cmd: menuentry_id_option=--id bHY x̽/A J7 ;y}L^2+RGkPm^%grub_cmd: export menuentry_id_option q #|A2Hǰ ΁$ G3DmynD: egrub_cmd: [ ] g?go複 Eh6X1-dZ40/B7R!]iYgrub_cmd: [ xy = xy ] v,$s6x: ;:~1,A~JQK93grub_cmd: font=unicode &G< 2gmӊĤ j~=K^ơMiޔ` Km8}grub_cmd: loadfont unicode q G"c D{@-Pm f1W)Ú𠵟 `wvBT  .-'(hd0,gpt4)/boot/grub/fonts/unicode.pf2 EaYC, UI gmHf -(O`#õT6kgrub_cmd: set gfxmode=auto f G$)SD v&ؾtBu6J>:;Sz VX ݐmPlqNgrub_cmd: insmod gfxterm 2UgaELd}\& YbL}2LS%&F ğA5grub_cmd: set locale_dir=(hd0,gpt4)/boot/grub/locale S5؟֮lO弚 7j徔ߕʂYWؙ< ngrub_cmd: set lang=en_US Tv<̣-5\ r#rp+*ܳr%)7/grub_cmd: insmod gettext 8Rg#Cbcisn7ȅ ?=ȠDCk&N/t"AEXp],"grub_cmd: terminal_output gfxterm E+ (]8 NCac(] Z9`{, sz8+grub_cmd: [ = 1 ] g?go複 Eh6X1-dZ40/B7R!]iYgrub_cmd: [ xy = xy ] Xy^;k9֍۩XG aʥOK?7Zt$NNގ ` _#grub_cmd: set timeout_style=hidden fHS[pd3* F\9%ZuA_Zg6c_grub_cmd: set timeout=10  WTZZ]P&'-cg |ڕVrE䀞e)},grub_cmd: set menu_color_normal=white/black ԥT ُ$:e7 oyĻL MɄ ;F>^DBF4*4grub_cmd: set menu_color_highlight=black/light-gray @YXG# .&. Wh4̟nglZZVHbjUl1&grub_cmd: background_color 79,25,76,0 .+4qϧו :L2Up%]z"#DQgrub_cmd: clear B2 K2f 1J,^sI07Agrub_cmd: [ != 1 ] 7Zցb) 1a\((7ervHIR;T7grub_cmd: [ -e (hd0,gpt4)/boot/grub/gfxblacklist.txt ]  Ҭtpv4c z(Y&Oy8_?b5@Fh +:grub_cmd: hwmatch (hd0,gpt4)/boot/grub/gfxblacklist.txt 3 kϩɕ*Kf@- mygt `\[K1/$4Igrub_cmd: [ = 0 ] hz'<^(6MbMԏ} IauBDL:Zq ;q  "grub_cmd: set linux_gfx_mode=keep DNYנa_%b& "A%N2p$WY>ӷzd}i`E grub_cmd: export linux_gfx_mode }`J fmNA؏>¹ 69+ +bb~@BGðhgrub_cmd: menuentry Ubuntu --class ubuntu --class gnu-linux --class gnu --class os --id gnulinux-simple-1f5f8d86-98e7-4c93-ab73-891051ab9a59 { recordfail load_video gfxmode $linux_gfx_mode insmod gzio if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_gpt insmod ext2 if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 else search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 fi linux /boot/vmlinuz-5.3.0-19-generic root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro quiet splash $vt_handoff initrd /boot/initrd.img-5.3.0-19-generic } 5fI=Ҥ̬n> %o (H{(Q:peCBHgrub_cmd: submenu Advanced options for Ubuntu --id gnulinux-advanced-1f5f8d86-98e7-4c93-ab73-891051ab9a59 { menuentry 'Ubuntu, with Linux 5.3.0-19-generic' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.3.0-19-generic-advanced-1f5f8d86-98e7-4c93-ab73-891051ab9a59' { recordfail load_video gfxmode $linux_gfx_mode insmod gzio if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_gpt insmod ext2 if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 else search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 fi echo 'Loading Linux 5.3.0-19-generic ...' linux /boot/vmlinuz-5.3.0-19-generic root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro quiet splash $vt_handoff echo 'Loading initial ramdisk ...' initrd /boot/initrd.img-5.3.0-19-generic } menuentry 'Ubuntu, with Linux 5.3.0-19-generic (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.3.0-19-generic-recovery-1f5f8d86-98e7-4c93-ab73-891051ab9a59' { recordfail load_video insmod gzio if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_gpt insmod ext2 if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 else search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 fi echo 'Loading Linux 5.3.0-19-generic ...' linux /boot/vmlinuz-5.3.0-19-generic root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro recovery nomodeset echo 'Loading initial ramdisk ...' initrd /boot/initrd.img-5.3.0-19-generic } menuentry 'Ubuntu, with Linux 5.3.0-18-generic' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.3.0-18-generic-advanced-1f5f8d86-98e7-4c93-ab73-891051ab9a59' { recordfail load_video gfxmode $linux_gfx_mode insmod gzio if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_gpt insmod ext2 if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 else search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 fi echo 'Loading Linux 5.3.0-18-generic ...' linux /boot/vmlinuz-5.3.0-18-generic root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro quiet splash $vt_handoff echo 'Loading initial ramdisk ...' initrd /boot/initrd.img-5.3.0-18-generic } menuentry 'Ubuntu, with Linux 5.3.0-18-generic (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.3.0-18-generic-recovery-1f5f8d86-98e7-4c93-ab73-891051ab9a59' { recordfail load_video insmod gzio if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_gpt insmod ext2 if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 else search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 fi echo 'Loading Linux 5.3.0-18-generic ...' linux /boot/vmlinuz-5.3.0-18-generic root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro recovery nomodeset echo 'Loading initial ramdisk ...' initrd /boot/initrd.img-5.3.0-18-generic } menuentry 'Ubuntu, with Linux 5.3.0-rc6+' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.3.0-rc6+-advanced-1f5f8d86-98e7-4c93-ab73-891051ab9a59' { recordfail load_video gfxmode $linux_gfx_mode insmod gzio if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_gpt insmod ext2 if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 else search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 fi echo 'Loading Linux 5.3.0-rc6+ ...' linux /boot/vmlinuz-5.3.0-rc6+ root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro quiet splash $vt_handoff echo 'Loading initial ramdisk ...' initrd /boot/initrd.img-5.3.0-rc6+ } menuentry 'Ubuntu, with Linux 5.3.0-rc6+ (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.3.0-rc6+-recovery-1f5f8d86-98e7-4c93-ab73-891051ab9a59' { recordfail load_video insmod gzio if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_gpt insmod ext2 if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 else search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 fi echo 'Loading Linux 5.3.0-rc6+ ...' linux /boot/vmlinuz-5.3.0-rc6+ root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro recovery nomodeset echo 'Loading initial ramdisk ...' initrd /boot/initrd.img-5.3.0-rc6+ } menuentry 'Ubuntu, with Linux 5.3.0-rc4+' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.3.0-rc4+-advanced-1f5f8d86-98e7-4c93-ab73-891051ab9a59' { recordfail load_video gfxmode $linux_gfx_mode insmod gzio if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_gpt insmod ext2 if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 else search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 fi echo 'Loading Linux 5.3.0-rc4+ ...' linux /boot/vmlinuz-5.3.0-rc4+ root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro quiet splash $vt_handoff echo 'Loading initial ramdisk ...' initrd /boot/initrd.img-5.3.0-rc4+ } menuentry 'Ubuntu, with Linux 5.3.0-rc4+ (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.3.0-rc4+-recovery-1f5f8d86-98e7-4c93-ab73-891051ab9a59' { recordfail load_video insmod gzio if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_gpt insmod ext2 if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 else search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 fi echo 'Loading Linux 5.3.0-rc4+ ...' linux /boot/vmlinuz-5.3.0-rc4+ root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro recovery nomodeset echo 'Loading initial ramdisk ...' initrd /boot/initrd.img-5.3.0-rc4+ } } \$-7Hw7~Jdž3 Nq'{;]/s&?jgrub_cmd: menuentry Windows Boot Manager (on /dev/nvme0n1p1) --class windows --class os --id osprober-efi-64E4-B39F { insmod part_gpt insmod fat if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root 64E4-B39F else search --no-floppy --fs-uuid --set=root 64E4-B39F fi chainloader /EFI/Microsoft/Boot/bootmgfw.efi }  +#LAnN V.JuםXE tQ/} !grub_cmd: set timeout_style=menu cY߽ . n :g:]7Xm5#n[Ngrub_cmd: [ 10 = 0 ] \ K+ 6$} O -⿢x.`\VԳ]^U[0SKAgrub_cmd: menuentry System setup --id uefi-firmware { fwsetup } h≂!XJhs c~g)/`ڱNP41grub_cmd: [ -f (hd0,gpt4)/boot/grub/custom.cfg ] G֪:.v{L# lң DDVLX'ʶKLgrub_cmd: [ -z (hd0,gpt4)/boot/grub -a -f (hd0,gpt4)/boot/grub/custom.cfg ] jejH; ]fx ,Ea-_Jgrub_cmd: setparams Ubuntu fF8魧) ~~(nNe@:8'{ gL.grub_cmd: recordfail p6b 6hDt d[׆HIf Pu Y@grub_cmd: set recordfail=1 UF>4>cQ ϤgouGzf3>4ٳ`Ygrub_cmd: [ -n true ] _kqmx5aD_e Nz"kF}iuvֹ)0e\e0`grub_cmd: [ -z ] ^|ē|GSQi ,w;[f!cëp4MK_@grub_cmd: save_env recordfail f G$)SD v&ؾtBu6J>:;S?똢ogrub_cmd: set gfxpayload=keep RAߥy] _BKU ]^j5lN;fgrub_cmd: [ keep = keep ] n*~ k ;6oaq"|s(&grub_cmd: set vt_handoff=vt.handoff=7 Ph lFtRgbT nǔėgrub_cmd: insmod gzio -EݺFv2[ui Th佟 ݡ3_@]ߍosMgrub_cmd: [ xefi = xxen ] y|A!p#A bv t.FFݐa'WYGgrub_cmd: insmod part_gpt ?\V/ 2 8҆ z eY ; 1 grub_cmd: insmod ext2 g?go複 Eh6X1-dZ40/B7R!]iYgrub_cmd: [ xy = xy ] "͐=Yȑz >J QަJ5 JH9 R/WgƇi1Wgrub_cmd: search --no-floppy --fs-uuid --set=root 1f5f8d86-98e7-4c93-ab73-891051ab9a59 -uF5,2qz /u<0" _%TeOHzY{grub_cmd: linux /boot/vmlinuz-5.3.0-19-generic root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro quiet splash vt.handoff=7 O@2d4av 5yu}Mw 1Fj00IB(/boot/vmlinuz-5.3.0-19-generic0Ϳ͏wDc6WD 4"+ʮJN#lPYrz,/tI)ɭ˭]zD('Rfl pܠ@>.g+C00U *#eZ&4Zc0U#0 *#eZ&4Zc0U00 U0CU<0:08642http://www.canonical.com/secure-boot-master-ca.crl0  *H  ?}v+zmRPGwҮW2:UVv Qۚ\?sڔj8m9qtv>V#5UG[AL b s^ֵz~>~f[9HQS1S;upLF=hG}QĚϣ]풻3Qs fm'wBj  Gy 3Oh( 1ԬB(A.J=&,E@w:;M{kernel_cmdline: /boot/vmlinuz-5.3.0-19-generic root=UUID=1f5f8d86-98e7-4c93-ab73-891051ab9a59 ro quiet splash vt.handoff=7 Cx=,CuCIa7! ݅ʎ>%h<}p83grub_cmd: initrd /boot/initrd.img-5.3.0-19-generic {=' fh 'oA`S|/!~`"/boot/initrd.img-5.3.0-19-genericD:k{VO.9<գJ =k{5j:#cROZ^Exit Boot Services InvocationGUEx׿6~.H OuBrꃛ+t|~^a\@/D(Exit Boot Services Returned with Successfwupd-1.3.9/plugins/tpm/000077500000000000000000000000001362775233600151505ustar00rootroot00000000000000fwupd-1.3.9/plugins/tpm/README.md000066400000000000000000000015561362775233600164360ustar00rootroot00000000000000TPM Support =========== Introduction ------------ This allows enumerating Trusted Platform Modules, also known as "TPM" devices, although it does not allow the user to update the firmware on them. GUID Generation --------------- These devices use custom GUIDs: * `TPM\VEN_$(manufacturer)&DEV_$(type)` * `TPM\VEN_$(manufacturer)&MOD_$(vendor-string)` * `TPM\VEN_$(manufacturer)&DEV_$(type)_VER_$(family)`, * `TPM\VEN_$(manufacturer)&MOD_$(vendor-string)_VER_$(family)` ...where `family` is either `2.0` or `1.2` Example GUIDs from a real system containing a TPM from Intel: ``` Guid: 34801700-3a50-5b05-820c-fe14580e4c2d <- TPM\VEN_INTC&DEV_0000 Guid: 03f304f4-223e-54f4-b2c1-c3cf3b5817c6 <- TPM\VEN_INTC&DEV_0000&VER_2.0 ``` Vendor ID Security ------------------ The device is not upgradable and thus requires no vendor ID set. fwupd-1.3.9/plugins/tpm/fu-plugin-tpm.c000066400000000000000000000006301362775233600200170ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-hash.h" #include "fu-plugin-vfuncs.h" #include "fu-tpm-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_udev_subsystem (plugin, "tpm"); fu_plugin_set_device_gtype (plugin, FU_TYPE_TPM_DEVICE); } fwupd-1.3.9/plugins/tpm/fu-tpm-device.c000066400000000000000000000201531362775233600177620ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-tpm-device.h" struct _FuTpmDevice { FuUdevDevice parent_instance; }; G_DEFINE_TYPE (FuTpmDevice, fu_tpm_device, FU_TYPE_UDEV_DEVICE) static void Esys_Finalize_autoptr_cleanup (ESYS_CONTEXT *esys_context) { Esys_Finalize (&esys_context); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (ESYS_CONTEXT, Esys_Finalize_autoptr_cleanup) static gboolean fu_tpm_device_probe (FuUdevDevice *device, GError **error) { return fu_udev_device_set_physical_id (device, "tpm", error); } static gboolean fu_tpm_device_get_uint32 (ESYS_CONTEXT *ctx, guint32 query, guint32 *val, GError **error) { TSS2_RC rc; g_autofree TPMS_CAPABILITY_DATA *capability = NULL; g_return_val_if_fail (val != NULL, FALSE); rc = Esys_GetCapability (ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_TPM_PROPERTIES, query, 1, NULL, &capability); if (rc != TSS2_RC_SUCCESS) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "capability request failed for query %x", query); return FALSE; } if (capability->data.tpmProperties.count == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no properties returned for query %x", query); return FALSE; } if (capability->data.tpmProperties.tpmProperty[0].property != query) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "wrong query returned (got %x expected %x)", capability->data.tpmProperties.tpmProperty[0].property, query); return FALSE; } *val = capability->data.tpmProperties.tpmProperty[0].value; return TRUE; } static gchar * fu_tpm_device_get_string (ESYS_CONTEXT *ctx, guint32 query, GError **error) { guint32 val_be = 0; guint32 val; gchar result[5] = {'\0'}; /* return four bytes */ if (!fu_tpm_device_get_uint32 (ctx, query, &val_be, error)) return NULL; val = GUINT32_FROM_BE(val_be); memcpy (result, (gchar *) &val, 4); /* convert non-ASCII into spaces */ for (guint i = 0; i < 4; i++) { if (!g_ascii_isgraph (result[i])) result[i] = 0x20; } return fu_common_strstrip (result); } /* taken from TCG-TPM-Vendor-ID-Registry-Version-1.01-Revision-1.00.pdf */ static const gchar * fu_tpm_device_convert_manufacturer (const gchar *manufacturer) { if (g_strcmp0 (manufacturer, "AMD") == 0) return "AMD"; if (g_strcmp0 (manufacturer, "ATML") == 0) return "Atmel"; if (g_strcmp0 (manufacturer, "BRCM") == 0) return "Broadcom"; if (g_strcmp0 (manufacturer, "HPE") == 0) return "HPE"; if (g_strcmp0 (manufacturer, "IBM") == 0) return "IBM"; if (g_strcmp0 (manufacturer, "IFX") == 0) return "Infineon"; if (g_strcmp0 (manufacturer, "INTC") == 0) return "Intel"; if (g_strcmp0 (manufacturer, "LEN") == 0) return "Lenovo"; if (g_strcmp0 (manufacturer, "MSFT") == 0) return "Microsoft"; if (g_strcmp0 (manufacturer, "NSM") == 0) return "National Semiconductor"; if (g_strcmp0 (manufacturer, "NTZ") == 0) return "Nationz"; if (g_strcmp0 (manufacturer, "NTC") == 0) return "Nuvoton Technology"; if (g_strcmp0 (manufacturer, "QCOM") == 0) return "Qualcomm"; if (g_strcmp0 (manufacturer, "SMSC") == 0) return "SMSC"; if (g_strcmp0 (manufacturer, "STM") == 0) return "ST Microelectronics"; if (g_strcmp0 (manufacturer, "SMSN") == 0) return "Samsung"; if (g_strcmp0 (manufacturer, "SNS") == 0) return "Sinosun"; if (g_strcmp0 (manufacturer, "TXN") == 0) return "Texas Instruments"; if (g_strcmp0 (manufacturer, "WEC") == 0) return "Winbond"; if (g_strcmp0 (manufacturer, "ROCC") == 0) return "Fuzhou Rockchip"; if (g_strcmp0 (manufacturer, "GOOG") == 0) return "Google"; return NULL; } static gboolean fu_tpm_device_setup (FuDevice *device, GError **error) { FwupdVersionFormat verfmt; TSS2_RC rc; const gchar *tmp; guint32 tpm_type = 0; guint32 version1 = 0; guint32 version2 = 0; guint64 version_raw; g_autofree gchar *family = NULL; g_autofree gchar *id1 = NULL; g_autofree gchar *id2 = NULL; g_autofree gchar *id3 = NULL; g_autofree gchar *id4 = NULL; g_autofree gchar *manufacturer = NULL; g_autofree gchar *model1 = NULL; g_autofree gchar *model2 = NULL; g_autofree gchar *model3 = NULL; g_autofree gchar *model4 = NULL; g_autofree gchar *model = NULL; g_autofree gchar *vendor_id = NULL; g_autofree gchar *version = NULL; g_autoptr(ESYS_CONTEXT) ctx = NULL; /* setup TSS */ rc = Esys_Initialize (&ctx, NULL, NULL); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to initialize TPM library"); return FALSE; } rc = Esys_Startup (ctx, TPM2_SU_CLEAR); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to initialize TPM"); return FALSE; } /* lookup guaranteed details from TPM */ family = fu_tpm_device_get_string (ctx, TPM2_PT_FAMILY_INDICATOR, error); if (family == NULL) { g_prefix_error (error, "failed to read TPM family"); return FALSE; } manufacturer = fu_tpm_device_get_string (ctx, TPM2_PT_MANUFACTURER, error); if (manufacturer == NULL) { g_prefix_error (error, "failed to read TPM manufacturer"); return FALSE; } model1 = fu_tpm_device_get_string (ctx, TPM2_PT_VENDOR_STRING_1, error); if (model1 == NULL) { g_prefix_error (error, "failed to read TPM vendor string"); return FALSE; } if (!fu_tpm_device_get_uint32 (ctx, TPM2_PT_VENDOR_TPM_TYPE, &tpm_type, error)) { g_prefix_error (error, "failed to read TPM type"); return FALSE; } /* these are not guaranteed by spec and may be NULL */ model2 = fu_tpm_device_get_string (ctx, TPM2_PT_VENDOR_STRING_2, error); model3 = fu_tpm_device_get_string (ctx, TPM2_PT_VENDOR_STRING_3, error); model4 = fu_tpm_device_get_string (ctx, TPM2_PT_VENDOR_STRING_4, error); model = g_strjoin ("", model1, model2, model3, model4, NULL); /* add GUIDs to daemon */ id1 = g_strdup_printf ("TPM\\VEN_%s&DEV_%04X", manufacturer, tpm_type); fu_device_add_instance_id (device, id1); id2 = g_strdup_printf ("TPM\\VEN_%s&MOD_%s", manufacturer, model); fu_device_add_instance_id (device, id2); id3 = g_strdup_printf ("TPM\\VEN_%s&DEV_%04X&VER_%s", manufacturer, tpm_type, family); fu_device_add_instance_id (device, id3); id4 = g_strdup_printf ("TPM\\VEN_%s&MOD_%s&VER_%s", manufacturer, model, family); fu_device_add_instance_id (device, id4); /* enforce vendors can only ship updates for their own hardware */ vendor_id = g_strdup_printf ("TPM:%s", manufacturer); fu_device_set_vendor_id (device, vendor_id); tmp = fu_tpm_device_convert_manufacturer (manufacturer); fu_device_set_vendor (device, tmp != NULL ? tmp : manufacturer); /* get version */ if (!fu_tpm_device_get_uint32 (ctx, TPM2_PT_FIRMWARE_VERSION_1, &version1, error)) return FALSE; if (!fu_tpm_device_get_uint32 (ctx, TPM2_PT_FIRMWARE_VERSION_2, &version2, error)) return FALSE; version_raw = ((guint64) version1) << 32 | ((guint64) version2); fu_device_set_version_raw (device, version_raw); /* this has to be done after _add_instance_id() sets the quirks */ verfmt = fu_device_get_version_format (device); version = fu_common_version_from_uint64 (version_raw, verfmt); fu_device_set_version (device, version, verfmt); /* success */ return TRUE; } static void fu_tpm_device_init (FuTpmDevice *self) { fu_device_set_name (FU_DEVICE (self), "TPM"); fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_QUAD); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon (FU_DEVICE (self), "computer"); fu_udev_device_set_flags (FU_UDEV_DEVICE (self), FU_UDEV_DEVICE_FLAG_NONE); fu_device_add_instance_id (FU_DEVICE (self), "system-tpm"); } static void fu_tpm_device_finalize (GObject *object) { G_OBJECT_CLASS (fu_tpm_device_parent_class)->finalize (object); } static void fu_tpm_device_class_init (FuTpmDeviceClass *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_tpm_device_finalize; klass_device->setup = fu_tpm_device_setup; klass_udev_device->probe = fu_tpm_device_probe; } fwupd-1.3.9/plugins/tpm/fu-tpm-device.h000066400000000000000000000004311362775233600177640ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_TPM_DEVICE (fu_tpm_device_get_type ()) G_DECLARE_FINAL_TYPE (FuTpmDevice, fu_tpm_device, FU, TPM_DEVICE, FuUdevDevice) fwupd-1.3.9/plugins/tpm/meson.build000066400000000000000000000007651362775233600173220ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginTpm"'] install_data([ 'tpm.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_tpm', fu_hash, sources : [ 'fu-plugin-tpm.c', 'fu-tpm-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupdplugin, fwupd, ], c_args : cargs, dependencies : [ plugin_deps, tpm2tss, ], ) fwupd-1.3.9/plugins/tpm/tpm.quirk000066400000000000000000000000441362775233600170230ustar00rootroot00000000000000[DeviceInstanceId=TPM] Plugin = tpm fwupd-1.3.9/plugins/uefi-recovery/000077500000000000000000000000001362775233600171345ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi-recovery/README.md000066400000000000000000000011221362775233600204070ustar00rootroot00000000000000UEFI Support ============ Introduction ------------ Some devices have firmware bugs which mean they do not include a valid ESRT table in old firmware versions. Create a 'fake' UEFI device with the lowest possible version so that it can be updated to a version of firmware which does have an ESRT table. GUID Generation --------------- All the HwId GUIDs are used for the fake UEFI device, and so should be used in the firmware metadata for releases that should recover the system. Vendor ID Security ------------------ The vendor ID is set from the BIOS vendor, for example `DMI:LENOVO` fwupd-1.3.9/plugins/uefi-recovery/fu-plugin-uefi-recovery.c000066400000000000000000000035761362775233600240030ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-device-metadata.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" void fu_plugin_init (FuPlugin *plugin) { /* make sure that UEFI plugin is ready to receive devices */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "uefi"); fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { if (!fu_plugin_has_custom_flag (plugin, "requires-uefi-recovery")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not required"); return FALSE; } return TRUE; } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { GPtrArray *hwids = fu_plugin_get_hwids (plugin); const gchar *dmi_vendor; g_autoptr(FuDevice) device = fu_device_new (); fu_device_set_id (device, "uefi-recovery"); fu_device_set_name (device, "System Firmware ESRT Recovery"); fu_device_set_version (device, "0.0.0", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_set_metadata (device, FU_DEVICE_METADATA_UEFI_DEVICE_KIND, "system-firmware"); fu_device_add_icon (device, "computer"); for (guint i = 0; i < hwids->len; i++) { const gchar *hwid = g_ptr_array_index (hwids, i); fu_device_add_guid (device, hwid); } /* set vendor ID as the BIOS vendor */ dmi_vendor = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BIOS_VENDOR); if (dmi_vendor != NULL) { g_autofree gchar *vendor_id = g_strdup_printf ("DMI:%s", dmi_vendor); fu_device_set_vendor_id (device, vendor_id); } fu_plugin_device_register (plugin, device); return TRUE; } fwupd-1.3.9/plugins/uefi-recovery/meson.build000066400000000000000000000007741362775233600213060ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginUefiRecovery"'] install_data(['uefi-recovery.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_uefi_recovery', fu_hash, sources : [ 'fu-plugin-uefi-recovery.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, ], dependencies : [ plugin_deps, ], ) fwupd-1.3.9/plugins/uefi-recovery/uefi-recovery.quirk000066400000000000000000000002111362775233600227670ustar00rootroot00000000000000# Silicom Minnowboard Turbot MNW2MAX1.X64.0100.R01.1811141729 [HwId=ea358e00-39f1-55b6-97be-a39225a585e1] Flags = requires-uefi-recovery fwupd-1.3.9/plugins/uefi/000077500000000000000000000000001362775233600153005ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/README.md000066400000000000000000000035401362775233600165610ustar00rootroot00000000000000UEFI 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. 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)}`. Vendor ID Security ------------------ The vendor ID is set from the BIOS vendor, for example `DMI:LENOVO` for all devices that are not marked as supporting Firmware Management Protocol. For FMP device no vendor ID is set. 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.3.9/plugins/uefi/efi/000077500000000000000000000000001362775233600160435ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/efi/fwup-cleanups.h000066400000000000000000000010011362775233600207750ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/efi/fwup-common.c000066400000000000000000000050751362775233600204650ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/efi/fwup-common.h000066400000000000000000000012551362775233600204660ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/efi/fwup-debug.c000066400000000000000000000031251362775233600202550ustar00rootroot00000000000000/* * 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\n", file, line, func, tmp); if (out1 == NULL) { Print(L"fwupdate: Allocation for debug log failed!\n"); return; } Print(L"%s", 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.3.9/plugins/uefi/efi/fwup-debug.h000066400000000000000000000015561362775233600202700ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/efi/fwup-efi.c000066400000000000000000000035131362775233600177330ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/efi/fwup-efi.h000066400000000000000000000033121362775233600177350ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/efi/fwupdate.c000066400000000000000000000400051362775233600200250ustar00rootroot00000000000000/* * 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_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; 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 capsule */ fwup_warning(L"Could not add capsule with guid %g for update: %r", updates[i]->info->guid, rc); continue; } j++; } if (j == 0) { fwup_warning(L"Could not build update list: %r\n", rc); return rc; } 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.3.9/plugins/uefi/efi/generate_binary.sh000077500000000000000000000007661362775233600215510ustar00rootroot00000000000000#!/bin/sh output=$2 objcopy_cmd=$(command -v objcopy) genpeimg_cmd=$(command -v genpeimg) "$objcopy_cmd" -j .text \ -j .sdata \ -j .data \ -j .dynamic \ -j .dynsym \ -j '.rel*' \ "$@" if [ -n "${genpeimg_cmd}" ]; then $genpeimg_cmd -d \ +d \ -d \ +n \ -d \ +s \ "$output" fi fwupd-1.3.9/plugins/uefi/efi/meson.build000066400000000000000000000152501362775233600202100ustar00rootroot00000000000000efi_cc = get_option('efi-cc') efi_ld = get_option('efi-ld') 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 message('efi-libdir: "@0@"'.format(efi_libdir)) message('efi-ldsdir: "@0@"'.format(efi_ldsdir)) 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_ldsdir, '-L', efi_libdir, join_paths(efi_ldsdir, 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) so = custom_target('fwup.so', input : [o_file1, o_file2, o_file3, o_file4], output : 'fwup.so', command : [efi_ld, '-o', '@OUTPUT@'] + efi_ldflags + ['@INPUT@'] + ['-lefi', '-lgnuefi', libgcc_file_name]) build_tool = join_paths(meson.source_root(), 'plugins', 'uefi', 'efi', 'generate_binary.sh') app = custom_target(efi_name, input : so, output : efi_name, command : [build_tool, '@INPUT@', '@OUTPUT@', efi_format], install : true, install_dir : efi_app_location) dbg = custom_target('efi_debug', input : so, output : efi_name + '.debug', command : [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.3.9/plugins/uefi/fu-plugin-uefi.c000066400000000000000000000650701362775233600203100ustar00rootroot00000000000000/* * 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 "fu-device-metadata.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.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 { 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_METADATA_SOURCE, "tpm_eventlog"); 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_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 = FWUPD_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); const gchar *esp_path = fu_device_get_metadata (device, "EspPath"); 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 (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); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { const gchar *str; guint32 flashes_left; 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/fwupd/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); /* 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); /* where the ESP was mounted during installation */ str = fu_device_get_metadata (device, "EspPath"); fu_plugin_add_report_metadata (plugin, "ESPMountPoint", str); return TRUE; } static gboolean fu_plugin_uefi_load_config (FuPlugin *plugin, FuDevice *device, GError **error) { gboolean shim_needed = FALSE; 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; g_autofree gchar *esp_path = NULL; /* parse free space needed for ESP */ 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); fu_device_set_metadata_integer (device, "RequireESPFreeSpace", sz_reqd); /* shim used for SB or not? */ 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) shim_needed = TRUE; fu_device_set_metadata_boolean (device, "RequireShimForSecureBoot", shim_needed); /* load ESP from file */ esp_path = fu_plugin_get_config_value (plugin, "OverrideESPMountPoint"); if (esp_path != NULL) { g_autoptr(GError) error_local = NULL; if (!fu_uefi_check_esp_path (esp_path, &error_local)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "invalid OverrideESPMountPoint=%s specified in config: %s", esp_path, error_local->message); return FALSE; } fu_device_set_metadata (device, "EspPath", esp_path); } /* success */ return TRUE; } static void fu_plugin_uefi_register_proxy_device (FuPlugin *plugin, FuDevice *device) { g_autoptr(FuUefiDevice) dev = fu_uefi_device_new_from_dev (device); g_autoptr(GError) error_local = NULL; /* load all configuration variables */ if (!fu_plugin_uefi_load_config (plugin, FU_DEVICE (dev), &error_local)) g_warning ("%s", error_local->message); fu_plugin_device_add (plugin, FU_DEVICE (dev)); } void fu_plugin_device_registered (FuPlugin *plugin, FuDevice *device) { if (fu_device_get_metadata (device, FU_DEVICE_METADATA_UEFI_DEVICE_KIND) != 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 "); 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); } /* set vendor ID as the BIOS vendor */ if (fu_uefi_device_get_kind (dev) != FU_UEFI_DEVICE_KIND_FMP) { const gchar *dmi_vendor; dmi_vendor = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BIOS_VENDOR); if (dmi_vendor != NULL) { g_autofree gchar *vendor_id = g_strdup_printf ("DMI:%s", dmi_vendor); fu_device_set_vendor_id (FU_DEVICE (dev), vendor_id); } } /* 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_smbios_enabled (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; } return TRUE; } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { g_autoptr(GError) error_local = NULL; /* some platforms have broken SMBIOS data */ if (fu_plugin_has_custom_flag (plugin, "uefi-force-enable")) return TRUE; /* check SMBIOS for 'UEFI Specification is supported' */ if (!fu_plugin_uefi_smbios_enabled (plugin, &error_local)) { g_autofree gchar *fw = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); g_autofree gchar *fn = g_build_filename (fw, "efi", NULL); if (g_file_test (fn, G_FILE_TEST_EXISTS)) { g_warning ("SMBIOS BIOS Characteristics Extension Byte 2 is invalid -- " "UEFI Specification is unsupported, but %s exists: %s", fn, error_local->message); return TRUE; } g_propagate_error (error, g_steal_pointer (&error_local)); 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_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, const gchar *reason, 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); fu_device_set_update_error (dev, reason); 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_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_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)) { const gchar *reason = "Firmware can not be updated in legacy mode, switch to UEFI mode"; g_warning ("%s", error_local->message); return fu_plugin_uefi_create_dummy (plugin, reason, 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_local); if (entries == NULL) { const gchar *reason = "UEFI Capsule updates not available or enabled"; g_warning ("%s", error_local->message); return fu_plugin_uefi_create_dummy (plugin, reason, error); } /* 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); } /* 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_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_add_flag (FU_DEVICE (dev), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (dev), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); } /* load all configuration variables */ if (!fu_plugin_uefi_load_config (plugin, FU_DEVICE (dev), error)) return FALSE; fu_plugin_device_add (plugin, FU_DEVICE (dev)); } /* no devices are updatable */ if (error_bootloader != NULL) return TRUE; /* 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.3.9/plugins/uefi/fu-self-test.c000066400000000000000000000262071362775233600177710ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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" #include "fwupd-error.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; g_setenv ("FWUPD_SYSFSTPMDIR", TESTDATADIR, 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, ==, 1); pcrXs = fu_uefi_pcrs_get_checksums (pcrs, 999); g_assert_nonnull (pcrXs); g_assert_cmpint (pcrXs->len, ==, 0); g_unsetenv ("FWUPD_SYSFSTPMDIR"); } static void fu_uefi_pcrs_2_0_func (void) { g_autoptr(FuUefiPcrs) pcrs = fu_uefi_pcrs_new (); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) pcr0s = NULL; g_autoptr(GPtrArray) pcrXs = NULL; const gchar *tpm_server_running = g_getenv ("TPM_SERVER_RUNNING"); g_setenv ("FWUPD_FORCE_TPM2", "1", TRUE); #ifdef HAVE_GETUID if (tpm_server_running == NULL && (getuid () != 0 || geteuid () != 0)) { g_test_skip ("TPM2.0 tests require simulated TPM2.0 running or need root access with physical TPM"); return; } #endif if (!fu_uefi_pcrs_setup (pcrs, &error)) { if (tpm_server_running == NULL && g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_test_skip ("no physical or simulated TPM 2.0 device available"); return; } } g_assert_no_error (error); 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); g_unsetenv ("FWUPD_FORCE_TPM2"); } 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 = g_build_filename (TESTDATADIR, "test.bmp", 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 = g_build_filename (TESTDATADIR, "efi/esrt/entries/entry0", 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 = g_build_filename (TESTDATADIR, "efi/esrt/entries/entry0", 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); /* 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/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.3.9/plugins/uefi/fu-ucs2.c000066400000000000000000000033221362775233600167300ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/fu-ucs2.h000066400000000000000000000006211362775233600167340ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/fu-uefi-bgrt.c000066400000000000000000000056451362775233600177520ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/fu-uefi-bgrt.h000066400000000000000000000011731362775233600177470ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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); fwupd-1.3.9/plugins/uefi/fu-uefi-bootmgr.c000066400000000000000000000260371362775233600204630ustar00rootroot00000000000000/* * 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; gsize var_data_size = 0; guint32 attr; guint16 boot_next = G_MAXUINT16; g_autofree guint8 *var_data = NULL; g_autofree guint8 *set_entries = g_malloc0 (G_MAXUINT16); while ((rc = efi_get_next_variable_name (&guid, &name)) > 0) { const gchar *desc; 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; /* mark this as used */ set_entries[entry] = 1; 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 (guint16 value = 0; value < G_MAXUINT16; value++) { if (set_entries[value]) continue; boot_next = value; break; } if (boot_next == G_MAXUINT16) { 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); 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 */ rc = efi_set_variable (efi_guid_global, "BootNext", (guint8 *)&boot_next, 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 ")", boot_next); 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_TEST") != 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.3.9/plugins/uefi/fu-uefi-bootmgr.h000066400000000000000000000010701362775233600204560ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include 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); fwupd-1.3.9/plugins/uefi/fu-uefi-common.c000066400000000000000000000302501362775233600202720ustar00rootroot00000000000000/* * 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 "fu-uefi-udisks.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 *base) { #ifndef EFI_OS_DIR const gchar *os_release_id = NULL; const gchar *id_like_id; g_autofree gchar *esp_path = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GHashTable) os_release = fwupd_get_os_release (&error_local); /* try to lookup /etc/os-release ID key */ 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"; /* if ID key points at something existing return it */ esp_path = g_build_filename (base, "EFI", os_release_id, NULL); if (g_file_test (esp_path, G_FILE_TEST_IS_DIR) || os_release == NULL) return g_steal_pointer (&esp_path); /* if ID key doesn't exist, try ID_LIKE */ id_like_id = g_hash_table_lookup (os_release, "ID_LIKE"); if (id_like_id != NULL) { g_autofree gchar* id_like_path = g_build_filename (base, "EFI", id_like_id, NULL); if (g_file_test (id_like_path, G_FILE_TEST_IS_DIR)) { g_debug ("Using ID_LIKE key from os-release"); return g_steal_pointer (&id_like_path); } } return g_steal_pointer (&esp_path); #else return g_build_filename (base, "EFI", EFI_OS_DIR, NULL); #endif } 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; /* skip the checks for unmounted disks */ if (fu_uefi_udisks_objpath (path)) return TRUE; 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; } static gchar * fu_uefi_probe_udisks_esp (GError **error) { g_autoptr(GPtrArray) devices = NULL; g_autofree gchar *found_esp = NULL; devices = fu_uefi_udisks_get_block_devices (error); if (devices == NULL) return NULL; for (guint i = 0; i < devices->len; i++) { const gchar *obj = g_ptr_array_index (devices, i); gboolean esp = fu_uefi_udisks_objpath_is_esp (obj); g_debug ("block device %s, is_esp: %d", obj, esp); if (!esp) continue; if (found_esp != NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "Multiple EFI system partitions found, " "See https://github.com/fwupd/fwupd/wiki/Determining-EFI-system-partition-location"); return NULL; } found_esp = g_strdup (obj); } if (found_esp == 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/fwupd/fwupd/wiki/Determining-EFI-system-partition-location"); return NULL; } g_debug ("Udisks detected objpath %s", found_esp); return g_steal_pointer (&found_esp); } gchar * fu_uefi_guess_esp_path (GError **error) { 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); /* try to use known paths */ for (guint i = 0; paths[i] != NULL; i++) { g_autoptr(GError) error_local = NULL; if (!fu_uefi_check_esp_path (paths[i], &error_local)) { g_debug ("ignoring ESP path: %s", error_local->message); continue; } return g_strdup (paths[i]); } /* probe using udisks2 */ return fu_uefi_probe_udisks_esp (error); } void fu_uefi_print_efivar_errors (void) { 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_debug ("{efivar error #%d} %s:%d %s(): %s: %s\t", i, filename, line, function, message, strerror (err)); } } fwupd-1.3.9/plugins/uefi/fu-uefi-common.h000066400000000000000000000044631362775233600203060ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #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 (GError **error); 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); void fu_uefi_print_efivar_errors (void); fwupd-1.3.9/plugins/uefi/fu-uefi-device.c000066400000000000000000000607071362775233600202530ustar00rootroot00000000000000/* * 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-common.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" #include "fu-uefi-udisks.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 automounted_esp; }; 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, guint idt, GString *str) { FuUefiDevice *self = FU_UEFI_DEVICE (device); fu_common_string_append_kv (str, idt, "Kind", fu_uefi_device_kind_to_string (self->kind)); fu_common_string_append_kv (str, idt, "FwClass", self->fw_class); fu_common_string_append_kx (str, idt, "CapsuleFlags", self->capsule_flags); fu_common_string_append_kx (str, idt, "FwVersion", self->fw_version); fu_common_string_append_kx (str, idt, "FwVersionLowest", self->fw_version_lowest); fu_common_string_append_kv (str, idt, "LastAttemptStatus", fu_uefi_device_status_to_string (self->last_attempt_status)); fu_common_string_append_kx (str, idt, "LastAttemptVersion", self->last_attempt_version); fu_common_string_append_kv (str, idt, "EspPath", fu_device_get_metadata (device, "EspPath")); fu_common_string_append_ku (str, idt, "RequireESPFreeSpace", fu_device_get_metadata_integer (device, "RequireESPFreeSpace")); fu_common_string_append_kb (str, idt, "RequireShimForSecureBoot", fu_device_get_metadata_boolean (device, "RequireShimForSecureBoot")); } 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); /* FMP payload */ } else if (fu_uefi_device_get_kind (self) == FU_UEFI_DEVICE_KIND_FMP) { g_debug ("performing FMP update"); 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_TEST") != 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_print_efivar_errors (); 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_print_efivar_errors (); return FALSE; } return TRUE; } static gboolean fu_uefi_device_is_esp_mounted (FuDevice *device, GError **error) { const gchar *esp_path = fu_device_get_metadata (device, "EspPath"); g_autofree gchar *contents = NULL; g_auto(GStrv) lines = NULL; gsize length; if (esp_path == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EFI System partition is not defined"); return FALSE; } 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], esp_path)) return TRUE; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EFI System partition %s is not mounted", esp_path); return FALSE; } static gboolean fu_uefi_device_check_esp_free (FuDevice *device, GError **error) { const gchar *esp_path = fu_device_get_metadata (device, "EspPath"); guint64 sz_reqd = fu_device_get_metadata_integer (device, "RequireESPFreeSpace"); if (sz_reqd == G_MAXUINT) { g_debug ("maximum size is not configured"); return TRUE; } return fu_uefi_check_esp_free_space (esp_path, sz_reqd, error); } static gboolean fu_uefi_device_cleanup_esp (FuDevice *device, GError **error) { const gchar *esp_path = fu_device_get_metadata (device, "EspPath"); g_autofree gchar *pattern = NULL; g_autoptr(GPtrArray) files = NULL; /* in case we call capsule install twice before reboot */ if (fu_uefi_vars_exists (FU_UEFI_VARS_GUID_EFI_GLOBAL, "BootNext")) return TRUE; /* delete any files matching the glob in the ESP */ files = fu_common_get_files_recursive (esp_path, error); if (files == NULL) return FALSE; pattern = g_build_filename (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 (fu_common_fnmatch (pattern, fn)) { g_autoptr(GFile) file = g_file_new_for_path (fn); g_debug ("deleting %s", fn); if (!g_file_delete (file, NULL, error)) return FALSE; } } /* delete any old variables */ if (!fu_uefi_vars_delete_with_glob (FU_UEFI_VARS_GUID_FWUPDATE, "fwupd*-*", error)) return FALSE; return TRUE; } static gboolean fu_uefi_device_prepare (FuDevice *device, FwupdInstallFlags flags, GError **error) { /* not set in conf, figure it out */ if (fu_device_get_metadata (device, "EspPath") == NULL) { g_autofree gchar *guessed = NULL; g_autofree gchar *detected_esp = NULL; guessed = fu_uefi_guess_esp_path (error); if (guessed == NULL) return FALSE; /* udisks objpath */ if (fu_uefi_udisks_objpath (guessed)) { FuUefiDevice *self = FU_UEFI_DEVICE (device); detected_esp = fu_uefi_udisks_objpath_is_mounted (guessed); if (detected_esp != NULL) { g_debug ("ESP already mounted @ %s", detected_esp); /* not mounted */ } else { g_debug ("Mounting ESP @ %s", guessed); detected_esp = fu_uefi_udisks_objpath_mount (guessed, error); if (detected_esp == NULL) return FALSE; self->automounted_esp = TRUE; } /* already mounted */ } else { detected_esp = g_steal_pointer (&guessed); } fu_device_set_metadata (device, "EspPath", detected_esp); } /* sanity checks */ if (!fu_uefi_device_is_esp_mounted (device, error)) return FALSE; if (!fu_uefi_device_check_esp_free (device, error)) return FALSE; if (!fu_uefi_device_cleanup_esp (device, error)) return FALSE; return TRUE; } static gboolean fu_uefi_device_cleanup (FuDevice *device, FwupdInstallFlags flags, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE (device); if (self->automounted_esp) { g_autofree gchar *guessed = NULL; guessed = fu_uefi_guess_esp_path (error); if (guessed == NULL) return FALSE; g_debug ("Unmounting ESP @ %s", guessed); if (!fu_uefi_udisks_objpath_umount (guessed, error)) return FALSE; self->automounted_esp = FALSE; /* we will detect again if necessary */ fu_device_remove_metadata (device, "EspPath"); } return TRUE; } static gboolean fu_uefi_device_write_firmware (FuDevice *device, FuFirmware *firmware, 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_autoptr(GBytes) 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; } /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) 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_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { 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); fu_device_set_version_raw (device, self->fw_version); 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; fu_device_add_flag (device, FWUPD_DEVICE_FLAG_CAN_VERIFY); if (!fu_uefi_device_add_system_checksum (device, &error_local)) g_warning ("Failed to get PCR0s: %s", error_local->message); } /* 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) { fu_device_set_protocol (FU_DEVICE (self), "org.uefi.capsule"); } 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->prepare = fu_uefi_device_prepare; klass_device->write_firmware = fu_uefi_device_write_firmware; klass_device->cleanup = fu_uefi_device_cleanup; } 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.3.9/plugins/uefi/fu-uefi-device.h000066400000000000000000000046341362775233600202550ustar00rootroot00000000000000/* * 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" #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); fwupd-1.3.9/plugins/uefi/fu-uefi-devpath.c000066400000000000000000000063161362775233600204430ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/fu-uefi-devpath.h000066400000000000000000000010661362775233600204450ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include 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); fwupd-1.3.9/plugins/uefi/fu-uefi-pcrs.c000066400000000000000000000161671362775233600177640ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "fu-uefi-pcrs.h" #include "fwupd-error.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 void Esys_Finalize_autoptr_cleanup (ESYS_CONTEXT *esys_context) { Esys_Finalize (&esys_context); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (ESYS_CONTEXT, Esys_Finalize_autoptr_cleanup) 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, " ", ""); 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_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, GError **error) { TSS2_RC rc; g_autoptr(ESYS_CONTEXT) ctx = NULL; g_autofree TPMS_CAPABILITY_DATA *capability_data = NULL; TPML_PCR_SELECTION pcr_selection_in = { 0, }; g_autofree TPML_DIGEST *pcr_values = NULL; /* suppress warning messages about missing TCTI libraries for tpm2-tss <2.3 */ if (g_getenv ("FWUPD_UEFI_VERBOSE") == NULL) { g_setenv ("TSS2_LOG", "esys+error,tcti+none", FALSE); } rc = Esys_Initialize (&ctx, NULL, NULL); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to initialize TPM library"); return FALSE; } rc = Esys_Startup (ctx, TPM2_SU_CLEAR); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to initialize TPM"); return FALSE; } /* get hash algorithms supported by the TPM */ rc = Esys_GetCapability (ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_PCRS, 0, 1, NULL, &capability_data); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to get hash algorithms supported by TPM"); return FALSE; } /* fetch PCR 0 for every supported hash algorithm */ pcr_selection_in.count = capability_data->data.assignedPCR.count; for (guint i = 0; i < pcr_selection_in.count; i++) { pcr_selection_in.pcrSelections[i].hash = capability_data->data.assignedPCR.pcrSelections[i].hash; pcr_selection_in.pcrSelections[i].sizeofSelect = capability_data->data.assignedPCR.pcrSelections[i].sizeofSelect; pcr_selection_in.pcrSelections[i].pcrSelect[0] = 0b00000001; } rc = Esys_PCR_Read (ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pcr_selection_in, NULL, NULL, &pcr_values); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to read PCR values from TPM"); return FALSE; } for (guint i = 0; i < pcr_values->count; i++) { FuUefiPcrItem *item; g_autoptr(GString) str = NULL; str = g_string_new (NULL); for (guint j = 0; j < pcr_values->digests[i].size; j++) { gint64 val = pcr_values->digests[i].buffer[j]; if (val > 0) g_string_append_printf (str, "%02x", pcr_values->digests[i].buffer[j]); } if (str->len > 0) { item = g_new0 (FuUefiPcrItem, 1); item->idx = 0; /* constant PCR index 0, since we only read this single PCR */ 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); } } /* success */ return TRUE; } 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; g_return_val_if_fail (FU_IS_UEFI_PCRS (self), FALSE); /* look for TPM 1.2 */ sysfstpmdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_TPM); devpath = g_build_filename (sysfstpmdir, "tpm0", NULL); fn_pcrs = g_build_filename (devpath, "pcrs", NULL); if (g_file_test (fn_pcrs, G_FILE_TEST_EXISTS) && g_getenv ("FWUPD_FORCE_TPM2") == NULL) { if (!fu_uefi_pcrs_setup_tpm12 (self, fn_pcrs, error)) return FALSE; /* assume TPM 2.0 */ } else { if (!fu_uefi_pcrs_setup_tpm20 (self, 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.3.9/plugins/uefi/fu-uefi-pcrs.h000066400000000000000000000006661362775233600177660ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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); fwupd-1.3.9/plugins/uefi/fu-uefi-tool.c000066400000000000000000000260461362775233600177670ustar00rootroot00000000000000/* * 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_autofree gchar *flags = FALSE; 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" }, { "flags", 'f', 0, G_OPTION_ARG_STRING, &flags, /* TRANSLATORS: command line option */ _("Use quirk flags when installing firmware"), NULL }, { NULL} }; setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); /* ensure root user */ #ifdef HAVE_GETUID 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")); #endif /* 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; } } /* 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; } if (esp_path != NULL) 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; } if (flags != NULL) fu_device_set_custom_flags (FU_DEVICE (dev), flags); if (!fu_device_prepare (FU_DEVICE (dev), FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_printerr ("failed: %s\n", error_local->message); return EXIT_FAILURE; } 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; } if (!fu_device_cleanup (FU_DEVICE (dev), FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_printerr ("failed: %s\n", error_local->message); return EXIT_FAILURE; } } /* success */ return EXIT_SUCCESS; } fwupd-1.3.9/plugins/uefi/fu-uefi-udisks.c000066400000000000000000000117251362775233600203120ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-uefi-udisks.h" #include "fwupd-common.h" #include "fwupd-error.h" #define UDISKS_DBUS_SERVICE "org.freedesktop.UDisks2" #define UDISKS_DBUS_PATH "/org/freedesktop/UDisks2/Manager" #define UDISKS_DBUS_MANAGER_INTERFACE "org.freedesktop.UDisks2.Manager" #define UDISKS_DBUS_PART_INTERFACE "org.freedesktop.UDisks2.Partition" #define UDISKS_DBUS_FILE_INTERFACE "org.freedesktop.UDisks2.Filesystem" #define ESP_DISK_TYPE "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" gboolean fu_uefi_udisks_objpath (const gchar *path) { return g_str_has_prefix (path, "/org/freedesktop/UDisks2/"); } static GDBusProxy * fu_uefi_udisks_get_dbus_proxy (const gchar *path, const gchar *interface, 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, UDISKS_DBUS_SERVICE, path, interface, NULL, error); if (proxy == NULL) { g_prefix_error (error, "failed to find %s: ", UDISKS_DBUS_SERVICE); return NULL; } return g_steal_pointer (&proxy); } GPtrArray * fu_uefi_udisks_get_block_devices (GError **error) { g_autoptr(GVariant) output = NULL; g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GVariantIter) iter = NULL; GVariant *input; GVariantBuilder builder; const gchar *obj; proxy = fu_uefi_udisks_get_dbus_proxy (UDISKS_DBUS_PATH, UDISKS_DBUS_MANAGER_INTERFACE, error); if (proxy == NULL) return NULL; g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); input = g_variant_new ("(a{sv})", &builder); output = g_dbus_proxy_call_sync (proxy, "GetBlockDevices", g_variant_ref (input), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (output == NULL) return NULL; devices = g_ptr_array_new_with_free_func (g_free); g_variant_get (output, "(ao)", &iter); while (g_variant_iter_next (iter, "o", &obj)) g_ptr_array_add (devices, g_strdup (obj)); return g_steal_pointer (&devices); } gboolean fu_uefi_udisks_objpath_is_esp (const gchar *obj) { g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GVariant) val = NULL; const gchar *str; proxy = fu_uefi_udisks_get_dbus_proxy (obj, UDISKS_DBUS_PART_INTERFACE, &error_local); if (proxy == NULL) { g_warning ("Failed to initialize d-bus proxy: %s", error_local->message); return FALSE; } val = g_dbus_proxy_get_cached_property (proxy, "Type"); if (val == NULL) return FALSE; g_variant_get (val, "s", &str); return g_strcmp0 (str, ESP_DISK_TYPE) == 0; } gboolean fu_uefi_udisks_objpath_umount (const gchar *path, GError **error) { GVariant *input; GVariantBuilder builder; g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GVariant) val = NULL; g_return_val_if_fail (fu_uefi_udisks_objpath (path), FALSE); proxy = fu_uefi_udisks_get_dbus_proxy (path, UDISKS_DBUS_FILE_INTERFACE, error); if (proxy == NULL) return FALSE; g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); input = g_variant_new ("(a{sv})", &builder); val = g_dbus_proxy_call_sync (proxy, "Unmount", input, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (val == NULL) return FALSE; return TRUE; } gchar * fu_uefi_udisks_objpath_mount (const gchar *path, GError **error) { GVariant *input; GVariantBuilder builder; const gchar *str; g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GVariant) val = NULL; g_return_val_if_fail (fu_uefi_udisks_objpath (path), NULL); proxy = fu_uefi_udisks_get_dbus_proxy (path, UDISKS_DBUS_FILE_INTERFACE, error); if (proxy == NULL) return NULL; g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); input = g_variant_new ("(a{sv})", &builder); val = g_dbus_proxy_call_sync (proxy, "Mount", input, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (val == NULL) return NULL; g_variant_get (val, "(s)", &str); return g_strdup (str); } gchar * fu_uefi_udisks_objpath_is_mounted (const gchar *path) { const gchar **mountpoints = NULL; g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GVariant) val = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail (fu_uefi_udisks_objpath (path), NULL); proxy = fu_uefi_udisks_get_dbus_proxy (path, UDISKS_DBUS_FILE_INTERFACE, &error_local); if (proxy == NULL) { g_warning ("%s", error_local->message); return NULL; } val = g_dbus_proxy_get_cached_property (proxy, "MountPoints"); if (val == NULL) return NULL; mountpoints = g_variant_get_bytestring_array (val, NULL); return g_strdup (mountpoints[0]); } fwupd-1.3.9/plugins/uefi/fu-uefi-udisks.h000066400000000000000000000010411362775233600203050ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once GPtrArray *fu_uefi_udisks_get_block_devices (GError **error); gboolean fu_uefi_udisks_objpath (const gchar *path); gboolean fu_uefi_udisks_objpath_is_esp (const gchar *obj); gchar *fu_uefi_udisks_objpath_mount (const gchar *path, GError **error); gboolean fu_uefi_udisks_objpath_umount (const gchar *path, GError **error); gchar *fu_uefi_udisks_objpath_is_mounted (const gchar *path); fwupd-1.3.9/plugins/uefi/fu-uefi-update-info.c000066400000000000000000000107721362775233600212240ustar00rootroot00000000000000/* * 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.3.9/plugins/uefi/fu-uefi-update-info.h000066400000000000000000000021651362775233600212260ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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); fwupd-1.3.9/plugins/uefi/fu-uefi-vars.c000066400000000000000000000170421362775233600177610ustar00rootroot00000000000000/* * 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 "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 (fu_common_fnmatch (nameguid_glob, fn)) { 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)) { g_prefix_error (error, "failed to touch efivarfs: "); 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) { g_prefix_error (error, "failed to write data to efivarfs: "); 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.3.9/plugins/uefi/fu-uefi-vars.h000066400000000000000000000027671362775233600177760ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015-2017 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); fwupd-1.3.9/plugins/uefi/meson.build000066400000000000000000000053641362775233600174520ustar00rootroot00000000000000subdir('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-udisks.c', 'fu-uefi-vars.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, efivar, efiboot, tpm2tss, ], ) fwupdate = 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-udisks.c', 'fu-uefi-vars.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ libxmlb, giounix, gusb, gudev, efivar, efiboot, tpm2tss, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : bindir, c_args : cargs, ) if get_option('man') custom_target('fwupdate-man', input : fwupdate, output : 'fwupdate.1', command : [ help2man, '@INPUT@', '--no-info', '--output', '@OUTPUT@', '--name', 'Debugging utility for UEFI firmware updates', '--manual', 'User Commands', '--version-string', fwupd_version, ], install : true, install_dir : join_paths(mandir, 'man1'), ) endif 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-uefi-udisks.c', 'fu-ucs2.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, efivar, efiboot, tpm2tss, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) test('uefi-self-test', e) endif fwupd-1.3.9/plugins/uefi/tests/000077500000000000000000000000001362775233600164425ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/.gitignore000066400000000000000000000001421362775233600204270ustar00rootroot00000000000000EFI efi/efivars/fwupd-c34cb672-a81e-5d32-9d89-cbcabe8ec37b-0-0abba7dc-e516-4167-bbf5-4d9d1c739416 fwupd-1.3.9/plugins/uefi/tests/acpi/000077500000000000000000000000001362775233600173565ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/acpi/bgrt/000077500000000000000000000000001362775233600203145ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/acpi/bgrt/image000077700000000000000000000000001362775233600234212../../test.bmpustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/acpi/bgrt/status000066400000000000000000000000021362775233600215520ustar00rootroot000000000000001 fwupd-1.3.9/plugins/uefi/tests/acpi/bgrt/type000066400000000000000000000000021362775233600212100ustar00rootroot000000000000000 fwupd-1.3.9/plugins/uefi/tests/acpi/bgrt/version000066400000000000000000000000021362775233600217140ustar00rootroot000000000000001 fwupd-1.3.9/plugins/uefi/tests/acpi/bgrt/xoffset000066400000000000000000000000041362775233600217070ustar00rootroot00000000000000123 fwupd-1.3.9/plugins/uefi/tests/acpi/bgrt/yoffset000066400000000000000000000000041362775233600217100ustar00rootroot00000000000000456 fwupd-1.3.9/plugins/uefi/tests/efi-framebuffer/000077500000000000000000000000001362775233600214675ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi-framebuffer/efi-framebuffer.0/000077500000000000000000000000001362775233600246525ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi-framebuffer/efi-framebuffer.0/height000066400000000000000000000000041362775233600260370ustar00rootroot00000000000000789 fwupd-1.3.9/plugins/uefi/tests/efi-framebuffer/efi-framebuffer.0/width000066400000000000000000000000041362775233600257060ustar00rootroot00000000000000456 fwupd-1.3.9/plugins/uefi/tests/efi/000077500000000000000000000000001362775233600172055ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/efivars/000077500000000000000000000000001362775233600206445ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/efivars/BootNext-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000000001362775233600272120ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000000011362775233600275230ustar00rootroot000000000000001fwupd-ddc0ee61-e7f0-4e7d-acc5-c070a398838e-0-0abba7dc-e516-4167-bbf5-4d9d1c739416000066400000000000000000000003461362775233600340010ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/efivars {iMi eY*@ZZ~I ʍ5Mm\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.capfwupd-1.3.9/plugins/uefi/tests/efi/esrt/000077500000000000000000000000001362775233600201625ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/000077500000000000000000000000001362775233600216335ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry0/000077500000000000000000000000001362775233600230545ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry0/capsule_flags000066400000000000000000000000051362775233600256020ustar00rootroot000000000000000xfe fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry0/fw_class000066400000000000000000000000451362775233600245770ustar00rootroot00000000000000ddc0ee61-e7f0-4e7d-acc5-c070a398838e fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry0/fw_type000066400000000000000000000000021362775233600244440ustar00rootroot000000000000001 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry0/fw_version000066400000000000000000000000061362775233600251540ustar00rootroot0000000000000065586 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry0/last_attempt_status000066400000000000000000000000021362775233600270730ustar00rootroot000000000000001 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry0/last_attempt_version000066400000000000000000000000111362775233600272350ustar00rootroot0000000000000018472960 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry0/lowest_supported_fw_version000066400000000000000000000000061362775233600306560ustar00rootroot0000000000000065582 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry1/000077500000000000000000000000001362775233600230555ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry1/capsule_flags000066400000000000000000000000071362775233600256050ustar00rootroot000000000000000x8010 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry1/fw_class000066400000000000000000000000451362775233600246000ustar00rootroot00000000000000671d19d0-d43c-4852-98d9-1ce16f9967e4 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry1/fw_type000066400000000000000000000000021362775233600244450ustar00rootroot000000000000002 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry1/fw_version000066400000000000000000000000131362775233600251530ustar00rootroot000000000000003090287969 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry1/last_attempt_status000066400000000000000000000000021362775233600270740ustar00rootroot000000000000000 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry1/last_attempt_version000066400000000000000000000000021362775233600272360ustar00rootroot000000000000000 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry1/lowest_supported_fw_version000066400000000000000000000000021362775233600306530ustar00rootroot000000000000001 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry2/000077500000000000000000000000001362775233600230565ustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry2/capsule_flags000077700000000000000000000000001362775233600317742../entry1/capsule_flagsustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry2/fw_class000066400000000000000000000000451362775233600246010ustar00rootroot0000000000000000000000-0000-0000-0000-000000000000 fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry2/fw_type000077700000000000000000000000001362775233600275062../entry1/fw_typeustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry2/fw_version000077700000000000000000000000001362775233600307162../entry1/fw_versionustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry2/last_attempt_status000077700000000000000000000000001362775233600345642../entry1/last_attempt_statusustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry2/last_attempt_version000077700000000000000000000000001362775233600350702../entry1/last_attempt_versionustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/esrt/entries/entry2/lowest_supported_fw_version000077700000000000000000000000001362775233600401222../entry1/lowest_supported_fw_versionustar00rootroot00000000000000fwupd-1.3.9/plugins/uefi/tests/efi/fw_platform_size000066400000000000000000000000031362775233600224730ustar00rootroot0000000000000064 fwupd-1.3.9/plugins/uefi/tests/test.bmp000066400000000000000000000046721362775233600201320ustar00rootroot00000000000000BM 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ά[ * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #define MINIMUM_BATTERY_PERCENTAGE_FALLBACK 10 struct FuPluginData { GDBusProxy *upower_proxy; GDBusProxy *display_proxy; guint64 minimum_battery; }; 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; g_autofree gchar *battery_str = 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; } battery_str = fu_plugin_get_config_value (plugin, "BatteryThreshold"); if (battery_str == NULL) data->minimum_battery = MINIMUM_BATTERY_PERCENTAGE_FALLBACK; else data->minimum_battery = fu_common_strtoull (battery_str); if (data->minimum_battery > 100) { g_warning ("Invalid minimum battery level specified: %" G_GUINT64_FORMAT, data->minimum_battery); data->minimum_battery = MINIMUM_BATTERY_PERCENTAGE_FALLBACK; } 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 >= data->minimum_battery; } 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) { FuPluginData *data = fu_plugin_get_data (plugin); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW, "Cannot install update when battery " "is not at least %" G_GUINT64_FORMAT "%% unless forced", data->minimum_battery); return FALSE; } return TRUE; } fwupd-1.3.9/plugins/upower/meson.build000066400000000000000000000007151362775233600200360ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginUpower"'] shared_module('fu_plugin_upower', fu_hash, sources : [ 'fu-plugin-upower.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) install_data(['upower.conf'], install_dir: join_paths(sysconfdir, 'fwupd') ) fwupd-1.3.9/plugins/upower/upower.conf000066400000000000000000000001711362775233600200600ustar00rootroot00000000000000[upower] # The threshold to to require battery be at or above to allow updates # Measure in percent BatteryThreshold=10 fwupd-1.3.9/plugins/vli/000077500000000000000000000000001362775233600151425ustar00rootroot00000000000000fwupd-1.3.9/plugins/vli/README.md000066400000000000000000000045631362775233600164310ustar00rootroot00000000000000VIA Support =========== Introduction ------------ This plugin is used to update USB hubs from VIA. Firmware Format --------------- The daemon will decompress the cabinet archive and extract a firmware blob in an undisclosed binary file format. This plugin supports the following protocol ID: * com.vli.i2c * com.vli.pd * com.vli.usbhub GUID Generation --------------- These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_17EF&PID_3083&REV_0001` * `USB\VID_17EF&PID_3083` * `USB\VID_17EF` All VLI devices also use custom GUID values for the device type, e.g. * `USB\VID_17EF&PID_3083&DEV_VL812B3` These devices also use custom GUID values for the SPI flash configuration, e.g. * `VLI_USBHUB\SPI_37303840` * `VLI_USBHUB\SPI_3730` * `VLI_USBHUB\SPI_37` Optional PD child devices sharing the SPI flash use just one extra GUID, e.g. * `USB\VID_17EF&PID_3083&DEV_VL102` Optional I²C child devices use just one extra GUID, e.g. * `USB\VID_17EF&PID_3083&I2C_MSP430` * `USB\VID_17EF&PID_3083&I2C_PS186` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, for example set to `USB:0x2109` Quirk Use --------- This plugin uses the following plugin-specific quirks: | Quirk | Description | Minimum fwupd version | |----------------------------|----------------------------------|-----------------------| | `DeviceKind` | Device kind, e.g. `VL102` | 1.3.7 | | `SpiAutoDetect` | SPI autodetect (default 0x1) | 1.3.7 | | `SpiCmdChipErase` | Flash command to erase chip | 1.3.3 | | `SpiCmdChipErase` | Flash command to erase sector | 1.3.3 | | `SpiCmdReadId` | Flash command to read the ID | 1.3.3 | | `SpiCmdReadIdSz` | Size of the ReadId response | 1.3.3 | The `SpiCmdReadId` and `SpiCmdReadIdSz` quirks have to be assigned to the device instance attribute, rather then the flash part as the ID is required to query the other flash chip parameters. For example: [DeviceInstanceId=USB\VID_2109&PID_0210] Plugin = vli GType = FuVliUsbhubDevice SpiCmdReadId = 0xf8 SpiCmdReadIdSz = 4 # W3IRDFLASHxxx [Guid=VLI_USBHUB\\SPI_37303840] SpiCmdChipErase = 0xc7 SpiCmdSectorErase = 0x20 fwupd-1.3.9/plugins/vli/fu-plugin-vli.c000066400000000000000000000027671362775233600200200ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-vli-pd-device.h" #include "fu-vli-pd-firmware.h" #include "fu-vli-usbhub-device.h" #include "fu-vli-usbhub-firmware.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_add_firmware_gtype (plugin, "vli-usbhub", FU_TYPE_VLI_USBHUB_FIRMWARE); fu_plugin_add_firmware_gtype (plugin, "vli-pd", FU_TYPE_VLI_PD_FIRMWARE); /* register the custom types */ g_type_ensure (FU_TYPE_VLI_USBHUB_DEVICE); g_type_ensure (FU_TYPE_VLI_PD_DEVICE); } /* reboot the FuVliUsbhubDevice if we update the FuVliUsbhubPdDevice */ static FuDevice * fu_plugin_vli_get_parent (GPtrArray *devices) { for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index (devices, i); FuDevice *parent = fu_device_get_parent (dev); if (parent != NULL && FU_IS_VLI_USBHUB_DEVICE (parent)) return g_object_ref (parent); if (FU_IS_VLI_USBHUB_DEVICE (dev)) return g_object_ref (dev); } return NULL; } gboolean fu_plugin_composite_cleanup (FuPlugin *plugin, GPtrArray *devices, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuDevice) parent = fu_plugin_vli_get_parent (devices); if (parent == NULL) return TRUE; locker = fu_device_locker_new (parent, error); if (locker == NULL) return FALSE; return fu_device_attach (parent, error); } fwupd-1.3.9/plugins/vli/fu-self-test.c000066400000000000000000000012571362775233600176310ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-common.h" static void fu_test_common_device_kind_func (void) { for (guint i = 0; i < 0xffff; i++) { const gchar *tmp = fu_vli_common_device_kind_to_string (i); if (tmp == NULL) continue; g_assert_cmpint (fu_vli_common_device_kind_from_string (tmp), ==, i); } } 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 ("/vli/common{device-kind}", fu_test_common_device_kind_func); return g_test_run (); } fwupd-1.3.9/plugins/vli/fu-vli-common.c000066400000000000000000000152051362775233600200010ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-common.h" guint8 fu_vli_common_crc8 (const guint8 *buf, gsize bufsz) { guint32 crc = 0; for (gsize j = bufsz; j > 0; j--) { crc ^= (*(buf++) << 8); for (guint32 i = 8; i; i--) { if (crc & 0x8000) crc ^= (0x1070 << 3); crc <<= 1; } } return (guint8) (crc >> 8); } guint16 fu_vli_common_crc16 (const guint8 *buf, gsize bufsz) { guint16 crc = 0xffff; for (gsize len = bufsz; len > 0; len--) { crc = (guint16) (crc ^ (*buf++)); for (guint8 i = 0; i < 8; i++) { if (crc & 0x1) { crc = (crc >> 1) ^ 0xa001; } else { crc >>= 1; } } } return ~crc; } const gchar * fu_vli_common_device_kind_to_string (FuVliDeviceKind device_kind) { if (device_kind == FU_VLI_DEVICE_KIND_VL100) return "VL100"; if (device_kind == FU_VLI_DEVICE_KIND_VL101) return "VL101"; if (device_kind == FU_VLI_DEVICE_KIND_VL102) return "VL102"; if (device_kind == FU_VLI_DEVICE_KIND_VL103) return "VL103"; if (device_kind == FU_VLI_DEVICE_KIND_VL104) return "VL104"; if (device_kind == FU_VLI_DEVICE_KIND_VL105) return "VL105"; if (device_kind == FU_VLI_DEVICE_KIND_VL810) return "VL810"; if (device_kind == FU_VLI_DEVICE_KIND_VL811) return "VL811"; if (device_kind == FU_VLI_DEVICE_KIND_VL811PB0) return "VL811PB0"; if (device_kind == FU_VLI_DEVICE_KIND_VL811PB3) return "VL811PB3"; if (device_kind == FU_VLI_DEVICE_KIND_VL812B0) return "VL812B0"; if (device_kind == FU_VLI_DEVICE_KIND_VL812B3) return "VL812B3"; if (device_kind == FU_VLI_DEVICE_KIND_VL812Q4S) return "VL812Q4S"; if (device_kind == FU_VLI_DEVICE_KIND_VL813) return "VL813"; if (device_kind == FU_VLI_DEVICE_KIND_VL815) return "VL815"; if (device_kind == FU_VLI_DEVICE_KIND_VL817) return "VL817"; if (device_kind == FU_VLI_DEVICE_KIND_VL819) return "VL819"; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q7) return "VL820Q7"; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q8) return "VL820Q8"; if (device_kind == FU_VLI_DEVICE_KIND_VL120) return "VL120"; if (device_kind == FU_VLI_DEVICE_KIND_VL210) return "VL210"; if (device_kind == FU_VLI_DEVICE_KIND_VL211) return "VL211"; if (device_kind == FU_VLI_DEVICE_KIND_VL212) return "VL212"; if (device_kind == FU_VLI_DEVICE_KIND_MSP430) return "MSP430"; if (device_kind == FU_VLI_DEVICE_KIND_PS186) return "PS186"; return NULL; } FuVliDeviceKind fu_vli_common_device_kind_from_string (const gchar *device_kind) { if (g_strcmp0 (device_kind, "VL100") == 0) return FU_VLI_DEVICE_KIND_VL100; if (g_strcmp0 (device_kind, "VL101") == 0) return FU_VLI_DEVICE_KIND_VL101; if (g_strcmp0 (device_kind, "VL102") == 0) return FU_VLI_DEVICE_KIND_VL102; if (g_strcmp0 (device_kind, "VL103") == 0) return FU_VLI_DEVICE_KIND_VL103; if (g_strcmp0 (device_kind, "VL104") == 0) return FU_VLI_DEVICE_KIND_VL104; if (g_strcmp0 (device_kind, "VL105") == 0) return FU_VLI_DEVICE_KIND_VL105; if (g_strcmp0 (device_kind, "VL810") == 0) return FU_VLI_DEVICE_KIND_VL810; if (g_strcmp0 (device_kind, "VL811") == 0) return FU_VLI_DEVICE_KIND_VL811; if (g_strcmp0 (device_kind, "VL811PB0") == 0) return FU_VLI_DEVICE_KIND_VL811PB0; if (g_strcmp0 (device_kind, "VL811PB3") == 0) return FU_VLI_DEVICE_KIND_VL811PB3; if (g_strcmp0 (device_kind, "VL812B0") == 0) return FU_VLI_DEVICE_KIND_VL812B0; if (g_strcmp0 (device_kind, "VL812B3") == 0) return FU_VLI_DEVICE_KIND_VL812B3; if (g_strcmp0 (device_kind, "VL812Q4S") == 0) return FU_VLI_DEVICE_KIND_VL812Q4S; if (g_strcmp0 (device_kind, "VL813") == 0) return FU_VLI_DEVICE_KIND_VL813; if (g_strcmp0 (device_kind, "VL815") == 0) return FU_VLI_DEVICE_KIND_VL815; if (g_strcmp0 (device_kind, "VL817") == 0) return FU_VLI_DEVICE_KIND_VL817; if (g_strcmp0 (device_kind, "VL819") == 0) return FU_VLI_DEVICE_KIND_VL819; if (g_strcmp0 (device_kind, "VL820Q7") == 0) return FU_VLI_DEVICE_KIND_VL820Q7; if (g_strcmp0 (device_kind, "VL820Q8") == 0) return FU_VLI_DEVICE_KIND_VL820Q8; if (g_strcmp0 (device_kind, "VL120") == 0) return FU_VLI_DEVICE_KIND_VL120; if (g_strcmp0 (device_kind, "VL210") == 0) return FU_VLI_DEVICE_KIND_VL210; if (g_strcmp0 (device_kind, "VL211") == 0) return FU_VLI_DEVICE_KIND_VL211; if (g_strcmp0 (device_kind, "VL212") == 0) return FU_VLI_DEVICE_KIND_VL212; if (g_strcmp0 (device_kind, "MSP430") == 0) return FU_VLI_DEVICE_KIND_MSP430; if (g_strcmp0 (device_kind, "PS186") == 0) return FU_VLI_DEVICE_KIND_PS186; return FU_VLI_DEVICE_KIND_UNKNOWN; } guint32 fu_vli_common_device_kind_get_size (FuVliDeviceKind device_kind) { if (device_kind == FU_VLI_DEVICE_KIND_VL100) return 0x8000; /* 32KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL101) return 0xc000; /* 48KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL102) return 0x8000; /* 32KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL103) return 0x8000; /* 32KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL104) return 0xc000; /* 48KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL105) return 0xc000; /* 48KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL210) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL211) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL212) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL810) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL811) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL811PB0) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL811PB3) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL812B0) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL812B3) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL812Q4S) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL813) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL815) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL817) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL819) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q7) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q8) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_PS186) return 0x40000; return 0x0; } guint32 fu_vli_common_device_kind_get_offset (FuVliDeviceKind device_kind) { if (device_kind == FU_VLI_DEVICE_KIND_VL100) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL101) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL102) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL103) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL104) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL105) return 0x20000; return 0x0; } fwupd-1.3.9/plugins/vli/fu-vli-common.h000066400000000000000000000032561362775233600200110ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" typedef enum { FU_VLI_DEVICE_KIND_UNKNOWN = 0x0000, FU_VLI_DEVICE_KIND_VL100 = 0x0100, FU_VLI_DEVICE_KIND_VL101 = 0x0101, FU_VLI_DEVICE_KIND_VL102 = 0x0102, FU_VLI_DEVICE_KIND_VL103 = 0x0103, FU_VLI_DEVICE_KIND_VL104 = 0x0104, FU_VLI_DEVICE_KIND_VL105 = 0x0105, FU_VLI_DEVICE_KIND_VL120 = 0x0120, FU_VLI_DEVICE_KIND_VL210 = 0x0210, FU_VLI_DEVICE_KIND_VL211 = 0x0211, FU_VLI_DEVICE_KIND_VL212 = 0x0212, FU_VLI_DEVICE_KIND_VL810 = 0x0810, FU_VLI_DEVICE_KIND_VL811 = 0x0811, FU_VLI_DEVICE_KIND_VL811PB0 = 0x8110, FU_VLI_DEVICE_KIND_VL811PB3 = 0x8113, FU_VLI_DEVICE_KIND_VL812B0 = 0xa812, FU_VLI_DEVICE_KIND_VL812B3 = 0xb812, FU_VLI_DEVICE_KIND_VL812Q4S = 0xc812, FU_VLI_DEVICE_KIND_VL813 = 0x0813, FU_VLI_DEVICE_KIND_VL815 = 0x0815, FU_VLI_DEVICE_KIND_VL817 = 0x0817, FU_VLI_DEVICE_KIND_VL819 = 0x0819, FU_VLI_DEVICE_KIND_VL820Q7 = 0xa820, FU_VLI_DEVICE_KIND_VL820Q8 = 0xb820, FU_VLI_DEVICE_KIND_MSP430 = 0xf430, /* guessed */ FU_VLI_DEVICE_KIND_PS186 = 0xf186, /* guessed */ } FuVliDeviceKind; const gchar *fu_vli_common_device_kind_to_string (FuVliDeviceKind device_kind); FuVliDeviceKind fu_vli_common_device_kind_from_string (const gchar *device_kind); guint32 fu_vli_common_device_kind_get_size (FuVliDeviceKind device_kind); guint32 fu_vli_common_device_kind_get_offset (FuVliDeviceKind device_kind); guint8 fu_vli_common_crc8 (const guint8 *buf, gsize bufsz); guint16 fu_vli_common_crc16 (const guint8 *buf, gsize bufsz); fwupd-1.3.9/plugins/vli/fu-vli-device.c000066400000000000000000000471321362775233600177540ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-chunk.h" #include "fu-vli-device.h" typedef struct { FuUsbDevice parent_instance; FuVliDeviceKind kind; gboolean spi_auto_detect; FuVliDeviceSpiReq spi_cmds[FU_VLI_DEVICE_SPI_REQ_LAST]; guint8 spi_cmd_read_id_sz; guint32 flash_id; } FuVliDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE (FuVliDevice, fu_vli_device, FU_TYPE_USB_DEVICE) #define GET_PRIVATE(o) (fu_vli_device_get_instance_private (o)) static const gchar * fu_vli_device_spi_req_to_string (FuVliDeviceSpiReq req) { if (req == FU_VLI_DEVICE_SPI_REQ_READ_ID) return "SpiCmdReadId"; if (req == FU_VLI_DEVICE_SPI_REQ_PAGE_PROG) return "SpiCmdPageProg"; if (req == FU_VLI_DEVICE_SPI_REQ_CHIP_ERASE) return "SpiCmdChipErase"; if (req == FU_VLI_DEVICE_SPI_REQ_READ_DATA) return "SpiCmdReadData"; if (req == FU_VLI_DEVICE_SPI_REQ_READ_STATUS) return "SpiCmdReadStatus"; if (req == FU_VLI_DEVICE_SPI_REQ_SECTOR_ERASE) return "SpiCmdSectorErase"; if (req == FU_VLI_DEVICE_SPI_REQ_WRITE_EN) return "SpiCmdWriteEn"; if (req == FU_VLI_DEVICE_SPI_REQ_WRITE_STATUS) return "SpiCmdWriteStatus"; return NULL; } gboolean fu_vli_device_get_spi_cmd (FuVliDevice *self, FuVliDeviceSpiReq req, guint8 *cmd, GError **error) { FuVliDevicePrivate *priv = GET_PRIVATE (self); if (req >= FU_VLI_DEVICE_SPI_REQ_LAST) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SPI req invalid"); return FALSE; } if (priv->spi_cmds[req] == 0x0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No defined SPI cmd for %s", fu_vli_device_spi_req_to_string (req)); return FALSE; } if (cmd != NULL) *cmd = priv->spi_cmds[req]; return TRUE; } gboolean fu_vli_device_reset (FuVliDevice *self, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self); if (klass->reset != NULL) { if (!klass->reset (self, error)) { g_prefix_error (error, "failed to reset device: "); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_write_enable (FuVliDevice *self, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self); if (klass->spi_write_enable != NULL) { if (!klass->spi_write_enable (self, error)) { g_prefix_error (error, "failed to write enable SPI: "); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_chip_erase (FuVliDevice *self, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self); if (klass->spi_chip_erase != NULL) { if (!klass->spi_chip_erase (self, error)) { g_prefix_error (error, "failed to erase SPI data: "); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_write_status (FuVliDevice *self, guint8 status, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self); if (klass->spi_write_status != NULL) { if (!klass->spi_write_status (self, status, error)) { g_prefix_error (error, "failed to write SPI status 0x%x: ", status); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_read_status (FuVliDevice *self, guint8 *status, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self); if (klass->spi_read_status != NULL) { if (!klass->spi_read_status (self, status, error)) { g_prefix_error (error, "failed to read status: "); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_sector_erase (FuVliDevice *self, guint32 addr, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self); if (klass->spi_sector_erase != NULL) { if (!klass->spi_sector_erase (self, addr, error)) { g_prefix_error (error, "failed to erase SPI data @0x%x: ", addr); return FALSE; } } return TRUE; } gboolean fu_vli_device_spi_read_block (FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self); if (klass->spi_read_data != NULL) { if (!klass->spi_read_data (self, addr, buf, bufsz, error)) { g_prefix_error (error, "failed to read SPI data @0x%x: ", addr); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_write_data (FuVliDevice *self, guint32 addr, const guint8 *buf, gsize bufsz, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self); if (klass->spi_write_data != NULL) { if (!klass->spi_write_data (self, addr, buf, bufsz, error)) { g_prefix_error (error, "failed to write SPI data @0x%x: ", addr); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_wait_finish (FuVliDevice *self, GError **error) { const guint32 rdy_cnt = 2; guint32 cnt = 0; for (guint32 idx = 0; idx < 1000; idx++) { guint8 status = 0x7f; /* must get bit[1:0] == 0 twice in a row for success */ if (!fu_vli_device_spi_read_status (self, &status, error)) return FALSE; if ((status & 0x03) == 0x00) { if (cnt++ >= rdy_cnt) return TRUE; } else { cnt = 0; } g_usleep (500 * 1000); } g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to wait for SPI"); return FALSE; } gboolean fu_vli_device_spi_erase_sector (FuVliDevice *self, guint32 addr, GError **error) { const guint32 bufsz = 0x1000; /* erase sector */ if (!fu_vli_device_spi_write_enable (self, error)) { g_prefix_error (error, "->spi_write_enable failed: "); return FALSE; } if (!fu_vli_device_spi_write_status (self, 0x00, error)) { g_prefix_error (error, "->spi_write_status failed: "); return FALSE; } if (!fu_vli_device_spi_write_enable (self, error)) { g_prefix_error (error, "->spi_write_enable failed: "); return FALSE; } if (!fu_vli_device_spi_sector_erase (self, addr, error)) { g_prefix_error (error, "->spi_sector_erase failed"); return FALSE; } if (!fu_vli_device_spi_wait_finish (self, error)) { g_prefix_error (error, "->spi_wait_finish failed"); return FALSE; } /* verify it really was blanked */ for (guint32 offset = 0; offset < bufsz; offset += FU_VLI_DEVICE_TXSIZE) { guint8 buf[FU_VLI_DEVICE_TXSIZE] = { 0x0 }; if (!fu_vli_device_spi_read_block (self, addr + offset, buf, sizeof (buf), error)) { g_prefix_error (error, "failed to read back empty: "); return FALSE; } for (guint i = 0; i < sizeof(buf); i++) { if (buf[i] != 0xff) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to check blank @0x%x", addr + offset + i); return FALSE; } } } /* success */ return TRUE; } GBytes * fu_vli_device_spi_read (FuVliDevice *self, guint32 address, gsize bufsz, GError **error) { g_autofree guint8 *buf = g_malloc0 (bufsz); g_autoptr(GPtrArray) chunks = NULL; /* get data from hardware */ chunks = fu_chunk_array_new (buf, bufsz, address, 0x0, FU_VLI_DEVICE_TXSIZE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); if (!fu_vli_device_spi_read_block (self, chk->address, (guint8 *) chk->data, chk->data_sz, error)) { g_prefix_error (error, "SPI data read failed @0x%x: ", chk->address); return NULL; } fu_device_set_progress_full (FU_DEVICE (self), (gsize) i, (gsize) chunks->len); } return g_bytes_new_take (g_steal_pointer (&buf), bufsz); } gboolean fu_vli_device_spi_write_block (FuVliDevice *self, guint32 address, const guint8 *buf, gsize bufsz, GError **error) { g_autofree guint8 *buf_tmp = g_malloc0 (bufsz); /* sanity check */ if (bufsz > FU_VLI_DEVICE_TXSIZE) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot write 0x%x in one block", (guint) bufsz); return FALSE; } /* write */ if (g_getenv ("FWUPD_VLI_USBHUB_VERBOSE") != NULL) g_debug ("writing 0x%x block @0x%x", (guint) bufsz, address); if (!fu_vli_device_spi_write_enable (self, error)) { g_prefix_error (error, "enabling SPI write failed: "); return FALSE; } if (!fu_vli_device_spi_write_data (self, address, buf, bufsz, error)) { g_prefix_error (error, "SPI data write failed: "); return FALSE; } g_usleep (800); /* verify */ if (!fu_vli_device_spi_read_block (self, address, buf_tmp, bufsz, error)) { g_prefix_error (error, "SPI data read failed: "); return FALSE; } return fu_common_bytes_compare_raw (buf, bufsz, buf_tmp, bufsz, error); } gboolean fu_vli_device_spi_write (FuVliDevice *self, guint32 address, const guint8 *buf, gsize bufsz, GError **error) { FuChunk *chk; g_autoptr(GPtrArray) chunks = NULL; /* write SPI data, then CRC bytes last */ g_debug ("writing 0x%x bytes @0x%x", (guint) bufsz, address); chunks = fu_chunk_array_new (buf, bufsz, 0x0, 0x0, FU_VLI_DEVICE_TXSIZE); if (chunks->len > 1) { for (guint i = 1; i < chunks->len; i++) { chk = g_ptr_array_index (chunks, i); if (!fu_vli_device_spi_write_block (self, chk->address + address, chk->data, chk->data_sz, error)) { g_prefix_error (error, "failed to write block 0x%x: ", chk->idx); return FALSE; } fu_device_set_progress_full (FU_DEVICE (self), (gsize) i - 1, (gsize) chunks->len); } } chk = g_ptr_array_index (chunks, 0); if (!fu_vli_device_spi_write_block (self, chk->address + address, chk->data, chk->data_sz, error)) { g_prefix_error (error, "failed to write CRC block: "); return FALSE; } fu_device_set_progress_full (FU_DEVICE (self), (gsize) chunks->len, (gsize) chunks->len); return TRUE; } gboolean fu_vli_device_spi_erase_all (FuVliDevice *self, GError **error) { fu_device_set_progress (FU_DEVICE (self), 0); if (!fu_vli_device_spi_write_enable (self, error)) return FALSE; if (!fu_vli_device_spi_write_status (self, 0x00, error)) return FALSE; if (!fu_vli_device_spi_write_enable (self, error)) return FALSE; if (!fu_vli_device_spi_chip_erase (self, error)) return FALSE; g_usleep (4 * G_USEC_PER_SEC); /* verify chip was erased */ for (guint addr = 0; addr < 0x10000; addr += 0x1000) { guint8 buf[FU_VLI_DEVICE_TXSIZE] = { 0x0 }; if (!fu_vli_device_spi_read_block (self, addr, buf, sizeof(buf), error)) { g_prefix_error (error, "failed to read @0x%x: ", addr); return FALSE; } for (guint i = 0; i < sizeof(buf); i++) { if (buf[i] != 0xff) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to verify erase @0x%x: ", addr); return FALSE; } } fu_device_set_progress_full (FU_DEVICE (self), (gsize) addr, (gsize) 0x10000); } return TRUE; } gboolean fu_vli_device_spi_erase (FuVliDevice *self, guint32 addr, gsize sz, GError **error) { g_autoptr(GPtrArray) chunks = fu_chunk_array_new (NULL, sz, addr, 0x0, 0x1000); g_debug ("erasing 0x%x bytes @0x%x", (guint) sz, addr); for (guint i = 0; i < chunks->len; i++) { FuChunk *chunk = g_ptr_array_index (chunks, i); if (g_getenv ("FWUPD_VLI_USBHUB_VERBOSE") != NULL) g_debug ("erasing @0x%x", chunk->address); if (!fu_vli_device_spi_erase_sector (FU_VLI_DEVICE (self), chunk->address, error)) { g_prefix_error (error, "failed to erase FW sector @0x%x: ", chunk->address); return FALSE; } fu_device_set_progress_full (FU_DEVICE (self), (gsize) i, (gsize) chunks->len); } return TRUE; } static gchar * fu_vli_device_get_flash_id_str (FuVliDevice *self) { FuVliDevicePrivate *priv = GET_PRIVATE (self); if (priv->spi_cmd_read_id_sz == 4) return g_strdup_printf ("%08X", priv->flash_id); if (priv->spi_cmd_read_id_sz == 2) return g_strdup_printf ("%04X", priv->flash_id); if (priv->spi_cmd_read_id_sz == 1) return g_strdup_printf ("%02X", priv->flash_id); return g_strdup_printf ("%X", priv->flash_id); } void fu_vli_device_set_kind (FuVliDevice *self, FuVliDeviceKind device_kind) { FuVliDevicePrivate *priv = GET_PRIVATE (self); guint32 sz; priv->kind = device_kind; /* set maximum firmware size */ sz = fu_vli_common_device_kind_get_size (device_kind); if (sz > 0x0) fu_device_set_firmware_size_max (FU_DEVICE (self), sz); } void fu_vli_device_set_spi_auto_detect (FuVliDevice *self, gboolean spi_auto_detect) { FuVliDevicePrivate *priv = GET_PRIVATE (self); priv->spi_auto_detect = spi_auto_detect; } FuVliDeviceKind fu_vli_device_get_kind (FuVliDevice *self) { FuVliDevicePrivate *priv = GET_PRIVATE (self); return priv->kind; } guint32 fu_vli_device_get_offset (FuVliDevice *self) { FuVliDevicePrivate *priv = GET_PRIVATE (self); return fu_vli_common_device_kind_get_offset (priv->kind); } static void fu_vli_device_to_string (FuDevice *device, guint idt, GString *str) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (device); FuVliDevice *self = FU_VLI_DEVICE (device); FuVliDevicePrivate *priv = GET_PRIVATE (self); fu_common_string_append_kv (str, idt, "DeviceKind", fu_vli_common_device_kind_to_string (priv->kind)); fu_common_string_append_kb (str, idt, "SpiAutoDetect", priv->spi_auto_detect); if (priv->flash_id != 0x0) { g_autofree gchar *tmp = fu_vli_device_get_flash_id_str (self); fu_common_string_append_kv (str, idt, "FlashId", tmp); } for (guint i = 0; i < FU_VLI_DEVICE_SPI_REQ_LAST; i++) { fu_common_string_append_kx (str, idt, fu_vli_device_spi_req_to_string (i), priv->spi_cmds[i]); } /* subclassed further */ if (klass->to_string != NULL) return klass->to_string (self, idt, str); } static gboolean fu_vli_device_spi_read_flash_id (FuVliDevice *self, GError **error) { FuVliDevicePrivate *priv = GET_PRIVATE (self); GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); guint8 buf[4] = { 0x0 }; 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 | (priv->spi_cmd_read_id_sz * 2), priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_READ_ID], 0x0000, buf, sizeof(buf), NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to read chip ID: "); return FALSE; } if (g_getenv ("FWUPD_VLI_USBHUB_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "SpiCmdReadId", buf, sizeof(buf)); if (priv->spi_cmd_read_id_sz == 4) priv->flash_id = fu_common_read_uint32 (buf, G_BIG_ENDIAN); else if (priv->spi_cmd_read_id_sz == 2) priv->flash_id = fu_common_read_uint16 (buf, G_BIG_ENDIAN); else if (priv->spi_cmd_read_id_sz == 1) priv->flash_id = buf[0]; return TRUE; } static gboolean fu_vli_device_setup (FuDevice *device, GError **error) { FuVliDevice *self = FU_VLI_DEVICE (device); FuVliDevicePrivate *priv = GET_PRIVATE (self); FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (device); /* get the flash chip attached */ if (priv->spi_auto_detect) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); if (!fu_vli_device_spi_read_flash_id (self, error)) { g_prefix_error (error, "failed to read SPI chip ID: "); return FALSE; } if (priv->flash_id != 0x0) { g_autofree gchar *spi_id = NULL; g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; g_autofree gchar *flash_id = fu_vli_device_get_flash_id_str (self); g_debug ("using flash part %s", flash_id); /* load the SPI parameters from quirks */ spi_id = g_strdup_printf ("VLI_USBHUB\\SPI_%s", flash_id); fu_device_add_instance_id (FU_DEVICE (self), spi_id); /* add extra instance IDs to include the SPI variant */ devid2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&SPI_%s&REV_%04X", g_usb_device_get_vid (usb_device), g_usb_device_get_pid (usb_device), flash_id, g_usb_device_get_release (usb_device)); fu_device_add_instance_id (device, devid2); devid1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&SPI_%s", g_usb_device_get_vid (usb_device), g_usb_device_get_pid (usb_device), flash_id); fu_device_add_instance_id (device, devid1); } } /* subclassed further */ if (klass->setup != NULL) { if (!klass->setup (self, error)) return FALSE; } /* add extra DEV GUID too */ if (priv->kind != FU_VLI_DEVICE_KIND_UNKNOWN) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); g_autofree gchar *devid1 = NULL; devid1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&DEV_%s", g_usb_device_get_vid (usb_device), g_usb_device_get_pid (usb_device), fu_vli_common_device_kind_to_string (priv->kind)); fu_device_add_instance_id (device, devid1); } /* success */ return TRUE; } static gboolean fu_vli_device_attach (FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; /* replug, and ignore the device going away */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); if (!fu_vli_device_reset (FU_VLI_DEVICE (device), &error_local)) { if (g_error_matches (error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE) || g_error_matches (error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { g_debug ("ignoring %s", error_local->message); } else { g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to restart device: "); return FALSE; } } return TRUE; } static gboolean fu_vli_device_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuVliDevice *self = FU_VLI_DEVICE (device); FuVliDevicePrivate *priv = GET_PRIVATE (self); if (g_strcmp0 (key, "SpiCmdReadId") == 0) { priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_READ_ID] = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "SpiCmdReadIdSz") == 0) { priv->spi_cmd_read_id_sz = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "SpiCmdChipErase") == 0) { priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_CHIP_ERASE] = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "SpiCmdSectorErase") == 0) { priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_SECTOR_ERASE] = fu_common_strtoull (value); return TRUE; } if (g_strcmp0 (key, "SpiAutoDetect") == 0) { priv->spi_auto_detect = fu_common_strtoull (value) > 0; return TRUE; } if (g_strcmp0 (key, "DeviceKind") == 0) { FuVliDeviceKind device_kind; device_kind = fu_vli_common_device_kind_from_string (value); if (device_kind == FU_VLI_DEVICE_KIND_UNKNOWN) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "DeviceKind %s is not supported", value); return FALSE; } fu_vli_device_set_kind (self, device_kind); return TRUE; } g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_vli_device_init (FuVliDevice *self) { FuVliDevicePrivate *priv = GET_PRIVATE (self); priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_WRITE_STATUS] = 0x01; priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_PAGE_PROG] = 0x02; priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_READ_DATA] = 0x03; priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_READ_STATUS] = 0x05; priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_WRITE_EN] = 0x06; priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_SECTOR_ERASE] = 0x20; priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_CHIP_ERASE] = 0x60; priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_READ_ID] = 0x9f; priv->spi_cmd_read_id_sz = 2; priv->spi_auto_detect = TRUE; } static void fu_vli_device_class_init (FuVliDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->to_string = fu_vli_device_to_string; klass_device->set_quirk_kv = fu_vli_device_set_quirk_kv; klass_device->setup = fu_vli_device_setup; klass_device->attach = fu_vli_device_attach; } fwupd-1.3.9/plugins/vli/fu-vli-device.h000066400000000000000000000062421362775233600177560ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-vli-common.h" #define FU_TYPE_VLI_DEVICE (fu_vli_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (FuVliDevice, fu_vli_device, FU, VLI_DEVICE, FuUsbDevice) struct _FuVliDeviceClass { FuUsbDeviceClass parent_class; gboolean (*setup) (FuVliDevice *self, GError **error); void (*to_string) (FuVliDevice *self, guint idt, GString *str); gboolean (*reset) (FuVliDevice *self, GError **error); gboolean (*spi_chip_erase) (FuVliDevice *self, GError **error); gboolean (*spi_sector_erase) (FuVliDevice *self, guint32 addr, GError **error); gboolean (*spi_read_data) (FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error); gboolean (*spi_read_status) (FuVliDevice *self, guint8 *status, GError **error); gboolean (*spi_write_enable) (FuVliDevice *self, GError **error); gboolean (*spi_write_data) (FuVliDevice *self, guint32 addr, const guint8 *buf, gsize bufsz, GError **error); gboolean (*spi_write_status) (FuVliDevice *self, guint8 status, GError **error); }; typedef enum { FU_VLI_DEVICE_SPI_REQ_READ_ID, FU_VLI_DEVICE_SPI_REQ_PAGE_PROG, FU_VLI_DEVICE_SPI_REQ_CHIP_ERASE, FU_VLI_DEVICE_SPI_REQ_READ_DATA, FU_VLI_DEVICE_SPI_REQ_READ_STATUS, FU_VLI_DEVICE_SPI_REQ_SECTOR_ERASE, FU_VLI_DEVICE_SPI_REQ_WRITE_EN, FU_VLI_DEVICE_SPI_REQ_WRITE_STATUS, FU_VLI_DEVICE_SPI_REQ_LAST } FuVliDeviceSpiReq; #define FU_VLI_DEVICE_TIMEOUT 3000 /* ms */ #define FU_VLI_DEVICE_TXSIZE 0x20 /* bytes */ void fu_vli_device_set_kind (FuVliDevice *self, FuVliDeviceKind device_kind); void fu_vli_device_set_spi_auto_detect (FuVliDevice *self, gboolean spi_auto_detect); FuVliDeviceKind fu_vli_device_get_kind (FuVliDevice *self); guint32 fu_vli_device_get_offset (FuVliDevice *self); gboolean fu_vli_device_reset (FuVliDevice *self, GError **error); gboolean fu_vli_device_get_spi_cmd (FuVliDevice *self, FuVliDeviceSpiReq req, guint8 *cmd, GError **error); gboolean fu_vli_device_spi_erase_sector (FuVliDevice *self, guint32 addr, GError **error); gboolean fu_vli_device_spi_erase_all (FuVliDevice *self, GError **error); gboolean fu_vli_device_spi_erase (FuVliDevice *self, guint32 addr, gsize sz, GError **error); gboolean fu_vli_device_spi_read_block (FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error); GBytes *fu_vli_device_spi_read (FuVliDevice *self, guint32 address, gsize bufsz, GError **error); gboolean fu_vli_device_spi_write_block (FuVliDevice *self, guint32 address, const guint8 *buf, gsize bufsz, GError **error); gboolean fu_vli_device_spi_write (FuVliDevice *self, guint32 address, const guint8 *buf, gsize bufsz, GError **error); fwupd-1.3.9/plugins/vli/fu-vli-pd-common.c000066400000000000000000000013711362775233600204010ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-pd-common.h" FuVliDeviceKind fu_vli_pd_common_guess_device_kind (guint32 fwver) { guint32 tmp = (fwver & 0x0f000000) >> 24; if (tmp == 0x01 || tmp == 0x02 || tmp == 0x03) return FU_VLI_DEVICE_KIND_VL100; if (tmp == 0x04 || tmp == 0x05 || tmp == 0x06) return FU_VLI_DEVICE_KIND_VL101; if (tmp == 0x07 || tmp == 0x08) return FU_VLI_DEVICE_KIND_VL102; if (tmp == 0x09 || tmp == 0x0a) return FU_VLI_DEVICE_KIND_VL103; if (tmp == 0x0b) return FU_VLI_DEVICE_KIND_VL104; if (tmp == 0x0c) return FU_VLI_DEVICE_KIND_VL105; return FU_VLI_DEVICE_KIND_UNKNOWN; } fwupd-1.3.9/plugins/vli/fu-vli-pd-common.h000066400000000000000000000006441362775233600204100ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-vli-common.h" typedef struct __attribute__ ((packed)) { guint32 fwver; /* BE */ guint16 vid; /* LE */ guint16 pid; /* LE */ } FuVliPdHdr; FuVliDeviceKind fu_vli_pd_common_guess_device_kind (guint32 fwver); fwupd-1.3.9/plugins/vli/fu-vli-pd-device.c000066400000000000000000000412761362775233600203600ustar00rootroot00000000000000/* * Copyright (C) 2015-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-firmware.h" #include "fu-vli-pd-device.h" #include "fu-vli-pd-firmware.h" #include "fu-vli-pd-parade-device.h" struct _FuVliPdDevice { FuVliDevice parent_instance; }; G_DEFINE_TYPE (FuVliPdDevice, fu_vli_pd_device, FU_TYPE_VLI_DEVICE) static gboolean fu_vli_pd_device_read_regs (FuVliPdDevice *self, guint16 addr, guint8 *buf, gsize bufsz, GError **error) { if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xe0, ((addr & 0xff) << 8) | 0x01, addr >> 8, buf, bufsz, NULL, 1000, NULL, error)) { g_prefix_error (error, "failed to write register @0x%x: ", addr); return FALSE; } return TRUE; } static gboolean fu_vli_pd_device_read_reg (FuVliPdDevice *self, guint16 addr, guint8 *value, GError **error) { return fu_vli_pd_device_read_regs (self, addr, value, 0x1, error); } static gboolean fu_vli_pd_device_write_reg (FuVliPdDevice *self, guint16 addr, guint8 value, GError **error) { if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xe0, ((addr & 0xff) << 8) | 0x02, addr >> 8, &value, sizeof(value), NULL, 1000, NULL, error)) { g_prefix_error (error, "failed to write register @0x%x: ", addr); return FALSE; } return TRUE; } static gboolean fu_vli_pd_device_spi_read_status (FuVliDevice *self, guint8 *status, GError **error) { guint8 spi_cmd = 0x0; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_READ_STATUS, &spi_cmd, error)) return FALSE; return g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc5, spi_cmd, 0x0000, status, 0x1, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_pd_device_spi_read_data (FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_READ_DATA, &spi_cmd, error)) return FALSE; value = ((addr << 8) & 0xff00) | spi_cmd; index = addr >> 8; return g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc4, value, index, buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_pd_device_spi_write_status (FuVliDevice *self, guint8 status, GError **error) { guint8 spi_cmd = 0x0; guint16 value; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_WRITE_STATUS, &spi_cmd, error)) return FALSE; value = ((guint16) status << 8) | spi_cmd; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd8, value, 0x0, NULL, 0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } /* Fix_For_GD_&_EN_SPI_Flash */ g_usleep (100 * 1000); return TRUE; } static gboolean fu_vli_pd_device_spi_write_enable (FuVliDevice *self, GError **error) { guint8 spi_cmd = 0x0; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_WRITE_EN, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd4, spi_cmd, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to write enable SPI: "); return FALSE; } return TRUE; } static gboolean fu_vli_pd_device_spi_chip_erase (FuVliDevice *self, GError **error) { guint8 spi_cmd = 0x0; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_CHIP_ERASE, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd1, spi_cmd, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } return TRUE; } static gboolean fu_vli_pd_device_spi_sector_erase (FuVliDevice *self, guint32 addr, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_SECTOR_ERASE, &spi_cmd, error)) return FALSE; value = ((addr << 8) & 0xff00) | spi_cmd; index = addr >> 8; return g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd2, value, index, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_pd_device_spi_write_data (FuVliDevice *self, guint32 addr, const guint8 *buf, gsize bufsz, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_PAGE_PROG, &spi_cmd, error)) return FALSE; value = ((addr << 8) & 0xff00) | spi_cmd; index = addr >> 8; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xdc, value, index, (guint8 *) buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } return TRUE; } static gboolean fu_vli_pd_device_reset (FuVliDevice *device, GError **error) { return g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (device)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xb0, 0x0000, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_pd_device_reset_vl103 (FuVliDevice *device, GError **error) { return g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (device)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc0, 0x0000, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_pd_device_parade_setup (FuVliPdDevice *self, GError **error) { g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* add child */ dev = fu_vli_pd_parade_device_new (FU_VLI_DEVICE (self)); if (!fu_device_probe (dev, &error_local)) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_debug ("%s", error_local->message); } else { g_warning ("cannot create I²C parade device: %s", error_local->message); } return TRUE; } if (!fu_device_setup (dev, error)) { g_prefix_error (error, "failed to set up parade device: "); return FALSE; } fu_device_add_child (FU_DEVICE (self), dev); return TRUE; } static gboolean fu_vli_pd_device_setup (FuVliDevice *device, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE (device); guint32 version_raw; guint8 verbuf[4] = { 0x0 }; guint8 tmp = 0; g_autofree gchar *version_str = NULL; FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (device); /* get version */ if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xe2, 0x0001, 0x0000, verbuf, sizeof(verbuf), NULL, 1000, NULL, error)) { g_prefix_error (error, "failed to get version: "); return FALSE; } version_raw = fu_common_read_uint32 (verbuf, G_BIG_ENDIAN); fu_device_set_version_raw (FU_DEVICE (self), version_raw); version_str = fu_common_version_from_uint32 (version_raw, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version (FU_DEVICE (self), version_str, FWUPD_VERSION_FORMAT_QUAD); /* get device kind if not already in ROM mode */ if (fu_vli_device_get_kind (device) == FU_VLI_DEVICE_KIND_UNKNOWN) { if (!fu_vli_pd_device_read_reg (self, 0x0018, &tmp, error)) return FALSE; switch (tmp) { case 0x00: fu_vli_device_set_kind (device, FU_VLI_DEVICE_KIND_VL100); break; case 0x10: /* this is also the code for VL101, but VL102 is more likely */ fu_vli_device_set_kind (device, FU_VLI_DEVICE_KIND_VL102); break; case 0x80: fu_vli_device_set_kind (device, FU_VLI_DEVICE_KIND_VL103); break; case 0x90: fu_vli_device_set_kind (device, FU_VLI_DEVICE_KIND_VL104); break; default: g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unable to map 0x0018=%0x02x to device kind", tmp); return FALSE; } } /* handle this in a different way */ if (fu_vli_device_get_kind (device) == FU_VLI_DEVICE_KIND_VL103) klass->reset = fu_vli_pd_device_reset_vl103; /* get bootloader mode */ if (!fu_vli_pd_device_read_reg (self, 0x00F7, &tmp, error)) return FALSE; if ((tmp & 0x80) == 0x00) fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); else fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); /* detect any I²C child, e.g. parade device */ if (fu_device_has_custom_flag (FU_DEVICE (self), "has-i2c-ps186")) { if (!fu_vli_pd_device_parade_setup (self, error)) return FALSE; } /* success */ return TRUE; } static FuFirmware * fu_vli_pd_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE (device); FuVliDeviceKind device_kind; g_autoptr(FuFirmware) firmware = fu_vli_pd_firmware_new (); /* check size */ if (g_bytes_get_size (fw) > fu_device_get_firmware_size_max (device)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too large, got 0x%x, expected <= 0x%x", (guint) g_bytes_get_size (fw), (guint) fu_device_get_firmware_size_max (device)); return NULL; } /* check is compatible with firmware */ fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; device_kind = fu_vli_pd_firmware_get_kind (FU_VLI_PD_FIRMWARE (firmware)); if (fu_vli_device_get_kind (FU_VLI_DEVICE (self)) != device_kind) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got %s, expected %s", fu_vli_common_device_kind_to_string (device_kind), fu_vli_common_device_kind_to_string (fu_vli_device_get_kind (FU_VLI_DEVICE (self)))); return NULL; } /* we could check this against flags */ g_debug ("parsed version: %s", fu_firmware_get_version (firmware)); return g_steal_pointer (&firmware); } static FuFirmware * fu_vli_pd_device_read_firmware (FuDevice *device, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE (device); g_autoptr(GBytes) fw = NULL; fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY); fw = fu_vli_device_spi_read (FU_VLI_DEVICE (self), 0x0, fu_device_get_firmware_size_max (device), error); if (fw == NULL) return NULL; return fu_firmware_new_from_bytes (fw); } static gboolean fu_vli_pd_device_write_gpios (FuVliPdDevice *self, GError **error) { /* write GPIO 4&5, 7 then 3 */ if (!fu_vli_pd_device_write_reg (self, 0x0015, 0x7F, error)) return FALSE; if (!fu_vli_pd_device_write_reg (self, 0x0019, 0x00, error)) return FALSE; if (!fu_vli_pd_device_write_reg (self, 0x001C, 0x02, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE (device); gsize bufsz = 0; guint8 tmp = 0; const guint8 *buf = NULL; g_autoptr(GBytes) fw = NULL; /* binary blob */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* write GPIOs in new mode */ if (!fu_vli_pd_device_write_gpios (self, error)) return FALSE; /* disable write protect in GPIO_3 */ if (!fu_vli_pd_device_read_reg (self, 0x0003, &tmp, error)) return FALSE; if (!fu_vli_pd_device_write_reg (self, 0x0003, tmp | 0x44, error)) return FALSE; /* erase */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); if (!fu_vli_device_spi_erase_all (FU_VLI_DEVICE (self), error)) return FALSE; /* write in chunks */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); buf = g_bytes_get_data (fw, &bufsz); if (!fu_vli_device_spi_write (FU_VLI_DEVICE (self), fu_vli_device_get_offset (FU_VLI_DEVICE (self)), buf, bufsz, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_vli_pd_device_detach (FuDevice *device, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE (device); guint8 tmp = 0; g_autoptr(GError) error_local = NULL; /* write GPIOs */ if (!fu_vli_pd_device_write_gpios (self, error)) return FALSE; /* patch APP5 FW bug (2AF2 -> 2AE2) */ if (!fu_vli_pd_device_read_reg (self, 0x0018, &tmp, error)) return FALSE; if (tmp != 0x80) { if (!fu_vli_pd_device_write_reg (self, 0x2AE2, 0x1E, error)) return FALSE; if (!fu_vli_pd_device_write_reg (self, 0x2AE3, 0xC3, error)) return FALSE; if (!fu_vli_pd_device_write_reg (self, 0x2AE4, 0x5A, error)) return FALSE; if (!fu_vli_pd_device_write_reg (self, 0x2AE5, 0x87, error)) return FALSE; /* set ROM sig */ if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (device)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xa0, 0x0000, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) return FALSE; } /* reset from SPI_Code into ROM_Code */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!fu_vli_device_reset (FU_VLI_DEVICE (device), &error_local)) { if (g_error_matches (error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { g_debug ("ignoring %s", error_local->message); } else { g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to restart device: "); return FALSE; } } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_vli_pd_device_init (FuVliPdDevice *self) { fu_device_add_icon (FU_DEVICE (self), "audio-card"); fu_device_set_protocol (FU_DEVICE (self), "com.vli.pd"); fu_device_set_summary (FU_DEVICE (self), "USB PD"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_vli_device_set_spi_auto_detect (FU_VLI_DEVICE (self), FALSE); } static void fu_vli_pd_device_class_init (FuVliPdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuVliDeviceClass *klass_vli_device = FU_VLI_DEVICE_CLASS (klass); klass_device->read_firmware = fu_vli_pd_device_read_firmware; klass_device->write_firmware = fu_vli_pd_device_write_firmware; klass_device->prepare_firmware = fu_vli_pd_device_prepare_firmware; klass_device->detach = fu_vli_pd_device_detach; klass_vli_device->setup = fu_vli_pd_device_setup; klass_vli_device->reset = fu_vli_pd_device_reset; klass_vli_device->spi_chip_erase = fu_vli_pd_device_spi_chip_erase; klass_vli_device->spi_sector_erase = fu_vli_pd_device_spi_sector_erase; klass_vli_device->spi_read_data = fu_vli_pd_device_spi_read_data; klass_vli_device->spi_read_status = fu_vli_pd_device_spi_read_status; klass_vli_device->spi_write_data = fu_vli_pd_device_spi_write_data; klass_vli_device->spi_write_enable = fu_vli_pd_device_spi_write_enable; klass_vli_device->spi_write_status = fu_vli_pd_device_spi_write_status; } fwupd-1.3.9/plugins/vli/fu-vli-pd-device.h000066400000000000000000000006031362775233600203520ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-vli-device.h" #define FU_TYPE_VLI_PD_DEVICE (fu_vli_pd_device_get_type ()) G_DECLARE_FINAL_TYPE (FuVliPdDevice, fu_vli_pd_device, FU, VLI_PD_DEVICE, FuVliDevice) struct _FuVliPdDeviceClass { FuVliDeviceClass parent_class; }; fwupd-1.3.9/plugins/vli/fu-vli-pd-firmware.c000066400000000000000000000114001362775233600207170ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-pd-common.h" #include "fu-vli-pd-firmware.h" struct _FuVliPdFirmware { FuFirmwareClass parent_instance; FuVliDeviceKind device_kind; FuVliPdHdr hdr; GArray *offsets; }; G_DEFINE_TYPE (FuVliPdFirmware, fu_vli_pd_firmware, FU_TYPE_FIRMWARE) FuVliDeviceKind fu_vli_pd_firmware_get_kind (FuVliPdFirmware *self) { g_return_val_if_fail (FU_IS_VLI_PD_FIRMWARE (self), 0); return self->device_kind; } guint16 fu_vli_pd_firmware_get_vid (FuVliPdFirmware *self) { g_return_val_if_fail (FU_IS_VLI_PD_FIRMWARE (self), 0); return GUINT16_FROM_LE (self->hdr.vid); } guint16 fu_vli_pd_firmware_get_pid (FuVliPdFirmware *self) { g_return_val_if_fail (FU_IS_VLI_PD_FIRMWARE (self), 0); return GUINT16_FROM_LE (self->hdr.pid); } static void fu_vli_pd_firmware_to_string (FuFirmware *firmware, guint idt, GString *str) { FuVliPdFirmware *self = FU_VLI_PD_FIRMWARE (firmware); fu_common_string_append_kv (str, idt, "DeviceKind", fu_vli_common_device_kind_to_string (self->device_kind)); fu_common_string_append_kx (str, idt, "VID", fu_vli_pd_firmware_get_vid (self)); fu_common_string_append_kx (str, idt, "PID", fu_vli_pd_firmware_get_pid (self)); } void fu_vli_pd_firmware_add_offset (FuVliPdFirmware *self, gsize offset) { g_array_append_val (self->offsets, offset); } static gboolean fu_vli_pd_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuVliPdFirmware *self = FU_VLI_PD_FIRMWARE (firmware); gsize bufsz = 0; guint32 fwver; const guint8 *buf = g_bytes_get_data (fw, &bufsz); g_autofree gchar *fwver_str = NULL; g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (fw); /* map into header */ if (self->offsets->len == 0) { if (!fu_memcpy_safe ((guint8 *) &self->hdr, sizeof(self->hdr), 0x0, buf, bufsz, 0x0, sizeof(self->hdr), error)) { g_prefix_error (error, "failed to read header: "); return FALSE; } } else { for (guint i = 0; i < self->offsets->len; i++) { gsize offset = g_array_index (self->offsets, gsize, i); if (!fu_memcpy_safe ((guint8 *) &self->hdr, sizeof(self->hdr), 0x0, buf, bufsz, offset, sizeof(self->hdr), error)) { g_prefix_error (error, "failed to read header @0x%x: ", (guint) offset); return FALSE; } if (GUINT16_FROM_LE (self->hdr.vid) == 0x2109) break; } } /* guess device kind from fwver */ fwver = GUINT32_FROM_BE (self->hdr.fwver); self->device_kind = fu_vli_pd_common_guess_device_kind (fwver); if (self->device_kind == FU_VLI_DEVICE_KIND_UNKNOWN) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "version invalid, using 0x%x", fwver); return FALSE; } fwver_str = fu_common_version_from_uint32 (fwver, FWUPD_VERSION_FORMAT_QUAD); fu_firmware_set_version (firmware, fwver_str); /* check size */ if (bufsz != fu_vli_common_device_kind_get_size (self->device_kind)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "size invalid, got 0x%x expected 0x%x", (guint) bufsz, fu_vli_common_device_kind_get_size (self->device_kind)); return FALSE; } /* check CRC */ if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { guint16 crc_actual; guint16 crc_file = 0x0; if (!fu_common_read_uint16_safe (buf, bufsz, bufsz - 2, &crc_file, G_LITTLE_ENDIAN, error)) { g_prefix_error (error, "failed to read file CRC: "); return FALSE; } crc_actual = fu_vli_common_crc16 (buf, bufsz - 2); if (crc_actual != crc_file) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "CRC invalid, got 0x%x expected 0x%x", crc_file, crc_actual); return FALSE; } } /* whole image */ fu_firmware_add_image (firmware, img); return TRUE; } static void fu_vli_pd_firmware_init (FuVliPdFirmware *self) { self->offsets = g_array_new (FALSE, FALSE, sizeof(gsize)); } static void fu_vli_pd_firmware_finalize (GObject *object) { FuVliPdFirmware *self = FU_VLI_PD_FIRMWARE (object); g_array_unref (self->offsets); G_OBJECT_CLASS (fu_vli_pd_firmware_parent_class)->finalize (object); } static void fu_vli_pd_firmware_class_init (FuVliPdFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); klass_firmware->parse = fu_vli_pd_firmware_parse; klass_firmware->to_string = fu_vli_pd_firmware_to_string; object_class->finalize = fu_vli_pd_firmware_finalize; } FuFirmware * fu_vli_pd_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_VLI_PD_FIRMWARE, NULL)); } fwupd-1.3.9/plugins/vli/fu-vli-pd-firmware.h000066400000000000000000000013031362775233600207250ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #include "fu-vli-common.h" #define FU_TYPE_VLI_PD_FIRMWARE (fu_vli_pd_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuVliPdFirmware, fu_vli_pd_firmware, FU, VLI_PD_FIRMWARE, FuFirmware) FuFirmware *fu_vli_pd_firmware_new (void); FuVliDeviceKind fu_vli_pd_firmware_get_kind (FuVliPdFirmware *self); guint16 fu_vli_pd_firmware_get_vid (FuVliPdFirmware *self); guint16 fu_vli_pd_firmware_get_pid (FuVliPdFirmware *self); void fu_vli_pd_firmware_add_offset (FuVliPdFirmware *self, gsize offset); fwupd-1.3.9/plugins/vli/fu-vli-pd-parade-device.c000066400000000000000000000515131362775233600216050ustar00rootroot00000000000000/* * Copyright (C) 2015-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-chunk.h" #include "fu-vli-pd-device.h" #include "fu-vli-pd-parade-device.h" struct _FuVliPdParadeDevice { FuDevice parent_instance; FuVliDeviceKind device_kind; guint8 page2; /* base address */ guint8 page7; /* base address */ }; G_DEFINE_TYPE (FuVliPdParadeDevice, fu_vli_pd_parade_device, FU_TYPE_DEVICE) #define FU_VLI_PD_PARADE_I2C_CMD_WRITE 0xa6 #define FU_VLI_PD_PARADE_I2C_CMD_READ 0xa5 static void fu_vli_pd_parade_device_to_string (FuDevice *device, guint idt, GString *str) { FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE (device); fu_common_string_append_kv (str, idt, "DeviceKind", fu_vli_common_device_kind_to_string (self->device_kind)); fu_common_string_append_kx (str, idt, "Page2", self->page2); fu_common_string_append_kx (str, idt, "Page7", self->page7); } static gboolean fu_vli_pd_parade_device_i2c_read (FuVliPdParadeDevice *self, guint8 page2, guint8 reg_offset, /* customers addr offset */ guint8 *buf, gsize bufsz, GError **error) { guint16 value; /* sanity check */ if (bufsz > 0x40) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "request too large"); return FALSE; } /* VL103 FW only Use bits[7:1], so divide by 2 */ value = ((guint16) reg_offset << 8)| (page2 >> 1); if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_VLI_PD_PARADE_I2C_CMD_READ, value, 0x0, buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to read 0x%x:0x%x: ", page2, reg_offset); return FALSE; } return TRUE; } static gboolean fu_vli_pd_parade_device_i2c_write (FuVliPdParadeDevice *self, guint8 page2, guint8 reg_offset, /* customers addr offset */ guint8 val, /* only one byte supported */ GError **error) { guint16 value; guint16 index; guint8 buf[2] = { 0x0 }; /* apparently unused... */ /* VL103 FW only Use bits[7:1], so divide by 2 */ value = ((guint16) reg_offset << 8) | (page2 >> 1); index = (guint16) val << 8; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_VLI_PD_PARADE_I2C_CMD_WRITE, value, index, buf, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to write 0x%x:0x%x: ", page2, reg_offset); return FALSE; } return TRUE; } static gboolean fu_vli_pd_parade_device_start_mcu (FuVliPdParadeDevice *self, GError **error) { if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xBC, 0x00, error)) { g_prefix_error (error, "failed to start MCU: "); return FALSE; } return TRUE; } static gboolean fu_vli_pd_parade_device_stop_mcu (FuVliPdParadeDevice *self, GError **error) { if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xBC, 0xC0, error)) { g_prefix_error (error, "failed to stop MCU: "); return FALSE; } if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xBC, 0x40, error)) { g_prefix_error (error, "failed to stop MCU 2nd: "); return FALSE; } return TRUE; } static gboolean fu_vli_pd_parade_device_set_offset (FuVliPdParadeDevice *self, guint16 addr, GError **error) { if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x8E, addr >> 8, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x8F, addr & 0xff, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_read_fw_ver (FuVliPdParadeDevice *self, GError **error) { guint8 buf[0x20] = { 0x0 }; g_autofree gchar *version_str = NULL; /* stop MCU */ if (!fu_vli_pd_parade_device_stop_mcu (self, error)) return FALSE; if (!fu_vli_pd_parade_device_set_offset (self, 0x0, error)) return FALSE; g_usleep (1000 * 10); if (!fu_vli_pd_parade_device_i2c_read (self, self->page7, 0x02, buf, 0x1, error)) return FALSE; if (buf[0] != 0x01 && buf[0] != 0x02) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return FALSE; } g_debug ("getting FW%X version", buf[0]); if (!fu_vli_pd_parade_device_set_offset (self, 0x5000 | buf[0], error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_read (self, self->page7, 0x00, buf, sizeof(buf), error)) return FALSE; /* start MCU */ if (!fu_vli_pd_parade_device_start_mcu (self, error)) return FALSE; /* format version triplet */ version_str = g_strdup_printf ("%u.%u.%u", buf[0], buf[1], buf[2]); fu_device_set_version (FU_DEVICE (self), version_str, FWUPD_VERSION_FORMAT_TRIPLET); return TRUE; } static gboolean fu_vli_pd_parade_device_set_wp (FuVliPdParadeDevice *self, gboolean val, GError **error) { return fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xB3, val ? 0x10 : 0x00, error); } static gboolean fu_vli_pd_parade_device_write_enable (FuVliPdParadeDevice *self, GError **error) { /* Set_WP_High, SPI_WEN_06, Len_00, Trigger_Write, Set_WP_Low */ if (!fu_vli_pd_parade_device_set_wp (self, TRUE, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, 0x06, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x92, 0x00, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x93, 0x05, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp (self, FALSE, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_write_disable (FuVliPdParadeDevice *self, GError **error) { if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x00, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_write_status (FuVliPdParadeDevice *self, guint8 target_status, GError **error) { /* Set_WP_High, SPI_WSTS_01, Target_Status, Len_01, Trigger_Write, Set_WP_Low */ if (!fu_vli_pd_parade_device_set_wp (self, TRUE, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, 0x01, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, target_status, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x92, 0x01, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x93, 0x05, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp (self, FALSE, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_wait_ready (FuVliPdParadeDevice *self, GError **error) { gboolean ret = FALSE; guint limit = 100; guint8 buf = 0x0; /* wait for SPI ROM */ for (guint wait_cnt1 = 0; wait_cnt1 < limit; wait_cnt1++) { buf = 0xFF; if (!fu_vli_pd_parade_device_i2c_read (self, self->page2, 0x9E, &buf, sizeof(buf), error)) return FALSE; /* busy status: * bit[1,0]:Byte_Program * bit[3,2]:Sector Erase * bit[5,4]:Chip Erase */ if ((buf & 0x0C) == 0) { ret = TRUE; break; } } if (!ret) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to wait for SPI not BUSY"); return FALSE; } /* wait for SPI ROM status clear */ ret = FALSE; for (guint wait_cnt1 = 0; wait_cnt1 < limit; wait_cnt1++) { gboolean ret2 = FALSE; /* SPI_RSTS_05, Len_01, Trigger_Read */ if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, 0x05, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x92, 0x00, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x93, 0x01, error)) return FALSE; /* wait for cmd done */ for (guint wait_cnt2 = 0; wait_cnt2 < limit; wait_cnt2++) { buf = 0xFF; if (!fu_vli_pd_parade_device_i2c_read (self, self->page2, 0x93, &buf, sizeof(buf), error)) return FALSE; if ((buf & 0x01) == 0) { ret2 = TRUE; break; } } if (!ret2) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to wait for SPI CMD done"); return FALSE; } /* Wait_SPI_STS_00 */ buf = 0xFF; if (!fu_vli_pd_parade_device_i2c_read (self, self->page2, 0x91, &buf, sizeof(buf), error)) return FALSE; if ((buf & 0x01) == 0) { ret = TRUE; break; } } if (!ret) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to wait for SPI status clear"); return FALSE; } /* success */ return TRUE; } static gboolean fu_vli_pd_parade_device_sector_erase (FuVliPdParadeDevice *self, guint16 addr, GError **error) { /* SPI_SE_20, SPI_Adr_H, SPI_Adr_M, SPI_Adr_L, Len_03, Trigger_Write */ if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, 0x20, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, addr >> 8, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, addr & 0xff, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, 0x00, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x92, 0x03, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x93, 0x05, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_enable_mapping (FuVliPdParadeDevice *self, GError **error) { if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0xAA, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x55, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x50, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x41, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x52, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x44, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_block_erase (FuVliPdParadeDevice *self, guint8 block_idx, GError **error) { /* erase */ for (guint idx = 0x00; idx < 0x100; idx += 0x10){ if (!fu_vli_pd_parade_device_write_enable (self, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp (self, TRUE, error)) return FALSE; if (!fu_vli_pd_parade_device_sector_erase (self, ((guint16) block_idx << 8) | idx, error)) return FALSE; if (!fu_vli_pd_parade_device_wait_ready (self, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp (self, FALSE, error)) return FALSE; } /* verify */ for (guint idx = 0; idx < 0x100; idx += 0x10){ guint8 buf[0x20] = { 0xff }; if (!fu_vli_pd_parade_device_set_offset (self, (block_idx << 8) | idx, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_read (self, self->page7, 0, buf, 0x20, error)) return FALSE; for (guint idx2 = 0; idx2 < 0x20; idx2++){ if (buf[idx2] != 0xFF) { guint32 addr = (block_idx << 16) + (idx << 8); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Erase failed @0x%x", addr); return FALSE; } } } /* success */ return TRUE; } static gboolean fu_vli_pd_parade_device_block_write (FuVliPdParadeDevice *self, guint8 block_idx, const guint8 *txbuf, GError **error) { for (guint idx = 0; idx < 0x100; idx++) { if (!fu_vli_pd_parade_device_set_offset (self, (block_idx << 8) | idx, error)) return FALSE; for (guint idx2 = 0; idx2 < 0x100; idx2++){ guint32 buf_offset = (idx << 8) + idx2; if (!fu_vli_pd_parade_device_i2c_write (self, self->page7, (guint8)idx2, txbuf[buf_offset], error)) return FALSE; } } /* success */ return TRUE; } static GBytes * _g_bytes_new_sized (gsize sz) { guint8 *buf = g_malloc0 (sz); return g_bytes_new_take (buf, sz); } static gboolean fu_vli_pd_parade_device_block_read (FuVliPdParadeDevice *self, guint8 block_idx, guint8 *buf, gsize bufsz, GError **error) { for (guint idx = 0; idx < 0x100; idx++){ if (!fu_vli_pd_parade_device_set_offset (self, (block_idx << 8) | idx, error)) return FALSE; for (guint idx2 = 0; idx2 < 0x100; idx2 += 0x20){ guint buf_offset = (idx << 8) + idx2; if (!fu_vli_pd_parade_device_i2c_read (self, self->page7, idx2, buf + buf_offset, 0x20, error)) return FALSE; } } return TRUE; } static gboolean fu_vli_pd_parade_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE (device); FuVliPdDevice *parent = FU_VLI_PD_DEVICE (fu_device_get_parent (device)); guint8 buf[0x20]; guint block_idx_tmp; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw_verify = NULL; g_autoptr(GPtrArray) blocks = NULL; g_autoptr(GPtrArray) blocks_verify = NULL; /* simple image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* open device */ locker = fu_device_locker_new (parent, error); if (locker == NULL) return FALSE; /* stop MPU and reset SPI */ if (!fu_vli_pd_parade_device_stop_mcu (self, error)) return FALSE; /* 64K block erase */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); if (!fu_vli_pd_parade_device_write_enable (self, error)) return FALSE; if (!fu_vli_pd_parade_device_write_status (self, 0x00, error)) return FALSE; if (!fu_vli_pd_parade_device_wait_ready (self, error)) return FALSE; blocks = fu_chunk_array_new_from_bytes (fw, 0x0, 0x0, 0x10000); for (guint i = 1; i < blocks->len; i++) { FuChunk *block = g_ptr_array_index (blocks, i); if (!fu_vli_pd_parade_device_block_erase (self, block->idx, error)) return FALSE; fu_device_set_progress_full (FU_DEVICE (self), i, blocks->len); } /* load F/W to SPI ROM */ if (!fu_vli_pd_parade_device_enable_mapping (self, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x82, 0x20, error)) return FALSE; /* Reset_CLT2SPI_Interface */ g_usleep (1000 * 100); if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x82, 0x00, error)) return FALSE; /* write blocks */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); for (guint i = 1; i < blocks->len; i++) { FuChunk *block = g_ptr_array_index (blocks, i); if (!fu_vli_pd_parade_device_block_write (self, block->idx, block->data, error)) return FALSE; fu_device_set_progress_full (FU_DEVICE (self), i, blocks->len); } if (!fu_vli_pd_parade_device_write_disable (self, error)) return FALSE; /* verify SPI ROM */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY); fw_verify = _g_bytes_new_sized (g_bytes_get_size (fw)); blocks_verify = fu_chunk_array_new_from_bytes (fw_verify, 0x0, 0x0, 0x10000); for (guint i = 1; i < blocks_verify->len; i++) { FuChunk *block = g_ptr_array_index (blocks_verify, i); if (!fu_vli_pd_parade_device_block_read (self, block->idx, (guint8 *) block->data, block->data_sz, error)) return FALSE; fu_device_set_progress_full (FU_DEVICE (self), i, blocks->len); } if (!fu_common_bytes_compare (fw, fw_verify, error)) return FALSE; /* save boot config into Block_0 */ if (!fu_vli_pd_parade_device_write_enable (self, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp (self, TRUE, error)) return FALSE; if (!fu_vli_pd_parade_device_sector_erase (self, 0x0, error)) return FALSE; if (!fu_vli_pd_parade_device_wait_ready (self, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp (self, FALSE, error)) return FALSE; /* Page_HW_Write_Disable */ if (!fu_vli_pd_parade_device_enable_mapping (self, error)) return FALSE; block_idx_tmp = 1; if (!fu_vli_pd_parade_device_set_offset (self, 0x0, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page7, 0x00, 0x55, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page7, 0x01, 0xAA, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page7, 0x02, (guint8) block_idx_tmp, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write (self, self->page7, 0x03, (guint8) (0x01 - block_idx_tmp), error)) return FALSE; if (!fu_vli_pd_parade_device_write_disable (self, error)) return FALSE; /* check boot config data */ if (!fu_vli_pd_parade_device_set_offset (self, 0x0, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_read (self, self->page7, 0, buf, sizeof(buf), error)) return FALSE; if (buf[0] != 0x55 || buf[1] != 0xAA || buf[2] != block_idx_tmp || buf[3] != 0x01 - block_idx_tmp) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "boot config data error"); return FALSE; } /* enable write protection */ if (!fu_vli_pd_parade_device_write_enable (self, error)) return FALSE; if (!fu_vli_pd_parade_device_write_status (self, 0x8C, error)) return FALSE; if (!fu_vli_pd_parade_device_wait_ready (self, error)) return FALSE; if (!fu_vli_pd_parade_device_write_disable (self, error)) return FALSE; /* success */ return TRUE; } static FuFirmware * fu_vli_pd_parade_device_read_firmware (FuDevice *device, GError **error) { FuVliPdDevice *parent = FU_VLI_PD_DEVICE (fu_device_get_parent (device)); FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) blocks = NULL; /* open device */ locker = fu_device_locker_new (parent, error); if (locker == NULL) return NULL; /* stop MPU and reset SPI */ if (!fu_vli_pd_parade_device_stop_mcu (self, error)) return NULL; /* read */ fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_DEVICE_VERIFY); fw = _g_bytes_new_sized (fu_device_get_firmware_size_max (device)); blocks = fu_chunk_array_new_from_bytes (fw, 0x0, 0x0, 0x10000); for (guint i = 0; i < blocks->len; i++) { FuChunk *block = g_ptr_array_index (blocks, i); if (!fu_vli_pd_parade_device_block_read (self, block->idx, (guint8 *) block->data, block->data_sz, error)) return NULL; } return fu_firmware_new_from_bytes (fw); } static FuFirmware * fu_vli_pd_parade_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { /* check size */ if (g_bytes_get_size (fw) < fu_device_get_firmware_size_min (device)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too small, got 0x%x, expected >= 0x%x", (guint) g_bytes_get_size (fw), (guint) fu_device_get_firmware_size_min (device)); return NULL; } return fu_firmware_new_from_bytes (fw); } static gboolean fu_vli_pd_parade_device_probe (FuDevice *device, GError **error) { FuVliPdDevice *parent = FU_VLI_PD_DEVICE (fu_device_get_parent (device)); FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE (device); g_autofree gchar *instance_id1 = NULL; /* get version */ if (!fu_vli_pd_parade_device_read_fw_ver (self, error)) return FALSE; /* use header to populate device info */ instance_id1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&I2C_%s", fu_usb_device_get_vid (FU_USB_DEVICE (parent)), fu_usb_device_get_pid (FU_USB_DEVICE (parent)), fu_vli_common_device_kind_to_string (self->device_kind)); fu_device_add_instance_id (device, instance_id1); /* success */ return TRUE; } static void fu_vli_pd_parade_device_init (FuVliPdParadeDevice *self) { self->device_kind = FU_VLI_DEVICE_KIND_PS186; self->page2 = 0x14; self->page7 = 0x1E; fu_device_add_icon (FU_DEVICE (self), "video-display"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_protocol (FU_DEVICE (self), "com.vli.i2c"); fu_device_set_install_duration (FU_DEVICE (self), 15); /* seconds */ fu_device_set_logical_id (FU_DEVICE (self), "PS186"); fu_device_set_summary (FU_DEVICE (self), "DisplayPort 1.4a to HDMI 2.0b Protocol Converter"); fu_device_set_firmware_size (FU_DEVICE (self), 0x40000); } static void fu_vli_pd_parade_device_class_init (FuVliPdParadeDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->to_string = fu_vli_pd_parade_device_to_string; klass_device->probe = fu_vli_pd_parade_device_probe; klass_device->read_firmware = fu_vli_pd_parade_device_read_firmware; klass_device->prepare_firmware = fu_vli_pd_parade_device_prepare_firmware; klass_device->write_firmware = fu_vli_pd_parade_device_write_firmware; } FuDevice * fu_vli_pd_parade_device_new (FuVliDevice *parent) { FuVliPdParadeDevice *self = g_object_new (FU_TYPE_VLI_PD_PARADE_DEVICE, "parent", parent, NULL); return FU_DEVICE (self); } fwupd-1.3.9/plugins/vli/fu-vli-pd-parade-device.h000066400000000000000000000007471362775233600216150ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-vli-pd-common.h" #define FU_TYPE_VLI_PD_PARADE_DEVICE (fu_vli_pd_parade_device_get_type ()) G_DECLARE_FINAL_TYPE (FuVliPdParadeDevice, fu_vli_pd_parade_device, FU, VLI_PD_PARADE_DEVICE, FuDevice) struct _FuVliPdParadeDeviceClass { FuDeviceClass parent_class; }; FuDevice *fu_vli_pd_parade_device_new (FuVliDevice *parent); fwupd-1.3.9/plugins/vli/fu-vli-usbhub-common.c000066400000000000000000000027241362775233600212710ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-usbhub-common.h" guint8 fu_vli_usbhub_header_crc8 (FuVliUsbhubHeader *hdr) { return fu_vli_common_crc8 ((const guint8 *) hdr, sizeof(hdr) - 1); } void fu_vli_usbhub_header_to_string (FuVliUsbhubHeader *hdr, guint idt, GString *str) { fu_common_string_append_kx (str, idt, "DevId", GUINT16_FROM_BE(hdr->dev_id)); fu_common_string_append_kx (str, idt, "Variant", hdr->variant); if (hdr->usb2_fw_sz > 0) { fu_common_string_append_kx (str, idt, "Usb2FwAddr", GUINT16_FROM_BE(hdr->usb2_fw_addr)); fu_common_string_append_kx (str, idt, "Usb2FwSz", GUINT16_FROM_BE(hdr->usb2_fw_sz)); } fu_common_string_append_kx (str, idt, "Usb3FwAddr", ((guint32) hdr->usb3_fw_addr_high) << 16 | GUINT16_FROM_BE(hdr->usb3_fw_addr)); fu_common_string_append_kx (str, idt, "Usb3FwSz", GUINT16_FROM_BE(hdr->usb3_fw_sz)); if (hdr->prev_ptr != VLI_USBHUB_FLASHMAP_IDX_INVALID) { fu_common_string_append_kx (str, idt, "PrevPtr", VLI_USBHUB_FLASHMAP_IDX_TO_ADDR(hdr->prev_ptr)); } if (hdr->next_ptr != VLI_USBHUB_FLASHMAP_IDX_INVALID) { fu_common_string_append_kx (str, idt, "NextPtr", VLI_USBHUB_FLASHMAP_IDX_TO_ADDR(hdr->next_ptr)); } fu_common_string_append_kb (str, idt, "ChecksumOK", hdr->checksum == fu_vli_usbhub_header_crc8 (hdr)); } fwupd-1.3.9/plugins/vli/fu-vli-usbhub-common.h000066400000000000000000000043551362775233600213000ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-vli-common.h" typedef struct __attribute__ ((packed)) { guint16 dev_id; /* 0x00, BE */ guint8 strapping1; /* 0x02 */ guint8 strapping2; /* 0x03 */ guint16 usb3_fw_addr; /* 0x04, BE */ guint16 usb3_fw_sz; /* 0x06, BE */ guint16 usb2_fw_addr; /* 0x08, BE */ guint16 usb2_fw_sz; /* 0x0a, BE */ guint8 usb3_fw_addr_high; /* 0x0c */ guint8 unknown_0d[3]; /* 0x0d */ guint8 usb2_fw_addr_high; /* 0x10 */ guint8 unknown_11[10]; /* 0x11 */ guint8 inverse_pe41; /* 0x1b */ guint8 prev_ptr; /* 0x1c, addr / 0x20 */ guint8 next_ptr; /* 0x1d, addr / 0x20 */ guint8 variant; /* 0x1e */ guint8 checksum; /* 0x1f */ } FuVliUsbhubHeader; G_STATIC_ASSERT(sizeof(FuVliUsbhubHeader) == 0x20); #define FU_VLI_USBHUB_HEADER_STRAPPING1_SELFW1 (1 << 1) #define FU_VLI_USBHUB_HEADER_STRAPPING1_76PIN (1 << 2) #define FU_VLI_USBHUB_HEADER_STRAPPING1_B3UP (1 << 3) #define FU_VLI_USBHUB_HEADER_STRAPPING1_LPC (1 << 4) #define FU_VLI_USBHUB_HEADER_STRAPPING1_U1U2 (1 << 5) #define FU_VLI_USBHUB_HEADER_STRAPPING1_BC (1 << 6) #define FU_VLI_USBHUB_HEADER_STRAPPING1_Q4S (1 << 7) #define FU_VLI_USBHUB_HEADER_STRAPPING2_IDXEN (1 << 0) #define FU_VLI_USBHUB_HEADER_STRAPPING2_FWRTY (1 << 1) #define FU_VLI_USBHUB_HEADER_STRAPPING2_SELFW2 (1 << 7) #define VLI_USBHUB_FLASHMAP_ADDR_TO_IDX(addr) (addr / 0x20) #define VLI_USBHUB_FLASHMAP_IDX_TO_ADDR(addr) (addr * 0x20) #define VLI_USBHUB_FLASHMAP_IDX_HD1 0x00 /* factory firmware */ #define VLI_USBHUB_FLASHMAP_IDX_HD2 0x80 /* update firmware */ #define VLI_USBHUB_FLASHMAP_IDX_INVALID 0xff #define VLI_USBHUB_FLASHMAP_ADDR_HD1 0x0 #define VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP 0x1800 #define VLI_USBHUB_FLASHMAP_ADDR_HD2 0x1000 #define VLI_USBHUB_FLASHMAP_ADDR_FW 0x2000 #define VLI_USBHUB_FLASHMAP_ADDR_PD_LEGACY 0x10000 #define VLI_USBHUB_FLASHMAP_ADDR_PD 0x20000 #define VLI_USBHUB_FLASHMAP_ADDR_PD_BACKUP 0x30000 guint8 fu_vli_usbhub_header_crc8 (FuVliUsbhubHeader *hdr); void fu_vli_usbhub_header_to_string (FuVliUsbhubHeader *hdr, guint idt, GString *str); fwupd-1.3.9/plugins/vli/fu-vli-usbhub-device.c000066400000000000000000001007271362775233600212420ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-chunk.h" #include "fu-firmware.h" #include "fu-vli-usbhub-common.h" #include "fu-vli-usbhub-device.h" #include "fu-vli-usbhub-firmware.h" #include "fu-vli-usbhub-i2c-device.h" #include "fu-vli-usbhub-pd-common.h" #include "fu-vli-usbhub-pd-device.h" struct _FuVliUsbhubDevice { FuVliDevice parent_instance; gboolean disable_powersave; guint8 update_protocol; FuVliUsbhubHeader hd1_hdr; /* factory */ FuVliUsbhubHeader hd2_hdr; /* update */ }; G_DEFINE_TYPE (FuVliUsbhubDevice, fu_vli_usbhub_device, FU_TYPE_VLI_DEVICE) static void fu_vli_usbhub_device_to_string (FuVliDevice *device, guint idt, GString *str) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE (device); fu_common_string_append_kb (str, idt, "DisablePowersave", self->disable_powersave); fu_common_string_append_kx (str, idt, "UpdateProtocol", self->update_protocol); if (self->update_protocol >= 0x2) { fu_common_string_append_kv (str, idt, "H1Hdr@0x0", NULL); fu_vli_usbhub_header_to_string (&self->hd1_hdr, idt + 1, str); fu_common_string_append_kv (str, idt, "H2Hdr@0x1000", NULL); fu_vli_usbhub_header_to_string (&self->hd2_hdr, idt + 1, str); } } gboolean fu_vli_usbhub_device_i2c_read (FuVliUsbhubDevice *self, guint8 cmd, guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); guint16 value = ((guint16) FU_VLI_USBHUB_I2C_ADDR_WRITE << 8) | cmd; guint16 index = (guint16) FU_VLI_USBHUB_I2C_ADDR_READ << 8; 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, FU_VLI_USBHUB_I2C_R_VDR, value, index, buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to read I2C: "); return FALSE; } if (g_getenv ("FWUPD_VLI_USBHUB_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "I2cReadData", buf, 0x1); return TRUE; } gboolean fu_vli_usbhub_device_i2c_read_status (FuVliUsbhubDevice *self, FuVliUsbhubI2cStatus *status, GError **error) { guint8 buf[1] = { 0xff }; if (!fu_vli_usbhub_device_i2c_read (self, FU_VLI_USBHUB_I2C_CMD_READ_STATUS, buf, sizeof(buf), error)) return FALSE; if (status != NULL) *status = buf[0]; return TRUE; } gboolean fu_vli_usbhub_device_i2c_write_data (FuVliUsbhubDevice *self, guint8 skip_s, guint8 skip_p, const guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); guint16 value = (((guint16) skip_s) << 8) | skip_p; if (g_getenv ("FWUPD_VLI_USBHUB_VERBOSE") != NULL) fu_common_dump_raw (G_LOG_DOMAIN, "I2cWriteData", buf, bufsz); 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, FU_VLI_USBHUB_I2C_W_VDR, value, 0x0, (guint8 *) buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to write I2C @0x%x: ", value); return FALSE; } return TRUE; } gboolean fu_vli_usbhub_device_i2c_write (FuVliUsbhubDevice *self, guint8 cmd, const guint8 *buf, gsize bufsz, GError **error) { guint8 buf2[10] = { FU_VLI_USBHUB_I2C_ADDR_WRITE, cmd, 0x0 }; if (!fu_memcpy_safe (buf2, sizeof(buf2), 0x2, buf, bufsz, 0x0, bufsz, error)) return FALSE; return fu_vli_usbhub_device_i2c_write_data (self, 0x0, 0x0, buf2, bufsz + 2, error); } static gboolean fu_vli_usbhub_device_vdr_unlock_813 (FuVliUsbhubDevice *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, 0x85, 0x8786, 0x8988, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to UnLock_VL813: "); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_read_reg (FuVliUsbhubDevice *self, guint16 addr, guint8 *buf, GError **error) { if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, addr >> 8, addr & 0xff, 0x0, buf, 0x1, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to read register 0x%x: ", addr); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_write_reg (FuVliUsbhubDevice *self, guint16 addr, guint8 value, GError **error) { if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, addr >> 8, addr & 0xff, (guint16) value, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to write register 0x%x: ", addr); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_spi_read_status (FuVliDevice *self, guint8 *status, GError **error) { guint8 spi_cmd = 0x0; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_READ_STATUS, &spi_cmd, error)) return FALSE; return g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc1, spi_cmd, 0x0000, status, 0x1, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_usbhub_device_spi_read_data (FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_READ_DATA, &spi_cmd, error)) return FALSE; value = ((addr >> 8) & 0xff00) | spi_cmd; index = ((addr << 8) & 0xff00) | ((addr >> 8) & 0x00ff); return g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc4, value, index, buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_usbhub_device_spi_write_status (FuVliDevice *self, guint8 status, GError **error) { guint8 spi_cmd = 0x0; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_WRITE_STATUS, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd1, spi_cmd, 0x0000, &status, 0x1, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } /* Fix_For_GD_&_EN_SPI_Flash */ g_usleep (100 * 1000); return TRUE; } static gboolean fu_vli_usbhub_device_spi_write_enable (FuVliDevice *self, GError **error) { guint8 spi_cmd = 0x0; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_WRITE_EN, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd1, spi_cmd, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to write enable SPI: "); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_spi_chip_erase (FuVliDevice *self, GError **error) { guint8 spi_cmd = 0x0; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_CHIP_ERASE, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd1, spi_cmd, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_spi_sector_erase (FuVliDevice *self, guint32 addr, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_SECTOR_ERASE, &spi_cmd, error)) return FALSE; value = ((addr >> 8) & 0xff00) | spi_cmd; index = ((addr << 8) & 0xff00) | ((addr >> 8) & 0x00ff); return g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd4, value, index, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_usbhub_device_spi_write_data (FuVliDevice *self, guint32 addr, const guint8 *buf, gsize bufsz, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_PAGE_PROG, &spi_cmd, error)) return FALSE; value = ((addr >> 8) & 0xff00) | spi_cmd; index = ((addr << 8) & 0xff00) | ((addr >> 8) & 0x00ff); if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd4, value, index, (guint8 *) buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_reset (FuVliDevice *device, GError **error) { return g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (device)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xf6, 0x0040, 0x0002, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } /* disable hub sleep states -- not really required by 815~ hubs */ static gboolean fu_vli_usbhub_device_disable_u1u2 (FuVliUsbhubDevice *self, GError **error) { guint8 buf = 0x0; /* clear Reg[0xF8A2] bit_3 & bit_7 -- also * clear Total Switch / Flag To Disable FW Auto-Reload Function */ if (!fu_vli_usbhub_device_read_reg (self, 0xf8a2, &buf, error)) return FALSE; buf &= 0x77; if (!fu_vli_usbhub_device_write_reg (self, 0xf8a2, buf, error)) return FALSE; /* clear Reg[0xF832] bit_0 & bit_1 */ if (!fu_vli_usbhub_device_read_reg (self, 0xf832, &buf, error)) return FALSE; buf &= 0xfc; if (!fu_vli_usbhub_device_write_reg (self, 0xf832, buf, error)) return FALSE; /* clear Reg[0xF920] bit_1 & bit_2 */ if (!fu_vli_usbhub_device_read_reg (self, 0xf920, &buf, error)) return FALSE; buf &= 0xf9; if (!fu_vli_usbhub_device_write_reg (self, 0xf920, buf, error)) return FALSE; /* set Reg[0xF836] bit_3 */ if (!fu_vli_usbhub_device_read_reg (self, 0xf836, &buf, error)) return FALSE; buf |= 0x08; if (!fu_vli_usbhub_device_write_reg (self, 0xf836, buf, error)) return FALSE; return TRUE; } static gboolean fu_vli_usbhub_device_guess_kind (FuVliUsbhubDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); guint8 b811P812 = 0x0; guint8 b820Q7Q8 = 0x0; guint8 chipid1 = 0x0; guint8 chipid2 = 0x0; guint8 chipid12 = 0x0; guint8 chipid22 = 0x0; guint8 chipver = 0x0; guint8 chipver2 = 0x0; gint tPid = g_usb_device_get_pid (usb_device) & 0x0fff; if (!fu_vli_usbhub_device_read_reg (self, 0xf88c, &chipver, error)) { g_prefix_error (error, "Read_ChipVer failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg (self, 0xf63f, &chipver2, error)) { g_prefix_error (error, "Read_ChipVer2 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg (self, 0xf800, &b811P812, error)) { g_prefix_error (error, "Read_811P812 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg (self, 0xf88e, &chipid1, error)) { g_prefix_error (error, "Read_ChipID1 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg (self, 0xf88f, &chipid2, error)) { g_prefix_error (error, "Read_ChipID2 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg (self, 0xf64e, &chipid12, error)) { g_prefix_error (error, "Read_ChipID12 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg (self, 0xf64f, &chipid22, error)) { g_prefix_error (error, "Read_ChipID22 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg (self, 0xf651, &b820Q7Q8, error)) { g_prefix_error (error, "Read_820Q7Q8 failed: "); return FALSE; } g_debug ("chipver = 0x%02x", chipver); g_debug ("chipver2 = 0x%02x", chipver2); g_debug ("b811P812 = 0x%02x", b811P812); g_debug ("chipid1 = 0x%02x", chipid1); g_debug ("chipid2 = 0x%02x", chipid2); g_debug ("chipid12 = 0x%02x", chipid12); g_debug ("chipid22 = 0x%02x", chipid22); g_debug ("b820Q7Q8 = 0x%02x", b820Q7Q8); if (chipid2 == 0x35 && chipid1 == 0x07) { fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL210); } else if (chipid2 == 0x35 && chipid1 == 0x18) { if (b820Q7Q8 & (1 << 2)) fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL820Q8); else fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL820Q7); } else if (chipid2 == 0x35 && chipid1 == 0x31) { fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL815); } else if (chipid2 == 0x35 && chipid1 == 0x38) { fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL817); } else if (chipid2 == 0x35 && chipid1 == 0x45) { fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL211); } else if (chipid22 == 0x35 && chipid12 == 0x53) { fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL120); } else if (chipid2 == 0x35 && chipid1 == 0x57) { fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL819); } else if (tPid == 0x810) { fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL810); } else if (tPid == 0x811) { fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL811); } else if ((b811P812 & ((1 << 5) | (1 << 4))) == 0) { if (chipver == 0x10) fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL811PB0); else fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL811PB3); } else if ((b811P812 & ((1 << 5) | (1 << 4))) == (1 << 4)) { fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL812Q4S); } else if ((b811P812 & ((1 << 5) | (1 << 4))) == ((1 << 5) | (1 << 4))) { if (chipver == 0x10) fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL812B0); else fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL812B3); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "hardware is not supported"); return FALSE; } /* success */ return TRUE; } static gboolean fu_vli_usbhub_device_probe (FuDevice *device, GError **error) { guint16 usbver = fu_usb_device_get_spec (FU_USB_DEVICE (device)); /* quirks now applied... */ if (usbver > 0x0300 || fu_device_has_custom_flag (device, "usb3")) { fu_device_set_summary (device, "USB 3.x Hub"); } else if (usbver > 0x0200 || fu_device_has_custom_flag (device, "usb2")) { fu_device_set_summary (device, "USB 2.x Hub"); } else { fu_device_set_summary (device, "USB Hub"); } return TRUE; } static gboolean fu_vli_usbhub_device_pd_setup (FuVliUsbhubDevice *self, GError **error) { FuVliPdHdr hdr = { 0x0 }; g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* legacy location */ if (!fu_vli_device_spi_read_block (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_PD_LEGACY + VLI_USBHUB_PD_FLASHMAP_ADDR_LEGACY, (guint8 *) &hdr, sizeof(hdr), error)) { g_prefix_error (error, "failed to read legacy PD header"); return FALSE; } /* new location */ if (GUINT16_FROM_LE (hdr.vid) != 0x2109) { g_debug ("PD VID was 0x%04x trying new location", GUINT16_FROM_LE (hdr.vid)); if (!fu_vli_device_spi_read_block (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_PD + VLI_USBHUB_PD_FLASHMAP_ADDR, (guint8 *) &hdr, sizeof(hdr), error)) { g_prefix_error (error, "failed to read PD header"); return FALSE; } } /* just empty space */ if (hdr.fwver == G_MAXUINT32) { g_debug ("no PD device header found"); return TRUE; } /* add child */ dev = fu_vli_usbhub_pd_device_new (&hdr); if (!fu_device_probe (dev, &error_local)) { g_warning ("cannot create PD device: %s", error_local->message); return TRUE; } fu_device_add_child (FU_DEVICE (self), dev); return TRUE; } static gboolean fu_vli_usbhub_device_i2c_setup (FuVliUsbhubDevice *self, GError **error) { g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* add child */ dev = fu_vli_usbhub_i2c_device_new (self); if (!fu_device_probe (dev, error)) return FALSE; if (!fu_device_setup (dev, &error_local)) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_debug ("%s", error_local->message); } else { g_warning ("cannot create I²C device: %s", error_local->message); } return TRUE; } fu_device_add_child (FU_DEVICE (self), dev); return TRUE; } static gboolean fu_vli_usbhub_device_setup (FuVliDevice *device, GError **error) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE (device); g_autoptr(GError) error_tmp = NULL; /* try to read a block of data which will fail for 813-type devices */ if (fu_device_has_custom_flag (FU_DEVICE (self), "needs-unlock-legacy813") && !fu_vli_device_spi_read_block (FU_VLI_DEVICE (self), 0x0, (guint8 *) &self->hd1_hdr, sizeof(self->hd1_hdr), &error_tmp)) { g_warning ("failed to read, trying to unlock 813: %s", error_tmp->message); if (!fu_vli_usbhub_device_vdr_unlock_813 (self, error)) return FALSE; if (!fu_vli_device_spi_read_block (FU_VLI_DEVICE (self), 0x0, (guint8 *) &self->hd1_hdr, sizeof(self->hd1_hdr), error)) { g_prefix_error (error, "813 unlock fail: "); return FALSE; } g_debug ("813 unlock OK"); /* VL813 & VL210 have same PID (0x0813), and only VL813 can reply */ fu_vli_device_set_kind (FU_VLI_DEVICE (self), FU_VLI_DEVICE_KIND_VL813); } else { if (!fu_vli_usbhub_device_guess_kind (self, error)) return FALSE; } /* read HD1 (factory) header */ if (!fu_vli_device_spi_read_block (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_HD1, (guint8 *) &self->hd1_hdr, sizeof(self->hd1_hdr), error)) { g_prefix_error (error, "failed to read HD1 header"); return FALSE; } /* detect update protocol from the device ID */ switch (GUINT16_FROM_BE(self->hd1_hdr.dev_id) >> 8) { /* VL810~VL813 */ case 0x0d: self->update_protocol = 0x1; self->disable_powersave = TRUE; fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_install_duration (FU_DEVICE (self), 10); /* seconds */ break; /* VL817~ */ case 0x05: self->update_protocol = 0x2; fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_install_duration (FU_DEVICE (self), 15); /* seconds */ break; default: g_warning ("unknown update protocol, device_id=0x%x", GUINT16_FROM_BE(self->hd1_hdr.dev_id)); break; } /* read HD2 (update) header */ if (self->update_protocol >= 0x2) { if (!fu_vli_device_spi_read_block (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_HD2, (guint8 *) &self->hd2_hdr, sizeof(self->hd2_hdr), error)) { g_prefix_error (error, "failed to read HD2 header"); return FALSE; } } /* detect the PD child */ if (fu_device_has_custom_flag (FU_DEVICE (self), "has-shared-spi-pd")) { if (!fu_vli_usbhub_device_pd_setup (self, error)) return FALSE; } /* detect the I²C child */ if (fu_device_has_custom_flag (FU_DEVICE (self), "has-shared-spi-i2c")) { if (!fu_vli_usbhub_device_i2c_setup (self, error)) return FALSE; } /* success */ return TRUE; } static FuFirmware * fu_vli_usbhub_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE (device); FuVliDeviceKind device_kind; guint16 device_id; g_autoptr(FuFirmware) firmware = fu_vli_usbhub_firmware_new (); /* check size */ if (g_bytes_get_size (fw) < fu_device_get_firmware_size_min (device)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too small, got 0x%x, expected >= 0x%x", (guint) g_bytes_get_size (fw), (guint) fu_device_get_firmware_size_min (device)); return NULL; } if (g_bytes_get_size (fw) > fu_device_get_firmware_size_max (device)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too large, got 0x%x, expected <= 0x%x", (guint) g_bytes_get_size (fw), (guint) fu_device_get_firmware_size_max (device)); return NULL; } /* check is compatible with firmware */ fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; device_kind = fu_vli_usbhub_firmware_get_device_kind (FU_VLI_USBHUB_FIRMWARE (firmware)); if (fu_vli_device_get_kind (FU_VLI_DEVICE (self)) != device_kind) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got %s, expected %s", fu_vli_common_device_kind_to_string (device_kind), fu_vli_common_device_kind_to_string (fu_vli_device_get_kind (FU_VLI_DEVICE (self)))); return NULL; } device_id = fu_vli_usbhub_firmware_get_device_id (FU_VLI_USBHUB_FIRMWARE (firmware)); if (GUINT16_FROM_BE(self->hd1_hdr.dev_id) != device_id) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got 0x%04x, expected 0x%04x", device_id, GUINT16_FROM_BE(self->hd1_hdr.dev_id)); return NULL; } /* we could check this against flags */ g_debug ("parsed version: %s", fu_firmware_get_version (firmware)); return g_steal_pointer (&firmware); } static gboolean fu_vli_usbhub_device_update_v1 (FuVliUsbhubDevice *self, FuFirmware *firmware, GError **error) { gsize bufsz = 0; const guint8 *buf; g_autoptr(GBytes) fw = NULL; /* simple image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* erase */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); if (!fu_vli_device_spi_erase_all (FU_VLI_DEVICE (self), error)) { g_prefix_error (error, "failed to erase chip: "); return FALSE; } /* write in chunks */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); buf = g_bytes_get_data (fw, &bufsz); if (!fu_vli_device_spi_write (FU_VLI_DEVICE (self), 0x0, buf, bufsz, error)) return FALSE; /* success */ return TRUE; } /* if no header1 or ROM code update, write data directly */ static gboolean fu_vli_usbhub_device_update_v2_recovery (FuVliUsbhubDevice *self, GBytes *fw, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data (fw, &bufsz); /* erase */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); for (guint32 addr = 0; addr < bufsz; addr += 0x1000) { if (!fu_vli_device_spi_erase_sector (FU_VLI_DEVICE (self), addr, error)) { g_prefix_error (error, "failed to erase sector @0x%x", addr); return FALSE; } fu_device_set_progress_full (FU_DEVICE (self), (gsize) addr, bufsz); } /* write in chunks */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); if (!fu_vli_device_spi_write (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_HD1, buf, bufsz, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_vli_usbhub_device_hd1_is_valid (FuVliUsbhubHeader *hdr) { if (hdr->prev_ptr != VLI_USBHUB_FLASHMAP_IDX_INVALID) return FALSE; if (hdr->checksum != fu_vli_usbhub_header_crc8 (hdr)) return FALSE; return TRUE; } static gboolean fu_vli_usbhub_device_hd1_recover (FuVliUsbhubDevice *self, FuVliUsbhubHeader *hdr, GError **error) { /* point to HD2, i.e. updated firmware */ if (hdr->next_ptr != VLI_USBHUB_FLASHMAP_IDX_HD2) { hdr->next_ptr = VLI_USBHUB_FLASHMAP_IDX_HD2; hdr->checksum = fu_vli_usbhub_header_crc8 (hdr); } /* write new header block */ if (!fu_vli_device_spi_erase_sector (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_HD1, error)) { g_prefix_error (error, "failed to erase header1 sector at 0x%x: ", (guint) VLI_USBHUB_FLASHMAP_ADDR_HD1); return FALSE; } if (!fu_vli_device_spi_write_block (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_HD1, (const guint8 *) hdr, sizeof(FuVliUsbhubHeader), error)) { g_prefix_error (error, "failed to write header1 block at 0x%x: ", (guint) VLI_USBHUB_FLASHMAP_ADDR_HD1); return FALSE; } /* update the cached copy */ memcpy (&self->hd1_hdr, hdr, sizeof(self->hd1_hdr)); return TRUE; } static gboolean fu_vli_usbhub_device_update_v2 (FuVliUsbhubDevice *self, FuFirmware *firmware, GError **error) { gsize buf_fwsz = 0; guint32 hd1_fw_sz; guint32 hd2_fw_sz; guint32 hd2_fw_addr; guint32 hd2_fw_offset; const guint8 *buf_fw; FuVliUsbhubHeader hdr = { 0x0 }; g_autoptr(GBytes) fw = NULL; /* simple image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* root header is valid */ if (fu_vli_usbhub_device_hd1_is_valid (&self->hd1_hdr)) { /* no update has ever been done */ if (self->hd1_hdr.next_ptr != VLI_USBHUB_FLASHMAP_IDX_HD2) { /* backup HD1 before recovering */ if (!fu_vli_device_spi_erase_sector (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_HD2, error)) { g_prefix_error (error, "failed to erase sector at header 1: "); return FALSE; } if (!fu_vli_device_spi_write_block (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP, (const guint8 *) &self->hd1_hdr, sizeof(hdr), error)) { g_prefix_error (error, "failed to write block at header 1: "); return FALSE; } if (!fu_vli_usbhub_device_hd1_recover (self, &self->hd1_hdr, error)) { g_prefix_error (error, "failed to write header: "); return FALSE; } } /* copy the header from the backup zone */ } else { g_debug ("HD1 was invalid, reading backup"); if (!fu_vli_device_spi_read_block (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP, (guint8 *) &self->hd1_hdr, sizeof(hdr), error)) { g_prefix_error (error, "failed to read root header from 0x%x", (guint) VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP); return FALSE; } if (!fu_vli_usbhub_device_hd1_is_valid (&self->hd1_hdr)) { g_debug ("backup header is also invalid, starting recovery"); return fu_vli_usbhub_device_update_v2_recovery (self, fw, error); } if (!fu_vli_usbhub_device_hd1_recover (self, &self->hd1_hdr, error)) { g_prefix_error (error, "failed to get root header in backup zone: "); return FALSE; } } /* align the update fw address to the sector after the factory size */ hd1_fw_sz = GUINT16_FROM_BE(self->hd1_hdr.usb3_fw_sz); if (hd1_fw_sz > 0xF000) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "FW1 size abnormal 0x%x", (guint) hd1_fw_sz); return FALSE; } hd2_fw_addr = (hd1_fw_sz + 0xfff) & 0xf000; hd2_fw_addr += VLI_USBHUB_FLASHMAP_ADDR_FW; /* get the size and offset of the update firmware */ buf_fw = g_bytes_get_data (fw, &buf_fwsz); memcpy (&hdr, buf_fw, sizeof(hdr)); hd2_fw_sz = GUINT16_FROM_BE(hdr.usb3_fw_sz); hd2_fw_offset = GUINT16_FROM_BE(hdr.usb3_fw_addr); g_debug ("FW2 @0x%x (length 0x%x, offset 0x%x)", hd2_fw_addr, hd2_fw_sz, hd2_fw_offset); /* make space */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); if (!fu_vli_device_spi_erase (FU_VLI_DEVICE (self), hd2_fw_addr, hd2_fw_sz, error)) return FALSE; /* perform the actual write */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); if (!fu_vli_device_spi_write (FU_VLI_DEVICE (self), hd2_fw_addr, buf_fw + hd2_fw_offset, hd2_fw_sz, error)) { g_prefix_error (error, "failed to write payload: "); return FALSE; } /* map into header */ if (!fu_memcpy_safe ((guint8 *) &self->hd2_hdr, sizeof(hdr), 0x0, buf_fw, buf_fwsz, 0x0, sizeof(hdr), error)) { g_prefix_error (error, "failed to read header: "); return FALSE; } /* write new HD2 */ self->hd2_hdr.usb3_fw_addr = GUINT16_TO_BE(hd2_fw_addr & 0xffff); self->hd2_hdr.usb3_fw_addr_high = (guint8) (hd2_fw_addr >> 16); self->hd2_hdr.prev_ptr = VLI_USBHUB_FLASHMAP_IDX_HD1; self->hd2_hdr.next_ptr = VLI_USBHUB_FLASHMAP_IDX_INVALID; self->hd2_hdr.checksum = fu_vli_usbhub_header_crc8 (&self->hd2_hdr); if (!fu_vli_device_spi_erase_sector (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_HD2, error)) { g_prefix_error (error, "failed to erase sectors for HD2: "); return FALSE; } if (!fu_vli_device_spi_write_block (FU_VLI_DEVICE (self), VLI_USBHUB_FLASHMAP_ADDR_HD2, (const guint8 *) &self->hd2_hdr, sizeof(self->hd2_hdr), error)) { g_prefix_error (error, "failed to write HD2: "); return FALSE; } /* success */ return TRUE; } static FuFirmware * fu_vli_usbhub_device_read_firmware (FuDevice *device, GError **error) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE (device); g_autoptr(GBytes) fw = NULL; fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY); fw = fu_vli_device_spi_read (FU_VLI_DEVICE (self), 0x0, fu_device_get_firmware_size_max (device), error); if (fw == NULL) return NULL; return fu_firmware_new_from_bytes (fw); } static gboolean fu_vli_usbhub_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE (device); /* disable powersaving if required */ if (self->disable_powersave) { if (!fu_vli_usbhub_device_disable_u1u2 (self, error)) { g_prefix_error (error, "disabling powersave failed: "); return FALSE; } } /* use correct method */ if (self->update_protocol == 0x1) return fu_vli_usbhub_device_update_v1 (self, firmware, error); if (self->update_protocol == 0x2) return fu_vli_usbhub_device_update_v2 (self, firmware, error); /* not sure what to do */ g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "update protocol 0x%x not supported", self->update_protocol); return FALSE; } static void fu_vli_usbhub_device_init (FuVliUsbhubDevice *self) { fu_device_add_icon (FU_DEVICE (self), "audio-card"); fu_device_set_protocol (FU_DEVICE (self), "com.vli.usbhub"); fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_vli_usbhub_device_class_init (FuVliUsbhubDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuVliDeviceClass *klass_vli_device = FU_VLI_DEVICE_CLASS (klass); klass_device->probe = fu_vli_usbhub_device_probe; klass_device->read_firmware = fu_vli_usbhub_device_read_firmware; klass_device->write_firmware = fu_vli_usbhub_device_write_firmware; klass_device->prepare_firmware = fu_vli_usbhub_device_prepare_firmware; klass_vli_device->to_string = fu_vli_usbhub_device_to_string; klass_vli_device->setup = fu_vli_usbhub_device_setup; klass_vli_device->reset = fu_vli_usbhub_device_reset; klass_vli_device->spi_chip_erase = fu_vli_usbhub_device_spi_chip_erase; klass_vli_device->spi_sector_erase = fu_vli_usbhub_device_spi_sector_erase; klass_vli_device->spi_read_data = fu_vli_usbhub_device_spi_read_data; klass_vli_device->spi_read_status = fu_vli_usbhub_device_spi_read_status; klass_vli_device->spi_write_data = fu_vli_usbhub_device_spi_write_data; klass_vli_device->spi_write_enable = fu_vli_usbhub_device_spi_write_enable; klass_vli_device->spi_write_status = fu_vli_usbhub_device_spi_write_status; } fwupd-1.3.9/plugins/vli/fu-vli-usbhub-device.h000066400000000000000000000021271362775233600212420ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-vli-device.h" #include "fu-vli-usbhub-i2c-common.h" #define FU_TYPE_VLI_USBHUB_DEVICE (fu_vli_usbhub_device_get_type ()) G_DECLARE_FINAL_TYPE (FuVliUsbhubDevice, fu_vli_usbhub_device, FU, VLI_USBHUB_DEVICE, FuVliDevice) struct _FuVliUsbhubDeviceClass { FuVliDeviceClass parent_class; }; gboolean fu_vli_usbhub_device_i2c_read (FuVliUsbhubDevice *self, guint8 cmd, guint8 *buf, gsize bufsz, GError **error); gboolean fu_vli_usbhub_device_i2c_read_status (FuVliUsbhubDevice *self, FuVliUsbhubI2cStatus *status, GError **error); gboolean fu_vli_usbhub_device_i2c_write (FuVliUsbhubDevice *self, guint8 cmd, const guint8 *buf, gsize bufsz, GError **error); gboolean fu_vli_usbhub_device_i2c_write_data (FuVliUsbhubDevice *self, guint8 skip_s, guint8 skip_p, const guint8 *buf, gsize bufsz, GError **error); fwupd-1.3.9/plugins/vli/fu-vli-usbhub-firmware.c000066400000000000000000000142651362775233600216200ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-vli-usbhub-firmware.h" struct _FuVliUsbhubFirmware { FuFirmwareClass parent_instance; FuVliDeviceKind device_kind; FuVliUsbhubHeader hdr; }; G_DEFINE_TYPE (FuVliUsbhubFirmware, fu_vli_usbhub_firmware, FU_TYPE_FIRMWARE) FuVliDeviceKind fu_vli_usbhub_firmware_get_device_kind (FuVliUsbhubFirmware *self) { g_return_val_if_fail (FU_IS_VLI_USBHUB_FIRMWARE (self), 0); return self->device_kind; } guint16 fu_vli_usbhub_firmware_get_device_id (FuVliUsbhubFirmware *self) { g_return_val_if_fail (FU_IS_VLI_USBHUB_FIRMWARE (self), 0); return GUINT16_FROM_BE(self->hdr.dev_id); } static void fu_vli_usbhub_firmware_to_string (FuFirmware *firmware, guint idt, GString *str) { FuVliUsbhubFirmware *self = FU_VLI_USBHUB_FIRMWARE (firmware); fu_common_string_append_kv (str, idt, "DeviceKind", fu_vli_common_device_kind_to_string (self->device_kind)); fu_vli_usbhub_header_to_string (&self->hdr, idt, str); } static gboolean fu_vli_usbhub_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuVliUsbhubFirmware *self = FU_VLI_USBHUB_FIRMWARE (firmware); gsize bufsz = 0; guint16 adr_ofs = 0; guint16 version = 0x0; guint8 tmp = 0x0; const guint8 *buf = g_bytes_get_data (fw, &bufsz); g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (fw); /* map into header */ if (!fu_memcpy_safe ((guint8 *) &self->hdr, sizeof(self->hdr), 0x0, buf, bufsz, 0x0, sizeof(self->hdr), error)) { g_prefix_error (error, "failed to read header: "); return FALSE; } /* get firmware versions */ switch (GUINT16_FROM_BE(self->hdr.dev_id)) { case 0x0d12: /* VL81x */ if (!fu_common_read_uint16_safe (buf, bufsz, 0x1f4c, &version, G_LITTLE_ENDIAN, error)) { g_prefix_error (error, "failed to get version: "); return FALSE; } version |= (self->hdr.strapping1 >> 4) & 0x07; if ((version & 0x0f) == 0x04 ) { if (!fu_common_read_uint8_safe (buf, bufsz, 0x700d, &tmp, error)) { g_prefix_error (error, "failed to get version increment: "); return FALSE; } if (tmp & 0x40) version += 1; } break; case 0x0507: /* VL210 */ if (!fu_common_read_uint16_safe (buf, bufsz, 0x8f0c, &version, G_LITTLE_ENDIAN, error)) { g_prefix_error (error, "failed to get version: "); return FALSE; } version |= (self->hdr.strapping1 >> 4) & 0x07; if ((version & 0x0f) == 0x04) version += 1; break; default: /* U3ID_Address_In_FW_Zone */ if (!fu_common_read_uint16_safe (buf, bufsz, 0x8000, &adr_ofs, G_BIG_ENDIAN, error)) { g_prefix_error (error, "failed to get offset addr: "); return FALSE; } if (!fu_common_read_uint16_safe (buf, bufsz, adr_ofs + 0x2000 + 0x04, /* U3-M? */ &version, G_LITTLE_ENDIAN, error)) { g_prefix_error (error, "failed to get offset version: "); return FALSE; } version |= (self->hdr.strapping1 >> 4) & 0x07; } /* version is set */ if (version != 0x0) { g_autofree gchar *version_str = NULL; version_str = fu_common_version_from_uint16 (version, FWUPD_VERSION_FORMAT_BCD); fu_firmware_set_version (firmware, version_str); } /* get device type from firmware image */ switch (GUINT16_FROM_BE(self->hdr.dev_id)) { case 0x0d12: { guint16 binver1 = 0x0; guint16 binver2 = 0x0; guint16 usb2_fw_addr = GUINT16_FROM_BE(self->hdr.usb2_fw_addr) + 0x1ff1; guint16 usb3_fw_addr = GUINT16_FROM_BE(self->hdr.usb3_fw_addr) + 0x1ffa; if (!fu_common_read_uint16_safe (buf, bufsz, usb2_fw_addr, &binver1, G_LITTLE_ENDIAN, error)) { g_prefix_error (error, "failed to get binver1: "); return FALSE; } if (!fu_common_read_uint16_safe (buf, bufsz, usb3_fw_addr, &binver2, G_LITTLE_ENDIAN, error)) { g_prefix_error (error, "failed to get binver2: "); return FALSE; } /* VL813 == VT3470 */ if ((binver1 == 0xb770 && binver2 == 0xb770) || (binver1 == 0xb870 && binver2 == 0xb870)) { self->device_kind = FU_VLI_DEVICE_KIND_VL813; /* VLQ4S == VT3470 (Q4S) */ } else if (self->hdr.strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_Q4S) { self->device_kind = FU_VLI_DEVICE_KIND_VL812Q4S; /* VL812 == VT3470 (812/813) */ } else if (self->hdr.strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_76PIN) { /* is B3 */ if (self->hdr.strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_B3UP) self->device_kind = FU_VLI_DEVICE_KIND_VL812B3; else self->device_kind = FU_VLI_DEVICE_KIND_VL812B0; /* VL811P == VT3470 */ } else { /* is B3 */ if (self->hdr.strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_B3UP) self->device_kind = FU_VLI_DEVICE_KIND_VL811PB3; else self->device_kind = FU_VLI_DEVICE_KIND_VL811PB0; } break; } case 0x0507: /* VL210 == VT3507 */ self->device_kind = FU_VLI_DEVICE_KIND_VL210; break; case 0x0545: /* VL211 == VT3545 */ self->device_kind = FU_VLI_DEVICE_KIND_VL211; break; case 0x0518: /* VL820 == VT3518 */ if (!fu_common_read_uint8_safe (buf, bufsz, 0xf000, &tmp, error)) { g_prefix_error (error, "failed to get Q7/Q8 difference: "); return FALSE; } if (tmp & (1 << 0)) self->device_kind = FU_VLI_DEVICE_KIND_VL820Q8; else self->device_kind = FU_VLI_DEVICE_KIND_VL820Q7; break; case 0x0538: /* VL817 == VT3538 */ self->device_kind = FU_VLI_DEVICE_KIND_VL817; break; case 0x0553: /* VL120 == VT3553 */ self->device_kind = FU_VLI_DEVICE_KIND_VL120; break; case 0x0557: /* VL819 == VT3557 */ self->device_kind = FU_VLI_DEVICE_KIND_VL819; break; default: break; } /* whole image */ fu_firmware_add_image (firmware, img); return TRUE; } static void fu_vli_usbhub_firmware_init (FuVliUsbhubFirmware *self) { } static void fu_vli_usbhub_firmware_class_init (FuVliUsbhubFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->parse = fu_vli_usbhub_firmware_parse; klass_firmware->to_string = fu_vli_usbhub_firmware_to_string; } FuFirmware * fu_vli_usbhub_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_VLI_USBHUB_FIRMWARE, NULL)); } fwupd-1.3.9/plugins/vli/fu-vli-usbhub-firmware.h000066400000000000000000000011521362775233600216140ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #include "fu-vli-usbhub-common.h" #define FU_TYPE_VLI_USBHUB_FIRMWARE (fu_vli_usbhub_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuVliUsbhubFirmware, fu_vli_usbhub_firmware, FU, VLI_USBHUB_FIRMWARE, FuFirmware) FuFirmware *fu_vli_usbhub_firmware_new (void); FuVliDeviceKind fu_vli_usbhub_firmware_get_device_kind (FuVliUsbhubFirmware *self); guint16 fu_vli_usbhub_firmware_get_device_id (FuVliUsbhubFirmware *self); fwupd-1.3.9/plugins/vli/fu-vli-usbhub-i2c-common.c000066400000000000000000000026641362775233600217470ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-usbhub-i2c-common.h" gboolean fu_vli_usbhub_i2c_check_status (FuVliUsbhubI2cStatus status, GError **error) { if (status == FU_VLI_USBHUB_I2C_STATUS_OK) return TRUE; if (status == FU_VLI_USBHUB_I2C_STATUS_HEADER) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Incorrect header value of data frame"); return FALSE; } if (status == FU_VLI_USBHUB_I2C_STATUS_COMMAND) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid command data"); return FALSE; } if (status == FU_VLI_USBHUB_I2C_STATUS_ADDRESS) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid address range"); return FALSE; } if (status == FU_VLI_USBHUB_I2C_STATUS_PACKETSIZE) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Incorrect payload data length"); return FALSE; } if (status == FU_VLI_USBHUB_I2C_STATUS_CHECKSUM) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Incorrect frame data checksum"); return FALSE; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Unknown error [0x%02x]", status); return FALSE; } fwupd-1.3.9/plugins/vli/fu-vli-usbhub-i2c-common.h000066400000000000000000000017061362775233600217500ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" typedef enum { FU_VLI_USBHUB_I2C_STATUS_OK, FU_VLI_USBHUB_I2C_STATUS_HEADER, FU_VLI_USBHUB_I2C_STATUS_COMMAND, FU_VLI_USBHUB_I2C_STATUS_ADDRESS, FU_VLI_USBHUB_I2C_STATUS_PACKETSIZE, FU_VLI_USBHUB_I2C_STATUS_CHECKSUM, } FuVliUsbhubI2cStatus; /* Texas Instruments BSL */ #define FU_VLI_USBHUB_I2C_ADDR_WRITE 0x18 #define FU_VLI_USBHUB_I2C_ADDR_READ 0x19 #define FU_VLI_USBHUB_I2C_CMD_WRITE 0x32 #define FU_VLI_USBHUB_I2C_CMD_READ_STATUS 0x33 #define FU_VLI_USBHUB_I2C_CMD_UPGRADE 0x34 #define FU_VLI_USBHUB_I2C_CMD_READ_VERSIONS 0x40 #define FU_VLI_USBHUB_I2C_R_VDR 0xa0 /* read vendor command */ #define FU_VLI_USBHUB_I2C_W_VDR 0xb0 /* write vendor command */ gboolean fu_vli_usbhub_i2c_check_status (FuVliUsbhubI2cStatus status, GError **error); fwupd-1.3.9/plugins/vli/fu-vli-usbhub-i2c-device.c000066400000000000000000000202641362775233600217120ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-firmware-common.h" #include "fu-ihex-firmware.h" #include "fu-vli-usbhub-common.h" #include "fu-vli-usbhub-device.h" #include "fu-vli-usbhub-i2c-common.h" #include "fu-vli-usbhub-i2c-device.h" struct _FuVliUsbhubI2cDevice { FuDevice parent_instance; FuVliDeviceKind device_kind; }; G_DEFINE_TYPE (FuVliUsbhubI2cDevice, fu_vli_usbhub_i2c_device, FU_TYPE_DEVICE) static void fu_vli_usbhub_i2c_device_to_string (FuDevice *device, guint idt, GString *str) { FuVliUsbhubI2cDevice *self = FU_VLI_USBHUB_I2C_DEVICE (device); fu_common_string_append_kv (str, idt, "DeviceKind", fu_vli_common_device_kind_to_string (self->device_kind)); } static gboolean fu_vli_usbhub_i2c_device_setup (FuDevice *device, GError **error) { FuVliUsbhubI2cDevice *self = FU_VLI_USBHUB_I2C_DEVICE (device); FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE (fu_device_get_parent (device)); guint8 buf[11] = { 0x0 }; g_autofree gchar *instance_id = NULL; g_autofree gchar *version = NULL; /* get versions */ if (!fu_vli_usbhub_device_i2c_read (parent, FU_VLI_USBHUB_I2C_CMD_READ_VERSIONS, buf, sizeof(buf), error)) { g_prefix_error (error, "failed to read versions: "); return FALSE; } if ((buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x00) || (buf[0] == 0xff && buf[1] == 0xff && buf[2] == 0xff)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no %s device detected", fu_vli_common_device_kind_to_string (self->device_kind)); return FALSE; } /* add instance ID */ instance_id = g_strdup_printf ("USB\\VID_%04X&PID_%04X&I2C_%s", fu_usb_device_get_vid (FU_USB_DEVICE (parent)), fu_usb_device_get_pid (FU_USB_DEVICE (parent)), fu_vli_common_device_kind_to_string (self->device_kind)); fu_device_add_instance_id (device, instance_id); /* set version */ version = g_strdup_printf ("%x.%x", buf[1], buf[2]); fu_device_set_version (device, version, FWUPD_VERSION_FORMAT_PAIR); return TRUE; } static gboolean fu_vli_usbhub_i2c_device_detach (FuDevice *device, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE (fu_device_get_parent (device)); const guint8 buf[] = { FU_VLI_USBHUB_I2C_ADDR_WRITE, FU_VLI_USBHUB_I2C_CMD_UPGRADE, }; if (!fu_vli_usbhub_device_i2c_write_data (parent, 0, 0, buf, sizeof(buf), error)) return FALSE; /* avoid power instability */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); g_usleep (5000); /* success */ return TRUE; } static FuFirmware * fu_vli_usbhub_i2c_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_ihex_firmware_new (); fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_tokenize (firmware, fw, flags, error)) return NULL; return g_steal_pointer (&firmware); } static gboolean fu_vli_usbhub_i2c_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE (fu_device_get_parent (device)); GPtrArray *records = fu_ihex_firmware_get_records (FU_IHEX_FIRMWARE (firmware)); guint16 usbver = fu_usb_device_get_spec (FU_USB_DEVICE (device)); g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new (parent, error); if (locker == NULL) return FALSE; /* transfer by I²C write, and check status by I²C read */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint j = 0; j < records->len; j++) { FuIhexFirmwareRecord *rcd = g_ptr_array_index (records, j); const gchar *line = rcd->buf->str; gsize bufsz; guint8 buf[0x40] = { 0x0 }; guint8 req_len; guint retry; /* check there's enough data for the smallest possible record */ if (rcd->buf->len < 11) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "line %u is incomplete, length %u", rcd->ln, (guint) rcd->buf->len); return FALSE; } /* check starting token */ if (line[0] != ':') { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid starting token on line %u: %s", rcd->ln, line); return FALSE; } /* length, 16-bit address, type */ req_len = fu_firmware_strparse_uint8 (line + 1); if (req_len >= sizeof(buf) - 7) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "line too long; buffer size is 0x%x bytes", (guint) sizeof(buf)); return FALSE; } if (9 + (guint) req_len * 2 > (guint) rcd->buf->len) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "line %u malformed", rcd->ln); return FALSE; } /* write each record directly to the hardware */ buf[0] = FU_VLI_USBHUB_I2C_ADDR_WRITE; buf[1] = FU_VLI_USBHUB_I2C_CMD_WRITE; buf[2] = 0x3a; /* ':' */ buf[3] = req_len; buf[4] = fu_firmware_strparse_uint8 (line + 3); buf[5] = fu_firmware_strparse_uint8 (line + 5); buf[6] = fu_firmware_strparse_uint8 (line + 7); for (guint8 i = 0; i < req_len; i++) buf[7 + i] = fu_firmware_strparse_uint8 (line + 9 + (i * 2)); buf[7 + req_len] = fu_firmware_strparse_uint8 (line + 9+ (req_len * 2)); bufsz = req_len + 8; for (retry = 0; retry < 5; retry++) { FuVliUsbhubI2cStatus status = 0xff; g_autoptr(GError) error_local = NULL; g_usleep (5 * 1000); if (usbver >= 0x0300 || bufsz <= 32) { if (!fu_vli_usbhub_device_i2c_write_data (parent, 0, 0, buf, bufsz, error)) return FALSE; } else { /* for U2, hub data buffer <= 32 bytes */ if (!fu_vli_usbhub_device_i2c_write_data (parent, 0, 1, buf, 32, error)) return FALSE; if (!fu_vli_usbhub_device_i2c_write_data (parent, 1, 0, buf + 32, bufsz - 32, error)) return FALSE; } /* end of file, no need to check status */ if (req_len == 0 && buf[6] == 0x01 && buf[7] == 0xFF) break; /* read data to check status */ g_usleep (5 * 1000); if (!fu_vli_usbhub_device_i2c_read_status (parent, &status, error)) return FALSE; if (!fu_vli_usbhub_i2c_check_status (status, &error_local)) { g_warning ("error on try %u: %s", retry + 1, error_local->message); } else { break; } } if (retry >= 5) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "I²C status retry failed"); return FALSE; } fu_device_set_progress_full (device, (gsize) j, (gsize) records->len); } /* success */ return TRUE; } static gboolean fu_vli_usbhub_i2c_device_probe (FuDevice *device, GError **error) { FuVliUsbhubI2cDevice *self = FU_VLI_USBHUB_I2C_DEVICE (device); self->device_kind = FU_VLI_DEVICE_KIND_MSP430; fu_device_set_name (device, fu_vli_common_device_kind_to_string (self->device_kind)); return TRUE; } static void fu_vli_usbhub_i2c_device_init (FuVliUsbhubI2cDevice *self) { fu_device_add_icon (FU_DEVICE (self), "audio-card"); fu_device_set_protocol (FU_DEVICE (self), "com.vli.i2c"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_logical_id (FU_DEVICE (self), "I2C"); fu_device_set_summary (FU_DEVICE (self), "I²C Dock Management Device"); } static void fu_vli_usbhub_i2c_device_class_init (FuVliUsbhubI2cDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->to_string = fu_vli_usbhub_i2c_device_to_string; klass_device->probe = fu_vli_usbhub_i2c_device_probe; klass_device->setup = fu_vli_usbhub_i2c_device_setup; klass_device->detach = fu_vli_usbhub_i2c_device_detach; klass_device->write_firmware = fu_vli_usbhub_i2c_device_write_firmware; klass_device->prepare_firmware = fu_vli_usbhub_i2c_device_prepare_firmware; } FuDevice * fu_vli_usbhub_i2c_device_new (FuVliUsbhubDevice *parent) { FuVliUsbhubI2cDevice *self = g_object_new (FU_TYPE_VLI_USBHUB_I2C_DEVICE, "parent", parent, NULL); return FU_DEVICE (self); } fwupd-1.3.9/plugins/vli/fu-vli-usbhub-i2c-device.h000066400000000000000000000007251362775233600217170ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define FU_TYPE_VLI_USBHUB_I2C_DEVICE (fu_vli_usbhub_i2c_device_get_type ()) G_DECLARE_FINAL_TYPE (FuVliUsbhubI2cDevice, fu_vli_usbhub_i2c_device, FU, VLI_USBHUB_I2C_DEVICE, FuDevice) struct _FuVliUsbhubI2cDeviceClass { FuDeviceClass parent_class; }; FuDevice *fu_vli_usbhub_i2c_device_new (FuVliUsbhubDevice *parent); fwupd-1.3.9/plugins/vli/fu-vli-usbhub-pd-common.h000066400000000000000000000004351362775233600216740ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #define VLI_USBHUB_PD_FLASHMAP_ADDR_LEGACY 0x4000 #define VLI_USBHUB_PD_FLASHMAP_ADDR 0x1003 fwupd-1.3.9/plugins/vli/fu-vli-usbhub-pd-device.c000066400000000000000000000166671362775233600216540ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-pd-common.h" #include "fu-vli-pd-firmware.h" #include "fu-vli-usbhub-common.h" #include "fu-vli-usbhub-device.h" #include "fu-vli-usbhub-pd-common.h" #include "fu-vli-usbhub-pd-device.h" struct _FuVliUsbhubPdDevice { FuDevice parent_instance; FuVliPdHdr hdr; FuVliDeviceKind device_kind; }; G_DEFINE_TYPE (FuVliUsbhubPdDevice, fu_vli_usbhub_pd_device, FU_TYPE_DEVICE) static void fu_vli_usbhub_pd_device_to_string (FuDevice *device, guint idt, GString *str) { FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE (device); fu_common_string_append_kv (str, idt, "DeviceKind", fu_vli_common_device_kind_to_string (self->device_kind)); fu_common_string_append_kx (str, idt, "FwOffset", fu_vli_common_device_kind_get_offset (self->device_kind)); fu_common_string_append_kx (str, idt, "FwSize", fu_vli_common_device_kind_get_size (self->device_kind)); } static gboolean fu_vli_usbhub_pd_device_probe (FuDevice *device, GError **error) { FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE (device); guint32 fwver; g_autofree gchar *fwver_str = NULL; g_autofree gchar *instance_id1 = NULL; /* get version */ fwver = GUINT32_FROM_BE (self->hdr.fwver); self->device_kind = fu_vli_pd_common_guess_device_kind (fwver); if (self->device_kind == FU_VLI_DEVICE_KIND_UNKNOWN) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "PD version invalid [0x%x]", fwver); return FALSE; } fu_device_set_name (device, fu_vli_common_device_kind_to_string (self->device_kind)); /* use header to populate device info */ fwver_str = fu_common_version_from_uint32 (fwver, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version (device, fwver_str, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version_raw (device, fwver); instance_id1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&DEV_%s", GUINT16_FROM_LE (self->hdr.vid), GUINT16_FROM_LE (self->hdr.pid), fu_vli_common_device_kind_to_string (self->device_kind)); fu_device_add_instance_id (device, instance_id1); /* these have a backup section */ if (fu_vli_common_device_kind_get_offset (self->device_kind) == VLI_USBHUB_FLASHMAP_ADDR_PD) fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); /* success */ return TRUE; } static FuFirmware * fu_vli_usbhub_pd_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE (device); FuVliDeviceKind device_kind; g_autoptr(FuFirmware) firmware = fu_vli_pd_firmware_new (); /* add the two offset locations the header can be found */ fu_vli_pd_firmware_add_offset (FU_VLI_PD_FIRMWARE (firmware), VLI_USBHUB_PD_FLASHMAP_ADDR_LEGACY); fu_vli_pd_firmware_add_offset (FU_VLI_PD_FIRMWARE (firmware), VLI_USBHUB_PD_FLASHMAP_ADDR); /* check size */ if (g_bytes_get_size (fw) < fu_device_get_firmware_size_min (device)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too small, got 0x%x, expected >= 0x%x", (guint) g_bytes_get_size (fw), (guint) fu_device_get_firmware_size_min (device)); return NULL; } if (g_bytes_get_size (fw) > fu_device_get_firmware_size_max (device)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too large, got 0x%x, expected <= 0x%x", (guint) g_bytes_get_size (fw), (guint) fu_device_get_firmware_size_max (device)); return NULL; } /* check is compatible with firmware */ fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; device_kind = fu_vli_pd_firmware_get_kind (FU_VLI_PD_FIRMWARE (firmware)); if (self->device_kind != device_kind) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got %s, expected %s", fu_vli_common_device_kind_to_string (device_kind), fu_vli_common_device_kind_to_string (self->device_kind)); return NULL; } /* we could check this against flags */ g_debug ("parsed version: %s", fu_firmware_get_version (firmware)); return g_steal_pointer (&firmware); } static FuFirmware * fu_vli_usbhub_pd_device_read_firmware (FuDevice *device, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE (fu_device_get_parent (device)); FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GBytes) fw = NULL; /* open device */ locker = fu_device_locker_new (parent, error); if (locker == NULL) return NULL; /* read */ fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_DEVICE_VERIFY); fw = fu_vli_device_spi_read (FU_VLI_DEVICE (parent), fu_vli_common_device_kind_get_offset (self->device_kind), fu_device_get_firmware_size_max (device), error); if (fw == NULL) return NULL; return fu_firmware_new_from_bytes (fw); } static gboolean fu_vli_usbhub_pd_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE (device); FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE (fu_device_get_parent (device)); gsize bufsz = 0; const guint8 *buf; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GBytes) fw = NULL; /* simple image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* open device */ locker = fu_device_locker_new (parent, error); if (locker == NULL) return FALSE; /* erase */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); buf = g_bytes_get_data (fw, &bufsz); if (!fu_vli_device_spi_erase (FU_VLI_DEVICE (parent), fu_vli_common_device_kind_get_offset (self->device_kind), bufsz, error)) return FALSE; /* write */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); if (!fu_vli_device_spi_write (FU_VLI_DEVICE (parent), fu_vli_common_device_kind_get_offset (self->device_kind), buf, bufsz, error)) return FALSE; /* success */ return TRUE; } static void fu_vli_usbhub_pd_device_init (FuVliUsbhubPdDevice *self) { fu_device_add_icon (FU_DEVICE (self), "audio-card"); fu_device_set_protocol (FU_DEVICE (self), "com.vli.usbhub"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_install_duration (FU_DEVICE (self), 15); /* seconds */ fu_device_set_logical_id (FU_DEVICE (self), "PD"); fu_device_set_summary (FU_DEVICE (self), "USB-C Power Delivery Device"); } static void fu_vli_usbhub_pd_device_class_init (FuVliUsbhubPdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->to_string = fu_vli_usbhub_pd_device_to_string; klass_device->probe = fu_vli_usbhub_pd_device_probe; klass_device->read_firmware = fu_vli_usbhub_pd_device_read_firmware; klass_device->write_firmware = fu_vli_usbhub_pd_device_write_firmware; klass_device->prepare_firmware = fu_vli_usbhub_pd_device_prepare_firmware; } FuDevice * fu_vli_usbhub_pd_device_new (FuVliPdHdr *hdr) { FuVliUsbhubPdDevice *self = g_object_new (FU_TYPE_VLI_USBHUB_PD_DEVICE, NULL); memcpy (&self->hdr, hdr, sizeof(self->hdr)); return FU_DEVICE (self); } fwupd-1.3.9/plugins/vli/fu-vli-usbhub-pd-device.h000066400000000000000000000007431362775233600216450ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #include "fu-vli-pd-common.h" #define FU_TYPE_VLI_USBHUB_PD_DEVICE (fu_vli_usbhub_pd_device_get_type ()) G_DECLARE_FINAL_TYPE (FuVliUsbhubPdDevice, fu_vli_usbhub_pd_device, FU, VLI_USBHUB_PD_DEVICE, FuDevice) struct _FuVliUsbhubPdDeviceClass { FuDeviceClass parent_class; }; FuDevice *fu_vli_usbhub_pd_device_new (FuVliPdHdr *hdr); fwupd-1.3.9/plugins/vli/meson.build000066400000000000000000000025651362775233600173140ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginVliUsbhub"'] install_data([ 'vli-pd.quirk', 'vli-usbhub.quirk', 'vli-usbhub-lenovo.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_vli', fu_hash, sources : [ 'fu-plugin-vli.c', 'fu-vli-common.c', 'fu-vli-device.c', 'fu-vli-pd-common.c', 'fu-vli-pd-device.c', 'fu-vli-pd-firmware.c', 'fu-vli-pd-parade-device.c', 'fu-vli-usbhub-common.c', 'fu-vli-usbhub-device.c', 'fu-vli-usbhub-firmware.c', 'fu-vli-usbhub-i2c-common.c', 'fu-vli-usbhub-i2c-device.c', 'fu-vli-usbhub-pd-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) if get_option('tests') testdatadir = join_paths(meson.current_source_dir(), 'data') cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'vli-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-vli-common.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) test('vli-self-test', e) endif fwupd-1.3.9/plugins/vli/vli-pd.quirk000066400000000000000000000056471362775233600174260ustar00rootroot00000000000000[DeviceInstanceId=USB\VID_17EF&PID_3070] Plugin = vli GType = FuVliPdDevice # Generic [DeviceInstanceId=USB\VID_0800&PID_0800] Plugin = vli GType = FuVliPdDevice DeviceKind = VL100 [DeviceInstanceId=USB\VID_2109&PID_0100] Plugin = vli GType = FuVliPdDevice DeviceKind = VL100 [DeviceInstanceId=USB\VID_2109&PID_0101] Plugin = vli GType = FuVliPdDevice DeviceKind = VL101 [DeviceInstanceId=USB\VID_2109&PID_0102] Plugin = vli GType = FuVliPdDevice DeviceKind = VL102 [DeviceInstanceId=USB\VID_2109&PID_0103] Plugin = vli GType = FuVliPdDevice DeviceKind = VL103 [DeviceInstanceId=USB\VID_2109&PID_0104] Plugin = vli GType = FuVliPdDevice DeviceKind = VL104 [DeviceInstanceId=USB\VID_2109&PID_0105] Plugin = vli GType = FuVliPdDevice DeviceKind = VL105 [DeviceInstanceId=USB\VID_2109&PID_0106] Plugin = vli GType = FuVliPdDevice DeviceKind = VL106 [DeviceInstanceId=USB\VID_2109&PID_0107] Plugin = vli GType = FuVliPdDevice DeviceKind = VL107 [DeviceInstanceId=USB\VID_2109&PID_D101] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_D102] Plugin = vli GType = FuVliPdDevice # Lenovo VGA [DeviceInstanceId=USB\VID_17EF&PID_7211] Plugin = vli GType = FuVliPdDevice # Lenovo HDMI [DeviceInstanceId=USB\VID_17EF&PID_7212] Plugin = vli GType = FuVliPdDevice # Lenovo [DeviceInstanceId=USB\VID_17EF&PID_7215] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_17EF&PID_7217] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_17EF&PID_721C] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_17EF&PID_7223] Plugin = vli GType = FuVliPdDevice # Samsung [DeviceInstanceId=USB\VID_04E8&PID_A025] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_04E8&PID_A048] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_8880] Plugin = vli GType = FuVliPdDevice # VL671,U3TT, VT3547 [DeviceInstanceId=USB\VID_2109&PID_8881] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_8882] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_8883] Plugin = vli GType = FuVliPdDevice # Realtek [DeviceInstanceId=USB\VID_2109&PID_8884] Plugin = vli GType = FuVliPdDevice # VL650, VT3555 [DeviceInstanceId=USB\VID_2109&PID_8885] Plugin = vli GType = FuVliPdDevice # MStar, VT3518 [DeviceInstanceId=USB\VID_2109&PID_8886] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_8887] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_8889] Plugin = vli GType = FuVliPdDevice # Novatek, VT3538 [DeviceInstanceId=USB\VID_2109&PID_888A] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_888B] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_888C] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_888D] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_888E] Plugin = vli GType = FuVliPdDevice [DeviceInstanceId=USB\VID_2109&PID_888F] Plugin = vli GType = FuVliPdDevice fwupd-1.3.9/plugins/vli/vli-usbhub-lenovo.quirk000066400000000000000000000136701362775233600216060ustar00rootroot00000000000000# Lenovo CS18 Ultra Dock [DeviceInstanceId=USB\VID_17EF&PID_3070] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [DeviceInstanceId=USB\VID_17EF&PID_3072&HUB_0006] ParentGuid = USB\VID_17EF&PID_3072&HUB_0002 [DeviceInstanceId=USB\VID_17EF&PID_3071] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 [DeviceInstanceId=USB\VID_17EF&PID_3071&HUB_0002] ParentGuid = USB\VID_17EF&PID_3071&HUB_0006 # Lenovo CS18 Pro and Basic Dock [DeviceInstanceId=USB\VID_17EF&PID_3072] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [DeviceInstanceId=USB\VID_17EF&PID_3073] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 [DeviceInstanceId=USB\VID_17EF&PID_3073&HUB_0006] ParentGuid = USB\VID_17EF&PID_3073&HUB_0002 # Lenovo TR Dock [DeviceInstanceId=USB\VID_17EF&PID_307F&HUB_0002] ParentGuid = TBT-01081720 [DeviceInstanceId=USB\VID_17EF&PID_307F] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-i2c [DeviceInstanceId=USB\VID_17EF&PID_307F&HUB_0006] ParentGuid = USB\VID_17EF&PID_307F&HUB_0002 [DeviceInstanceId=USB\VID_17EF&PID_3080] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,has-shared-spi-i2c [DeviceInstanceId=USB\VID_17EF&PID_3080&HUB_06] ParentGuid = USB\VID_17EF&PID_3080&HUB_20 [DeviceInstanceId=USB\VID_17EF&PID_3080&HUB_20] ParentGuid = TBT-01081720 # Lenovo CS13 KG Dock [DeviceInstanceId=USB\VID_17EF&PID_1010] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo CS13 GD Dock [DeviceInstanceId=USB\VID_17EF&PID_1012] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo CS13 MO Dock [DeviceInstanceId=USB\VID_17EF&PID_1013] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo Payton dock [DeviceInstanceId=USB\VID_17EF&PID_305A] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 [DeviceInstanceId=USB\VID_17EF&PID_305B] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 ParentGuid = USB\VID_17EF&PID_305A # Lenovo USB3 Ultra Dock [DeviceInstanceId=USB\VID_17EF&PID_1014] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 [DeviceInstanceId=USB\VID_17EF&PID_1015] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 ParentGuid = USB\VID_17EF&PID_1014 # Lenovo USB3 Pro Dock [DeviceInstanceId=USB\VID_17EF&PID_1016] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 [DeviceInstanceId=USB\VID_17EF&PID_1018] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 ParentGuid = USB\VID_17EF&PID_1016 # Lenovo Workstation D40 [DeviceInstanceId=USB\VID_17EF&PID_1033] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo Workstation S40 [DeviceInstanceId=USB\VID_17EF&PID_1034] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo Workstation v40 [DeviceInstanceId=USB\VID_17EF&PID_1035] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo One Link Plus [DeviceInstanceId=USB\VID_17EF&PID_1018] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 [DeviceInstanceId=USB\VID_17EF&PID_1019] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 ParentGuid = USB\VID_17EF&PID_1018 # Lenovo Hybrid dock [DeviceInstanceId=USB\VID_17EF&PID_A356] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [DeviceInstanceId=USB\VID_17EF&PID_1028] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 [DeviceInstanceId=USB\VID_17EF&PID_A357] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 ParentGuid = USB\VID_17EF&PID_A356 [DeviceInstanceId=USB\VID_17EF&PID_1029] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_1028 # Lenovo Travel hub [DeviceInstanceId=USB\VID_17EF&PID_7216] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [DeviceInstanceId=USB\VID_17EF&PID_7224] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 # Lenovo Travel hub Gen2 [DeviceInstanceId=USB\VID_17EF&PID_721D] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd [DeviceInstanceId=USB\VID_17EF&PID_7225] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,has-shared-spi-pd # Lenovo USB-C Mini dock [DeviceInstanceId=USB\VID_17EF&PID_3094] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd [DeviceInstanceId=USB\VID_17EF&PID_3095] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,has-shared-spi-pd # Lenovo Travel Hub 1in3 [DeviceInstanceId=USB\VID_17EF&PID_7228] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [DeviceInstanceId=USB\VID_17EF&PID_7226] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 # Lenovo USB-C 7-in-1 Hub [DeviceInstanceId=USB\VID_17EF&PID_722A] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd [DeviceInstanceId=USB\VID_17EF&PID_7229] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,has-shared-spi-pd # Lenovo USB-C to 4 USB-A Hub [DeviceInstanceId=USB\VID_17EF&PID_1039] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd [DeviceInstanceId=USB\VID_17EF&PID_103A] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,has-shared-spi-pd # Lenovo Gen2 dock [DeviceInstanceId=USB\VID_17EF&PID_A391] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [DeviceInstanceId=USB\VID_17EF&PID_A392] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 [DeviceInstanceId=USB\VID_17EF&PID_A393] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 ParentGuid = USB\VID_17EF&PID_A391 [DeviceInstanceId=USB\VID_17EF&PID_A394] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_A392 [DeviceInstanceId=USB\VID_17EF&PID_A395] Plugin = vli GType = FuVliUsbhubDevice # Lenovo Modularized dock (with VIA USB PID?!) [DeviceInstanceId=USB\VID_2109&PID_1822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [DeviceInstanceId=USB\VID_2109&PID_2822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 [DeviceInstanceId=USB\VID_2109&PID_3822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 ParentGuid = USB\VID_2109&PID_1822 [DeviceInstanceId=USB\VID_2109&PID_4822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_2109&PID_2822 # Lenovo Powered Hub [DeviceInstanceId=USB\VID_17EF&PID_721C] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 fwupd-1.3.9/plugins/vli/vli-usbhub.quirk000066400000000000000000000052711362775233600203040ustar00rootroot00000000000000# 3470_Class [DeviceInstanceId=USB\VID_2109&PID_0810] Plugin = vli GType = FuVliUsbhubDevice [DeviceInstanceId=USB\VID_2109&PID_0811] Plugin = vli GType = FuVliUsbhubDevice [DeviceInstanceId=USB\VID_2109&PID_0812] Plugin = vli GType = FuVliUsbhubDevice [DeviceInstanceId=USB\VID_2109&PID_0813] Plugin = vli GType = FuVliUsbhubDevice Flags = needs-unlock-legacy813 [DeviceInstanceId=USB\VID_2109&PID_8110] Plugin = vli GType = FuVliUsbhubDevice [DeviceInstanceId=USB\VID_2109&PID_8113] Plugin = vli GType = FuVliUsbhubDevice # 3507_Class [DeviceInstanceId=USB\VID_2109&PID_0210] Plugin = vli GType = FuVliUsbhubDevice # 3545_Class [DeviceInstanceId=USB\VID_2109&PID_0211] Plugin = vli GType = FuVliUsbhubDevice [DeviceInstanceId=USB\VID_2109&PID_2211] Plugin = vli GType = FuVliUsbhubDevice [DeviceInstanceId=USB\VID_2109&PID_0212] Plugin = vli GType = FuVliUsbhubDevice [DeviceInstanceId=USB\VID_2109&PID_2212] Plugin = vli GType = FuVliUsbhubDevice # VL817 [DeviceInstanceId=USB\VID_2109&PID_0817] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [DeviceInstanceId=USB\VID_2109&PID_2817] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 # VL820 [DeviceInstanceId=USB\VID_2109&PID_0820] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd [DeviceInstanceId=USB\VID_2109&PID_2820] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,has-shared-spi-pd # A25Lxxx [Guid=VLI_USBHUB\\SPI_3730] SpiCmdChipErase = 0xc7 SpiCmdSectorErase = 0x20 # AT25F512A/B [Guid=VLI_USBHUB\\SPI_1F65] # SpiCmdReadId = 0x15 SpiCmdChipErase = 0x62 SpiCmdSectorErase = 0x00 # EN25Fxx [Guid=VLI_USBHUB\\SPI_1C31] SpiCmdChipErase = 0x60 SpiCmdSectorErase = 0x20 # GD25Qxxx [Guid=VLI_USBHUB\SPI_C840] SpiCmdChipErase = 0xC7 SpiCmdSectorErase = 0x20 # M25PxxA/xx [Guid=VLI_USBHUB\\SPI_0020] SpiCmdChipErase = 0xC7 SpiCmdSectorErase = 0x00 # MX25Lxxx/xxxC/xxxE [Guid=VLI_USBHUB\\SPI_C220] SpiCmdChipErase = 0x60 SpiCmdSectorErase = 0x20 # MX25Lxxx1E [Guid=VLI_USBHUB\\SPI_C222] SpiCmdChipErase = 0x60 SpiCmdSectorErase = 0x20 # PCT/SST25VFxxx/xxxA [Guid=VLI_USBHUB\\SPI_00BF] # SpiCmdReadId = 0x90 SpiCmdChipErase = 0x60 SpiCmdSectorErase = 0x20 # PM25LDxxx [Guid=VLI_USBHUB\\SPI_009D] # SpiCmdReadId = 0x90 SpiCmdChipErase = 0xC7 SpiCmdSectorErase = 0xD7 # PM25LVxxx [Guid=VLI_USBHUB\\SPI_009D] # SpiCmdReadId = 0xAB SpiCmdChipErase = 0xC7 SpiCmdSectorErase = 0xD7 # W25XxxBV/W25XxxCL [Guid=VLI_USBHUB\\SPI_00EF] SpiCmdChipErase = 0xC7 SpiCmdSectorErase = 0x20 # FM25Fxxx #[Guid=VLI_USBHUB\\SPI_XXXX] #SpiCmdChipErase = 0xC7 #SpiCmdSectorErase = 0x20 # KH25LxxxxE #[Guid=VLI_USBHUB\\SPI_XXXX] #SpiCmdChipErase = 0x60 #SpiCmdSectorErase = 0x20 # MX25Vxxx #[Guid=VLI_USBHUB\\SPI_XXXX] #SpiCmdChipErase = 0x60 #SpiCmdSectorErase = 0x20 fwupd-1.3.9/plugins/wacom-raw/000077500000000000000000000000001362775233600162455ustar00rootroot00000000000000fwupd-1.3.9/plugins/wacom-raw/README.md000066400000000000000000000021601362775233600175230ustar00rootroot00000000000000Wacom 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 | Vendor ID Security ------------------ The vendor ID is set from the udev vendor, in this instance set to `HIDRAW:0x056A` fwupd-1.3.9/plugins/wacom-raw/data/000077500000000000000000000000001362775233600171565ustar00rootroot00000000000000fwupd-1.3.9/plugins/wacom-raw/data/hid-recorder.txt000066400000000000000000001054561362775233600223010ustar00rootroot00000000000000# 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.3.9/plugins/wacom-raw/fu-plugin-wacom-raw.c000066400000000000000000000010411362775233600222060ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.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_udev_subsystem (plugin, "hidraw"); /* register the custom types */ g_type_ensure (FU_TYPE_WACOM_AES_DEVICE); g_type_ensure (FU_TYPE_WACOM_EMR_DEVICE); } fwupd-1.3.9/plugins/wacom-raw/fu-wacom-aes-device.c000066400000000000000000000152771362775233600221460ustar00rootroot00000000000000/* * 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), "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.3.9/plugins/wacom-raw/fu-wacom-aes-device.h000066400000000000000000000006051362775233600221400ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wacom-device.h" #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); fwupd-1.3.9/plugins/wacom-raw/fu-wacom-common.c000066400000000000000000000046501362775233600214220ustar00rootroot00000000000000/* * 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.3.9/plugins/wacom-raw/fu-wacom-common.h000066400000000000000000000040121362775233600214170ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); fwupd-1.3.9/plugins/wacom-raw/fu-wacom-device.c000066400000000000000000000236211362775233600213700ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-chunk.h" #include "fu-ihex-firmware.h" #include "fu-wacom-common.h" #include "fu-wacom-device.h" typedef struct { 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, guint idt, GString *str) { FuWacomDevice *self = FU_WACOM_DEVICE (device); FuWacomDevicePrivate *priv = GET_PRIVATE (self); fu_common_string_append_kx (str, idt, "FlashBlockSize", priv->flash_block_size); fu_common_string_append_kx (str, idt, "FlashBaseAddr", priv->flash_base_addr); fu_common_string_append_kx (str, idt, "FlashSize", 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_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 FuFirmware * fu_wacom_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_ihex_firmware_new (); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; return g_steal_pointer (&firmware); } static gboolean fu_wacom_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE (device); FuWacomDevicePrivate *priv = GET_PRIVATE (self); FuWacomDeviceClass *klass = FU_WACOM_DEVICE_GET_CLASS (device); g_autoptr(FuFirmwareImage) img = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; /* use the correct image from the firmware */ img = fu_firmware_get_image_default (firmware, error); if (img == NULL) return FALSE; g_debug ("using element at addr 0x%0x", (guint) fu_firmware_image_get_addr (img)); /* check start address and size */ if (fu_firmware_image_get_addr (img) != priv->flash_base_addr) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "base addr invalid: 0x%05x", (guint) fu_firmware_image_get_addr (img)); return FALSE; } fw = fu_firmware_image_write (img, error); if (fw == NULL) return FALSE; if (g_bytes_get_size (fw) > 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)); 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, 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) { fu_common_dump_raw (G_LOG_DOMAIN, "SetFeature", data, datasz); return fu_udev_device_ioctl (FU_UDEV_DEVICE (self), HIDIOCSFEATURE(datasz), (guint8 *) data, NULL, error); } gboolean fu_wacom_device_get_feature (FuWacomDevice *self, guint8 *data, guint datasz, GError **error) { if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self), HIDIOCGFEATURE(datasz), data, NULL, error)) 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_set_protocol (FU_DEVICE (self), "com.wacom.raw"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL); } 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->prepare_firmware = fu_wacom_device_prepare_firmware; 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.3.9/plugins/wacom-raw/fu-wacom-device.h000066400000000000000000000026371362775233600214010ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wacom-common.h" #include "fu-udev-device.h" #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); fwupd-1.3.9/plugins/wacom-raw/fu-wacom-emr-device.c000066400000000000000000000153301362775233600221470ustar00rootroot00000000000000/* * 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); fu_device_set_version_raw (device, fw_ver); } /* 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), "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.3.9/plugins/wacom-raw/fu-wacom-emr-device.h000066400000000000000000000006051362775233600221530ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wacom-device.h" #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, FuWacomDevice) FuWacomEmrDevice *fu_wacom_emr_device_new (FuUdevDevice *device); fwupd-1.3.9/plugins/wacom-raw/meson.build000066400000000000000000000011151362775233600204050ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], ) fwupd-1.3.9/plugins/wacom-raw/wacom-raw.quirk000066400000000000000000000044051362775233600212220ustar00rootroot00000000000000# 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=WacomEMR] GType=FuWacomEmrDevice [Guid=WacomAES] GType=FuWacomAesDevice WacomI2cFlashBlockSize=128 WacomI2cFlashBaseAddr=0x8000 WacomI2cFlashSize=0x24000 fwupd-1.3.9/plugins/wacom-usb/000077500000000000000000000000001362775233600162455ustar00rootroot00000000000000fwupd-1.3.9/plugins/wacom-usb/README.md000066400000000000000000000026251362775233600175310ustar00rootroot00000000000000Wacom 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` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, for example set to `USB:0x056A` fwupd-1.3.9/plugins/wacom-usb/data/000077500000000000000000000000001362775233600171565ustar00rootroot00000000000000fwupd-1.3.9/plugins/wacom-usb/data/lsusb.txt000066400000000000000000000037021362775233600210510ustar00rootroot00000000000000Bus 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.3.9/plugins/wacom-usb/fu-plugin-wacom-usb.c000066400000000000000000000031441362775233600222140ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-vfuncs.h" #include "fu-hash.h" #include "fu-wac-device.h" #include "fu-wac-firmware.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); fu_plugin_set_device_gtype (plugin, FU_TYPE_WAC_DEVICE); fu_plugin_add_firmware_gtype (plugin, "wacom", FU_TYPE_WAC_FIRMWARE); } 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.3.9/plugins/wacom-usb/fu-self-test.c000066400000000000000000000033701362775233600207320ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-common.h" #include "fu-wac-common.h" #include "fu-wac-firmware.h" #include "fwupd-error.h" static void fu_wac_firmware_parse_func (void) { gboolean ret; g_autofree gchar *fn = NULL; g_autoptr(FuFirmware) firmware = fu_wac_firmware_new (); g_autoptr(FuFirmwareImage) img = NULL; g_autoptr(GBytes) blob_block = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GError) error = NULL; /* parse the test file */ fn = g_build_filename (TESTDATADIR, "test.wac", NULL); if (!g_file_test (fn, G_FILE_TEST_EXISTS)) { 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_firmware_parse (firmware, bytes, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert_true (ret); /* get image data */ img = fu_firmware_get_image_default (firmware, &error); g_assert_no_error (error); g_assert_nonnull (img); /* get block */ blob_block = fu_firmware_image_write_chunk (img, 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.3.9/plugins/wacom-usb/fu-wac-common.c000066400000000000000000000047561362775233600210750ustar00rootroot00000000000000/* * 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.3.9/plugins/wacom-usb/fu-wac-common.h000066400000000000000000000036741362775233600211000ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); fwupd-1.3.9/plugins/wacom-usb/fu-wac-device.c000066400000000000000000000646011362775233600210370ustar00rootroot00000000000000/* * 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" 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_flash_descriptor_to_string (FuWacFlashDescriptor *fd, guint idt, GString *str) { fu_common_string_append_kx (str, idt, "StartAddr", fd->start_addr); fu_common_string_append_kx (str, idt, "BlockSize", fd->block_sz); fu_common_string_append_kx (str, idt, "WriteSize", fd->write_sz & ~0x8000); fu_common_string_append_kb (str, idt, "Protected", fu_wav_device_flash_descriptor_is_wp (fd)); } static void fu_wac_device_to_string (FuDevice *device, guint idt, GString *str) { FuWacDevice *self = FU_WAC_DEVICE (device); g_autoptr(GString) status_str = NULL; if (self->firmware_index != 0xffff) { g_autofree gchar *tmp = g_strdup_printf ("0x%04x", self->firmware_index); fu_common_string_append_kv (str, idt, "FwIndex", tmp); } if (self->loader_ver > 0) { g_autofree gchar *tmp = g_strdup_printf ("0x%04x", (guint) self->loader_ver); fu_common_string_append_kv (str, idt, "LoaderVer", tmp); } if (self->read_data_sz > 0) { g_autofree gchar *tmp = g_strdup_printf ("0x%04x", (guint) self->read_data_sz); fu_common_string_append_kv (str, idt, "ReadDataSize", tmp); } if (self->write_word_sz > 0) { g_autofree gchar *tmp = g_strdup_printf ("0x%04x", (guint) self->write_word_sz); fu_common_string_append_kv (str, idt, "WriteWordSize", tmp); } if (self->write_block_sz > 0) { g_autofree gchar *tmp = g_strdup_printf ("0x%04x", (guint) self->write_block_sz); fu_common_string_append_kv (str, idt, "WriteBlockSize", tmp); } if (self->nr_flash_blocks > 0) { g_autofree gchar *tmp = g_strdup_printf ("0x%04x", (guint) self->nr_flash_blocks); fu_common_string_append_kv (str, idt, "NrFlashBlocks", tmp); } if (self->configuration != 0xffff) { g_autofree gchar *tmp = g_strdup_printf ("0x%04x", (guint) self->configuration); fu_common_string_append_kv (str, idt, "Configuration", tmp); } for (guint i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index (self->flash_descriptors, i); g_autofree gchar *title = g_strdup_printf ("FlashDescriptor%02u", i); fu_common_string_append_kv (str, idt, title, NULL); fu_wac_device_flash_descriptor_to_string (fd, idt + 1, str); } status_str = fu_wac_device_status_to_string (self->status_word); fu_common_string_append_kv (str, idt, "Status", status_str->str); } 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, FU_HID_REPORT_GET, /* bRequest */ FU_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, FU_HID_REPORT_SET, /* bRequest */ FU_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) { if (!fu_memcpy_safe (buf, bufsz, 0x5, /* dst */ tmp, sz, 0x0, /* src */ sz, error)) return FALSE; } /* 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 FuFirmware * fu_wac_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_wac_firmware_new (); fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; return g_steal_pointer (&firmware); } static gboolean fu_wac_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuWacDevice *self = FU_WAC_DEVICE (device); gsize blocks_done = 0; gsize blocks_total = 0; g_autofree guint32 *csum_local = NULL; g_autoptr(FuFirmwareImage) img = NULL; g_autoptr(GHashTable) fd_blobs = NULL; /* use the correct image from the firmware */ img = fu_firmware_get_image_by_idx (firmware, self->firmware_index == 1 ? 1 : 0, error); if (img == NULL) return FALSE; g_debug ("using image at addr 0x%0x", (guint) fu_firmware_image_get_addr (img)); /* 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; /* 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 = fu_firmware_image_write_chunk (img, fd->start_addr, fd->block_sz, NULL); if (blob_tmp == NULL) break; blob_block = fu_common_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_set_protocol (FU_DEVICE (self), "com.wacom.usb"); 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->prepare_firmware = fu_wac_device_prepare_firmware; 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; } fwupd-1.3.9/plugins/wacom-usb/fu-wac-device.h000066400000000000000000000016361362775233600210430ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #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; 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); fwupd-1.3.9/plugins/wacom-usb/fu-wac-firmware.c000066400000000000000000000144261362775233600214140ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-common.h" #include "fu-srec-firmware.h" #include "fu-firmware-common.h" #include "fu-wac-firmware.h" #include "fwupd-error.h" struct _FuWacFirmware { FuFirmware parent_instance; }; G_DEFINE_TYPE (FuWacFirmware, fu_wac_firmware, FU_TYPE_FIRMWARE) typedef struct { guint32 addr; guint32 sz; guint32 prog_start_addr; } FuFirmwareWacHeaderRecord; static gboolean fu_wac_firmware_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { gsize len; guint8 *data; guint8 images_cnt = 0; g_auto(GStrv) lines = NULL; g_autoptr(GPtrArray) header_infos = g_ptr_array_new_with_free_func (g_free); g_autoptr(GString) image_buffer = NULL; /* check the prefix (BE) */ data = (guint8 *) g_bytes_get_data (fw, &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 */ lines = fu_common_strnsplit ((const gchar *) data, len, "\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 = fu_firmware_strparse_uint4 (lines[i] + 5); for (guint j = 0; j < header_image_cnt; j++) { FuFirmwareWacHeaderRecord *hdr = g_new0 (FuFirmwareWacHeaderRecord, 1); hdr->addr = fu_firmware_strparse_uint32 (lines[i] + (j * 16) + 6); hdr->sz = fu_firmware_strparse_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) { FuFirmwareWacHeaderRecord *hdr; guint8 idx = fu_firmware_strparse_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 = fu_firmware_strparse_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(FuFirmware) firmware_srec = fu_srec_firmware_new (); g_autoptr(FuFirmwareImage) img = NULL; FuFirmwareWacHeaderRecord *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 (!fu_firmware_parse_full (firmware_srec, blob, hdr->addr, 0x0, flags, error)) return FALSE; img = fu_firmware_get_image_default (firmware_srec, error); if (img == NULL) return FALSE; fu_firmware_image_set_idx (img, images_cnt); fu_firmware_add_image (firmware, img); 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 */ return TRUE; } static void fu_wac_firmware_init (FuWacFirmware *self) { } static void fu_wac_firmware_class_init (FuWacFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); klass_firmware->parse = fu_wac_firmware_parse; } FuFirmware * fu_wac_firmware_new (void) { return FU_FIRMWARE (g_object_new (FU_TYPE_WAC_FIRMWARE, NULL)); } fwupd-1.3.9/plugins/wacom-usb/fu-wac-firmware.h000066400000000000000000000005221362775233600214110ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_WAC_FIRMWARE (fu_wac_firmware_get_type ()) G_DECLARE_FINAL_TYPE (FuWacFirmware, fu_wac_firmware, FU, WAC_FIRMWARE, FuFirmware) FuFirmware *fu_wac_firmware_new (void); fwupd-1.3.9/plugins/wacom-usb/fu-wac-module-bluetooth.c000066400000000000000000000132721362775233600230660ustar00rootroot00000000000000/* * 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, GError **error) { 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; if (!fu_memcpy_safe (bd->cdata, sizeof(bd->cdata), 0x0, /* dst */ data, sz, addr, /* src */ cdata_sz, error)) return NULL; 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, FuFirmware *firmware, 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); g_autoptr(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* build each data packet */ data = g_bytes_get_data (fw, &len); blocks = fu_wac_module_bluetooth_parse_blocks (data, len, TRUE, error); if (blocks == NULL) return FALSE; 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.3.9/plugins/wacom-usb/fu-wac-module-bluetooth.h000066400000000000000000000006211362775233600230650ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wac-module.h" #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); fwupd-1.3.9/plugins/wacom-usb/fu-wac-module-touch.c000066400000000000000000000070441362775233600222030ustar00rootroot00000000000000/* * 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 "fu-ihex-firmware.h" struct _FuWacModuleTouch { FuWacModule parent_instance; }; G_DEFINE_TYPE (FuWacModuleTouch, fu_wac_module_touch, FU_TYPE_WAC_MODULE) static FuFirmware * fu_wac_module_touch_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_ihex_firmware_new (); if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; return g_steal_pointer (&firmware); } static gboolean fu_wac_module_touch_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuWacModule *self = FU_WAC_MODULE (device); gsize blocks_total = 0; g_autoptr(FuFirmwareImage) img = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; /* use the correct image from the firmware */ img = fu_firmware_get_image_default (firmware, error); if (img == NULL) return FALSE; g_debug ("using element at addr 0x%0x", (guint) fu_firmware_image_get_addr (img)); fw = fu_firmware_image_write (img, error); if (fw == NULL) return FALSE; /* build each data packet */ chunks = fu_chunk_array_new_from_bytes (fw, fu_firmware_image_get_addr (img), 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->prepare_firmware = fu_wac_module_touch_prepare_firmware; 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.3.9/plugins/wacom-usb/fu-wac-module-touch.h000066400000000000000000000005711362775233600222060ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wac-module.h" #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); fwupd-1.3.9/plugins/wacom-usb/fu-wac-module.c000066400000000000000000000245161362775233600210660ustar00rootroot00000000000000/* * 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" #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, guint idt, GString *str) { FuWacModule *self = FU_WAC_MODULE (device); FuWacModulePrivate *priv = GET_PRIVATE (self); fu_common_string_append_kv (str, idt, "FwType", fu_wac_module_fw_type_to_string (priv->fw_type)); fu_common_string_append_kv (str, idt, "Status", fu_wac_module_status_to_string (priv->status)); fu_common_string_append_kv (str, idt, "Command", 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.3.9/plugins/wacom-usb/fu-wac-module.h000066400000000000000000000015051362775233600210640ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" #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); fwupd-1.3.9/plugins/wacom-usb/meson.build000066400000000000000000000023311362775233600204060ustar00rootroot00000000000000cargs = ['-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], ) 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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ libxmlb, gio, gusb, gudev, libm, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) test('wacom-usb-self-test', e) endif fwupd-1.3.9/plugins/wacom-usb/wacom-usb.quirk000066400000000000000000000015511362775233600212210ustar00rootroot00000000000000 # 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.3.9/po/000077500000000000000000000000001362775233600133055ustar00rootroot00000000000000fwupd-1.3.9/po/.gitignore000066400000000000000000000000061362775233600152710ustar00rootroot00000000000000*.pot fwupd-1.3.9/po/LINGUAS000066400000000000000000000001641362775233600143330ustar00rootroot00000000000000af 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.3.9/po/POTFILES.in000066400000000000000000000006301362775233600150610ustar00rootroot00000000000000data/remotes.d/lvfs.metainfo.xml data/remotes.d/lvfs-testing.metainfo.xml policy/org.freedesktop.fwupd.policy.in plugins/dfu/dfu-tool.c plugins/tpm-eventlog/fu-tpm-eventlog.c plugins/uefi/fu-plugin-uefi.c plugins/uefi/fu-uefi-tool.c src/fu-agent.c src/fu-debug.c src/fu-engine-helper.c src/fu-main.c src/fu-offline.c src/fu-progressbar.c src/fu-remote-list.c src/fu-tool.c src/fu-util.c src/fu-util-common.c fwupd-1.3.9/po/POTFILES.skip000066400000000000000000000000501362775233600154150ustar00rootroot00000000000000data/org.freedesktop.fwupd.metainfo.xml fwupd-1.3.9/po/af.po000066400000000000000000000170241362775233600142370ustar00rootroot00000000000000# 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: 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: remote checksum msgid "Checksum" msgstr "Kontrolesom" #. 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" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Pak tans uit…" #. TRANSLATORS: multiline description of device 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: 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 filename base msgid "Password" msgstr "Wagwoord" 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: reading from the flash chips msgid "Reading…" msgstr "Lees tans…" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Skeduleer tans…" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Wys toestelle wat nie bygewerk kan word nie" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Opsomming" msgid "Target" msgstr "Teiken" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipe" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update msgid "Unknown" msgstr "Onbekend" #. 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: waiting for device to do something msgid "Waiting…" msgstr "Wag tans…" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Skryf tans…" fwupd-1.3.9/po/ast.po000066400000000000000000000036711362775233600144430ustar00rootroot00000000000000# 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.3.9/po/ca.po000066400000000000000000001453521362775233600142420ustar00rootroot00000000000000# 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-2020 # 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: 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` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Actualització del controlador Thunderbolt %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: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s i tots els dispositius connectats poden no ser usables en actualitzar." #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s haurà d'estar connectat durant tota l'actualització per evitar danys." #. TRANSLATORS: warn the user before updating, %1 is a machine name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s haurà de romandre connectat a una font d'alimentació durant tota l'actualització per evitar danys." #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dia" msgstr[1] "%u dies" #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] " %u dispositiu té una actualització de microprogramari disponible." msgstr[1] "%u dispositius tenen una actualització de microprogramari disponible." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hora" msgstr[1] "%u hores" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "Està admès %u dispositiu local" msgstr[1] "Estan admesos %u dispositius locals" #. 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 reinstalling existing firmware versions" msgstr "Permetre tornar a instal·lar les versions existents del microprogramari" #. 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 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 to firmware mode" msgstr "Ajunta al mode microprogramari" #. 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: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Informa automàticament" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Versió del carregador d'arrencada" #. 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: command description msgid "Checks cryptographic hash matches firmware" msgstr "Comprova que la suma criptogràfica coincideix amb el microprogramari" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Suma de comprovació" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Trieu un dispositiu:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Trieu un tipus de microprogramari:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Trieu un alliberament:" #. 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: prompt to apply the update msgid "Continue with update?" msgstr "Continuo amb l'actualització?" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Converteix el microprogramari al format DFU" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Està disponible la verificació de la suma criptogràfica" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Versió actual" #. 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: multiline description of device msgid "Description" msgstr "Descripció" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Separa del mode carregador d'arrencada" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Detalls" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Etiquetes del dispositiu" #. 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "El dispositiu pot recuperar fallades de flaix" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "S'ha canviat el dispositiu:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "És obligatori el microprogramari del dispositiu per a comprovar la versió" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "El dispositiu està bloquejat" #. TRANSLATORS: a version check is required for all firmware msgid "Device is required to install all provided releases" msgstr "El dispositiu és necessari per instal·lar totes les versions llançades" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "El dispositiu es podrà usar durant tota l'actualització" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "S'ha eliminat el dispositiu:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Etapes d'actualització del dispositiu" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Cal activar l'actualització del dispositiu" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "El dispositiu no tornarà a aparèixer un cop finalitzada l'actualització" #. 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: turn on all debugging msgid "Do not include log domain prefix" msgstr "No incloure el prefix de domini del registre" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "No incloure el prefix de la marca de temps" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "No realitzar les comprovacions de seguretat al dispositiu" msgid "Do not upload report at this time, but prompt again for future updates" msgid_plural "Do not upload reports at this time, but prompt again for future updates" msgstr[0] "No enviar l'informe en aquest moment, però sol·licita-ho de nou en les futures actualitzacions" msgstr[1] "No enviar els informes en aquest moment, però sol·licita-ho de nou en les futures actualitzacions" msgid "Do not upload report, and never ask to upload reports for future updates" msgid_plural "Do not upload reports, and never ask to upload reports for future updates" msgstr[0] "No enviar l'informe, i no sol·licitar-ho mai per a les futures actualitzacions" msgstr[1] "No enviar els informes, i no sol·licitar-ho mai per a les futures actualitzacions" #. 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: length of time the update takes to apply msgid "Duration" msgstr "Durada" #. 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 "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: failed to read measurements file msgid "Failed to parse file" msgstr "Ha fallat en analitzar el fitxer" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Ha fallat en analitzar les etiquetes per a --filter" #. 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: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtra amb un conjunt d'etiquetes del dispositiu amb un prefix «~» per a excloure, p. ex., «internal,~needs-reboot»" #. 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: release properties msgid "Flags" msgstr "Etiquetes" msgid "Force the action ignoring all warnings" msgstr "Força l'acció ignorant tots els avisos" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "El GUID" msgstr[1] "Els GUID" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Obté totes les etiquetes admeses per «fwupd»" #. 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: 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: The hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "El maquinari està esperant a ser endollat" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Està ociós..." #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignora les comprovacions estrictes de SSL quan es baixin els fitxers" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignora les comprovacions de seguretat de validació" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Durada de la instal·lació" #. 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" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Instal·la primer al dispositiu pare" 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…" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Dispositiu intern" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Està en el mode carregador d'arrencada" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problema" msgstr[1] "Problemes" msgid "Keyring" msgstr " Anell de claus" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Última modificació" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Manca menys d'un minut" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Llicència" 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 line option msgid "List supported firmware updates" msgstr "Llista les actualitzacions de microprogramari compatibles" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Llista els tipus de microprogramari disponibles" #. 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: remote URI msgid "Metadata Signature" msgstr "Signatura de les metadades" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "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: smallest version number installable on device msgid "Minimum Version" msgstr "Versió mínima" #. 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" #. 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: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Requereix un reinici després de la instal·lació" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Requereix aturar després de la instal·lació" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Versió nova" msgid "No action specified!" msgstr "No s'ha especificat cap acció!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "No hi ha cap desactualització per a %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "No s'ha trobat cap ID de microprogramari" #. 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: no repositories to download from msgid "No releases available" msgstr "No hi ha cap llançament disponible" #. 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: no repositories to download from msgid "No remotes available" msgstr "No hi ha cap remot disponible" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "No s’han aplicat les actualitzacions" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Mostra només un únic valor de PCR" #. 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: command description msgid "Parse and show details about a firmware file" msgstr "Analitza i mostra els detalls sobre un fitxer de microprogramari" #. 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" #. 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: " #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Versió anterior" 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 l'enviament?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Proprietari" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Consulta el suport per a l'actualització del microprogramari" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Llegeix un blob de microprogramari des d'un dispositiu" #. 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: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Llegint des de %s…" #. 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: command description msgid "Reinstall current firmware on the device." msgstr "Torna a instal·lar el microprogramari actual al dispositiu." #. 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: the server the file is coming from #. 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: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Informat al servidor remot" #. TRANSLATORS: Must be plugged in to an outlet msgid "Requires AC power" msgstr "Requereix una font d'alimentació" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Requereix un carregador d’arrencada" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Requereix connexió a Internet" #. 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" msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Per a més informació executeu «fwupdmgr get-upgrades»." #. 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" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Planificació..." #. TRANSLATORS: Device has been chosen by the daemon for the user msgid "Selected device" msgstr "Dispositiu seleccionat" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Número de sèrie" #. 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 vendor ID on firmware file" msgstr "Estableix l'ID del proveïdor al fitxer del 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 enviades amb el certificat del client" msgid "Signature" msgstr "Signatura" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Mida" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Origen" 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: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "S'han activat correctament tots els dispositius" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "El remot s'ha inhabilitat correctament" #. TRANSLATORS: success message where we made the firmware on the #. * device older than it was before msgid "Successfully downgraded device" msgstr "El dispositiu s'ha desactualitzat correctament" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "S'han baixat les metadades noves amb èxit:" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "El remot s'ha habilitat correctament" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "El microprogramari s'ha instal·lat correctament" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "S'ha modificat amb èxit el valor de la configuració" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "El remot ha estat modificat amb èxit" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "S'han refrescat manualment amb èxit les metadades" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "S'han actualitzat correctament les sumes de verificació del dispositiu" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "S'ha enviat %u informe correctament" msgstr[1] "S'han enviat %u informes correctament" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "S'han verificat correctament les sumes de verificació del dispositiu" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Resum" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Admès en el servidor remot" 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é microprogramari que no està embargat, però encara l'ha de provar el proveïdor del maquinari. Haureu d'assegurar-vos que teniu una manera de desactualitzar 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 type, e.g. remote or local msgid "Type" msgstr "Tipus" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilitat per al microprogramari UEFI" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update msgid "Unknown" msgstr "Desconegut" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Dispositiu 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: Device is updatable in this or any other mode msgid "Updatable" msgstr "Actualitzable" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Error en actualitzar" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Missatge de l'actualització" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Estat en actualitzar" #. 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?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "L'actualització requereix un reinici" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Actualitza la suma criptogràfica emmagatzemada amb el contingut actual de la ROM" 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 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: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade available for %s from %s to %s" msgstr "Actualització disponible per a %s des de %s a %s" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Missatge de l'enviament:" msgid "Upload report just this one time, but prompt again for future updates" msgid_plural "Upload reports just this one time, but prompt again for future updates" msgstr[0] "Envia l'informe només aquesta vegada, però sol·licita-ho de nou en les futures actualitzacions" msgstr[1] "Envia els informes només aquesta vegada, però sol·licita-ho de nou en les futures actualitzacions" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Envio l'informe ara?" msgid "Upload report this time and automatically upload reports after completing future updates" msgid_plural "Upload reports this time and automatically upload reports after completing future updates" msgstr[0] "Envia l'informe aquesta vegada i torna a carregar-lo després de completar les futures actualitzacions" msgstr[1] "Envia els informes aquesta vegada i torna a carregar-los després de completar les futures actualitzacions" #. 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: command line option msgid "Use quirk flags when installing firmware" msgstr "Usa les etiquetes peculiars en instal·lar el microprogramari" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "S'ha notificat a l'usuari" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nom d'usuari" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variant" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Venedor" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "S'està verificant..." #. TRANSLATORS: try to help msgid "WARNING: Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "AVÍS: S'ignoren les comprovacions estrictes de SSL. Per a fer-ho automàticament en el futur, exporteu DISABLE_SSL_STRICT en el vostre entorn" #. 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.3.9/po/cs.po000066400000000000000000000610061362775233600142550ustar00rootroot00000000000000# 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,2019 # 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: 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: 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 line option msgid "Apply firmware updates" msgstr "Použít aktualizace firmwaru" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Napojit do režimu firmwaru" #. 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: remote checksum msgid "Checksum" msgstr "Kontrolní součet" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Vyberte zařízení:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Vyberte verzi:" #. 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: 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: multiline description of device msgid "Description" msgstr "Popis" #. 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: 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 "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: 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 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: 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 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: remote URI msgid "Metadata URI" msgstr "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." #. 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" msgid "No action specified!" msgstr "Není určena žádné akce!" #. 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." #. 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" #. 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: command line option msgid "Query for firmware update support" msgstr "Dotázat se na podporu aktualizace firmwaru" #. 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: 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: the server the file is coming from #. 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: 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" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Plánuje se…" #. 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 vendor ID on firmware file" msgstr "Nastavit ID výrobce 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: one line summary of device 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 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: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update 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: 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 "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: 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.3.9/po/da.po000066400000000000000000001366311362775233600142430ustar00rootroot00000000000000# 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-2020 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 "Opdatering for controller %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 "Opdatering af indlejret controller %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: 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` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s opdatering af Thunderbolt-controller" #. 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: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s og alle tilsluttede enheder vil måske ikke være anvendelige under opdatering." #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s skal være tilsluttet under hele opdateringen, for at undgå skade." #. TRANSLATORS: warn the user before updating, %1 is a machine name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s skal være tilsluttet en strømkilde under hele opdateringen, for at undgå skade." #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dag" msgstr[1] "%u dage" #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u enhed har en tilgængelig firmwareopgradering." msgstr[1] "%u enheder har en tilgængelig firmwareopgradering." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u time" msgstr[1] "%u timer" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u lokal enhed understøttes" msgstr[1] "%u lokale enheder understøttes" #. 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 reinstalling 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 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 to firmware mode" msgstr "Tilkobl til firmwaretilstand" #. 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: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatisk rapportering" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Opstartsindlæser version" #. 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: command description msgid "Checks cryptographic hash matches firmware" msgstr "Tjekker om den kryptografiske hash passer med firmwaren" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Vælg en enhed:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Vælg en firmwaretype:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Vælg en udgivelse:" #. 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: prompt to apply the update msgid "Continue with update?" msgstr "Fortsæt opdateringen?" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Konverter firmware til DFU-format" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Bekræftelse af kryptografisk hash er tilgængelig" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Nuværende version" #. 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: multiline description of device msgid "Description" msgstr "Beskrivelse" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Frakobl til opstartsindlæsertilstand" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Detaljer" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Enhedsflag" #. 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Enheden kan gendannes efter mislykkedes flash" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Enhed ændret:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Enhedsfirmware kræves for at have et versiontjek" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Enheden er låst" #. TRANSLATORS: a version check is required for all firmware msgid "Device is required to install all provided releases" msgstr "Enhed kræves for at installere alle leverede udgivelser" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Enheden kan anvendes under hele opdateringen" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Enhed fjernet:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Enhedstrin-opdateringer" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Enhedsopdatering behøver aktivering" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Enheden vises ikke igen når opdateringen er færdig" #. 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: turn on all debugging msgid "Do not include log domain prefix" msgstr "Medtag ikke præfiks for logdomæne" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Medtag ikke tidsstempelpræfiks" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Udfør ikke sikkerhedstjek af enhed" msgid "Do not upload report at this time, but prompt again for future updates" msgid_plural "Do not upload reports at this time, but prompt again for future updates" msgstr[0] "Upload ikke rapport på nuværende tidspunkt men spørg igen om fremtidige opdateringer" msgstr[1] "Upload ikke rapporter på nuværende tidspunkt men spørg igen om fremtidige opdateringer" msgid "Do not upload report, and never ask to upload reports for future updates" msgid_plural "Do not upload reports, and never ask to upload reports for future updates" msgstr[0] "Upload ikke rapport og spørg aldrig om at uploade rapporter om fremtidige opdateringer" msgstr[1] "Upload ikke rapporter og spørg aldrig om at uploade rapporter om fremtidige opdateringer" #. 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: length of time the update takes to apply msgid "Duration" msgstr "Varighed" #. 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 "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: failed to read measurements file msgid "Failed to parse file" msgstr "Kunne ikke fortolke fil" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Kunne ikke fortolke flag for --filter" #. 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: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtrer med et sæt enhedsflag med et ~-præfiks for at udelukke, f.eks. 'intern,~behøver-genstart'" #. 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: release properties msgid "Flags" msgstr "Flag" msgid "Force the action ignoring all warnings" msgstr "Tving handlingen og ignorer alle advarsler" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID'er" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Hent alle enhedsflag som understøttes af fwupd" #. 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: 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: The hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Hardwaren venter på at bliver gentilkoblet" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Inaktiv …" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignorer strikse SSL-tjek ved download af filer" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignorer sikkerhedstjek af bekræftelse" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Varighed for installation" #. 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" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Installer først til forælderenhed" 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 …" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Intern enhed" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Er i opstartsindlæsertilstand" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problemstilling" msgstr[1] "Problemstillinger" msgid "Keyring" msgstr "Nøglering" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Sidst redigeret" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Mindre end et minut tilbage" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licens" 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 line option msgid "List supported firmware updates" msgstr "Vis understøttede firmwareopdateringer" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Vis de tilgængelige firmwaretyper" #. 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: remote URI msgid "Metadata Signature" msgstr "Underskrift for metadata" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "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: smallest version number installable on device msgid "Minimum Version" msgstr "Minimum version" #. 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" #. 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: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Genstart efter installation er nødvendig" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Nedlukning efter installation er nødvendig" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Ny version" msgid "No action specified!" msgstr "Der er ikke angivet nogen handling!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Ingen nedgraderinger til %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Fandt ingen firmware-id'er" #. 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: no repositories to download from msgid "No releases available" msgstr "Ingen tilgængelige udgivelser" #. 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: no repositories to download from msgid "No remotes available" msgstr "Ingen tilgængelige fjerne" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Der blev ikke anvendt nogen opdateringer" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Vis kun én PCR-værdi" #. 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: command description msgid "Parse and show details about a firmware file" msgstr "Fortolk og vis deltaljer om en firmwarefil" #. 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" #. 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: " #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Forrige version" 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: a non-free software license msgid "Proprietary" msgstr "Proprietær" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Forespørg om understøttelse af firmwareopdatering" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Læs en firmwareblob fra en enhed" #. 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: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Læser fra %s …" #. 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: command description msgid "Reinstall current firmware on the device." msgstr "Geninstaller den nuværende firmware på enheden." #. 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: the server the file is coming from #. 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: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Rapporteret til fjernserver" #. TRANSLATORS: Must be plugged in to an outlet msgid "Requires AC power" msgstr "Kræver strømforsygning" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Kræver en opstartsindlæser" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Kræver internetforbindelse" #. 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" msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Kør `fwupdmgr get-upgrades` for mere information." #. 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" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Planlægger …" #. TRANSLATORS: Device has been chosen by the daemon for the user msgid "Selected device" msgstr "Valgte enhed" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Serienummer" #. 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 vendor ID on firmware file" msgstr "Indstil producent-id på 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" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Størrelse" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Kilde" 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: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Det lykkedes at aktivere alle enheder" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Det lykkedes at deaktivere fjern" #. TRANSLATORS: success message where we made the firmware on the #. * device older than it was before msgid "Successfully downgraded device" msgstr "Det lykkedes at nedgradere enhed" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Det lykkedes at downloade ny metadata: " #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Det lykkedes at aktivere fjern" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Det lykkedes at installere firmware" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Det lykkedes at redigere konfigurationsværdi" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Det lykkedes at redigere fjern" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Det lykkedes at opdatere metadata manuelt" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Opdatering af enhedens tjeksumme lykkedes" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Det lykkedes at uploade %u rapport" msgstr[1] "Det lykkedes at uploade %u rapporter" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Bekræftelse af enhedens tjeksumme lykkedes" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Opsummering" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Understøttes på fjernserver" 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 type, e.g. remote or local msgid "Type" msgstr "Type" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI-firmwareredskab" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update msgid "Unknown" msgstr "Ukendt" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Ukendt enhed" 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: Device is updatable in this or any other mode msgid "Updatable" msgstr "Kan opdateres" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Fejl ved opdatering" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Opdateringsmeddelelese" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Opdateringstilstand" #. 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?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Opdatering kræver en genstart" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Opdater den gemte kryptografiske hash med indholdet fra den nuværende ROM" msgid "Update the stored device verification information" msgstr "Opdaterer de gemte informationer om enhedsverifikation" #. 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: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade available for %s from %s to %s" msgstr "Opgradering tilgængelig for %s fra %s til %s" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Uploadmeddelelse:" msgid "Upload report just this one time, but prompt again for future updates" msgid_plural "Upload reports just this one time, but prompt again for future updates" msgstr[0] "Upload rapport denne ene gang men spørg igen om fremtidige opdateringer" msgstr[1] "Upload rapporter denne ene gang men spørg igen om fremtidige opdateringer" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Upload rapport nu?" msgid "Upload report this time and automatically upload reports after completing future updates" msgid_plural "Upload reports this time and automatically upload reports after completing future updates" msgstr[0] "Upload rapport denne gang og upload automatisk rapporter efter fuldførsel af fremtidige opdateringer" msgstr[1] "Upload rapporter denne gang og upload automatisk rapporter efter fuldførsel af fremtidige opdateringer" #. 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: command line option msgid "Use quirk flags when installing firmware" msgstr "Brug quirk-flag ved installation af firmware" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Brugeren er blevet underrettet" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Brugernavn" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variant" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Producent" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verificerer …" #. TRANSLATORS: try to help msgid "WARNING: Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "ADVARSEL: Ignorerer strikse SSL-tjeks. Eksportér DISABLE_SSL_STRICT i dit miljø for at gøre det automatisk i fremtiden" #. 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.3.9/po/de.po000066400000000000000000000544141362775233600142450ustar00rootroot00000000000000# 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: 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: 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 line option msgid "Apply firmware updates" msgstr "Firmware-Aktualisierungen anwenden" #. 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: remote checksum msgid "Checksum" msgstr "Prüfsumme" #. 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: 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: 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: multiline description of device msgid "Description" msgstr "Beschreibung" #. 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: 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 "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: 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 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: 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 line option msgid "List supported firmware updates" msgstr "Unterstützte Firmware-Aktualisierungen auflisten" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Laden …" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadaten-URI" #. 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." #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Hintergrundprogramm auf Ereignisse überwachen" msgid "No action specified!" msgstr "Keine Aktion angegeben!" #. 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" #. 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" #. 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: command line option msgid "Query for firmware update support" msgstr "Abfrage der Unterstützung für Firmware-Aktualisierungen" #. 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: 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: 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" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Einplanen …" #. 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 vendor ID on firmware file" msgstr "Hersteller-Kennung 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: one line summary of device 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 type, e.g. remote or local msgid "Type" msgstr "Typ" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI-Firmware-Dienstprogramm" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update 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: 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 "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: 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.3.9/po/en_GB.po000066400000000000000000001326411362775233600146260ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Andi Chandler , 2019 # 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: 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 Consumer ME Update" #. 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 Controller Update" #. 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 Corporate ME Update" #. 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 Device Update" #. 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 Embedded Controller Update" #. 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 Update" #. 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 System Update" #. 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` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt Controller Update" #. 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 Update" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s and all connected devices may not be usable while updating." #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s must remain connected for the duration of the update to avoid damage." #. TRANSLATORS: warn the user before updating, %1 is a machine name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s must remain plugged into a power source for the duration of the update to avoid damage." #. 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: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u local device supported" msgstr[1] "%u local devices supported" #. 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: command description msgid "Activate devices" msgstr "Activate devices" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Activate pending devices" msgid "Activate the new firmware on the device" msgstr "Activate the new firmware on the device" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Activating firmware update" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Activating firmware update for" #. 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 reinstalling existing firmware versions" msgstr "Allow reinstalling 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 line option msgid "Apply firmware updates" msgstr "Apply firmware updates" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Approved firmware:" msgstr[1] "Approved firmware:" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Attach to firmware mode" #. 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 modify daemon configuration" msgstr "Authentication is required to modify daemon configuration" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Authentication is required to set the list of approved firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Authentication is required to sign data using the client certificate" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Authentication is required to switch to the new firmware version" #. 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: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatic Reporting" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Bootloader Version" #. 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: command description msgid "Checks cryptographic hash matches firmware" msgstr "Checks cryptographic hash matches firmware" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Choose a device:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Choose a firmware type:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Choose a release:" #. 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: prompt to apply the update msgid "Continue with update?" msgstr "Continue with update?" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Convert firmware to DFU format" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Cryptographic hash verification is available" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Current version" #. 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: multiline description of device msgid "Description" msgstr "Description" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Detach to bootloader mode" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Details" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Device Flags" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Device ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Device added:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Device can recover flash failures" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Device changed:" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Device is locked" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Device is usable for the duration of the update" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Device removed:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Device stages updates" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Device update needs activation" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Device will not re-appear after update completes" #. 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: turn on all debugging msgid "Do not include log domain prefix" msgstr "Do not include log domain prefix" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Do not include timestamp prefix" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Do not perform device safety checks" msgid "Do not upload report at this time, but prompt again for future updates" msgid_plural "Do not upload reports at this time, but prompt again for future updates" msgstr[0] "Do not upload report at this time, but prompt again for future updates" msgstr[1] "Do not upload reports at this time, but prompt again for future updates" msgid "Do not upload report, and never ask to upload reports for future updates" msgid_plural "Do not upload reports, and never ask to upload reports for future updates" msgstr[0] "Do not upload report, and never ask to upload reports for future updates" msgstr[1] "Do not upload reports, and never ask to upload reports for future updates" #. 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: length of time the update takes to apply msgid "Duration" msgstr "Duration" #. 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 "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: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "Failed to connect to daemon" #. TRANSLATORS: the server is rate-limiting downloads msgid "Failed to download due to server limit" msgstr "Failed to download due to server limit" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Failed to get pending devices" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Failed to install firmware update" #. 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: failed to read measurements file msgid "Failed to parse file" msgstr "Failed to parse file" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Failed to parse flags for --filter" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Failed to reboot" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Failed to set splash mode" #. 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: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" #. TRANSLATORS: program name msgid "Firmware Agent" msgstr "Firmware Agent" #. 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." #. TRANSLATORS: release properties msgid "Flags" msgstr "Flags" msgid "Force the action ignoring all warnings" msgstr "Force the action ignoring all warnings" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUIDs" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Get all device flags supported by fwupd" #. TRANSLATORS: command description msgid "Get all devices and possible releases" msgstr "Get all devices and possible releases" #. 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: firmware approved by the admin msgid "Gets the list of approved firmware." msgstr "Gets the list of approved 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: The hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Hardware is waiting to be replugged" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Idle…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignore SSL strict checks when downloading files" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignore validation safety checks" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Install Duration" #. 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" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Install to parent device first" msgid "Install unsigned device firmware" msgstr "Install unsigned device firmware" msgid "Install unsigned system firmware" msgstr "Install unsigned system firmware" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Installing 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…" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Internal device" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Is in bootloader mode" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Issue" msgstr[1] "Issues" msgid "Keyring" msgstr "Keyring" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Last modified" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Less than one minute remaining" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licence" 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 line option msgid "List supported firmware updates" msgstr "List supported firmware updates" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "List the available firmware types" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Loading…" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Manually whitelist specific plugins" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Metadata Signature" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadata URI" #. 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." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Minimum Version" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Mismatched daemon and client, use %s instead" #. TRANSLATORS: sets something in daemon.conf msgid "Modifies a daemon configuration value." msgstr "Modifies a daemon configuration value." #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifies a given remote" msgid "Modify a configured remote" msgstr "Modify a configured remote" msgid "Modify daemon configuration" msgstr "Modify daemon configuration" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitor the daemon for events" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Needs a reboot after installation" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Needs shutdown after installation" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "New version" msgid "No action specified!" msgstr "No action specified!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "No downgrades for %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "No firmware IDs found" #. 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: no repositories to download from msgid "No releases available" msgstr "No releases available" #. 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." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "No remotes available" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "No updates were applied" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Only show single PCR value" #. 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: command line option msgid "Override warnings and force the action" msgstr "Override warnings and force the action" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Parse and show details about a firmware file" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Password" msgid "Payload" msgstr "Payload" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentage complete" #. 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: " #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Previous version" 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: a non-free software license msgid "Proprietary" msgstr "Proprietary" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Query for firmware update support" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Read a firmware blob from a device" #. 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: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Reading from %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Reading…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Rebooting…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Refresh metadata from remote server" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device." msgstr "Reinstall current firmware on the device." #. 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: the server the file is coming from #. 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: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Reported to remote server" #. TRANSLATORS: Must be plugged in to an outlet msgid "Requires AC power" msgstr "Requires AC power" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Requires a bootloader" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Requires internet connection" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Restart now?" #. TRANSLATORS: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Restart the daemon to make the change effective?" #. 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" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "Save device state into a JSON file between executions" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Schedule installation for next reboot when possible" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Scheduling…" #. TRANSLATORS: Device has been chosen by the daemon for the user msgid "Selected device" msgstr "Selected device" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Serial Number" #. 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 vendor ID on firmware file" msgstr "Set vendor ID on firmware file" msgid "Sets the list of approved firmware" msgstr "Sets the list of approved firmware" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware." msgstr "Sets the list of approved firmware." #. 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: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Show daemon verbose information for a particular domain" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Show debugging information for all domains" #. 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 "Sign data using the client certificate" msgstr "Sign data using the client certificate" msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Sign data using the client certificate" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Sign the uploaded data with the client certificate" msgid "Signature" msgstr "Signature" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Size" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Source" 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: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Successfully activated all devices" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Successfully disabled remote" #. TRANSLATORS: success message where we made the firmware on the #. * device older than it was before msgid "Successfully downgraded device" msgstr "Successfully downgraded device" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Successfully downloaded new metadata: " #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Successfully enabled remote" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Successfully installed firmware" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Successfully modified configuration value" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Successfully modified remote" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Successfully refreshed metadata manually" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Successfully updated device checksums" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Successfully uploaded %u report" msgstr[1] "Successfully uploaded %u reports" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Successfully verified device checksums" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Summary" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Supported on remote server" 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: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "There is no approved firmware." #. 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: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "This tool can only be used by the root user" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Type" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI Firmware Utility" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update msgid "Unknown" msgstr "Unknown" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Unknown Device" 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: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Unsupported daemon version %s, client version is %s" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Updatable" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Update Error" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Update Message" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Update State" #. 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?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Update requires a reboot" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Update the stored cryptographic hash with current ROM contents" msgid "Update the stored device verification information" msgstr "Update the stored device verification information" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Update the stored metadata with current 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: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade available for %s from %s to %s" msgstr "Upgrade available for %s from %s to %s" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Upload message:" msgid "Upload report just this one time, but prompt again for future updates" msgid_plural "Upload reports just this one time, but prompt again for future updates" msgstr[0] "Upload report just this one time, but prompt again for future updates" msgstr[1] "Upload reports just this one time, but prompt again for future updates" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Upload report now?" msgid "Upload report this time and automatically upload reports after completing future updates" msgid_plural "Upload reports this time and automatically upload reports after completing future updates" msgstr[0] "Upload report this time and automatically upload reports after completing future updates" msgstr[1] "Upload reports this time and automatically upload reports after completing future updates" #. 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: command line option msgid "Use quirk flags when installing firmware" msgstr "Use quirk flags when installing firmware" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "User has been notified" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Username" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variant" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Vendor" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifying…" #. TRANSLATORS: try to help msgid "WARNING: Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "WARNING: Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" #. 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.3.9/po/eo.po000066400000000000000000000036461362775233600142610ustar00rootroot00000000000000# 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: multiline description of device msgid "Description" msgstr "Priskribo" #. success msgid "Done!" msgstr "Farita!" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Trovita" msgid "ID" msgstr "ID" msgid "Mode" msgstr "Reĝimo" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' 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" fwupd-1.3.9/po/eu.po000066400000000000000000000030701362775233600142560ustar00rootroot00000000000000# 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.3.9/po/fi.po000066400000000000000000001400771362775233600142540ustar00rootroot00000000000000# 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-2020 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: 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` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt-ohjaimen 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: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s ja kaikki kytketyt laitteet eivät välttämättä ole käyttökelpoisia päivityksen aikana." #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s on oltava kytkettynä päivityksen ajaksi vaurioiden välttämiseksi." #. TRANSLATORS: warn the user before updating, %1 is a machine name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s on oltava kytkettynä virtalähteeseen päivityksen ajaksi vaurioiden välttämiseksi." #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%upäivää" msgstr[1] "%upäivää" #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u laitteessa on saatavilla firmware -päivitys." msgstr[1] "%u laitteessa on saatavilla firmware -päivitys." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u tuntia" msgstr[1] "%utuntia" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%upaikallisia laitteita tuettu" msgstr[1] "%upaikallisia laitteita tuettu" #. 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 reinstalling 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 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 to firmware mode" msgstr "Kiinnitä laiteohjelmistotilaan" #. 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: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automaattinen raportointi" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Käynnistyslataimen versio" #. 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: command description msgid "Checks cryptographic hash matches firmware" msgstr "Tarkistaa salaustekniikan, joka vastaa laiteohjelmistoa" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Tarkistussumma" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Valitse laite:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Valitse laiteohjelman tyyppi:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Valitse julkaisu:" #. 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: prompt to apply the update msgid "Continue with update?" msgstr "Jatketaanko päivitystä?" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Muunna firmware DFU-muotoon" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Salauksen algoritmin tarkistus on käytettävissä" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Nykyinen versio" #. 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: multiline description of device msgid "Description" msgstr "Kuvaus" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Irrota käynnistyslataimen tilaan" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Tiedot" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Laitteen liput" #. 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Laite voi palauttaa virheitä" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Laite muutettu:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Laitteen firmware vaatii version tarkistamista" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Laite on lukittu" #. TRANSLATORS: a version check is required for all firmware msgid "Device is required to install all provided releases" msgstr "Laite edellyttää asentamaan kaikki toimitetut versiot" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Laitetta voidaan käyttää päivityksen aikana" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Laite poistettu:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Laitevyöhykkeen päivitykset" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Laitteen päivitys tarvitsee aktivointia" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Laitetta ei näytetä uudelleen päivityksen päätyttyä" #. 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: turn on all debugging msgid "Do not include log domain prefix" msgstr "Älä sisällytä lokiin toimialueen etuliitettä" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Älä sisällytä aikaleiman etuliitettä" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Älä suorita laitteen turvallisuustarkastuksia" msgid "Do not upload report at this time, but prompt again for future updates" msgid_plural "Do not upload reports at this time, but prompt again for future updates" msgstr[0] "Älä lähetä raportteja tällä hetkellä, mutta kysy uudelleen tulevissa päivityksissä" msgstr[1] "Älä lähetä raportteja tällä hetkellä, mutta kysy uudelleen tulevissa päivityksissä" msgid "Do not upload report, and never ask to upload reports for future updates" msgid_plural "Do not upload reports, and never ask to upload reports for future updates" msgstr[0] "Älä lähetä raportteja äläkä koskaan pyydä lähettämään raportteja tulevissa päivityksissä" msgstr[1] "Älä lähetä raportteja äläkä koskaan pyydä lähettämään raportteja tulevissa päivityksissä" #. 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: length of time the update takes to apply msgid "Duration" msgstr "Kesto" #. 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 "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: failed to read measurements file msgid "Failed to parse file" msgstr "Tiedoston käsittely epäonnistui" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Lippujen erittely ei onnistunut --suodatin" #. 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: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Suodata laite lippuja käyttäen ~ etuliiteellä sulkea pois, esim. 'sisäiset, ~ vaatii käynnistyksen'" #. 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: release properties msgid "Flags" msgstr "Liput" msgid "Force the action ignoring all warnings" msgstr "Pakota toimenpide huomioimatta kaikki varoitukset" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUIDs" msgstr[1] "GUIDs" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Hanki kaikki fwupd -tukemat laiteliput" #. 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: 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: The hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Laitteisto odottaa uudelleen kytkemistä" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Jouten…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ohita tiukat SSL tarkistukset tiedostoja ladattaessa" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ohita turvatarkastukset" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Asennuksen kesto" #. 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" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Asenna ensin päälaitteelle" 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…" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Sisäinen laite" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "On käynnistyslatain moodissa" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Kysymykset" msgstr[1] "Kysymykset" msgid "Keyring" msgstr "Avaimet" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Viimeksi muokattu" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Jäljellä on alle minuutti" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Lisenssi" 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 line option msgid "List supported firmware updates" msgstr "Lista tuetuista firmware-päivityksistä" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Luettelo käytettävissä olevista laiteohjelmistotyypeistä" #. 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: remote URI msgid "Metadata Signature" msgstr "Metatietojen allekirjoitus" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metatietojen URI" #. 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: smallest version number installable on device msgid "Minimum Version" msgstr "Vähimmäisversio" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Virheellinen taustaprosessi ja asiakas, käytä sen sijaan %s" #. 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: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Tarvitsee käynnistyksen asennuksen jälkeen" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Tarvitsee sammutuksen asennuksen jälkeen" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Uusi versio" msgid "No action specified!" msgstr "Toimintoa ei ole määritetty!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Ei versioalennusta kohteelle %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Laiteohjelmiston tunnuksia ei löytynyt" #. 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: no repositories to download from msgid "No releases available" msgstr "Ei julkaisuja saatavilla" #. 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: no repositories to download from msgid "No remotes available" msgstr "Kaukosäätimet ei saatavilla" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Ei soveltuvia päivityksiä" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Näytä vain yksi PCR-arvo" #. 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: command description msgid "Parse and show details about a firmware file" msgstr "Analysoi ja näytä tiedot laiteohjelmiston tiedostosta" #. 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" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Anna numero 0 -%u" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Aiempi versio" 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: a non-free software license msgid "Proprietary" msgstr "Patentoitu" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Kysely firmware-päivityksen tuesta" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Lue laiteohjelmiston BLOB laitteesta" #. 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: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Lukeminen %s…" #. 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: command description msgid "Reinstall current firmware on the device." msgstr "Asenna nykyinen laiteohjelmisto laitteeseen." #. 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: the server the file is coming from #. 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: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Raportoitu etäpalvelimelle" #. TRANSLATORS: Must be plugged in to an outlet msgid "Requires AC power" msgstr "Vaatii verkkovirtaa" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Vaatii käynnistyslataimen" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Vaatii Internet-yhteyden" #. 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" msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Suorita `fwupdmgr get-upgrades` saadaksesi lisätietoja." #. 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 " #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Ajoitetaan…" #. TRANSLATORS: Device has been chosen by the daemon for the user msgid "Selected device" msgstr "Valittu laite" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Sarjanumero" #. 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 vendor ID on firmware file" msgstr "Aseta toimittajan tunnus 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" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Koko" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Lähde" 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: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Kaikkien laitteiden aktivointi onnistui" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Onnistuneesti poistettu etäkone" #. TRANSLATORS: success message where we made the firmware on the #. * device older than it was before msgid "Successfully downgraded device" msgstr "Laite uusittu onnistuneesti" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Uusien metatietojen lataus onnistui:" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Onnistuneesti lisätty etäkone" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Laiteohjelmiston asennus onnistui" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Konfigurointiarvo on muokattu onnistuneesti" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Etäkäytön muokkaus onnistui" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Päivitetty metatiedot manuaalisesti" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Laitteiden tarkistussummat päivitettiin onnistuneesti" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Raporttien lataus %u onnistui" msgstr[1] "Raporttien lataus %u onnistui" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Laitteiden tarkistussummat vahvistettu onnistuneesti" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Yhteenveto" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Tuettu etäpalvelimella" 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 type, e.g. remote or local msgid "Type" msgstr "Tyyppi" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI firmware -apuohjelma" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update msgid "Unknown" msgstr "Tuntematon" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Tuntematon laite" 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: Device is updatable in this or any other mode msgid "Updatable" msgstr "Päivitettävissä" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Päivitys virhe" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Päivitä viesti" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Päivitä tila" #. 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?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Päivitys edellyttää käynnistyksen" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Päivitä tallennettu salaus nykyisellä ROM-sisällöllä" msgid "Update the stored device verification information" msgstr "Päivitä laitteen tallennetut vahvistustiedot" #. 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: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade available for %s from %s to %s" msgstr "Päivitys saatavilla %s alkaen %s kohde %s" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Lähetä viesti:" msgid "Upload report just this one time, but prompt again for future updates" msgid_plural "Upload reports just this one time, but prompt again for future updates" msgstr[0] "Lataa raportit vain tällä kertaa, mutta pyydä uudelleen tulevissa päivityksissä" msgstr[1] "Lataa raportit vain tällä kertaa, mutta pyydä uudelleen tulevissa päivityksissä" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Lataa raportti nyt?" msgid "Upload report this time and automatically upload reports after completing future updates" msgid_plural "Upload reports this time and automatically upload reports after completing future updates" msgstr[0] "Lataa raportit tällä kertaa ja lähetä raportit automaattisesti, kun olet suorittanut tulevat päivitykset" msgstr[1] "Lataa raportit tällä kertaa ja lähetä raportit automaattisesti, kun olet suorittanut tulevat päivitykset" #. 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: command line option msgid "Use quirk flags when installing firmware" msgstr "Käytä Quirk-lippuja asennettaessa laiteohjelmistoa" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Käyttäjälle on ilmoitettu" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Käyttäjätunnus" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Muunnelma" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Toimittaja" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Vahvistetaan…" #. TRANSLATORS: try to help msgid "WARNING: Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "VAROITUS: SSL tarkistusten sivuuttaminen, jotta se tapahtuu automaattisesti tulevassa DISABLE_SSL_STRICT ympäristössä" #. 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.3.9/po/fr.po000066400000000000000000000062051362775233600142570ustar00rootroot00000000000000# 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 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.3.9/po/fur.po000066400000000000000000000764411362775233600144550ustar00rootroot00000000000000# 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,2020 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" #. more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Al mancjie %.0f minût" msgstr[1] "A mancjin %.0f minûts" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u dispositîf locâl supuartât" msgstr[1] "%u dispositîfs locâi supuartâts" msgid "Activate the new firmware on the device" msgstr "Ative il gnûf firmware sul dispositîf" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Ativazion inzornament 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: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Acetâ e abilitâ il rimot?" #. 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 reinstalling existing firmware versions" msgstr "Permet di tornâ a instalâ lis versions dal 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: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "Un inzornament, par completâsi, al à bisugne che si distudedi il sisteme." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Rispuint di sì a dutis lis domandis" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Apliche i inzornaments firmware" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Firmware aprovât:" msgstr[1] "Firmware aprovâ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 modify daemon configuration" msgstr "La autenticazion e je necessarie par modificâ la configurazion dal demoni" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "La autenticazion e je necessarie par stabilî la liste dai firmware aprovâts" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "La autenticazion e je necessarie par firmâ i dâts doprant il certificât dal client" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "La autenticazion e je necessarie par passâ ae gnove version 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: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Sielç un dispositîf:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Sielç un gjenar di firmware:" #. 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: prompt to apply the update msgid "Continue with update?" msgstr "Continuâ cul inzornament?" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Convertìs il firmware tal formât DFU" #. 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: multiline description of device 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:" msgid "Disabled fwupdate debugging" msgstr "Disabilite il debug di fwupdate" #. TRANSLATORS: command line option msgid "Do not check for reboot after update" msgstr "No sta controlâ se si scugne tornâ a inviâ daspò un inzornament" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "No sta includi il prefìs il domini dal regjistri" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "No sta includi il prefìs date/ore" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "No sta eseguî i controi di sigurece dal dispositîf" msgid "Do not upload report at this time, but prompt again for future updates" msgid_plural "Do not upload reports at this time, but prompt again for future updates" msgstr[0] "Pal moment no sta inviâ il rapuart, ma torne domande pai inzornaments futûrs" msgstr[1] "Pal moment no sta inviâ i rapuarts, ma torne domande pai inzornaments futûrs" msgid "Do not upload report, and never ask to upload reports for future updates" msgid_plural "Do not upload reports, and never ask to upload reports for future updates" msgstr[0] "No sta inviâ il rapuart e no sta domandâ plui di inviâ i rapuarts pai inzornaments futûrs" msgstr[1] "No sta inviâ i rapuarts e no sta domandâ plui di inviâ i rapuarts pai inzornaments futûrs" #. 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: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Daûr a degradâ di version %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: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "L'ESP specificât nol jere valit" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Abilite il supuart dal inzornament dal firmware sui sistemis supuartâts" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Abilitâ chest rimot?" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Abilitât" msgid "Enabled fwupdate debugging" msgstr "Abilite il debug di fwupdate" 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 abilite cheste funzionalitât a propri pericul, che al significhe che, par ogni probleme causât di chescj inzornaments, si à di contatâ il produtôr origjinâl dal imprest. Dome i problemis che si àn cul sôl procès di inzornament a àn di sei inviâts a $OS_RELEASE:BUG_REPORT_URL$." #. 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: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "No si è rivâts a conetisi al demoni" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "No si è rivâts a otignî i dispositîfs in spiete" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "No si è rivâts a instalâ l'inzornament dal firmware" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "No si è rivâts a analizâ i argoments" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "No si è rivâts a analizâ il file" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr " No si è rivâts a tornâ a inviâ il sisteme" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "No si è rivâts a stabilî la modalitât splash" #. 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: program name msgid "Firmware Agent" msgstr "Agjent Firmware" #. 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." msgid "Firmware updates are not supported on this machine." msgstr "Su cheste machine no son supuartâts i inzornaments firmware." msgid "Firmware updates are supported on this machine." msgstr "Su cheste machine a son supuartâts i inzornaments firmware." #. TRANSLATORS: command description msgid "Get all devices and possible releases" msgstr "Oten ducj i dispositîfs e lis pussibilis publicazions" #. 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 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: 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: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Daûr a instalâ il firmware…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Daûr a instalâ l'inzornament dal firmware…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Daûr a instalâ su %s…" msgid "Keyring" msgstr "Puarteclâfs" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Al mancje mancul di un minût" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Servizi Firmware dal vendidôr di Linux (firmware stabil)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Servizi Firmware dal vendidôr di Linux (firmware di prove)" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Liste dai inzornaments di firmware supuartâts" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Daûr a cjariâ…" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI metadata" #. 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" msgid "Modify daemon configuration" msgstr "Modifiche la configurazion dal demoni" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitore il demoni pai events" msgid "No action specified!" msgstr "Nissune azion specificade!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Nissune degradazion di version par %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nissun ID di firmware cjatât" #. 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" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nissun plugin cjatât" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Nissune publicazion disponibile" #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Nissun rimot disponibil" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nol è stât aplicât nissun inzornament" #. TRANSLATORS: command line option msgid "Override plugin warning" msgstr "Ignore i avertiments dal plugin" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Passe parsore al valôr dal percors ESP predefinît" #. TRANSLATORS: command line option msgid "Override warnings and force the action" msgstr "Ignore i avertiments e sfuarce la azion" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Password" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentuâl di completament" #. 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: " msgid "Print the version number" msgstr "Stampe il numar de version" msgid "Print verbose debug statements" msgstr "Stampe lis declarazions di debug in maniere prolisse" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritât" msgid "Proceed with upload?" msgstr "Procedi cul inviament?" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Interogazion pal supuart dai inzornaments firmware" #. 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: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Daûr a lei di %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Daûr a lei…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Daûr a tornâ a inviâ il sisteme…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Inzorne i metadâts dal servidôr rimot" #. 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: the server the file is coming from #. 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: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Tornâ a inviâ il demoni par rindi efetive la modifiche?" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Daûr a planificâ…" #. TRANSLATORS: Device has been chosen by the daemon for the user msgid "Selected device" msgstr "Dispositîf selezionât" #. 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 line option msgid "Set the debugging flag during update" msgstr "Stabilìs la opzion di debug dilunc l'inzornament" #. TRANSLATORS: command description msgid "Set vendor ID on firmware file" msgstr "Met il ID dal produtôr sul file dal firmware" msgid "Sets the list of approved firmware" msgstr "Al stabilìs la liste dai firmware aprovâts" #. 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: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Mostre lis informazions prolissis dal demoni par un domini particolâr" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Mostre lis informazions di debug par ducj i dominis" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostre opzions di debug" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Mostre i dispositîfs che no si puedin inzornâ" #. 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: command line option msgid "Show the debug log from the last attempted update" msgstr "Mostre il regjistri dal debug dal ultin tentatîf di inzornament" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Mostre lis informazions sul stât dal inzornament dal firmware" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Distudâ cumò?" msgid "Sign data using the client certificate" msgstr "Firme i dâts doprant il certificât dal client" msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Firme i dâts doprant il certificât dal client" msgid "Signature" msgstr "Firme" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Rimot disabilitât cun sucès" #. TRANSLATORS: success message where we made the firmware on the #. * device older than it was before msgid "Successfully downgraded device" msgstr "Dispositîf degradât di version cun sucès" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Gnûf metadât discjariât cun sucès:" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Rimot abilitât cun sucès" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware instalât cun sucès" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Valôr di configurazion modificât cun sucès" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Rimot modificât cun sucès" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadât inzornât a man cun sucès" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "%u rapuart inviât cun sucès" msgstr[1] "%u rapuarts inviâts cun sucès" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Sintesi" msgid "Target" msgstr "Destinazion" #. 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 "Il LVFS — Servizi firmware dal vendidôr di linux — al è un servizi gratuit che al opere come entitât legâl indipendente e no à conession cun $OS_RELEASE:NAME$. Il to distributôr al podarès no vê verificât la compatibilitât di nissun dai inzornaments firmware cul vuestri sisteme o cui dispositîfs tacâts. Ducj i firmware a son furnîts dome dal produtôr origjinâl dal imprest." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "No'nd è nissun firmware aprovât." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Chest program al pues lavorâ in maniere juste dome come 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 "Chest rimot al conten firmware che no son sot di embargo, ma a son ancjemò in prove dal vendidôr dal hardware. Si à di sigurâsi di vê une maniere par puartâ indaûr a man il firmware ae version precedente, se l'inzornament al falìs." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Chest strument al pues jessi doprât dome dal utent root" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Gjenar" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilitât Firmware UEFI" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update 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: command line option msgid "Unset the debugging flag during update" msgstr "Gjave la opzion di debug dilunc l'inzornament" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Version %s dal demoni no supuartade, la version dal client e je la %s" #. 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 "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: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Daûr a inzornâ %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade available for %s from %s to %s" msgstr "Inzornament disponibil par %s di %s a %s" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Messaç dal inviament:" msgid "Upload report just this one time, but prompt again for future updates" msgid_plural "Upload reports just this one time, but prompt again for future updates" msgstr[0] "Invie il rapuart dome cheste volte, ma torne domande pai inzornaments futûrs" msgstr[1] "Invie i rapuarts dome cheste volte, ma torne domande pai inzornaments futûrs" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Inviâ il rapuart cumò?" msgid "Upload report this time and automatically upload reports after completing future updates" msgid_plural "Upload reports this time and automatically upload reports after completing future updates" msgstr[0] "Invie il rapuart par cheste volte e invie in automatic i rapuarts dopo vê completât i inzornaments futûrs" msgstr[1] "Invie i rapuarts par cheste volte e invie in automatic i rapuarts dopo vê completât i inzornaments futûrs" #. 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 "Inviâ i rapuarts dal firmware al jude i vendidôrs di hardware a identificâ subite i inzornaments bogns e falimentârs sui dispositîfs reâi." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Non utent" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Daûr a verificâ…" #. 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.3.9/po/he.po000066400000000000000000000072201362775233600142420ustar00rootroot00000000000000# 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: 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: remote checksum msgid "Checksum" msgstr "סכום ביקורת" #. TRANSLATORS: error message msgid "Command not found" msgstr "פקודה לא נמצאה" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "אפשרויות ניפוי שגיאות" #. TRANSLATORS: multiline description of device 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 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.3.9/po/hi.po000066400000000000000000000075051362775233600142540ustar00rootroot00000000000000# 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 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.3.9/po/hr.po000066400000000000000000001111221362775233600142540ustar00rootroot00000000000000# 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: 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` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s nadopuna Thunderbolt upravljača" #. 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: 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: 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 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 to firmware mode" msgstr "Prebaci u način frimvera" #. 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: firmware version of bootloader msgid "Bootloader Version" msgstr "Inačica učitača pokretanja" #. 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: remote checksum msgid "Checksum" msgstr "Kontrolni zbroj" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Odaberite uređaj:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Odaberi izdanje:" #. 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: version number of current firmware msgid "Current version" msgstr "Trenutna inačica" #. 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: multiline description of device msgid "Description" msgstr "Opis" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Prebaci u način učitača pokretanja" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Pojedinosti" #. 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: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Ne uključuj prefiks vremena" #. 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: length of time the update takes to apply msgid "Duration" msgstr "Trajanje" #. 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 "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: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Neuspjela obrada oznake za --filter" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Neuspjelo ponovno pokretanje" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Neuspjelo postavljanje splash načina" #. 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: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filter sa skupom oznaka uređaja koji koristi ~ prefiks za izuzimanje, npr. 'internal,~needs-reboot'" #. 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: release properties msgid "Flags" msgstr "Oznake" msgid "Force the action ignoring all warnings" msgstr "Prisili radnju zanemarivanja svih upozorenja" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Prikaži sve oznake uređaja koje podržava fwupd" #. 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: 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: daemon is inactive msgid "Idle…" msgstr "Mirovanje..." #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Zanemari SSL ograničene provjere pri preuzimanju datoteka" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Trajanje instalacije" #. 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" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licenca" 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 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: remote URI msgid "Metadata Signature" msgstr "Metapodaci potpisa" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "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: smallest version number installable on device msgid "Minimum Version" msgstr "Najmanja inačica" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Pozadinski program i klijent se ne podudaraju, umjesto koristite %s" #. 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" msgid "No action specified!" msgstr "Nema zadane radnje!" #. 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: no repositories to download from msgid "No releases available" msgstr "Nema dostupnih izdanja" #. 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: no repositories to download from msgid "No remotes available" msgstr "Nema dostupnih udaljenih lokacija" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nema primijenjenih nadopuna" #. 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" #. 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: command line option msgid "Query for firmware update support" msgstr "Zatraži podršku nadopune frimvera" #. 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: 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: the server the file is coming from #. 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: 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" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Zakazivanje..." #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Serijski broj" #. 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 vendor ID on firmware file" msgstr "Postavi ID proizvođača u datoteku firmvera" 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" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Veličina" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Izvor" 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: one line summary of device 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 type, e.g. remote or local msgid "Type" msgstr "Vrsta" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI firmver pomagalo" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update msgid "Unknown" msgstr "Nepoznat" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Nepoznat uređaj" 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: error message from last update attempt msgid "Update Error" msgstr "Greška nadopune" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Poruka nadopune" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Stanje 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 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: manufacturer of hardware msgid "Vendor" msgstr "Proizvođač" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Provjeravanje..." #. TRANSLATORS: try to help msgid "WARNING: Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "UPOZORENJE: Zanemarivanje SSL ograničenja provjera, kako bi se ovo obavljalo ubuduće automatski postavite DISABLE_SSL_STRICT u vašem radnom okruženju" #. 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.3.9/po/hu.po000066400000000000000000001402131362775233600142620ustar00rootroot00000000000000# 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: 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 fogyasztói ME frissíté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 "%s vezérlőfrissíté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 vállalati ME 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: 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` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt vezérlőfrissí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: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "Lehet, hogy a(z) %s és az összes kapcsolódó eszköz használhatatlan lesz a frissítés alatt." #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "A károsodás elkerülése miatt szükséges, hogy a(z) %s kapcsolódva maradjon a frissítés során." #. TRANSLATORS: warn the user before updating, %1 is a machine name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "A károsodás elkerülése miatt szükséges, hogy a(z) %s csatlakoztatva maradjon egy áramforráshoz a frissítés során." #. 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: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u helyi eszköz támogatott" msgstr[1] "%u helyi eszköz támogatott" #. 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 reinstalling existing firmware versions" msgstr "A 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 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 to firmware mode" msgstr "Csatlakoztatás a firmware módhoz" #. 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 modify daemon configuration" msgstr "Hitelesítés szükséges a démon konfigurációjának 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: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatikus jelentés" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Rendszerbetöltő verziója" #. 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: command description msgid "Checks cryptographic hash matches firmware" msgstr "Ellenőrzi, hogy a kriptográfiai hash egyezik-e a firmware-rel" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Ellenőrzőösszeg" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Válasszon eszközt:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Válasszon firmware típust:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Válasszon kiadást:" #. 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: prompt to apply the update msgid "Continue with update?" msgstr "Folytatja a frissítést?" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Firmware átalakítása DFU formátumra" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Kriptográfiai hash ellenőrzés érhető el" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Jelenlegi verzió" #. 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: multiline description of device msgid "Description" msgstr "Leírás" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Leválasztás a rendszerbetöltő módhoz" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Részletek" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Eszközjelzők" #. 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Az eszköz képes helyreállni a felülírási hibák után" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Eszköz módosítva:" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Az eszköz zárolt" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Az eszköz használható marad a frissítés ideje alatt" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Eszköz eltávolítva:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Az eszköz szakaszosan frissít" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Az eszközfrissítés aktiválást igényel" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Az eszköz a frissítés végeztével újra meg fog jelenni" #. 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: turn on all debugging msgid "Do not include log domain prefix" msgstr "Ne tartalmazza a naplózási tartomány előtagot" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Ne tartalmazza az időbélyeg előtagot" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Ne végezzen eszköz biztonsági ellenőrzéseket" msgid "Do not upload report at this time, but prompt again for future updates" msgid_plural "Do not upload reports at this time, but prompt again for future updates" msgstr[0] "Most ne töltsön fel jelentést, de kérdezzen rá a jövőben" msgstr[1] "Most ne töltsön fel jelentéseket, de kérdezzen rá a jövőben" msgid "Do not upload report, and never ask to upload reports for future updates" msgid_plural "Do not upload reports, and never ask to upload reports for future updates" msgstr[0] "Ne töltsön fel jelentést, és sose kérdezzen rá a jövőben" msgstr[1] "Ne töltsön fel jelentéseket, és sose kérdezzen rá a jövőben" #. 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: length of time the update takes to apply msgid "Duration" msgstr "Hossz" #. 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 "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: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "A --filter jelzőinek feldolgozása sikertelen" #. 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: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Szűrés eszközjelzők megadásával, ~ előtag a kihagyáshoz, például „internal,~needs-reboot”" #. 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: release properties 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: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID-ok" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Az fwupd által támogatott összes eszközjelző lekérése" #. 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: 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: The hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "A hardver újracsatlakoztatásra vár" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Üresjárat…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "A szogorú SSL ellenőrzések mellőzése a fájlok letöltésekor" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Biztonsági ellenőrzések mellőzése" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Telepítés hossza" #. 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" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Elsőként telepítés a szülő eszközre" 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…" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Belső eszköz" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Rendszerbetöltő módban van" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Probléma" msgstr[1] "Problémák" msgid "Keyring" msgstr "Kulcstartó" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Utoljára módosítva" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Kevesebb mint egy perc van hátra" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licenc" 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 line option msgid "List supported firmware updates" msgstr "A támogatott firmware-frissítések listázása" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Az elérhető firmware típusok 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: remote URI msgid "Metadata Signature" msgstr "Metaadatok aláírása" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metaadat URI" #. 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." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Legkisebb verzió" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Eltérő démon és kliens, inkább ezt használja: %s" #. TRANSLATORS: sets something in daemon.conf msgid "Modifies a daemon configuration value." msgstr "Módosítja a démon egy konfigurációs értékét." #. 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" msgid "Modify daemon configuration" msgstr "Démon konfigurációjának módosítása" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "A démon eseményeinek figyelése" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "A telepítés után újraindítást igényel" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "A telepítés után kikapcsolást igényel" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Új verzi" msgid "No action specified!" msgstr "Nincs művelet megadva!" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nem találhatók firmware azonosítók" #. 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: no repositories to download from msgid "No releases available" msgstr "Nincsenek elérhető kiadások" #. 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: no repositories to download from msgid "No remotes available" msgstr "Nincsenek elérhető távoli tárolók" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nem lett frissítés alkalmazva" #. 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: command line option msgid "Override warnings and force the action" msgstr "Figyelmeztetések felülbírálása, és a művelet kényszerítése" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "A firmware fájl részleteinek értelmezése és megjelenítése" #. 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" #. 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:" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Előző verzió" 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: a non-free software license msgid "Proprietary" msgstr "Tulajdonosi" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Firmware frissítési támogatás lekérdezése" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Egy firmware blob beolvasása egy eszközről" #. 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: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Olvasás a(z) %s eszközről…" #. 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: command description msgid "Reinstall current firmware on the device." msgstr "Újratelepíti a jelenlegi firmware-t az eszközön." #. 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: the server the file is coming from #. 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: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Jelentve a távoli kiszolgálónak" #. TRANSLATORS: Must be plugged in to an outlet msgid "Requires AC power" msgstr "Hálózati áramforrás szükséges" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Rendszerbetöltő szükséges" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Internetkapcsolat szükséges" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Újraindítja most?" #. TRANSLATORS: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Újraindítja a démont a változás életbe léptetéséhez?" #. 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" #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "A bővítmény összetett tisztítási rutinjának futtatása az install-blob használatakor" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "A bővítmény összetett előkészítési rutinjának futtatása az install-blob használatakor" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Ütemezés…" #. TRANSLATORS: Device has been chosen by the daemon for the user msgid "Selected device" msgstr "Kiválasztott eszköz" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Sorozatszám" #. 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 vendor ID on firmware file" msgstr "Gyártóazonosító beállítása a 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: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "A démon részletes információinak megjelenítése egy adott tartományhoz" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Hibakeresési információk megjelenítése az összes tartományhoz" #. 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" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Méret" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Forrá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: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Az összes eszköz sikeresen aktiválva" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Távoli tároló sikeresen letiltva" #. TRANSLATORS: success message where we made the firmware on the #. * device older than it was before msgid "Successfully downgraded device" msgstr "Eszköz sikeresen visszaállítva" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Új metaadatok sikeresen letöltve:" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Távoli tároló sikeresen engedélyezve" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware sikeresen telepítve" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Konfigurációs érték sikeresen módosítva" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Távoli tároló sikeresen módosítva" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metaadatok kézi frissítése sikeres" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Eszköz ellenőrzőösszegek sikeresen frissítve" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "%u jelentés sikeresen feltöltve" msgstr[1] "%u jelentés sikeresen feltöltve" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Eszköz ellenőrzőösszegek sikeresen ellenőrizve" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Összegzés" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Távoli kiszolgálón nem támogatott " 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 type, e.g. remote or local msgid "Type" msgstr "Típus" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI firmware segédprogram" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update msgid "Unknown" msgstr "Ismeretlen" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Ismeretlen eszköz" 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: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Nem támogatott démonverzió: %s, a kliensverzió %s" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Frissíthet" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Frissítési hiba" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Frissítési üzenet" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Frissítés állapota" #. 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?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "A frissítés újraindítást igényel" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "A tárolt kriptográfiai hash frissítése a jelenlegi ROM tartalmával" 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 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: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade available for %s from %s to %s" msgstr "%s frissítés érhető el, erről: %s, erre: %s" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Feltöltési üzenet:" msgid "Upload report just this one time, but prompt again for future updates" msgid_plural "Upload reports just this one time, but prompt again for future updates" msgstr[0] "Most töltse fel a jelentést, de kérdezzen rá a jövőben" msgstr[1] "Most töltse fel a jelentéseket, de kérdezzen rá a jövőben" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Feltölti most a jelentést?" msgid "Upload report this time and automatically upload reports after completing future updates" msgid_plural "Upload reports this time and automatically upload reports after completing future updates" msgstr[0] "Most töltse fel a jelentést, és automatikusan tegye meg ezt a jövőben" msgstr[1] "Most töltse fel a jelentéseket, és automatikusan tegye meg ezt a jövőben" #. 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: command line option msgid "Use quirk flags when installing firmware" msgstr "Kerülőmegoldás jelzők használata a firmware telepítésekor" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "A felhasználó értesítve" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Felhasználónév" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Változat" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Gyártó" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Ellenőrzés…" #. TRANSLATORS: try to help msgid "WARNING: Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "FIGYELMEZTETÉS: A szigorú SSL ellenőrzések mellőzése, ahhoz hogy ezt automatikusan megtegye a jövőben, exportálja a DISABLE_SSL_STRICT változót a környezetében" #. 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.3.9/po/id.po000066400000000000000000000434251362775233600142510ustar00rootroot00000000000000# 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-2019 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: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Mengaktifkan 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: 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: 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: remote checksum msgid "Checksum" msgstr "Checksum" #. 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: 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: 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: multiline description of device msgid "Description" msgstr "Deskripsi" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Rincian" #. 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 "Display version" msgstr "Tampilkan versi" #. 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: length of time the update takes to apply msgid "Duration" msgstr "Durasi" #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Difungsikan" #. 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: 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: firmware approved by the admin msgid "Gets the list of approved firmware." msgstr "Dapatkan daftar firmware yang disetujui." #. 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: 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..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Memasang pada %s…" msgid "Keyring" msgstr "Keyring" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Lisensi" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Memuat..." #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Tanda Tangan Metadata" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI Metadata" #. TRANSLATORS: sets something in daemon.conf msgid "Modifies a daemon configuration value." msgstr "Mengubah suatu nilai konfigurasi daemon." #. 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: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Tidak terdeteksi perangkat keras dengan kapabilitas pemutakhiran firmware" #. 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 "Print the version number" msgstr "Cetak nomor versi" msgid "Print verbose debug statements" msgstr "Cetak pernyataan awakutu rinci" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritas" msgid "Proceed with upload?" msgstr "Lanjutkan mengunggah?" #. 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: 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: the server the file is coming from #. 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: 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" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Jadwalkan instalasi untuk boot ulang selanjutnya bila mungkin" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Menjadwalkan..." #. 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 vendor ID on firmware file" msgstr "Atur ID vendor pada berkas firmware" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware." msgstr "Tata daftar firmware yang disetujui." #. 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 devices that are not updatable" msgstr "Tampilkan peranti yang tidak dapat dimutakhirkan" #. 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: file size of the download msgid "Size" msgstr "Ukuran" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Sumber" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Ringkasan" msgid "Target" msgstr "Target" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipe" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update 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: 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 "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: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Memutakhirkan %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: 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.3.9/po/it.po000066400000000000000000001411041362775233600142620ustar00rootroot00000000000000# 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-2020 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: 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` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Aggiornamento unità di controllo Thunderbolt %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: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s e tutti i dispositivi collegati potrebbero non essere utilizzabili durante l'aggiornamento." #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s deve rimanere collegato durante l'aggiornamento per evitare possibili danni." #. TRANSLATORS: warn the user before updating, %1 is a machine name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s deve rimanere collegato alla rete elettrica durante l'aggiornamento per evitare possibili danni." #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u giorno" msgstr[1] "%u giorni" #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u dispositivo ha un aggiornamento firmware disponibile." msgstr[1] "%u dispositivi hanno un aggiornamento firmware disponibile." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u ora" msgstr[1] "%u ore" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u dispositivo locale supportato" msgstr[1] "%u dispositivi locali supportati" #. 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 reinstalling existing firmware versions" msgstr "Consente la re-installazione di versioni esistenti del firmware" #. 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 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 to firmware mode" msgstr "Collega in modalità firmware" #. 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: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Rapporti automatici" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Versione bootloader" #. 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: command description msgid "Checks cryptographic hash matches firmware" msgstr "Verifica che l'hash crittografico corrisponda col firmware" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Codice di controllo" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Scegliere un dispositivo:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Scegliere un tipo di firmware:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Scegliere un rilascio:" #. 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: prompt to apply the update msgid "Continue with update?" msgstr "Continuare con l'aggiornamento?" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "Converte il firmware nel formato DFU" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "È disponibile la verifica dell'hash crittografico" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Versione attuale" #. 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: multiline description of device msgid "Description" msgstr "Descrizione" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Scollega in modalità bootloader" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Dettagli" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Flag dispositivo" #. 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Il dispositivo è in grado di correggere problemi di flash" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Dispositivo modificato:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "È richiesto il firmware dispositivo per eseguire un controllo della versione" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Il dispositivo è bloccato" #. TRANSLATORS: a version check is required for all firmware msgid "Device is required to install all provided releases" msgstr "È richiesto un dispositivo per installare tutti i rilasci forniti" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Il dispositivo è utilizzabile per la durata dell'aggiornamento" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositivo rimosso:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Il dispositivo applica gli aggiornamenti a fasi" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "L'aggiornamento del dispositivo richiede l'attivazione" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Il dispositivo non comparirà nuovamente dopo l'aggiornamento" #. 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: turn on all debugging msgid "Do not include log domain prefix" msgstr "Non include il prefisso del dominio" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Non include il prefisso della marcatura temporale" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Non esegue i controlli di sicurezza del dispositivo" msgid "Do not upload report at this time, but prompt again for future updates" msgid_plural "Do not upload reports at this time, but prompt again for future updates" msgstr[0] "Non caricare il rapporto ora, ma chiedere nuovamente con i prossimi aggiornamenti" msgstr[1] "Non caricare i rapporti ora, ma chiedere nuovamente con i prossimi aggiornamenti" msgid "Do not upload report, and never ask to upload reports for future updates" msgid_plural "Do not upload reports, and never ask to upload reports for future updates" msgstr[0] "Non caricare il rapporto e non chiedere nuovamente con i prossimi aggiornamenti" msgstr[1] "Non caricare i rapporti e non chiedere nuovamente con i prossimi aggiornamenti" #. 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 "Torna a una vecchia versione del firmware 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: length of time the update takes to apply msgid "Duration" msgstr "Durata" #. 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 "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: failed to read measurements file msgid "Failed to parse file" msgstr "Lettura del file non riuscita" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Analisi del flag --filter 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: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtra tramite un insieme di flag utilizzando ~ per escludere, per esempio «internal, ~needs-reboot»" #. 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: release properties msgid "Flags" msgstr "Flag" msgid "Force the action ignoring all warnings" msgstr "Forza l'azione ignorando gli avvisi" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Ottiene tutti i flag dispositivo supportati da fwupd" #. 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: 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: The hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "L'hardware è in attesa di essere ricollegato" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Inattivo…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignora controlli SSL nello scaricare i file" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignora i controlli di sicurezza di validazione" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Tempo di installazione" #. 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" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Installare sul dispositivo genitore prima" 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…" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Dispositivo interno" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "È in modalità bootloader" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problema" msgstr[1] "Problemi" msgid "Keyring" msgstr "Portachiavi" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Ultima modifica" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Manca meno di un minuto" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licenza" 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 line option msgid "List supported firmware updates" msgstr "Elenca gli aggiornamenti firmware supportati" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Elenca tutti i tipi di firmware disponibili" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Caricamento…" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "Abilita manualmente plugin specifici" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Firma metadati" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI 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: smallest version number installable on device msgid "Minimum Version" msgstr "Versione minima" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Versioni di demone e client non corrispondenti, usare %s" #. 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: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Necessita un riavvio dopo l'installazione" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Necessita l'arresto dopo l'installazione" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Nuova versione" msgid "No action specified!" msgstr "Nessuna azione specificata." #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Nessuna versione precedente per %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nessun ID firmware trovato" #. 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: no repositories to download from msgid "No releases available" msgstr "Nessuna versione disponibile" #. 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: no repositories to download from msgid "No remotes available" msgstr "Nessun server disponibile" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Non è stato applicato alcun aggiornamento" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Mostra solo il valore PCR" #. 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: command description msgid "Parse and show details about a firmware file" msgstr "Legge e mostra i dettagli riguardo a un file firmware" #. 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" #. 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:" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Versione precedente" 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: a non-free software license msgid "Proprietary" msgstr "Proprietaria" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Interroga il supporto per gli aggiornamenti firmware" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Legge un blob firmware da un dispositivo" #. 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: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Lettura da %s…" #. 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: command description msgid "Reinstall current firmware on the device." msgstr "Installa nuovamente il firmware attuale sul dispositivo" #. 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: the server the file is coming from #. 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: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Segnalato al server remoto" #. TRANSLATORS: Must be plugged in to an outlet msgid "Requires AC power" msgstr "Richiede corrente elettrica" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Richiede un bootloader" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Richiede una connessione a Internet" #. 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" msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Per maggiori informazioni eseguire «fwupdmgr get-upgrades»." #. 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" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Pianificazione…" #. TRANSLATORS: Device has been chosen by the daemon for the user msgid "Selected device" msgstr "Dispositivo selezionato" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Numero di serie" #. 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 vendor ID on firmware file" msgstr "Imposta l'identificativo del produttore sul file del 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" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Dimensione" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Sorgente" 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: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Attivazione di tutti i dispositivi avvenuta con successo" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Remoto disabilitato con successo" #. TRANSLATORS: success message where we made the firmware on the #. * device older than it was before msgid "Successfully downgraded device" msgstr "Firmware dispositivo impostato a una vecchia versione" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Nuovi metadati scaricati con successo:" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Remoto abilitato con successo" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware installato con successo" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Valore della configurazione modificato con successo" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Remoto modificato con successo" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadati aggiornati manualmente con successo" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Codici di controllo del dispositivo aggiornati con successo" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "%u rapporto caricato con successo" msgstr[1] "%u rapporti caricati con successo" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Codici di controllo del dispositivo verificati con successo" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Riepilogo" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Supportato sul server remoto" 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 type, e.g. remote or local msgid "Type" msgstr "Tipo" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Strumento firmware UEFI" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update msgid "Unknown" msgstr "Sconosciuto" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Dispositivo 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: Device is updatable in this or any other mode msgid "Updatable" msgstr "Aggiornabile" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Errore aggiornamento" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Messaggio aggiornamento" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Stato 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?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "L'aggiornamento richiede il riavvio" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Aggiorna l'hash crittografico archiviato con il contenuto della ROM" 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 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: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade available for %s from %s to %s" msgstr "Aggiornamento disponibile per %s da %s a %s" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "Messaggio di caricamento:" msgid "Upload report just this one time, but prompt again for future updates" msgid_plural "Upload reports just this one time, but prompt again for future updates" msgstr[0] "Caricare il rapporto ora e chiedere nuovamente con i prossimi aggiornamenti" msgstr[1] "Caricare i rapporti ora e chiedere nuovamente con i prossimi aggiornamenti" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Caricare il rapporto ora?" msgid "Upload report this time and automatically upload reports after completing future updates" msgid_plural "Upload reports this time and automatically upload reports after completing future updates" msgstr[0] "Caricare il rapporto ora e caricare automaticamente con i prossimi aggiornamenti" msgstr[1] "Caricare i rapporti ora e caricare automaticamente con i prossimi aggiornamenti" #. 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: command line option msgid "Use quirk flags when installing firmware" msgstr "Usa flag quirk nell'installare il firmware" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Notifica inviata all'utente" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nome utente" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variante" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Fornitore" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifica…" #. TRANSLATORS: try to help msgid "WARNING: Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Attenzione: controlli SSL ignorati, per fare ciò automaticamente in futuro, esportare DISABLE_SSL_STRICT nel proprio ambiente" #. 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.3.9/po/its/000077500000000000000000000000001362775233600141045ustar00rootroot00000000000000fwupd-1.3.9/po/its/appdata.its000066400000000000000000000013501362775233600162360ustar00rootroot00000000000000 fwupd-1.3.9/po/its/appdata.loc000066400000000000000000000005121362775233600162130ustar00rootroot00000000000000 fwupd-1.3.9/po/kk.po000066400000000000000000000024751362775233600142620ustar00rootroot00000000000000# 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.3.9/po/ko.po000066400000000000000000001405271362775233600142670ustar00rootroot00000000000000# 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-2020 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: 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` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt 컨트롤러 업데이트" #. 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: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "업데이트 중에는 %s 및 연결된 모든 장치를 사용할 수 없을 수도 있습니다." #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "장치 손상을 방지하려면 업데이트 중 %s의 연결을 계속 유지해야 합니다." #. TRANSLATORS: warn the user before updating, %1 is a machine name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "장치 손상을 방지하려면 업데이트 중 %s을(를) 전원에 계속 연결해 두어야 합니다." #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u일" #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "장치 %u개의 펌웨어를 업데이트할 수 있습니다." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u시간" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" 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 reinstalling 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 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 to firmware mode" 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: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "자동 보고" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" 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: command description msgid "Checks cryptographic hash matches firmware" msgstr "암호화 해시가 펌웨어와 일치하는지 확인합니다" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "체크섬" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "장치를 선택하십시오:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "펌웨어 종류를 선택하십시오:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" 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: prompt to apply the update msgid "Continue with update?" msgstr "업데이트를 계속 진행하시겠습니까?" #. TRANSLATORS: command description msgid "Convert firmware to DFU format" msgstr "펌웨어를 DFU 형식으로 변환" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "암호화 해시 검사 사용 가능" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "현재 버전" #. 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: multiline description of device msgid "Description" msgstr "설명" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "부트로더 모드로 전환" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "자세한 정보" #. TRANSLATORS: description of device ability msgid "Device Flags" 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "장치 업데이트 실패 시 복구 가능" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "장치 상태 바뀜:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "장치 펌웨어에서 버전 확인을 지원해야 함" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "장치가 잠김" #. TRANSLATORS: a version check is required for all firmware msgid "Device is required to install all provided releases" msgstr "장치에 제공된 모든 릴리스를 설치해야 함" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "업데이트 중 장치를 사용할 수 있음" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "장치 제거됨:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "안전한 업데이트 지원" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "장치 업데이트 활성화 필요" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" 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: turn on all debugging msgid "Do not include log domain prefix" msgstr "로그 도메인 접두사를 포함하지 않기" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "timestamp 접두사를 포함하지 않기" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "장치 안정성 검사를 실행하지 않습니다" msgid "Do not upload report at this time, but prompt again for future updates" msgid_plural "Do not upload reports at this time, but prompt again for future updates" msgstr[0] "지금 보고서를 업로드하지 않고 다음에 업데이트할 때 묻기" msgid "Do not upload report, and never ask to upload reports for future updates" msgid_plural "Do not upload reports, and never ask to upload reports for future updates" msgstr[0] "보고서를 업로드하지 않고 다음에 업데이트할 때에도 다시 묻지 않기" #. 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: length of time the update takes to apply msgid "Duration" 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 "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: failed to read measurements file msgid "Failed to parse file" msgstr "파일을 해석할 수 없음" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "--filter에 전달한 플래그 해석에 실패함" #. 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: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "플래그로 장치를 필터하며, ~ 기호를 앞에 붙이면 제외할 수 있습니다. 예: 'internal,~needs-reboot'" #. 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: release properties msgid "Flags" msgstr "플래그" msgid "Force the action ignoring all warnings" msgstr "모든 경고를 무시하고 작업 강제 진행" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "fwupd를 지원하는 모든 장치 정보를 가져옵니다" #. 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: 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: The hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "하드웨어를 다시 연결해야 함" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "대기 중…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "파일을 다운로드할 때 SSL 엄격한 검사를 건너뜁니다" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "장치 안정성 검사 무시" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" 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 "서명된 시스템 펌웨어 설치" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" 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에 설치 중..." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "내장 장치" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "현재 부트로더 모드에 있음" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "문제점" msgid "Keyring" msgstr "키 모음" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "마지막으로 수정한 날짜" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "1분 미만 남음" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "라이선스" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "리눅스 제조사 펌웨어 서비스(안정 버전 펌웨어)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "리눅스 제조사 펌웨어 서비스(테스트 버전 펌웨어)" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "지원하는 펌웨어 업데이트 목록 표시" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "사용 가능한 펌웨어 종류를 표시합니다" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "불러오는 중…" #. TRANSLATORS: command line option msgid "Manually whitelist specific plugins" msgstr "지정한 플러그인을 수동으로 허용 목록에 추가" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "메타데이터 서명" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "메타데이터 URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "리눅스 제조사 펌웨어 서비스에서 메타데이터를 가져올 수 있습니다." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "최소 버전" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "데몬과 클라이언트가 일치하지 않음, %s을(를) 대신 사용함" #. 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: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "설치 후 다시 시작 필요함" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "설치 후 종료 필요함" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "새 버전" msgid "No action specified!" msgstr "동작을 지정하지 않았습니다!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "%s의 다운그레이드 없음" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "펌웨어 ID를 찾을 수 없음" #. 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: no repositories to download from msgid "No releases available" msgstr "사용 가능한 릴리스 없음" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "원격 자원이 설정되지 않았으므로 메타데이터를 사용할 수 없습니다." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "원격 저장소 없음" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "적용된 업데이트 없음" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "단일 PCR 값만 표시" #. 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: command description msgid "Parse and show details about a firmware file" msgstr "펌웨어 파일을 처리하고 세부 정보를 표시합니다" #. TRANSLATORS: remote filename base msgid "Password" msgstr "암호" msgid "Payload" msgstr "페이로드" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "진행률" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "0에서 %u까지의 숫자 중 하나를 입력하십시오:" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "이전 버전" 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: a non-free software license msgid "Proprietary" msgstr "독점적" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "펌웨어 업데이트 지원 조회" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" 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: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "%s에서 읽는 중..." #. 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: command description msgid "Reinstall current firmware on the device." 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: the server the file is coming from #. 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: Has been reported to a metadata server msgid "Reported to remote server" msgstr "원격 서버에 보고됨" #. TRANSLATORS: Must be plugged in to an outlet msgid "Requires AC power" msgstr "AC 전원 필요함" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "부트로더 모드 진입 필요함" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "인터넷 연결 필요" #. 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를 반환합니다" msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "더 많은 정보를 보려면 `fwupdmgr get-upgrades` 명령을 실행하십시오." #. 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 사용 시 플러그인 준비 루틴 실행" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "작업 계획 중…" #. TRANSLATORS: Device has been chosen by the daemon for the user msgid "Selected device" msgstr "선택한 장치" #. TRANSLATORS: serial number of hardware msgid "Serial Number" 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 vendor ID on firmware file" msgstr "펌웨어 파일의 제조사 ID 설정" 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 "서명" #. TRANSLATORS: file size of the download msgid "Size" msgstr "크기" #. TRANSLATORS: source (as in code) link msgid "Source" 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: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "모든 장치를 활성화함" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "원격 저장소를 비활성화함" #. TRANSLATORS: success message where we made the firmware on the #. * device older than it was before msgid "Successfully downgraded device" msgstr "장치를 다운그레이드함" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "새 메타데이터를 다운로드함:" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "원격 저장소를 활성화함" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "펌웨어 설치 성공" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "설정을 수정함" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "원격 저장소를 수정함" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "메타데이터를 수동으로 업데이트함" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "장치 체크섬을 업데이트함" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "보고서 %u개 업로드함" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "장치 체크섬을 검증함" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "요약" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" 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 type, e.g. remote or local msgid "Type" msgstr "형식" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI 펌웨어 유틸리티" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update msgid "Unknown" msgstr "알 수 없음" #. TRANSLATORS: Name of hardware msgid "Unknown Device" 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: Device is updatable in this or any other mode msgid "Updatable" msgstr "업데이트 가능" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "업데이트 오류" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "업데이트 메시지" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" 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 "지금 업데이트하시겠습니까?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "업데이트 시 다시 시작 필요함" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "현재 ROM 내용으로 저장된 암호화 해시를 업데이트합니다" msgid "Update the stored device verification information" msgstr "저장된 장치 검증 정보 업데이트" #. 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: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade available for %s from %s to %s" msgstr "%s 업그레이드 사용 가능: %s에서 %s(으)로" #. TRANSLATORS: the server sent the user a small message msgid "Upload message:" msgstr "업로드 메시지:" msgid "Upload report just this one time, but prompt again for future updates" msgid_plural "Upload reports just this one time, but prompt again for future updates" msgstr[0] "이번 한 번만 보고서를 업로드하고 다음에 업데이트할 때 묻기" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "지금 보고서를 업로드하시겠습니까?" msgid "Upload report this time and automatically upload reports after completing future updates" msgid_plural "Upload reports this time and automatically upload reports after completing future updates" msgstr[0] "이번 한 번 보고서를 업로드하고 다음에 업데이트할 때 보고서를 자동으로 업로드하기" #. 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: command line option msgid "Use quirk flags when installing firmware" msgstr "펌웨어를 설치할 때 quirk 플래그 사용" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "사용자에게 알림이 표시됨" #. TRANSLATORS: remote filename base msgid "Username" msgstr "사용자 이름" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "파생형" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "제조사" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "검증 중…" #. TRANSLATORS: try to help msgid "WARNING: Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "경고: SSL 엄격 검사를 건너뜁니다. 이 옵션을 자동으로 지정하려면 DISABLE_SSL_STRICT 환경 변수를 지정하십시오" #. 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.3.9/po/ky.po000066400000000000000000000032341362775233600142720ustar00rootroot00000000000000# 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.3.9/po/lt.po000066400000000000000000000753101362775233600142720ustar00rootroot00000000000000# 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: 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: 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 line option msgid "Apply firmware updates" msgstr "Taikyti programinės aparatinės įrangos atnaujinimus" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Pridėti į programinės aparatinės įrangos veikseną" #. 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: remote checksum msgid "Checksum" msgstr "Kontrolinė suma" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Pasirinkite įrenginį:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Pasirinkite laidą:" #. 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: 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: multiline description of device msgid "Description" msgstr "Aprašas" #. 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: 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 "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: release properties msgid "Flags" msgstr "Vėliavėlės" msgid "Force the action ignoring all warnings" msgstr "Priverstinai atlikti veiksmą nepaisant visų įspėjimų" #. 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: 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: 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 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: remote URI msgid "Metadata URI" msgstr "Metaduomenų URI" #. 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." #. 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ų" msgid "No action specified!" msgstr "Nenurodytas joks veiksmas!" #. 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" #. 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" #. 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: command line option msgid "Query for firmware update support" msgstr "Užklausti programinės aparatinės įrangos atnaujinimų palaikymo" #. 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: 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: the server the file is coming from #. 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: 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" #. 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: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Suplanuojama…" #. 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 vendor ID on firmware file" msgstr "Nustatyti programinės aparatinės įrangos faile tiekėjo ID" 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: one line summary of device 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 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: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update 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: 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 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: 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.3.9/po/make-images000077500000000000000000000144671362775233600154270ustar00rootroot00000000000000#!/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 * _fwupd_remote_get_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 * _fwupd_remote_build_component_id (FwupdRemote *remote) { return g_strdup_printf ("org.freedesktop.fwupd.remotes.%s", fwupd_remote_get_id (remote)); } static gboolean fu_remote_list_add_for_path (FuRemoteList *self, const gchar *path, GError **error) { const gchar *tmp; g_autofree gchar *path_remotes = NULL; g_autoptr(GDir) dir = NULL; g_autoptr(GHashTable) os_release = 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_remote_list_add_inotify (self, path_remotes, error)) return FALSE; dir = g_dir_open (path_remotes, 0, error); if (dir == NULL) return FALSE; os_release = fwupd_get_os_release (error); if (os_release == 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 (); g_autofree gchar *localstatedir = NULL; g_autofree gchar *remotesdir = NULL; /* skip invalid files */ if (!g_str_has_suffix (tmp, ".conf")) { g_debug ("skipping invalid file %s", filename); continue; } /* set directory to store data */ localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG); remotesdir = g_build_filename (localstatedir, "remotes.d", NULL); fwupd_remote_set_remotes_dir (remote, remotesdir); /* load from keyfile */ g_debug ("loading remotes 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 remote_list file and the XML file itself */ if (!fu_remote_list_add_inotify (self, filename, error)) return FALSE; if (!fu_remote_list_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 = _fwupd_remote_build_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 = _fwupd_remote_get_agreement_for_app (remote, component, error); } else { agreement_markup = _fwupd_remote_get_agreement_default (remote, error); } if (agreement_markup == NULL) return FALSE; /* replace any dynamic values from os-release */ tmp = g_hash_table_lookup (os_release, "NAME"); if (tmp == NULL) tmp = "this distribution"; fu_common_string_replace (agreement_markup, "$OS_RELEASE:NAME$", tmp); tmp = g_hash_table_lookup (os_release, "BUG_REPORT_URL"); if (tmp == NULL) tmp = "https://github.com/fwupd/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, _fwupd_remote_get_mtime (remote)); g_ptr_array_add (self->array, g_steal_pointer (&remote)); } return TRUE; } gboolean fu_remote_list_set_key_value (FuRemoteList *self, const gchar *remote_id, const gchar *key, const gchar *value, GError **error) { FwupdRemote *remote; const gchar *filename; g_autoptr(GKeyFile) keyfile = g_key_file_new (); /* check remote is valid */ remote = fu_remote_list_get_by_id (self, remote_id); if (remote == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "remote %s not found", remote_id); return FALSE; } /* modify the remote */ 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); if (!g_key_file_save_to_file (keyfile, filename, error)) return FALSE; /* reload values */ if (!fwupd_remote_load_from_filename (remote, filename, NULL, error)) { g_prefix_error (error, "failed to load %s: ", filename); return FALSE; } fu_remote_list_emit_changed (self); return TRUE; } static gint fu_remote_list_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 guint fu_remote_list_depsolve_with_direction (FuRemoteList *self, gint inc) { guint cnt = 0; for (guint i = 0; i < self->array->len; i++) { FwupdRemote *remote = g_ptr_array_index (self->array, 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_remote_list_get_by_id (self, 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; } gboolean fu_remote_list_reload (FuRemoteList *self, GError **error) { guint depsolve_check; g_autofree gchar *remotesdir = NULL; /* clear */ g_ptr_array_set_size (self->array, 0); g_ptr_array_set_size (self->monitors, 0); /* use sysremotes, and then fall back to /etc */ remotesdir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR_PKG); if (!g_file_test (remotesdir, G_FILE_TEST_EXISTS)) { g_debug ("no remotes found"); return TRUE; } /* look for all remote_list */ if (!fu_remote_list_add_for_path (self, remotesdir, error)) return FALSE; /* depsolve */ for (depsolve_check = 0; depsolve_check < 100; depsolve_check++) { guint cnt = 0; cnt += fu_remote_list_depsolve_with_direction (self, 1); cnt += fu_remote_list_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->array, fu_remote_list_sort_cb); /* success */ return TRUE; } static gboolean fu_remote_list_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_remote_list_load (FuRemoteList *self, FuRemoteListLoadFlags flags, GError **error) { const gchar *const *locales = g_get_language_names (); 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_REMOTE_LIST (self), FALSE); g_return_val_if_fail (self->silo == NULL, FALSE); /* load AppStream about the remote_list */ if (!fu_remote_list_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]); /* on a read-only filesystem don't care about the cache GUID */ if (flags & FU_REMOTE_LIST_LOAD_FLAG_READONLY_FS) compile_flags |= XB_BUILDER_COMPILE_FLAG_IGNORE_GUID; /* 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 remote_list */ return fu_remote_list_reload (self, error); } GPtrArray * fu_remote_list_get_all (FuRemoteList *self) { g_return_val_if_fail (FU_IS_REMOTE_LIST (self), NULL); return self->array; } FwupdRemote * fu_remote_list_get_by_id (FuRemoteList *self, const gchar *remote_id) { g_return_val_if_fail (FU_IS_REMOTE_LIST (self), NULL); for (guint i = 0; i < self->array->len; i++) { FwupdRemote *remote = g_ptr_array_index (self->array, i); if (g_strcmp0 (remote_id, fwupd_remote_get_id (remote)) == 0) return remote; } return NULL; } static void fu_remote_list_class_init (FuRemoteListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_remote_list_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_remote_list_init (FuRemoteList *self) { self->array = 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_remote_list_finalize (GObject *obj) { FuRemoteList *self = FU_REMOTE_LIST (obj); if (self->silo != NULL) g_object_unref (self->silo); g_ptr_array_unref (self->array); g_ptr_array_unref (self->monitors); G_OBJECT_CLASS (fu_remote_list_parent_class)->finalize (obj); } FuRemoteList * fu_remote_list_new (void) { FuRemoteList *self; self = g_object_new (FU_TYPE_REMOTE_LIST, NULL); return FU_REMOTE_LIST (self); } fwupd-1.3.9/src/fu-remote-list.h000066400000000000000000000023751362775233600165120ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-remote.h" #define FU_TYPE_REMOTE_LIST (fu_remote_list_get_type ()) G_DECLARE_FINAL_TYPE (FuRemoteList, fu_remote_list, FU, REMOTE_LIST, GObject) /** * FuRemoteListLoadFlags: * @FU_REMOTE_LIST_LOAD_FLAG_NONE: No flags set * @FU_REMOTE_LIST_LOAD_FLAG_READONLY_FS: Ignore readonly filesystem errors * * The flags to use when loading a remote_listuration file. **/ typedef enum { FU_REMOTE_LIST_LOAD_FLAG_NONE = 0, FU_REMOTE_LIST_LOAD_FLAG_READONLY_FS = 1 << 0, /*< private >*/ FU_REMOTE_LIST_LOAD_FLAG_LAST } FuRemoteListLoadFlags; FuRemoteList *fu_remote_list_new (void); gboolean fu_remote_list_load (FuRemoteList *self, FuRemoteListLoadFlags flags, GError **error); gboolean fu_remote_list_reload (FuRemoteList *self, GError **error); gboolean fu_remote_list_set_key_value (FuRemoteList *self, const gchar *remote_id, const gchar *key, const gchar *value, GError **error); GPtrArray *fu_remote_list_get_all (FuRemoteList *self); FwupdRemote *fu_remote_list_get_by_id (FuRemoteList *self, const gchar *remote_id); fwupd-1.3.9/src/fu-self-test.c000066400000000000000000003363271362775233600161560ustar00rootroot00000000000000/* * 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-config.h" #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-engine.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-smbios-private.h" #ifdef ENABLE_GPG #include "fu-keyring-gpg.h" #endif #ifdef ENABLE_PKCS7 #include "fu-keyring-pkcs7.h" #endif typedef struct { FuPlugin *plugin; } FuTest; 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; } static 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); } static 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; } } 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 gboolean fu_test_compare_lines (const gchar *txt1, const gchar *txt2, GError **error) { g_autofree gchar *output = NULL; if (g_strcmp0 (txt1, txt2) == 0) return TRUE; if (fu_common_fnmatch (txt2, txt1)) return TRUE; 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; g_set_error_literal (error, 1, 0, output); return FALSE; } static void fu_test_free (FuTest *self) { g_object_unref (self->plugin); g_free (self); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuTest, fu_test_free) #pragma clang diagnostic pop static void fu_engine_generate_md_func (gconstpointer user_data) { 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 = g_build_filename (TESTDATADIR_DST, "colorhug", "colorhug-als-3.0.2.cab", NULL); 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_NO_ENUMERATE, &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_plugin_hash_func (gconstpointer user_data) { GError *error = NULL; g_autofree gchar *pluginfn = 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_NO_ENUMERATE, &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 */ pluginfn = g_build_filename (PLUGINBUILDDIR, "libfu_plugin_invalid." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open (plugin, pluginfn, &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_engine_requirements_missing_func (gconstpointer user_data) { 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_version_require_func (gconstpointer user_data) { 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" " " " " " " " " " " ""; /* 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_flag (device, FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED); fu_device_add_guid (device, "12345678-1234-1234-1234-123456789012"); /* 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 (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 (g_str_has_prefix (error->message, "device requires firmware with a version check")); g_assert (!ret); } static void fu_engine_requirements_unsupported_func (gconstpointer user_data) { 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 (gconstpointer user_data) { 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 (gconstpointer user_data) { 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 (gconstpointer user_data) { 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 (gconstpointer user_data) { 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" #ifndef _WIN32 " org.kernel" #endif " " " " " 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_flag (device, FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED); 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_device_plain_func (gconstpointer user_data) { 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" " " " " " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version (device, "5101AALB", FWUPD_VERSION_FORMAT_PLAIN); 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 (gconstpointer user_data) { 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 (gconstpointer user_data) { 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_vendor_id (device2, "USB:FFFF"); fu_device_set_protocol (device2, "com.acme"); 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_requirements_protocol_check_func (gconstpointer user_data) { 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(GPtrArray) devices = NULL; g_autoptr(FuInstallTask) task1 = NULL; g_autoptr(FuInstallTask) task2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); gboolean ret; const gchar *xml = "" " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " " " " org.bar" " " ""; /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); fu_device_set_id (device1, "NVME"); fu_device_set_protocol (device1, "com.acme"); fu_device_set_name (device1, "NVME device"); fu_device_set_vendor_id (device1, "ACME"); fu_device_set_version (device1, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_guid (device1, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag (device1, FWUPD_DEVICE_FLAG_UPDATABLE); fu_engine_add_device (engine, device1); fu_device_set_id (device2, "UEFI"); fu_device_set_protocol (device2, "org.bar"); fu_device_set_name (device2, "UEFI device"); fu_device_set_vendor_id (device2, "ACME"); fu_device_set_version (device2, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_guid (device2, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag (device2, FWUPD_DEVICE_FLAG_UPDATABLE); fu_engine_add_device (engine, device2); /* make sure both devices added */ devices = fu_engine_get_devices (engine, &error); g_assert_no_error (error); g_assert_nonnull (devices); g_assert_cmpint (devices->len, ==, 2); /* 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 fails */ task1 = fu_install_task_new (device1, component); ret = fu_engine_check_requirements (engine, task1, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error (&error); /* check this passes */ task2 = fu_install_task_new (device2, component); ret = fu_engine_check_requirements (engine, task2, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error (error); g_assert (ret); } static void fu_engine_requirements_parent_device_func (gconstpointer user_data) { 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 = "" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " 1ff60ab2-3905-06a1-b476-0371f00c9e9b" " " " " " " " " " " " " ""; /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* set up child device */ fu_device_set_id (device2, "child"); fu_device_set_name (device2, "child"); fu_device_set_version (device2, "4.5.6", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag (device2, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_guid (device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b"); /* set up a parent device */ fu_device_set_id (device1, "parent"); fu_device_set_vendor_id (device1, "USB:FFFF"); fu_device_set_protocol (device1, "com.acme"); fu_device_set_name (device1, "parent"); fu_device_set_version (device1, "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_guid (device1, "12345678-1234-1234-1234-123456789012"); fu_device_add_child (device1, device2); fu_engine_add_device (engine, device1); /* 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 (device2, 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 (gconstpointer user_data) { FuDevice *device; g_autoptr(FuDevice) device1 = fu_device_new (); g_autoptr(FuDevice) device2 = fu_device_new (); g_autoptr(FuDevice) device3 = fu_device_new (); g_autoptr(FuDevice) device4 = fu_device_new (); g_autoptr(FuDevice) device5 = fu_device_new (); g_autoptr(GPtrArray) devices = 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_vendor_id (device1, "USB:FFFF"); fu_device_set_protocol (device1, "com.acme"); 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_vendor_id (device2, "USB:FFFF"); fu_device_set_protocol (device2, "com.acme"); fu_device_set_priority (device2, 1); fu_device_set_plugin (device2, "redfish"); fu_device_add_instance_id (device2, "GUID1"); fu_device_set_name (device2, "123"); fu_device_convert_instance_ids (device2); fu_engine_add_device (engine, device2); fu_device_set_id (device3, "id3"); fu_device_set_vendor_id (device3, "USB:FFFF"); fu_device_set_protocol (device3, "com.acme"); 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); g_clear_error (&error); /* add extra devices that should sort */ fu_device_set_id (device4, "id4"); fu_device_set_vendor_id (device4, "USB:FFFF"); fu_device_set_protocol (device4, "com.acme"); fu_device_set_priority (device4, 0); fu_device_set_plugin (device4, "redfish"); fu_device_add_instance_id (device4, "GUID4"); fu_device_set_name (device4, "BCD"); fu_device_convert_instance_ids (device4); fu_engine_add_device (engine, device4); fu_device_set_id (device5, "id5"); fu_device_set_vendor_id (device5, "USB:FFFF"); fu_device_set_protocol (device5, "com.acme"); fu_device_set_priority (device5, 0); fu_device_set_plugin (device5, "uefi"); fu_device_add_instance_id (device5, "GUID5"); fu_device_set_name (device5, "ABC"); fu_device_convert_instance_ids (device5); fu_engine_add_device (engine, device5); /* make sure the devices are all sorted properly */ devices = fu_engine_get_devices (engine, &error); g_assert_no_error (error); g_assert_nonnull (devices); g_assert_cmpint (devices->len, ==, 3); /* first should be top priority device */ device = g_ptr_array_index (devices, 0); g_assert_cmpstr (fu_device_get_name (device), ==, "123"); device = g_ptr_array_index (devices, 1); g_assert_cmpstr (fu_device_get_name (device), ==, "ABC"); device = g_ptr_array_index (devices, 2); g_assert_cmpstr (fu_device_get_name (device), ==, "BCD"); } static void fu_engine_device_parent_func (gconstpointer user_data) { 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_set_vendor_id (device2, "USB:FFFF"); fu_device_set_protocol (device2, "com.acme"); 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_set_vendor_id (device2, "USB:FFFF"); fu_device_set_protocol (device2, "com.acme"); 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 (gconstpointer user_data) { 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_vendor_id (device1, "USB:FFFF"); fu_device_set_protocol (device1, "com.acme"); 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_vendor_id (device2, "USB:FFFF"); fu_device_set_protocol (device2, "com.acme"); 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 (gconstpointer user_data) { 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_NO_ENUMERATE, &error); g_assert_no_error (error); g_assert (ret); /* add the hardcoded 'fwupd' metadata */ filename = g_build_filename (TESTDATADIR_SRC, "metadata.xml", 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_set_vendor_id (device, "USB:FFFF"); fu_device_set_protocol (device, "com.acme"); 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 (gconstpointer user_data) { 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/fwupd/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_NO_ENUMERATE, &error); g_assert_no_error (error); g_assert (ret); /* get generated file as a blob */ filename = g_build_filename (TESTDATADIR_DST, "missing-hwid", "hwid-1.2.3.cab", 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_vendor_id (device, "USB:FFFF"); fu_device_set_protocol (device, "com.acme"); 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 (gconstpointer user_data) { FwupdRelease *rel; gboolean ret; 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); g_setenv ("CONFIGURATION_DIRECTORY", TESTDATADIR_SRC, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NO_ENUMERATE, &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_vendor_id (device, "USB:FFFF"); fu_device_set_protocol (device, "com.acme"); 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 (gconstpointer user_data) { FwupdRelease *rel; gboolean ret; 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); g_setenv ("CONFIGURATION_DIRECTORY", TESTDATADIR_SRC, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NO_ENUMERATE, &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_set_vendor_id (device, "USB:FFFF"); fu_device_set_protocol (device, "com.acme"); 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 (gconstpointer user_data) { FuTest *self = (FuTest *) user_data; 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_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(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 */ fu_engine_add_plugin (engine, self->plugin); g_setenv ("CONFIGURATION_DIRECTORY", TESTDATADIR_SRC, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NO_ENUMERATE, &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_vendor_id (device, "USB:FFFF"); fu_device_set_protocol (device, "com.acme"); 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 = g_build_filename (TESTDATADIR_DST, "missing-hwid", "noreqs-1.2.3.cab", 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 ( "FuDevice:\n" "Test Device\n" " DeviceId: 894e8c17a29428b09d10cd90d1db74ea76fbcfe8\n" " Guid: 12345678-1234-1234-1234-123456789012\n" " Plugin: test\n" " Flags: updatable|historical\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_multiple_rels_func (gconstpointer user_data) { FuTest *self = (FuTest *) user_data; 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; /* ensure empty tree */ fu_self_test_mkroot (); /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* set up dummy plugin */ fu_engine_add_plugin (engine, self->plugin); g_setenv ("CONFIGURATION_DIRECTORY", TESTDATADIR_SRC, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NO_ENUMERATE, &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_vendor_id (device, "USB:FFFF"); fu_device_set_protocol (device, "com.acme"); 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_add_flag (device, FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES); fu_device_set_created (device, 1515338000); fu_engine_add_device (engine, device); filename = g_build_filename (TESTDATADIR_DST, "multiple-rels", "multiple-rels-1.2.4.cab", 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 up counter */ 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 we did 1.2.2 -> 1.2.3 -> 1.2.4 */ g_assert_cmpint (fu_device_get_metadata_integer (device, "nr-update"), ==, 2); g_assert_cmpstr (fu_device_get_version (device), ==, "1.2.4"); } static void fu_engine_history_inherit (gconstpointer user_data) { FuTest *self = (FuTest *) user_data; 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(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); fu_engine_add_plugin (engine, self->plugin); g_setenv ("CONFIGURATION_DIRECTORY", TESTDATADIR_SRC, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NO_ENUMERATE, &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_vendor_id (device, "USB:FFFF"); fu_device_set_protocol (device, "com.acme"); 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 = g_build_filename (TESTDATADIR_DST, "missing-hwid", "noreqs-1.2.3.cab", 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, self->plugin); device = fu_device_new (); fu_device_set_id (device, "test_device"); fu_device_set_vendor_id (device, "USB:FFFF"); fu_device_set_protocol (device, "com.acme"); 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 (gconstpointer user_data) { FuTest *self = (FuTest *) user_data; 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_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(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); fu_engine_add_plugin (engine, self->plugin); g_setenv ("CONFIGURATION_DIRECTORY", TESTDATADIR_SRC, TRUE); ret = fu_engine_load (engine, FU_ENGINE_LOAD_FLAG_NO_ENUMERATE, &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_vendor_id (device, "USB:FFFF"); fu_device_set_protocol (device, "com.acme"); 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 = g_build_filename (TESTDATADIR_DST, "missing-hwid", "noreqs-1.2.3.cab", 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 ( "FuDevice:\n" "Test Device\n" " DeviceId: 894e8c17a29428b09d10cd90d1db74ea76fbcfe8\n" " Guid: 12345678-1234-1234-1234-123456789012\n" " Plugin: test\n" " Flags: updatable|historical\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 (gconstpointer user_data) { 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 (gconstpointer user_data) { 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 (gconstpointer user_data) { 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 (gconstpointer user_data) { 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 (gconstpointer user_data) { 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 (gconstpointer user_data) { 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_plugin_list_func (gconstpointer user_data) { 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 (gconstpointer user_data) { 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 (gconstpointer user_data) { 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 = g_build_filename (TESTDATADIR_SRC, "history_v1.db", NULL); 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 _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 _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_module_func (gconstpointer user_data) { FuTest *self = (FuTest *) user_data; 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(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); g_autoptr(FuHistory) history = NULL; g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GMappedFile) mapped_file = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new (); /* no metadata in daemon */ fu_engine_set_silo (engine, silo_empty); /* create a fake device */ g_setenv ("FWUPD_PLUGIN_TEST", "registration", TRUE); ret = fu_plugin_runner_startup (self->plugin, &error); g_assert_no_error (error); g_assert (ret); g_signal_connect (self->plugin, "device-added", G_CALLBACK (_plugin_device_added_cb), &device); g_signal_connect (self->plugin, "device-register", G_CALLBACK (_plugin_device_register_cb), &device); ret = fu_plugin_runner_coldplug (self->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™"); g_signal_handlers_disconnect_by_data (self->plugin, &device); #ifdef _WIN32 g_test_skip ("No offline update support on Windows"); return; #endif /* schedule an offline update */ g_signal_connect (device, "notify::status", G_CALLBACK (_plugin_status_changed_cb), &cnt); mapped_file_fn = g_build_filename (TESTDATADIR_SRC, "colorhug", "firmware.bin", NULL); 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_engine_schedule_update (engine, 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 */ fu_engine_add_device (engine, device); fu_engine_add_plugin (engine, self->plugin); ret = fu_engine_install_blob (engine, 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 (self->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 (self->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_history_func (gconstpointer user_data) { 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 | FWUPD_DEVICE_FLAG_HISTORICAL); 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, &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 (gconstpointer user_data) { #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 = g_build_filename (TESTDATADIR_SRC, "pki", NULL); ret = fu_keyring_add_public_keys (keyring, pki_dir, &error); g_assert_no_error (error); g_assert_true (ret); /* verify with GnuPG */ fw_pass = g_build_filename (TESTDATADIR_SRC, "colorhug", "firmware.bin", NULL); 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 = g_build_filename (TESTDATADIR_DST, "colorhug", "colorhug-als-3.0.2.cab", NULL); 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 (gconstpointer user_data) { #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 = g_build_filename (TESTDATADIR_SRC, "pki", NULL); 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 = g_build_filename (TESTDATADIR_SRC, "colorhug", "firmware.bin", NULL); blob_pass = fu_common_get_contents_bytes (fw_pass, &error); g_assert_no_error (error); g_assert_nonnull (blob_pass); sig_fn = g_build_filename (TESTDATADIR_SRC, "colorhug", "firmware.bin.p7b", NULL); 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 = g_build_filename (TESTDATADIR_DST, "colorhug", "firmware.bin.p7c", NULL); 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 = g_build_filename (TESTDATADIR_DST, "colorhug", "colorhug-als-3.0.2.cab", NULL); 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 (gconstpointer user_data) { #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 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 (gconstpointer user_data) { FuTest *self = (FuTest *) user_data; GError *error = NULL; gboolean ret; g_autoptr(FuEngine) engine = fu_engine_new (FU_APP_FLAGS_NONE); 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); fu_engine_add_plugin (engine, self->plugin); ret = fu_plugin_runner_startup (self->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 (self->plugin, "device-added", G_CALLBACK (_plugin_composite_device_added_cb), devices); ret = fu_plugin_runner_coldplug (self->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_memcpy_func (gconstpointer user_data) { const guint8 src[] = {'a', 'b', 'c', 'd', 'e' }; gboolean ret; guint8 dst[4]; g_autoptr(GError) error = NULL; /* copy entire buffer */ ret = fu_memcpy_safe (dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 4, &error); g_assert_no_error (error); g_assert_true (ret); g_assert (memcmp (src, dst, 4) == 0); /* copy first char */ ret = fu_memcpy_safe (dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 1, &error); g_assert_no_error (error); g_assert_true (ret); g_assert_cmpint (dst[0], ==, 'a'); /* copy last char */ ret = fu_memcpy_safe (dst, sizeof(dst), 0x0, src, sizeof(src), 0x4, 1, &error); g_assert_no_error (error); g_assert_true (ret); g_assert_cmpint (dst[0], ==, 'e'); /* copy nothing */ ret = fu_memcpy_safe (dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 0, &error); g_assert_no_error (error); g_assert_true (ret); /* write past the end of dst */ ret = fu_memcpy_safe (dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 5, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE); g_assert_false (ret); g_clear_error (&error); /* write past the end of dst with offset */ ret = fu_memcpy_safe (dst, sizeof(dst), 0x1, src, sizeof(src), 0x0, 4, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE); g_assert_false (ret); g_clear_error (&error); /* read past past the end of dst */ ret = fu_memcpy_safe (dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 6, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_READ); g_assert_false (ret); g_clear_error (&error); /* read past the end of src with offset */ ret = fu_memcpy_safe (dst, sizeof(dst), 0x0, src, sizeof(src), 0x4, 4, &error); g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_READ); g_assert_false (ret); g_clear_error (&error); } static void fu_progressbar_func (gconstpointer user_data) { 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); } int main (int argc, char **argv) { gboolean ret; g_autofree gchar *pluginfn = NULL; g_autoptr(GError) error = NULL; g_autoptr(FuTest) self = g_new0 (FuTest, 1); 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_OFFLINE_TRIGGER", "/tmp/fwupd-self-test/system-update", TRUE); g_setenv ("FWUPD_LOCALSTATEDIR", "/tmp/fwupd-self-test/var", TRUE); /* ensure empty tree */ fu_self_test_mkroot (); /* load the test plugin */ self->plugin = fu_plugin_new (); pluginfn = g_build_filename (PLUGINBUILDDIR, "libfu_plugin_test." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open (self->plugin, pluginfn, &error); g_assert_no_error (error); g_assert_true (ret); /* tests go here */ if (g_test_slow ()) { g_test_add_data_func ("/fwupd/progressbar", self, fu_progressbar_func); } g_test_add_data_func ("/fwupd/plugin{build-hash}", self, fu_plugin_hash_func); g_test_add_data_func ("/fwupd/plugin{module}", self, fu_plugin_module_func); g_test_add_data_func ("/fwupd/memcpy", self, fu_memcpy_func); g_test_add_data_func ("/fwupd/device-list", self, fu_device_list_func); g_test_add_data_func ("/fwupd/device-list{delay}", self, fu_device_list_delay_func); g_test_add_data_func ("/fwupd/device-list{compatible}", self, fu_device_list_compatible_func); g_test_add_data_func ("/fwupd/device-list{remove-chain}", self, fu_device_list_remove_chain_func); g_test_add_data_func ("/fwupd/engine{device-unlock}", self, fu_engine_device_unlock_func); g_test_add_data_func ("/fwupd/engine{multiple-releases}", self, fu_engine_multiple_rels_func); g_test_add_data_func ("/fwupd/engine{history-success}", self, fu_engine_history_func); g_test_add_data_func ("/fwupd/engine{history-error}", self, fu_engine_history_error_func); if (g_test_slow ()) { g_test_add_data_func ("/fwupd/device-list{replug-auto}", self, fu_device_list_replug_auto_func); } g_test_add_data_func ("/fwupd/device-list{replug-user}", self, fu_device_list_replug_user_func); g_test_add_data_func ("/fwupd/engine{require-hwid}", self, fu_engine_require_hwid_func); g_test_add_data_func ("/fwupd/engine{history-inherit}", self, fu_engine_history_inherit); g_test_add_data_func ("/fwupd/engine{partial-hash}", self, fu_engine_partial_hash_func); g_test_add_data_func ("/fwupd/engine{downgrade}", self, fu_engine_downgrade_func); g_test_add_data_func ("/fwupd/engine{requirements-success}", self, fu_engine_requirements_func); g_test_add_data_func ("/fwupd/engine{requirements-missing}", self, fu_engine_requirements_missing_func); g_test_add_data_func ("/fwupd/engine{requirements-version-require}", self, fu_engine_requirements_version_require_func); g_test_add_data_func ("/fwupd/engine{requirements-parent-device}", self, fu_engine_requirements_parent_device_func); g_test_add_data_func ("/fwupd/engine{requirements_protocol_check_func}", self, fu_engine_requirements_protocol_check_func); g_test_add_data_func ("/fwupd/engine{requirements-not-child}", self, fu_engine_requirements_child_func); g_test_add_data_func ("/fwupd/engine{requirements-not-child-fail}", self, fu_engine_requirements_child_fail_func); g_test_add_data_func ("/fwupd/engine{requirements-unsupported}", self, fu_engine_requirements_unsupported_func); g_test_add_data_func ("/fwupd/engine{requirements-device}", self, fu_engine_requirements_device_func); g_test_add_data_func ("/fwupd/engine{requirements-device-plain}", self, fu_engine_requirements_device_plain_func); g_test_add_data_func ("/fwupd/engine{requirements-version-format}", self, fu_engine_requirements_version_format_func); g_test_add_data_func ("/fwupd/engine{device-auto-parent}", self, fu_engine_device_parent_func); g_test_add_data_func ("/fwupd/engine{device-priority}", self, fu_engine_device_priority_func); g_test_add_data_func ("/fwupd/engine{install-duration}", self, fu_engine_install_duration_func); g_test_add_data_func ("/fwupd/engine{generate-md}", self, fu_engine_generate_md_func); g_test_add_data_func ("/fwupd/engine{requirements-other-device}", self, fu_engine_requirements_other_device_func); g_test_add_data_func ("/fwupd/plugin{composite}", self, fu_plugin_composite_func); g_test_add_data_func ("/fwupd/history", self, fu_history_func); g_test_add_data_func ("/fwupd/history{migrate}", self, fu_history_migrate_func); g_test_add_data_func ("/fwupd/plugin-list", self, fu_plugin_list_func); g_test_add_data_func ("/fwupd/plugin-list{depsolve}", self, fu_plugin_list_depsolve_func); g_test_add_data_func ("/fwupd/keyring{gpg}", self, fu_keyring_gpg_func); g_test_add_data_func ("/fwupd/keyring{pkcs7}", self, fu_keyring_pkcs7_func); g_test_add_data_func ("/fwupd/keyring{pkcs7-self-signed}", self, fu_keyring_pkcs7_self_signed_func); return g_test_run (); } fwupd-1.3.9/src/fu-systemd.c000066400000000000000000000115021362775233600157210ustar00rootroot00000000000000/* * 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" #define SYSTEMD_UNIT_INTERFACE "org.freedesktop.systemd1.Unit" 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_UNIT_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, "Stop", g_variant_new ("(s)", "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.3.9/src/fu-systemd.h000066400000000000000000000010121362775233600157210ustar00rootroot00000000000000/* * Copyright (C) 2017-2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include 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); fwupd-1.3.9/src/fu-tool.c000066400000000000000000001703251362775233600152170ustar00rootroot00000000000000/* * Copyright (C) 2015-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include #ifdef HAVE_GIO_UNIX #include #endif #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-private.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_READ, FU_UTIL_OPERATION_LAST } FuUtilOperation; struct FuUtilPrivate { GCancellable *cancellable; GMainLoop *loop; GOptionContext *context; FuEngine *engine; FuProgressbar *progressbar; gboolean no_reboot_check; gboolean no_safety_check; gboolean prepare_blob; gboolean cleanup_blob; gboolean enable_json_state; FwupdInstallFlags flags; gboolean show_all_devices; gboolean disable_ssl_strict; /* only valid in update and downgrade */ FuUtilOperation current_operation; FwupdDevice *current_device; gchar *current_message; FwupdDeviceFlags completion_flags; FwupdDeviceFlags filter_include; FwupdDeviceFlags filter_exclude; }; 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; fwupd_device_array_ensure_parents (devices); /* 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 to 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; } #ifdef HAVE_GIO_UNIX 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; } #endif 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_filter_device (FuUtilPrivate *priv, FwupdDevice *dev) { if (priv->filter_include != FWUPD_DEVICE_FLAG_NONE) { if (!fwupd_device_has_flag (dev, priv->filter_include)) return FALSE; } if (priv->filter_exclude != FWUPD_DEVICE_FLAG_NONE) { if (fwupd_device_has_flag (dev, priv->filter_exclude)) return FALSE; } return TRUE; } static gchar * fu_util_get_tree_title (FuUtilPrivate *priv) { return g_strdup (fu_engine_get_host_product (priv->engine)); } static gboolean fu_util_get_updates (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_autoptr(GNode) root = g_node_new (NULL); g_autofree gchar *title = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; title = fu_util_get_tree_title (priv); /* get devices from daemon */ devices = fu_engine_get_devices (priv->engine, error); if (devices == NULL) return FALSE; fwupd_device_array_ensure_parents (devices); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index (devices, i); g_autofree gchar *upgrade_str = NULL; g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; GNode *child; /* not going to have results, so save a engine round-trip */ if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE)) continue; if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { /* TRANSLATORS: message letting the user know no device upgrade available due to missing on LVFS * %1 is the device name */ g_autofree gchar *tmp = g_strdup_printf (_("• %s has no available firmware updates"), fwupd_device_get_name (dev)); g_printerr ("%s\n", tmp); continue; } if (!fu_util_filter_device (priv, dev)) 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) { /* TRANSLATORS: message letting the user know no device upgrade available * %1 is the device name */ g_autofree gchar *tmp = g_strdup_printf (_("• %s has the latest available firmware version"), fwupd_device_get_name (dev)); g_printerr ("%s\n", tmp); /* discard the actual reason from user, but leave for debugging */ g_debug ("%s", error_local->message); continue; } child = g_node_append_data (root, dev); for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel = g_ptr_array_index (rels, j); g_node_append_data (child, g_object_ref (rel)); } } if (g_node_n_nodes (root, G_TRAVERSE_ALL) > 1) fu_util_print_tree (root, title); /* 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; g_autoptr(GNode) root = g_node_new (NULL); g_autofree gchar *title = NULL; gint fd; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; title = fu_util_get_tree_title (priv); /* check args */ if (g_strv_length (values) != 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* implied, important for get-details on a device not in your system */ priv->show_all_devices = TRUE; /* 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); FwupdRelease *rel; GNode *child; if (!fu_util_filter_device (priv, dev)) continue; child = g_node_append_data (root, dev); rel = fwupd_device_get_release_default (dev); if (rel != NULL) g_node_append_data (child, rel); } fu_util_print_tree (root, title); return TRUE; } static gboolean fu_util_get_device_flags (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GString) str = g_string_new (NULL); for (FwupdDeviceFlags i = FWUPD_DEVICE_FLAG_INTERNAL; i < FWUPD_DEVICE_FLAG_UNKNOWN; i<<=1) { const gchar *tmp = fwupd_device_flag_to_string (i); if (tmp == NULL) break; if (i != FWUPD_DEVICE_FLAG_INTERNAL) g_string_append (str, " "); g_string_append (str, tmp); g_string_append (str, " ~"); g_string_append (str, tmp); } g_print ("%s\n", str->str); 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 (!fu_util_filter_device (priv, FWUPD_DEVICE (dev_tmp))) continue; 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_devices (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new (NULL); g_autofree gchar *title = NULL; g_autoptr(GPtrArray) devs = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; title = fu_util_get_tree_title (priv); /* 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; } fwupd_device_array_ensure_parents (devs); fu_util_build_device_tree (priv, root, devs, NULL); fu_util_print_tree (root, title); /* save the device state for other applications to see */ return fu_util_save_current_state (priv, error); } static FuDevice * fu_util_prompt_for_device (FuUtilPrivate *priv, GError **error) { FuDevice *dev; guint idx; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_filtered = NULL; /* get devices from daemon */ devices = fu_engine_get_devices (priv->engine, error); if (devices == NULL) return NULL; fwupd_device_array_ensure_parents (devices); /* filter results */ devices_filtered = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (guint i = 0; i < devices->len; i++) { dev = g_ptr_array_index (devices, i); if (!fu_util_filter_device (priv, FWUPD_DEVICE (dev))) continue; g_ptr_array_add (devices_filtered, g_object_ref (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); /* TRANSLATORS: Device has been chosen by the daemon for the user */ g_print ("%s: %s\n", _("Selected device"), fu_device_get_name (dev)); 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, fu_device_get_id (dev), fu_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 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 if (priv->current_operation == FU_UTIL_OPERATION_READ) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf (_("Reading from %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 gboolean fu_util_firmware_read (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GBytes) blob_empty = g_bytes_new (NULL, 0); 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; } /* file already exists */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && g_file_test (values[0], G_FILE_TEST_EXISTS)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Filename already exists"); return FALSE; } /* write a zero length file to ensure the destination is writable to * avoid failing at the end of a potentially lengthy operation */ if (!fu_common_set_contents_bytes (values[0], blob_empty, 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_READ; g_signal_connect (priv->engine, "device-changed", G_CALLBACK (fu_util_update_device_changed_cb), priv); /* dump firmware */ blob_fw = fu_engine_firmware_read (priv->engine, device, priv->flags, error); if (blob_fw == NULL) return FALSE; return fu_common_set_contents_bytes (values[0], blob_fw, 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 (g_file_test (perhapsfn, G_FILE_TEST_EXISTS)) return g_strdup (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; fwupd_device_array_ensure_parents (devices_possible); } 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; fwupd_device_array_ensure_parents (devices); 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_UPDATABLE)) continue; if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { /* TRANSLATORS: message letting the user know no device upgrade available due to missing on LVFS * %1 is the device name */ g_autofree gchar *tmp = g_strdup_printf (_("• %s has no available firmware updates"), fwupd_device_get_name (dev)); g_printerr ("%s\n", tmp); continue; } if (!fu_util_filter_device (priv, dev)) continue; device_id = fu_device_get_id (dev); rels = fu_engine_get_upgrades (priv->engine, device_id, &error_local); if (rels == NULL) { /* TRANSLATORS: message letting the user know no device upgrade available * %1 is the device name */ g_autofree gchar *tmp = g_strdup_printf (_("• %s has the latest available firmware version"), fwupd_device_get_name (dev)); g_printerr ("%s\n", tmp); /* discard the actual reason from user, but leave for debugging */ g_debug ("%s", error_local->message); continue; } if (!priv->no_safety_check) { if (!fu_util_prompt_warning (dev, fu_util_get_tree_title (priv), error)) return FALSE; } 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 (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "--allow-older is not supported for this command"); return FALSE; } if (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "--allow-reinstall is not supported for this command"); return FALSE; } 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; /* 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; /* 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_printerr ("No firmware to activate\n"); return TRUE; } /* 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_util_filter_device (priv, FWUPD_DEVICE (device))) continue; 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 = fu_util_device_to_string (device, 0); /* 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 = fu_util_device_to_string (device, 0); /* 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 = fu_util_device_to_string (device, 0); /* 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_get_firmware_types (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) firmware_types = NULL; /* load engine */ if (!fu_engine_load (priv->engine, FU_ENGINE_LOAD_FLAG_NO_ENUMERATE, error)) return FALSE; firmware_types = fu_engine_get_firmware_gtype_ids (priv->engine); for (guint i = 0; i < firmware_types->len; i++) { const gchar *id = g_ptr_array_index (firmware_types, i); g_print ("%s\n", id); } if (firmware_types->len == 0) { /* TRANSLATORS: nothing found */ g_print ("%s\n", _("No firmware IDs found")); return TRUE; } return TRUE; } static gchar * fu_util_prompt_for_firmware_type (FuUtilPrivate *priv, GError **error) { g_autoptr(GPtrArray) firmware_types = NULL; guint idx; firmware_types = fu_engine_get_firmware_gtype_ids (priv->engine); /* TRANSLATORS: get interactive prompt */ g_print ("%s\n", _("Choose a firmware type:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print ("0.\t%s\n", _("Cancel")); for (guint i = 0; i < firmware_types->len; i++) { const gchar *id = g_ptr_array_index (firmware_types, i); g_print ("%u.\t%s\n", i + 1, id); } idx = fu_util_prompt_for_number (firmware_types->len); if (idx == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return NULL; } return g_strdup (g_ptr_array_index (firmware_types, idx - 1)); } static gboolean fu_util_firmware_parse (FuUtilPrivate *priv, gchar **values, GError **error) { GType gtype; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autofree gchar *firmware_type = NULL; g_autofree gchar *str = NULL; /* check args */ if (g_strv_length (values) == 0 || g_strv_length (values) > 2) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: filename required"); return FALSE; } if (g_strv_length (values) == 2) firmware_type = g_strdup (values[1]); /* load file */ blob = fu_common_get_contents_bytes (values[0], error); if (blob == NULL) return FALSE; /* load engine */ if (!fu_engine_load (priv->engine, FU_ENGINE_LOAD_FLAG_NO_ENUMERATE, error)) return FALSE; /* find the GType to use */ if (firmware_type == NULL) firmware_type = fu_util_prompt_for_firmware_type (priv, error); if (firmware_type == NULL) return FALSE; gtype = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type); if (gtype == G_TYPE_INVALID) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not supported", firmware_type); return FALSE; } firmware = g_object_new (gtype, NULL); if (!fu_firmware_parse (firmware, blob, priv->flags, error)) return FALSE; str = fu_firmware_to_string (firmware); g_print ("%s", str); 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; g_autoptr(GNode) root = g_node_new (NULL); g_autofree gchar *title = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; title = fu_util_get_tree_title (priv); /* 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++) { g_autoptr(GPtrArray) rels = NULL; FwupdDevice *dev = g_ptr_array_index (devices, i); FwupdRelease *rel; const gchar *remote; GNode *child; if (!fu_util_filter_device (priv, dev)) continue; child = g_node_append_data (root, dev); rel = fwupd_device_get_release_default (dev); if (rel == NULL) continue; remote = fwupd_release_get_remote_id (rel); /* doesn't actually map to remote */ if (remote == NULL) { g_node_append_data (child, rel); continue; } /* try to lookup releases from client */ rels = fu_engine_get_releases (priv->engine, fwupd_device_get_id (dev), error); if (rels == NULL) return FALSE; /* map to a release in client */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel2 = g_ptr_array_index (rels, j); if (g_strcmp0 (remote, fwupd_release_get_remote_id (rel2)) != 0) continue; if (g_strcmp0 (fwupd_release_get_version (rel), fwupd_release_get_version (rel2)) != 0) continue; g_node_append_data (child, g_object_ref (rel2)); rel = NULL; break; } /* didn't match anything */ if (rels->len == 0 || rel != NULL) { g_node_append_data (child, rel); continue; } } fu_util_print_tree (root, title); return TRUE; } static gboolean fu_util_refresh_remote (FuUtilPrivate *priv, FwupdRemote *remote, GError **error) { g_autofree gchar *fn_raw = NULL; g_autofree gchar *fn_sig = NULL; g_autoptr(GBytes) bytes_raw = NULL; g_autoptr(GBytes) bytes_sig = NULL; /* payload */ fn_raw = fu_util_get_user_cache_path (fwupd_remote_get_metadata_uri (remote)); if (!fu_common_mkdir_parent (fn_raw, error)) return FALSE; if (!fu_util_download_out_of_process (fwupd_remote_get_metadata_uri (remote), fn_raw, error)) return FALSE; bytes_raw = fu_common_get_contents_bytes (fn_raw, error); if (bytes_raw == NULL) return FALSE; /* signature */ fn_sig = fu_util_get_user_cache_path (fwupd_remote_get_metadata_uri_sig (remote)); if (!fu_util_download_out_of_process (fwupd_remote_get_metadata_uri_sig (remote), fn_sig, error)) return FALSE; bytes_sig = fu_common_get_contents_bytes (fn_sig, error); if (bytes_sig == NULL) return FALSE; /* send to daemon */ g_debug ("updating %s", fwupd_remote_get_id (remote)); return fu_engine_update_metadata_bytes (priv->engine, fwupd_remote_get_id (remote), bytes_raw, bytes_sig, error); } static gboolean fu_util_refresh (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) remotes = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* download new metadata */ remotes = fu_engine_get_remotes (priv->engine, 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 (!fu_util_refresh_remote (priv, remote, error)) return FALSE; } return TRUE; } static gboolean fu_util_get_remotes (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new (NULL); g_autoptr(GPtrArray) remotes = NULL; g_autofree gchar *title = NULL; /* load engine */ if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; title = fu_util_get_tree_title (priv); /* list remotes */ remotes = fu_engine_get_remotes (priv->engine, error); if (remotes == NULL) return FALSE; if (remotes->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "no remotes available"); return FALSE; } for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote_tmp = g_ptr_array_index (remotes, i); g_node_append_data (root, remote_tmp); } fu_util_print_tree (root, title); 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; g_autofree gchar *filter = 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 reinstalling 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 }, { "no-safety-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_safety_check, /* TRANSLATORS: command line option */ _("Do not perform device safety checks"), 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 }, { "disable-ssl-strict", '\0', 0, G_OPTION_ARG_NONE, &priv->disable_ssl_strict, /* TRANSLATORS: command line option */ _("Ignore SSL strict checks when downloading files"), NULL }, { "filter", '\0', 0, G_OPTION_ARG_STRING, &filter, /* TRANSLATORS: command line option */ _("Filter with a set of device flags using a ~ prefix to " "exclude, e.g. 'internal,~needs-reboot'"), NULL }, { NULL} }; #ifdef _WIN32 /* workaround Windows setting the codepage to 1252 */ g_setenv ("LANG", "C.UTF-8", FALSE); #endif setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); #ifdef HAVE_GETUID /* 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")); #endif /* 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,get-upgrades", 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,get-topology", NULL, /* TRANSLATORS: command description */ _("Get all devices that support firmware updates"), fu_util_get_devices); fu_util_cmd_array_add (cmd_array, "get-device-flags", NULL, /* TRANSLATORS: command description */ _("Get all device flags supported by fwupd"), fu_util_get_device_flags); 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 [DEVICE-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,upgrade", 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); fu_util_cmd_array_add (cmd_array, "firmware-read", "FILENAME [DEVICE-ID]", /* TRANSLATORS: command description */ _("Read a firmware blob from a device"), fu_util_firmware_read); fu_util_cmd_array_add (cmd_array, "firmware-parse", "FILENAME [FIRMWARE-TYPE]", /* TRANSLATORS: command description */ _("Parse and show details about a firmware file"), fu_util_firmware_parse); fu_util_cmd_array_add (cmd_array, "get-firmware-types", NULL, /* TRANSLATORS: command description */ _("List the available firmware types"), fu_util_get_firmware_types); 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, "refresh", NULL, /* TRANSLATORS: command description */ _("Refresh metadata from remote server"), fu_util_refresh); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new (); #ifdef HAVE_GIO_UNIX g_unix_signal_add_full (G_PRIORITY_DEFAULT, SIGINT, fu_util_sigint_cb, priv, NULL); #endif 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; priv->no_safety_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; } /* allow disabling SSL strict mode for broken corporate proxies */ if (priv->disable_ssl_strict) { /* TRANSLATORS: try to help */ g_printerr ("%s\n", _("WARNING: Ignoring SSL strict checks, " "to do this automatically in the future " "export DISABLE_SSL_STRICT in your environment")); g_setenv ("DISABLE_SSL_STRICT", "1", TRUE); } /* parse filter flags */ if (filter != NULL) { if (!fu_util_parse_filter_flags (filter, &priv->filter_include, &priv->filter_exclude, &error)) { /* TRANSLATORS: the user didn't read the man page */ g_print ("%s: %s\n", _("Failed to parse flags for --filter"), 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.3.9/src/fu-util-common.c000066400000000000000000001300651362775233600165020ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include #include #include #include #include #include "fu-common.h" #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 = fu_common_strwidth (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; } static gboolean fu_util_traverse_tree (GNode *n, gpointer data) { guint idx = g_node_depth (n) - 1; g_autofree gchar *tmp = NULL; g_auto(GStrv) split = NULL; /* get split lines */ if (FWUPD_IS_DEVICE (n->data)) { FwupdDevice *dev = FWUPD_DEVICE (n->data); tmp = fu_util_device_to_string (dev, idx); } else if (FWUPD_IS_REMOTE (n->data)) { FwupdRemote *remote = FWUPD_REMOTE (n->data); tmp = fu_util_remote_to_string (remote, idx); } else if (FWUPD_IS_RELEASE (n->data)) { FwupdRelease *release = FWUPD_RELEASE (n->data); tmp = fu_util_release_to_string (release, idx); g_debug ("%s", tmp); } /* root node */ if (n->data == NULL && g_getenv ("FWUPD_VERBOSE") == NULL) { const gchar *str = data; g_print ("%s\n│\n", str != NULL ? str : "○"); return FALSE; } if (n->parent == NULL) return FALSE; if (tmp == NULL) return FALSE; split = g_strsplit (tmp, "\n", -1); for (guint i = 0; split[i] != NULL; i++) { g_autoptr(GString) str = g_string_new (NULL); /* header */ if (i == 0) { if (g_node_next_sibling (n) == NULL) g_string_prepend (str, "└─"); else g_string_prepend (str, "├─"); /* properties */ } else { g_string_prepend (str, n->children == NULL ? " " : " │"); g_string_prepend (str, g_node_next_sibling (n) == NULL ? " " : "│"); g_string_append (str, " "); } /* ancestors */ for (GNode *c = n->parent; c->parent != NULL; c = c->parent) { if (g_node_next_sibling (c) != NULL || idx == 0) { g_string_prepend (str, "│ "); continue; } g_string_prepend (str, " "); } /* empty line */ if (split[i][0] == '\0') { g_print ("%s\n", str->str); continue; } /* dump to the console */ g_string_append (str, split[i] + (idx * 2)); g_print ("%s\n", str->str); } return FALSE; } void fu_util_print_tree (GNode *n, gpointer data) { g_node_traverse (n, G_PRE_ORDER, G_TRAVERSE_ALL, -1, fu_util_traverse_tree, data); } static gboolean fu_util_is_interesting_child (FwupdDevice *dev) { GPtrArray *children = fwupd_device_get_children (dev); for (guint i = 0; i < children->len; i++) { FwupdDevice *child = g_ptr_array_index (children, i); if (fu_util_is_interesting_device (child)) return TRUE; } 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; /* device not plugged in, get-details */ if (fwupd_device_get_flags (dev) == 0) return TRUE; if (fu_util_is_interesting_child (dev)) return TRUE; return FALSE; } gchar * fu_util_get_user_cache_path (const gchar *fn) { const gchar *root = g_get_user_cache_dir (); g_autofree gchar *basename = g_path_get_basename (fn); g_autofree gchar *cachedir_legacy = NULL; /* if run from a systemd unit, use the cache directory set there */ if (g_getenv ("CACHE_DIRECTORY") != NULL) root = g_getenv ("CACHE_DIRECTORY"); /* 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 (root, "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 (root, "fwupd", basename, NULL); } gchar * fu_util_get_client_version (void) { GString *string = g_string_new (""); g_string_append_printf (string, "%i.%i.%i", FWUPD_MAJOR_VERSION, FWUPD_MINOR_VERSION, FWUPD_MICRO_VERSION); #ifdef FWUPD_DIRTY_VERSION g_string_append_printf (string, "-%i-%s", FWUPD_DIRTY_VERSION, FWUPD_COMMIT_VERSION); #endif return g_string_free (string, FALSE); } gchar * fu_util_get_versions (void) { GString *string = g_string_new (""); g_autofree gchar *client_version = fu_util_get_client_version (); g_string_append_printf (string, "client version:\t%s\n", client_version); 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_warning (FwupdDevice *device, const gchar *machine, GError **error) { FwupdDeviceFlags flags; g_autofree gchar *str = NULL; /* device is already in bootloader mode */ flags = fwupd_device_get_flags (device); if (flags & FWUPD_DEVICE_FLAG_IS_BOOTLOADER) return TRUE; /* device may reboot */ if ((flags & FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE) == 0) { /* TRANSLATORS: warn the user before updating, %1 is a device name */ str = g_strdup_printf (_("%s and all connected devices may not be usable while updating."), fwupd_device_get_name (device)); /* device can get bricked */ } else if ((flags & FWUPD_DEVICE_FLAG_SELF_RECOVERY) == 0) { /* external device */ if ((flags & FWUPD_DEVICE_FLAG_INTERNAL) == 0) { /* TRANSLATORS: warn the user before updating, %1 is a device name */ str = g_strdup_printf (_("%s must remain connected for the duration of the update to avoid damage."), fwupd_device_get_name (device)); } else if (flags & FWUPD_DEVICE_FLAG_REQUIRE_AC) { /* TRANSLATORS: warn the user before updating, %1 is a machine name */ str = g_strdup_printf (_("%s must remain plugged into a power source for the duration of the update to avoid damage."), machine); } } if (str != NULL) { g_print ("%s %s [Y|n]: ", str, /* TRANSLATORS: prompt to apply the update */ _("Continue with update?")); if (!fu_util_prompt_for_boolean (TRUE)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return FALSE; } } return TRUE; } 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 (FALSE)) 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 (FALSE)) 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) { g_auto(GStrv) values_copy = g_new0 (gchar *, g_strv_length (values) + 1); /* clear out bash completion sentinel */ for (guint i = 0; values[i] != NULL; i++) { if (g_strcmp0 (values[i], "{") == 0) break; values_copy[i] = g_strdup (values[i]); } /* 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_copy, 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 = fu_common_strwidth (item->name) + 2; if (item->arguments != NULL) { g_string_append (string, " "); g_string_append (string, item->arguments); len += fu_common_strwidth (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; } /* relax the SSL checks for broken corporate proxies */ if (g_getenv ("DISABLE_SSL_STRICT") != NULL) g_object_set (session, SOUP_SESSION_SSL_STRICT, FALSE, 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 + fu_common_strwidth (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 += fu_common_strwidth (start); g_print ("%s", start); } if (text != NULL) { offset += fu_common_strwidth (text); g_print ("%s", text); } if (end != NULL) offset += fu_common_strwidth (end); 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); } gboolean fu_util_parse_filter_flags (const gchar *filter, FwupdDeviceFlags *include, FwupdDeviceFlags *exclude, GError **error) { FwupdDeviceFlags tmp; g_auto(GStrv) strv = g_strsplit (filter, ",", -1); g_return_val_if_fail (include != NULL, FALSE); g_return_val_if_fail (exclude != NULL, FALSE); for (guint i = 0; strv[i] != NULL; i++) { if (g_str_has_prefix (strv[i], "~")) { tmp = fwupd_device_flag_from_string (strv[i] + 1); if (tmp == FWUPD_DEVICE_FLAG_UNKNOWN) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown device flag %s", strv[i] + 1); return FALSE; } if ((tmp & *include) > 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already included", fwupd_device_flag_to_string (tmp)); return FALSE; } if ((tmp & *exclude) > 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already excluded", fwupd_device_flag_to_string (tmp)); return FALSE; } *exclude |= tmp; } else { tmp = fwupd_device_flag_from_string (strv[i]); if (tmp == FWUPD_DEVICE_FLAG_UNKNOWN) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown device flag %s", strv[i]); return FALSE; } if ((tmp & *exclude) > 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already excluded", fwupd_device_flag_to_string (tmp)); return FALSE; } if ((tmp & *include) > 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already included", fwupd_device_flag_to_string (tmp)); return FALSE; } *include |= tmp; } } return TRUE; } gchar * fu_util_convert_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); } /* success */ return fu_common_strstrip (str->str); } /** * fu_util_time_to_str: * @tmp: the time in seconds * * Converts a timestamp to a 'pretty' translated string * * Return value: (transfer full): A string * * Since: 1.3.7 **/ 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 gchar * fu_util_device_flag_to_string (guint64 device_flag) { if (device_flag == FWUPD_DEVICE_FLAG_NONE) { return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_INTERNAL) { /* TRANSLATORS: Device cannot be removed easily*/ return _("Internal device"); } if (device_flag == FWUPD_DEVICE_FLAG_UPDATABLE) { /* TRANSLATORS: Device is updatable in this or any other mode */ return _("Updatable"); } if (device_flag == FWUPD_DEVICE_FLAG_ONLY_OFFLINE) { /* TRANSLATORS: Update can only be done from offline mode */ return _("Update requires a reboot"); } if (device_flag == FWUPD_DEVICE_FLAG_REQUIRE_AC) { /* TRANSLATORS: Must be plugged in to an outlet */ return _("Requires AC power"); } if (device_flag == FWUPD_DEVICE_FLAG_LOCKED) { /* TRANSLATORS: Is locked and can be unlocked */ return _("Device is locked"); } if (device_flag == FWUPD_DEVICE_FLAG_SUPPORTED) { /* TRANSLATORS: Is found in current metadata */ return _("Supported on remote server"); } if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER) { /* TRANSLATORS: Requires a bootloader mode to be manually enabled by the user */ return _("Requires a bootloader"); } if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_REBOOT) { /* TRANSLATORS: Requires a reboot to apply firmware or to reload hardware */ return _("Needs a reboot after installation"); } if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) { /* TRANSLATORS: Requires system shutdown to apply firmware */ return _("Needs shutdown after installation"); } if (device_flag == FWUPD_DEVICE_FLAG_REPORTED) { /* TRANSLATORS: Has been reported to a metadata server */ return _("Reported to remote server"); } if (device_flag == FWUPD_DEVICE_FLAG_NOTIFIED) { /* TRANSLATORS: User has been notified */ return _("User has been notified"); } if (device_flag == FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST) { /* TRANSLATORS: Install composite firmware on the parent before the child */ return _("Install to parent device first"); } if (device_flag == FWUPD_DEVICE_FLAG_IS_BOOTLOADER) { /* TRANSLATORS: Is currently in bootloader mode */ return _("Is in bootloader mode"); } if (device_flag == FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) { /* TRANSLATORS: The hardware is waiting to be replugged */ return _("Hardware is waiting to be replugged"); } if (device_flag == FWUPD_DEVICE_FLAG_IGNORE_VALIDATION) { /* TRANSLATORS: Ignore validation safety checks when flashing this device */ return _("Ignore validation safety checks"); } if (device_flag == FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION) { /* TRANSLATORS: Device update needs to be separately activated */ return _("Device update needs activation"); } if (device_flag == FWUPD_DEVICE_FLAG_ENSURE_SEMVER) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_HISTORICAL) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_ONLY_SUPPORTED) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_WILL_DISAPPEAR) { /* TRANSLATORS: Device will not return after update completes */ return _("Device will not re-appear after update completes"); } if (device_flag == FWUPD_DEVICE_FLAG_CAN_VERIFY) { /* TRANSLATORS: Device supports some form of checksum verification */ return _("Cryptographic hash verification is available"); } if (device_flag == FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_DUAL_IMAGE) { /* TRANSLATORS: Device supports a safety mechanism for flashing */ return _("Device stages updates"); } if (device_flag == FWUPD_DEVICE_FLAG_SELF_RECOVERY) { /* TRANSLATORS: Device supports a safety mechanism for flashing */ return _("Device can recover flash failures"); } if (device_flag == FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE) { /* TRANSLATORS: Device remains usable during update */ return _("Device is usable for the duration of the update"); } if (device_flag == FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED) { /* TRANSLATORS: a version check is required for all firmware */ return _("Device firmware is required to have a version check"); } if (device_flag == FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES) { /* TRANSLATORS: a version check is required for all firmware */ return _("Device is required to install all provided releases"); } if (device_flag == FWUPD_DEVICE_FLAG_UNKNOWN) { return NULL; } return NULL; } gchar * fu_util_device_to_string (FwupdDevice *dev, guint idt) { FwupdUpdateState state; GPtrArray *guids = fwupd_device_get_guids (dev); GPtrArray *instance_ids = fwupd_device_get_instance_ids (dev); GString *str = g_string_new (NULL); const gchar *tmp; const gchar *tmp2; guint64 flags = fwupd_device_get_flags (dev); guint64 modified = fwupd_device_get_modified (dev); g_autoptr(GHashTable) ids = NULL; /* some fields are intentionally not included and are only shown in --verbose */ if (g_getenv ("FWUPD_VERBOSE") != NULL) { g_autofree gchar *debug_str = NULL; debug_str = fwupd_device_to_string (dev); g_debug ("%s", debug_str); return NULL; } tmp = fwupd_device_get_name (dev); if (tmp == NULL) { /* TRANSLATORS: Name of hardware */ tmp = _("Unknown Device"); } fu_common_string_append_kv (str, idt, tmp, NULL); tmp = fwupd_device_get_id (dev); if (tmp != NULL) { /* TRANSLATORS: ID for hardware, typically a SHA1 sum */ fu_common_string_append_kv (str, idt + 1, _("Device ID"), tmp); } /* summary */ tmp = fwupd_device_get_summary (dev); if (tmp != NULL) { /* TRANSLATORS: one line summary of device */ fu_common_string_append_kv (str, idt + 1, _("Summary"), tmp); } /* description */ tmp = fwupd_device_get_description (dev); if (tmp != NULL) { g_autofree gchar *desc = NULL; desc = fu_util_convert_description (tmp, NULL); /* TRANSLATORS: multiline description of device */ fu_common_string_append_kv (str, idt + 1, _("Description"), desc); } /* versions */ tmp = fwupd_device_get_version (dev); if (tmp != NULL) { if (flags & FWUPD_DEVICE_FLAG_HISTORICAL) { /* TRANSLATORS: version number of previous firmware */ fu_common_string_append_kv (str, idt + 1, _("Previous version"), tmp); } else { /* TRANSLATORS: version number of current firmware */ fu_common_string_append_kv (str, idt + 1, _("Current version"), tmp); } } tmp = fwupd_device_get_version_lowest (dev); if (tmp != NULL) { /* TRANSLATORS: smallest version number installable on device */ fu_common_string_append_kv (str, idt + 1, _("Minimum Version"), tmp); } tmp = fwupd_device_get_version_bootloader (dev); if (tmp != NULL) { /* TRANSLATORS: firmware version of bootloader */ fu_common_string_append_kv (str, idt + 1, _("Bootloader Version"), tmp); } /* vendor */ tmp = fwupd_device_get_vendor (dev); tmp2 = fwupd_device_get_vendor_id (dev); if (tmp != NULL && tmp2 != NULL) { g_autofree gchar *both = g_strdup_printf ("%s (%s)", tmp, tmp2); /* TRANSLATORS: manufacturer of hardware */ fu_common_string_append_kv (str, idt + 1, _("Vendor"), both); } else if (tmp != NULL) { /* TRANSLATORS: manufacturer of hardware */ fu_common_string_append_kv (str, idt + 1, _("Vendor"), tmp); } else if (tmp2 != NULL) { /* TRANSLATORS: manufacturer of hardware */ fu_common_string_append_kv (str, idt + 1, _("Vendor"), tmp2); } /* install duration */ if (fwupd_device_get_install_duration (dev) > 0) { g_autofree gchar *time = fu_util_time_to_str (fwupd_device_get_install_duration (dev)); /* TRANSLATORS: length of time the update takes to apply */ fu_common_string_append_kv (str, idt + 1, _("Install Duration"), time); } /* serial # */ tmp = fwupd_device_get_serial (dev); if (tmp != NULL) { /* TRANSLATORS: serial number of hardware */ fu_common_string_append_kv (str, idt + 1, _("Serial Number"), tmp); } /* update state */ state = fwupd_device_get_update_state (dev); if (state != FWUPD_UPDATE_STATE_UNKNOWN) { /* TRANSLATORS: hardware state, e.g. "pending" */ fu_common_string_append_kv (str, idt + 1, _("Update State"), fwupd_update_state_to_string (state)); } tmp = fwupd_device_get_update_error (dev); if (tmp != NULL) { /* TRANSLATORS: error message from last update attempt */ fu_common_string_append_kv (str, idt + 1, _("Update Error"), tmp); } tmp = fwupd_device_get_update_message (dev); if (tmp != NULL) { /* TRANSLATORS: helpful messages from last update */ fu_common_string_append_kv (str, idt + 1, _("Update Message"), tmp); } /* modified date: for history devices */ if (modified > 0) { g_autoptr(GDateTime) date = NULL; g_autofree gchar *time_str = NULL; date = g_date_time_new_from_unix_utc (modified); time_str = g_date_time_format (date, "%F %R"); /* TRANSLATORS: the original time/date the device was modified */ fu_common_string_append_kv (str, idt +1, _("Last modified"), time_str); } /* all GUIDs for this hardware, with IDs if available */ ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); for (guint i = 0; i < instance_ids->len; i++) { const gchar *instance_id = g_ptr_array_index (instance_ids, i); g_hash_table_insert (ids, fwupd_guid_hash_string (instance_id), g_strdup (instance_id)); } for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index (guids, i); const gchar *instance_id = g_hash_table_lookup (ids, guid); g_autofree gchar *guid_src = NULL; /* instance IDs are only available as root */ if (instance_id == NULL) { guid_src = g_strdup (guid); } else { guid_src = g_strdup_printf ("%s ← %s", guid, instance_id); } if (i == 0) { /* TRANSLATORS: global ID common to all similar hardware */ fu_common_string_append_kv (str, idt + 1, ngettext ("GUID", "GUIDs", guids->len), guid_src); } else { fu_common_string_append_kv (str, idt + 1, "", guid_src); } } /* TRANSLATORS: description of device ability */ tmp = _("Device Flags"); for (guint i = 0; i < 64; i++) { if ((flags & ((guint64) 1 << i)) == 0) continue; tmp2 = fu_util_device_flag_to_string ((guint64) 1 << i); if (tmp2 == NULL) continue; /* header */ if (tmp != NULL) { g_autofree gchar *bullet = NULL; bullet = g_strdup_printf ("• %s", tmp2); fu_common_string_append_kv (str, idt + 1, tmp, bullet); tmp = NULL; } else { g_autofree gchar *bullet = NULL; bullet = g_strdup_printf ("• %s", tmp2); fu_common_string_append_kv (str, idt + 1, "", bullet); } } return g_string_free (str, FALSE); } static const gchar * fu_util_license_to_string (const gchar *license) { if (license == NULL) { /* TRANSLATORS: we don't know the license of the update */ return _("Unknown"); } if (g_strcmp0 (license, "LicenseRef-proprietary") == 0 || g_strcmp0 (license, "proprietary") == 0) { /* TRANSLATORS: a non-free software license */ return _("Proprietary"); } return license; } gchar * fu_util_release_to_string (FwupdRelease *rel, guint idt) { GPtrArray *issues = fwupd_release_get_issues (rel); GString *str = g_string_new (NULL); guint64 flags = fwupd_release_get_flags (rel); g_autoptr(GString) flags_str = g_string_new (NULL); g_return_val_if_fail (FWUPD_IS_RELEASE (rel), NULL); fu_common_string_append_kv (str, idt, fwupd_release_get_name (rel), NULL); /* TRANSLATORS: version number of new firmware */ fu_common_string_append_kv (str, idt + 1 , _("New version"), fwupd_release_get_version (rel)); if (fwupd_release_get_remote_id (rel) != NULL) { /* TRANSLATORS: the server the file is coming from */ fu_common_string_append_kv (str, idt + 1, _("Remote ID"), fwupd_release_get_remote_id (rel)); } if (fwupd_release_get_summary (rel) != NULL) { /* TRANSLATORS: one line summary of device */ fu_common_string_append_kv (str, idt + 1, _("Summary"), fwupd_release_get_summary (rel)); } if (fwupd_release_get_name_variant_suffix (rel) != NULL) { /* TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') */ fu_common_string_append_kv (str, idt + 1, _("Variant"), fwupd_release_get_name_variant_suffix (rel)); } /* TRANSLATORS: e.g. GPLv2+, Proprietary etc */ fu_common_string_append_kv (str, idt + 1, _("License"), fu_util_license_to_string (fwupd_release_get_license (rel))); if (fwupd_release_get_size (rel) != 0) { g_autofree gchar *tmp = NULL; tmp = g_format_size (fwupd_release_get_size (rel)); /* TRANSLATORS: file size of the download */ fu_common_string_append_kv (str, idt + 1, _("Size"), tmp); } if (fwupd_release_get_details_url (rel) != NULL) { /* TRANSLATORS: more details about the update link */ fu_common_string_append_kv (str, idt + 1, _("Details"), fwupd_release_get_details_url (rel)); } if (fwupd_release_get_source_url (rel) != NULL) { /* TRANSLATORS: source (as in code) link */ fu_common_string_append_kv (str, idt + 1, _("Source"), fwupd_release_get_source_url (rel)); } if (fwupd_release_get_vendor (rel) != NULL) { /* TRANSLATORS: manufacturer of hardware */ fu_common_string_append_kv (str, idt + 1, _("Vendor"), fwupd_release_get_vendor (rel)); } if (fwupd_release_get_install_duration (rel) != 0) { g_autofree gchar *tmp = fu_util_time_to_str (fwupd_release_get_install_duration (rel)); /* TRANSLATORS: length of time the update takes to apply */ fu_common_string_append_kv (str, idt + 1, _("Duration"), tmp); } if (fwupd_release_get_update_message (rel) != NULL) { /* TRANSLATORS: helpful messages for the update */ fu_common_string_append_kv (str, idt + 1, _("Update Message"), fwupd_release_get_update_message (rel)); } for (guint i = 0; i < 64; i++) { if ((flags & ((guint64) 1 << i)) == 0) continue; g_string_append_printf (flags_str, "%s|", fwupd_release_flag_to_string ((guint64) 1 << i)); } if (flags_str->len > 0) { g_string_truncate (flags_str, flags_str->len - 1); /* TRANSLATORS: release properties */ fu_common_string_append_kv (str, idt + 1, _("Flags"), flags_str->str); } if (fwupd_release_get_description (rel) != NULL) { g_autofree gchar *desc = NULL; desc = fu_util_convert_description (fwupd_release_get_description (rel), NULL); /* TRANSLATORS: multiline description of device */ fu_common_string_append_kv (str, idt + 1, _("Description"), desc); } for (guint i = 0; i < issues->len; i++) { const gchar *issue = g_ptr_array_index (issues, i); if (i == 0) { /* TRANSLATORS: issue fixed with the release, e.g. CVE */ fu_common_string_append_kv (str, idt + 1, ngettext ("Issue", "Issues", issues->len), issue); } else { fu_common_string_append_kv (str, idt + 1, "", issue); } } return g_string_free (str, FALSE); } gchar * fu_util_remote_to_string (FwupdRemote *remote, guint idt) { GString *str = g_string_new (NULL); FwupdRemoteKind kind = fwupd_remote_get_kind (remote); FwupdKeyringKind keyring_kind = fwupd_remote_get_keyring_kind (remote); const gchar *tmp; gint priority; gdouble age; g_return_val_if_fail (FWUPD_IS_REMOTE (remote), NULL); fu_common_string_append_kv (str, idt, fwupd_remote_get_title (remote), NULL); /* TRANSLATORS: remote identifier, e.g. lvfs-testing */ fu_common_string_append_kv (str, idt + 1, _("Remote ID"), fwupd_remote_get_id (remote)); /* TRANSLATORS: remote type, e.g. remote or local */ fu_common_string_append_kv (str, idt + 1, _("Type"), fwupd_remote_kind_to_string (kind)); /* TRANSLATORS: keyring type, e.g. GPG or PKCS7 */ if (keyring_kind != FWUPD_KEYRING_KIND_UNKNOWN) { fu_common_string_append_kv (str, idt + 1, _("Keyring"), fwupd_keyring_kind_to_string (keyring_kind)); } /* TRANSLATORS: if the remote is enabled */ fu_common_string_append_kv (str, idt + 1, _("Enabled"), fwupd_remote_get_enabled (remote) ? "true" : "false"); tmp = fwupd_remote_get_checksum (remote); if (tmp != NULL) { /* TRANSLATORS: remote checksum */ fu_common_string_append_kv (str, idt + 1, _("Checksum"), tmp); } /* 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_common_string_append_kv (str, idt + 1, _("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_common_string_append_kv (str, idt + 1, _("Priority"), priority_str); } tmp = fwupd_remote_get_username (remote); if (tmp != NULL) { /* TRANSLATORS: remote filename base */ fu_common_string_append_kv (str, idt + 1, _("Username"), tmp); } tmp = fwupd_remote_get_password (remote); if (tmp != NULL) { g_autofree gchar *hidden = g_strnfill (fu_common_strwidth (tmp), '*'); /* TRANSLATORS: remote filename base */ fu_common_string_append_kv (str, idt + 1, _("Password"), hidden); } tmp = fwupd_remote_get_filename_cache (remote); if (tmp != NULL) { /* TRANSLATORS: filename of the local file */ fu_common_string_append_kv (str, idt + 1, _("Filename"), tmp); } tmp = fwupd_remote_get_filename_cache_sig (remote); if (tmp != NULL) { /* TRANSLATORS: filename of the local file */ fu_common_string_append_kv (str, idt + 1, _("Filename Signature"), tmp); } tmp = fwupd_remote_get_metadata_uri (remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_common_string_append_kv (str, idt + 1, _("Metadata URI"), tmp); } tmp = fwupd_remote_get_metadata_uri_sig (remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_common_string_append_kv (str, idt + 1, _("Metadata Signature"), tmp); } tmp = fwupd_remote_get_firmware_base_uri (remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_common_string_append_kv (str, idt + 1, _("Firmware Base URI"), tmp); } tmp = fwupd_remote_get_report_uri (remote); if (tmp != NULL) { /* TRANSLATORS: URI to send success/failure reports */ fu_common_string_append_kv (str, idt + 1, _("Report URI"), tmp); /* TRANSLATORS: Boolean value to automatically send reports */ fu_common_string_append_kv (str, idt + 1, _("Automatic Reporting"), fwupd_remote_get_automatic_reports (remote) ? "true" : "false"); } return g_string_free (str, FALSE); } fwupd-1.3.9/src/fu-util-common.h000066400000000000000000000047331362775233600165110ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include /* 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); void fu_util_print_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_client_version (void); gchar *fu_util_get_versions (void); void fu_util_warning_box (const gchar *str, guint width); gboolean fu_util_prompt_warning (FwupdDevice *device, const gchar *machine, GError **error); 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); gboolean fu_util_parse_filter_flags (const gchar *filter, FwupdDeviceFlags *include, FwupdDeviceFlags *exclude, GError **error); gchar *fu_util_convert_description (const gchar *xml, GError **error); gchar *fu_util_time_to_str (guint64 tmp); gchar *fu_util_device_to_string (FwupdDevice *dev, guint idt); gchar *fu_util_release_to_string (FwupdRelease *rel, guint idt); gchar *fu_util_remote_to_string (FwupdRemote *remote, guint idt); fwupd-1.3.9/src/fu-util.c000066400000000000000000002522311362775233600152140ustar00rootroot00000000000000/* * 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 #ifdef HAVE_GIO_UNIX #include #endif #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_HISTORY_DO_NOTHING, FU_UTIL_HISTORY_NEVER, FU_UTIL_HISTORY_PROMPT, FU_UTIL_HISTORY_AUTOMATIC, FU_UTIL_HISTORY_LAST, } FuUtilHistoryAction; 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 no_safety_check; gboolean assume_yes; gboolean sign; gboolean show_all_devices; gboolean disable_ssl_strict; /* only valid in update and downgrade */ FuUtilOperation current_operation; FwupdDevice *current_device; gchar *current_message; FwupdDeviceFlags completion_flags; FwupdDeviceFlags filter_include; FwupdDeviceFlags filter_exclude; }; 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 gboolean fu_util_filter_device (FuUtilPrivate *priv, FwupdDevice *dev) { for (guint i = 0; i < 64; i++) { FwupdDeviceFlags flag = 1LLU << i; if (priv->filter_include & flag) { if (!fwupd_device_has_flag (dev, flag)) return FALSE; } if (priv->filter_exclude & flag) { if (fwupd_device_has_flag (dev, flag)) return FALSE; } } return TRUE; } 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 (!fu_util_filter_device (priv, dev)) 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); /* TRANSLATORS: Device has been chosen by the daemon for the user */ g_print ("%s: %s\n", _("Selected device"), fwupd_device_get_name (dev)); 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_maybe_enable_automatic (FuUtilPrivate *priv, GPtrArray *remotes, GError **error) { guint idx; /* TRANSLATORS: Display a message asking every time an update is performed */ g_print ("%d.\t%s\n", FU_UTIL_HISTORY_DO_NOTHING, ngettext ("Do not upload report at this time, but prompt again for future updates", "Do not upload reports at this time, but prompt again for future updates", remotes->len)); /* TRANSLATORS: Display a message asking every time an update is performed */ g_print ("%d.\t%s\n", FU_UTIL_HISTORY_NEVER, ngettext ("Do not upload report, and never ask to upload reports for future updates", "Do not upload reports, and never ask to upload reports for future updates", remotes->len)); /* TRANSLATORS: Display a message asking every time an update is performed */ g_print ("%d.\t%s\n", FU_UTIL_HISTORY_PROMPT, ngettext ("Upload report just this one time, but prompt again for future updates", "Upload reports just this one time, but prompt again for future updates", remotes->len)); /* TRANSLATORS: Display a message asking every time an update is performed */ g_print ("%d.\t%s\n", FU_UTIL_HISTORY_AUTOMATIC, ngettext ("Upload report this time and automatically upload reports after completing future updates", "Upload reports this time and automatically upload reports after completing future updates", remotes->len)); idx = fu_util_prompt_for_number (FU_UTIL_HISTORY_LAST - 1); switch (idx) { case FU_UTIL_HISTORY_NEVER: for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); const gchar *remote_id = fwupd_remote_get_id (remote); if (fwupd_remote_get_report_uri (remote) == NULL) continue; if (!fwupd_client_modify_remote (priv->client, remote_id, "ReportURI", "", NULL, error)) return FALSE; } g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Reporting disabled"); return FALSE; case FU_UTIL_HISTORY_DO_NOTHING: g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return FALSE; case FU_UTIL_HISTORY_AUTOMATIC: for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index (remotes, i); const gchar *remote_id = fwupd_remote_get_id (remote); if (fwupd_remote_get_report_uri (remote) == NULL) continue; if (fwupd_remote_get_automatic_reports (remote)) continue; if (!fwupd_client_modify_remote (priv->client, remote_id, "AutomaticReports", "true", NULL, error)) return FALSE; } break; default: break; } return TRUE; } 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; gboolean all_automatic = FALSE; /* 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); gboolean remote_automatic; 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)); remote_automatic = fwupd_remote_get_automatic_reports (remote); g_debug ("%s is %d", fwupd_remote_get_title (remote), remote_automatic); if (remote_automatic && !all_automatic) all_automatic = TRUE; if (!remote_automatic && all_automatic) { all_automatic = FALSE; break; } } /* 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 (!fu_util_filter_device (priv, dev)) continue; 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; } g_debug ("All automatic: %d", all_automatic); /* show the success and failures */ if (!priv->assume_yes && !all_automatic) { /* 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):\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_maybe_enable_automatic (priv, remotes, error)) return FALSE; } /* success */ return fu_util_report_history (priv, NULL, error); } 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_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 (!fu_util_filter_device (priv, dev_tmp)) continue; if (!priv->show_all_devices && !fu_util_is_interesting_device (dev_tmp)) continue; if (fwupd_device_get_parent (dev_tmp) == dev) { FwupdRelease *rel = fwupd_device_get_release_default (dev_tmp); GNode *child = g_node_append_data (root, dev_tmp); if (rel != NULL) g_node_append_data (child, rel); fu_util_build_device_tree (priv, child, devs, dev_tmp); } } } static gchar * fu_util_get_tree_title (FuUtilPrivate *priv) { return g_strdup (fwupd_client_get_host_product (priv->client)); } static gboolean fu_util_get_devices (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new (NULL); g_autoptr(GPtrArray) devs = NULL; g_autofree gchar *title = fu_util_get_tree_title (priv); /* 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); fu_util_print_tree (root, title); /* 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 */ if (g_file_test (perhapsfn, G_FILE_TEST_EXISTS)) return g_strdup (perhapsfn); 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) { /* TRANSLATORS: success message */ g_print ("%s\n", _("Successfully installed firmware")); return; } /* TRANSLATORS: success message */ g_print ("%s: %s\n", _("Successfully installed firmware"), 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; g_autoptr(GNode) root = g_node_new (NULL); g_autofree gchar *title = fu_util_get_tree_title (priv); /* check args */ if (g_strv_length (values) != 1) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* implied, important for get-details on a device not in your system */ priv->show_all_devices = TRUE; array = fwupd_client_get_details (priv->client, values[0], NULL, error); if (array == NULL) return FALSE; fu_util_build_device_tree (priv, root, array, NULL); fu_util_print_tree (root, title); 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_remote (FuUtilPrivate *priv, const gchar *remote_id, GPtrArray *devices, GError **error) { JsonNode *json_root; JsonObject *json_object; const gchar *server_msg = NULL; guint status_code; const gchar *report_uri; g_autofree gchar *data = NULL; g_autofree gchar *sig = NULL; g_autoptr(JsonParser) json_parser = NULL; g_autoptr(SoupMessage) msg = NULL; g_autoptr(FwupdRemote) remote = 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; } remote = fwupd_client_get_remote_by_id (priv->client, remote_id, NULL, error); if (remote == NULL) return FALSE; report_uri = fwupd_remote_get_report_uri (remote); /* ask for permission */ if (!priv->assume_yes && !fwupd_remote_get_automatic_reports (remote)) { 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) ids = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) remotes = NULL; g_autoptr(GString) str = g_string_new (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 (!fu_util_filter_device (priv, dev)) continue; 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_id); if (devices_tmp == NULL) { devices_tmp = g_ptr_array_new (); g_hash_table_insert (report_map, g_strdup (remote_id), devices_tmp); } g_debug ("using %s for %s", remote_id, 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 */ ids = g_hash_table_get_keys (report_map); for (GList *l = ids; l != NULL; l = l->next) { const gchar *id = l->data; GPtrArray *devices_tmp = g_hash_table_lookup (report_map, id); if (!fu_util_report_history_for_remote (priv, id, 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; } /* TRANSLATORS: success message -- where the user has uploaded * success and/or failure reports to the remote server */ g_string_append_printf (str, ngettext ("Successfully uploaded %u report", "Successfully uploaded %u reports", g_hash_table_size (report_map)), g_hash_table_size (report_map)); g_print ("%s\n", str->str); return TRUE; } static gboolean fu_util_get_history (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_autoptr(GNode) root = g_node_new (NULL); g_autofree gchar *title = fu_util_get_tree_title (priv); /* 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++) { g_autoptr(GPtrArray) rels = NULL; FwupdDevice *dev = g_ptr_array_index (devices, i); FwupdRelease *rel; const gchar *remote; GNode *child; g_autoptr(GError) error_local = NULL; if (!fu_util_filter_device (priv, dev)) continue; child = g_node_append_data (root, dev); rel = fwupd_device_get_release_default (dev); if (rel == NULL) continue; remote = fwupd_release_get_remote_id (rel); /* doesn't actually map to remote */ if (remote == NULL) { g_node_append_data (child, rel); continue; } /* try to lookup releases from client */ rels = fwupd_client_get_releases (priv->client, fwupd_device_get_id (dev), NULL, &error_local); if (rels == NULL) { g_debug ("failed to get releases for %s: %s", fwupd_device_get_id (dev), error_local->message); g_node_append_data (child, rel); continue; } /* map to a release in client */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel2 = g_ptr_array_index (rels, j); if (g_strcmp0 (remote, fwupd_release_get_remote_id (rel2)) != 0) continue; if (g_strcmp0 (fwupd_release_get_version (rel), fwupd_release_get_version (rel2)) != 0) continue; g_node_append_data (child, g_object_ref (rel2)); rel = NULL; break; } /* didn't match anything */ if (rels->len == 0 || rel != NULL) { g_node_append_data (child, rel); continue; } } fu_util_print_tree (root, title); 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 (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; priv->filter_include |= FWUPD_DEVICE_FLAG_CAN_VERIFY; dev = fu_util_get_device_or_prompt (priv, values, error); if (dev == NULL) return FALSE; if (!fwupd_client_verify_update (priv->client, fwupd_device_get_id (dev), NULL, error)) { g_prefix_error (error, "failed to verify update %s: ", fu_device_get_name (dev)); return FALSE; } /* TRANSLATORS: success message when user refreshes device checksums */ g_print ("%s\n", _("Successfully updated device checksums")); return TRUE; } 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_check_oldest_remote (FuUtilPrivate *priv, guint64 *age_oldest, GError **error) { g_autoptr(GPtrArray) remotes = NULL; /* 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); } return TRUE; } static gboolean fu_util_download_metadata (FuUtilPrivate *priv, GError **error) { gboolean download_remote_enabled = FALSE; guint devices_supported_cnt = 0; g_autoptr(GPtrArray) devs = NULL; g_autoptr(GPtrArray) remotes = NULL; g_autoptr(GString) str = g_string_new (NULL); /* metadata refreshed recently */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { guint64 age_oldest = 0; const guint64 age_limit_hours = 24; if (!fu_util_check_oldest_remote (priv, &age_oldest, error)) return FALSE; if (age_oldest < 60 * 60 * age_limit_hours) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: error message for a user who ran fwupdmgr refresh recently %1 is an already translated timestamp such as 6 hours or 15 seconds */ "Firmware metadata last refresh: %s ago. " "Use --force to refresh again.", fu_util_time_to_str (age_oldest)); return FALSE; } } 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) { /* we don't want to ask anything */ if (priv->no_metadata_check) { g_debug ("skipping metadata check"); return TRUE; } if (!fu_util_download_metadata_enable_lvfs (priv, error)) return FALSE; } /* 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++) { FwupdDevice *dev = g_ptr_array_index (devs, i); if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) devices_supported_cnt++; } /* TRANSLATORS: success message -- where 'metadata' is information * about available firmware on the remote server */ g_string_append (str, _("Successfully downloaded new metadata: ")); /* TRANSLATORS: how many local devices can expect updates now */ g_string_append_printf (str, ngettext ("%u local device supported", "%u local devices supported", devices_supported_cnt), devices_supported_cnt); g_print ("%s\n", str->str); 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 */ if (!fwupd_client_update_metadata (priv->client, values[2], values[0], values[1], NULL, error)) return FALSE; /* TRANSLATORS: success message -- the user can do this by-hand too */ g_print ("%s\n", _("Successfully refreshed metadata manually")); return TRUE; } 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 = fu_util_device_to_string (rel, 0); 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; g_autoptr(GNode) root = g_node_new (NULL); g_autofree gchar *title = fu_util_get_tree_title (priv); priv->filter_include |= FWUPD_DEVICE_FLAG_SUPPORTED; 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; if (rels->len == 0) { /* TRANSLATORS: no repositories to download from */ g_print ("%s\n", _("No releases available")); return TRUE; } for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index (rels, i); g_node_append_data (root, rel); } fu_util_print_tree (root, title); 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_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 (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; priv->filter_include |= FWUPD_DEVICE_FLAG_CAN_VERIFY; dev = fu_util_get_device_or_prompt (priv, values, error); if (dev == NULL) return FALSE; if (!fwupd_client_verify (priv->client, fwupd_device_get_id (dev), NULL, error)) { g_prefix_error (error, "failed to verify %s: ", fu_device_get_name (dev)); return FALSE; } /* TRANSLATORS: success message when user verified device checksums */ g_print ("%s\n", _("Successfully verified device checksums")); return TRUE; } static gboolean fu_util_unlock (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; priv->filter_include |= FWUPD_DEVICE_FLAG_LOCKED; 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) { 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; } if (!fu_util_check_oldest_remote (priv, &age_oldest, error)) return FALSE; /* 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 gboolean fu_util_get_updates (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; gboolean supported = FALSE; g_autoptr(GNode) root = g_node_new (NULL); g_autofree gchar *title = fu_util_get_tree_title (priv); /* 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); g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; GNode *child; /* not going to have results, so save a D-Bus round-trip */ if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE)) continue; if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { /* TRANSLATORS: message letting the user know no device upgrade available due to missing on LVFS * %1 is the device name */ g_autofree gchar *tmp = g_strdup_printf (_("• %s has no available firmware updates"), fwupd_device_get_name (dev)); g_printerr ("%s\n", tmp); continue; } if (!fu_util_filter_device (priv, dev)) continue; supported = TRUE; /* 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) { /* TRANSLATORS: message letting the user know no device upgrade available * %1 is the device name */ g_autofree gchar *tmp = g_strdup_printf (_("• %s has the latest available firmware version"), fwupd_device_get_name (dev)); g_printerr ("%s\n", tmp); /* discard the actual reason from user, but leave for debugging */ g_debug ("%s", error_local->message); continue; } child = g_node_append_data (root, dev); /* add all releases */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel = g_ptr_array_index (rels, j); g_node_append_data (child, g_object_ref (rel)); } } if (g_node_n_nodes (root, G_TRAVERSE_ALL) > 1) fu_util_print_tree (root, title); /* nag? */ if (!fu_util_perhaps_show_unreported (priv, error)) return FALSE; /* no devices supported by LVFS or all are filtered */ if (!supported) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No updatable devices"); return FALSE; } /* success */ return TRUE; } static gboolean fu_util_get_remotes (FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new (NULL); g_autoptr(GPtrArray) remotes = NULL; g_autofree gchar *title = fu_util_get_tree_title (priv); remotes = fwupd_client_get_remotes (priv->client, NULL, error); if (remotes == NULL) return FALSE; if (remotes->len == 0) { /* TRANSLATORS: no repositories to download from */ g_print ("%s\n", _("No remotes available")); return TRUE; } for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote_tmp = g_ptr_array_index (remotes, i); g_node_append_data (root, remote_tmp); } fu_util_print_tree (root, title); 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; if (!priv->no_safety_check && !priv->assume_yes) { if (!fu_util_prompt_warning (dev, fu_util_get_tree_title (priv), error)) return FALSE; } /* 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_maybe_send_reports (FuUtilPrivate *priv, const gchar *remote_id, GError **error) { g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error_local = NULL; if (remote_id == NULL) { g_debug ("not sending reports, no remote"); return TRUE; } remote = fwupd_client_get_remote_by_id (priv->client, remote_id, NULL, error); if (remote == NULL) return FALSE; if (fwupd_remote_get_automatic_reports (remote)) { if (!fu_util_report_history (priv, NULL, &error_local)) if (!g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) g_warning ("%s", error_local->message); } return TRUE; } static gboolean fu_util_update_all (FuUtilPrivate *priv, GError **error) { g_autoptr(GPtrArray) devices = NULL; gboolean supported = FALSE; /* 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; const gchar *remote_id; g_autofree gchar *upgrade_str = NULL; 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_UPDATABLE)) continue; if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { /* TRANSLATORS: message letting the user know no device upgrade available due to missing on LVFS * %1 is the device name */ g_autofree gchar *tmp = g_strdup_printf (_("• %s has no available firmware updates"), fwupd_device_get_name (dev)); g_printerr ("%s\n", tmp); continue; } if (!fu_util_filter_device (priv, dev)) continue; supported = TRUE; /* 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) { /* TRANSLATORS: message letting the user know no device upgrade available * %1 is the device name */ g_autofree gchar *tmp = g_strdup_printf (_("• %s has the latest available firmware version"), fwupd_device_get_name (dev)); g_printerr ("%s\n", tmp); /* discard the actual reason from user, but leave for debugging */ g_debug ("%s", error_local->message); continue; } rel = g_ptr_array_index (rels, 0); /* TRANSLATORS: message letting the user know an upgrade is available * %1 is the device name and %2 and %3 are version strings */ upgrade_str = g_strdup_printf (_("Upgrade available for %s from %s to %s"), fwupd_device_get_name (dev), fwupd_device_get_version (dev), fwupd_release_get_version (rel)); g_print ("%s\n", upgrade_str); if (!fu_util_update_device_with_release (priv, dev, rel, error)) return FALSE; fu_util_display_current_message (priv); /* send report if we're supposed to */ remote_id = fwupd_release_get_remote_id (rel); if (!fu_util_maybe_send_reports (priv, remote_id, error)) return FALSE; } /* no devices supported by LVFS or all are filtered */ if (!supported) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No updatable devices"); return FALSE; } /* 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; const gchar *remote_id; 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); /* send report if we're supposed to */ remote_id = fwupd_release_get_remote_id (rel); if (!fu_util_maybe_send_reports (priv, remote_id, error)) return FALSE; /* 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 (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "--allow-older is not supported for this command"); return FALSE; } if (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "--allow-reinstall is not supported for this command"); return FALSE; } 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; } if (!fu_util_modify_remote (priv, values[0], values[1], values[2], error)) return FALSE; /* TRANSLATORS: success message for a per-remote setting change */ g_print ("%s\n", _("Successfully modified remote")); return TRUE; } 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; } if (!fu_util_modify_remote (priv, values[0], "Enabled", "true", error)) return FALSE; /* TRANSLATORS: success message */ g_print ("%s\n", _("Successfully enabled remote")); return TRUE; } 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; } if (!fu_util_modify_remote (priv, values[0], "Enabled", "false", error)) return FALSE; /* TRANSLATORS: success message */ g_print ("%s\n", _("Successfully disabled remote")); return TRUE; } static gboolean fu_util_downgrade (FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *remote_id; g_autoptr(FwupdDevice) dev = NULL; g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GPtrArray) rels = NULL; if (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "--allow-reinstall is not supported for this command"); return FALSE; } priv->filter_include |= FWUPD_DEVICE_FLAG_SUPPORTED; 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) { /* TRANSLATORS: message letting the user know no device downgrade available * %1 is the device name */ g_autofree gchar *downgrade_str = g_strdup_printf (_("No downgrades for %s"), fwupd_device_get_name (dev)); g_prefix_error (error, "%s: ", downgrade_str); 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; if (!fu_util_update_device_with_release (priv, dev, rel, error)) return FALSE; /* TRANSLATORS: success message where we made the firmware on the * device older than it was before */ g_print ("%s\n", _("Successfully downgraded device")); /* send report if we're supposed to */ remote_id = fwupd_release_get_remote_id (rel); if (!fu_util_maybe_send_reports (priv, remote_id, error)) return FALSE; return TRUE; } static gboolean fu_util_reinstall (FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *remote_id; g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GPtrArray) rels = NULL; g_autoptr(FwupdDevice) dev = NULL; priv->filter_include |= FWUPD_DEVICE_FLAG_SUPPORTED; dev = fu_util_get_device_or_prompt (priv, values, error); if (dev == NULL) return FALSE; /* try to lookup/match release from client */ rels = fwupd_client_get_releases (priv->client, fwupd_device_get_id (dev), NULL, error); if (rels == NULL) return FALSE; for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel_tmp = g_ptr_array_index (rels, j); if (fu_common_vercmp_full (fwupd_release_get_version (rel_tmp), fu_device_get_version (dev), fwupd_device_get_version_format (dev)) == 0) { rel = g_object_ref (rel_tmp); break; } } if (rel == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unable to locate release for %s version %s", fu_device_get_name (dev), fu_device_get_version (dev)); return FALSE; } /* update the console if composite devices are also updated */ priv->current_operation = FU_UTIL_OPERATION_INSTALL; g_signal_connect (priv->client, "device-changed", G_CALLBACK (fu_util_update_device_changed_cb), priv); priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; if (!fu_util_update_device_with_release (priv, dev, rel, error)) return FALSE; fu_util_display_current_message (priv); /* send report if we're supposed to */ remote_id = fwupd_release_get_remote_id (rel); if (!fu_util_maybe_send_reports (priv, remote_id, error)) return FALSE; /* 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_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_util_filter_device (priv, device)) continue; 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; } /* TRANSLATORS: success message -- where activation is making the new * firmware take effect, usually after updating offline */ g_print ("%s\n", _("Successfully activated all devices")); 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 /* TRANSLATORS: success message -- a per-system setting value */ g_print ("%s\n", _("Successfully modified configuration value")); return TRUE; } static void fu_util_ignore_cb (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { } #ifdef HAVE_GIO_UNIX 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; } #endif 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 = fu_util_get_client_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/fwupd/fwupd/wiki/PolicyKit-files-are-missing"); return FALSE; } return TRUE; } static void fu_util_display_help (FuUtilPrivate *priv) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help (priv->context, TRUE, NULL); g_printerr ("%s\n", tmp); } #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; g_autofree gchar *filter = NULL; g_autofree gchar *log = 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 reinstalling 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-safety-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_safety_check, /* TRANSLATORS: command line option */ _("Do not perform device safety checks"), 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 }, { "disable-ssl-strict", '\0', 0, G_OPTION_ARG_NONE, &priv->disable_ssl_strict, /* TRANSLATORS: command line option */ _("Ignore SSL strict checks when downloading files"), NULL }, { "filter", '\0', 0, G_OPTION_ARG_STRING, &filter, /* TRANSLATORS: command line option */ _("Filter with a set of device flags using a ~ prefix to " "exclude, e.g. 'internal,~needs-reboot'"), NULL }, { NULL} }; setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, FWUPD_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,get-topology", NULL, /* TRANSLATORS: command description */ _("Get all devices that support firmware updates"), fu_util_get_devices); 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 [DEVICE-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,get-upgrades", NULL, /* TRANSLATORS: command description */ _("Gets the list of updates for connected hardware"), fu_util_get_updates); fu_util_cmd_array_add (cmd_array, "update,upgrade", 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 */ _("Checks cryptographic hash matches 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 cryptographic hash 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); fu_util_cmd_array_add (cmd_array, "reinstall", "[DEVICE-ID]", /* TRANSLATORS: command description */ _("Reinstall current firmware on the device."), fu_util_reinstall); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new (); #ifdef HAVE_GIO_UNIX g_unix_signal_add_full (G_PRIORITY_DEFAULT, SIGINT, fu_util_sigint_cb, priv, NULL); #endif /* sort by command name */ fu_util_cmd_array_sort (cmd_array); /* 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; } /* allow disabling SSL strict mode for broken corporate proxies */ if (priv->disable_ssl_strict) { /* TRANSLATORS: try to help */ g_printerr ("%s\n", _("WARNING: Ignoring SSL strict checks, " "to do this automatically in the future " "export DISABLE_SSL_STRICT in your environment")); g_setenv ("DISABLE_SSL_STRICT", "1", TRUE); } /* 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; priv->no_safety_check = TRUE; fu_progressbar_set_interactive (priv->progressbar, FALSE); } /* parse filter flags */ if (filter != NULL) { if (!fu_util_parse_filter_flags (filter, &priv->filter_include, &priv->filter_exclude, &error)) { /* TRANSLATORS: the user didn't read the man page */ g_print ("%s: %s\n", _("Failed to parse flags for --filter"), error->message); return EXIT_FAILURE; } } /* set verbose? */ if (verbose) { g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); g_setenv ("FWUPD_VERBOSE", "1", 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 ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && !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 && !fwupd_client_get_daemon_interactive (priv->client) && !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) { ret = EXIT_FAILURE; g_printerr ("%s\n", error->message); if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) fu_util_display_help (priv); else if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) ret = EXIT_NOTHING_TO_DO; } else { ret = EXIT_SUCCESS; } return ret; } fwupd-1.3.9/src/fuzzing/000077500000000000000000000000001362775233600151525ustar00rootroot00000000000000fwupd-1.3.9/src/fuzzing/firmware/000077500000000000000000000000001362775233600167665ustar00rootroot00000000000000fwupd-1.3.9/src/fuzzing/firmware/example-addr32.srec000066400000000000000000000000551362775233600223540ustar00rootroot00000000000000S00400006299 S108000031202D6E0A01 S5030001FB fwupd-1.3.9/src/fuzzing/firmware/example.srec000066400000000000000000000000611362775233600212740ustar00rootroot00000000000000S0040000619A S30A0000000031202D6E0AFF S5030001FB fwupd-1.3.9/src/fuzzing/firmware/firmware.hex000066400000000000000000000006001362775233600213040ustar00rootroot00000000000000:044000003DEF20F080 :10400800FACF01F0FBCF02F0E9CF03F0EACF04F0DA :10401800E1CF05F0E2CF06F0D9CF07F0DACF08F00C :10402800F3CF09F0F4CF0AF0F6CF0BF0F7CF0CF08E :10403800F8CF0DF0F5CF0EF00EC0F5FF0DC0F8FF6C :104048000CC0F7FF0BC0F6FF0AC0F4FF09C0F3FF6E :1040580008C0DAFF07C0D9FF06C0E2FF05C0E1FFCC :1040680004C0EAFF03C0E9FF02C0FBFF01C0FAFF7A :1040780011003FEF20F0000142EF20F03DEF20F06B :00000001FF fwupd-1.3.9/src/fuzzing/meson.build000066400000000000000000000010561362775233600173160ustar00rootroot00000000000000run_target('fuzz-smbios', command: [ join_paths(meson.source_root(), 'contrib/afl-fuzz.py'), '-i', join_paths(meson.current_source_dir(), 'smbios'), '-o', join_paths(meson.current_build_dir(), '..', 'findings-smbios'), '--command', 'smbios-dump', fwupdtool, ], ) run_target('fuzz-firmware', command: [ join_paths(meson.source_root(), 'contrib/afl-fuzz.py'), '-i', join_paths(meson.current_source_dir(), 'firmware'), '-o', join_paths(meson.current_build_dir(), '..', 'findings-firmware'), fwupd_firmware_dump, ], ) fwupd-1.3.9/src/fuzzing/smbios/000077500000000000000000000000001362775233600164465ustar00rootroot00000000000000fwupd-1.3.9/src/fuzzing/smbios/DMI-MicroServer.bin000066400000000000000000000021711362775233600220100ustar00rootroot00000000000000ڋw3HPO41 09/30/2010NKÙHPProLiant MicroServer CN7107P23C633724-421 HP *cACPU 1AMD AMD Athlon(tm) II Neo N36L Dual-Core Processor To Be Filled By O.E.M.To Be Filled By O.E.M.To Be Filled By O.E.M.L1-CacheL2-CacheL3-Cache J13Front USB J14Front USB J15Rear USB  J15LAN 1 J16VGA " J23eSATA " J8 - MINISAS J20 -TPM J22 -FAN " J24 - SATA ODD J33 - Internal USB   PCIE1-J5  PCIE2-J6H@ 5DIMM0BANK0Manufacturer00SerNum00AssetTagNum0ModulePartNumber00H@ 5DIMM1BANK1Manufacturer01SerNum01AssetTagNum1ModulePartNumber01 & 00/00/000023/08/2010 KÙ !HP"fwupd-1.3.9/src/fuzzing/smbios/DMI-T440s.bin000066400000000000000000000047331362775233600203740ustar00rootroot00000000000000*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 ۊԑ H|/3LENOVO (uu#/ i"_8 ?4LENOVO 5LENOVO 6LENOVO MS 7LENOVO 8LENOVO 9":6;TP<LENOVO GJHT25WW11/07/2013+=LENOVO /fwupd-1.3.9/src/fuzzing/smbios/DMI-xps13.bin000066400000000000000000000133021362775233600205240ustar00rootroot00000000000000? Dell Inc.1.10.101/22/2019DELLYD5O562NULLDell Inc.XPS 13 93500704GYD5562  Dell Inc.07TYC2A00/GYD5562/CN129635AQ01F4/ Dell Inc.GYD5562Laptop JUSB1USB1 JUSB2USB2 JSD1Cardreader JHP1Audio Jack JTypeCUSB3 JeDP1 - eDP JNGFF1 - WLAN/BT/Wigig CONN JNGFF2 - HDD  JKBTP1 - Keyboard JFAN1 - CPU FAN JSPK1 - Speaker JXDP1 - CPU XDP Port JAPS1 - Automatic Power JLPDE1 - 80 PORT JDEG1 - Debug PORT JRTC1 - RTC   J6B2  J6B1  J6D1  J7B1  J8B4 "Intel HD Graphics" Dell System1[0704]3[1.0]12[www.dell.com]14[1]15[0] To Be Filled By O.E.M.#jl " #" $LM78-1$&$($*) 0 Onboard IGD) 1 Onboard LAN) 2 Onboard 13943_SIDLLEwPLyhIgmg0704_SIDAvf90w7wKZiZDELL4@@ L1 Cache5@@ L1 Cache6 L2 Cache7  L3 Cache08dA567U3E1Intel(R) CorporationIntel(R) Core(TM) i5-6200U CPU @ 2.30GHzTo Be Filled By O.E.M.To Be Filled By O.E.M.To Be Filled By O.E.M.9(:9@@KKSystem Board MemoryBANK 0SK Hynix121612179876543210H9CCNNNBJTMLAR-NUD(;9@@KKSystem Board MemoryBANK 1SK Hynix121212129876543210H9CCNNNBJTMLAR-NUD<9 =Reference Code - ACPI#>?:<#?@;<~ Dell Inc.GYD5562Docking Stationy+G Sys. Battery BaySMP09/11/2015089CDELL JHXPY531.0LiPCN0JHXPY7176959BI05DA00 epCPU Fang dCPU Probeg dTrue Ambientg dMemory Modulec dOther Probe T= < ; R BXW !"P&D  2015102920151110 x  "Intel Corp.""2089"US-101Proprietaryڲ@   ""##(())**++,,@@AABBCCPPUU\\]]eeff}}ڲ@  ++,,--..558899@@AADDEEFFGGJJKKLLMMRRSSuuvvڲ@--..22335566JJKKLLddeeffgghhiillmmnnڲ@  %%&&))**ڲ@++,,8899AABBCCFFGGHHIIJJMMNNOOPPWWXX[[\\]]^^__``aabbddeeffggiijjkkllmmnnttuuxxڲ@yy  ++11223388ڲ@99::;;@@AABBCCDDEEFFGGQQRRaabbJ@J@K@K@ Mڲ@ ""0022ڲ@:: "02Y($AMT@)1 H <"vPro*Reference Code - CPUuCode VersionTXT ACM version+  < Reference Code - ME 11.0MEBx versionME Firmware VersionConsumer SKUD, !!>4 > 4Reference Code - SKL PCHPCH-CRID StatusDisabledPCH-CRID Original ValuePCH-CRID New ValueOPROM - RST - RAIDSKL PCH H Bx Hsio VersionSKL PCH H Dx Hsio VersionSKL PCH LP Bx Hsio VersionSKL PCH LP Cx Hsio Version6-Reference Code - SA - System AgentReference Code - MRCSA - PCIe VersionSA-CRID StatusDisabledSA-CRID Original ValueSA-CRID New ValueOPROM - VBIOS`.  B Lan Phy VersionSensor Firmware VersionDebug Mode StatusEnabled Performance Mode StatusDisabledDebug Use USB(Disabled:Serial)DisabledICC Overclocking VersionUNDI VersionEC FW VersionGOP VersionBIOS Guard VersionBase EC FW VersionEC-EC Protocol VersionRoyal Park VersionBP1.2.2.0_RP03<==*+,-.Firmware Version Info>$MEIQ?Ef @ GMEI1MEI2MEI3 @en|US|iso8859-1Afwupd-1.3.9/src/fwupd.gresource.xml000066400000000000000000000003251362775233600173220ustar00rootroot00000000000000 org.freedesktop.fwupd.xml fwupd-1.3.9/src/meson.build000066400000000000000000000151261362775233600156250ustar00rootroot00000000000000if build_daemon install_data(['org.freedesktop.fwupd.xml'], install_dir : join_paths(datadir, 'dbus-1', 'interfaces') ) endif keyring_deps = [] keyring_src = [] systemd_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 endif if get_option('systemd') systemd_src += 'fu-systemd.c' endif if build_daemon fwupdmgr = executable( 'fwupdmgr', sources : [ 'fu-util.c', 'fu-history.c', 'fu-progressbar.c', 'fu-util-common.c', systemd_src ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ libxmlb, giounix, gudev, gusb, soup, sqlite, libarchive, libjsonglib, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : bindir ) endif if get_option('agent') fwupdagent = executable( 'fwupdagent', sources : [ 'fu-agent.c', 'fu-util-common.c', systemd_src, ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ libxmlb, giounix, gudev, gusb, soup, libjsonglib, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : bindir ) endif if get_option('systemd') fwupdoffline = executable( 'fwupdoffline', sources : [ 'fu-history.c', 'fu-offline.c', 'fu-util-common.c', systemd_src ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ giounix, gudev, gusb, libxmlb, soup, sqlite, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : join_paths(libexecdir, 'fwupd') ) endif resources_src = gnome.compile_resources( 'fwupd-resources', 'fwupd.gresource.xml', source_dir : '.', c_name : 'fu' ) fwupdtool = executable( 'fwupdtool', resources_src, fu_hash, export_dynamic : true, sources : [ 'fu-tool.c', keyring_src, 'fu-config.c', 'fu-debug.c', 'fu-device-list.c', 'fu-engine.c', 'fu-engine-helper.c', 'fu-history.c', 'fu-idle.c', 'fu-install-task.c', 'fu-keyring.c', 'fu-keyring-result.c', 'fu-keyring-utils.c', 'fu-plugin-list.c', 'fu-progressbar.c', 'fu-remote-list.c', 'fu-util-common.c', systemd_src ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ keyring_deps, libxmlb, libgcab, giounix, gmodule, gudev, gusb, soup, sqlite, valgrind, libarchive, libjsonglib, ], link_with : [ fwupd, fwupdplugin ], install : true, install_dir : bindir ) if build_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', 'Firmware update manager client utility', '--manual', 'User Commands', '--version-string', fwupd_version, ], install : true, install_dir : join_paths(mandir, 'man1'), ) custom_target('fwupdagent-man', input : fwupdagent, output : 'fwupdagent.1', command : [ help2man, '@INPUT@', '--no-info', '--output', '@OUTPUT@', '--name', 'Firmware updating agent', '--manual', 'User Commands', '--version-string', fwupd_version, ], install : true, install_dir : join_paths(mandir, 'man1'), ) endif if get_option('man') custom_target('fwupdtool-man', input : fwupdtool, output : 'fwupdtool.1', command : [ help2man, '@INPUT@', '--no-info', '--output', '@OUTPUT@', '--name', 'Standalone firmware update utility', '--manual', 'User Commands', '--version-string', fwupd_version, ], install : true, install_dir : join_paths(mandir, 'man1'), ) endif if build_daemon executable( 'fwupd', resources_src, fu_hash, sources : [ keyring_src, 'fu-config.c', 'fu-debug.c', 'fu-device-list.c', 'fu-engine.c', 'fu-engine-helper.c', 'fu-history.c', 'fu-idle.c', 'fu-install-task.c', 'fu-keyring.c', 'fu-keyring-result.c', 'fu-keyring-utils.c', 'fu-main.c', 'fu-plugin-list.c', 'fu-remote-list.c', systemd_src ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ keyring_deps, libxmlb, libgcab, giounix, gmodule, gudev, gusb, polkit, soup, sqlite, valgrind, libarchive, libjsonglib, ], link_with : [ fwupd, fwupdplugin ], 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, test_deps, fu_hash, sources : [ keyring_src, 'fu-config.c', 'fu-device-list.c', 'fu-engine.c', 'fu-engine-helper.c', 'fu-history.c', 'fu-idle.c', 'fu-install-task.c', 'fu-keyring.c', 'fu-keyring-result.c', 'fu-keyring-utils.c', 'fu-plugin-list.c', 'fu-progressbar.c', 'fu-remote-list.c', 'fu-self-test.c', systemd_src ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ keyring_deps, libxmlb, libgcab, giounix, gmodule, gudev, gusb, soup, sqlite, valgrind, libarchive, libjsonglib, ], link_with : [ fwupd, fwupdplugin ], c_args : [ '-DTESTDATADIR_SRC="' + testdatadir_src + '"', '-DTESTDATADIR_DST="' + testdatadir_dst + '"', '-DPLUGINBUILDDIR="' + pluginbuilddir + '"', ], ) test('fu-self-test', e, is_parallel:false, timeout:180) endif if get_option('tests') # for fuzzing fwupd_firmware_dump = executable( 'fwupd-firmware-dump', sources : [ 'fu-firmware-dump.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ gio, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) endif if get_option('tests') subdir('fuzzing') endif fwupd-1.3.9/src/org.freedesktop.fwupd.xml000066400000000000000000000510341362775233600204300ustar00rootroot00000000000000 The interface used for querying firmware for the system. The daemon version. The product name string for the host. The machine ID for the host. If the daemon has been tainted with a 3rd party plugin. If the daemon is running on an interactive terminal. 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.3.9/subprojects/000077500000000000000000000000001362775233600152325ustar00rootroot00000000000000fwupd-1.3.9/subprojects/.gitignore000066400000000000000000000000331362775233600172160ustar00rootroot00000000000000gusb gcab flashrom libxmlb fwupd-1.3.9/subprojects/flashrom.wrap000066400000000000000000000001331362775233600177350ustar00rootroot00000000000000[wrap-git] directory = flashrom url = https://github.com/flashrom/flashrom revision = v1.2 fwupd-1.3.9/subprojects/gcab.wrap000066400000000000000000000001761362775233600170250ustar00rootroot00000000000000[wrap-git] directory = gcab url = https://gitlab.gnome.org/GNOME/gcab.git revision = b55268ac1020cd6c033acb52f2e6ae984bf5c9fd fwupd-1.3.9/subprojects/gusb.wrap000066400000000000000000000001751362775233600170700ustar00rootroot00000000000000[wrap-git] directory = gusb url = https://github.com/hughsie/libgusb.git revision = 43b327a1e213aff00833842c455a796a068077cd fwupd-1.3.9/subprojects/libxmlb.wrap000066400000000000000000000001361362775233600175560ustar00rootroot00000000000000[wrap-git] directory = libxmlb url = https://github.com/hughsie/libxmlb.git revision = 0.1.14