[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n"
  },
  {
    "path": "CHANGELOG.txt",
    "content": "CHANGELOG\n\nVersion 9.20\n-----------\n\n* TOP Bridge now uniformly uses SAPI requests.\n* MQTT connection adds max_inflight_messages and session_expiry_interval parameters.\n* Added control tasks for system restart, file download, command execution, software update, etc.\n* Added OpenAI task executor supporting semantic task execution.\n* Integrated MCP extensions (API path /mcp/)\n* Updated built-in Frida version.\n\nBREAKING CHANGES\n=================================================================\nVersion 9.20 includes some breaking changes, which only affect users who utilize hub and hub-bridge. The main issue is an adjustment made to the response data format of hub-bridge (this adjustment does not affect the underlying database). This will cause versions prior to 9.15 to fail to connect correctly to the new version of hub-bridge. However, the new version of lamda-server remains compatible with the old version of hub-bridge.\nThe solutions are as follows:\n1. Use the new version of hub and hub-bridge (v3, which supports connecting to both local and remote devices), but all devices must be upgraded to version 9.20 or later.\n2. Continue using the old version of hub and hub-bridge, which can normally connect to all versions around 9.20 and earlier.\n=================================================================\n\nVersion 9.9\n-----------\n\n* Task and event system logic adjustments.\n\nVersion 9.8\n-----------\n\n* Added Python MMKV read/write library.\n* Fixed auto exit of top bridge after removing device from hub.\n* Improved stealth performance.\n\nVersion 9.5\n-----------\n\n* Fixed functional abnormalities on some device models.\n* Fixed illegal instruction issue with Frida.\n\nVersion 9.4\n-----------\n\n* Added support for terminating all running tasks.\n* Optimized memory usage.\n\nVersion 9.3\n-----------\n\n* Other optimizations and fixes.\n\nVersion 9.2\n-----------\n\n* Fixed stack overflow issue on some devices.\n* Other optimizations and fixes.\n\nVersion 9.0\n-----------\n\n* Optimized network performance of the service.\n* Switched MCP transport protocol to streamable-http with support for notifications and progress.\n* Proxy now supports HTTPS and Shadowsocks protocols.\n* Proxy now supports IPv6 and UDP protocols.\n* Added P2P Bridge (peer-to-peer connectivity) support.\n* Built-in distributed task system.\n* Other optimizations and fixes.\n\nVersion 8.45\n-----------\n\n* Updated Frida version.\n* Other compatibility fixes.\n\nVersion 8.44\n-----------\n\n* Optimized underlying Python compatibility.\n* Added interface for playing WAV audio.\n* Disabled H.264 screen projection by default.\n\nVersion 8.40\n-----------\n\n* Fixed incomplete /data mounting issue.\n* Improved stability of enhanced automation.\n\nVersion 8.38\n-----------\n\n* Enhanced automation features.\n* Fixed compatibility issues with some Samsung models.\n* Optimized audio real-time performance.\n\nVersion 8.35\n-----------\n\n* Remote desktop now supports real-time audio streaming.\n* Fixed segment fault in hex_patch.\n\nVersion 8.30\n-----------\n\n* Added binary patching interface.\n* Added support for using child and sibling in Selector.\n* Added support for viewing XML tree layout in remote desktop.\n* Updated Frida to fix some issues.\n\nVersion 8.28\n-----------\n\n* Fixed install_local_file.\n* Fixed Frida reporting ID increment issue.\n* Improved built-in TensorFlow inference performance.\n* Updated some third-party modules.\n\nVersion 8.25\n-----------\n\n* Added hexedit command.\n* Permanently fixed Permission Loophole (maybe).\n* Added on-device AI framework (tflite-runtime).\n* Updated Frida for better stealth.\n\nVersion 8.22\n-----------\n\n* Switched to a new SQLite version.\n* Remote desktop inspector now displays current coordinates and RGB values.\n* Added plugin setup logic.\n\nVersion 8.20\n-----------\n\n* Added official MCP plugin.\n* Improved Frida compatibility.\n* Optimized MCP protocol implementation.\n* Fixed self-recovery logic.\n\nVersion 8.18\n-----------\n\n* Reverted problematic Frida version.\n* Added support for MCP and HTTP extension plugins.\n\nVersion 8.15\n-----------\n\n* Fixed service unavailability issue.\n* Added support for calling exported scripts via jsonrpc.\n* Fixed SSH user directory issue.\n* Updated some submodules.\n\nVersion 8.12\n-----------\n\n* Fixed touch abnormality.\n* Added some utility scripts.\n* Enhanced stability.\n\nVersion 8.10\n-----------\n\n* Optimized self-recovery logic.\n* Optimized touch compatibility.\n\nVersion 8.9\n-----------\n\n* Fixed parsing error.\n\nVersion 8.8\n-----------\n\n* Frida data reporting now supports AMQP.\n* Fixed certificate issue caused by upstream library change in cert.py.\n* Fixed resource release issue on service restart.\n\nVersion 8.5\n-----------\n\n* Optimized clipboard sharing logic.\n* Added Frida script crash logs.\n* Added support for Android 15.\n\nVersion 8.0\n-----------\n\n* Interfaces now fully support multi-instance applications.\n* Remote desktop now supports shared clipboard.\n* Added fix configuration for device models that cannot open apps.\n* Added Yaml Frida script persistence.\n* Fixed compatibility with lower version systems like Android 6.0.\n* Fixed automation-related abnormalities on higher version systems.\n* Removed/renamed some methods.\n* Updated underlying implementation.\n\nVersion 7.90\n-----------\n\n* Persistent scripts now support spawn mode.\n* Added support for output logs from persistent scripts.\n* Fixed dump_window_hierarchy.\n* Fixed Frida instance retrieval logic error.\n\nVersion 7.85\n-----------\n\n* Added support for mDNS service broadcasting.\n* Added support for enumerating all elements selected by a selector.\n* Client now includes an automatic retry mechanism.\n* Fixed Bound comparison logic error.\n* Allowed loading certificates remotely.\n\nVersion 7.80\n-----------\n\n* Optimized real-time screen projection smoothness.\n* Added support for persistent hook scripts.\n* Added Hook RPC support.\n* Added data reporting support.\n\nVersion 7.76\n-----------\n\n* Fixed tool version dependencies.\n* Fixed Python version matching issue.\n* Updated some submodules.\n\nVersion 7.75\n-----------\n\n* Added OCR recognition interface.\n* Added get_application_by_name.\n* Updated some submodules and dependency versions.\n\nVersion 7.73\n-----------\n\n* Fixed white screen issue for some applications.\n\nVersion 7.72\n-----------\n\n* Updated some submodules.\n* Fixed known issues.\n\nVersion 7.71\n-----------\n\n* Fixed Permission Loophole #95.\n* Fixed enumerate_all_pkg_names.\n\nVersion 7.70\n-----------\n\n* Updated some submodules.\n* Fixed known issues.\n\nVersion 7.68\n-----------\n\n* Optimized H.264 real-time screen projection.\n\nVersion 7.67\n-----------\n\n* Removed some invalid program logic.\n* Fixed excessively long auto-recovery time.\n* Other optimizations and fixes.\n\nVersion 7.65\n-----------\n\n* Removed IDA-related tools and interfaces.\n* Fixed startup failure in some cases.\n* Other optimizations and fixes.\n* Added enhanced stealth mode.\n\nVersion 7.60\n-----------\n\n* Optimized image search speed.\n* Added support for area screenshot in remote desktop.\n* Fixed some remote desktop issues.\n\nVersion 7.57\n-----------\n\n* Added client interfaces for feature and template-based image search.\n* Other optimizations and fixes.\n\nVersion 7.55\n-----------\n\n* Fixed abnormal display on screen rotation.\n* Fixed disconnection on initial remote desktop connection.\n* Fixed element existence check.\n* Added Meta key definition.\n* Other optimizations and fixes.\n\nVersion 7.52\n-----------\n\n* Fixed Magisk version Leidian emulator compatibility.\n* Fixed service not exiting properly.\n\nVersion 7.50\n-----------\n\n* Permanently fixed Nox emulator compatibility.\n* Fixed zombie process caused by logic error.\n* New network subscription service, enabling networking without Frp/OpenVPN.\n* Fixed multi-resolution system issue.\n* Optimized system certificate injection logic for Android 13/14.\n* Added support for multi-instance applications (user).\n* OpenVPN now supports IPv6.\n\nVersion 7.30\n-----------\n\n* Fixed Leidian/Nox emulator compatibility.\n* Minor adjustments.\n\nVersion 7.28\n-----------\n\n* Added show_toast interface.\n* Built-in proxy now supports DNS traffic.\n* startmitm now supports DNS via upstream proxy.\n* Fixed Android 10+ Frida spawn.\n\nVersion 7.25\n-----------\n\n* Fixed scheduled task execution failure.\n* Fixed startup failure from Termux.\n* Updated built-in Frida version.\n\nVersion 7.22\n-----------\n\n* Added automatic system time synchronization.\n* Updated some built-in modules.\n* Minor fixes.\n\nVersion 7.20\n-----------\n\n* Reduced detection likelihood.\n* Optimized lock mechanism, allowing locking for all API resources.\n* Fixed emulator compatibility.\n* Other minor modifications and fixes.\n\nVersion 7.15\n-----------\n\n* Added support for Android 14 (SDK 34).\n* Fixed monitor registration exception.\n* Improved remote desktop compatibility (theoretically supports all devices).\n* Fixed scroll_from_bottom_to_top_to_end anomaly. Thanks ThanhPham.\n* Fixed code errors in drag_to, long_click.\n* Built-in OpenVPN now supports user/password login.\n* Remote desktop now supports up to 60 FPS.\n* Updated DISCLAIMER.TXT.\n* Other minor modifications and fixes.\n\nVersion 5.6\n-----------\n\n* Fixed incomplete layout export. Thanks ThanhPham.\n\nVersion 5.5\n-----------\n\n* Fixed file corruption with adb push.\n* Added install_local_file interface.\n* Optimized code structure.\n\nVersion 5.3\n-----------\n\n* Added support for custom remote desktop password after using certificate.\n* Fixed port multiplexing unsupported on some devices. Thanks alex han.\n* Fixed issues with Magisk installation script.\n* Fixed Debian launcher compatibility.\n\nVersion 5.2\n-----------\n\n* Fixed Selector containing False value being invalid. Thanks ThanhPham.\n* LAMDA can now be used with other accessibility services simultaneously (Android >= 8.0 only).\n\nVersion 5.0\n-----------\n\n* Fixed a series of issues related to login certificates.\n* Fixed Magisk module configuration reading strategy.\n* Remote desktop and RPC now fully support TLS.\n* Built-in Debian module can launch a Debian subsystem.\n* Fixed remote desktop bugs and made simple layout adjustments.\n* Adjusted service internal permissions and related directories.\n* Improved server-side stability.\n* Adjusted service installation method.\n* Proxy service nameserver now supports specifying port.\n* Added internal storage (memory configuration) read/write interfaces.\n* A series of other updates and fixes.\n\n=================================================================\nNote: Version 5.0 client is not fully compatible with version 3.0. Please update both simultaneously.\n=================================================================\n\nVersion 3.157\n-----------\n\n* Added UI element highlighting in inspector.\n* Added support for system crash count.\n\nVersion 3.155\n-----------\n\n* Added Tab key traversal for UI elements.\n* Added English character input support in remote desktop.\n* Added remote desktop touch support.\n\nVersion 3.153\n-----------\n\n* Fixed screenshot failure in some scenarios.\n* Minor changes.\n\nVersion 3.152\n-----------\n\n* Minor UI style adjustments.\n\nVersion 3.151\n-----------\n\n* Fixed high-DPI screen projection stretching issue #41.\n\nVersion 3.150\n-----------\n\n* Modified scheduled task reload logic.\n* Fixed Scapy routing issue.\n* Improved compatibility with some Xiaomi devices.\n* Fixed Android 11 interface compatibility (Thanks Kate Swan).\n* Added support for using 4G as proxy while connected to WiFi.\n* Added new UI controls.\n\nVersion 3.138\n-----------\n\n* Fixed gRPC dependency issue.\n* Added retrieval of last system toast.\n\nVersion 3.135\n-----------\n\n* Fixed remote desktop loading issue.\n* Completely fixed race condition in protocol.\n* Fixed Windows Python 3.10 compatibility.\n* Allowed cross-site calls for HTTP interfaces.\n* Added some missing modules.\n* Added service status indicator to remote desktop.\n* Added remote desktop responsive layout.\n* Pre-release next version.\n\nVersion 3.123\n-----------\n\n* Fixed incomplete retrieval of recent activities.\n\nVersion 3.120-1\n-----------\n\n* LAMDA can now act as a proxy itself.\n* Added interface to get system's recent activities.\n* Fixed a potential race condition in protocol.\n* Added some commands, removed SQLite db view.\n* Experimental H.264 screen projection.\n\nVersion 3.108\n-----------\n\n* Optimized network disconnection handling.\n* Added Redroid (remote android) support.\n* Partial compatibility with uiautomator2.\n* Added folder upload support.\n\nVersion 3.102\n-----------\n\n* Fixed file descriptor leak.\n* Added support for loading startup config from remote file server.\n* Now provides armeabi-v7a server build.\n* Fixed root certificate installation failure under Magisk.\n* Fixed configuration parsing error.\n* Minor UI adjustments.\n\nVersion 3.98\n-----------\n\n* Added crontab, vi commands.\n\nVersion 3.95\n-----------\n\n* Fixed build process issues.\n* Minor changes.\n\nVersion 3.93\n-----------\n\n* Added Android constant definitions.\n\nVersion 3.90\n-----------\n\n* Removed unused libraries to reduce size.\n* Removed macOS-incompatible command line history feature from client.\n* Updated DISCLAIMER.TXT.\n* Updated some dependency versions.\n\nVersion 3.83\n-----------\n\n* Added WSA support #24 @aimardcr.\n* Fixed black screen on note7pro MIUI10 @yu1chaofan.\n* Minor changes.\n\nVersion 3.80\n-----------\n\n* Fixed SSH disconnection issue.\n* Reduced package size.\n\nVersion 3.78\n-----------\n\n* Fixed issue #21 @yu1chaofan.\n* Updated frida-server.\n\nVersion 3.76\n-----------\n\n* Default built-in shell changed to bash.\n* Fixed remote desktop touch unusable after network disconnect.\n* Fixed OpenVPN zombie process issue.\n* startmitm.py now supports specifying adb serial number.\n* Added Magisk auto-start support.\n\nVersion 3.0.59\n-----------\n\n* All UI prompts now in English.\n* Fixed an unauthenticated web interface issue.\n* Fixed compatibility with older versions.\n\nVersion 3.0.55\n-----------\n\n* Fixed crash caused by wide-character request headers.\n* Merged mitmweb into startmitm process.\n* Minor Docker image modifications.\n* Added layout inspection support.\n\nVersion 3.0.50\n-----------\n\n* Added child, sibling selector support.\n\nVersion 3.0.48\n-----------\n\n* Added portable Windows startmitm command.\n* Added support for uploading/downloading files from/to memory.\n* Added screenshot() alias.\n\nVersion 3.0.47\n-----------\n\n* Simplified globalmitm, added HTTP, SOCKS5 proxy support.\n* Enhanced webview node finding.\n\nVersion 3.0.46\n-----------\n\n* Added two-finger zoom support.\n* Simplified startmitm DNS man-in-the-middle operations.\n\nVersion 3.0.45\n-----------\n\n* Added custom server port support (--port).\n* Fixed hang on special files during directory index file type detection.\n* globalmitm now checks DNS service availability.\n* startmitm.py selected wrong network interface when multiple networks present.\n* Client communication no longer automatically uses system proxy.\n\nVersion 3.0.35\n-----------\n\n* Improved built-in ADB performance.\n* OpenVPN service now supports auth parameter (default SHA1).\n* Fixed issue with scrcpy via built-in ADB."
  },
  {
    "path": "DISCLAIMER.TXT",
    "content": "DISCLAIMER\r\n\r\nTo use this service, you (hereinafter referred to as \"the user\") must agree to all terms of this agreement and complete the entire application process as prompted on the page. You can find DISCLAIMER.TXT in the source code or the released program, or view the copy below.\r\n\r\nTo download and use the LAMDA software (hereinafter referred to as \"this service\"), developed by firerpa (address: github.com/firerpa, email: lamda.devel@gmail.com, hereinafter referred to as \"the developer\"), you must carefully read and agree to all terms in this agreement. Please ensure you have fully understood and agreed to the following content before downloading, installing, or using this software.\r\nYou are not authorized to download, install, or use this software and its related services until you have fully read and accepted the terms of this agreement. Once you download, install, or use this software, you are deemed to have read and agreed to all terms of this agreement and are willing to be bound by them.\r\n\r\nRisk Disclosure:\r\nThis service requires the device to have root access to run, and the default communication protocols and related certificate files are open information, which may increase the risk of your device being compromised.\r\nThis service may contain unknown logical errors that could lead to potential risks such as data loss, system crashes, etc. The user decides whether to download and use this service at their own risk.\r\n\r\n\r\n1. The purpose of this service is to improve the work efficiency of security analysis and testing personnel for tasks such as application behavior and compliance analysis. The related tools provided are for legitimate and compliant app testing, analysis, and mock scenarios.\r\nThis service itself does not provide any functionality to intrude, modify, or capture memory and network data of other applications. It integrates services provided by various major open-source frameworks for users to choose from, to facilitate security analysts and to reduce users' repetitive work and management costs.\r\nThis service is not for profit. Users can download and use it according to their own needs, and no fees will be charged during the download or use process.\r\n\r\n2. This service respects and protects the user's personal privacy and will not steal any information from the user's device. The startup of this service and any rights to read, store, or transmit device data are entirely under the user's control.\r\n\r\n3. Users must use this service on a virtual device or a dedicated device with no private data. When using this service, users must comply with the laws and regulations of the People's Republic of China or the user's country or region of residence.\r\nThis service must not be used for any illegal purposes, nor for any activities detrimental to others.\r\n\r\n4. Users may only use this service for legitimate study, research, or legally authorized application analysis, testing, and other similar activities. If a user violates the above principles and causes losses to a third party while using the software service, the user shall bear all responsibility.\r\n\r\n5. The developer shall not be held legally responsible for any accident, negligence, contract breach, defamation, copyright or intellectual property infringement, or any resulting losses (including but not limited to direct, indirect, incidental, or consequential losses) incurred by any entity or individual as a result of downloading or using this service.\r\n\r\n6. You may use this service for commercial purposes, but only for extending functionality or developing products based on the features, interfaces, or related services provided by this service. You agree not to use this service, its related services, or interfaces for any purpose that violates local laws and regulations, or to engage in activities that harm the interests of others.\r\n\r\n7. The user explicitly agrees to all the contents listed in the terms of this agreement and shall solely bear any potential risks and consequences arising from the use of this service. The developer assumes no legal liability.\r\n\r\n8. The developer has the right to unilaterally change, interrupt, or terminate part or all of this service and the terms of this disclaimer and its attachments at any time. Such changes will be announced via message push, web announcements, or other means and will take effect immediately upon publication without separate notification. If you continue to use the service after the disclaimer has been changed, it indicates that you have fully read, understood, and accepted the modified content.\r\n\r\n9. If any part of this disclaimer is deemed invalid or unenforceable, that part shall be amended in a manner consistent with applicable law to reflect, as closely as possible, the developer's original intent, and the remaining portions shall remain in full force and effect. The unenforceability of any part of this disclaimer does not constitute a waiver by the developer of the right to enforce that provision.\r\n\r\n10. Reservation of Rights: All other rights not expressly granted herein are reserved by the developer.\r\n\r\n\r\nPlease confirm that you have read and accepted all the terms of this agreement. Otherwise, you are not authorized to download, install, or use this software and its related services."
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2021 - present, firerpa\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# **FIRERPA Android** ｜ AI-Powered Automation\n\n<img src=\"image/logo.svg\" alt=\"FIRERPA\" width=\"200\" align=\"right\" />\n\n<p>\n<img src=\"https://img.shields.io/badge/python-3.6+-blue.svg?logo=python&labelColor=yellow\" />\n<img src=\"https://img.shields.io/badge/android-6.0+-blue.svg?logo=android&labelColor=white\" />\n<img src=\"https://img.shields.io/badge/root%20require-red.svg?logo=android&labelColor=black\" />\n<img src=\"https://img.shields.io/github/downloads/rev1si0n/lamda/total\" />\n<img src=\"https://img.shields.io/badge/Built--in%20MCP-000.svg?logo=anthropic&labelColor=black\" />\n</p>\n\n<h6>An all-in-one next-gen Android automation framework blending robust on-device services with AI-ready agents and extensible tool-calling capabilities.</h6>\n\n<p align=\"left\"><a href=\"https://device-farm.com/docs/en/\">Documentation</a> | <a href=\"https://device-farm.com/docs/zh/\">使用文档</a> | <a href=\"https://t.me/lamda_dev\">TELEGRAM</a> | <a href=\"https://qm.qq.com/q/zDaX2a594I\">QQ Group</a> | <a href=\"https://device-farm.com/llms.txt\">llms.txt</a> | <a href=\"https://device-farm.com/llms-full.txt\">llms-full.txt</a></p>\n\n<h3>Core Capabilities</h3>\n\nFIRERPA is a lightweight, on-device Android automation stack with no external dependencies. It unifies low-latency remote desktop with 160+ APIs for device discovery/status/logs, system and app control, UI automation, OCR/image matching, file I/O, storage, scheduling, and shell execution, plus distributed deployment via Hub/FRP/OpenVPN. Built-in ADB/SSH/SCP, logging, API locking, certificates, script encryption, proxy/VPN/MITM utilities, and reverse tooling (Frida persistence/export/report, IDA debugging, binary patching) round out production workflows, alongside MCP/Agent tool-calls and extensions.\n\n<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/wiki/firerpa/lamda/images/banner.gif\" alt=\"MCP\" width=\"100%\">\n</p>\n\n<h3>Deployment & Stability</h3>\n\nFIRERPA delivers stable automation across Android 6.0 to 16, including emulators and cloud phones. Built to run non-intrusively, it requires no complex configuration and supports rooted, long-running services with clean install/upgrade/uninstall flows and repeatable lifecycle control.\n<h3>160+ APIs & Python SDK</h3>\n\nFIRERPA offers extensive APIs covering system config, app/process control, UI selectors/gestures, file I/O, encrypted key-value storage, OCR, and image matching. The Python SDK mirrors the full surface and provides higher-level helpers to build reliable automation quickly.\n\n<p align=\"center\">\n<img src=\"image/inspect.png\" alt=\"demo\" width=\"100%\">\n</p>\n\n<h3>Remote Desktop & Diagnostics</h3>\n\nMonitor and control devices visually with minimal setup. Includes file upload/download, WebSocket video (MJPEG/H.264), touch streaming, UI inspection, and event monitoring for validation and real-time diagnostics.\n\n<p align=\"center\">\n<img src=\"image/demo.gif\" alt=\"demo\" width=\"100%\">\n</p>\n\n---\n\nOur project is not entirely open source to prevent increased countermeasure costs. We remain committed to keeping it free for the community. We guarantee no malicious code; you are welcome to reverse-engineer and analyze its security. See our [Privacy Policy](https://device-farm.com/privacy) or [Contact Us](https://device-farm.com/contact) for enterprise support.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n| ------- | ------------------ |\n|   9.x   | :white_check_mark: |\n\n## Reporting a Vulnerability\n\nmailto:lamda.devel@gmail.com\n"
  },
  {
    "path": "all-llms.txt",
    "content": "FIRERPA (FIRERPAλ) - COMPLETE DOCUMENTATION\n\n========================================\nTABLE OF CONTENTS\n========================================\n\n1. OVERVIEW\n2. SETUP & INSTALLATION\n   - Requirements\n   - Pre-installation Checklist\n   - Server Installation Methods\n   - Client Installation\n   - Network Configuration\n   - Starting/Stopping/Uninstalling\n3. BASIC USAGE\n   - Preparation for Use\n   - Connecting via Python\n4. REMOTE DESKTOP\n   - Access & Connection\n   - Security Configuration\n   - File Management\n   - Customization Options\n   - Limitations\n5. DEVICE DISCOVERY (mDNS)\n6. SCHEDULED TASKS\n7. BUILT-IN TERMINAL\n8. VIRTUAL DEBIAN ENVIRONMENT\n9. PROGRAMMING INTERFACE (API)\n   - Basic Operations\n   - Device Information\n   - Screen Control\n   - Input Operations\n10. ADVANCED INTERFACE OPERATIONS\n11. IMAGE MATCHING OPERATIONS\n12. TEXT RECOGNITION (OCR)\n13. INTERFACE MONITOR\n14. BUILT-IN FRIDA\n    - Three Usage Methods\n    - Persistent Frida Scripts\n    - Frida Data Reporting\n    - Frida Exported Interfaces (RPC)\n15. BINARY PATCH\n16. BUILT-IN ADB MANAGEMENT\n17. CONNECTIVITY\n    - SSH Connection\n    - File Transfer (SCP)\n18. ONE-CLICK PACKET CAPTURE\n19. ENCRYPTION CERTIFICATES\n20. DISTRIBUTED DEPLOYMENT\n21. DEPLOYING OPENVPN SERVICE\n22. INTERFACE LOCKING\n23. VERSION HISTORY\n\n========================================\n1. OVERVIEW\n========================================\n\nFIRERPAλ is an Android device management and automation system that requires rooted devices.\nIt provides:\n- Remote desktop access via browser\n- Comprehensive automation APIs (160+ interfaces)\n- Advanced debugging capabilities\n- Built-in Frida with anti-detection\n- Packet capture and network manipulation\n- Device farm management\n\nARCHITECTURE:\n- Server component: Runs on rooted Android devices\n- Client library: Python 3.6-3.12\n- Default port: 65000\n- Browser-based remote desktop (Chrome 95+)\n\nPHILOSOPHY:\nThe documentation emphasizes sequential reading from first chapter. Users should read\ndocumentation when encountering issues before seeking community support.\n\n========================================\n2. SETUP & INSTALLATION\n========================================\n\nREQUIREMENTS\n------------\n- Android phone with root privileges\n- Minimum: 2GB+ RAM, 1GB+ available storage\n- Python 3.6-3.12 for client\n- Recommended emulators: NoxPlayer, LDPlayer, AVD\n- Chrome 95+ browser for remote desktop\n\nPRE-INSTALLATION CHECKLIST\n---------------------------\nCRITICAL - Must complete before installation:\n1. Turn off Magisk Hide\n2. Turn off frida-server\n3. Restart the device after confirmation\n4. Disable or uninstall all accessibility services (talkback, autojs, etc.)\n5. Configure timezone and time correctly\n   - Settings > Date and Time\n   - Verify timezone matches location\n   - If automatic fails, set manually\n\nSkipping these steps may cause compatibility issues with FIRERPA interfaces and\nremote desktop functionality.\n\nNETWORK CONFIGURATION\n---------------------\nReal Devices:\n- Ensure computer and phone share the same network\n\nEmulators:\n- Android x86 (VMWare): Set bridge mode in virtual machine settings\n- LDPlayer/NoxPlayer: Install drivers, enable bridge mode, restart\n- Android Studio AVD: Execute `adb forward tcp:65000 tcp:65000`, use localhost to connect\n- WSA (Windows Subsystem Android):\n  * Minimum version: 2210.40000 (rooted)\n  * Settings: Continuous subsystem resources, disable Advanced Networking\n  * Enable Developer Mode and \"Support single machine UI automation\"\n  * Restart subsystem\n- Redroid (Docker-based Android):\n  * For Ubuntu 20.04:\n    apt install linux-modules-extra-`uname -r`\n  * Edit /etc/modules, add: mac80211_hwsim, binder_linux, ashmem_linux\n  * Restart host machine\n  * Launch container mapping port 65001 to container's 65000\n  * Access via: http://127.0.0.1:65001\n\nNote: Use localhost instead of 127.0.0.1 for tools requiring USB-based detection.\n\nSERVER INSTALLATION METHODS\n----------------------------\n\nMETHOD 1: APP Installation (Easiest)\n-------------------------------------\n\"This installation method is the easiest and doesn't require you to do any extra operations.\"\n\nSteps:\n1. Download lamda-autorun.apk from the provided link\n2. Install and grant root privileges within the app\n3. Enable auto-start switch\n4. Add app to auto-start whitelist if device settings restrict auto-launching\n5. Restart the device\n6. Wait 1-2 minutes after restart\n7. Find device's IP address in WIFI settings (example: 192.168.1.8)\n8. Navigate to http://192.168.1.8:65000 in browser to access remote desktop\n\nTroubleshooting:\n- If connection fails, check network connectivity and simulator bridging\n- Manual startup: adb shell su root sh /data/server/bin/launch.sh\n\nMETHOD 2: Magisk Module Installation\n-------------------------------------\nFor devices running Magisk 20.4 or newer:\n\n1. Download lamda-magisk-module.zip from releases page\n2. Use Magisk App → Modules → Install from local\n3. Restart device\n\nThe system waits 30 seconds before launching to prevent crashes.\nAuto-starts at boot.\n\nMETHOD 3: Manual Installation\n------------------------------\nFor devices without Magisk support:\n\nStep 1: Get Device Architecture\ngetprop ro.product.cpu.abi\n\nCommon outputs: arm64-v8a, x86, x86_64, armeabi-v7a\n\nStep 2: Push Files\nadb push lamda-server-[arch].tar.gz /data/local/tmp\nadb push busybox-[arch] /data/local/tmp\n\nStep 3: Extract and Configure\nAfter entering `adb shell` and running `su`:\n\nchmod 755 /data/local/tmp/busybox-[arch]\n/data/local/tmp/busybox-[arch] tar -C /data -xzf /data/local/tmp/lamda-server-[arch].tar.gz\nrm /data/local/tmp/lamda-server-[arch].tar.gz\nrm /data/local/tmp/busybox-[arch]\n\nIMPORTANT CONFIGURATION NOTES:\n- Directory Location: /data/usr serves as FIRERPA's user data directory\n- Must be manually created before first startup if pre-configuring\n- Startup Configuration: Use properties.local file for automatic connections to OpenVPN,\n  proxies, and port forwarding\n- Security Warning: \"FIRERPA installed by default does not enable any authentication\"\n  Requires trusted network usage unless encryption certificates are enabled\n\nCLIENT INSTALLATION\n-------------------\nRequirements: Python 3.6-3.12\n\nInstallation:\npip3 install -U lamda[full]\n\nAlternative (if above fails):\npip3 install -U 'lamda[full]'\n\nTip: Add -i parameter to specify nearby PIP mirror for faster installation\n\nVerification:\npython3 -m lamda.client\n\nCOMMON ISSUES AND SOLUTIONS:\n\n1. UnicodeEncodeError with Chinese characters:\n   - Occurs when system paths contain non-ASCII characters\n   - gRPC cannot process correctly\n   - Verify: import sys; print(sys.path)\n   - Remove Chinese paths from environment variables like PYTHONPATH\n\n2. ImportError problems:\n   pip3 install -U --force-reinstall 'lamda[full]'\n\n3. Persistent issues:\n   - Use a virtual environment (uv recommended based on user settings)\n\nPost-installation:\nUpdate third-party Frida-dependent libraries (frida-tools, objection, etc.)\nto prevent runtime anomalies.\n\nSTARTING THE SERVER\n-------------------\nAuto-starts if installed via Magisk or auto-start APP after device restart.\nManual installations require:\n\nsh /data/server/bin/launch.sh\n\nSteps:\n1. Enter adb shell and switch to root: su\n2. Run launch command\n3. Wait for process completion\n4. Success message: \"llllaamDaa started\"\n5. Service operates in background mode\n\nTROUBLESHOOTING ERROR MESSAGES:\n\nError                  | Resolution\n-----------------------|--------------------------------------------------\nalready running        | Service is currently active\ninvalid TZ area        | System timezone requires configuration\nnot run as root        | Command must execute with root privileges\nunsupported sdk        | Android version incompatibility detected\nabi not match          | Incorrect architecture package installed\nfile broken            | Reinstall required due to corruption\n\nNotes:\n- Initial remote desktop access may experience loading delays; device restart may help\n- After successful startup, close terminal and proceed with other tasks\n- Failed startups require troubleshooting based on displayed error messages\n\nEXITING THE SERVER\n------------------\n\"FIRERPAλ is intended for continuous 24/7 operation; frequent starts/stops are not recommended\"\n\nMETHOD 1: Command Line (Primary)\nkill -SIGUSR2 $(cat /data/usr/lamda.pid)\n\nMETHOD 2: Interface-Based\nUse \"Shutdown and Restart\" section in documentation\n\nIMPORTANT WARNINGS:\n- Service Design: Intended for continuous operation; frequent cycling not recommended\n- Exit Duration: Complete shutdown may require more than 10 seconds\n- Command Caution: Execute kill command only once; repeating consecutively can cause instability\n- Risk Advisory: Frequent service cycling can easily introduce system instability\n\nUNINSTALLING THE SERVER\n------------------------\nPrerequisites:\nFollow \"Exiting the Server\" and wait at least 30 seconds to ensure service exits normally.\n\nSTANDARD INSTALLATION:\nrm -rf /data/server /data/usr\n\nMAGISK INSTALLATION:\nSimply remove from Magisk modules—no additional commands needed.\n\nWARNING:\n\"Please be cautious when removing the /data/usr directory, as this directory contains\nrelevant data for your use of the FIRERPA service.\"\n\nAfter uninstallation:\n\"FIRERPA will be completely removed from your device without leaving any other files.\"\nRecommend restarting device afterward to ensure clean completion.\n\n========================================\n3. BASIC USAGE\n========================================\n\nPREPARATION FOR USE\n-------------------\nBefore continuing with automation and API usage:\n\n1. Ensure FIRERPA server on mobile device has been started\n2. Default port: 65000\n3. Obtain device IP address from WLAN settings\n4. Port specification not necessary unless modified from default\n5. NAT Considerations: Some devices behind NAT; refer to Installation Preparation if\n   IP identification problematic\n\nExample: Documentation assumes reference device IP of 192.168.0.2\n\nCONNECTING VIA PYTHON\n----------------------\nBasic connection:\n\nfrom lamda.client import *\nd = Device(\"192.168.0.2\")\n\nWith encryption certificate:\n\nfrom lamda.client import *\nd = Device(\"192.168.0.2\", certificate=\"/path/to/lamda.pem\")\n\nWORKING WITH API RESPONSES:\nMany interfaces return native proto classes. Access values through output properties:\n\nresult = status.get_battery_info()\nprint(result.batt_temperature)  # Access specific field\n\nSIMPLE EXAMPLES:\n\nDisplay message on device:\nd.show_toast(\"Hello from Lamda!\")\n\nMake device beep (useful for locating devices):\nd.beep()\n\n========================================\n4. REMOTE DESKTOP\n========================================\n\nACCESS & CONNECTION\n-------------------\nNetwork-based device control via browser.\n\nAccess: http://192.168.0.2:65000\nRequirements: Chrome 95+ browsers\nMode: Single active operator - only first connected user can control device\n      Subsequent connections display in view-only mode\n\nSECURITY CONFIGURATION\n----------------------\nWhen launching server with encryption certificates via --certificate:\n- HTTPS becomes mandatory\n- Convert URL to https://\n- Authenticate with certificate's embedded password (line one of certificate file)\n- Custom credentials in properties.local: ssl-web-credential=12345\n\nFILE MANAGEMENT\n---------------\nUpload Capability:\n- Drag files/folders directly onto right-side terminal\n- Supports up to 2,000 simultaneous uploads\n- 256MB per-file limit\n- All uploaded content receives 644 permissions\n- Default storage: /data/usr/uploads (configurable)\n\nDownload Method:\n- Access http://192.168.0.2:65000/fs/ to browse device files in index format\n- Or use folder icon in remote desktop's top-right corner\n\nCUSTOMIZATION OPTIONS\n----------------------\nSettings gear (upper-right) provides adjustments for:\n- Upload directory\n- Frame rate\n- Resolution scaling\n- Image quality\n- Clipboard sharing\n\nVideo Encoding Options:\n- H.264: Reduces bandwidth\n- Hardware acceleration: Select \"System\" backend\n- \"Default\": Uses software encoding\n\nLIMITATIONS\n-----------\n- Platform doesn't support Chinese character input—only standard English text works\n- For enhanced keyboard functionality, explore \"Connecting to Built-in ADB\" documentation\n- Tools like Genymobile/scrcpy recommended for advanced input\n\n========================================\n5. DEVICE DISCOVERY (mDNS)\n========================================\n\nOVERVIEW\n--------\nmDNS-based discovery of FIRERPA devices on local networks.\nRequirements: FIRERPA version 7.85 or higher\nEnables: Locate devices and access services via domain names like {ro.serialno}.local\n\nDEVICE DISCOVERY METHODS\n------------------------\n\nUsing mdns-beacon Tool:\n1. Install: pip install mdns-beacon\n2. Execute: mdns-beacon listen --service _lamda._tcp.local.\n\nDisplays all devices running FIRERPA on current network.\nNote: Results may vary based on network configuration and device model compatibility.\n\nAccessing via Domain Names:\nFormat: {android_id}.local:65000\nRetrieve Android device ID: adb shell settings get secure android_id\n\nProgrammatic Discovery:\nUse zeroconf library (python-zeroconf) for automated device discovery and\nAndroid device information enumeration.\n\nCONFIGURATION OPTIONS\n---------------------\nCustomize via properties.local configuration file:\n\nmdns.meta=true         - Enable broadcasting of device information (ID, ABI, Android version, model)\nmdns.service           - Modify mDNS service name from default \"lamda\"\nmdns.name              - Assign fixed, unique server names per device for enhanced privacy\n\n========================================\n6. SCHEDULED TASKS\n========================================\n\nOVERVIEW\n--------\nEnable periodic execution of scripts following standard Linux Crontab syntax.\n\"All rules will be executed with root privileges.\"\n\nREQUIREMENTS:\n- Access via FIRERPA's remote desktop terminal, built-in ADB terminal, or SSH terminal\n- Basic crontab knowledge required\n- Device should remain powered on for reliable task execution\n\nIMPLEMENTATION STEPS\n--------------------\n1. Enter Edit Mode: crontab -e\n2. Write Rules: Press 'i' (English input mode), then add crontab expressions\n3. Save: Press ESC, then SHIFT+:, type 'wq' and press Enter\n\nCOMMON CRONTAB EXAMPLES\n-----------------------\nSchedule                          | Command\n----------------------------------|------------------------------------------------\nSystem startup                    | @reboot echo Execute when framework starts\nHourly                           | 0 */1 * * * echo Execute every hour\nEvery minute                     | * * * * * echo Execute every minute\nDaily at 8 AM                    | 0 8 * * * echo Execute at 8 o'clock daily\n\nOUTPUT REDIRECTION:\nTo save task output:\n* * * * * echo hello >/data/usr/script.log 2>&1\n\nIMPORTANT NOTE:\n\"Due to Android's sleep mechanism, scheduled tasks may not run at the time you expect\nafter the screen is turned off.\"\n\nVALIDATION:\nUse an online Crontab verification website to confirm rule accuracy before deployment.\n\n========================================\n7. BUILT-IN TERMINAL\n========================================\n\nOVERVIEW\n--------\nKey FIRERPA feature allowing real-time command execution.\nAccess: Through remote desktop, SSH, or ADB connections\nNote: \"The terminal supports command completion but not parameter completion.\"\n\nAVAILABLE ALIASES\n-----------------\nl      → ls\nll     → ls -l\nla     → ls -la\npy     → python\n..     → Navigate parent directory\n...    → Navigate parent directories\nt      → Go to /data/local/tmp\np      → Return to previous directory\n\nBUILT-IN COMMANDS\n-----------------\nThe system includes numerous pre-installed tools:\n\nDevelopment & Analysis:\nPython, strace, ltrace, frida tools suite, capstone, keystone, unicorn\n\nNetworking:\ncurl, scapy, tcpdump, socat, stunnel, redir, iperf3\n\nSystem Tools:\nbusybox, ncdu, nano, vi, sqlite3, MemDumper, fsmon\n\nPYTHON LIBRARIES\n----------------\nPre-installed third-party modules:\n\nCryptography:\nCrypto, OpenSSL, bcrypt, cryptography\n\nImage Processing:\nPIL, cv2\n\nData Handling:\nprotobuf, msgpack, ujson\n\nNetworking:\nrequests, websocket\n\nSpecialized Tools:\nfrida, numpy, redis, peewee\n\nCRITICAL LIMITATION:\n\"You cannot install additional libraries through PIP or APT in the built-in terminal environment.\"\nUsers requiring additional dependencies should utilize the Virtual Debian Environment.\n\n========================================\n8. VIRTUAL DEBIAN ENVIRONMENT\n========================================\n\nOVERVIEW\n--------\nComplete Debian environment within Android devices, comparable to Termux or androdeb.\nAllows: apt package installation, software compilation, BPF program development on Android.\n\nINSTALLATION STEPS\n------------------\n1. Download Package:\n   Obtain lamda-mod-debian-arm64-v8a.tar.gz from project's release page\n   (select architecture-appropriate version)\n\n2. Upload via Remote Desktop:\n   Use remote desktop interface to drag and upload file\n   Default location: /data/usr/uploads\n\n3. Extract Installation (one-time command):\n   tar -C /data/usr/modules -xzf /data/usr/uploads/lamda-mod-debian-arm64-v8a.tar.gz\n\nBASIC USAGE\n-----------\nEntering Interactive Terminal:\ndebian /bin/bash\n\nExecuting Single Commands:\ndebian /bin/bash -c id\n\nIMPORTANT LIMITATION:\n\"Only one instance can enter the virtual environment at a time. After you execute\ndebian /bin/bash and keep using it, if you continue to execute this command in other\nterminals, it will return an error, unless you exit the first started debian /bin/bash.\"\n\nADVANCED SETUP (SSH + Python)\n------------------------------\n1. Enter environment and install packages\n2. Configure SSH settings in sshd_config\n3. Set root password\n4. Start SSH daemon: debian /usr/sbin/sshd -D -e\n5. Automate via scheduled tasks:\n   @reboot debian /usr/sbin/sshd -D -e >/data/usr/sshd.log 2>&1\n6. Connect remotely: ssh root@[device-ip]\n   Default password: lamda\n\n========================================\n9. PROGRAMMING INTERFACE (API)\n========================================\n\nOVERVIEW\n--------\n\"Up to 160 programming API interfaces, allowing you to manage and operate Android\ndevices meticulously.\"\nCoverage: Command execution, system settings, application management, automation,\nproxy services, file operations.\n\nBASIC OPERATIONS\n----------------\n\nDisplay Message:\nd.show_toast(\"message\")\n\nBeep (useful for locating devices):\nd.beep()\n\nDEVICE INFORMATION\n------------------\n\nGet Device Details:\nd.device_info()\nReturns: productName, sdkInt, displayHeight, displaySizeDpX, displaySizeDpY,\n         displayWidth, screenOn, naturalOrientation, currentPackageName\n\nServer Information:\nd.server_info()\nReturns: uniqueId, version, architecture, uptime, secure metrics\n\nScreen Status Checks:\nd.is_screen_on()        - Determines if display is active\nd.is_screen_locked()    - Verifies lock state\n\nSCREEN CONTROL\n--------------\nd.sleep()                                    - Deactivates display (power button equivalent)\nd.wake_up()                                  - Activates display\nd.screenshot(60)                             - Captures with quality parameter\nd.screenshot(60, Bound(top, left, right, bottom))  - Regional cropping\n\nINPUT OPERATIONS\n----------------\n\nTouch Actions:\nd.click(Point(x=100, y=100))                - Single tap\nd.drag(Point_A, Point_B)                    - Press and drag motion\nd.swipe(Point_A, Point_B)                   - Swipe gesture\nd.swipe_points(p1, p2, p3)                  - Multi-point swipe path\n\nPhysical Keys:\nd.press_key(Keys.KEY_BACK)                  - Supports KEY_BACK, KEY_HOME, KEY_VOLUME_UP,\n                                              KEY_POWER, and others\nd.press_keycode(code)                       - Additional Android KeyEvent codes\n\nSYSTEM INTERFACE\n----------------\nd.get_clipboard()                           - Retrieve clipboard content (Android 9 and below)\nd.set_clipboard(\"text\")                     - Write clipboard data\nd.dump_window_hierarchy()                   - Extract current page XML layout\nd.wait_for_idle(5000)                       - Pause until UI stabilizes (milliseconds)\nd.get_last_toast()                          - Retrieve latest system toast\n\nSETTINGS & NOTIFICATIONS\n------------------------\nd.open_quick_settings()                     - Display settings panel (half-open state)\nd.open_notification()                       - Expand notification drawer\n\n========================================\n10. ADVANCED INTERFACE OPERATIONS\n========================================\n\nOVERVIEW\n--------\nDeep automation interfaces for detailed operations on mobile device interfaces.\n\nGETTING ELEMENTS\n----------------\nElements located via selectors.\nImportant: \"The element you click directly on the left interface may not be the actual\nelement\" due to overlapping components.\n\nSelection Methods:\nelement = d(text=\"同意\")\nelement = d(resourceId=\"com.tencent.news:id/btm_first_agree\")\n\nELEMENT OPERATIONS\n------------------\n\nClick Operations:\nelement.click()                              - Standard click\nelement.click_exists(corner=Corner.COR_TOPLEFT)  - Click with position specification\nelement.long_click()                         - Extended press\nelement.click_exists()                       - Conditional click without exception throwing\n\nInformation Retrieval:\nelement.exists()                            - Existence check\nelement.info()                              - Detailed metadata (bounds, className, text)\nelement.count()                             - Quantity of matching elements\nelement.get(3)                              - Retrieve specific nth element\n\nCoordinate Access:\ninfo.bounds.center()                        - Center point\ninfo.bounds.corner(\"top-left\")              - Corner coordinates\ninfo.bounds.width, info.bounds.height       - Dimensions\n\nTEXT INPUT\n----------\nImportant: \"When getting input box elements, your input method must be in a popped-up\nstate before finding relevant elements.\"\n\nOperations:\nelement.set_text(\"你好世界\")                 - Input text\nelement.get_text()                          - Retrieve current content\nelement.clear_text_field()                  - Clear field\n\nNAVIGATION\n----------\nNormal Swipe:\nd().swipe(direction=Direction.DIR_UP, step=32)\n\nFast Swipe:\nd().fling_from_top_to_bottom()\nd().fling_from_bottom_to_top()\nd().fling_from_left_to_right()\nd().fling_from_right_to_left()\n\nScroll (mechanical swiping):\nd().scroll_from_top_to_bottom(step)\nd().scroll_from_bottom_to_top(step)\nd().scroll_from_left_to_right(step)\nd().scroll_from_right_to_left(step)\n\nADVANCED FEATURES\n-----------------\nelement.wait_for_exists(10*1000)            - Wait for appearance (milliseconds)\nelement.wait_until_gone(10*1000)            - Wait for disappearance\nelement.screenshot(quality=60)               - Capture element-level screenshots\nelement.child(index=1)                       - Locate child elements\nelement.sibling()                            - Find sibling elements\nelement.drag_to(Selector(...))               - Drag operations\n\n========================================\n11. IMAGE MATCHING OPERATIONS\n========================================\n\nOVERVIEW\n--------\nLocate and interact with UI elements through image matching.\nTwo primary methods: Template matching and Feature point matching (SIFT)\n\nKEY CONCEPTS:\n- Template matching: Better performance, consistent resolutions\n- Feature point matching: Adapts to varying screen resolutions, requires threshold tuning\n- Processing: Server-side (preserves local resources)\n- Limitation: Mobile device performance may limit efficiency\n\nMAIN FUNCTION SYNTAX\n--------------------\nd.find_similar_image(\n    data,\n    threshold=0.0,\n    distance=250,\n    scale=1.0,\n    area=FindImageArea.FIA_WHOLE_SCREEN,\n    method=FindImageMethod.FIM_TEMPLATE\n)\n\nPARAMETERS\n----------\nParameter  | Purpose\n-----------|------------------------------------------------------------------\ndata       | Image byte data for matching\nthreshold  | Similarity filter level\ndistance   | Maximum feature point tolerance\nscale      | Performance optimization ratio\narea       | Screen region restriction\nmethod     | Algorithm selection\n\nMATCHING METHODS\n----------------\nFIM_TEMPLATE:\n- Fast, texture-dependent\n- Limited rotation/scale tolerance\n\nFIM_FEATURE:\n- Robust against transformations\n- Suits complex scenes\n\nMATCHING AREAS\n--------------\nNine predefined regions optimize performance:\n- Full-screen\n- Halves: left, right, top, bottom\n- Corners: top-left, top-right, bottom-left, bottom-right\n\n========================================\n12. TEXT RECOGNITION (OCR)\n========================================\n\nOVERVIEW\n--------\nLeverage OCR technology for interface automation when conventional selectors don't work.\nParticularly useful in gaming applications.\n\nOCR BACKEND SETUP\n-----------------\n\nPaddleOCR with GPU:\nd.setup_ocr_backend(\n    \"paddleocr\",\n    quality=80,\n    use_gpu=True,\n    drop_score=0.85,\n    use_space_char=True\n)\n\nEasyOCR for Chinese and English:\nd.setup_ocr_backend(\"easyocr\", [\"ch_sim\", \"en\"], quality=80)\n\nCustom HTTP Backend:\nFor systems managing multiple devices, implement custom backend as HTTP service\nto avoid excessive resource consumption from repeated initialization.\n\nOCR SELECTOR TYPES\n------------------\nMethod                  | Purpose\n------------------------|--------------------------------------------------\ntext=\"Mine\"            | Exact text matching\ntextContains=\"Mine\"    | Partial text matching\ntextMatches=\".*?Mine\"  | Regex pattern matching\n\nSUPPORTED OPERATIONS\n--------------------\n- click()          - Activate matched element\n- click_exists()   - Conditional activation\n- exists()         - Verify presence\n- screenshot()     - Capture element image\n- info()           - Retrieve OCR metadata\n\nKEY LIMITATION:\n\"OCR recognition methods only support checking if elements exist, clicking, taking\nscreenshots, and other operations.\"\nMore complex interactions aren't available through this interface.\n\n========================================\n13. INTERFACE MONITOR\n========================================\n\nOVERVIEW\n--------\nReal-time listener for UI changes that performs automatic actions when conditions match.\nFunctions similarly to ad-skipping automation tools.\nCaution: Performs automatic clicks and key presses that may interfere with manual tasks.\n\nCORE FUNCTIONS\n--------------\nEnable Monitoring:\nd.set_watcher_loop_enabled(True)\n\nCheck Status:\nd.get_watcher_loop_enabled()\n\nDisable Monitoring:\nd.set_watcher_loop_enabled(False)\n\nClear All Events:\nd.remove_all_watchers()\n\nEVENT REGISTRATION TYPES\n------------------------\n\nClick Events - Auto-clicks matching UI elements:\nd.register_click_target_selector_watcher(\"EventName\", [Selector conditions], target_selector)\n\nKey Press Events - Triggers keyboard actions:\nd.register_press_key_watcher(\"EventName\", [Selector conditions], Keys.KEY_CODE)\n\nCount Events - Tallies element appearances:\nd.register_none_op_watcher(\"EventName\", [Selector conditions])\n\nRetrieve counts:\nd.get_watcher_triggered_count(\"EventName\")\n\nEVENT MANAGEMENT\n----------------\nEnable: d.set_watcher_enabled(\"name\", True)\nDisable: d.set_watcher_enabled(\"name\", False)\nRemove: d.remove_watcher(name)\nCheck status: d.get_watcher_enabled(name)\n\nCRITICAL NOTE:\n\"Any matching event operations that occur on the interface before enabling the monitor\nor enabling monitor events will not be processed!\"\n\n========================================\n14. BUILT-IN FRIDA\n========================================\n\nOVERVIEW\n--------\nFIRERPA includes integrated, latest-version Frida with:\n- Built-in anti-detection patches\n- Custom hiding features\n- No separate frida-server setup required\n\nIMPORTANT: \"Since FIRERPA version 7.18, the built-in FRIDA requires a token parameter\nto connect.\"\n\nTHREE USAGE METHODS\n-------------------\n\nMETHOD 1: Code-Based Integration\n---------------------------------\nAccess Frida through FIRERPA client API:\n\nconn = d.frida\nconn.enumerate_processes()\n\nAlternative approach using direct token retrieval:\n\ntoken = d._get_session_token()\nmanager = frida.get_device_manager()\nconn = manager.add_remote_device(\"192.168.0.2:65000\", token=token)\n\nMETHOD 2: Command-Line Usage\n-----------------------------\nDocumentation emphasizes using frida commands through remote desktop for simplicity\n(no additional parameters needed there).\n\nFor local machine usage, command structure requires:\n- Host parameter: -H 192.168.0.2:65000 (not -U)\n- Token parameter: --token xxxxxxxxxxxxxxxx\n- Optional certificate: --certificate /path/to/lamda.pem (if encryption enabled)\n\nExample command:\nfrida -H 192.168.0.2:65000 -f com.android.settings --token xxxxxxxxxxxxxxxx\n\nMETHOD 3: Objection Tool Integration\n-------------------------------------\nPatched version available at provided GitHub repository.\nUsage pattern:\n\nobjection -N -h 192.168.0.2 -p 65000 --token xxxxxxxxxxxxxxxx explore\n\nCRITICAL DETAILS:\n- Default FIRERPA port is 65000 (not Frida's 27042)\n- Tokens are fixed 16-character strings\n- Encryption certificates require additional command parameters for security\n\n========================================\nPERSISTENT FRIDA SCRIPTS\n========================================\n\nOVERVIEW\n--------\nFIRERPA enables persistent Frida script injection with automatic management.\nScripts remain active even after app crashes or process exits.\nAutomatic reinitiation upon app restart.\nFeature launched in version 7.80.\n\nINSTALLING SCRIPTS\n------------------\nDeploy scripts to applications:\n\napp = d.application(\"com.android.settings\")\napp.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)\n\nIMPORTANT LIMITATION:\n\"The script manager only allows one script to be injected for each APP at a time.\"\n\nParameters:\n- script: Frida script content (supports bytecode)\n- runtime: Execution environment (default: qjs)\n- standup: Delay in seconds (1-300) to prevent early injection issues\n- spawn: Optional mode for automatic app restart on failure\n\nUNINSTALLING SCRIPTS\n--------------------\nRemove injected scripts and halt monitoring:\n\napp = d.application(\"com.android.settings\")\napp.detach_script()\n\nSTATUS VERIFICATION\n-------------------\nTwo diagnostic methods:\n\nInstallation check:\napp.is_attached_script()\n\nInjection verification (determines if script actively runs in process):\napp.is_script_alive()\n\nSCRIPT LOGGING\n--------------\nMonitor console output and errors by filtering logs:\n\ngrep SCRIPT /data/local/tmp/server.log\n\nOFFLINE PERSISTENCE\n-------------------\nConfigure scripts as YAML files in /data/usr/modules/script/ for automatic loading\nwithout API calls.\n\nConfiguration template:\n\nenable: true\napplication: \"com.android.settings\"\nversion: \"2.10\"\nruntime: \"qjs\"\nscript: !!binary \"[base64-encoded content]\"\nstandup: 10\nspawn: false\n\nThe system monitors this directory for real-time updates, additions, and deletions.\n\n========================================\nFRIDA DATA REPORTING\n========================================\n\nOVERVIEW\n--------\nEnable automatic capture of method call data through custom scripts.\nAutomated submission to Redis, HTTP interfaces, or RabbitMQ queues.\nSupports data compression via zlib for improved network performance.\n\nWRITING REPORTING SCRIPTS\n--------------------------\nScripts use emit(name, content) method to submit intercepted data.\n\nParameters:\n- name: Identifies data type (e.g., \"product_info\")\n- content: Actual data as string or byte array\n\nExample structure:\nJava.perform function intercepts okhttp traffic, extracts URL and body information,\nthen emits data as JSON string.\n\nDATA REPORTING DESTINATIONS\n----------------------------\nThree supported options:\n- Redis queues (simplest, no metadata)\n- HTTP interfaces (includes metadata via query parameters)\n- RabbitMQ queues (metadata in headers)\n\nMETADATA FIELDS:\nReports include: application name, device ID, encoding type, data name, script ID,\nsequence number, timestamp, multi-instance application ID.\nNote: Redis reporting excludes metadata due to protocol limitations.\n\nIMPLEMENTATION\n--------------\nScript Injection:\n\napp = d.application(\"com.android.settings\")\napp.attach_script(script, emit=\"redis://192.168.1.10/0\")\n\nAlternative HTTP endpoint:\napp.attach_script(script, emit=\"http://192.168.1.10/dataReport\")\n\nCompression Option:\napp.attach_script(..., encode=DataEncode.DATA_ENCODE_ZLIB)\n\nDecompression:\nimport zlib\nzlib.decompress(data)\n\nRemoval:\napp.detach_script()\n\nKEY REQUIREMENTS:\n- HTTP services must return \"OK\" or \"SUCCESS\" with status 200\n- RabbitMQ queues require manual creation\n- Only standalone Redis is supported (no clusters)\n- HTTP requests are multi-threaded and may arrive out-of-sequence\n\n========================================\nFRIDA EXPORTED INTERFACES (RPC)\n========================================\n\nOVERVIEW\n--------\nEnables developers to extend FIRERPA functionality by implementing custom Frida RPC methods.\nUsers write Hook code for ultimate app control.\nRequires familiarity with Frida scripting.\n\nWRITING EXPORT SCRIPTS\n-----------------------\nScripts must follow specific formatting requirements:\n- Function names use camelCase with lowercase first letter\n- Avoid all-caps naming (e.g., use sendHttpRequest not sendHTTPRequest)\n- Wrap code in Java.perform() blocks\n- Use mandatory return performRpc patterns\n\nTHREE EXECUTION CONTEXTS:\n\nperformRpcJVMCall:\n\"Execute on JVM thread\" - supports Java operations like Java.use()\n\nperformRpcJVMCallOnMain:\n\"Execute on UI thread\" - necessary for UI operations; avoid blocking main thread\n\nperformRpcCall:\nBasic JavaScript only - no Java layer logic\n\nINJECTING SCRIPTS\n-----------------\nInject using persistent Frida script method:\n\napp = d.application(\"com.android.settings\")\napp.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)\n\nVerify injection status:\napp.is_script_alive()\n\nCALLING EXPORTED METHODS\n-------------------------\nDirect API calls:\n\napp = d.application(\"com.android.settings\")\napp.exampleFunc1(\"FIRE\", \"RPA\")\n\nMethod names support both camelCase and snake_case variations:\nexampleFunc1 equals example_func1\n\nMulti-instance applications: Specify UID parameter to target specific instances\n\nHTTP INTERFACE CALLS\n--------------------\nStarting version 8.15, supports JSON-RPC 2.0 protocol:\n\nimport jsonrpclib\nserver = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')\nserver.example_func1(\"FIRE\", \"RPA\")\n\nLegacy protocol also supported via POST requests with JSON-serialized arguments.\n\nFor secured servers:\n- Include X-Token header with certificate password\n- Use HTTPS\n\nHTTP STATUS CODES:\n\nCode | Meaning\n-----|--------------------------------------------------------------\n200  | Success\n410  | Script not injected/installed\n500  | Script/parameter error\n400  | Invalid parameters\n\nTROUBLESHOOTING:\nHangs or timeouts typically occur when apps run backgrounded and enter sleep state.\nKeep apps in foreground during testing.\n\n========================================\n15. BINARY PATCH\n========================================\n\nOVERVIEW\n--------\nModify device files using hexadecimal search-and-replace operations with wildcard support.\n\nWILDCARD SYNTAX\n---------------\nSystem supports flexible pattern matching:\n- ?? matches any byte\n- B? matches bytes starting with B (like BA, B1, B9)\n- Example: 49 BA ?? ?C matches 49 BA [any] [any ending in C]\n\nBASIC USAGE\n-----------\nStandard replacement:\n\nd.hex_patch(\"AA BB CC D?\", \"AA BB CC DD\", \"/data/test.bin\")\n\nReturns object containing:\n- count: number of replacements made\n- replaces[].offset: byte position of each replacement\n\nPARAMETERS\n----------\n\nmaxreplace - Limits replacement count (default: all matches replaced):\nd.hex_patch(\"AA BB CC D?\", \"AA BB CC DD\", \"/data/test.bin\", maxreplace=2)\n\ndryrun - Test mode that identifies matches without modifying files:\nd.hex_patch(\"AA BB ?? ??\", \"AA BB 00 00\", \"/data/test.bin\", dryrun=True)\n\nFILE PATH WILDCARDS\n-------------------\nPaths support glob patterns:\n/data/app/*/test.bin matches test.bin in any first-level subdirectory under /data/app\n\nREQUIREMENTS:\nPatterns require \"at least two valid matching digits\" for functionality.\n\n========================================\n16. BUILT-IN ADB MANAGEMENT\n========================================\n\nOVERVIEW\n--------\nFIRERPA system includes independent ADB service that operates separately from system's\nnative ADB implementation.\n\nKey feature: \"Connect to the highest privilege ADB without enabling developer mode\"\nthrough wireless connectivity.\n\nIMPORTANT LIMITATION:\nBuilt-in ADB service does not currently support JDWP debugging functions\n(conflicts with system's native implementation).\n\nINSTALLING ADB PUBLIC KEYS\n---------------------------\n\nKey Location:\n- Linux/Mac: ~/.android/ directory\n- Windows: C:\\Users\\[username]\\.android\\ directory\n- Filename: adbkey.pub\n\nIf only adbkey exists, generate public key:\nadb pubkey adbkey >adbkey.pub\n\nInstallation Process:\nd.install_adb_pubkey(\"/path/to/adbkey.pub\")\n\nAfter successful installation, connect wirelessly:\nadb connect 192.168.0.2:65000\n\nREMOVING ADB PUBLIC KEYS\n-------------------------\nUninstall public key from built-in ADB service:\n\nd.uninstall_adb_pubkey(\"/path/to/adbkey.pub\")\n\nNote: System-authorized key from developer mode settings is incompatible with\nbuilt-in ADB service.\n\n========================================\n17. CONNECTIVITY\n========================================\n\nCONNECTING TO BUILT-IN ADB\n---------------------------\nInstall Key:\npython3 -u adb_pubkey.py install 192.168.1.2\n\nConnect:\nadb connect 192.168.1.2:65000\n\nUninstall Key:\npython3 -u adb_pubkey.py uninstall 192.168.1.2\n\nCONNECTING TO BUILT-IN SSH\n---------------------------\nConnection command:\nbash ssh.sh 192.168.1.2\n\nCOPYING FILES VIA SCP\n---------------------\nRetrieve from device:\nbash scp.sh 192.168.1.2:/sdcard/DCIM .\n\nSend to device:\nbash scp.sh test/ 192.168.1.2:/sdcard\n\nTechnical Details:\n- Tool uses wrapper script: scp.sh\n- Operations target device IP addresses\n- File paths follow standard SCP syntax with colon-separated device locations\n- Supports both directory and individual file transfers\n\n========================================\n18. ONE-CLICK PACKET CAPTURE\n========================================\n\nOVERVIEW\n--------\nAutomated man-in-the-middle packet capture for Android 6.0-14.\nNo manual certificate or proxy configuration required.\nSupports real-time packet modification.\nGuarantees: \"No packets cannot be captured (except certificate pinning).\"\n\nKEY REQUIREMENTS\n----------------\n- Computer and device on same network or USB-connected\n- mitmproxy installed (mitmdump command available)\n- Network firewall temporarily disabled\n- Python 3 (or use pre-packaged startmitm.exe)\n\nBASIC COMMANDS\n--------------\n\nSimple packet capture:\npython3 -u startmitm.py 192.168.0.2\n\nShared analysis (colleagues view via browser):\npython3 -u startmitm.py 192.168.0.2 --web-port 7890 --web-host 0.0.0.0\n\nSpecific application only:\npython3 -u startmitm.py 192.168.0.2:com.some.package\n\nWith real-time modification script:\npython3 -u startmitm.py 192.168.0.2 -s http_flow_hook.py\n\nNETWORK SCENARIOS\n-----------------\n\nUSB ADB connection:\npython3 -u startmitm.py localhost\npython3 -u startmitm.py localhost --serial [device_serial]\n\nInternational/Upstream proxy:\npython3 -u startmitm.py 192.168.0.2 --upstream http://127.0.0.1:7890 --dns https://dns.google/dns-query\n\nDNS packet capture:\npython3 -u startmitm.py 192.168.0.2 --dns 114.114.114.114\n\nCRITICAL NOTES\n--------------\n- Close target applications completely before starting capture (not just via taskbar)\n- Exit with CONTROL+C once only\n- SSL pinning requires Frida bypass scripts\n- QUIC auto-downgrades but may cause temporary lag\n\n========================================\n19. ENCRYPTION CERTIFICATES\n========================================\n\nOVERVIEW\n--------\nCreate encryption certificates for FIRERPA to secure communication between users\nand service.\n\"This certificate is used to encrypt the communication traffic between you and the\nFIRERPA service, preventing unauthorized access.\"\n\nKEY DISTINCTION:\nCertificate discussed here differs from those used in packet capture operations.\nPurpose: Protecting remote desktop access and service communication rather than\nintercepting network traffic.\n\nGENERATION PROCESS\n------------------\nLocation: cert.py script resides in project's tools directory.\n\nCommand:\npython3 cert.py mydevice.local\n\nPrerequisites: Install necessary environment and dependencies beforehand.\nRegular updates recommended to avoid compatibility issues.\n\nGENERATED FILES\n---------------\n- mydevice.local.pem - The encryption certificate\n- root.crt and root.key - Root certificates (require secure storage but aren't directly used)\n\nCERTIFICATE FORMAT\n------------------\nOutput contains RSA private key and certificate blocks in PEM format.\nIncludes default remote desktop password embedded as parameter: PASSWD=...\n\nIMPLEMENTATION EFFECTS\n----------------------\nOnce applied to FIRERPA:\n- Remote desktop access requires HTTPS protocol\n- Authorization demands password from certificate\n- SSH login also requires certificate-based authentication\n\nIMPORTANT: Users shouldn't manually edit certificate file.\n\n========================================\n20. DISTRIBUTED DEPLOYMENT\n========================================\n\nOVERVIEW\n--------\nEnable remote access to devices located elsewhere using distributed deployment methods.\nThree primary approaches: FRP forwarding, OpenVPN networking, Xinghuo Platform integration.\n\nFRP-BASED SOLUTIONS\n-------------------\n\nInternal Port Forwarding:\nDownload FRP server from fatedier/frp repository (v0.45.0+).\nLaunch with parameters controlling token authentication, bind addresses, allowed port\nranges (10000-15000).\n\nConfiguration - properties.local variables:\nfwd.enable=true\nfwd.host=your-server.com # server-address\nfwd.port=6009 # server-port\nfwd.rport=12345 # remote-port\nfwd.token=your-token\nfwd.protocol=tcp\n\nConnecting via FRP:\nDevice(\"127.0.0.1\", port=12345)\n\nOr browser access:\nhttp://[frp-server-ip]:12345\nhttps://[frp-server-ip]:12345 (with certificate)\n\nPUBLIC NETWORK ACCESS WARNING:\nDocumentation advises against exposing devices directly to public networks.\nIf necessary:\n- FIRERPA must use encryption certificates\n- Proxy bind address must change from localhost to 0.0.0.0\n- Without certificates: \"anyone will be able to access it, which is extremely dangerous\"\n\nOPENVPN APPROACH\n----------------\nSetup involves deploying OpenVPN server (detailed in separate documentation).\nConnect personal devices to same virtual network.\n\nXINGHUO PLATFORM\n-----------------\nSimplified alternative requiring free access code from Device Farm WeChat account.\n\n========================================\n21. DEPLOYING OPENVPN SERVICE\n========================================\n\nOVERVIEW\n--------\nDeploy OpenVPN service using Docker on Linux systems (tested on Debian 9).\nDefault port: 1190/UDP\n\nSERVER PREPARATION\n------------------\n\nEnable IP forwarding:\necho net.ipv4.ip_forward=1 >>/etc/sysctl.conf\nsysctl -p\n\nFor UFW firewall systems:\nAdd to /etc/ufw/before.rules before *filter rules:\n\n*nat\n:POSTROUTING ACCEPT [0:0]\n-A POSTROUTING -s 172.27.27.0/24 -o eth0 -j MASQUERADE\nCOMMIT\n\nChange /etc/default/ufw:\nDEFAULT_FORWARD_POLICY=\"ACCEPT\"\n\nThen reload:\nufw reload\n\nFor systems without UFW:\niptables -P FORWARD ACCEPT\n\nCONFIGURATION SETUP\n-------------------\n\nCreate directory and initialize:\nmkdir -p ~/lamda-openvpn-server\ndocker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-server-new\n\nKey configurable fields in config.ovpn:\n- VPN network: server 172.27.27.0 255.255.255.0\n- Port: port 1190\n- DNS: push \"dhcp-option DNS 114.114.114.114\"\n\nCLIENT MANAGEMENT\n-----------------\n\nCreate client credentials:\ndocker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-new myname\n\nGenerate client profile:\ndocker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-profile ovpn myname >myname.ovpn\n\nRevoke client access:\ndocker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-revoke myname\n\nSERVICE OPERATION\n-----------------\n\nForeground mode (testing):\ndocker run -it --rm --name openvpn-server --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn run\n\nBackground mode (production):\ndocker run -d --rm --name openvpn-server --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn run\n\n========================================\n22. INTERFACE LOCKING\n========================================\n\nOVERVIEW\n--------\nControl API access through interface locking mechanisms.\nAllows device instances to restrict API usage to prevent unauthorized access.\n\nKEY CONCEPT:\n\"The interfaces in this chapter are used to lock all API interfaces, allowing you to\nrestrict interfaces to only be used by the current Device instance.\"\n\nRecommended approach: Acquire lock, maintain through periodic refreshes, release when done.\n\nTHREE MAIN FUNCTIONS\n--------------------\n\n1. Acquire Lock\nCommand: d._acquire_lock(leaseTime=60)\n- Automatically releases after 60 seconds by default\n- Reentrant capability (subsequent calls refresh existing lock)\n- Warning: Setting excessively high lease times risks permanent device lockout if scripts crash\n\n2. Release Lock\nCommand: d._release_lock()\n- Manually relinquishes API lock\n- Allows other clients to acquire access\n\n3. Refresh Lock\nCommand: d._refresh_lock(leaseTime=60)\n- Extends lock duration by resetting expiration time\n- Intended for periodic calls to maintain continuous access\n\nBEST PRACTICE:\nSecure sequence:\n1. Acquire lock\n2. Spawn thread to refresh periodically\n3. Release when finished\n\nThis approach prevents unexpected disconnections while maintaining safety against\nscript failures.\n\n========================================\n23. VERSION HISTORY\n========================================\n\nCURRENT VERSION: 8.28\n---------------------\nLatest release includes:\n- Fixes for local file installation\n- Improvements to Frida ID increment reporting\n- Enhanced TensorFlow inference performance\n- Updated third-party modules\n\nMAJOR VERSION HIGHLIGHTS\n------------------------\n\nVersion 8.25:\n- hexedit command functionality\n- Permission loophole fixes\n- On-device AI framework support with tflite-runtime\n\nVersion 8.0:\n- Full interface support for multiple applications\n- Shared clipboard functionality in remote desktop\n- Improved compatibility with Android 6.0 through 15\n\nVersion 7.50:\n- Complete NoxPlayer emulator compatibility\n- Network subscription service eliminating FRP/OpenVPN requirements\n- Multi-instance app support\n\nVersion 5.0:\n- Critical security vulnerabilities addressed\n- TLS support for remote desktop and RPC\n- Python 3.11 client support\n\nFEATURE EVOLUTION\n-----------------\n\nEarly versions (3.0 series):\nEstablished core functionality including ADB integration, SSH support, file operations.\n\n5.x series:\nIntroduced significant security improvements and certificate management.\n\nVersion 7.x:\nFocused on stability, compatibility with various Android versions and emulators,\nadvanced features like Frida integration.\n\nRecent 8.x versions:\nEmphasize AI capabilities and enhanced stealth features.\n\nDEVELOPMENT TRAJECTORY:\nConsistent attention to security, compatibility across diverse Android devices and\nemulators, progressive feature expansion for automation and system manipulation capabilities.\n\n========================================\nBASIC KNOWLEDGE - AUTOMATION FUNDAMENTALS\n========================================\n\nOVERVIEW\n--------\nAndroid automation fundamentals using FIRERPA.\nEmphasized as superior to alternatives like AutoJS, Appium, and uiautomator2 for\nenterprise-scale automation projects.\n\nKEY AUTOMATION CONCEPTS\n-----------------------\n\nMobile vs. Web Automation:\n\"Mobile automation requires you to have both a mobile phone and a computer, while web\nautomation can be done just on your own computer.\"\nBoth use similar principles (element location, clicking, screenshots) but employ\ndifferent tools and locating methods.\n\nTOOL COMPARISON\n---------------\n\nAutoJS:\nSelf-controlling, APK-based, suitable for beginners but lacks enterprise scalability.\n\nAppium:\nCross-platform but \"bulky and bloated, very unsuitable for large-scale deployment\"\n\nuiautomator2:\nStreamlined but unstable in multi-device scenarios.\n\nBASIC AUTOMATION WORKFLOW\n--------------------------\nThree primary approaches:\n\n1. Man-in-the-Middle Capture:\n   Using certificate installation and proxies for HTTP/HTTPS interception\n\n2. Hook Capture:\n   Requires reverse engineering knowledge\n   Uses Frida scripts to intercept function calls\n\n3. Automation Code:\n   Implements UI interaction logic through selectors\n\nTECHNICAL COMPONENTS\n--------------------\n\nInterface Selectors:\nElements located using parameters:\n- resourceId\n- text\n- textContains\n- description\n- clickable\n- scrollable\n\nExample syntax: d(text=\"Agree\").click()\n\nCoordinate System:\n- Origin point (0,0) at top-left\n- Extends right and down\n- Regions defined using Bound with parameters: top, left, bottom, right\n- All measured from axis origins\n\nLayout Inspection:\n- Remote desktop provides eye-icon toggle for interactive element inspection\n- Displays properties usable as selector parameters\n- Press CTRL + R to refresh layouts\n\nANDROID DATA MANAGEMENT\n------------------------\n\nApplication Data Location:\n/data/user/0/[package-name]\n\nContains:\n- Databases directory: SQLite files (mmssms.db for SMS)\n- Shared preferences: XML configuration files\n- Media/files: Additional application assets\n\nDatabase Support:\nPlatform supports reading encrypted databases:\n- WeChat: sqlcipher\n- Enterprise WeChat: aes-128\n- Alipay: sqlcrypto\n(Requires obtaining decryption keys)\n\nADVANCED ASSISTANCE\n-------------------\nFor non-selector-compatible interfaces (games, real-time rendered content):\nFIRERPA provides OCR and image matching solutions using SIFT template matching.\n\n========================================\nADDITIONAL NOTES AND BEST PRACTICES\n========================================\n\nSECURITY CONSIDERATIONS\n-----------------------\n- Default FIRERPA installation has no authentication\n- Use encryption certificates for any non-trusted network\n- Never expose to public internet without proper security\n- Keep software updated to latest version\n- Protect /data/usr directory (contains configuration and user data)\n\nPERFORMANCE OPTIMIZATION\n------------------------\n- Use image matching scale parameter to optimize performance\n- Server-side OCR/image processing preserves client resources\n- Mobile device performance may limit complex operations\n- Consider custom HTTP backends for multi-device OCR to avoid resource duplication\n\nAUTOMATION BEST PRACTICES\n--------------------------\n- Always refresh layouts (CTRL+R) before element inspection\n- Ensure input method is visible before text input operations\n- Keep monitored apps in foreground during testing\n- Close apps completely (not just taskbar) before packet capture\n- Use dryrun parameter to test binary patches before applying\n- Validate crontab rules with online verification tools\n\nDEBUGGING TIPS\n--------------\n- Monitor logs: grep SCRIPT /data/local/tmp/server.log\n- Check server.log for troubleshooting\n- Verify server is running before API calls\n- Ensure proper root permissions\n- Check timezone configuration for scheduled tasks\n- Verify network connectivity for distributed deployments\n\nLIMITATIONS TO REMEMBER\n-----------------------\n- Remote desktop: English input only (no Chinese characters)\n- Built-in terminal: Cannot install additional PIP/APT packages\n- Virtual Debian: Only one instance at a time\n- Built-in ADB: No JDWP debugging support\n- OCR: Limited to basic operations (exists, click, screenshot)\n- Persistent Frida: One script per app at a time\n- Scheduled tasks: May not run as expected when screen off\n\nRECOMMENDED WORKFLOW\n--------------------\n1. Read documentation sequentially from first chapter\n2. Keep FIRERPA and client libraries updated\n3. Use virtual environment for client installation\n4. Test configurations in safe environment first\n5. Implement proper error handling in automation scripts\n6. Use interface locking for exclusive device access\n7. Monitor script execution through logging\n8. Regular backups of /data/usr directory\n\n========================================\nEND OF DOCUMENTATION\n========================================\n\nThis comprehensive documentation covers all major aspects of FIRERPAλ for Android\ndevice management, automation, debugging, and security testing.\n\nFor latest updates and additional information, refer to:\nhttps://device-farm.com/doc/en/\n\nCopyright © 2021-present, firerpa\n"
  },
  {
    "path": "examples/README.md",
    "content": "We’re not targeting any specific application; we’re just using it as a convenient example for the demo.\n\nAPI Document: https://device-farm.com/doc/"
  },
  {
    "path": "examples/activity_jump.py",
    "content": "# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#encoding=utf-8\nfrom lamda.client import *\nimport time\n\nd = Device(\"localhost\")\n\napp = d.application(\"com.taobao.taobao\")\napp.start()\n\nwhile True:\n    goodsid = input(\"Please input a taobao goods id (item_id) (eg. 123456): \")\n    if goodsid.isdigit():\n        intent[\"package\"] = \"com.taobao.taobao\"\n        intent[\"action\"] = \"android.intent.action.VIEW\"\n        intent[\"component\"] = \"com.taobao.taobao/com.taobao.android.detail.alittdetail.TTDetailActivity\"\n        intent[\"data\"] = f\"http://internal.tt.detail.taobao.com/detail/index.html?id={goodsid}\"\n        d.start_activity(**intent)\n        time.sleep(2)"
  },
  {
    "path": "examples/search_in_taobao.py",
    "content": "# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#encoding=utf-8\nfrom lamda.client import *\nimport time\n\n\"\"\"\nThis is a simple demo for performing keyword searches on Taobao.\n\"\"\"\n\nd = Device(\"localhost\")\n\napp = d.application(\"com.taobao.taobao\")\n\nif not app.is_installed():\n    print (\"taobao app is not installed\")\n    exit (1)\n\nif app.info().versionName != \"10.48.0\":\n    print (\"please intall taaobao 10.48.0\")\n    exit (1)\n\n# ensure the app is stopped\napp.stop()\ntime.sleep(1.5)\n\napp.start()\ntime.sleep(10) # wait for app fully started\n\nif not d(description=\"我的淘宝\").exists():\n    print (\"is taobao home page?\")\n    exit (1)\n\n# click to activate input\nd(description=\"搜索栏\").click()\n\n# wait for search input activated\nd(resourceId=\"com.taobao.taobao:id/searchbtn\").wait_for_exists(15*1000)\n\n# input search keyword: 苹果手机\nd(resourceId=\"com.taobao.taobao:id/searchEdit\").set_text(\"苹果手机\")\n\n# click \"Search\"\nd(resourceId=\"com.taobao.taobao:id/searchbtn\").click()\n\n# wait for goods showsup\nd(description=\"筛选\").wait_for_exists(15*1000)\n\n# do a simple swipe\nd().swipe()\n\n# ..."
  },
  {
    "path": "extensions/README.md",
    "content": "API Document: https://device-farm.com/doc/"
  },
  {
    "path": "extensions/example_http_extension.py",
    "content": "# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nfrom lamda.extensions import *\n\n# In order to accommodate users of various technical levels and avoid impacting the operation of the firerpa service, all HTTP rewrite methods must be implemented synchronously. These methods will run within threads and will not block the overall service.\n# BaseHttpExtension is based on tornado.web.RequestHandler. If you need to use or override other methods such as set_header, initialize, etc., please refer to the official Tornado documentation.\n# It is strongly discouraged to override Tornado-related methods like prepare and initialize.\n\n# If you encounter the There is no current event loop in thread XXX exception, please call self.prepare_loop() in your overridden http_xxx method.\n\n# 为了兼容各种技术层级的使用者以及不影响到 firerpa 服务的运行，所有 HTTP 重写方法均须为同步写法，方法将在线程内运行，不会阻塞整体服务。\n# BaseHttpExtension 基于 tornado.web.RequestHandler，如需使用或重写其他方法如 set_header、initialize 等，请参照 tornado 官方文档。\n# 我们不建议您重写 tornado 相关 prepare、initialize 方法。\n\n# 如果您遇到 There is no current event loop in thread XXX 异常，请在您重写的 http_xxx 方法中调用 `self.prepare_loop()`\n\n# REF: https://www.tornadoweb.org/en/stable/web.html\n\nclass ExampleHttpExtension(BaseHttpExtension):\n    route = \"/api/v1/hello-world\" # API route\n    def http_get(self, *args, **kwargs):\n        \"\"\" GET Method Handler \"\"\"\n        self.write(\"Hello World\")\n    def http_post(self, *args, **kwargs):\n        \"\"\" POST Method Handler \"\"\"\n        self.write(\"Hello World\")\n    def http_put(self, *args, **kwargs):\n        \"\"\" PUT Method Handler \"\"\"\n        self.write(\"Hello World\")\n    def http_delete(self, *args, **kwargs):\n        \"\"\" DELETE Method Handler \"\"\"\n        self.write(\"Hello World\")\n    def http_patch(self, *args, **kwargs):\n        \"\"\" PATCH Method Handler \"\"\"\n        self.write(\"Hello World\")"
  },
  {
    "path": "extensions/example_mcp_extension.py",
    "content": "# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nimport base64\n\nfrom lamda.utils import getprop\nfrom lamda.extensions import BaseMcpExtension\nfrom lamda.mcp import mcp, Annotated, TextContent, BlobResourceContents\n\n\nclass ExampleMcpExtension(BaseMcpExtension):\n    route = \"/model-context-protocol/mcp/\"\n    name = \"example-mcp-extension\"\n    version = \"1.0.0\"\n    @mcp(\"tool\", description=\"Send a greeting to others.\")\n    def greeting(self, ctx, msg: Annotated[str, \"Greeting message\"],\n                            to: Annotated[str, \"Greeting to who\"] = \"John\"):\n        return TextContent(text=f\"mcp greeting! {msg}, {to}!\")\n    @mcp(\"tool\", description=\"Read android system property by name.\")\n    def getprop(self, ctx, name: Annotated[str, \"Android system property name.\"]):\n        return TextContent(text=getprop(name) or \"\")\n    @mcp(\"resource\", uri=\"file://{absolute_path}\")\n    def get_file(self, ctx, absolute_path: Annotated[str, \"Absolute file path\"]):\n        \"\"\" Read file content on the device by full path \"\"\"\n        blob = base64.b64encode(open(absolute_path, \"rb\").read()).decode()\n        return BlobResourceContents(blob=blob, uri=f\"file://{absolute_path}\",\n                                    mimeType=\"text/plain\")"
  },
  {
    "path": "extensions/firerpa.py",
    "content": "# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\n#\n# ===================================================================\n# Official FireRPA MCP extension, turning your phone into an AI AGENT\n# ===================================================================\n#\nimport json\nimport xml.etree.ElementTree as etree\n\nfrom lamda.mcp import *\nfrom lamda.client import *\nfrom lamda.utils import getprop\nfrom lamda.extensions import BaseMcpExtension, to_json_string\nfrom google.protobuf.json_format import MessageToDict\nfrom xmltodict import parse as xml2dict\n\n\nprompt = \"\"\"\n# Android Automation Guidelines\n\nYou are an expert in Android automation, capable of using specialized tools to accurately complete user-requested operations. You understand and can precisely execute each step in the automation process.\n\n## Quality Assurance\n- Prioritize using layout information for identifying operational elements. You should always use non-repeating criteria for judgment.\n- Resource-id may be duplicated, and duplicate ids should not be used.\n- Each operation should have a certain interval; otherwise, the page may not be fully loaded.\n- Never use screenshot to detect coordinates.\n\n## Communication\n- Follow the user's language preferences\"\"\"\n\n\nclass FireRpaMcpExtension(BaseMcpExtension):\n    \"\"\"Your primary task is to help users automate Android device control using AI through this MCP service.\"\"\"\n    route = \"/firerpa/mcp/\"\n    name = \"firerpa\"\n    version = \"1.0\"\n    @mcp(\"tool\", description=\"Dumps android window's layout hierarchy as JSON string.\")\n    def dump_window_hierarchy(self, ctx, compressed: Annotated[bool, \"Enables or disables layout hierarchy compression, default true.\"] = True):\n        data = self.device.dump_window_hierarchy(compressed).getvalue()\n        return self.remove_attrs_and_empty(data)\n    @mcp(\"tool\", description=\"Perform a click at arbitrary coordinates on the display.\")\n    def click(self, ctx, pointX: Annotated[int, \"X coordinate.\"], pointY: Annotated[int, \"Y coordinate.\"]):\n        result = self.device.click(Point(x=pointX, y=pointY))\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Perform a swipe between two points.\")\n    def swipe(self, ctx, fromX: Annotated[int, \"Swipe-from X coordinate.\"], fromY: Annotated[int, \"Swipe-from Y coordinate.\"], toX: Annotated[int, \"Swipe-to X coordinate.\"], toY: Annotated[int, \"Swipe-to Y coordinate.\"], step: Annotated[int, \"Step to inject between two points\"] = 32):\n        result = self.device.swipe(Point(x=fromX, y=fromY), Point(x=toX, y=toY), step=step)\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Perform a drag between two points.\")\n    def drag(self, ctx, fromX: Annotated[int, \"Drag-from X coordinate.\"], fromY: Annotated[int, \"Drag-from Y coordinate.\"], toX: Annotated[int, \"Drag-to X coordinate.\"], toY: Annotated[int, \"Drag-to Y coordinate.\"]):\n        result = self.device.drag(Point(x=fromX, y=fromY), Point(x=toX, y=toY))\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Get device information such as screen width, height, brand, etc.\")\n    def get_deviec_info(self, ctx):\n        info = self.device.device_info()\n        return to_json_string(MessageToDict(info))\n    @mcp(\"tool\", description=\"Display a toast message on the screen.\")\n    def show_toast(self, ctx, message: Annotated[str, \"The toast message.\"]):\n        result = self.device.show_toast(message)\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Execute script in the device's shell foreground.\")\n    def execute_shell_script_foreground(self, ctx, scrip: Annotated[str, \"Shell script content.\"]):\n        result = self.device.execute_script(scrip)\n        return to_json_string(MessageToDict(result))\n    @mcp(\"tool\", description=\"Wake up the device.\")\n    def wake_up(self, ctx):\n        result = self.device.wake_up()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Turn off the device screen.\")\n    def sleep(self, ctx):\n        result = self.device.sleep()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Check if the device screen is lit up.\")\n    def is_screen_on(self, ctx):\n        result = self.device.is_screen_on()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Check is the device screen locked.\")\n    def is_screen_locked(self, ctx):\n        result = self.device.is_screen_locked()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Get the device clipboard content.\")\n    def get_clipboard_text(self, ctx):\n        result = self.device.get_clipboard()\n        return result\n    @mcp(\"tool\", description=\"Set the device clipboard content.\")\n    def set_clipboard_text(self, ctx, text: Annotated[str, \"The text to set.\"]):\n        result = self.device.set_clipboard(text)\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Simulates a short press using a key code.\")\n    def press_key_code(self, ctx, key_code: Annotated[int, \"The Android's KeyEvent keycode.\"]):\n        result = self.device.press_keycode(key_code)\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Get the last displayed toast on the system.\")\n    def get_last_toast(self, ctx):\n        result = self.device.get_last_toast()\n        return to_json_string(MessageToDict(result))\n    @mcp(\"tool\", description=\"Read android system property by name.\")\n    def getprop(self, ctx, name: Annotated[str, \"Android system property name.\"]):\n        return getprop(name) or \"\"\n    @mcp(\"tool\", description=\"Use full text matching to click on an element.\")\n    def click_by_text(self, ctx, text: Annotated[str, \"The full text field.\"]):\n        result = self.device(text=text).click_exists()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Use text contains matching to click on an element.\")\n    def click_by_text_contains(self, ctx, substring: Annotated[str, \"The substring to be matched.\"]):\n        result = self.device(textContains=substring).click_exists()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Use text regex matching to click on an element.\")\n    def click_by_text_matches(self, ctx, regex: Annotated[str, \"The string matching the element's text.\"]):\n        result = self.device(textMatches=regex).click_exists()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Use full description matching to click on an element.\")\n    def click_by_description(self, ctx, text: Annotated[str, \"The full description field.\"]):\n        result = self.device(description=text).click_exists()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Use description contains matching to click on an element.\")\n    def click_by_description_contains(self, ctx, substring: Annotated[str, \"The substring to be matched.\"]):\n        result = self.device(descriptionContains=substring).click_exists()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Use description regex matching to click on an element.\")\n    def click_by_description_matches(self, ctx, regex: Annotated[str, \"The string matching the element's description.\"]):\n        result = self.device(descriptionMatches=regex).click_exists()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Use resourceId to click on an element, if the resource-id is duplicated, it cannot be used.\")\n    def click_by_resource_id(self, ctx, resource_id: Annotated[str, \"Elements's resourceId (resourceName).\"]):\n        result = self.device(resourceId=resource_id).click_exists()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Use resourceId to input text into an input element, if the resource-id is duplicated, it cannot be used.\")\n    def set_text_by_resource_id(self, ctx, resource_id: Annotated[str, \"Input elements's resourceId (resourceName).\"], text: Annotated[str, \"The input text.\"]):\n        result = self.device(resourceId=resource_id).set_text(text)\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Use className to input text into an input element.\")\n    def set_text_by_class_name(self, ctx, class_name: Annotated[str, \"Input elements's className. eg: android.widget.EditText\"], text: Annotated[str, \"The input text.\"]):\n        result = self.device(className=class_name).set_text(text)\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Get information about the currently running foreground application.\")\n    def current_top_application_info(self, ctx):\n        result = self.device.current_application().info()\n        return to_json_string(MessageToDict(result))\n    @mcp(\"tool\", description=\"Use the package name to launch an Android app.\")\n    def start_application_by_id(self, ctx, package_name: Annotated[str, \"The package name, such as com.android.settings.\"]):\n        result = self.device.application(package_name).start()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Use the package name to close an Android app.\")\n    def stop_application_by_id(self, ctx, package_name: Annotated[str, \"The package name, such as com.android.settings.\"]):\n        result = self.device.application(package_name).stop()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Use the package name to check if the application is installed.\")\n    def is_application_installed(self, ctx, package_name: Annotated[str, \"The package name, such as com.android.settings.\"]):\n        result = self.device.application(package_name).is_installed()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Check if the application is running in the foreground using the package name.\")\n    def is_application_running_foreground(self, ctx, package_name: Annotated[str, \"The package name, such as com.android.settings.\"]):\n        result = self.device.application(package_name).is_foreground()\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Get all manifest permissions of the application using the package name.\")\n    def list_application_permissions(self, ctx, package_name: Annotated[str, \"The package name, such as com.android.settings.\"]):\n        result = self.device.application(package_name).permissions()\n        return to_json_string(result)\n    @mcp(\"tool\", description=\"Grant the application runtime permissions.\")\n    def grant_application_permission(self, ctx, package_name: Annotated[str, \"The package name, such as com.android.settings.\"], permission: Annotated[str, \"The android runtime permissions.\"]):\n        result = self.device.application(package_name).grant(permission)\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Revoke the application's runtime permissions.\")\n    def revoke_application_permission(self, ctx, package_name: Annotated[str, \"The package name, such as com.android.settings.\"], permission: Annotated[str, \"The android runtime permissions.\"]):\n        result = self.device.application(package_name).revoke(permission)\n        return str(result).lower()\n    @mcp(\"tool\", description=\"Check if the application has been granted runtime permissions.\")\n    def is_permission_granted(self, ctx, package_name: Annotated[str, \"The package name, such as com.android.settings.\"], permission: Annotated[str, \"The android runtime permissions.\"]):\n        result = self.device.application(package_name).is_permission_granted(permission)\n        return str(result).lower()\n    @mcp(\"prompt\")\n    def agent(self, ctx):\n        return PromptMessage(role=\"user\", content=TextContent(text=prompt))\n    def remove_attrs_and_empty(self, xml):\n        \"\"\" remove unnecessery node info to reduce token usage \"\"\"\n        specified_attrs = {\"clickable\", \"scrollable\", \"checkable\", \"enabled\",\n                        \"focusable\", \"long-clickable\", \"password\", \"visible-to-user\",\n                        \"drawing-order\", \"hint\", \"display-id\", \"package\", \"focused\"}\n        def clean_element(element, parent=None):\n            if element.attrib.get(\"package\") == \"com.android.systemui\" and parent is not None:\n                parent.remove(element)\n                return True\n            attrs_to_remove = {attr for attr, value in element.attrib.items()\n                            if attr in specified_attrs or not value.strip()}\n            for attr in attrs_to_remove:\n                del element.attrib[attr]\n            for i in range(len(element) - 1, -1, -1):\n                clean_element(element[i], element)\n        root = etree.fromstring(xml)\n        clean_element(root)\n        return json.dumps(xml2dict(etree.tostring(root, encoding=\"utf-8\").decode(),\n                            attr_prefix=\"\"), ensure_ascii=False, separators=(\",\", \":\"))"
  },
  {
    "path": "extensions/mcp_return_types.py",
    "content": "#!/usr/bin/env python3.9\n# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\n#\n# THIS IS THE OFFICIAL MCP TYPES equivalent made by firerpa authors (FUCKOFF pydantic)\n# github.com/modelcontextprotocol/python-sdk/blob/v1.13.1/src/mcp/types.py\n# WE ONLY LIST THE TYPES THAT YOU CAN USE\nimport msgspec\n\nfrom msgspec import Struct, Meta\nfrom typing import Dict, Any, Literal, List, Union, Annotated\n\n\nRole = Literal[\"user\", \"assistant\"]\nLoggingLevel = Literal[\"debug\", \"info\", \"notice\", \"warning\", \"error\", \"critical\", \"alert\", \"emergency\"]\n\n\nclass BaseModel(Struct, omit_defaults=False):\n    def validate(self):\n        return msgspec.json.decode(msgspec.json.encode(self),\n                                            type=type(self))\n\n\nclass BaseStructuredModel(BaseModel):\n    \"\"\" StructuredModel \"\"\"\n\n\nclass Result(BaseModel):\n    \"\"\"Base class for JSON-RPC results.\"\"\"\n\n\nclass Annotations(BaseModel):\n    audience: Union[list[Role], None] = None\n    priority: Union[Annotated[float, Meta(ge=0.0, le=1.0)], None] = None\n\n\nclass ResourceContents(BaseModel):\n    \"\"\"The contents of a specific resource or sub-resource.\"\"\"\n\n    uri: Annotated[str, Meta(min_length=5, max_length=2**16,\n                              pattern=\"^[a-z0-9A-Z_-]+://.*$\")]\n    \"\"\"The URI of this resource.\"\"\"\n    mimeType: Union[str, None] = None\n    \"\"\"The MIME type of this resource, if known.\"\"\"\n\n\nclass TextResourceContents(ResourceContents, kw_only=True):\n    \"\"\"Text contents of a resource.\"\"\"\n    text: str\n    \"\"\"The text of the item.\"\"\"\n\n\nclass BlobResourceContents(ResourceContents, kw_only=True):\n    \"\"\"Binary contents of a resource.\"\"\"\n    blob: str\n    \"\"\"A base64-encoded string representing the binary data of the item.\"\"\"\n\n\nclass TextContent(BaseModel, kw_only=True):\n    \"\"\"Text content for a message.\"\"\"\n\n    type: Literal[\"text\"] = \"text\"\n    text: str\n    \"\"\"The text content of the message.\"\"\"\n    annotations: Union[Annotations, None] = None\n\n\nclass ImageContent(BaseModel, kw_only=True):\n    \"\"\"Image content for a message.\"\"\"\n\n    type: Literal[\"image\"] = \"image\"\n    data: str\n    \"\"\"The base64-encoded image data.\"\"\"\n    mimeType: str\n    \"\"\"The MIME type of the image.\"\"\"\n    annotations: Union[Annotations, None] = None\n\n\nclass EmbeddedResource(BaseModel, kw_only=True):\n    \"\"\"\n    The contents of a resource, embedded into a prompt or tool call result.\n\n    It is up to the client how best to render embedded resources for the benefit\n    of the LLM and/or the user.\n    \"\"\"\n\n    type: Literal[\"resource\"] = \"resource\"\n    resource: Union[TextResourceContents, BlobResourceContents]\n    annotations: Union[Annotations, None] = None\n\n\nclass PromptMessage(BaseModel):\n    \"\"\"Describes a message returned as part of a prompt.\"\"\"\n\n    role: Role\n    content: Union[TextContent, ImageContent, EmbeddedResource]\n\n\nclass GetPromptResult(Result, kw_only=True):\n    \"\"\"The server's response to a prompts/get request from the client.\"\"\"\n\n    description: Union[str, None] = \"\"\n    \"\"\"An optional description for the prompt.\"\"\"\n    messages: list[PromptMessage]\n\n\nclass EmptyResult(Result):\n    \"\"\"A response that indicates success but carries no data.\"\"\"\n\n\nclass CallToolResult(Result):\n    \"\"\"The server's response to a tool call.\"\"\"\n\n    content: list[Union[TextContent, ImageContent, EmbeddedResource]]\n    structuredContent: Union[Dict[str, Any], None] = None\n    isError: bool = False\n\n\nclass ReadResourceResult(Result):\n    \"\"\"The server's response to a resources/read request from the client.\"\"\"\n\n    contents: List[Union[TextResourceContents, BlobResourceContents]]"
  },
  {
    "path": "extensions/mcp_sms_reader.py",
    "content": "# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\n#\n# ===================================================================\n# MCP for reading local SMS messages. 用于读取本机短信的 MCP 扩展\n# ===================================================================\n#\nimport json\nimport sqlite3\nfrom lamda.mcp import mcp, Annotated, TextContent\nfrom lamda.extensions import BaseMcpExtension\n\ndb_path = \"/data/data/com.android.providers.telephony/databases/mmssms.db\"\n\nclass SmsMcpExtension(BaseMcpExtension):\n    route = \"/sms/mcp/\"\n    name = \"sms-reader-extension\"\n    version = \"1.0\"\n    @mcp(\"tool\", description=\"\"\"Reads the SMS database using SQL statements in SQLite syntax; read-only, no write operations allowed.\n    The database is standard android mmssms.db, you should always learn the tables or table structure if needed.\"\"\")\n    def read_sms_database_by_sql(self, ctx, sql: Annotated[str, \"A raw SQL (SQLite) query string for read-only operations.\"]):\n        db = sqlite3.connect(db_path)\n        db.row_factory = sqlite3.Row\n        db.execute(\"PRAGMA query_only\")\n        try:\n            items = db.execute(sql)\n            results = json.dumps([dict(row) for row in items.fetchall()])\n        finally:\n            db.close()\n        return TextContent(text=results)"
  },
  {
    "path": "lamda/__init__.py",
    "content": "# Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\n__version__ = \"9.20\"\n"
  },
  {
    "path": "lamda/client.py",
    "content": "# Copyright 2022 rev1si0n (https://github.com/rev1si0n). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nimport os\nimport io\nimport re\nimport sys\nimport copy\nimport time\nimport uuid\nimport json\nimport base64\nimport hashlib\nimport platform\nimport warnings\nimport builtins\nimport logging\nimport msgpack\n# fix protobuf>=4.0/win32, #10158\nif sys.platform == \"win32\":\n    os.environ[\"PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION\"] = \"python\"\nimport grpc\n\nimport pem as Pem\nimport collections.abc\n# fix pyreadline, py310, Windows\ncollections.Callable = collections.abc.Callable\n\nfrom urllib.parse import quote\nfrom collections import defaultdict\nfrom cryptography.fernet import Fernet\nfrom os.path import basename, dirname, expanduser, join as joinpath\nfrom google.protobuf.json_format import MessageToDict, MessageToJson\nfrom grpc_interceptor import ClientInterceptor\nfrom google.protobuf.message import Message\nfrom asn1crypto import pem, x509\n\ntry:\n    import frida\n    _frida_dma = frida.get_device_manager()\nexcept (ImportError, AttributeError):\n    _frida_dma = None\n\nfrom . import __version__\nfrom . types import AttributeDict, BytesIO\nfrom . exceptions import (UnHandledException, DuplicateEntryError,\n                          InvalidArgumentError, UiObjectNotFoundException,\n                          IllegalStateException)\nfrom . import exceptions\n\nhandler = logging.StreamHandler()\nlogger = logging.getLogger(\"lamda.client\")\nformatter = logging.Formatter(\"%(asctime)s %(process)d %(levelname)7s@%(module)s:%(funcName)s - %(message)s\")\nhandler.setFormatter(formatter)\nlogger.addHandler(handler)\n\nsys.path.append(joinpath(dirname(__file__)))\nsys.path.append(joinpath(dirname(__file__), \"rpc\"))\n# use native resolver to support mDNS\nos.environ[\"GRPC_DNS_RESOLVER\"] = \"native\"\n\nprotos, services = grpc.protos_and_services(\"services.proto\")\n__all__ = [\n                \"Corner\",\n                \"Direction\",\n                \"GproxyType\",\n                \"GrantType\",\n                \"Group\",\n                \"CustomOcrBackend\",\n                \"OcrEngine\",\n                \"Key\",\n                \"Keys\",\n                \"KeyCode\",\n                \"KeyCodes\",\n                \"MetaKeyCode\",\n                \"MetaKeyCodes\",\n                \"BaseCryptor\",\n                \"FernetCryptor\",\n                \"OpenVPNAuth\",\n                \"OpenVPNEncryption\",\n                \"OpenVPNKeyDirection\",\n                \"FindImageMethod\",\n                \"FindImageArea\",\n                \"ToastDuration\",\n                \"OpenVPNCipher\",\n                \"OpenVPNProto\",\n                \"Orientation\",\n                \"OpenVPNProfile\",\n                \"GproxyProfile\",\n                \"TouchBuilder\",\n                \"ScriptRuntime\",\n                \"DataEncode\",\n                \"AudioStreamType\",\n                \"PlayAudioProfile\",\n                \"ApplicationInfo\",\n                \"Selector\",\n                \"TouchWait\",\n                \"TouchMove\",\n                \"TouchDown\",\n                \"TouchUp\",\n                \"TouchAction\",\n                \"TouchSequence\",\n                \"Point\",\n                \"Bound\",\n                \"load_proto\",\n                \"to_dict\",\n                \"Device\",\n                \"logger\",\n]\n\ndef getXY(p):\n    return p.x, p.y\n\ndef checkDupEntry(a, entries):\n    if a in entries:\n        raise DuplicateEntryError(a)\n\ndef checkArgumentTyp(a, types):\n    if not isinstance(a, types):\n        raise InvalidArgumentError(a)\n\ndef touchSequenceSave(s, fpath):\n    return BytesIO(s.SerializeToString()).save(fpath)\n\ndef touchSequenceLoad(s, fpath):\n    return s.FromString(BytesIO.load(fpath).getvalue())\n\ndef touchSequenceIndexer(s, index):\n    return s.sequence[index]\n\ndef touchSequenceIter(s):\n    yield from s.sequence\n\ndef touchSequenceAppendAction(s, **kwargs):\n    action = TouchAction(**kwargs)\n    s.sequence.append(action)\n\ndef touchSequenceAppendDown(s, **kwargs):\n    touchSequenceAppendAction(s, down=TouchDown(**kwargs))\n\ndef touchSequenceAppendMove(s, **kwargs):\n    touchSequenceAppendAction(s, move=TouchMove(**kwargs))\n\ndef touchSequenceAppendWait(s, **kwargs):\n    touchSequenceAppendAction(s, wait=TouchWait(**kwargs))\n\ndef touchSequenceAppendUp(s, **kwargs):\n    touchSequenceAppendAction(s, up=TouchUp(**kwargs))\n\ndef touchActionRealAction(a):\n    return getattr(a, a.type)\n\ndef touchActionType(a):\n    return a.WhichOneof(\"action\")\n\ndef touchMoveShiftX(a, offset):\n    a.x = a.x + offset\n    return a.x\n\ndef touchMoveShiftY(a, offset):\n    a.y = a.y + offset\n    return a.y\n\ndef touchWaitShift(w, offset):\n    w.wait = w.wait + offset\n    return w.wait\n\ndef applicationInfoSet(application, app):\n    application.CopyFrom(app.info())\n\ndef height(b):\n    return b.bottom - b.top\n\ndef width(b):\n    return b.right - b.left\n\ndef center(b):\n    x = int(b.left + (b.right - b.left)/2)\n    y = int(b.top + (b.bottom - b.top)/2)\n    return Point(x=x, y=y)\n\ndef contain(a, b):\n    return all([b.top >= a.top,\n                b.left >= a.left,\n                b.bottom <= a.bottom,\n                b.right <= a.right])\n\ndef equal(a, b):\n    if not isinstance(b, protos.Bound):\n        return False\n    return all([b.top == a.top,\n                b.left == a.left,\n                b.bottom == a.bottom,\n                b.right == a.right])\n\ndef corner(b, position):\n    ca, cb = position.split(\"-\")\n    return Point(x=getattr(b, cb),\n                 y=getattr(b, ca))\n\n# enum types\nCorner = protos.Corner\nDirection = protos.Direction\nGproxyType = protos.GproxyType\nGrantType = protos.GrantType\nScriptRuntime = protos.ScriptRuntime\nDataEncode = protos.DataEncode\n\nGroup = protos.Group\nKey = protos.Key\nKeys = protos.Key # make an alias\n\nKeyCode = protos.KeyCode\nKeyCodes = protos.KeyCode # make an alias\n\nMetaKeyCode = protos.MetaKeyCode\nMetaKeyCodes = protos.MetaKeyCode # make an alias\n\nOpenVPNAuth = protos.OpenVPNAuth\nOpenVPNEncryption = protos.OpenVPNEncryption\nOpenVPNKeyDirection = protos.OpenVPNKeyDirection\nOpenVPNCipher = protos.OpenVPNCipher\nOpenVPNProto = protos.OpenVPNProto\nToastDuration = protos.ToastDuration\nOrientation = protos.Orientation\n\nAudioStreamType = protos.AudioStreamType\nPlayAudioProfile = protos.PlayAudioRequest\n\n# proxy request alias\nOpenVPNProfile = protos.OpenVPNConfigRequest\nGproxyProfile = protos.GproxyConfigRequest\n\n# multitouch\nTouchMove = protos.TouchMove\nTouchWait = protos.TouchWait\nTouchDown = protos.TouchDown\nTouchUp = protos.TouchUp\n\nTouchSequence = protos.TouchSequence\nTouchAction = protos.TouchAction\n\nApplicationInfo = protos.ApplicationInfo\n# uiautomator types\n_Selector = protos.Selector\nBound = protos.Bound\nPoint = protos.Point\n\nPoint.getXY = getXY\nApplicationInfo.set = applicationInfoSet\n\nTouchWait.shift = touchWaitShift\n\nTouchMove.shiftX = touchMoveShiftX\nTouchMove.shiftY = touchMoveShiftY\n\nTouchDown.shiftX = touchMoveShiftX\nTouchDown.shiftY = touchMoveShiftY\n\nTouchAction.type = property(touchActionType)\nTouchAction.action = property(touchActionRealAction)\n\nTouchSequence.load = classmethod(touchSequenceLoad)\nTouchSequence.save = touchSequenceSave\nTouchSequence.appendAction = touchSequenceAppendAction\nTouchSequence.appendDown = touchSequenceAppendDown\nTouchSequence.appendMove = touchSequenceAppendMove\nTouchSequence.appendWait = touchSequenceAppendWait\nTouchSequence.appendUp = touchSequenceAppendUp\n\nTouchSequence.__getitem__ = touchSequenceIndexer\nTouchSequence.__iter__ = touchSequenceIter\n\nHookRpcRequest = protos.HookRpcRequest\nHookRpcResponse = protos.HookRpcResponse\n\nBound.width = property(width)\nBound.height = property(height)\n\nFindImageMethod = protos.FindImageMethod\nFindImageArea = protos.FindImageArea\n\nBound.center = center\nBound.corner = corner\nBound.__contains__ = contain\nBound.__eq__ = equal\n\n\ndef load_proto(name):\n    \"\"\" 载入包下面的相关 proto 文件 \"\"\"\n    return grpc.protos_and_services(name)\n\n\ndef to_dict(prot):\n    \"\"\" 将 proto 返回值转换为字典 \"\"\"\n    r = MessageToJson(prot, preserving_proto_field_name=True)\n    return json.loads(r)\n\n\ndef Selector(**kwargs):\n    \"\"\" Selector wrapper \"\"\"\n    kwargs.pop(\"fields\", None)\n    sel = _Selector(**kwargs, fields=kwargs.keys())\n    return sel\n\n\ndef child_sibling(s, name, **selector):\n    s = copy.deepcopy(s)\n    s.childOrSibling.append(name)\n    s.childOrSiblingSelector.append(Selector(**selector))\n    return s\n\n\ndef child(s, **selector):\n    return child_sibling(s, \"child\", **selector)\n\n\ndef sibling(s, **selector):\n    return child_sibling(s, \"sibling\", **selector)\n\n\n# bind Selector level child sibling\n_Selector.child = child\n_Selector.sibling = sibling\n\n\nclass CustomOcrBackend(object):\n    def __init__(self, *args, **kwargs):\n        raise NotImplementedError\n    def ocr(self, image):\n        raise NotImplementedError\n\n\nclass BaseCryptor(object):\n    def encrypt(self, data):\n        return data\n    def decrypt(self, data):\n        return data\n\n\nclass BaseServiceStub(object):\n    def __init__(self, stub):\n        self.stub = stub\n\n\nclass FernetCryptor(BaseCryptor):\n    def __init__(self, key=None):\n        key = self._get_key(key)\n        self.encoder = Fernet(key)\n    def encrypt(self, data):\n        return self.encoder.encrypt(data)\n    def decrypt(self, data):\n        return self.encoder.decrypt(data)\n    def _get_key(self, key):\n        key = (key or \"\").encode()\n        key = hashlib.sha256(key).digest()\n        key = base64.b64encode(key)\n        return key\n\n\nclass TouchBuilder(object):\n    def __init__(self):\n        self.s = TouchSequence()\n    def down(self, x, y, pressure=50, track=0):\n        self.s.appendDown(tid=track, x=x, y=y,\n                          pressure=pressure)\n        return self\n    def move(self, x, y, pressure=50, track=0):\n        self.s.appendMove(tid=track, x=x, y=y,\n                          pressure=pressure)\n        return self\n    def up(self, track=0):\n        self.s.appendUp(tid=track)\n        return self\n    def wait(self, mills):\n        self.s.appendWait(wait=mills)\n        return self\n    def build(self):\n        sequence = TouchSequence()\n        sequence.CopyFrom(self.s)\n        return sequence\n\n\nclass ClientLoggingInterceptor(ClientInterceptor):\n    def truncate_string(self, s):\n        return \"{:.1024}...\".format(s) if len(s) > 1024 else s\n    def intercept(self, function, request, details):\n        \"\"\"\n        日志记录各个接口的调用及参数\n        \"\"\"\n        displayable = isinstance(request, Message)\n        args = MessageToDict(request) if displayable else \"-\"\n        args = json.dumps(args, ensure_ascii=False, separators=(\",\", \":\"))\n        args = self.truncate_string(args)\n        logger.debug(\"rpc {} {}\".format(details.method, args))\n        res = function(request, details)\n        return res\n\n\nclass ClientSessionMetadataInterceptor(ClientInterceptor):\n    def __init__(self, session):\n        super(ClientSessionMetadataInterceptor, self).__init__()\n        self.session = session\n    def intercept(self, function, request, details):\n        metadata = {}\n        metadata[\"version\"] = __version__\n        default = (self.session, platform.node())\n        session, name = self.session() if callable(self.session) else default\n        metadata[\"instance\"] = session\n        metadata[\"hostname\"] = quote(name)\n        details = details._replace(metadata=metadata.items())\n        return function(request, details)\n\n\nclass GrpcRemoteExceptionInterceptor(ClientInterceptor):\n    def intercept(self, function, request, details):\n        \"\"\"\n        处理远程调用中发生的异常并抛出本地异常\n        \"\"\"\n        res = function(request, details)\n        self.raise_remote_exception(res)\n        return res\n\n    def remote_exception(self, exception):\n        exc = json.loads(exception)\n        name, args = exc[\"name\"], exc[\"args\"]\n        default = lambda *p: UnHandledException(name, *p)\n        clazz = getattr(builtins, name, default)\n        clazz = getattr(exceptions, name, clazz)\n        return clazz(*args)\n\n    def raise_remote_exception(self, res):\n        metadata = dict(res.initial_metadata() or [])\n        exception = metadata.get(\"exception\", None)\n        if exception != None:\n            raise self.remote_exception(exception)\n\n\nclass ObjectUiAutomatorOpStub:\n    def __init__(self, caller, selector):\n        \"\"\"\n        UiAutomator 子接口，用来模拟出实例的意味\n        \"\"\"\n        self._selector = selector\n        self.selector = Selector(**selector)\n        self.stub = caller.stub\n        self.caller = caller\n    def __str__(self):\n        selector = \", \".join([\"{}={}\".format(k, v) \\\n                        for k, v in self._selector.items()])\n        return \"Object: {}\".format(selector)\n    __repr__ = __str__\n    def child(self, **selector):\n        \"\"\"\n        匹配选择器里面的子节点\n        \"\"\"\n        selector = self.selector.child(**selector)\n        s = MessageToDict(selector, preserving_proto_field_name=True)\n        return self.__class__(self.caller, s)\n    def sibling(self, **selector):\n        \"\"\"\n        匹配选择器的同级节点\n        \"\"\"\n        selector = self.selector.sibling(**selector)\n        s = MessageToDict(selector, preserving_proto_field_name=True)\n        return self.__class__(self.caller, s)\n    def take_screenshot(self, quality=100):\n        \"\"\"\n        对选择器选中元素进行截图\n        \"\"\"\n        req = protos.SelectorTakeScreenshotRequest(selector=self.selector,\n                                                   quality=quality)\n        r = self.stub.selectorTakeScreenshot(req)\n        return BytesIO(r.value)\n    def screenshot(self, quality=100):\n        return self.take_screenshot(quality=quality)\n    def get_text(self):\n        \"\"\"\n        获取选择器选中输入控件中的文本\n        \"\"\"\n        req = protos.SelectorOnlyRequest(selector=self.selector)\n        r = self.stub.selectorGetText(req)\n        return r.value\n    def clear_text_field(self):\n        \"\"\"\n        清空选择器选中输入控件中的文本\n        \"\"\"\n        req = protos.SelectorOnlyRequest(selector=self.selector)\n        r = self.stub.selectorClearTextField(req)\n        return r.value\n    def set_text(self, text):\n        \"\"\"\n        向选择器选中输入控件中填入文本\n        \"\"\"\n        req = protos.SelectorSetTextRequest(selector=self.selector,\n                                            text=text)\n        r = self.stub.selectorSetText(req)\n        return r.value\n    def click(self, corner=Corner.COR_CENTER):\n        \"\"\"\n        点击选择器选中的控件\n        \"\"\"\n        req = protos.SelectorClickRequest(selector=self.selector,\n                                          corner=corner)\n        r = self.stub.selectorClick(req)\n        return r.value\n    def click_exists(self, corner=Corner.COR_CENTER):\n        \"\"\"\n        点击选择器选中的控件（不存在将不会产生异常）\n        \"\"\"\n        req = protos.SelectorClickRequest(selector=self.selector,\n                                          corner=corner)\n        r = self.stub.selectorClickExists(req)\n        return r.value\n    def long_click(self, corner=Corner.COR_CENTER):\n        \"\"\"\n        长按选择器选中的控件\n        \"\"\"\n        req = protos.SelectorClickRequest(selector=self.selector,\n                                          corner=corner)\n        r = self.stub.selectorLongClick(req)\n        return r.value\n    def exists(self):\n        \"\"\"\n        是否存在选择器选中的控件\n        \"\"\"\n        req = protos.SelectorOnlyRequest(selector=self.selector)\n        r = self.stub.selectorExists(req)\n        return r.value\n    def info(self):\n        \"\"\"\n        获取选择器选中控件的信息\n        \"\"\"\n        req = protos.SelectorOnlyRequest(selector=self.selector)\n        return self.stub.selectorObjInfo(req)\n    def _new_object(self, **kwargs):\n        selector = copy.deepcopy(self._selector)\n        child_sibling = selector.get(\"childOrSiblingSelector\")\n        target = child_sibling[-1] if child_sibling else selector\n        target.update(**kwargs)\n        return self.caller(**selector)\n    def text(self, txt):\n        return self._new_object(text=txt)\n    def resourceId(self, name):\n        return self._new_object(resourceId=name)\n    def description(self, desc):\n        return self._new_object(description=desc)\n    def packageName(self, name):\n        return self._new_object(packageName=name)\n    def className(self, name):\n        return self._new_object(className=name)\n    def textContains(self, needle):\n        return self._new_object(textContains=needle)\n    def descriptionContains(self, needle):\n        return self._new_object(descriptionContains=needle)\n    def textStartsWith(self, needle):\n        return self._new_object(textStartsWith=needle)\n    def descriptionStartsWith(self, needle):\n        return self._new_object(descriptionStartsWith=needle)\n    def textMatches(self, match):\n        return self._new_object(textMatches=match)\n    def descriptionMatches(self, match):\n        return self._new_object(descriptionMatches=match)\n    def resourceIdMatches(self, match):\n        return self._new_object(resourceIdMatches=match)\n    def packageNameMatches(self, match):\n        return self._new_object(packageNameMatches=match)\n    def classNameMatches(self, match):\n        return self._new_object(classNameMatches=match)\n    def checkable(self, value):\n        return self._new_object(checkable=value)\n    def clickable(self, value):\n        return self._new_object(clickable=value)\n    def focusable(self, value):\n        return self._new_object(focusable=value)\n    def scrollable(self, value):\n        return self._new_object(scrollable=value)\n    def longClickable(self, value):\n        return self._new_object(longClickable=value)\n    def enabled(self, value):\n        return self._new_object(enabled=value)\n    def checked(self, value):\n        return self._new_object(checked=value)\n    def focused(self, value):\n        return self._new_object(focused=value)\n    def selected(self, value):\n        return self._new_object(selected=value)\n    def index(self, idx):\n        return self._new_object(index=idx)\n    def instance(self, idx):\n        return self._new_object(instance=idx)\n    def get(self, idx):\n        \"\"\"\n        获取匹配的第 N 个索引的元素\n        \"\"\"\n        return self.instance(idx)\n    def __iter__(self):\n        \"\"\"\n        遍历所有符合选择器条件的元素实例\n        \"\"\"\n        yield from [self.instance(i) for i in \\\n                            range(self.count())]\n    def count(self):\n        \"\"\"\n        获取选择器选中控件的数量\n        \"\"\"\n        req = protos.SelectorOnlyRequest(selector=self.selector)\n        r = self.stub.selectorCount(req)\n        return r.value\n    def _set_target_Point(self, req, target):\n        req.point.CopyFrom(target)\n    def _set_target_Selector(self, req, target):\n        req.target.CopyFrom(target)\n    def drag_to(self, target, step=32):\n        \"\"\"\n        将选择器选中的控件拖动到另一个选择器或者点上\n        \"\"\"\n        checkArgumentTyp(target, (Point, _Selector))\n        func = \"_set_target_{}\".format(target.DESCRIPTOR.name)\n        req = protos.SelectorDragToRequest(selector=self.selector,\n                                           step=step)\n        getattr(self, func)(req, target)\n        r = self.stub.selectorDragTo(req)\n        return r.value\n    def wait_for_exists(self, timeout):\n        \"\"\"\n        等待选择器选中控件出现\n        \"\"\"\n        req = protos.SelectorWaitRequest(selector=self.selector,\n                                         timeout=timeout)\n        r = self.stub.selectorWaitForExists(req)\n        return r.value\n    def wait_until_gone(self, timeout):\n        \"\"\"\n        等待选择器选中控件消失\n        \"\"\"\n        req = protos.SelectorWaitRequest(selector=self.selector,\n                                         timeout=timeout)\n        r = self.stub.selectorWaitUntilGone(req)\n        return r.value\n    def swipe(self, direction=Direction.DIR_UP, step=32):\n        \"\"\"\n        在选择器选中的元素上进行滑动操作\n        \"\"\"\n        req = protos.SelectorSwipeRequest(selector=self.selector,\n                                          direction=direction,\n                                          step=step)\n        r = self.stub.selectorSwipe(req)\n        return r.value\n    def pinch_in(self, percent, step=16):\n        \"\"\"\n        双指捏紧（缩小）\n        \"\"\"\n        req = protos.SelectorPinchRequest(selector=self.selector,\n                                         percent=percent, step=step)\n        r = self.stub.selectorPinchIn(req)\n        return r.value\n    def pinch_out(self, percent, step=16):\n        \"\"\"\n        双指放开（放大）\n        \"\"\"\n        req = protos.SelectorPinchRequest(selector=self.selector,\n                                          percent=percent, step=step)\n        r = self.stub.selectorPinchOut(req)\n        return r.value\n    def scroll_to(self, target, is_vertical=True):\n        \"\"\"\n        滚动 scrollable 直到匹配目标元素的选择器\n        \"\"\"\n        checkArgumentTyp(target, _Selector)\n        req = protos.SelectorScrollRequest(selector=self.selector,\n                                           vertical=is_vertical,\n                                           target=target)\n        r = self.stub.selectorScrollTo(req)\n        return r.value\n    def _fling_forward(self, is_vertical=True):\n        req = protos.SelectorFlingRequest(selector=self.selector,\n                                          vertical=is_vertical)\n        r = self.stub.selectorFlingForward(req)\n        return r.value\n    def _fling_backward(self, is_vertical=True):\n        req = protos.SelectorFlingRequest(selector=self.selector,\n                                          vertical=is_vertical)\n        r = self.stub.selectorFlingBackward(req)\n        return r.value\n    def _fling_to_end(self, max_swipes, is_vertical=True):\n        req = protos.SelectorFlingRequest(selector=self.selector,\n                                          maxSwipes=max_swipes,\n                                          vertical=is_vertical)\n        r = self.stub.selectorFlingToEnd(req)\n        return r.value\n    def _fling_to_beginning(self, max_swipes, is_vertical=True):\n        req = protos.SelectorFlingRequest(selector=self.selector,\n                                          maxSwipes=max_swipes,\n                                          vertical=is_vertical)\n        r = self.stub.selectorFlingToBeginning(req)\n        return r.value\n    def fling_from_top_to_bottom(self):\n        \"\"\"\n        在选择器选中元素上进行从上至下阅读式滑动（单次）\n        \"\"\"\n        return self._fling_backward(is_vertical=True)\n    def fling_from_bottom_to_top(self):\n        \"\"\"\n        在选择器选中元素上进行从下至上阅读式滑动（单次）\n        \"\"\"\n        return self._fling_forward(is_vertical=True)\n    def fling_from_left_to_right(self):\n        \"\"\"\n        在选择器选中元素上进行从左至右阅读式滑动（单次）\n        \"\"\"\n        return self._fling_backward(is_vertical=False)\n    def fling_from_right_to_left(self):\n        \"\"\"\n        在选择器选中元素上进行从右至左阅读式滑动（单次）\n        \"\"\"\n        return self._fling_forward(is_vertical=False)\n    def fling_from_top_to_bottom_to_end(self, max_swipes):\n        \"\"\"\n        在选择器选中元素上进行从上至下阅读式滑动直至无法滑动或达到 max_swipes 次\n        \"\"\"\n        return self._fling_to_beginning(max_swipes, is_vertical=True)\n    def fling_from_bottom_to_top_to_end(self, max_swipes):\n        \"\"\"\n        在选择器选中元素上进行从下至上阅读式滑动直至无法滑动或达到 max_swipes 次\n        \"\"\"\n        return self._fling_to_end(max_swipes, is_vertical=True)\n    def fling_from_left_to_right_to_end(self, max_swipes):\n        \"\"\"\n        在选择器选中元素上进行从左至右阅读式滑动直至无法滑动或达到 max_swipes 次\n        \"\"\"\n        return self._fling_to_beginning(max_swipes, is_vertical=False)\n    def fling_from_right_to_left_to_end(self, max_swipes):\n        \"\"\"\n        在选择器选中元素上进行从右至左阅读式滑动直至无法滑动或达到 max_swipes 次\n        \"\"\"\n        return self._fling_to_end(max_swipes, is_vertical=False)\n    def _scroll_forward(self, step, is_vertical=True):\n        req = protos.SelectorScrollRequest(selector=self.selector,\n                                           vertical=is_vertical,\n                                           step=step)\n        r = self.stub.selectorScrollForward(req)\n        return r.value\n    def _scroll_backward(self, step, is_vertical=True):\n        req = protos.SelectorScrollRequest(selector=self.selector,\n                                           vertical=is_vertical,\n                                           step=step)\n        r = self.stub.selectorScrollBackward(req)\n        return r.value\n    def _scroll_to_end(self, max_swipes, step, is_vertical=True):\n        req = protos.SelectorScrollRequest(selector=self.selector,\n                                           maxSwipes=max_swipes,\n                                           vertical=is_vertical,\n                                           step=step)\n        r = self.stub.selectorScrollToEnd(req)\n        return r.value\n    def _scroll_to_beginning(self, max_swipes, step, is_vertical=True):\n        req = protos.SelectorScrollRequest(selector=self.selector,\n                                           maxSwipes=max_swipes,\n                                           vertical=is_vertical,\n                                           step=step)\n        r = self.stub.selectorScrollToBeginning(req)\n        return r.value\n    def scroll_from_top_to_bottom(self, step):\n        \"\"\"\n        在选择器选中元素上进行从上至下普通滑动\n        \"\"\"\n        return self._scroll_backward(step, is_vertical=True)\n    def scroll_from_bottom_to_top(self, step):\n        \"\"\"\n        在选择器选中元素上进行从下至上普通滑动\n        \"\"\"\n        return self._scroll_forward(step, is_vertical=True)\n    def scroll_from_left_to_right(self, step):\n        \"\"\"\n        在选择器选中元素上进行从左至右普通滑动\n        \"\"\"\n        return self._scroll_backward(step, is_vertical=False)\n    def scroll_from_right_to_left(self, step):\n        \"\"\"\n        在选择器选中元素上进行从右至左普通滑动\n        \"\"\"\n        return self._scroll_forward(step, is_vertical=False)\n    def scroll_from_top_to_bottom_to_end(self, max_swipes, step):\n        \"\"\"\n        在选择器选中元素上进行从上至下普通滑动直至无法滑动或达到 max_swipes 次\n        \"\"\"\n        return self._scroll_to_beginning(max_swipes, step, is_vertical=True)\n    def scroll_from_bottom_to_top_to_end(self, max_swipes, step):\n        \"\"\"\n        在选择器选中元素上进行从下至上普通滑动直至无法滑动或达到 max_swipes 次\n        \"\"\"\n        return self._scroll_to_end(max_swipes, step, is_vertical=True)\n    def scroll_from_left_to_right_to_end(self, max_swipes, step):\n        \"\"\"\n        在选择器选中元素上进行从左至右普通滑动直至无法滑动或达到 max_swipes 次\n        \"\"\"\n        return self._scroll_to_beginning(max_swipes, step, is_vertical=False)\n    def scroll_from_right_to_left_to_end(self, max_swipes, step):\n        \"\"\"\n        在选择器选中元素上进行从右至左普通滑动直至无法滑动或达到 max_swipes 次\n        \"\"\"\n        return self._scroll_to_end(max_swipes, step, is_vertical=False)\n\n\nclass UiAutomatorStub(BaseServiceStub):\n    def __init__(self, *args, **kwargs):\n        super(UiAutomatorStub, self).__init__(*args, **kwargs)\n        self.watchers = defaultdict(dict)\n    def device_info(self):\n        \"\"\"\n        获取设备基本/分辨率等信息\n        \"\"\"\n        r = self.stub.deviceInfo(protos.Empty())\n        return r\n    def set_watcher_loop_enabled(self, enabled):\n        \"\"\"\n        设置是否启用设备上的 watcher UI 检测\n        \"\"\"\n        req = protos.Boolean(value=enabled)\n        r = self.stub.setWatcherLoopEnabled(req)\n        return r.value\n    def get_watcher_loop_enabled(self):\n        \"\"\"\n        获取是否启用设备上的 watcher UI 检测\n        \"\"\"\n        r = self.stub.getWatcherLoopEnabled(protos.Empty())\n        return r.value\n    def get_watcher_triggered_count(self, name):\n        \"\"\"\n        获取这个 watcher 被触发的次数\n        \"\"\"\n        req = protos.String(value=name)\n        r = self.stub.getWatcherTriggeredCount(req)\n        return r.value\n    def reset_watcher_triggered_count(self, name):\n        \"\"\"\n        重置这个 watcher 的触发次数为 0\n        \"\"\"\n        req = protos.String(value=name)\n        r = self.stub.resetWatcherTriggeredCount(req)\n        return r.value\n    def get_applied_watchers(self):\n        \"\"\"\n        获取已经在系统应用的 watcher 名称列表\n        \"\"\"\n        r = self.stub.getAppliedWatchers(protos.Empty())\n        return r.watchers\n    # 注意：下面这些 watcher 实现不是安全的\n    # 注册时都是统一存储到本地实例的变量中，直至 enable 时才会应用至服务端\n    # 这样做的原因是让你知道你都干了什么，过多的 watcher 会影响性能\n    def remove_all_watchers(self):\n        \"\"\"\n        移除所有应用/未应用的 watcher\n        \"\"\"\n        for name in list(self.get_applied_watchers()):\n            self.remove_watcher(name)\n        for name in list(self.watchers.keys()):\n            self.remove_watcher(name)\n    def register_click_target_selector_watcher(self, name, conditions,\n                                               target):\n        \"\"\"\n        注册一个满足条件点击 selector 的 watcher\n        \"\"\"\n        checkDupEntry(name, self.watchers)\n        req = protos.WatcherRegistRequest(name=name, selectors=conditions,\n                                          target=target)\n        self.watchers[name][\"enabled\"] = False\n        func = lambda: self.stub.registerClickUiObjectWatcher(req).value\n        self.watchers[name][\"enable\"] = func\n    def register_press_key_watcher(self, name, conditions, key):\n        \"\"\"\n        注册一个满足条件点击 key 的 watcher\n        \"\"\"\n        checkDupEntry(name, self.watchers)\n        req = protos.WatcherRegistRequest(name=name, selectors=conditions,\n                                          key=key)\n        self.watchers[name][\"enabled\"] = False\n        func = lambda: self.stub.registerPressKeysWatcher(req).value\n        self.watchers[name][\"enable\"] = func\n    def register_none_op_watcher(self, name, conditions):\n        \"\"\"\n        注册一个满足条件无操作的 watcher（用来检测是否出现过某个场景）\n        \"\"\"\n        checkDupEntry(name, self.watchers)\n        req = protos.WatcherRegistRequest(name=name, selectors=conditions)\n        self.watchers[name][\"enabled\"] = False\n        func = lambda: self.stub.registerNoneOpWatcher(req).value\n        self.watchers[name][\"enable\"] = func\n    def _remove_watcher(self, name):\n        return self.stub.removeWatcher(protos.String(value=name)).value\n    def set_watcher_enabled(self, name, enable):\n        \"\"\"\n        设置是否启用此 watcher\n        \"\"\"\n        if name not in self.watchers:\n            return False\n        self.watchers[name][\"enabled\"] = enable\n        if self.watchers[name][\"enabled\"]:\n            return self.watchers[name][\"enable\"]()\n        return self._remove_watcher(name)\n    def get_watcher_enabled(self, name):\n        \"\"\"\n        获取此 watcher 是否启用\n        \"\"\"\n        return self.watchers.get(name, {}).get(\"enable\")\n    def get_last_toast(self):\n        \"\"\"\n        获取系统中最后一个 toast 消息\n        \"\"\"\n        r = self.stub.getLastToast(protos.Empty())\n        return r\n    def remove_watcher(self, name):\n        \"\"\"\n        移除一个 watcher\n        \"\"\"\n        self.watchers.pop(name, None)\n        return self._remove_watcher(name)\n    def click(self, point):\n        \"\"\"\n        点击屏幕中的某个点(Point)\n        \"\"\"\n        req = protos.ClickPointRequest(point=point)\n        r = self.stub.click(req)\n        return r.value\n    def drag(self, A, B, step=32):\n        \"\"\"\n        从点(Point) A 拖动到点(Point) B\n        \"\"\"\n        req = protos.DragPointRequest(A=A, B=B, step=step)\n        r = self.stub.drag(req)\n        return r.value\n    def swipe(self, A, B, step=32):\n        \"\"\"\n        从点(Point) A 滑动到点(Point) B\n        \"\"\"\n        req = protos.SwipePointRequest(A=A, B=B, step=step)\n        r = self.stub.swipe(req)\n        return r.value\n    def swipe_points(self, *points, step=32):\n        \"\"\"\n        滑动一个点(Point)序列（超过两个点）\n        \"\"\"\n        req = protos.SwipePointsRequest(points=points, step=step)\n        r = self.stub.swipePoints(req)\n        return r.value\n    def open_notification(self):\n        \"\"\"\n        打开通知栏（状态栏）\n        \"\"\"\n        r = self.stub.openNotification(protos.Empty())\n        return r.value\n    def open_quick_settings(self):\n        \"\"\"\n        打开设置栏（状态栏）\n        \"\"\"\n        r = self.stub.openQuickSettings(protos.Empty())\n        return r.value\n    def wake_up(self):\n        \"\"\"\n        唤醒设备（点亮屏幕）\n        \"\"\"\n        r = self.stub.wakeUp(protos.Empty())\n        return r.value\n    def sleep(self):\n        \"\"\"\n        关闭设备（熄灭屏幕）\n        \"\"\"\n        r = self.stub.sleep(protos.Empty())\n        return r.value\n    def is_screen_on(self):\n        \"\"\"\n        设备是否处于唤醒状态\n        \"\"\"\n        r = self.stub.isScreenOn(protos.Empty())\n        return r.value\n    def is_screen_locked(self):\n        \"\"\"\n        设备屏幕是否已经锁定\n        \"\"\"\n        r = self.stub.isScreenLocked(protos.Empty())\n        return r.value\n    def set_clipboard(self, text):\n        \"\"\"\n        设置剪切板文字\n        \"\"\"\n        req = protos.ClipboardRequest(ID=str(uuid.uuid4()), value=text)\n        r = self.stub.setClipboard(req)\n        return r.value\n    def get_clipboard(self):\n        \"\"\"\n        获取剪切板文字（小于 Android10）\n        \"\"\"\n        r = self.stub.getClipboard(protos.Empty())\n        return r.value\n    def _set_target_Area(self, req, area):\n        req.area = area\n    def _set_target_Bound(self, req, bound):\n        req.bound.CopyFrom(bound)\n    def find_similar_image(self, data, threshold=0.0, distance=250,\n                           scale=1.0, area=FindImageArea.FIA_WHOLE_SCREEN,\n                           method=FindImageMethod.FIM_TEMPLATE):\n        \"\"\"\n        根据提供的目标图片从屏幕中获取相似图片位置\n        \"\"\"\n        req = protos.FindImageRequest()\n        checkArgumentTyp(area, (Bound, int))\n        name = getattr(getattr(area, \"DESCRIPTOR\", None),\n                                         \"name\", \"Area\")\n        func = \"_set_target_{}\".format(name)\n        getattr(self, func)(req, area)\n        req.method = method\n        req.distance = distance\n        req.threshold = threshold\n        req.scale = scale\n        req.partial = data\n        r = self.stub.findSimilarImage(req)\n        return r.bounds\n    def freeze_rotation(self, freeze=True):\n        \"\"\"\n        锁定屏幕旋转\n        \"\"\"\n        r = self.stub.freezeRotation(protos.Boolean(value=freeze))\n        return r.value\n    def set_orientation(self, orien=Orientation.ORIEN_NATURE):\n        \"\"\"\n        设置屏幕旋转方向\n        \"\"\"\n        req = protos.OrientationRequest(orientation=orien)\n        r = self.stub.setOrientation(req)\n        return r.value\n    def press_key(self, key):\n        \"\"\"\n        按下设备物理按键（HOME/VOLUME/BACK)\n        \"\"\"\n        req = protos.PressKeyRequest(key=key)\n        r = self.stub.pressKey(req)\n        return r.value\n    def press_keycode(self, code, meta=0):\n        \"\"\"\n        通过 Keycode(整数)按下未定义的按键\n        ref: https://developer.android.com/reference/android/view/KeyEvent\n        \"\"\"\n        req = protos.PressKeyRequest(code=code, meta=meta)\n        r = self.stub.pressKeyCode(req)\n        return r.value\n    def take_screenshot(self, quality, bound=None):\n        \"\"\"\n        截取全屏幕截图\n        \"\"\"\n        req = protos.TakeScreenshotRequest(quality=quality,\n                                           bound=bound)\n        r = self.stub.takeScreenshot(req)\n        return BytesIO(r.value)\n    def screenshot(self, quality, bound=None):\n        return self.take_screenshot(quality, bound=bound)\n    def dump_window_hierarchy(self, compressed=False):\n        \"\"\"\n        获取屏幕界面布局 XML 文档\n        \"\"\"\n        req = protos.Boolean(value=compressed)\n        r = self.stub.dumpWindowHierarchy(req)\n        return BytesIO(r.value)\n    def wait_for_idle(self, timeout):\n        \"\"\"\n        等待当前屏幕处于闲置状态（无频繁活动切换）\n        \"\"\"\n        r = self.stub.waitForIdle(protos.Integer(value=timeout))\n        return r.value\n    def __call__(self, **kwargs):\n        return ObjectUiAutomatorOpStub(self, kwargs)\n\n\nclass AppScriptRpcInterface(object):\n    def __init__(self, stub, application,\n                                    name):\n        self.application = application\n        self.stub = stub\n        self.name = name\n    def __str__(self):\n        return \"{}:Script:{}\".format(self.application,\n                                            self.name)\n    __repr__ = __str__\n    def __call__(self, *args):\n        call_args = dict()\n        call_args[\"method\"] = self.name\n        call_args[\"args\"] = args\n        req = HookRpcRequest()\n        req.package = self.application.applicationId\n        req.user = self.application.user\n        req.callinfo = json.dumps(call_args)\n        result = self.stub.callScript(req)\n        data = json.loads(result.callresult)\n        return data\n\n\nclass ApplicationOpStub:\n    def __init__(self, stub, applicationId, user=0):\n        \"\"\"\n        Application 子接口，用来模拟出实例的意味\n        \"\"\"\n        self.user = user\n        self.applicationId = applicationId\n        self.stub = stub\n    def __str__(self):\n        return \"Application:{}@{}\".format(self.applicationId,\n                                          self.user)\n    __repr__ = __str__\n    def is_foreground(self):\n        \"\"\"\n        应用是否正处于前台运行\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.isForeground(req)\n        return r.value\n    def permissions(self):\n        \"\"\"\n        获取应用的所有权限列表\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.getPermissions(req)\n        return r.permissions\n    def grant(self, permission, mode=GrantType.GRANT_ALLOW):\n        \"\"\"\n        授予应用某个权限（应用需要运行时获取的权限）\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId,\n                                        permission=permission,\n                                        mode=mode)\n        req.user = self.user\n        r = self.stub.grantPermission(req)\n        return r.value\n    def revoke(self, permission):\n        \"\"\"\n        撤销授予应用的权限（应用需要运行时获取的权限）\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId,\n                                        permission=permission)\n        req.user = self.user\n        r = self.stub.revokePermission(req)\n        return r.value\n    def query_launch_activity(self):\n        \"\"\"\n        获取应用的启动 activity 信息\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.queryLaunchActivity(req)\n        return to_dict(r)\n    def is_permission_granted(self, permission):\n        \"\"\"\n        检查是否已经授予应用某权限（应用需要运行时获取的权限）\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId,\n                                        permission=permission)\n        req.user = self.user\n        r = self.stub.isPermissionGranted(req)\n        return r.value\n    def clear_cache(self):\n        \"\"\"\n        清空应用的缓存数据（非数据仅缓存）\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.deleteApplicationCache(req)\n        return r.value\n    def reset(self):\n        \"\"\"\n        清空应用的所有数据\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.resetApplicationData(req)\n        return r.value\n    def start(self):\n        \"\"\"\n        启动应用\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.startApplication(req)\n        return r.value\n    def stop(self):\n        \"\"\"\n        停止应用\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.stopApplication(req)\n        return r.value\n    def info(self):\n        \"\"\"\n        获取应用信息\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.applicationInfo(req)\n        return r\n    def uninstall(self):\n        \"\"\"\n        卸载应用 (always return true)\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.uninstallApplication(req)\n        return r.value\n    def enable(self):\n        \"\"\"\n        启用应用\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.enableApplication(req)\n        return r.value\n    def disable(self):\n        \"\"\"\n        禁用应用（这将使应用从启动器消失）\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.disableApplication(req)\n        return r.value\n    def add_to_doze_mode_whitelist(self):\n        \"\"\"\n        将APP加入省电白名单（可以一直运行，可能不会覆盖所有系统）\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.addToDozeModeWhiteList(req)\n        return True\n    def remove_from_doze_mode_whitelist(self):\n        \"\"\"\n        将APP移除省电白名单 (always return true)\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.removeFromDozeModeWhiteList(req)\n        return True\n    def is_installed(self):\n        \"\"\"\n        检查应用是否已经安装\n        \"\"\"\n        req = protos.ApplicationRequest(name=self.applicationId)\n        req.user = self.user\n        r = self.stub.isInstalled(req)\n        return r.value\n    def attach_script(self, script, runtime=ScriptRuntime.RUNTIME_QJS,\n                                                    emit=\"\",\n                                encode=DataEncode.DATA_ENCODE_NONE,\n                                spawn=False,\n                                standup=5):\n        \"\"\"\n        向应用注入持久化 Hook 脚本\n        \"\"\"\n        s = isinstance(script, str)\n        script = script.encode() if s else script\n        req = protos.HookRequest()\n        req.package     = self.applicationId\n        req.script      = script\n        req.runtime     = runtime\n        req.standup     = standup\n        req.spawn       = spawn\n        req.destination = emit\n        req.encode      = encode\n        req.user        = self.user\n        r = self.stub.attachScript(req)\n        return r.value\n    def detach_script(self):\n        \"\"\"\n        移除注入应用的 Hook 脚本\n        \"\"\"\n        req = protos.HookRequest()\n        req.package     = self.applicationId\n        req.user        = self.user\n        r = self.stub.detachScript(req)\n        return r.value\n    def is_attached_script(self):\n        \"\"\"\n        检查使用在此应用注入了 Hook 脚本\n        \"\"\"\n        req = protos.HookRequest()\n        req.package     = self.applicationId\n        req.user        = self.user\n        r = self.stub.isScriptAttached(req)\n        return r.value\n    def is_script_alive(self):\n        \"\"\"\n        检查应用中的 Hook 脚本是否正常\n        \"\"\"\n        req = protos.HookRequest()\n        req.package     = self.applicationId\n        req.user        = self.user\n        r = self.stub.isScriptAlive(req)\n        return r.value\n    def __getattr__(self, name):\n        \"\"\"\n        调用注入应用 Hook 脚本的导出方法\n        \"\"\"\n        return AppScriptRpcInterface(self.stub, self,\n                                            name)\n\n\nclass ApplicationStub(BaseServiceStub):\n    def current_application(self):\n        \"\"\"\n        获取当前处于前台的应用的信息\n        \"\"\"\n        top = self.stub.currentApplication(protos.Empty())\n        app = self.__call__(top.packageName, user=top.user)\n        app.activity = top.activity\n        return app\n    def get_application_by_name(self, name, user=0):\n        req = protos.String(value=name)\n        r = self.stub.getIdentifierByLabel(req)\n        app = self.__call__(r.value, user=user)\n        return app\n    def enumerate_running_processes(self):\n        \"\"\"\n        列出设备上所有正在运行的安卓应用进程\n        \"\"\"\n        r = self.stub.enumerateRunningProcesses(protos.Empty())\n        return r.processes\n    def enumerate_all_pkg_names(self):\n        \"\"\"\n        列出所有已安装的应用的 applicationId\n        \"\"\"\n        r = self.stub.enumerateAllPkgNames(protos.Empty())\n        return r.names\n    def get_last_activities(self, count=3):\n        \"\"\"\n        获取系统中最后一个活动的详细信息\n        \"\"\"\n        req = protos.Integer(value=count)\n        r = self.stub.getLastActivities(req).activities\n        return list(map(to_dict, r))\n    def start_activity(self, **activity):\n        \"\"\"\n        启动 activity（总是返回 True）\n        \"\"\"\n        activity.setdefault(\"extras\", {})\n        extras = activity.pop(\"extras\")\n        req = protos.ApplicationActivityRequest(**activity)\n        req.extras.update(extras)\n        r = self.stub.startActivity(req)\n        return r.value\n    def install_local_file(self, fpath, user=0):\n        \"\"\"\n        安装设备上的 apk 文件（注意此路径为设备上的 apk 路径）\n        \"\"\"\n        req = protos.ApplicationRequest(path=fpath)\n        req.user = user\n        r = self.stub.installFromLocalFile(req)\n        return r\n    def __call__(self, applicationId, user=0):\n        return ApplicationOpStub(self.stub, applicationId, user)\n\n\nclass StorageOpStub:\n    # 用于容器值序列化的方法\n    def _decrypt(self, data):\n        return self.cryptor.decrypt(data)\n    def _encrypt(self, data):\n        return self.cryptor.encrypt(data)\n    def _unpack(self, value):\n        return msgpack.loads(self._decrypt(value))\n    def _pack(self, value):\n        return self._encrypt(msgpack.dumps(value))\n    # 注意：此接口可能并不是跨语言通用\n    def __init__(self, stub, name, cryptor=None):\n        self.cryptor = cryptor\n        self.name = name\n        self.stub = stub\n    def delete(self, key):\n        \"\"\"\n        删除一个 KEY\n        \"\"\"\n        req = protos.StorageRequest(key=key)\n        req.container = self.name\n        res = self.stub.delete(req)\n        return res.value\n    def exists(self, key):\n        \"\"\"\n        检查一个 KEY 是否存在\n        \"\"\"\n        req = protos.StorageRequest(key=key)\n        req.container = self.name\n        res = self.stub.exists(req)\n        return res.value\n    def get(self, key, default=None):\n        \"\"\"\n        获取 KEY 对应的键值\n        \"\"\"\n        req = protos.StorageRequest(key=key)\n        req.container = self.name\n        val = self.stub.get(req).value\n        res = self._unpack(val) if val else default\n        return res\n    def set(self, key, value):\n        \"\"\"\n        设置 KEY 对应的键值\n        \"\"\"\n        value = self._pack(value)\n        req = protos.StorageRequest(key=key, value=value)\n        req.container = self.name\n        res = self.stub.set(req)\n        return res.value\n    def setex(self, key, value, ttl):\n        \"\"\"\n        设置 KEY 对应的键值，该 KEY 在 TTL 秒后自动删除\n        \"\"\"\n        value = self._pack(value)\n        req = protos.StorageRequest(key=key, value=value)\n        req.container = self.name\n        req.ttl = ttl\n        res = self.stub.setex(req)\n        return res.value\n    def setnx(self, key, value):\n        \"\"\"\n        设置 KEY 对应的键值 (仅当该键不存在时)\n        \"\"\"\n        value = self._pack(value)\n        req = protos.StorageRequest(key=key, value=value)\n        req.container = self.name\n        res = self.stub.setnx(req)\n        return res.value\n    def expire(self, key, ttl):\n        \"\"\"\n        设置 KEY 在 TTL 秒后过期\n        \"\"\"\n        req = protos.StorageRequest(key=key, ttl=ttl)\n        req.container = self.name\n        res = self.stub.expire(req)\n        return res.value\n    def ttl(self, key):\n        \"\"\"\n        获取 KEY 的 TTL (过期时间)\n        \"\"\"\n        req = protos.StorageRequest(key=key)\n        req.container = self.name\n        res = self.stub.ttl(req)\n        return res.value\n\n\nclass StorageStub(BaseServiceStub):\n    def clear(self):\n        \"\"\"\n        删除所有 Storage 容器\n        \"\"\"\n        r = self.stub.clearAll(protos.Empty())\n        return r.value\n    def use(self, name, cryptor=BaseCryptor, **kwargs):\n        \"\"\"\n        使用一个 Storage 容器\n        \"\"\"\n        return StorageOpStub(self.stub, name, cryptor(**kwargs))\n    def remove(self, name):\n        \"\"\"\n        删除一个 Storage 容器\n        \"\"\"\n        req = protos.String(value=name)\n        r = self.stub.clearContainer(req)\n        return r.value\n\n\nclass UtilStub(BaseServiceStub):\n    def _get_file_content(self, certfile):\n        with open(certfile, \"rb\") as fd:\n            return fd.read()\n    def is_ca_certificate_installed(self, certfile):\n        \"\"\"\n        安装系统证书（用于 MITM）\n        \"\"\"\n        data = self._get_file_content(certfile)\n        req = protos.CertifiRequest(cert=data)\n        r = self.stub.isCACertificateInstalled(req)\n        return r.value\n    def install_ca_certificate(self, certfile):\n        \"\"\"\n        安装系统证书（用于 MITM）\n        \"\"\"\n        data = self._get_file_content(certfile)\n        req = protos.CertifiRequest(cert=data)\n        r = self.stub.installCACertificate(req)\n        return r.value\n    def uninstall_ca_certificate(self, certfile):\n        \"\"\"\n        移除系统证书（用于 MITM）\n        \"\"\"\n        data = self._get_file_content(certfile)\n        req = protos.CertifiRequest(cert=data)\n        r = self.stub.uninstallCACertificate(req)\n        return r.value\n    def record_touch(self):\n        \"\"\"\n        录制滑动轨迹\n        \"\"\"\n        r = self.stub.recordTouch(protos.Empty())\n        return r\n    def perform_touch(self, tas, wait=True):\n        \"\"\"\n        在设备上进行真实滑动（重放录制的滑动轨迹）\n        \"\"\"\n        checkArgumentTyp(tas, TouchSequence)\n        req = protos.PerformTouchRequest(sequence=tas, wait=wait)\n        r = self.stub.performTouch(req)\n        return r.value\n    def reboot(self):\n        \"\"\"\n        重启系统（宿主设备）\n        \"\"\"\n        r = self.stub.reboot(protos.Empty())\n        return r.value\n    def shutdown(self):\n        \"\"\"\n        关闭系统（宿主设备）\n        \"\"\"\n        r = self.stub.shutdown(protos.Empty())\n        return r.value\n    def reload(self, clean=False):\n        \"\"\"\n        重载设备上运行的服务端\n        \"\"\"\n        req = protos.Boolean(value=clean)\n        r = self.stub.reload(req)\n        return r.value\n    def exit(self):\n        \"\"\"\n        退出设备上运行的服务端\n        \"\"\"\n        r = self.stub.exit(protos.Empty())\n        return r.value\n    def beep(self):\n        \"\"\"\n        播放一声蜂鸣（物理查找）\n        \"\"\"\n        r = self.stub.beepBeep(protos.Empty())\n        return r.value\n    def play_audio(self, file, type=AudioStreamType.AST_SYSTEM,\n                                        loop=1, interval=0):\n        \"\"\"\n        播放 wav 音频\n        \"\"\"\n        profile = PlayAudioProfile()\n        profile.file = file\n        profile.type = type\n        profile.loop = loop\n        profile.interval = interval\n        r = self.stub.playAudio(profile)\n        return r.value\n    def show_toast(self, text, duration=ToastDuration.TD_SHORT):\n        \"\"\"\n        在系统界面底部显示一个 Toast 消息\n        \"\"\"\n        req = protos.ShowToastRequest(text=text, duration=duration)\n        r = self.stub.showToast(req)\n        return r.value\n    def setprop(self, name, value):\n        \"\"\"\n        设置系统属性（aka: setprop，支持设置 ro.xx 只读属性）\n        \"\"\"\n        req = protos.SetPropRequest(name=name, value=value)\n        r = self.stub.setProp(req)\n        return r.value\n    def getprop(self, name):\n        \"\"\"\n        获取系统属性（aka: getprop）\n        \"\"\"\n        req = protos.String(value=name)\n        r = self.stub.getProp(req)\n        return r.value\n    def server_info(self):\n        \"\"\"\n        获取服务端ID、版本等信息\n        \"\"\"\n        r = self.stub.serverInfo(protos.Empty())\n        return r\n    def hex_patch(self, pattern, replacement, path,\n                                        maxreplace=-1,\n                                        dryrun=False):\n        \"\"\"\n        对设备上的文件进行十六进制字节替换\n        \"\"\"\n        req = protos.HexPatchRequest()\n        req.pattern     = pattern\n        req.replacement = replacement\n        req.path        = path\n        req.maxreplace  = maxreplace\n        req.dryrun      = dryrun\n        return self.stub.hexPatch(req)\n\n\nclass DebugStub(BaseServiceStub):\n    def _read_pubkey(self, pubkey):\n        with open(pubkey, \"rb\") as fd:\n            return fd.read()\n    def install_adb_pubkey(self, pubkey):\n        \"\"\"\n        给内置 adb 服务添加公钥\n        \"\"\"\n        req = protos.ADBDConfigRequest()\n        req.adb_pubkey = self._read_pubkey(pubkey)\n        r = self.stub.installADBPubKey(req)\n        return r.value\n    def uninstall_adb_pubkey(self, pubkey):\n        \"\"\"\n        从内置 adb 服务移除公钥\n        \"\"\"\n        req = protos.ADBDConfigRequest()\n        req.adb_pubkey = self._read_pubkey(pubkey)\n        r = self.stub.uninstallADBPubKey(req)\n        return r.value\n    def is_android_debug_bridge_running(self):\n        \"\"\"\n        远端 adb daemon 是否在运行\n        \"\"\"\n        r = self.stub.isAndroidDebugBridgeRunning(protos.Empty())\n        return r.value\n    def start_android_debug_bridge(self):\n        \"\"\"\n        启动内置 adbd (默认随框架启动)\n        \"\"\"\n        r = self.stub.startAndroidDebugBridge(protos.Empty())\n        return r.value\n    def stop_android_debug_bridge(self):\n        \"\"\"\n        停止内置 adb daemon\n        \"\"\"\n        r = self.stub.stopAndroidDebugBridge(protos.Empty())\n        return r.value\n\n\nclass SettingsStub(BaseServiceStub):\n    def _put(self, group, name, value):\n        req = protos.SettingsRequest(group=group, name=name,\n                                            value=value)\n        r = self.stub.putSettings(req)\n        return r.value\n    def _get(self, group, name):\n        req = protos.SettingsRequest(group=group,name=name)\n        r = self.stub.getSettings(req)\n        return r.value\n    def get_system(self, name):\n        \"\"\"\n        等价于 settings get system xxxx\n        \"\"\"\n        return self._get(Group.GROUP_SYSTEM, name)\n    def put_system(self, name, value):\n        \"\"\"\n        等价于 settings put system xxxx xxxx\n        \"\"\"\n        return self._put(Group.GROUP_SYSTEM, name, value)\n    def get_global(self, name):\n        \"\"\"\n        等价于 settings get global xxxx\n        \"\"\"\n        return self._get(Group.GROUP_GLOBAL, name)\n    def put_global(self, name, value):\n        \"\"\"\n        等价于 settings put global xxxx xxxx\n        \"\"\"\n        return self._put(Group.GROUP_GLOBAL, name, value)\n    def get_secure(self, name):\n        \"\"\"\n        等价于 settings get secure xxxx\n        \"\"\"\n        return self._get(Group.GROUP_SECURE, name)\n    def put_secure(self, name, value):\n        \"\"\"\n        等价于 settings put secure xxxx xxxx\n        \"\"\"\n        return self._put(Group.GROUP_SECURE, name, value)\n\n\nclass ShellStub(BaseServiceStub):\n    def execute_script(self, script, alias=None,\n                                    timeout=60):\n        \"\"\"\n        前台执行一段脚本（支持标准的多行脚本）\n        \"\"\"\n        req = protos.ShellRequest(name=alias, script=script,\n                                            timeout=timeout)\n        r = self.stub.executeForeground(req)\n        return r\n    def execute_background_script(self, script, alias=None):\n        \"\"\"\n        后台执行一段脚本（支持标准的多行脚本）\n        \"\"\"\n        req = protos.ShellRequest(name=alias, script=script)\n        r = self.stub.executeBackground(req)\n        return r.tid\n    def is_background_script_finished(self, tid):\n        \"\"\"\n        后台脚本是否已经结束\n        \"\"\"\n        req = protos.ShellTask(tid=tid)\n        r = self.stub.isBackgroundFinished(req)\n        return r.value\n    def kill_background_script(self, tid):\n        \"\"\"\n        强行停止后台脚本\n        \"\"\"\n        req = protos.ShellTask(tid=tid)\n        r = self.stub.killBackground(req)\n        return r.value\n\n\nclass StatusStub(BaseServiceStub):\n    def get_boot_time(self):\n        \"\"\"\n        获取设备启动时间 Unix 时间戳\n        \"\"\"\n        r = self.stub.getBootTime(protos.Empty())\n        return r.value\n    def get_disk_usage(self, mountpoint=\"/data\"):\n        \"\"\"\n        获取分区数据使用情况\n        \"\"\"\n        req = protos.String(value=mountpoint)\n        r = self.stub.getDiskUsage(req)\n        return r\n    def get_battery_info(self):\n        \"\"\"\n        获取电池信息\n        \"\"\"\n        r = self.stub.getBatteryInfo(protos.Empty())\n        return r\n    def get_cpu_info(self):\n        \"\"\"\n        获取 CPU 用量等信息\n        \"\"\"\n        r = self.stub.getCpuInfo(protos.Empty())\n        return r\n    def get_overall_disk_io_info(self):\n        \"\"\"\n        获取全局的设备磁盘读写状况\n        \"\"\"\n        r = self.stub.getOverallDiskIOInfo(protos.Empty())\n        return r\n    def get_overall_net_io_info(self):\n        \"\"\"\n        获取全局的设备网络收发状况\n        \"\"\"\n        r = self.stub.getOverallNetIOInfo(protos.Empty())\n        return r\n    def get_userdata_disk_io_info(self):\n        \"\"\"\n        获取用户数据设备磁盘读写状况\n        \"\"\"\n        r = self.stub.getUserDataDiskIOInfo(protos.Empty())\n        return r\n    def get_net_io_info(self, interface):\n        \"\"\"\n        获取特定接口的网络收发状况\n        \"\"\"\n        req = protos.String(value=interface)\n        r = self.stub.getNetIOInfo(req)\n        return r\n    def get_mem_info(self):\n        \"\"\"\n        获取设备内存状况\n        \"\"\"\n        r = self.stub.getMemInfo(protos.Empty())\n        return r\n\n\nclass ProxyStub(BaseServiceStub):\n    def is_openvpn_running(self):\n        \"\"\"\n        检查 OPENVPN 是否正在运行\n        \"\"\"\n        r = self.stub.isOpenVPNRunning(protos.Empty())\n        return r.value\n    def is_gproxy_running(self):\n        \"\"\"\n        检查 GPROXY 是否正在运行\n        \"\"\"\n        r = self.stub.isGproxyRunning(protos.Empty())\n        return r.value\n    def start_openvpn(self, profile):\n        \"\"\"\n        启动 OPENVPN\n        \"\"\"\n        checkArgumentTyp(profile, OpenVPNProfile)\n        r = self.stub.startOpenVPN(profile)\n        return r.value\n    def start_gproxy(self, profile):\n        \"\"\"\n        启动 GPROXY\n        \"\"\"\n        checkArgumentTyp(profile, GproxyProfile)\n        r = self.stub.startGproxy(profile)\n        return r.value\n    def stop_openvpn(self):\n        \"\"\"\n        停止 OPENVPN\n        \"\"\"\n        r = self.stub.stopOpenVPN(protos.Empty())\n        return r.value\n    def stop_gproxy(self):\n        \"\"\"\n        停止 GPROXY\n        \"\"\"\n        r = self.stub.stopGproxy(protos.Empty())\n        return r.value\n\n\nclass SelinuxPolicyStub(BaseServiceStub):\n    def allow(self, source, target, tclass, action):\n        \"\"\"\n        selinux allow\n        \"\"\"\n        req = protos.SelinuxPolicyRequest(source=source, target=target,\n                                          tclass=tclass, action=action)\n        r = self.stub.policySetAllow(req)\n        return r.value\n    def disallow(self, source, target, tclass, action):\n        \"\"\"\n        selinux disallow\n        \"\"\"\n        req = protos.SelinuxPolicyRequest(source=source, target=target,\n                                          tclass=tclass, action=action)\n        r = self.stub.policySetDisallow(req)\n        return r.value\n    def get_enforce(self):\n        \"\"\"\n        获取当前 selinux enforce 状态\n        \"\"\"\n        r = self.stub.getEnforce(protos.Empty())\n        return r.value\n    def set_enforce(self, enforced=True):\n        \"\"\"\n        设置当前 selinux enforce 状态 (aka: setenforce 0/1)\n        \"\"\"\n        req = protos.Boolean(value=enforced)\n        r = self.stub.setEnforce(req)\n        return r.value\n    def enabled(self):\n        \"\"\"\n        获取设备上的 selinux 是否已经启用\n        \"\"\"\n        r = self.stub.isEnabled(protos.Empty())\n        return r.value\n    def enforce(self, name):\n        \"\"\"\n        设置一个域为 enforce\n        \"\"\"\n        req = protos.String(value=name)\n        r = self.stub.policySetEnforce(req)\n        return r.value\n    def permissive(self, name):\n        \"\"\"\n        设置一个域为 permissive\n        \"\"\"\n        req = protos.String(value=name)\n        r = self.stub.policySetPermissive(req)\n        return r.value\n    def create_domain(self, name):\n        \"\"\"\n        新建一个 selinux 域\n        \"\"\"\n        req = protos.String(value=name)\n        r = self.stub.policyCreateDomain(req)\n        return r.value\n\n\nclass FileStub(BaseServiceStub):\n    def _fd_stream_read(self, fd, chunksize):\n        for chunk in iter(lambda: fd.read(chunksize), bytes()):\n            yield chunk\n    def _fd_streaming_send(self, fd, dest, chunksize):\n        yield protos.FileRequest(path=dest)\n        for chunk in self._fd_stream_read(fd, chunksize):\n            yield protos.FileRequest(payload=chunk)\n    def _fd_streaming_recv(self, fd, iterator):\n        for chunk in iterator:\n            fd.write(chunk.payload)\n    def download_fd(self, fpath, fd):\n        \"\"\"\n        从设备下载文件到文件描述符\n        \"\"\"\n        req = protos.FileRequest(path=fpath)\n        iterator = self.stub.downloadFile(req)\n        self._fd_streaming_recv(fd, iterator)\n        st = self.file_stat(fpath)\n        return st\n    def upload_fd(self, fd, dest):\n        \"\"\"\n        上传文件描述符至设备\n        \"\"\"\n        chunksize = 1024*1024*1\n        streaming = self._fd_streaming_send(fd, dest,\n                                              chunksize)\n        self.stub.uploadFile(streaming)\n        st = self.file_stat(dest)\n        return st\n    def download_file(self, fpath, dest):\n        \"\"\"\n        从设备下载文件到本地\n        \"\"\"\n        with io.open(dest, mode=\"wb\") as fd:\n            return self.download_fd(fpath, fd)\n    def upload_file(self, fpath, dest):\n        \"\"\"\n        上传本地文件至设备\n        \"\"\"\n        with io.open(fpath, mode=\"rb\") as fd:\n            return self.upload_fd(fd, dest)\n    def delete_file(self, fpath):\n        \"\"\"\n        删除设备上的文件\n        \"\"\"\n        req = protos.FileRequest(path=fpath)\n        r = self.stub.deleteFile(req)\n        return r.value\n    def file_chmod(self, fpath, mode=0o644):\n        \"\"\"\n        更改设备上文件的权限\n        \"\"\"\n        req = protos.FileRequest(path=fpath, mode=mode)\n        r = self.stub.fileChmod(req)\n        return r\n    def file_stat(self, fpath):\n        \"\"\"\n        获取设备上文件的信息\n        \"\"\"\n        req = protos.FileRequest(path=fpath)\n        r = self.stub.fileStat(req)\n        return r\n\n\nclass LockStub(BaseServiceStub):\n    def acquire_lock(self, leaseTime=60):\n        \"\"\"\n        获取用于控制设备的锁，成功返回 true，被占用则会引发异常提示\n        \"\"\"\n        req = protos.Integer(value=leaseTime)\n        r = self.stub.acquireLock(req)\n        return r.value\n    def get_session_token(self):\n        \"\"\"\n        获取当前会话的动态令牌\n        \"\"\"\n        r = self.stub.getSessionToken(protos.Empty())\n        return r.value\n    def refresh_lock(self, leaseTime=60):\n        \"\"\"\n        刷新用于控制设备的锁，应该在定时任务每60s内调用以保持会话\n        \"\"\"\n        req = protos.Integer(value=leaseTime)\n        r = self.stub.refreshLock(req)\n        return r.value\n    def release_lock(self):\n        \"\"\"\n        释放控制设备的锁，释放后该设备可被其他客户端控制\n        \"\"\"\n        r = self.stub.releaseLock(protos.Empty())\n        return r.value\n\n\nclass WifiStub(BaseServiceStub):\n    def status(self):\n        \"\"\"\n        获取当前已连接 WIFI 的信息\n        \"\"\"\n        r = self.stub.status(protos.Empty())\n        return r\n    def blacklist_add(self, bssid):\n        \"\"\"\n        将 BSSID 加入 WIFI BSSID 黑名单（将不会在WIFI列表显示）\n        \"\"\"\n        r = self.stub.blacklistAdd(protos.String(value=bssid))\n        return r.value\n    def blacklist_clear(self):\n        \"\"\"\n        清空 WIFI BSSID 黑名单\n        \"\"\"\n        r = self.stub.blacklistClear(protos.Empty())\n        return r.value\n    def blacklist_get_all(self):\n        \"\"\"\n        获取在 WIFI BSSID 黑名单中的所有 BSSID\n        \"\"\"\n        r = self.stub.blacklistAll(protos.Empty())\n        return r.bssids\n    def scan(self):\n        \"\"\"\n        请求扫描附近 WIFI\n        \"\"\"\n        r = self.stub.scan(protos.Empty())\n        return r.value\n    def scan_results(self):\n        \"\"\"\n        获取已扫描到的附近 WIFI\n        \"\"\"\n        r = self.stub.scanResults(protos.Empty())\n        return r.stations\n    def get_mac_addr(self):\n        \"\"\"\n        获取当前 WIFI 的 MAC 地址\n        \"\"\"\n        r = self.stub.getMacAddr(protos.Empty())\n        return r.value\n    def signal_poll(self):\n        \"\"\"\n        获取当前已连接 WIFI 的信号强度等信息\n        \"\"\"\n        r = self.stub.signalPoll(protos.Empty())\n        return r\n    def list_networks(self):\n        \"\"\"\n        列出已连接过的 WIFI 网络\n        \"\"\"\n        r = self.stub.listNetworks(protos.Empty())\n        return r.networks\n    def select_network(self, networkId):\n        raise NotImplementedError\n    def enable_network(self, networkId):\n        raise NotImplementedError\n    def disable_network(self, networkId):\n        raise NotImplementedError\n    def add_network(self):\n        raise NotImplementedError\n    def remove_network(self, networkId):\n        raise NotImplementedError\n    def set_network_config(self, networkId, name, value):\n        raise NotImplementedError\n    def get_network_config(self, networkId, name):\n        raise NotImplementedError\n    def disconnect(self):\n        \"\"\"\n        断开 WIFI 连接\n        \"\"\"\n        r = self.stub.disconnect(protos.Empty())\n        return r.value\n    def reconnect(self):\n        \"\"\"\n        重连 WIFI\n        \"\"\"\n        r = self.stub.reconnect(protos.Empty())\n        return r.value\n    def set_config(self, name, value):\n        raise NotImplementedError\n    def set_auto_connect(self, auto=True):\n        raise NotImplementedError\n    def save_config(self):\n        raise NotImplementedError\n\n\nclass OcrOperator(object):\n    def __init__(self, device, elements=None,\n                                    **kwargs):\n        self.elements = elements\n        self.index = kwargs.pop(\"index\", 0)\n        self.func, self.rule = kwargs.popitem()\n        self.match = getattr(self, self.func)\n        self.device = device\n    def text(self, item):\n        return self.rule == item[\"text\"]\n    def textMatches(self, item):\n        return bool(re.match(self.rule, item[\"text\"],\n                                        re.DOTALL))\n    def textContains(self, item):\n        return self.rule in item[\"text\"]\n    def find_target_item(self):\n        m = [e for e in self.elements \\\n                            if self.match(e)]\n        o = (m and len(m) > self.index) != True\n        return None if o else m[self.index]\n    def find_item_or_throw(self):\n        item = self.find_target_item()\n        msg = \"OcrSelector[{}={}]\".format(self.func, self.rule)\n        item or self.throw(UiObjectNotFoundException, msg)\n        return item\n    def find_cb(self, func, ret, *args):\n        item = self.find_target_item()\n        return func(item, *args) if item else ret\n    def find_or_throw_cb(self, func, *args):\n        item = self.find_item_or_throw()\n        return func(item, *args)\n    def throw(self, exception, *args):\n        raise exception(*args)\n    def _screenshot(self, item, quality):\n        return self.device.screenshot(quality,\n                            bound=item[\"bound\"])\n    def _click(self, item):\n        point = item[\"bound\"].center()\n        return self.device.click(point)\n    def __str__(self):\n        return \"Ocr: {}={}\".format(self.func, self.rule)\n    __repr__ = __str__\n    def exists(self):\n        \"\"\"\n        OCR - 检查元素是否存在\n        \"\"\"\n        return bool(self.find_target_item())\n    def click(self):\n        \"\"\"\n        OCR - 点击元素（不存在则报错）\n        \"\"\"\n        return self.find_or_throw_cb(self._click)\n    def click_exists(self):\n        \"\"\"\n        OCR - 点击元素（不存在将不会产生异常）\n        \"\"\"\n        return self.find_cb(self._click, False)\n    def screenshot(self, quality=100):\n        \"\"\"\n        OCR - 对元素进行截图\n        \"\"\"\n        return self.find_or_throw_cb(self._screenshot,\n                                            quality)\n    def take_screenshot(self, quality=100):\n        \"\"\"\n        OCR - 对元素进行截图\n        \"\"\"\n        return self.screenshot(quality)\n    def info(self):\n        \"\"\"\n        OCR - 获取匹配元素的信息\n        \"\"\"\n        item = self.find_item_or_throw()\n        return item\n\n\nclass OcrEngine(object):\n    def __init__(self, service, *args,\n                                     **kwargs):\n        args = list(args)\n        if type(service) == type:\n            args.insert(0, service)\n            service = \"custom\"\n        func = getattr(self, \"init_{}\".format(service))\n        func(*args, **kwargs)\n    def init_paddleocr(self, *args, **kwargs):\n        from paddleocr import PaddleOCR\n        self._service = PaddleOCR(*args, **kwargs)\n        self._ocr = self.ocr_paddleocr\n    def init_easyocr(self, *args, **kwargs):\n        from easyocr import Reader\n        self._service = Reader(*args, **kwargs)\n        self._ocr = self.ocr_easyocr\n    def init_custom(self, service, *args, **kwargs):\n        self._service = service(*args, **kwargs)\n        self._ocr = self.ocr_custom\n    def ocr_custom(self, image):\n        result = self._service.ocr(image)\n        return result\n    def ocr_paddleocr(self, image):\n        r = self._service.ocr(image)\n        n = bool(r and r[0] and type(r[0][-1])==float)\n        result = (r if n else r[0]) or []\n        output = [[n[0], n[1][0], n[1][1]] for n in result]\n        return output\n    def ocr_easyocr(self, image):\n        result = self._service.readtext(image)\n        return result\n    def ocr(self, screenshot):\n        img = screenshot.getvalue()\n        result = self._ocr(img) or []\n        output = [self.format(*n) for n in result]\n        return output\n    def format(self, box, text, confidence):\n        bound = Bound()\n        bound.left      = int(min(p[0] for p in box))\n        bound.top       = int(min(p[1] for p in box))\n        bound.bottom    = int(max(p[1] for p in box))\n        bound.right     = int(max(p[0] for p in box))\n        info = dict(text=text, confidence=confidence,\n                                        bound=bound)\n        return info\n\n\nclass Device(object):\n    def __init__(self, host, port=65000,\n                                        certificate=None,\n                                        session=None):\n        self.certificate = certificate\n        self.server = \"{0}:{1}\".format(host, port)\n        policy = dict()\n        policy[\"maxAttempts\"] = 5\n        policy[\"retryableStatusCodes\"] = [\"UNAVAILABLE\"]\n        policy[\"backoffMultiplier\"] = 2\n        policy[\"initialBackoff\"] = \"0.5s\"\n        policy[\"maxBackoff\"] = \"15s\"\n        config = json.dumps(dict(methodConfig=[{\"name\": [{}],\n                                 \"retryPolicy\": policy,}]))\n        option = dict()\n        option[\"grpc.max_send_message_length\"] = 64*1024*1024\n        option[\"grpc.max_receive_message_length\"] = 128*1024*1024\n        option[\"grpc.keepalive_time_ms\"] = 30*1000\n        option[\"grpc.keepalive_timeout_ms\"] = 15*1000\n        option[\"grpc.keepalive_permit_without_calls\"] = True\n        option[\"grpc.max_pings_without_data\"] = 0\n        option[\"grpc.service_config\"] = config\n        option[\"grpc.enable_http_proxy\"] = 0\n        if certificate is not None:\n            with open(certificate, \"rb\") as fd:\n                key, crt, ca = self._parse_certdata(fd.read())\n            creds = grpc.ssl_channel_credentials(root_certificates=ca,\n                                                 certificate_chain=crt,\n                                                 private_key=key)\n            self._chan = grpc.secure_channel(self.server, creds,\n                    options=((\"grpc.ssl_target_name_override\",\n                                self._parse_cname(crt)),\n                             *tuple(option.items()),))\n        else:\n            self._chan = grpc.insecure_channel(self.server,\n                    options=(*tuple(option.items()),)\n            )\n        session = session or uuid.uuid4().hex\n        interceptors = [ClientSessionMetadataInterceptor(session),\n                        GrpcRemoteExceptionInterceptor(),\n                        ClientLoggingInterceptor()]\n        self._ocr = None\n        self._ocr_img_quality = 75\n        self.channel = grpc.intercept_channel(self._chan,\n                        *interceptors)\n        self.session = session\n    @property\n    def frida(self):\n        if _frida_dma is None:\n            raise ModuleNotFoundError(\"frida\")\n        try:\n            device = _frida_dma.get_device_matching(\n                        lambda d: d.name==self.server)\n            # make a call to check server connectivity\n            device.query_system_parameters()\n            return device\n        except:\n            \"\"\" No-op \"\"\"\n        kwargs = {}\n        if self.certificate is not None:\n            kwargs[\"certificate\"] = self.certificate\n        if self._get_session_token():\n            kwargs[\"token\"] = self._get_session_token()\n        try:\n            _frida_dma.remove_remote_device(self.server)\n        except frida.InvalidArgumentError:\n            \"\"\" No-op \"\"\"\n        device = _frida_dma.add_remote_device(self.server,\n                                        **kwargs)\n        return device\n    def __str__(self):\n        return \"Device@{}\".format(self.server)\n    __repr__ = __str__\n    def _parse_certdata(self, data):\n        key, crt, ca = Pem.parse(data)\n        ca = ca.as_bytes()\n        crt = crt.as_bytes()\n        key = key.as_bytes()\n        return key, crt, ca\n    def _parse_cname(self, crt):\n        _, _, der = pem.unarmor(crt)\n        subject = x509.Certificate.load(der).subject\n        return subject.native[\"common_name\"]\n    def _get_service_stub(self, module):\n        stub = getattr(services, \"{0}Stub\".format(module))\n        return stub(self.channel)\n    def stub(self, module):\n        modu = sys.modules[__name__]\n        stub = self._get_service_stub(module)\n        wrap = getattr(modu, \"{0}Stub\".format(module))\n        inst = getattr(self, module, wrap(stub))\n        self.__setattr__(module, inst)\n        return inst\n    # 快速调用: File\n    def download_fd(self, fpath, fd):\n        return self.stub(\"File\").download_fd(fpath, fd)\n    def upload_fd(self, fd, dest):\n        return self.stub(\"File\").upload_fd(fd, dest)\n    def download_file(self, fpath, dest):\n        return self.stub(\"File\").download_file(fpath, dest)\n    def upload_file(self, fpath, dest):\n        return self.stub(\"File\").upload_file(fpath, dest)\n    def delete_file(self, fpath):\n        return self.stub(\"File\").delete_file(fpath)\n    def file_chmod(self, fpath, mode=0o644):\n        return self.stub(\"File\").file_chmod(fpath, mode=mode)\n    def file_stat(self, fpath):\n        return self.stub(\"File\").file_stat(fpath)\n    # 快速调用: Application\n    def install_local_file(self, rpath, user=0):\n        return self.stub(\"Application\").install_local_file(rpath, user=user)\n    def current_application(self):\n        return self.stub(\"Application\").current_application()\n    def enumerate_all_pkg_names(self):\n        return self.stub(\"Application\").enumerate_all_pkg_names()\n    def enumerate_running_processes(self):\n        return self.stub(\"Application\").enumerate_running_processes()\n    def get_last_activities(self, count=3):\n        return self.stub(\"Application\").get_last_activities(count=count)\n    def start_activity(self, **activity):\n        return self.stub(\"Application\").start_activity(**activity)\n    def get_application_by_name(self, name):\n        return self.stub(\"Application\").get_application_by_name(name)\n    def application(self, applicationId, user=0):\n        return self.stub(\"Application\")(applicationId, user=user)\n    # 快速调用: Util\n    def record_touch(self):\n        return self.stub(\"Util\").record_touch()\n    def perform_touch(self, sequence, wait=True):\n        return self.stub(\"Util\").perform_touch(sequence, wait=wait)\n    def show_toast(self, text, duration=ToastDuration.TD_SHORT):\n        return self.stub(\"Util\").show_toast(text, duration=duration)\n    def is_ca_certificate_installed(self, certdata):\n        return self.stub(\"Util\").is_ca_certificate_installed(certdata)\n    def uninstall_ca_certificate(self, certfile):\n        return self.stub(\"Util\").uninstall_ca_certificate(certfile)\n    def install_ca_certificate(self, certfile):\n        return self.stub(\"Util\").install_ca_certificate(certfile)\n    def reboot(self):\n        return self.stub(\"Util\").reboot()\n    def shutdown(self):\n        return self.stub(\"Util\").shutdown()\n    def exit(self):\n        return self.stub(\"Util\").exit()\n    def reload(self, clean=False):\n        return self.stub(\"Util\").reload(clean)\n    def beep(self):\n        return self.stub(\"Util\").beep()\n    def play_audio(self, file, type=AudioStreamType.AST_SYSTEM,\n                                        loop=1, interval=0):\n        return self.stub(\"Util\").play_audio(file, type=type, loop=loop,\n                                        interval=interval)\n    def setprop(self, name, value):\n        return self.stub(\"Util\").setprop(name, value)\n    def getprop(self, name):\n        return self.stub(\"Util\").getprop(name)\n    def hex_patch(self, pattern, replacement, path,\n                            maxreplace=-1, dryrun=False):\n        return self.stub(\"Util\").hex_patch(pattern, replacement, path,\n                                    maxreplace=maxreplace,\n                                    dryrun=dryrun)\n    # 快速调用: Debug\n    def install_adb_pubkey(self, pubkey):\n        return self.stub(\"Debug\").install_adb_pubkey(pubkey)\n    def uninstall_adb_pubkey(self, pubkey):\n        return self.stub(\"Debug\").uninstall_adb_pubkey(pubkey)\n    def start_android_debug_bridge(self):\n        return self.stub(\"Debug\").start_android_debug_bridge()\n    def is_android_debug_bridge_running(self):\n        return self.stub(\"Debug\").is_android_debug_bridge_running()\n    def stop_android_debug_bridge(self):\n        return self.stub(\"Debug\").stop_android_debug_bridge()\n    # 快速调用: Proxy\n    def is_openvpn_running(self):\n        return self.stub(\"Proxy\").is_openvpn_running()\n    def is_gproxy_running(self):\n        return self.stub(\"Proxy\").is_gproxy_running()\n    def start_openvpn(self, profile):\n        return self.stub(\"Proxy\").start_openvpn(profile)\n    def start_gproxy(self, profile):\n        return self.stub(\"Proxy\").start_gproxy(profile)\n    def stop_openvpn(self):\n        return self.stub(\"Proxy\").stop_openvpn()\n    def stop_gproxy(self):\n        return self.stub(\"Proxy\").stop_gproxy()\n    # 快速调用: Shell\n    def execute_script(self, script, alias=None, timeout=60):\n        return self.stub(\"Shell\").execute_script(script, alias=alias,\n                                                        timeout=timeout)\n    def execute_background_script(self, script, alias=None):\n        return self.stub(\"Shell\").execute_background_script(script, alias=alias)\n    def is_background_script_finished(self, tid):\n        return self.stub(\"Shell\").is_background_script_finished(tid)\n    def kill_background_script(self, tid):\n        return self.stub(\"Shell\").kill_background_script(tid)\n    # 快速调用: UiAutomator\n    def click(self, point):\n        return self.stub(\"UiAutomator\").click(point)\n    def drag(self, A, B, step=32):\n        return self.stub(\"UiAutomator\").drag(A, B, step=step)\n    def swipe(self, A, B, step=32):\n        return self.stub(\"UiAutomator\").swipe(A, B, step=step)\n    def swipe_points(self, *points, step=32):\n        return self.stub(\"UiAutomator\").swipe_points(*points, step=step)\n    def open_notification(self):\n        return self.stub(\"UiAutomator\").open_notification()\n    def open_quick_settings(self):\n        return self.stub(\"UiAutomator\").open_quick_settings()\n    def wake_up(self):\n        return self.stub(\"UiAutomator\").wake_up()\n    def sleep(self):\n        return self.stub(\"UiAutomator\").sleep()\n    def is_screen_on(self):\n        return self.stub(\"UiAutomator\").is_screen_on()\n    def is_screen_locked(self):\n        return self.stub(\"UiAutomator\").is_screen_locked()\n    def set_clipboard(self, text):\n        return self.stub(\"UiAutomator\").set_clipboard(text)\n    def get_clipboard(self):\n        return self.stub(\"UiAutomator\").get_clipboard()\n    def freeze_rotation(self, freeze=True):\n        return self.stub(\"UiAutomator\").freeze_rotation(freeze=freeze)\n    def set_orientation(self, orien=Orientation.ORIEN_NATURE):\n        return self.stub(\"UiAutomator\").set_orientation(orien)\n    def press_key(self, key):\n        return self.stub(\"UiAutomator\").press_key(key)\n    def press_keycode(self, code, meta=0):\n        return self.stub(\"UiAutomator\").press_keycode(code, meta)\n    def take_screenshot(self, quality=100, bound=None):\n        return self.stub(\"UiAutomator\").take_screenshot(quality, bound=bound)\n    def screenshot(self, quality=100, bound=None):\n        return self.stub(\"UiAutomator\").screenshot(quality, bound=bound)\n    def dump_window_hierarchy(self, compressed=False):\n        return self.stub(\"UiAutomator\").dump_window_hierarchy(compressed=compressed)\n    def wait_for_idle(self, timeout):\n        return self.stub(\"UiAutomator\").wait_for_idle(timeout)\n    def get_last_toast(self):\n        return self.stub(\"UiAutomator\").get_last_toast()\n    def find_similar_image(self, data, threshold=0.0, distance=250,\n                                scale=1.0, area=FindImageArea.FIA_WHOLE_SCREEN,\n                                method=FindImageMethod.FIM_TEMPLATE):\n        return self.stub(\"UiAutomator\").find_similar_image(data, threshold=threshold,\n                                distance=distance, scale=scale,\n                                area=area, method=method)\n    # watcher\n    def remove_all_watchers(self):\n        return self.stub(\"UiAutomator\").remove_all_watchers()\n    def set_watcher_loop_enabled(self, enabled):\n        return self.stub(\"UiAutomator\").set_watcher_loop_enabled(enabled)\n    def get_watcher_loop_enabled(self):\n        return self.stub(\"UiAutomator\").get_watcher_loop_enabled()\n    def get_watcher_triggered_count(self, name):\n        return self.stub(\"UiAutomator\").get_watcher_triggered_count(name)\n    def reset_watcher_triggered_count(self, name):\n        return self.stub(\"UiAutomator\").reset_watcher_triggered_count(name)\n    def get_applied_watchers(self):\n        return self.stub(\"UiAutomator\").get_applied_watchers()\n    def register_click_target_selector_watcher(self, name, conditions,\n                                               target):\n        return self.stub(\"UiAutomator\").register_click_target_selector_watcher(\n                                                name, conditions, target\n        )\n    def register_press_key_watcher(self, name, conditions, key):\n        return self.stub(\"UiAutomator\").register_press_key_watcher(\n                                                name, conditions, key\n        )\n    def register_none_op_watcher(self, name, conditions):\n        return self.stub(\"UiAutomator\").register_none_op_watcher(\n                                                name, conditions\n        )\n    def set_watcher_enabled(self, name, enable):\n        return self.stub(\"UiAutomator\").set_watcher_enabled(name, enable)\n    def get_watcher_enabled(self, name):\n        return self.stub(\"UiAutomator\").get_watcher_enabled(name)\n    def remove_watcher(self, name):\n        return self.stub(\"UiAutomator\").remove_watcher(name)\n    def device_info(self):\n        return self.stub(\"UiAutomator\").device_info()\n    def server_info(self):\n        return self.stub(\"Util\").server_info()\n    def __call__(self, **kwargs):\n        return self.stub(\"UiAutomator\")(**kwargs)\n    # OCR 功能扩展\n    def ocr(self, index=0, **kwargs):\n        if not isinstance(self._ocr, OcrEngine):\n            raise IllegalStateException(\"Ocr engine is not setted up\")\n        if any(r not in [\"text\", \"textContains\", \"textMatches\"] \\\n                                        for r in kwargs.keys()):\n            raise InvalidArgumentError(\"Only text* matches are supported\")\n        if len(kwargs) != 1:\n            raise InvalidArgumentError(\"Only or at least one rule can be used\")\n        image = self.screenshot(self._ocr_img_quality)\n        return OcrOperator(self,\n        elements=self._ocr.ocr(image),\n                            index=index,\n                            **kwargs\n        )\n    def setup_ocr_backend(self, service, *args, quality=75,\n                                                **kwargs):\n        self._ocr_img_quality = quality\n        self._ocr = OcrEngine(service, *args,\n                                    **kwargs)\n    # 日志打印\n    def set_debug_log_enabled(self, enable):\n        level = logging.DEBUG if enable else logging.WARN\n        logger.setLevel(level)\n        return enable\n    # 接口锁定\n    def _get_session_token(self):\n        return self.stub(\"Lock\").get_session_token()\n    def _acquire_lock(self, leaseTime=60):\n        return self.stub(\"Lock\").acquire_lock(leaseTime)\n    def _refresh_lock(self, leaseTime=60):\n        return self.stub(\"Lock\").refresh_lock(leaseTime)\n    def _release_lock(self):\n        return self.stub(\"Lock\").release_lock()\n    def __enter__(self):\n        self._acquire_lock(leaseTime=sys.maxsize)\n        return self\n    def __exit__(self, type, value, traceback):\n        self._release_lock()\n\n\nif __name__ == \"__main__\":\n    import code\n    import readline\n    import rlcompleter\n    import argparse\n\n    parser = argparse.ArgumentParser()\n    crt = os.environ.get(\"CERTIFICATE\", None)\n    port = int(os.environ.get(\"PORT\", 65000))\n    parser.add_argument(\"-device\", type=str, default=\"127.0.0.1\",\n                                   help=\"service ip address\")\n    parser.add_argument(\"-port\", type=int, default=port,\n                                   help=\"service port\")\n    parser.add_argument(\"-cert\", type=str, default=crt,\n                                   help=\"ssl cert\")\n    args = parser.parse_args()\n\n    readline.parse_and_bind(\"tab: complete\")\n    d = Device(args.device, port=args.port,\n                        certificate=args.cert)\n    code.interact(local=globals())"
  },
  {
    "path": "lamda/const.py",
    "content": "# Copyright 2022 rev1si0n (https://github.com/rev1si0n). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\n\n# Android runtime permissions\nPERMISSION_READ_SMS = \"android.permission.READ_SMS\"\nPERMISSION_READ_CALENDAR = \"android.permission.READ_CALENDAR\"\nPERMISSION_READ_CALL_LOG = \"android.permission.READ_CALL_LOG\"\nPERMISSION_ACCESS_FINE_LOCATION = \"android.permission.ACCESS_FINE_LOCATION\"\nPERMISSION_ANSWER_PHONE_CALLS = \"android.permission.ANSWER_PHONE_CALLS\"\nPERMISSION_RECEIVE_WAP_PUSH = \"android.permission.RECEIVE_WAP_PUSH\"\nPERMISSION_BODY_SENSORS = \"android.permission.BODY_SENSORS\"\nPERMISSION_READ_PHONE_NUMBERS = \"android.permission.READ_PHONE_NUMBERS\"\nPERMISSION_RECEIVE_MMS = \"android.permission.RECEIVE_MMS\"\nPERMISSION_RECEIVE_SMS = \"android.permission.RECEIVE_SMS\"\nPERMISSION_READ_EXTERNAL_STORAGE = \"android.permission.READ_EXTERNAL_STORAGE\"\nPERMISSION_ACCESS_COARSE_LOCATION = \"android.permission.ACCESS_COARSE_LOCATION\"\nPERMISSION_READ_PHONE_STATE = \"android.permission.READ_PHONE_STATE\"\nPERMISSION_SEND_SMS = \"android.permission.SEND_SMS\"\nPERMISSION_CALL_PHONE = \"android.permission.CALL_PHONE\"\nPERMISSION_WRITE_CONTACTS = \"android.permission.WRITE_CONTACTS\"\nPERMISSION_ACCEPT_HANDOVER = \"android.permission.ACCEPT_HANDOVER\"\nPERMISSION_CAMERA = \"android.permission.CAMERA\"\nPERMISSION_WRITE_CALENDAR = \"android.permission.WRITE_CALENDAR\"\nPERMISSION_WRITE_CALL_LOG = \"android.permission.WRITE_CALL_LOG\"\nPERMISSION_USE_SIP = \"android.permission.USE_SIP\"\nPERMISSION_PROCESS_OUTGOING_CALLS = \"android.permission.PROCESS_OUTGOING_CALLS\"\nPERMISSION_READ_CELL_BROADCASTS = \"android.permission.READ_CELL_BROADCASTS\"\nPERMISSION_GET_ACCOUNTS = \"android.permission.GET_ACCOUNTS\"\nPERMISSION_WRITE_EXTERNAL_STORAGE = \"android.permission.WRITE_EXTERNAL_STORAGE\"\nPERMISSION_ACTIVITY_RECOGNITION = \"android.permission.ACTIVITY_RECOGNITION\"\nPERMISSION_RECORD_AUDIO = \"android.permission.RECORD_AUDIO\"\nPERMISSION_READ_CONTACTS = \"android.permission.READ_CONTACTS\"\nPERMISSION_ACCESS_BACKGROUND_LOCATION = \"android.permission.ACCESS_BACKGROUND_LOCATION\"\nPERMISSION_ACCESS_MEDIA_LOCATION = \"android.permission.ACCESS_MEDIA_LOCATION\"\n\n# Android activity flags\nFLAG_ACTIVITY_BROUGHT_TO_FRONT = 0x00400000\nFLAG_ACTIVITY_CLEAR_TASK = 0x00008000\nFLAG_ACTIVITY_CLEAR_TOP = 0x04000000\nFLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 0x00800000\nFLAG_ACTIVITY_FORWARD_RESULT = 0x02000000\nFLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000\nFLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000\nFLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800\n\nFLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000\nFLAG_ACTIVITY_NEW_DOCUMENT = 0x00080000\nFLAG_ACTIVITY_NEW_TASK = 0x10000000\nFLAG_ACTIVITY_NO_ANIMATION = 0x00010000\nFLAG_ACTIVITY_NO_HISTORY = 0x40000000\nFLAG_ACTIVITY_NO_USER_ACTION = 0x00040000\n\nFLAG_ACTIVITY_PREVIOUS_IS_TOP = 0x01000000\nFLAG_ACTIVITY_REORDER_TO_FRONT = 0x00020000\nFLAG_ACTIVITY_REQUIRE_DEFAULT = 0x00000200\nFLAG_ACTIVITY_REQUIRE_NON_BROWSER = 0x00000400\n\nFLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000\nFLAG_ACTIVITY_RETAIN_IN_RECENTS = 0x00002000\nFLAG_ACTIVITY_SINGLE_TOP = 0x20000000\nFLAG_ACTIVITY_TASK_ON_HOME = 0x00004000"
  },
  {
    "path": "lamda/exceptions.py",
    "content": "# Copyright 2022 rev1si0n (https://github.com/rev1si0n). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nclass CompatibilityException(Exception):\n    \"\"\" Exception \"\"\"\nclass DeadSystemException(Exception):\n    \"\"\" Exception \"\"\"\nclass DeviceUnavailable(Exception):\n    \"\"\" Exception \"\"\"\nclass DuplicateEntryError(Exception):\n    \"\"\" Exception \"\"\"\nclass IllegalArgumentException(Exception):\n    \"\"\" Exception \"\"\"\nclass IllegalStateException(Exception):\n    \"\"\" Exception \"\"\"\nclass InstallPackageFailed(Exception):\n    \"\"\" Exception \"\"\"\nclass InternalRpcException(Exception):\n    \"\"\" Exception \"\"\"\nclass InvalidAndroidPackage(Exception):\n    \"\"\" Exception \"\"\"\nclass InvalidArgumentError(Exception):\n    \"\"\" Exception \"\"\"\nclass InvalidOperationError(Exception):\n    \"\"\" Exception \"\"\"\nclass InvalidRootCertificate(Exception):\n    \"\"\" Exception \"\"\"\nclass MethodNotFoundException(Exception):\n    \"\"\" Exception \"\"\"\nclass NameNotFoundException(Exception):\n    \"\"\" Exception \"\"\"\nclass NotImplementedException(Exception):\n    \"\"\" Exception \"\"\"\nclass NullPointerException(Exception):\n    \"\"\" Exception \"\"\"\nclass SecurityException(Exception):\n    \"\"\" Exception \"\"\"\nclass ServiceUnavailable(Exception):\n    \"\"\" Exception \"\"\"\nclass StaleObjectException(Exception):\n    \"\"\" Exception \"\"\"\nclass StartupActivityNotFound(Exception):\n    \"\"\" Exception \"\"\"\nclass StorageOutOfMemory(Exception):\n    \"\"\" Exception \"\"\"\nclass UiAutomatorException(Exception):\n    \"\"\" Exception \"\"\"\nclass UiObjectNotFoundException(Exception):\n    \"\"\" Exception \"\"\"\nclass UnHandledException(Exception):\n    \"\"\" Exception \"\"\""
  },
  {
    "path": "lamda/google/protobuf/any.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption go_package = \"google.golang.org/protobuf/types/known/anypb\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"AnyProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\n\n// `Any` contains an arbitrary serialized protocol buffer message along with a\n// URL that describes the type of the serialized message.\n//\n// Protobuf library provides support to pack/unpack Any values in the form\n// of utility functions or additional generated methods of the Any type.\n//\n// Example 1: Pack and unpack a message in C++.\n//\n//     Foo foo = ...;\n//     Any any;\n//     any.PackFrom(foo);\n//     ...\n//     if (any.UnpackTo(&foo)) {\n//       ...\n//     }\n//\n// Example 2: Pack and unpack a message in Java.\n//\n//     Foo foo = ...;\n//     Any any = Any.pack(foo);\n//     ...\n//     if (any.is(Foo.class)) {\n//       foo = any.unpack(Foo.class);\n//     }\n//\n//  Example 3: Pack and unpack a message in Python.\n//\n//     foo = Foo(...)\n//     any = Any()\n//     any.Pack(foo)\n//     ...\n//     if any.Is(Foo.DESCRIPTOR):\n//       any.Unpack(foo)\n//       ...\n//\n//  Example 4: Pack and unpack a message in Go\n//\n//      foo := &pb.Foo{...}\n//      any, err := anypb.New(foo)\n//      if err != nil {\n//        ...\n//      }\n//      ...\n//      foo := &pb.Foo{}\n//      if err := any.UnmarshalTo(foo); err != nil {\n//        ...\n//      }\n//\n// The pack methods provided by protobuf library will by default use\n// 'type.googleapis.com/full.type.name' as the type URL and the unpack\n// methods only use the fully qualified type name after the last '/'\n// in the type URL, for example \"foo.bar.com/x/y.z\" will yield type\n// name \"y.z\".\n//\n//\n// JSON\n// ====\n// The JSON representation of an `Any` value uses the regular\n// representation of the deserialized, embedded message, with an\n// additional field `@type` which contains the type URL. Example:\n//\n//     package google.profile;\n//     message Person {\n//       string first_name = 1;\n//       string last_name = 2;\n//     }\n//\n//     {\n//       \"@type\": \"type.googleapis.com/google.profile.Person\",\n//       \"firstName\": <string>,\n//       \"lastName\": <string>\n//     }\n//\n// If the embedded message type is well-known and has a custom JSON\n// representation, that representation will be embedded adding a field\n// `value` which holds the custom JSON in addition to the `@type`\n// field. Example (for message [google.protobuf.Duration][]):\n//\n//     {\n//       \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n//       \"value\": \"1.212s\"\n//     }\n//\nmessage Any {\n  // A URL/resource name that uniquely identifies the type of the serialized\n  // protocol buffer message. This string must contain at least\n  // one \"/\" character. The last segment of the URL's path must represent\n  // the fully qualified name of the type (as in\n  // `path/google.protobuf.Duration`). The name should be in a canonical form\n  // (e.g., leading \".\" is not accepted).\n  //\n  // In practice, teams usually precompile into the binary all types that they\n  // expect it to use in the context of Any. However, for URLs which use the\n  // scheme `http`, `https`, or no scheme, one can optionally set up a type\n  // server that maps type URLs to message definitions as follows:\n  //\n  // * If no scheme is provided, `https` is assumed.\n  // * An HTTP GET on the URL must yield a [google.protobuf.Type][]\n  //   value in binary format, or produce an error.\n  // * Applications are allowed to cache lookup results based on the\n  //   URL, or have them precompiled into a binary to avoid any\n  //   lookup. Therefore, binary compatibility needs to be preserved\n  //   on changes to types. (Use versioned type names to manage\n  //   breaking changes.)\n  //\n  // Note: this functionality is not currently available in the official\n  // protobuf release, and it is not used for type URLs beginning with\n  // type.googleapis.com.\n  //\n  // Schemes other than `http`, `https` (or the empty scheme) might be\n  // used with implementation specific semantics.\n  //\n  string type_url = 1;\n\n  // Must be a valid serialized protocol buffer of the above specified type.\n  bytes value = 2;\n}\n"
  },
  {
    "path": "lamda/google/protobuf/api.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\nimport \"google/protobuf/source_context.proto\";\nimport \"google/protobuf/type.proto\";\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"ApiProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\noption go_package = \"google.golang.org/protobuf/types/known/apipb\";\n\n// Api is a light-weight descriptor for an API Interface.\n//\n// Interfaces are also described as \"protocol buffer services\" in some contexts,\n// such as by the \"service\" keyword in a .proto file, but they are different\n// from API Services, which represent a concrete implementation of an interface\n// as opposed to simply a description of methods and bindings. They are also\n// sometimes simply referred to as \"APIs\" in other contexts, such as the name of\n// this message itself. See https://cloud.google.com/apis/design/glossary for\n// detailed terminology.\nmessage Api {\n  // The fully qualified name of this interface, including package name\n  // followed by the interface's simple name.\n  string name = 1;\n\n  // The methods of this interface, in unspecified order.\n  repeated Method methods = 2;\n\n  // Any metadata attached to the interface.\n  repeated Option options = 3;\n\n  // A version string for this interface. If specified, must have the form\n  // `major-version.minor-version`, as in `1.10`. If the minor version is\n  // omitted, it defaults to zero. If the entire version field is empty, the\n  // major version is derived from the package name, as outlined below. If the\n  // field is not empty, the version in the package name will be verified to be\n  // consistent with what is provided here.\n  //\n  // The versioning schema uses [semantic\n  // versioning](http://semver.org) where the major version number\n  // indicates a breaking change and the minor version an additive,\n  // non-breaking change. Both version numbers are signals to users\n  // what to expect from different versions, and should be carefully\n  // chosen based on the product plan.\n  //\n  // The major version is also reflected in the package name of the\n  // interface, which must end in `v<major-version>`, as in\n  // `google.feature.v1`. For major versions 0 and 1, the suffix can\n  // be omitted. Zero major versions must only be used for\n  // experimental, non-GA interfaces.\n  //\n  //\n  string version = 4;\n\n  // Source context for the protocol buffer service represented by this\n  // message.\n  SourceContext source_context = 5;\n\n  // Included interfaces. See [Mixin][].\n  repeated Mixin mixins = 6;\n\n  // The source syntax of the service.\n  Syntax syntax = 7;\n}\n\n// Method represents a method of an API interface.\nmessage Method {\n  // The simple name of this method.\n  string name = 1;\n\n  // A URL of the input message type.\n  string request_type_url = 2;\n\n  // If true, the request is streamed.\n  bool request_streaming = 3;\n\n  // The URL of the output message type.\n  string response_type_url = 4;\n\n  // If true, the response is streamed.\n  bool response_streaming = 5;\n\n  // Any metadata attached to the method.\n  repeated Option options = 6;\n\n  // The source syntax of this method.\n  Syntax syntax = 7;\n}\n\n// Declares an API Interface to be included in this interface. The including\n// interface must redeclare all the methods from the included interface, but\n// documentation and options are inherited as follows:\n//\n// - If after comment and whitespace stripping, the documentation\n//   string of the redeclared method is empty, it will be inherited\n//   from the original method.\n//\n// - Each annotation belonging to the service config (http,\n//   visibility) which is not set in the redeclared method will be\n//   inherited.\n//\n// - If an http annotation is inherited, the path pattern will be\n//   modified as follows. Any version prefix will be replaced by the\n//   version of the including interface plus the [root][] path if\n//   specified.\n//\n// Example of a simple mixin:\n//\n//     package google.acl.v1;\n//     service AccessControl {\n//       // Get the underlying ACL object.\n//       rpc GetAcl(GetAclRequest) returns (Acl) {\n//         option (google.api.http).get = \"/v1/{resource=**}:getAcl\";\n//       }\n//     }\n//\n//     package google.storage.v2;\n//     service Storage {\n//       rpc GetAcl(GetAclRequest) returns (Acl);\n//\n//       // Get a data record.\n//       rpc GetData(GetDataRequest) returns (Data) {\n//         option (google.api.http).get = \"/v2/{resource=**}\";\n//       }\n//     }\n//\n// Example of a mixin configuration:\n//\n//     apis:\n//     - name: google.storage.v2.Storage\n//       mixins:\n//       - name: google.acl.v1.AccessControl\n//\n// The mixin construct implies that all methods in `AccessControl` are\n// also declared with same name and request/response types in\n// `Storage`. A documentation generator or annotation processor will\n// see the effective `Storage.GetAcl` method after inheriting\n// documentation and annotations as follows:\n//\n//     service Storage {\n//       // Get the underlying ACL object.\n//       rpc GetAcl(GetAclRequest) returns (Acl) {\n//         option (google.api.http).get = \"/v2/{resource=**}:getAcl\";\n//       }\n//       ...\n//     }\n//\n// Note how the version in the path pattern changed from `v1` to `v2`.\n//\n// If the `root` field in the mixin is specified, it should be a\n// relative path under which inherited HTTP paths are placed. Example:\n//\n//     apis:\n//     - name: google.storage.v2.Storage\n//       mixins:\n//       - name: google.acl.v1.AccessControl\n//         root: acls\n//\n// This implies the following inherited HTTP annotation:\n//\n//     service Storage {\n//       // Get the underlying ACL object.\n//       rpc GetAcl(GetAclRequest) returns (Acl) {\n//         option (google.api.http).get = \"/v2/acls/{resource=**}:getAcl\";\n//       }\n//       ...\n//     }\nmessage Mixin {\n  // The fully qualified name of the interface which is included.\n  string name = 1;\n\n  // If non-empty specifies a path under which inherited HTTP paths\n  // are rooted.\n  string root = 2;\n}\n"
  },
  {
    "path": "lamda/google/protobuf/compiler/plugin.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n// Author: kenton@google.com (Kenton Varda)\n//\n// WARNING:  The plugin interface is currently EXPERIMENTAL and is subject to\n//   change.\n//\n// protoc (aka the Protocol Compiler) can be extended via plugins.  A plugin is\n// just a program that reads a CodeGeneratorRequest from stdin and writes a\n// CodeGeneratorResponse to stdout.\n//\n// Plugins written using C++ can use google/protobuf/compiler/plugin.h instead\n// of dealing with the raw protocol defined here.\n//\n// A plugin executable needs only to be placed somewhere in the path.  The\n// plugin should be named \"protoc-gen-$NAME\", and will then be used when the\n// flag \"--${NAME}_out\" is passed to protoc.\n\nsyntax = \"proto2\";\n\npackage google.protobuf.compiler;\noption java_package = \"com.google.protobuf.compiler\";\noption java_outer_classname = \"PluginProtos\";\n\noption go_package = \"google.golang.org/protobuf/types/pluginpb\";\n\nimport \"google/protobuf/descriptor.proto\";\n\n// The version number of protocol compiler.\nmessage Version {\n  optional int32 major = 1;\n  optional int32 minor = 2;\n  optional int32 patch = 3;\n  // A suffix for alpha, beta or rc release, e.g., \"alpha-1\", \"rc2\". It should\n  // be empty for mainline stable releases.\n  optional string suffix = 4;\n}\n\n// An encoded CodeGeneratorRequest is written to the plugin's stdin.\nmessage CodeGeneratorRequest {\n  // The .proto files that were explicitly listed on the command-line.  The\n  // code generator should generate code only for these files.  Each file's\n  // descriptor will be included in proto_file, below.\n  repeated string file_to_generate = 1;\n\n  // The generator parameter passed on the command-line.\n  optional string parameter = 2;\n\n  // FileDescriptorProtos for all files in files_to_generate and everything\n  // they import.  The files will appear in topological order, so each file\n  // appears before any file that imports it.\n  //\n  // protoc guarantees that all proto_files will be written after\n  // the fields above, even though this is not technically guaranteed by the\n  // protobuf wire format.  This theoretically could allow a plugin to stream\n  // in the FileDescriptorProtos and handle them one by one rather than read\n  // the entire set into memory at once.  However, as of this writing, this\n  // is not similarly optimized on protoc's end -- it will store all fields in\n  // memory at once before sending them to the plugin.\n  //\n  // Type names of fields and extensions in the FileDescriptorProto are always\n  // fully qualified.\n  repeated FileDescriptorProto proto_file = 15;\n\n  // The version number of protocol compiler.\n  optional Version compiler_version = 3;\n\n}\n\n// The plugin writes an encoded CodeGeneratorResponse to stdout.\nmessage CodeGeneratorResponse {\n  // Error message.  If non-empty, code generation failed.  The plugin process\n  // should exit with status code zero even if it reports an error in this way.\n  //\n  // This should be used to indicate errors in .proto files which prevent the\n  // code generator from generating correct code.  Errors which indicate a\n  // problem in protoc itself -- such as the input CodeGeneratorRequest being\n  // unparseable -- should be reported by writing a message to stderr and\n  // exiting with a non-zero status code.\n  optional string error = 1;\n\n  // A bitmask of supported features that the code generator supports.\n  // This is a bitwise \"or\" of values from the Feature enum.\n  optional uint64 supported_features = 2;\n\n  // Sync with code_generator.h.\n  enum Feature {\n    FEATURE_NONE = 0;\n    FEATURE_PROTO3_OPTIONAL = 1;\n  }\n\n  // Represents a single generated file.\n  message File {\n    // The file name, relative to the output directory.  The name must not\n    // contain \".\" or \"..\" components and must be relative, not be absolute (so,\n    // the file cannot lie outside the output directory).  \"/\" must be used as\n    // the path separator, not \"\\\".\n    //\n    // If the name is omitted, the content will be appended to the previous\n    // file.  This allows the generator to break large files into small chunks,\n    // and allows the generated text to be streamed back to protoc so that large\n    // files need not reside completely in memory at one time.  Note that as of\n    // this writing protoc does not optimize for this -- it will read the entire\n    // CodeGeneratorResponse before writing files to disk.\n    optional string name = 1;\n\n    // If non-empty, indicates that the named file should already exist, and the\n    // content here is to be inserted into that file at a defined insertion\n    // point.  This feature allows a code generator to extend the output\n    // produced by another code generator.  The original generator may provide\n    // insertion points by placing special annotations in the file that look\n    // like:\n    //   @@protoc_insertion_point(NAME)\n    // The annotation can have arbitrary text before and after it on the line,\n    // which allows it to be placed in a comment.  NAME should be replaced with\n    // an identifier naming the point -- this is what other generators will use\n    // as the insertion_point.  Code inserted at this point will be placed\n    // immediately above the line containing the insertion point (thus multiple\n    // insertions to the same point will come out in the order they were added).\n    // The double-@ is intended to make it unlikely that the generated code\n    // could contain things that look like insertion points by accident.\n    //\n    // For example, the C++ code generator places the following line in the\n    // .pb.h files that it generates:\n    //   // @@protoc_insertion_point(namespace_scope)\n    // This line appears within the scope of the file's package namespace, but\n    // outside of any particular class.  Another plugin can then specify the\n    // insertion_point \"namespace_scope\" to generate additional classes or\n    // other declarations that should be placed in this scope.\n    //\n    // Note that if the line containing the insertion point begins with\n    // whitespace, the same whitespace will be added to every line of the\n    // inserted text.  This is useful for languages like Python, where\n    // indentation matters.  In these languages, the insertion point comment\n    // should be indented the same amount as any inserted code will need to be\n    // in order to work correctly in that context.\n    //\n    // The code generator that generates the initial file and the one which\n    // inserts into it must both run as part of a single invocation of protoc.\n    // Code generators are executed in the order in which they appear on the\n    // command line.\n    //\n    // If |insertion_point| is present, |name| must also be present.\n    optional string insertion_point = 2;\n\n    // The file contents.\n    optional string content = 15;\n\n    // Information describing the file content being inserted. If an insertion\n    // point is used, this information will be appropriately offset and inserted\n    // into the code generation metadata for the generated files.\n    optional GeneratedCodeInfo generated_code_info = 16;\n  }\n  repeated File file = 15;\n}\n"
  },
  {
    "path": "lamda/google/protobuf/descriptor.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n// Author: kenton@google.com (Kenton Varda)\n//  Based on original Protocol Buffers design by\n//  Sanjay Ghemawat, Jeff Dean, and others.\n//\n// The messages in this file describe the definitions found in .proto files.\n// A valid .proto file can be translated directly to a FileDescriptorProto\n// without any other information (e.g. without reading its imports).\n\n\nsyntax = \"proto2\";\n\npackage google.protobuf;\n\noption go_package = \"google.golang.org/protobuf/types/descriptorpb\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"DescriptorProtos\";\noption csharp_namespace = \"Google.Protobuf.Reflection\";\noption objc_class_prefix = \"GPB\";\noption cc_enable_arenas = true;\n\n// descriptor.proto must be optimized for speed because reflection-based\n// algorithms don't work during bootstrapping.\noption optimize_for = SPEED;\n\n// The protocol compiler can output a FileDescriptorSet containing the .proto\n// files it parses.\nmessage FileDescriptorSet {\n  repeated FileDescriptorProto file = 1;\n}\n\n// Describes a complete .proto file.\nmessage FileDescriptorProto {\n  optional string name = 1;     // file name, relative to root of source tree\n  optional string package = 2;  // e.g. \"foo\", \"foo.bar\", etc.\n\n  // Names of files imported by this file.\n  repeated string dependency = 3;\n  // Indexes of the public imported files in the dependency list above.\n  repeated int32 public_dependency = 10;\n  // Indexes of the weak imported files in the dependency list.\n  // For Google-internal migration only. Do not use.\n  repeated int32 weak_dependency = 11;\n\n  // All top-level definitions in this file.\n  repeated DescriptorProto message_type = 4;\n  repeated EnumDescriptorProto enum_type = 5;\n  repeated ServiceDescriptorProto service = 6;\n  repeated FieldDescriptorProto extension = 7;\n\n  optional FileOptions options = 8;\n\n  // This field contains optional information about the original source code.\n  // You may safely remove this entire field without harming runtime\n  // functionality of the descriptors -- the information is needed only by\n  // development tools.\n  optional SourceCodeInfo source_code_info = 9;\n\n  // The syntax of the proto file.\n  // The supported values are \"proto2\" and \"proto3\".\n  optional string syntax = 12;\n}\n\n// Describes a message type.\nmessage DescriptorProto {\n  optional string name = 1;\n\n  repeated FieldDescriptorProto field = 2;\n  repeated FieldDescriptorProto extension = 6;\n\n  repeated DescriptorProto nested_type = 3;\n  repeated EnumDescriptorProto enum_type = 4;\n\n  message ExtensionRange {\n    optional int32 start = 1;  // Inclusive.\n    optional int32 end = 2;    // Exclusive.\n\n    optional ExtensionRangeOptions options = 3;\n  }\n  repeated ExtensionRange extension_range = 5;\n\n  repeated OneofDescriptorProto oneof_decl = 8;\n\n  optional MessageOptions options = 7;\n\n  // Range of reserved tag numbers. Reserved tag numbers may not be used by\n  // fields or extension ranges in the same message. Reserved ranges may\n  // not overlap.\n  message ReservedRange {\n    optional int32 start = 1;  // Inclusive.\n    optional int32 end = 2;    // Exclusive.\n  }\n  repeated ReservedRange reserved_range = 9;\n  // Reserved field names, which may not be used by fields in the same message.\n  // A given name may only be reserved once.\n  repeated string reserved_name = 10;\n}\n\nmessage ExtensionRangeOptions {\n  // The parser stores options it doesn't recognize here. See above.\n  repeated UninterpretedOption uninterpreted_option = 999;\n\n\n  // Clients can define custom options in extensions of this message. See above.\n  extensions 1000 to max;\n}\n\n// Describes a field within a message.\nmessage FieldDescriptorProto {\n  enum Type {\n    // 0 is reserved for errors.\n    // Order is weird for historical reasons.\n    TYPE_DOUBLE = 1;\n    TYPE_FLOAT = 2;\n    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT64 if\n    // negative values are likely.\n    TYPE_INT64 = 3;\n    TYPE_UINT64 = 4;\n    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT32 if\n    // negative values are likely.\n    TYPE_INT32 = 5;\n    TYPE_FIXED64 = 6;\n    TYPE_FIXED32 = 7;\n    TYPE_BOOL = 8;\n    TYPE_STRING = 9;\n    // Tag-delimited aggregate.\n    // Group type is deprecated and not supported in proto3. However, Proto3\n    // implementations should still be able to parse the group wire format and\n    // treat group fields as unknown fields.\n    TYPE_GROUP = 10;\n    TYPE_MESSAGE = 11;  // Length-delimited aggregate.\n\n    // New in version 2.\n    TYPE_BYTES = 12;\n    TYPE_UINT32 = 13;\n    TYPE_ENUM = 14;\n    TYPE_SFIXED32 = 15;\n    TYPE_SFIXED64 = 16;\n    TYPE_SINT32 = 17;  // Uses ZigZag encoding.\n    TYPE_SINT64 = 18;  // Uses ZigZag encoding.\n  }\n\n  enum Label {\n    // 0 is reserved for errors\n    LABEL_OPTIONAL = 1;\n    LABEL_REQUIRED = 2;\n    LABEL_REPEATED = 3;\n  }\n\n  optional string name = 1;\n  optional int32 number = 3;\n  optional Label label = 4;\n\n  // If type_name is set, this need not be set.  If both this and type_name\n  // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP.\n  optional Type type = 5;\n\n  // For message and enum types, this is the name of the type.  If the name\n  // starts with a '.', it is fully-qualified.  Otherwise, C++-like scoping\n  // rules are used to find the type (i.e. first the nested types within this\n  // message are searched, then within the parent, on up to the root\n  // namespace).\n  optional string type_name = 6;\n\n  // For extensions, this is the name of the type being extended.  It is\n  // resolved in the same manner as type_name.\n  optional string extendee = 2;\n\n  // For numeric types, contains the original text representation of the value.\n  // For booleans, \"true\" or \"false\".\n  // For strings, contains the default text contents (not escaped in any way).\n  // For bytes, contains the C escaped value.  All bytes >= 128 are escaped.\n  // TODO(kenton):  Base-64 encode?\n  optional string default_value = 7;\n\n  // If set, gives the index of a oneof in the containing type's oneof_decl\n  // list.  This field is a member of that oneof.\n  optional int32 oneof_index = 9;\n\n  // JSON name of this field. The value is set by protocol compiler. If the\n  // user has set a \"json_name\" option on this field, that option's value\n  // will be used. Otherwise, it's deduced from the field's name by converting\n  // it to camelCase.\n  optional string json_name = 10;\n\n  optional FieldOptions options = 8;\n\n  // If true, this is a proto3 \"optional\". When a proto3 field is optional, it\n  // tracks presence regardless of field type.\n  //\n  // When proto3_optional is true, this field must be belong to a oneof to\n  // signal to old proto3 clients that presence is tracked for this field. This\n  // oneof is known as a \"synthetic\" oneof, and this field must be its sole\n  // member (each proto3 optional field gets its own synthetic oneof). Synthetic\n  // oneofs exist in the descriptor only, and do not generate any API. Synthetic\n  // oneofs must be ordered after all \"real\" oneofs.\n  //\n  // For message fields, proto3_optional doesn't create any semantic change,\n  // since non-repeated message fields always track presence. However it still\n  // indicates the semantic detail of whether the user wrote \"optional\" or not.\n  // This can be useful for round-tripping the .proto file. For consistency we\n  // give message fields a synthetic oneof also, even though it is not required\n  // to track presence. This is especially important because the parser can't\n  // tell if a field is a message or an enum, so it must always create a\n  // synthetic oneof.\n  //\n  // Proto2 optional fields do not set this flag, because they already indicate\n  // optional with `LABEL_OPTIONAL`.\n  optional bool proto3_optional = 17;\n}\n\n// Describes a oneof.\nmessage OneofDescriptorProto {\n  optional string name = 1;\n  optional OneofOptions options = 2;\n}\n\n// Describes an enum type.\nmessage EnumDescriptorProto {\n  optional string name = 1;\n\n  repeated EnumValueDescriptorProto value = 2;\n\n  optional EnumOptions options = 3;\n\n  // Range of reserved numeric values. Reserved values may not be used by\n  // entries in the same enum. Reserved ranges may not overlap.\n  //\n  // Note that this is distinct from DescriptorProto.ReservedRange in that it\n  // is inclusive such that it can appropriately represent the entire int32\n  // domain.\n  message EnumReservedRange {\n    optional int32 start = 1;  // Inclusive.\n    optional int32 end = 2;    // Inclusive.\n  }\n\n  // Range of reserved numeric values. Reserved numeric values may not be used\n  // by enum values in the same enum declaration. Reserved ranges may not\n  // overlap.\n  repeated EnumReservedRange reserved_range = 4;\n\n  // Reserved enum value names, which may not be reused. A given name may only\n  // be reserved once.\n  repeated string reserved_name = 5;\n}\n\n// Describes a value within an enum.\nmessage EnumValueDescriptorProto {\n  optional string name = 1;\n  optional int32 number = 2;\n\n  optional EnumValueOptions options = 3;\n}\n\n// Describes a service.\nmessage ServiceDescriptorProto {\n  optional string name = 1;\n  repeated MethodDescriptorProto method = 2;\n\n  optional ServiceOptions options = 3;\n}\n\n// Describes a method of a service.\nmessage MethodDescriptorProto {\n  optional string name = 1;\n\n  // Input and output type names.  These are resolved in the same way as\n  // FieldDescriptorProto.type_name, but must refer to a message type.\n  optional string input_type = 2;\n  optional string output_type = 3;\n\n  optional MethodOptions options = 4;\n\n  // Identifies if client streams multiple client messages\n  optional bool client_streaming = 5 [default = false];\n  // Identifies if server streams multiple server messages\n  optional bool server_streaming = 6 [default = false];\n}\n\n\n// ===================================================================\n// Options\n\n// Each of the definitions above may have \"options\" attached.  These are\n// just annotations which may cause code to be generated slightly differently\n// or may contain hints for code that manipulates protocol messages.\n//\n// Clients may define custom options as extensions of the *Options messages.\n// These extensions may not yet be known at parsing time, so the parser cannot\n// store the values in them.  Instead it stores them in a field in the *Options\n// message called uninterpreted_option. This field must have the same name\n// across all *Options messages. We then use this field to populate the\n// extensions when we build a descriptor, at which point all protos have been\n// parsed and so all extensions are known.\n//\n// Extension numbers for custom options may be chosen as follows:\n// * For options which will only be used within a single application or\n//   organization, or for experimental options, use field numbers 50000\n//   through 99999.  It is up to you to ensure that you do not use the\n//   same number for multiple options.\n// * For options which will be published and used publicly by multiple\n//   independent entities, e-mail protobuf-global-extension-registry@google.com\n//   to reserve extension numbers. Simply provide your project name (e.g.\n//   Objective-C plugin) and your project website (if available) -- there's no\n//   need to explain how you intend to use them. Usually you only need one\n//   extension number. You can declare multiple options with only one extension\n//   number by putting them in a sub-message. See the Custom Options section of\n//   the docs for examples:\n//   https://developers.google.com/protocol-buffers/docs/proto#options\n//   If this turns out to be popular, a web service will be set up\n//   to automatically assign option numbers.\n\nmessage FileOptions {\n\n  // Sets the Java package where classes generated from this .proto will be\n  // placed.  By default, the proto package is used, but this is often\n  // inappropriate because proto packages do not normally start with backwards\n  // domain names.\n  optional string java_package = 1;\n\n\n  // If set, all the classes from the .proto file are wrapped in a single\n  // outer class with the given name.  This applies to both Proto1\n  // (equivalent to the old \"--one_java_file\" option) and Proto2 (where\n  // a .proto always translates to a single class, but you may want to\n  // explicitly choose the class name).\n  optional string java_outer_classname = 8;\n\n  // If set true, then the Java code generator will generate a separate .java\n  // file for each top-level message, enum, and service defined in the .proto\n  // file.  Thus, these types will *not* be nested inside the outer class\n  // named by java_outer_classname.  However, the outer class will still be\n  // generated to contain the file's getDescriptor() method as well as any\n  // top-level extensions defined in the file.\n  optional bool java_multiple_files = 10 [default = false];\n\n  // This option does nothing.\n  optional bool java_generate_equals_and_hash = 20 [deprecated=true];\n\n  // If set true, then the Java2 code generator will generate code that\n  // throws an exception whenever an attempt is made to assign a non-UTF-8\n  // byte sequence to a string field.\n  // Message reflection will do the same.\n  // However, an extension field still accepts non-UTF-8 byte sequences.\n  // This option has no effect on when used with the lite runtime.\n  optional bool java_string_check_utf8 = 27 [default = false];\n\n\n  // Generated classes can be optimized for speed or code size.\n  enum OptimizeMode {\n    SPEED = 1;         // Generate complete code for parsing, serialization,\n                       // etc.\n    CODE_SIZE = 2;     // Use ReflectionOps to implement these methods.\n    LITE_RUNTIME = 3;  // Generate code using MessageLite and the lite runtime.\n  }\n  optional OptimizeMode optimize_for = 9 [default = SPEED];\n\n  // Sets the Go package where structs generated from this .proto will be\n  // placed. If omitted, the Go package will be derived from the following:\n  //   - The basename of the package import path, if provided.\n  //   - Otherwise, the package statement in the .proto file, if present.\n  //   - Otherwise, the basename of the .proto file, without extension.\n  optional string go_package = 11;\n\n\n\n\n  // Should generic services be generated in each language?  \"Generic\" services\n  // are not specific to any particular RPC system.  They are generated by the\n  // main code generators in each language (without additional plugins).\n  // Generic services were the only kind of service generation supported by\n  // early versions of google.protobuf.\n  //\n  // Generic services are now considered deprecated in favor of using plugins\n  // that generate code specific to your particular RPC system.  Therefore,\n  // these default to false.  Old code which depends on generic services should\n  // explicitly set them to true.\n  optional bool cc_generic_services = 16 [default = false];\n  optional bool java_generic_services = 17 [default = false];\n  optional bool py_generic_services = 18 [default = false];\n  optional bool php_generic_services = 42 [default = false];\n\n  // Is this file deprecated?\n  // Depending on the target platform, this can emit Deprecated annotations\n  // for everything in the file, or it will be completely ignored; in the very\n  // least, this is a formalization for deprecating files.\n  optional bool deprecated = 23 [default = false];\n\n  // Enables the use of arenas for the proto messages in this file. This applies\n  // only to generated classes for C++.\n  optional bool cc_enable_arenas = 31 [default = true];\n\n\n  // Sets the objective c class prefix which is prepended to all objective c\n  // generated classes from this .proto. There is no default.\n  optional string objc_class_prefix = 36;\n\n  // Namespace for generated classes; defaults to the package.\n  optional string csharp_namespace = 37;\n\n  // By default Swift generators will take the proto package and CamelCase it\n  // replacing '.' with underscore and use that to prefix the types/symbols\n  // defined. When this options is provided, they will use this value instead\n  // to prefix the types/symbols defined.\n  optional string swift_prefix = 39;\n\n  // Sets the php class prefix which is prepended to all php generated classes\n  // from this .proto. Default is empty.\n  optional string php_class_prefix = 40;\n\n  // Use this option to change the namespace of php generated classes. Default\n  // is empty. When this option is empty, the package name will be used for\n  // determining the namespace.\n  optional string php_namespace = 41;\n\n  // Use this option to change the namespace of php generated metadata classes.\n  // Default is empty. When this option is empty, the proto file name will be\n  // used for determining the namespace.\n  optional string php_metadata_namespace = 44;\n\n  // Use this option to change the package of ruby generated classes. Default\n  // is empty. When this option is not set, the package name will be used for\n  // determining the ruby package.\n  optional string ruby_package = 45;\n\n\n  // The parser stores options it doesn't recognize here.\n  // See the documentation for the \"Options\" section above.\n  repeated UninterpretedOption uninterpreted_option = 999;\n\n  // Clients can define custom options in extensions of this message.\n  // See the documentation for the \"Options\" section above.\n  extensions 1000 to max;\n\n  reserved 38;\n}\n\nmessage MessageOptions {\n  // Set true to use the old proto1 MessageSet wire format for extensions.\n  // This is provided for backwards-compatibility with the MessageSet wire\n  // format.  You should not use this for any other reason:  It's less\n  // efficient, has fewer features, and is more complicated.\n  //\n  // The message must be defined exactly as follows:\n  //   message Foo {\n  //     option message_set_wire_format = true;\n  //     extensions 4 to max;\n  //   }\n  // Note that the message cannot have any defined fields; MessageSets only\n  // have extensions.\n  //\n  // All extensions of your type must be singular messages; e.g. they cannot\n  // be int32s, enums, or repeated messages.\n  //\n  // Because this is an option, the above two restrictions are not enforced by\n  // the protocol compiler.\n  optional bool message_set_wire_format = 1 [default = false];\n\n  // Disables the generation of the standard \"descriptor()\" accessor, which can\n  // conflict with a field of the same name.  This is meant to make migration\n  // from proto1 easier; new code should avoid fields named \"descriptor\".\n  optional bool no_standard_descriptor_accessor = 2 [default = false];\n\n  // Is this message deprecated?\n  // Depending on the target platform, this can emit Deprecated annotations\n  // for the message, or it will be completely ignored; in the very least,\n  // this is a formalization for deprecating messages.\n  optional bool deprecated = 3 [default = false];\n\n  // Whether the message is an automatically generated map entry type for the\n  // maps field.\n  //\n  // For maps fields:\n  //     map<KeyType, ValueType> map_field = 1;\n  // The parsed descriptor looks like:\n  //     message MapFieldEntry {\n  //         option map_entry = true;\n  //         optional KeyType key = 1;\n  //         optional ValueType value = 2;\n  //     }\n  //     repeated MapFieldEntry map_field = 1;\n  //\n  // Implementations may choose not to generate the map_entry=true message, but\n  // use a native map in the target language to hold the keys and values.\n  // The reflection APIs in such implementations still need to work as\n  // if the field is a repeated message field.\n  //\n  // NOTE: Do not set the option in .proto files. Always use the maps syntax\n  // instead. The option should only be implicitly set by the proto compiler\n  // parser.\n  optional bool map_entry = 7;\n\n  reserved 8;  // javalite_serializable\n  reserved 9;  // javanano_as_lite\n\n\n  // The parser stores options it doesn't recognize here. See above.\n  repeated UninterpretedOption uninterpreted_option = 999;\n\n  // Clients can define custom options in extensions of this message. See above.\n  extensions 1000 to max;\n}\n\nmessage FieldOptions {\n  // The ctype option instructs the C++ code generator to use a different\n  // representation of the field than it normally would.  See the specific\n  // options below.  This option is not yet implemented in the open source\n  // release -- sorry, we'll try to include it in a future version!\n  optional CType ctype = 1 [default = STRING];\n  enum CType {\n    // Default mode.\n    STRING = 0;\n\n    CORD = 1;\n\n    STRING_PIECE = 2;\n  }\n  // The packed option can be enabled for repeated primitive fields to enable\n  // a more efficient representation on the wire. Rather than repeatedly\n  // writing the tag and type for each element, the entire array is encoded as\n  // a single length-delimited blob. In proto3, only explicit setting it to\n  // false will avoid using packed encoding.\n  optional bool packed = 2;\n\n  // The jstype option determines the JavaScript type used for values of the\n  // field.  The option is permitted only for 64 bit integral and fixed types\n  // (int64, uint64, sint64, fixed64, sfixed64).  A field with jstype JS_STRING\n  // is represented as JavaScript string, which avoids loss of precision that\n  // can happen when a large value is converted to a floating point JavaScript.\n  // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to\n  // use the JavaScript \"number\" type.  The behavior of the default option\n  // JS_NORMAL is implementation dependent.\n  //\n  // This option is an enum to permit additional types to be added, e.g.\n  // goog.math.Integer.\n  optional JSType jstype = 6 [default = JS_NORMAL];\n  enum JSType {\n    // Use the default type.\n    JS_NORMAL = 0;\n\n    // Use JavaScript strings.\n    JS_STRING = 1;\n\n    // Use JavaScript numbers.\n    JS_NUMBER = 2;\n  }\n\n  // Should this field be parsed lazily?  Lazy applies only to message-type\n  // fields.  It means that when the outer message is initially parsed, the\n  // inner message's contents will not be parsed but instead stored in encoded\n  // form.  The inner message will actually be parsed when it is first accessed.\n  //\n  // This is only a hint.  Implementations are free to choose whether to use\n  // eager or lazy parsing regardless of the value of this option.  However,\n  // setting this option true suggests that the protocol author believes that\n  // using lazy parsing on this field is worth the additional bookkeeping\n  // overhead typically needed to implement it.\n  //\n  // This option does not affect the public interface of any generated code;\n  // all method signatures remain the same.  Furthermore, thread-safety of the\n  // interface is not affected by this option; const methods remain safe to\n  // call from multiple threads concurrently, while non-const methods continue\n  // to require exclusive access.\n  //\n  //\n  // Note that implementations may choose not to check required fields within\n  // a lazy sub-message.  That is, calling IsInitialized() on the outer message\n  // may return true even if the inner message has missing required fields.\n  // This is necessary because otherwise the inner message would have to be\n  // parsed in order to perform the check, defeating the purpose of lazy\n  // parsing.  An implementation which chooses not to check required fields\n  // must be consistent about it.  That is, for any particular sub-message, the\n  // implementation must either *always* check its required fields, or *never*\n  // check its required fields, regardless of whether or not the message has\n  // been parsed.\n  optional bool lazy = 5 [default = false];\n\n  // Is this field deprecated?\n  // Depending on the target platform, this can emit Deprecated annotations\n  // for accessors, or it will be completely ignored; in the very least, this\n  // is a formalization for deprecating fields.\n  optional bool deprecated = 3 [default = false];\n\n  // For Google-internal migration only. Do not use.\n  optional bool weak = 10 [default = false];\n\n\n  // The parser stores options it doesn't recognize here. See above.\n  repeated UninterpretedOption uninterpreted_option = 999;\n\n  // Clients can define custom options in extensions of this message. See above.\n  extensions 1000 to max;\n\n  reserved 4;  // removed jtype\n}\n\nmessage OneofOptions {\n  // The parser stores options it doesn't recognize here. See above.\n  repeated UninterpretedOption uninterpreted_option = 999;\n\n  // Clients can define custom options in extensions of this message. See above.\n  extensions 1000 to max;\n}\n\nmessage EnumOptions {\n\n  // Set this option to true to allow mapping different tag names to the same\n  // value.\n  optional bool allow_alias = 2;\n\n  // Is this enum deprecated?\n  // Depending on the target platform, this can emit Deprecated annotations\n  // for the enum, or it will be completely ignored; in the very least, this\n  // is a formalization for deprecating enums.\n  optional bool deprecated = 3 [default = false];\n\n  reserved 5;  // javanano_as_lite\n\n  // The parser stores options it doesn't recognize here. See above.\n  repeated UninterpretedOption uninterpreted_option = 999;\n\n  // Clients can define custom options in extensions of this message. See above.\n  extensions 1000 to max;\n}\n\nmessage EnumValueOptions {\n  // Is this enum value deprecated?\n  // Depending on the target platform, this can emit Deprecated annotations\n  // for the enum value, or it will be completely ignored; in the very least,\n  // this is a formalization for deprecating enum values.\n  optional bool deprecated = 1 [default = false];\n\n  // The parser stores options it doesn't recognize here. See above.\n  repeated UninterpretedOption uninterpreted_option = 999;\n\n  // Clients can define custom options in extensions of this message. See above.\n  extensions 1000 to max;\n}\n\nmessage ServiceOptions {\n\n  // Note:  Field numbers 1 through 32 are reserved for Google's internal RPC\n  //   framework.  We apologize for hoarding these numbers to ourselves, but\n  //   we were already using them long before we decided to release Protocol\n  //   Buffers.\n\n  // Is this service deprecated?\n  // Depending on the target platform, this can emit Deprecated annotations\n  // for the service, or it will be completely ignored; in the very least,\n  // this is a formalization for deprecating services.\n  optional bool deprecated = 33 [default = false];\n\n  // The parser stores options it doesn't recognize here. See above.\n  repeated UninterpretedOption uninterpreted_option = 999;\n\n  // Clients can define custom options in extensions of this message. See above.\n  extensions 1000 to max;\n}\n\nmessage MethodOptions {\n\n  // Note:  Field numbers 1 through 32 are reserved for Google's internal RPC\n  //   framework.  We apologize for hoarding these numbers to ourselves, but\n  //   we were already using them long before we decided to release Protocol\n  //   Buffers.\n\n  // Is this method deprecated?\n  // Depending on the target platform, this can emit Deprecated annotations\n  // for the method, or it will be completely ignored; in the very least,\n  // this is a formalization for deprecating methods.\n  optional bool deprecated = 33 [default = false];\n\n  // Is this method side-effect-free (or safe in HTTP parlance), or idempotent,\n  // or neither? HTTP based RPC implementation may choose GET verb for safe\n  // methods, and PUT verb for idempotent methods instead of the default POST.\n  enum IdempotencyLevel {\n    IDEMPOTENCY_UNKNOWN = 0;\n    NO_SIDE_EFFECTS = 1;  // implies idempotent\n    IDEMPOTENT = 2;       // idempotent, but may have side effects\n  }\n  optional IdempotencyLevel idempotency_level = 34\n      [default = IDEMPOTENCY_UNKNOWN];\n\n  // The parser stores options it doesn't recognize here. See above.\n  repeated UninterpretedOption uninterpreted_option = 999;\n\n  // Clients can define custom options in extensions of this message. See above.\n  extensions 1000 to max;\n}\n\n\n// A message representing a option the parser does not recognize. This only\n// appears in options protos created by the compiler::Parser class.\n// DescriptorPool resolves these when building Descriptor objects. Therefore,\n// options protos in descriptor objects (e.g. returned by Descriptor::options(),\n// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions\n// in them.\nmessage UninterpretedOption {\n  // The name of the uninterpreted option.  Each string represents a segment in\n  // a dot-separated name.  is_extension is true iff a segment represents an\n  // extension (denoted with parentheses in options specs in .proto files).\n  // E.g.,{ [\"foo\", false], [\"bar.baz\", true], [\"qux\", false] } represents\n  // \"foo.(bar.baz).qux\".\n  message NamePart {\n    required string name_part = 1;\n    required bool is_extension = 2;\n  }\n  repeated NamePart name = 2;\n\n  // The value of the uninterpreted option, in whatever type the tokenizer\n  // identified it as during parsing. Exactly one of these should be set.\n  optional string identifier_value = 3;\n  optional uint64 positive_int_value = 4;\n  optional int64 negative_int_value = 5;\n  optional double double_value = 6;\n  optional bytes string_value = 7;\n  optional string aggregate_value = 8;\n}\n\n// ===================================================================\n// Optional source code info\n\n// Encapsulates information about the original source file from which a\n// FileDescriptorProto was generated.\nmessage SourceCodeInfo {\n  // A Location identifies a piece of source code in a .proto file which\n  // corresponds to a particular definition.  This information is intended\n  // to be useful to IDEs, code indexers, documentation generators, and similar\n  // tools.\n  //\n  // For example, say we have a file like:\n  //   message Foo {\n  //     optional string foo = 1;\n  //   }\n  // Let's look at just the field definition:\n  //   optional string foo = 1;\n  //   ^       ^^     ^^  ^  ^^^\n  //   a       bc     de  f  ghi\n  // We have the following locations:\n  //   span   path               represents\n  //   [a,i)  [ 4, 0, 2, 0 ]     The whole field definition.\n  //   [a,b)  [ 4, 0, 2, 0, 4 ]  The label (optional).\n  //   [c,d)  [ 4, 0, 2, 0, 5 ]  The type (string).\n  //   [e,f)  [ 4, 0, 2, 0, 1 ]  The name (foo).\n  //   [g,h)  [ 4, 0, 2, 0, 3 ]  The number (1).\n  //\n  // Notes:\n  // - A location may refer to a repeated field itself (i.e. not to any\n  //   particular index within it).  This is used whenever a set of elements are\n  //   logically enclosed in a single code segment.  For example, an entire\n  //   extend block (possibly containing multiple extension definitions) will\n  //   have an outer location whose path refers to the \"extensions\" repeated\n  //   field without an index.\n  // - Multiple locations may have the same path.  This happens when a single\n  //   logical declaration is spread out across multiple places.  The most\n  //   obvious example is the \"extend\" block again -- there may be multiple\n  //   extend blocks in the same scope, each of which will have the same path.\n  // - A location's span is not always a subset of its parent's span.  For\n  //   example, the \"extendee\" of an extension declaration appears at the\n  //   beginning of the \"extend\" block and is shared by all extensions within\n  //   the block.\n  // - Just because a location's span is a subset of some other location's span\n  //   does not mean that it is a descendant.  For example, a \"group\" defines\n  //   both a type and a field in a single declaration.  Thus, the locations\n  //   corresponding to the type and field and their components will overlap.\n  // - Code which tries to interpret locations should probably be designed to\n  //   ignore those that it doesn't understand, as more types of locations could\n  //   be recorded in the future.\n  repeated Location location = 1;\n  message Location {\n    // Identifies which part of the FileDescriptorProto was defined at this\n    // location.\n    //\n    // Each element is a field number or an index.  They form a path from\n    // the root FileDescriptorProto to the place where the definition.  For\n    // example, this path:\n    //   [ 4, 3, 2, 7, 1 ]\n    // refers to:\n    //   file.message_type(3)  // 4, 3\n    //       .field(7)         // 2, 7\n    //       .name()           // 1\n    // This is because FileDescriptorProto.message_type has field number 4:\n    //   repeated DescriptorProto message_type = 4;\n    // and DescriptorProto.field has field number 2:\n    //   repeated FieldDescriptorProto field = 2;\n    // and FieldDescriptorProto.name has field number 1:\n    //   optional string name = 1;\n    //\n    // Thus, the above path gives the location of a field name.  If we removed\n    // the last element:\n    //   [ 4, 3, 2, 7 ]\n    // this path refers to the whole field declaration (from the beginning\n    // of the label to the terminating semicolon).\n    repeated int32 path = 1 [packed = true];\n\n    // Always has exactly three or four elements: start line, start column,\n    // end line (optional, otherwise assumed same as start line), end column.\n    // These are packed into a single field for efficiency.  Note that line\n    // and column numbers are zero-based -- typically you will want to add\n    // 1 to each before displaying to a user.\n    repeated int32 span = 2 [packed = true];\n\n    // If this SourceCodeInfo represents a complete declaration, these are any\n    // comments appearing before and after the declaration which appear to be\n    // attached to the declaration.\n    //\n    // A series of line comments appearing on consecutive lines, with no other\n    // tokens appearing on those lines, will be treated as a single comment.\n    //\n    // leading_detached_comments will keep paragraphs of comments that appear\n    // before (but not connected to) the current element. Each paragraph,\n    // separated by empty lines, will be one comment element in the repeated\n    // field.\n    //\n    // Only the comment content is provided; comment markers (e.g. //) are\n    // stripped out.  For block comments, leading whitespace and an asterisk\n    // will be stripped from the beginning of each line other than the first.\n    // Newlines are included in the output.\n    //\n    // Examples:\n    //\n    //   optional int32 foo = 1;  // Comment attached to foo.\n    //   // Comment attached to bar.\n    //   optional int32 bar = 2;\n    //\n    //   optional string baz = 3;\n    //   // Comment attached to baz.\n    //   // Another line attached to baz.\n    //\n    //   // Comment attached to qux.\n    //   //\n    //   // Another line attached to qux.\n    //   optional double qux = 4;\n    //\n    //   // Detached comment for corge. This is not leading or trailing comments\n    //   // to qux or corge because there are blank lines separating it from\n    //   // both.\n    //\n    //   // Detached comment for corge paragraph 2.\n    //\n    //   optional string corge = 5;\n    //   /* Block comment attached\n    //    * to corge.  Leading asterisks\n    //    * will be removed. */\n    //   /* Block comment attached to\n    //    * grault. */\n    //   optional int32 grault = 6;\n    //\n    //   // ignored detached comments.\n    optional string leading_comments = 3;\n    optional string trailing_comments = 4;\n    repeated string leading_detached_comments = 6;\n  }\n}\n\n// Describes the relationship between generated code and its original source\n// file. A GeneratedCodeInfo message is associated with only one generated\n// source file, but may contain references to different source .proto files.\nmessage GeneratedCodeInfo {\n  // An Annotation connects some span of text in generated code to an element\n  // of its generating .proto file.\n  repeated Annotation annotation = 1;\n  message Annotation {\n    // Identifies the element in the original source .proto file. This field\n    // is formatted the same as SourceCodeInfo.Location.path.\n    repeated int32 path = 1 [packed = true];\n\n    // Identifies the filesystem path to the original source .proto.\n    optional string source_file = 2;\n\n    // Identifies the starting offset in bytes in the generated code\n    // that relates to the identified object.\n    optional int32 begin = 3;\n\n    // Identifies the ending offset in bytes in the generated code that\n    // relates to the identified offset. The end offset should be one past\n    // the last relevant byte (so the length of the text = end - begin).\n    optional int32 end = 4;\n  }\n}\n"
  },
  {
    "path": "lamda/google/protobuf/duration.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption cc_enable_arenas = true;\noption go_package = \"google.golang.org/protobuf/types/known/durationpb\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"DurationProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\n\n// A Duration represents a signed, fixed-length span of time represented\n// as a count of seconds and fractions of seconds at nanosecond\n// resolution. It is independent of any calendar and concepts like \"day\"\n// or \"month\". It is related to Timestamp in that the difference between\n// two Timestamp values is a Duration and it can be added or subtracted\n// from a Timestamp. Range is approximately +-10,000 years.\n//\n// # Examples\n//\n// Example 1: Compute Duration from two Timestamps in pseudo code.\n//\n//     Timestamp start = ...;\n//     Timestamp end = ...;\n//     Duration duration = ...;\n//\n//     duration.seconds = end.seconds - start.seconds;\n//     duration.nanos = end.nanos - start.nanos;\n//\n//     if (duration.seconds < 0 && duration.nanos > 0) {\n//       duration.seconds += 1;\n//       duration.nanos -= 1000000000;\n//     } else if (duration.seconds > 0 && duration.nanos < 0) {\n//       duration.seconds -= 1;\n//       duration.nanos += 1000000000;\n//     }\n//\n// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.\n//\n//     Timestamp start = ...;\n//     Duration duration = ...;\n//     Timestamp end = ...;\n//\n//     end.seconds = start.seconds + duration.seconds;\n//     end.nanos = start.nanos + duration.nanos;\n//\n//     if (end.nanos < 0) {\n//       end.seconds -= 1;\n//       end.nanos += 1000000000;\n//     } else if (end.nanos >= 1000000000) {\n//       end.seconds += 1;\n//       end.nanos -= 1000000000;\n//     }\n//\n// Example 3: Compute Duration from datetime.timedelta in Python.\n//\n//     td = datetime.timedelta(days=3, minutes=10)\n//     duration = Duration()\n//     duration.FromTimedelta(td)\n//\n// # JSON Mapping\n//\n// In JSON format, the Duration type is encoded as a string rather than an\n// object, where the string ends in the suffix \"s\" (indicating seconds) and\n// is preceded by the number of seconds, with nanoseconds expressed as\n// fractional seconds. For example, 3 seconds with 0 nanoseconds should be\n// encoded in JSON format as \"3s\", while 3 seconds and 1 nanosecond should\n// be expressed in JSON format as \"3.000000001s\", and 3 seconds and 1\n// microsecond should be expressed in JSON format as \"3.000001s\".\n//\n//\nmessage Duration {\n  // Signed seconds of the span of time. Must be from -315,576,000,000\n  // to +315,576,000,000 inclusive. Note: these bounds are computed from:\n  // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years\n  int64 seconds = 1;\n\n  // Signed fractions of a second at nanosecond resolution of the span\n  // of time. Durations less than one second are represented with a 0\n  // `seconds` field and a positive or negative `nanos` field. For durations\n  // of one second or more, a non-zero value for the `nanos` field must be\n  // of the same sign as the `seconds` field. Must be from -999,999,999\n  // to +999,999,999 inclusive.\n  int32 nanos = 2;\n}\n"
  },
  {
    "path": "lamda/google/protobuf/empty.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption go_package = \"google.golang.org/protobuf/types/known/emptypb\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"EmptyProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\noption cc_enable_arenas = true;\n\n// A generic empty message that you can re-use to avoid defining duplicated\n// empty messages in your APIs. A typical example is to use it as the request\n// or the response type of an API method. For instance:\n//\n//     service Foo {\n//       rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);\n//     }\n//\n// The JSON representation for `Empty` is empty JSON object `{}`.\nmessage Empty {}\n"
  },
  {
    "path": "lamda/google/protobuf/field_mask.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"FieldMaskProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\noption go_package = \"google.golang.org/protobuf/types/known/fieldmaskpb\";\noption cc_enable_arenas = true;\n\n// `FieldMask` represents a set of symbolic field paths, for example:\n//\n//     paths: \"f.a\"\n//     paths: \"f.b.d\"\n//\n// Here `f` represents a field in some root message, `a` and `b`\n// fields in the message found in `f`, and `d` a field found in the\n// message in `f.b`.\n//\n// Field masks are used to specify a subset of fields that should be\n// returned by a get operation or modified by an update operation.\n// Field masks also have a custom JSON encoding (see below).\n//\n// # Field Masks in Projections\n//\n// When used in the context of a projection, a response message or\n// sub-message is filtered by the API to only contain those fields as\n// specified in the mask. For example, if the mask in the previous\n// example is applied to a response message as follows:\n//\n//     f {\n//       a : 22\n//       b {\n//         d : 1\n//         x : 2\n//       }\n//       y : 13\n//     }\n//     z: 8\n//\n// The result will not contain specific values for fields x,y and z\n// (their value will be set to the default, and omitted in proto text\n// output):\n//\n//\n//     f {\n//       a : 22\n//       b {\n//         d : 1\n//       }\n//     }\n//\n// A repeated field is not allowed except at the last position of a\n// paths string.\n//\n// If a FieldMask object is not present in a get operation, the\n// operation applies to all fields (as if a FieldMask of all fields\n// had been specified).\n//\n// Note that a field mask does not necessarily apply to the\n// top-level response message. In case of a REST get operation, the\n// field mask applies directly to the response, but in case of a REST\n// list operation, the mask instead applies to each individual message\n// in the returned resource list. In case of a REST custom method,\n// other definitions may be used. Where the mask applies will be\n// clearly documented together with its declaration in the API.  In\n// any case, the effect on the returned resource/resources is required\n// behavior for APIs.\n//\n// # Field Masks in Update Operations\n//\n// A field mask in update operations specifies which fields of the\n// targeted resource are going to be updated. The API is required\n// to only change the values of the fields as specified in the mask\n// and leave the others untouched. If a resource is passed in to\n// describe the updated values, the API ignores the values of all\n// fields not covered by the mask.\n//\n// If a repeated field is specified for an update operation, new values will\n// be appended to the existing repeated field in the target resource. Note that\n// a repeated field is only allowed in the last position of a `paths` string.\n//\n// If a sub-message is specified in the last position of the field mask for an\n// update operation, then new value will be merged into the existing sub-message\n// in the target resource.\n//\n// For example, given the target message:\n//\n//     f {\n//       b {\n//         d: 1\n//         x: 2\n//       }\n//       c: [1]\n//     }\n//\n// And an update message:\n//\n//     f {\n//       b {\n//         d: 10\n//       }\n//       c: [2]\n//     }\n//\n// then if the field mask is:\n//\n//  paths: [\"f.b\", \"f.c\"]\n//\n// then the result will be:\n//\n//     f {\n//       b {\n//         d: 10\n//         x: 2\n//       }\n//       c: [1, 2]\n//     }\n//\n// An implementation may provide options to override this default behavior for\n// repeated and message fields.\n//\n// In order to reset a field's value to the default, the field must\n// be in the mask and set to the default value in the provided resource.\n// Hence, in order to reset all fields of a resource, provide a default\n// instance of the resource and set all fields in the mask, or do\n// not provide a mask as described below.\n//\n// If a field mask is not present on update, the operation applies to\n// all fields (as if a field mask of all fields has been specified).\n// Note that in the presence of schema evolution, this may mean that\n// fields the client does not know and has therefore not filled into\n// the request will be reset to their default. If this is unwanted\n// behavior, a specific service may require a client to always specify\n// a field mask, producing an error if not.\n//\n// As with get operations, the location of the resource which\n// describes the updated values in the request message depends on the\n// operation kind. In any case, the effect of the field mask is\n// required to be honored by the API.\n//\n// ## Considerations for HTTP REST\n//\n// The HTTP kind of an update operation which uses a field mask must\n// be set to PATCH instead of PUT in order to satisfy HTTP semantics\n// (PUT must only be used for full updates).\n//\n// # JSON Encoding of Field Masks\n//\n// In JSON, a field mask is encoded as a single string where paths are\n// separated by a comma. Fields name in each path are converted\n// to/from lower-camel naming conventions.\n//\n// As an example, consider the following message declarations:\n//\n//     message Profile {\n//       User user = 1;\n//       Photo photo = 2;\n//     }\n//     message User {\n//       string display_name = 1;\n//       string address = 2;\n//     }\n//\n// In proto a field mask for `Profile` may look as such:\n//\n//     mask {\n//       paths: \"user.display_name\"\n//       paths: \"photo\"\n//     }\n//\n// In JSON, the same mask is represented as below:\n//\n//     {\n//       mask: \"user.displayName,photo\"\n//     }\n//\n// # Field Masks and Oneof Fields\n//\n// Field masks treat fields in oneofs just as regular fields. Consider the\n// following message:\n//\n//     message SampleMessage {\n//       oneof test_oneof {\n//         string name = 4;\n//         SubMessage sub_message = 9;\n//       }\n//     }\n//\n// The field mask can be:\n//\n//     mask {\n//       paths: \"name\"\n//     }\n//\n// Or:\n//\n//     mask {\n//       paths: \"sub_message\"\n//     }\n//\n// Note that oneof type names (\"test_oneof\" in this case) cannot be used in\n// paths.\n//\n// ## Field Mask Verification\n//\n// The implementation of any API method which has a FieldMask type field in the\n// request should verify the included field paths, and return an\n// `INVALID_ARGUMENT` error if any path is unmappable.\nmessage FieldMask {\n  // The set of field mask paths.\n  repeated string paths = 1;\n}\n"
  },
  {
    "path": "lamda/google/protobuf/source_context.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"SourceContextProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\noption go_package = \"google.golang.org/protobuf/types/known/sourcecontextpb\";\n\n// `SourceContext` represents information about the source of a\n// protobuf element, like the file in which it is defined.\nmessage SourceContext {\n  // The path-qualified name of the .proto file that contained the associated\n  // protobuf element.  For example: `\"google/protobuf/source_context.proto\"`.\n  string file_name = 1;\n}\n"
  },
  {
    "path": "lamda/google/protobuf/struct.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption cc_enable_arenas = true;\noption go_package = \"google.golang.org/protobuf/types/known/structpb\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"StructProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\n\n// `Struct` represents a structured data value, consisting of fields\n// which map to dynamically typed values. In some languages, `Struct`\n// might be supported by a native representation. For example, in\n// scripting languages like JS a struct is represented as an\n// object. The details of that representation are described together\n// with the proto support for the language.\n//\n// The JSON representation for `Struct` is JSON object.\nmessage Struct {\n  // Unordered map of dynamically typed values.\n  map<string, Value> fields = 1;\n}\n\n// `Value` represents a dynamically typed value which can be either\n// null, a number, a string, a boolean, a recursive struct value, or a\n// list of values. A producer of value is expected to set one of that\n// variants, absence of any variant indicates an error.\n//\n// The JSON representation for `Value` is JSON value.\nmessage Value {\n  // The kind of value.\n  oneof kind {\n    // Represents a null value.\n    NullValue null_value = 1;\n    // Represents a double value.\n    double number_value = 2;\n    // Represents a string value.\n    string string_value = 3;\n    // Represents a boolean value.\n    bool bool_value = 4;\n    // Represents a structured value.\n    Struct struct_value = 5;\n    // Represents a repeated `Value`.\n    ListValue list_value = 6;\n  }\n}\n\n// `NullValue` is a singleton enumeration to represent the null value for the\n// `Value` type union.\n//\n//  The JSON representation for `NullValue` is JSON `null`.\nenum NullValue {\n  // Null value.\n  NULL_VALUE = 0;\n}\n\n// `ListValue` is a wrapper around a repeated field of values.\n//\n// The JSON representation for `ListValue` is JSON array.\nmessage ListValue {\n  // Repeated field of dynamically typed values.\n  repeated Value values = 1;\n}\n"
  },
  {
    "path": "lamda/google/protobuf/timestamp.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption cc_enable_arenas = true;\noption go_package = \"google.golang.org/protobuf/types/known/timestamppb\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"TimestampProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\n\n// A Timestamp represents a point in time independent of any time zone or local\n// calendar, encoded as a count of seconds and fractions of seconds at\n// nanosecond resolution. The count is relative to an epoch at UTC midnight on\n// January 1, 1970, in the proleptic Gregorian calendar which extends the\n// Gregorian calendar backwards to year one.\n//\n// All minutes are 60 seconds long. Leap seconds are \"smeared\" so that no leap\n// second table is needed for interpretation, using a [24-hour linear\n// smear](https://developers.google.com/time/smear).\n//\n// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By\n// restricting to that range, we ensure that we can convert to and from [RFC\n// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.\n//\n// # Examples\n//\n// Example 1: Compute Timestamp from POSIX `time()`.\n//\n//     Timestamp timestamp;\n//     timestamp.set_seconds(time(NULL));\n//     timestamp.set_nanos(0);\n//\n// Example 2: Compute Timestamp from POSIX `gettimeofday()`.\n//\n//     struct timeval tv;\n//     gettimeofday(&tv, NULL);\n//\n//     Timestamp timestamp;\n//     timestamp.set_seconds(tv.tv_sec);\n//     timestamp.set_nanos(tv.tv_usec * 1000);\n//\n// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.\n//\n//     FILETIME ft;\n//     GetSystemTimeAsFileTime(&ft);\n//     UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;\n//\n//     // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z\n//     // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.\n//     Timestamp timestamp;\n//     timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));\n//     timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));\n//\n// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.\n//\n//     long millis = System.currentTimeMillis();\n//\n//     Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)\n//         .setNanos((int) ((millis % 1000) * 1000000)).build();\n//\n//\n// Example 5: Compute Timestamp from Java `Instant.now()`.\n//\n//     Instant now = Instant.now();\n//\n//     Timestamp timestamp =\n//         Timestamp.newBuilder().setSeconds(now.getEpochSecond())\n//             .setNanos(now.getNano()).build();\n//\n//\n// Example 6: Compute Timestamp from current time in Python.\n//\n//     timestamp = Timestamp()\n//     timestamp.GetCurrentTime()\n//\n// # JSON Mapping\n//\n// In JSON format, the Timestamp type is encoded as a string in the\n// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the\n// format is \"{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z\"\n// where {year} is always expressed using four digits while {month}, {day},\n// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional\n// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),\n// are optional. The \"Z\" suffix indicates the timezone (\"UTC\"); the timezone\n// is required. A proto3 JSON serializer should always use UTC (as indicated by\n// \"Z\") when printing the Timestamp type and a proto3 JSON parser should be\n// able to accept both UTC and other timezones (as indicated by an offset).\n//\n// For example, \"2017-01-15T01:30:15.01Z\" encodes 15.01 seconds past\n// 01:30 UTC on January 15, 2017.\n//\n// In JavaScript, one can convert a Date object to this format using the\n// standard\n// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)\n// method. In Python, a standard `datetime.datetime` object can be converted\n// to this format using\n// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with\n// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use\n// the Joda Time's [`ISODateTimeFormat.dateTime()`](\n// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D\n// ) to obtain a formatter capable of generating timestamps in this format.\n//\n//\nmessage Timestamp {\n  // Represents seconds of UTC time since Unix epoch\n  // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n  // 9999-12-31T23:59:59Z inclusive.\n  int64 seconds = 1;\n\n  // Non-negative fractions of a second at nanosecond resolution. Negative\n  // second values with fractions must still have non-negative nanos values\n  // that count forward in time. Must be from 0 to 999,999,999\n  // inclusive.\n  int32 nanos = 2;\n}\n"
  },
  {
    "path": "lamda/google/protobuf/type.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\nimport \"google/protobuf/any.proto\";\nimport \"google/protobuf/source_context.proto\";\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption cc_enable_arenas = true;\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"TypeProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\noption go_package = \"google.golang.org/protobuf/types/known/typepb\";\n\n// A protocol buffer message type.\nmessage Type {\n  // The fully qualified message name.\n  string name = 1;\n  // The list of fields.\n  repeated Field fields = 2;\n  // The list of types appearing in `oneof` definitions in this type.\n  repeated string oneofs = 3;\n  // The protocol buffer options.\n  repeated Option options = 4;\n  // The source context.\n  SourceContext source_context = 5;\n  // The source syntax.\n  Syntax syntax = 6;\n}\n\n// A single field of a message type.\nmessage Field {\n  // Basic field types.\n  enum Kind {\n    // Field type unknown.\n    TYPE_UNKNOWN = 0;\n    // Field type double.\n    TYPE_DOUBLE = 1;\n    // Field type float.\n    TYPE_FLOAT = 2;\n    // Field type int64.\n    TYPE_INT64 = 3;\n    // Field type uint64.\n    TYPE_UINT64 = 4;\n    // Field type int32.\n    TYPE_INT32 = 5;\n    // Field type fixed64.\n    TYPE_FIXED64 = 6;\n    // Field type fixed32.\n    TYPE_FIXED32 = 7;\n    // Field type bool.\n    TYPE_BOOL = 8;\n    // Field type string.\n    TYPE_STRING = 9;\n    // Field type group. Proto2 syntax only, and deprecated.\n    TYPE_GROUP = 10;\n    // Field type message.\n    TYPE_MESSAGE = 11;\n    // Field type bytes.\n    TYPE_BYTES = 12;\n    // Field type uint32.\n    TYPE_UINT32 = 13;\n    // Field type enum.\n    TYPE_ENUM = 14;\n    // Field type sfixed32.\n    TYPE_SFIXED32 = 15;\n    // Field type sfixed64.\n    TYPE_SFIXED64 = 16;\n    // Field type sint32.\n    TYPE_SINT32 = 17;\n    // Field type sint64.\n    TYPE_SINT64 = 18;\n  }\n\n  // Whether a field is optional, required, or repeated.\n  enum Cardinality {\n    // For fields with unknown cardinality.\n    CARDINALITY_UNKNOWN = 0;\n    // For optional fields.\n    CARDINALITY_OPTIONAL = 1;\n    // For required fields. Proto2 syntax only.\n    CARDINALITY_REQUIRED = 2;\n    // For repeated fields.\n    CARDINALITY_REPEATED = 3;\n  }\n\n  // The field type.\n  Kind kind = 1;\n  // The field cardinality.\n  Cardinality cardinality = 2;\n  // The field number.\n  int32 number = 3;\n  // The field name.\n  string name = 4;\n  // The field type URL, without the scheme, for message or enumeration\n  // types. Example: `\"type.googleapis.com/google.protobuf.Timestamp\"`.\n  string type_url = 6;\n  // The index of the field type in `Type.oneofs`, for message or enumeration\n  // types. The first type has index 1; zero means the type is not in the list.\n  int32 oneof_index = 7;\n  // Whether to use alternative packed wire representation.\n  bool packed = 8;\n  // The protocol buffer options.\n  repeated Option options = 9;\n  // The field JSON name.\n  string json_name = 10;\n  // The string value of the default value of this field. Proto2 syntax only.\n  string default_value = 11;\n}\n\n// Enum type definition.\nmessage Enum {\n  // Enum type name.\n  string name = 1;\n  // Enum value definitions.\n  repeated EnumValue enumvalue = 2;\n  // Protocol buffer options.\n  repeated Option options = 3;\n  // The source context.\n  SourceContext source_context = 4;\n  // The source syntax.\n  Syntax syntax = 5;\n}\n\n// Enum value definition.\nmessage EnumValue {\n  // Enum value name.\n  string name = 1;\n  // Enum value number.\n  int32 number = 2;\n  // Protocol buffer options.\n  repeated Option options = 3;\n}\n\n// A protocol buffer option, which can be attached to a message, field,\n// enumeration, etc.\nmessage Option {\n  // The option's name. For protobuf built-in options (options defined in\n  // descriptor.proto), this is the short name. For example, `\"map_entry\"`.\n  // For custom options, it should be the fully-qualified name. For example,\n  // `\"google.api.http\"`.\n  string name = 1;\n  // The option's value packed in an Any message. If the value is a primitive,\n  // the corresponding wrapper type defined in google/protobuf/wrappers.proto\n  // should be used. If the value is an enum, it should be stored as an int32\n  // value using the google.protobuf.Int32Value type.\n  Any value = 2;\n}\n\n// The syntax in which a protocol buffer element is defined.\nenum Syntax {\n  // Syntax `proto2`.\n  SYNTAX_PROTO2 = 0;\n  // Syntax `proto3`.\n  SYNTAX_PROTO3 = 1;\n}\n"
  },
  {
    "path": "lamda/google/protobuf/wrappers.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n// Wrappers for primitive (non-message) types. These types are useful\n// for embedding primitives in the `google.protobuf.Any` type and for places\n// where we need to distinguish between the absence of a primitive\n// typed field and its default value.\n//\n// These wrappers have no meaningful use within repeated fields as they lack\n// the ability to detect presence on individual elements.\n// These wrappers have no meaningful use within a map or a oneof since\n// individual entries of a map or fields of a oneof can already detect presence.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption cc_enable_arenas = true;\noption go_package = \"google.golang.org/protobuf/types/known/wrapperspb\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"WrappersProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\n\n// Wrapper message for `double`.\n//\n// The JSON representation for `DoubleValue` is JSON number.\nmessage DoubleValue {\n  // The double value.\n  double value = 1;\n}\n\n// Wrapper message for `float`.\n//\n// The JSON representation for `FloatValue` is JSON number.\nmessage FloatValue {\n  // The float value.\n  float value = 1;\n}\n\n// Wrapper message for `int64`.\n//\n// The JSON representation for `Int64Value` is JSON string.\nmessage Int64Value {\n  // The int64 value.\n  int64 value = 1;\n}\n\n// Wrapper message for `uint64`.\n//\n// The JSON representation for `UInt64Value` is JSON string.\nmessage UInt64Value {\n  // The uint64 value.\n  uint64 value = 1;\n}\n\n// Wrapper message for `int32`.\n//\n// The JSON representation for `Int32Value` is JSON number.\nmessage Int32Value {\n  // The int32 value.\n  int32 value = 1;\n}\n\n// Wrapper message for `uint32`.\n//\n// The JSON representation for `UInt32Value` is JSON number.\nmessage UInt32Value {\n  // The uint32 value.\n  uint32 value = 1;\n}\n\n// Wrapper message for `bool`.\n//\n// The JSON representation for `BoolValue` is JSON `true` and `false`.\nmessage BoolValue {\n  // The bool value.\n  bool value = 1;\n}\n\n// Wrapper message for `string`.\n//\n// The JSON representation for `StringValue` is JSON string.\nmessage StringValue {\n  // The string value.\n  string value = 1;\n}\n\n// Wrapper message for `bytes`.\n//\n// The JSON representation for `BytesValue` is JSON string.\nmessage BytesValue {\n  // The bytes value.\n  bytes value = 1;\n}\n"
  },
  {
    "path": "lamda/rpc/application.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nimport \"google/protobuf/struct.proto\";\n\nenum GrantType {\n                GRANT_ALLOW       = 0;\n                GRANT_DENY        = 1;\n                GRANT_IGNORE      = 2;\n}\n\nenum DataEncode {\n        DATA_ENCODE_NONE  = 0;\n        DATA_ENCODE_ZLIB  = 1;\n}\n\nenum ScriptRuntime {\n        RUNTIME_QJS        = 0;\n        RUNTIME_V8         = 1;\n}\n\nmessage ApplicationRequest {\n        string name             = 1;\n        string permission       = 2;\n        GrantType mode          = 3;\n        string path             = 4;\n        uint32 user             = 5;\n}\n\nmessage ApplicationActivityRequest {\n        string package          = 1;\n        string action           = 2;\n        string category         = 3;\n        string component        = 4;\n        google.protobuf.Struct extras = 5;\n        repeated string categories = 6;\n        int64  flags            = 7;\n        bool   debug            = 8;\n        string data             = 9;\n        uint32 user             = 10;\n}\n\nmessage ApplicationActivityInfo {\n        string package          = 1;\n        string action           = 2;\n        string category         = 3;\n        string component        = 4;\n        google.protobuf.Struct extras = 5;\n        repeated string categories = 6;\n        int64  flags            = 7;\n        bool   debug            = 8;\n        string data             = 9;\n        uint32 user             = 10;\n}\n\nmessage ApplicationActivityInfoList {\n        repeated ApplicationActivityInfo activities = 1;\n}\n\nmessage ApplicationPermissions {\n        repeated string permissions = 1;\n}\n\nmessage ApplicationInfo {\n        string packageName      = 1;\n        uint32 uid              = 2;\n        bool   enabled          = 3;\n        string processName      = 4;\n        string sourceDir        = 5;\n        string dataDir          = 6;\n        uint32 baseRevisionCode = 7;\n        int64  firstInstallTime = 8;\n        int64  lastUpdateTime   = 9;\n        uint32 versionCode      = 10;\n        string versionName      = 11;\n        string activity         = 12;\n        uint32 user             = 13;\n}\n\nmessage ApplicationProcess {\n        repeated string packages = 1;\n        string processName = 2;\n        int64  uid         = 3;\n        int64  pid         = 4;\n}\n\nmessage ApplicationProcesses {\n        repeated ApplicationProcess processes   = 1;\n}\n\nmessage ApplicationPkgNames {\n        repeated string names   = 1;\n}\n\nmessage HookRequest {\n        string package          = 1;\n        bytes script            = 2;\n        ScriptRuntime runtime   = 3;\n        string destination      = 4;\n        DataEncode encode       = 5;\n        uint32 standup          = 6;\n        bool spawn              = 7;\n        uint32 user             = 8;\n}\n\nmessage HookRpcRequest {\n        string package          = 1;\n        string callinfo         = 2;\n        uint32 user             = 3;\n}\n\nmessage HookRpcResponse {\n        string package          = 1;\n        string callresult       = 2;\n}"
  },
  {
    "path": "lamda/rpc/debug.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nimport \"google/protobuf/struct.proto\";\n\nmessage ADBDConfigRequest {\n        string adb_pubkey = 1;\n}"
  },
  {
    "path": "lamda/rpc/file.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nmessage FileStat {\n        string name     = 1;\n        string path     = 2;\n        bool  directory = 3;\n        int64 st_mode   = 4;\n        int64 st_atime  = 5;\n        int64 st_mtime  = 6;\n        int64 st_ctime  = 7;\n        int32 st_uid    = 8;\n        int32 st_gid    = 9;\n        int64 st_size   = 10;\n}\n\nmessage FileRequest {\n        string path     = 1;\n        uint64  mode    = 2;\n        bytes payload   = 3;\n}\n\nmessage FileDataResponse {\n        bytes payload   = 1;\n}\n"
  },
  {
    "path": "lamda/rpc/policy.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nmessage SelinuxPolicyRequest {\n        string source       = 1;\n        string target       = 2;\n        string tclass       = 3;\n        string action       = 4;\n}"
  },
  {
    "path": "lamda/rpc/proxy.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nimport \"application.proto\";\n\n// Supported Shadowsocks Ciphers (no ref)\nenum ShadowCipher {\n                SS_AES_128_CFB = 0;\n                SS_AES_192_CFB = 1;\n                SS_AES_256_CFB = 2;\n                SS_AES_128_CTR = 3;\n                SS_AES_192_CTR = 4;\n                SS_AES_256_CTR = 5;\n                SS_CAMELLIA_128_CFB = 6;\n                SS_CAMELLIA_192_CFB = 7;\n                SS_CAMELLIA_256_CFB = 8;\n                SS_DES_CFB = 9;\n                SS_AES_128_GCM = 20;\n                SS_AES_192_GCM = 21;\n                SS_AES_256_GCM = 22;\n                SS_CHACHA20_IETF_POLY1305 = 23;\n}\n\nenum GproxyType {\n                SOCKS5          = 0;\n                HTTP_CONNECT    = 1;\n                HTTP_RELAY      = 2;\n                HTTPS_CONNECT   = 3;\n                SHADOWSOCKS     = 4;\n}\n\nmessage GproxyConfigRequest {\n        ApplicationInfo application = 1;\n        GproxyType type             = 2;\n        string nameserver           = 3;\n        string login                = 4;\n        string password             = 5;\n        string host                 = 6;\n        uint32 port                 = 7;\n        bool bypass_local_subnet    = 8;\n        bool drop_udp               = 9;\n        bool udp_proxy              = 10;\n        bool dns_proxy              = 11;\n}\n\nenum OpenVPNProto {\n                TCP             = 0;\n                UDP             = 1;\n}\n\nenum OpenVPNAuth {\n                SHA1            = 0;\n                SHA224          = 1;\n                SHA256          = 2;\n                SHA384          = 3;\n                SHA512          = 4;\n                RIPEMD160       = 5;\n                RSA_SHA1        = 6;\n                RSA_SHA224      = 7;\n                RSA_SHA256      = 8;\n                RSA_SHA384      = 9;\n                RSA_SHA512      = 10;\n                RSA_RIPEMD160   = 11;\n}\n\nenum OpenVPNCipher {\n                AES_128_GCM      = 0;\n                AES_256_GCM      = 1;\n                CHACHA20_POLY1305= 2;\n                AES_128_CBC      = 3;\n                AES_256_CBC      = 4;\n}\n\nenum OpenVPNKeyDirection {\n// because 0 is a default value\n// so we use 1 as key-direction 0\n                KEY_DIRECTION_NONE = 0;\n                KEY_DIRECTION_0    = 1;\n                KEY_DIRECTION_1    = 2;\n}\n\nenum OpenVPNEncryption {\n                TLS_NONE        = 0;\n                TLS_AUTH        = 1;\n                TLS_CRYPT       = 2;\n                TLS_CRYPT_V2    = 3;\n}\n\nmessage OpenVPNConfigRequest {\n        bool all_traffic            = 1;\n        OpenVPNProto    proto       = 2;\n        string host                 = 3;\n        uint32 port                 = 4;\n        OpenVPNCipher   cipher      = 5;\n        string ca                   = 6;\n        string cert                 = 7;\n        string key                  = 8;\n        OpenVPNEncryption tls_encryption = 9;\n        OpenVPNKeyDirection tls_key_direction = 10;\n        string tls_key              = 11;\n        OpenVPNAuth     auth        = 12;\n        string          login       = 13;\n        string          password    = 14;\n}\n"
  },
  {
    "path": "lamda/rpc/services.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nimport public \"google/protobuf/empty.proto\";\nimport public \"google/protobuf/any.proto\";\n\nimport public \"types.proto\";\n\nimport public \"util.proto\";\nimport public \"shell.proto\";\nimport public \"policy.proto\";\nimport public \"debug.proto\";\nimport public \"settings.proto\";\nimport public \"status.proto\";\nimport public \"application.proto\";\nimport public \"uiautomator.proto\";\nimport public \"storage.proto\";\nimport public \"proxy.proto\";\nimport public \"file.proto\";\nimport public \"wifi.proto\";\n\n\nservice Application {\n    rpc isForeground(ApplicationRequest) returns (Boolean) {}\n    rpc currentApplication(google.protobuf.Empty) returns (ApplicationInfo) {}\n    rpc enumerateAllPkgNames(google.protobuf.Empty) returns (ApplicationPkgNames) {}\n    rpc enumerateRunningProcesses(google.protobuf.Empty) returns (ApplicationProcesses) {}\n    rpc queryLaunchActivity(ApplicationRequest) returns (ApplicationActivityInfo) {}\n    rpc getPermissions(ApplicationRequest) returns (ApplicationPermissions) {}\n    rpc resetApplicationData(ApplicationRequest) returns (Boolean) {}\n    rpc deleteApplicationCache(ApplicationRequest) returns (Boolean) {}\n    rpc getLastActivities(Integer) returns (ApplicationActivityInfoList) {}\n    rpc startActivity(ApplicationActivityRequest) returns (Boolean) {}\n    rpc applicationInfo(ApplicationRequest) returns (ApplicationInfo) {}\n    rpc startApplication(ApplicationRequest) returns (Boolean) {}\n    rpc stopApplication(ApplicationRequest) returns (Boolean) {}\n    rpc uninstallApplication(ApplicationRequest) returns (Boolean) {}\n    rpc installFromLocalFile(ApplicationRequest) returns (ApplicationInfo) {}\n    rpc enableApplication(ApplicationRequest) returns (Boolean) {}\n    rpc disableApplication(ApplicationRequest) returns (Boolean) {}\n    rpc grantPermission(ApplicationRequest) returns (Boolean) {}\n    rpc revokePermission(ApplicationRequest) returns (Boolean) {}\n    rpc isPermissionGranted(ApplicationRequest) returns (Boolean) {}\n    rpc isInstalled(ApplicationRequest) returns (Boolean) {}\n\n    rpc addToDozeModeWhiteList(ApplicationRequest) returns (Boolean) {}\n    rpc removeFromDozeModeWhiteList(ApplicationRequest) returns (Boolean) {}\n    rpc getIdentifierByLabel(String) returns (String) {}\n\n    rpc callScript(HookRpcRequest) returns (HookRpcResponse) {}\n    rpc isScriptAlive(HookRequest) returns (Boolean) {}\n    rpc isScriptAttached(HookRequest) returns (Boolean) {}\n    rpc attachScript(HookRequest) returns (Boolean) {}\n    rpc detachScript(HookRequest) returns (Boolean) {}\n}\n\nservice Debug {\n    rpc isAndroidDebugBridgeRunning(google.protobuf.Empty) returns (Boolean) {}\n\n    rpc installADBPubKey(ADBDConfigRequest) returns (Boolean) {}\n    rpc uninstallADBPubKey(ADBDConfigRequest) returns (Boolean) {}\n\n    rpc startAndroidDebugBridge(google.protobuf.Empty) returns (Boolean) {}\n    rpc stopAndroidDebugBridge(google.protobuf.Empty) returns (Boolean) {}\n}\n\nservice Proxy {\n    rpc isOpenVPNRunning(google.protobuf.Empty) returns (Boolean) {}\n    rpc isGproxyRunning(google.protobuf.Empty) returns (Boolean) {}\n    rpc stopOpenVPN(google.protobuf.Empty) returns (Boolean) {}\n    rpc stopGproxy(google.protobuf.Empty) returns (Boolean) {}\n    rpc startOpenVPN(OpenVPNConfigRequest) returns (Boolean) {}\n    rpc startGproxy(GproxyConfigRequest) returns (Boolean) {}\n}\n\nservice SelinuxPolicy {\n    rpc setEnforce(Boolean) returns (Integer) {}\n    rpc getEnforce(google.protobuf.Empty) returns (Integer) {}\n    rpc isEnabled(google.protobuf.Empty) returns (Boolean) {}\n\n    rpc policySetEnforce(String) returns (Boolean) {}\n    rpc policySetPermissive(String) returns (Boolean) {}\n    rpc policySetAllow(SelinuxPolicyRequest) returns (Boolean) {}\n    rpc policySetDisallow(SelinuxPolicyRequest) returns (Boolean) {}\n    rpc policyCreateDomain(String) returns (Boolean) {}\n}\n\nservice Settings {\n    rpc putSettings(SettingsRequest) returns (Boolean) {}\n    rpc getSettings(SettingsRequest) returns (String) {}\n}\n\nservice Shell {\n    rpc executeForeground(ShellRequest) returns (ShellResult) {}\n    rpc executeBackground(ShellRequest) returns (ShellTask) {}\n    rpc isBackgroundFinished(ShellTask) returns (Boolean) {}\n    rpc killBackground(ShellTask) returns (Boolean) {}\n}\n\nservice Status {\n    rpc getBootTime(google.protobuf.Empty) returns (Integer) {}\n    rpc getDiskUsage(String) returns (DiskUsage) {}\n    rpc getBatteryInfo(google.protobuf.Empty) returns (BatteryInfo) {}\n    rpc getCpuInfo(google.protobuf.Empty) returns (CpuInfo) {}\n    rpc getOverallDiskIOInfo(google.protobuf.Empty) returns (DiskIOInfo) {}\n    rpc getOverallNetIOInfo(google.protobuf.Empty) returns (NetIOInfo) {}\n    rpc getMemInfo(google.protobuf.Empty) returns (MemInfo) {}\n    rpc getUserDataDiskIOInfo(google.protobuf.Empty) returns (DiskIOInfo) {}\n    rpc getNetIOInfo(String) returns (NetIOInfo) {}\n}\n\nservice UiAutomator {\n    rpc click(ClickPointRequest) returns (Boolean) {}\n    rpc drag(DragPointRequest) returns (Boolean) {}\n    rpc swipe(SwipePointRequest) returns (Boolean) {}\n    rpc swipePoints(SwipePointsRequest) returns (Boolean) {}\n    rpc openNotification(google.protobuf.Empty) returns (Boolean) {}\n    rpc openQuickSettings(google.protobuf.Empty) returns (Boolean) {}\n    rpc wakeUp(google.protobuf.Empty) returns (Boolean) {}\n    rpc sleep(google.protobuf.Empty) returns (Boolean) {}\n    rpc isScreenOn(google.protobuf.Empty) returns (Boolean) {}\n    rpc isScreenLocked(google.protobuf.Empty) returns (Boolean) {}\n    rpc setClipboard(ClipboardRequest) returns (Boolean) {}\n    rpc getClipboard(google.protobuf.Empty) returns (String) {}\n    rpc freezeRotation(Boolean) returns (Boolean) {}\n    rpc setOrientation(OrientationRequest) returns (Boolean) {}\n    rpc pressKey(PressKeyRequest) returns (Boolean) {}\n    rpc pressKeyCode(PressKeyRequest) returns (Boolean) {}\n\n    rpc deviceInfo(google.protobuf.Empty) returns (DeviceInfo) {}\n\n    rpc takeScreenshot(TakeScreenshotRequest) returns (Bytes) {}\n    rpc dumpWindowHierarchy(Boolean) returns (Bytes) {}\n    rpc waitForIdle(Integer) returns (Boolean) {}\n\n    rpc selectorTakeScreenshot(SelectorTakeScreenshotRequest) returns (Bytes) {}\n\n    rpc selectorGetText(SelectorOnlyRequest) returns (String) {}\n    rpc selectorClearTextField(SelectorOnlyRequest) returns (Boolean) {}\n    rpc selectorSetText(SelectorSetTextRequest) returns (Boolean) {}\n\n    rpc selectorClick(SelectorClickRequest) returns (Boolean) {}\n    rpc selectorClickExists(SelectorClickRequest) returns (Boolean) {}\n    rpc selectorLongClick(SelectorClickRequest) returns (Boolean) {}\n\n    rpc selectorExists(SelectorOnlyRequest) returns (Boolean) {}\n    rpc getLastToast(google.protobuf.Empty) returns (ToastInfo) {}\n\n    rpc selectorObjInfo(SelectorOnlyRequest) returns (ObjInfo) {}\n    rpc selectorObjInfoOfAllInstances(SelectorOnlyRequest) returns (ObjInfoList) {}\n    rpc selectorCount(SelectorOnlyRequest) returns (Integer) {}\n\n    rpc selectorDragTo(SelectorDragToRequest) returns (Boolean) {}\n\n    rpc selectorWaitForExists(SelectorWaitRequest) returns (Boolean) {}\n    rpc selectorWaitUntilGone(SelectorWaitRequest) returns (Boolean) {}\n    rpc selectorSwipe(SelectorSwipeRequest) returns (Boolean) {}\n\n    rpc selectorFlingToBeginning(SelectorFlingRequest) returns (Boolean) {}\n    rpc selectorFlingToEnd(SelectorFlingRequest) returns (Boolean) {}\n    rpc selectorFlingBackward(SelectorFlingRequest) returns (Boolean) {}\n    rpc selectorFlingForward(SelectorFlingRequest) returns (Boolean) {}\n\n    rpc selectorScrollToBeginning(SelectorScrollRequest) returns (Boolean) {}\n    rpc selectorScrollToEnd(SelectorScrollRequest) returns (Boolean) {}\n    rpc selectorScrollBackward(SelectorScrollRequest) returns (Boolean) {}\n    rpc selectorScrollForward(SelectorScrollRequest) returns (Boolean) {}\n    rpc selectorScrollTo(SelectorScrollRequest) returns (Boolean) {}\n\n    rpc selectorPinchOut(SelectorPinchRequest) returns (Boolean) {}\n    rpc selectorPinchIn(SelectorPinchRequest) returns (Boolean) {}\n\n    rpc setWatcherLoopEnabled(Boolean) returns (Boolean) {}\n    rpc getWatcherLoopEnabled(google.protobuf.Empty) returns (Boolean) {}\n    rpc getWatcherTriggeredCount(String) returns (Integer) {}\n    rpc resetWatcherTriggeredCount(String) returns (Boolean) {}\n\n    rpc removeWatcher(String) returns (Boolean) {}\n    rpc getAppliedWatchers(google.protobuf.Empty) returns (WatcherNameList) {}\n\n    rpc registerClickUiObjectWatcher(WatcherRegistRequest) returns (Boolean) {}\n    rpc registerPressKeysWatcher(WatcherRegistRequest) returns (Boolean) {}\n    rpc registerNoneOpWatcher(WatcherRegistRequest) returns (Boolean) {}\n    rpc findSimilarImage(FindImageRequest) returns (FindImageResponse) {}\n}\n\nservice Wifi {\n    rpc status(google.protobuf.Empty) returns (WifiStatus) {}\n    rpc blacklistAdd(String) returns (Boolean) {}\n    rpc blacklistClear(google.protobuf.Empty) returns (Boolean) {}\n    rpc blacklistAll(google.protobuf.Empty) returns (WifiBlacklist) {}\n    rpc scan(Boolean) returns (Boolean) {}\n    rpc scanResults(google.protobuf.Empty) returns (ScanResult) {}\n    rpc listNetworks(google.protobuf.Empty) returns (NetworkList) {}\n    rpc selectNetwork(Network) returns (Boolean) {}\n    rpc enableNetwork(Network) returns (Boolean) {}\n    rpc disableNetwork(Network) returns (Boolean) {}\n    rpc addNetwork(google.protobuf.Empty) returns (Network) {}\n    rpc removeNetwork(Network) returns (Boolean) {}\n    rpc setNetworkConfig(NetworkConfig) returns (Boolean) {}\n    rpc getNetworkConfig(NetworkConfig) returns (NetworkConfig) {}\n    rpc disconnect(google.protobuf.Empty) returns (Boolean) {}\n    rpc reconnect(google.protobuf.Empty) returns (Boolean) {}\n    rpc setConfig(WifiConfig) returns (Boolean) {}\n    rpc setAutoConnect(Boolean) returns (Boolean) {}\n    rpc saveConfig(google.protobuf.Empty) returns (Boolean) {}\n    rpc getMacAddr(google.protobuf.Empty) returns (String) {}\n    rpc signalPoll(google.protobuf.Empty) returns (SignalPoll) {}\n}\n\nservice Util {\n    rpc recordTouch(google.protobuf.Empty) returns (TouchSequence) {}\n    rpc performTouch(PerformTouchRequest) returns (Boolean) {}\n\n    rpc serverInfo(google.protobuf.Empty) returns (ServerInfoResponse) {}\n\n    rpc reboot(google.protobuf.Empty) returns (Boolean) {}\n    rpc beepBeep(google.protobuf.Empty) returns (Boolean) {}\n    rpc isCACertificateInstalled(CertifiRequest) returns (Boolean) {}\n    rpc installCACertificate(CertifiRequest) returns (Boolean) {}\n    rpc uninstallCACertificate(CertifiRequest) returns (Boolean) {}\n    rpc shutdown(google.protobuf.Empty) returns (Boolean) {}\n    rpc showToast(ShowToastRequest) returns (Boolean) {}\n    rpc reload(Boolean) returns (Boolean) {}\n    rpc exit(google.protobuf.Empty) returns (Boolean) {}\n    rpc setProp(SetPropRequest) returns (Boolean) {}\n    rpc getProp(String) returns (String) {}\n    rpc hexPatch(HexPatchRequest) returns (HexPatchResponse) {}\n    rpc playAudio(PlayAudioRequest) returns (Boolean) {}\n}\n\nservice File {\n    rpc uploadFile(stream FileRequest) returns (FileStat) {}\n    rpc downloadFile(FileRequest) returns (stream FileDataResponse) {}\n    rpc deleteFile(FileRequest) returns (Boolean) {}\n    rpc fileChmod(FileRequest) returns (FileStat) {}\n    rpc fileStat(FileRequest) returns (FileStat) {}\n}\n\nservice Lock {\n    rpc releaseLock(google.protobuf.Empty) returns (Boolean) {}\n    rpc getSessionToken(google.protobuf.Empty) returns (String) {}\n    rpc acquireLock(Integer) returns (Boolean) {}\n    rpc refreshLock(Integer) returns (Boolean) {}\n}\n\nservice Storage {\n    rpc clearAll(StorageRequest) returns (Boolean) {}\n    rpc clearContainer(StorageRequest) returns (Boolean) {}\n    rpc exists(StorageRequest) returns (Boolean) {}\n    rpc delete(StorageRequest) returns (Boolean) {}\n    rpc expire(StorageRequest) returns (Boolean) {}\n    rpc ttl(StorageRequest) returns (Integer) {}\n    rpc set(StorageRequest) returns (Boolean) {}\n    rpc setnx(StorageRequest) returns (Boolean) {}\n    rpc setex(StorageRequest) returns (Boolean) {}\n    rpc get(StorageRequest) returns (Bytes) {}\n}"
  },
  {
    "path": "lamda/rpc/settings.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nenum Group {\n                GROUP_SYSTEM       = 0;\n                GROUP_SECURE       = 1;\n                GROUP_GLOBAL       = 2;\n}\n\nmessage SettingsRequest {\n        Group  group    = 1;\n        string name     = 2;\n        string value    = 3;\n}\n"
  },
  {
    "path": "lamda/rpc/shell.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nmessage ShellRequest {\n        string tid        = 1;\n        string name       = 2;\n        string script     = 3;\n        int32  timeout    = 4;\n}\n\nmessage ShellResult {\n        int32  exitstatus = 1;\n        bytes  stdout     = 2;\n        bytes  stderr     = 3;\n}\n\nmessage ShellTask {\n        string tid        = 1;\n        string name       = 2;\n        int32  pid        = 3;\n}"
  },
  {
    "path": "lamda/rpc/status.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nmessage BatteryInfo {\n        bool  batt_charging         = 1;\n        int32 batt_percent          = 2;\n        float batt_temperature      = 3;\n}\n\nmessage CpuInfo {\n        float cpu_percent           = 1;\n        int32 cpu_count             = 2;\n        float cpu_freq_current      = 3;\n        float cpu_freq_max          = 4;\n        float cpu_freq_min          = 5;\n        float cpu_times_user        = 6;\n        float cpu_times_system      = 7;\n        float cpu_times_idle        = 8;\n}\n\nmessage DiskUsage {\n        int64 disk_total            = 1;\n        int64 disk_used             = 2;\n        int64 disk_free             = 3;\n        float disk_percent          = 4;\n}\n\nmessage DiskIOInfo {\n        int64 disk_io_read_bytes    = 1;\n        int64 disk_io_read_count    = 2;\n        int64 disk_io_write_bytes   = 3;\n        int64 disk_io_write_count   = 4;\n        int64 disk_io_read_time     = 5;\n        int64 disk_io_write_time    = 6;\n        int64 disk_io_busy_time     = 7;\n}\n\nmessage NetIOInfo {\n        int64 net_io_bytes_sent     = 1;\n        int64 net_io_packets_sent   = 2;\n        int64 net_io_bytes_recv     = 3;\n        int64 net_io_packets_recv   = 4;\n        int64 net_io_dropin         = 5;\n        int64 net_io_dropout        = 6;\n        int64 net_io_errin          = 7;\n        int64 net_io_errout         = 8;\n}\n\nmessage MemInfo {\n        int64 mem_total             = 1;\n        int64 mem_available         = 2;\n        float mem_percent           = 3;\n        int64 mem_used              = 4;\n        int64 mem_free              = 5;\n        int64 mem_active            = 6;\n        int64 mem_inactive          = 7;\n        int64 mem_buffers           = 8;\n        int64 mem_cached            = 9;\n        int64 mem_shared            = 10;\n        int64 mem_slab              = 11;\n}\n"
  },
  {
    "path": "lamda/rpc/storage.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nmessage StorageRequest {\n        string container      = 1;\n        string key            = 2;\n        bytes  value          = 3;\n        uint32 ttl            = 4;\n}"
  },
  {
    "path": "lamda/rpc/types.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nmessage Boolean {\n        bool   value  = 1;\n}\n\nmessage Integer {\n        int64  value  = 1;\n}\n\nmessage String {\n        string value  = 1;\n}\n\nmessage Bytes {\n        bytes  value  = 1;\n}\n\nmessage Empty {\n}\n"
  },
  {
    "path": "lamda/rpc/uiautomator.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nmessage Point {\n        uint32 x            = 1;\n        uint32 y            = 2;\n}\n\nmessage Bound {\n        uint32 top          = 1;\n        uint32 left         = 2;\n        uint32 bottom       = 3;\n        uint32 right        = 4;\n}\n\nenum Orientation {\n        ORIEN_NATURE        = 0;\n        ORIEN_LEFT          = 1;\n        ORIEN_UPSIDEDOWN    = 2;\n        ORIEN_RIGHT         = 3;\n}\n\nenum Key {\n        KEY_BACK            = 0;\n        KEY_CAMERA          = 1;\n        KEY_CENTER          = 2;\n        KEY_DELETE          = 3;\n        KEY_DOWN            = 4;\n        KEY_ENTER           = 5;\n        KEY_HOME            = 6;\n        KEY_LEFT            = 7;\n        KEY_MENU            = 8;\n        KEY_POWER           = 9;\n        KEY_RECENT          = 10;\n        KEY_RIGHT           = 11;\n        KEY_SEARCH          = 12;\n        KEY_UP              = 13;\n        KEY_VOLUME_DOWN     = 14;\n        KEY_VOLUME_MUTE     = 15;\n        KEY_VOLUME_UP       = 16;\n}\n\nenum KeyCode {\n        KEYCODE_UNKNOWN         = 0;\n        KEYCODE_SOFT_LEFT       = 1;\n        KEYCODE_SOFT_RIGHT      = 2;\n        KEYCODE_HOME            = 3;\n        KEYCODE_BACK            = 4;\n        KEYCODE_CALL            = 5;\n        KEYCODE_ENDCALL         = 6;\n        KEYCODE_0               = 7;\n        KEYCODE_1               = 8;\n        KEYCODE_2               = 9;\n        KEYCODE_3               = 10;\n        KEYCODE_4               = 11;\n        KEYCODE_5               = 12;\n        KEYCODE_6               = 13;\n        KEYCODE_7               = 14;\n        KEYCODE_8               = 15;\n        KEYCODE_9               = 16;\n        KEYCODE_STAR            = 17;\n        KEYCODE_POUND           = 18;\n        KEYCODE_DPAD_UP         = 19;\n        KEYCODE_DPAD_DOWN       = 20;\n        KEYCODE_DPAD_LEFT       = 21;\n        KEYCODE_DPAD_RIGHT      = 22;\n        KEYCODE_DPAD_CENTER     = 23;\n        KEYCODE_VOLUME_UP       = 24;\n        KEYCODE_VOLUME_DOWN     = 25;\n        KEYCODE_POWER           = 26;\n        KEYCODE_CAMERA          = 27;\n        KEYCODE_CLEAR           = 28;\n        KEYCODE_A               = 29;\n        KEYCODE_B               = 30;\n        KEYCODE_C               = 31;\n        KEYCODE_D               = 32;\n        KEYCODE_E               = 33;\n        KEYCODE_F               = 34;\n        KEYCODE_G               = 35;\n        KEYCODE_H               = 36;\n        KEYCODE_I               = 37;\n        KEYCODE_J               = 38;\n        KEYCODE_K               = 39;\n        KEYCODE_L               = 40;\n        KEYCODE_M               = 41;\n        KEYCODE_N               = 42;\n        KEYCODE_O               = 43;\n        KEYCODE_P               = 44;\n        KEYCODE_Q               = 45;\n        KEYCODE_R               = 46;\n        KEYCODE_S               = 47;\n        KEYCODE_T               = 48;\n        KEYCODE_U               = 49;\n        KEYCODE_V               = 50;\n        KEYCODE_W               = 51;\n        KEYCODE_X               = 52;\n        KEYCODE_Y               = 53;\n        KEYCODE_Z               = 54;\n        KEYCODE_COMMA           = 55;\n        KEYCODE_PERIOD          = 56;\n        KEYCODE_ALT_LEFT        = 57;\n        KEYCODE_ALT_RIGHT       = 58;\n        KEYCODE_SHIFT_LEFT      = 59;\n        KEYCODE_SHIFT_RIGHT     = 60;\n        KEYCODE_TAB             = 61;\n        KEYCODE_SPACE           = 62;\n        KEYCODE_SYM             = 63;\n        KEYCODE_EXPLORER        = 64;\n        KEYCODE_ENVELOPE        = 65;\n        KEYCODE_ENTER           = 66;\n        KEYCODE_DEL             = 67;\n        KEYCODE_GRAVE           = 68;\n        KEYCODE_MINUS           = 69;\n        KEYCODE_EQUALS          = 70;\n        KEYCODE_LEFT_BRACKET    = 71;\n        KEYCODE_RIGHT_BRACKET   = 72;\n        KEYCODE_BACKSLASH       = 73;\n        KEYCODE_SEMICOLON       = 74;\n        KEYCODE_APOSTROPHE      = 75;\n        KEYCODE_SLASH           = 76;\n        KEYCODE_AT              = 77;\n        KEYCODE_NUM             = 78;\n        KEYCODE_HEADSETHOOK     = 79;\n        KEYCODE_FOCUS           = 80;\n        KEYCODE_PLUS            = 81;\n        KEYCODE_MENU            = 82;\n        KEYCODE_NOTIFICATION    = 83;\n        KEYCODE_SEARCH          = 84;\n        KEYCODE_MEDIA_PLAY_PAUSE= 85;\n        KEYCODE_MEDIA_STOP      = 86;\n        KEYCODE_MEDIA_NEXT      = 87;\n        KEYCODE_MEDIA_PREVIOUS  = 88;\n        KEYCODE_MEDIA_REWIND    = 89;\n        KEYCODE_MEDIA_FAST_FORWARD = 90;\n        KEYCODE_MUTE            = 91;\n        KEYCODE_PAGE_UP         = 92;\n        KEYCODE_PAGE_DOWN       = 93;\n        KEYCODE_PICTSYMBOLS     = 94;\n        KEYCODE_SWITCH_CHARSET  = 95;\n        KEYCODE_BUTTON_A        = 96;\n        KEYCODE_BUTTON_B        = 97;\n        KEYCODE_BUTTON_C        = 98;\n        KEYCODE_BUTTON_X        = 99;\n        KEYCODE_BUTTON_Y        = 100;\n        KEYCODE_BUTTON_Z        = 101;\n        KEYCODE_BUTTON_L1       = 102;\n        KEYCODE_BUTTON_R1       = 103;\n        KEYCODE_BUTTON_L2       = 104;\n        KEYCODE_BUTTON_R2       = 105;\n        KEYCODE_BUTTON_THUMBL   = 106;\n        KEYCODE_BUTTON_THUMBR   = 107;\n        KEYCODE_BUTTON_START    = 108;\n        KEYCODE_BUTTON_SELECT   = 109;\n        KEYCODE_BUTTON_MODE     = 110;\n        KEYCODE_ESCAPE          = 111;\n        KEYCODE_FORWARD_DEL     = 112;\n        KEYCODE_CTRL_LEFT       = 113;\n        KEYCODE_CTRL_RIGHT      = 114;\n        KEYCODE_CAPS_LOCK       = 115;\n        KEYCODE_SCROLL_LOCK     = 116;\n        KEYCODE_META_LEFT       = 117;\n        KEYCODE_META_RIGHT      = 118;\n        KEYCODE_FUNCTION        = 119;\n        KEYCODE_SYSRQ           = 120;\n        KEYCODE_BREAK           = 121;\n        KEYCODE_MOVE_HOME       = 122;\n        KEYCODE_MOVE_END        = 123;\n        KEYCODE_INSERT          = 124;\n        KEYCODE_FORWARD         = 125;\n        KEYCODE_MEDIA_PLAY      = 126;\n        KEYCODE_MEDIA_PAUSE     = 127;\n        KEYCODE_MEDIA_CLOSE     = 128;\n        KEYCODE_MEDIA_EJECT     = 129;\n        KEYCODE_MEDIA_RECORD    = 130;\n        KEYCODE_F1              = 131;\n        KEYCODE_F2              = 132;\n        KEYCODE_F3              = 133;\n        KEYCODE_F4              = 134;\n        KEYCODE_F5              = 135;\n        KEYCODE_F6              = 136;\n        KEYCODE_F7              = 137;\n        KEYCODE_F8              = 138;\n        KEYCODE_F9              = 139;\n        KEYCODE_F10             = 140;\n        KEYCODE_F11             = 141;\n        KEYCODE_F12             = 142;\n        KEYCODE_NUM_LOCK        = 143;\n        KEYCODE_NUMPAD_0        = 144;\n        KEYCODE_NUMPAD_1        = 145;\n        KEYCODE_NUMPAD_2        = 146;\n        KEYCODE_NUMPAD_3        = 147;\n        KEYCODE_NUMPAD_4        = 148;\n        KEYCODE_NUMPAD_5        = 149;\n        KEYCODE_NUMPAD_6        = 150;\n        KEYCODE_NUMPAD_7        = 151;\n        KEYCODE_NUMPAD_8        = 152;\n        KEYCODE_NUMPAD_9        = 153;\n        KEYCODE_NUMPAD_DIVIDE   = 154;\n        KEYCODE_NUMPAD_MULTIPLY = 155;\n        KEYCODE_NUMPAD_SUBTRACT = 156;\n        KEYCODE_NUMPAD_ADD      = 157;\n        KEYCODE_NUMPAD_DOT      = 158;\n        KEYCODE_NUMPAD_COMMA    = 159;\n        KEYCODE_NUMPAD_ENTER    = 160;\n        KEYCODE_NUMPAD_EQUALS   = 161;\n        KEYCODE_NUMPAD_LEFT_PAREN = 162;\n        KEYCODE_NUMPAD_RIGHT_PAREN = 163;\n        KEYCODE_VOLUME_MUTE     = 164;\n        KEYCODE_INFO            = 165;\n        KEYCODE_CHANNEL_UP      = 166;\n        KEYCODE_CHANNEL_DOWN    = 167;\n        KEYCODE_ZOOM_IN         = 168;\n        KEYCODE_ZOOM_OUT        = 169;\n        KEYCODE_TV              = 170;\n        KEYCODE_WINDOW          = 171;\n        KEYCODE_GUIDE           = 172;\n        KEYCODE_DVR             = 173;\n        KEYCODE_BOOKMARK        = 174;\n        KEYCODE_CAPTIONS        = 175;\n        KEYCODE_SETTINGS        = 176;\n        KEYCODE_TV_POWER        = 177;\n        KEYCODE_TV_INPUT        = 178;\n        KEYCODE_STB_POWER       = 179;\n        KEYCODE_STB_INPUT       = 180;\n        KEYCODE_AVR_POWER       = 181;\n        KEYCODE_AVR_INPUT       = 182;\n        KEYCODE_PROG_RED        = 183;\n        KEYCODE_PROG_GREEN      = 184;\n        KEYCODE_PROG_YELLOW     = 185;\n        KEYCODE_PROG_BLUE       = 186;\n        KEYCODE_APP_SWITCH      = 187;\n        KEYCODE_BUTTON_1        = 188;\n        KEYCODE_BUTTON_2        = 189;\n        KEYCODE_BUTTON_3        = 190;\n        KEYCODE_BUTTON_4        = 191;\n        KEYCODE_BUTTON_5        = 192;\n        KEYCODE_BUTTON_6        = 193;\n        KEYCODE_BUTTON_7        = 194;\n        KEYCODE_BUTTON_8        = 195;\n        KEYCODE_BUTTON_9        = 196;\n        KEYCODE_BUTTON_10       = 197;\n        KEYCODE_BUTTON_11       = 198;\n        KEYCODE_BUTTON_12       = 199;\n        KEYCODE_BUTTON_13       = 200;\n        KEYCODE_BUTTON_14       = 201;\n        KEYCODE_BUTTON_15       = 202;\n        KEYCODE_BUTTON_16       = 203;\n        KEYCODE_LANGUAGE_SWITCH = 204;\n        KEYCODE_MANNER_MODE     = 205;\n        KEYCODE_3D_MODE         = 206;\n        KEYCODE_CONTACTS        = 207;\n        KEYCODE_CALENDAR        = 208;\n        KEYCODE_MUSIC           = 209;\n        KEYCODE_CALCULATOR      = 210;\n        KEYCODE_ZENKAKU_HANKAKU = 211;\n        KEYCODE_EISU            = 212;\n        KEYCODE_MUHENKAN        = 213;\n        KEYCODE_HENKAN          = 214;\n        KEYCODE_KATAKANA_HIRAGANA = 215;\n        KEYCODE_YEN             = 216;\n        KEYCODE_RO              = 217;\n        KEYCODE_KANA            = 218;\n        KEYCODE_ASSIST          = 219;\n        KEYCODE_BRIGHTNESS_DOWN = 220;\n        KEYCODE_BRIGHTNESS_UP   = 221;\n        KEYCODE_MEDIA_AUDIO_TRACK = 222;\n        KEYCODE_SLEEP           = 223;\n        KEYCODE_WAKEUP          = 224;\n        KEYCODE_PAIRING         = 225;\n        KEYCODE_MEDIA_TOP_MENU  = 226;\n        KEYCODE_11              = 227;\n        KEYCODE_12              = 228;\n        KEYCODE_LAST_CHANNEL    = 229;\n        KEYCODE_TV_DATA_SERVICE = 230;\n        KEYCODE_VOICE_ASSIST    = 231;\n        KEYCODE_TV_RADIO_SERVICE = 232;\n        KEYCODE_TV_TELETEXT     = 233;\n        KEYCODE_TV_NUMBER_ENTRY = 234;\n        KEYCODE_TV_TERRESTRIAL_ANALOG = 235;\n        KEYCODE_TV_TERRESTRIAL_DIGITAL = 236;\n        KEYCODE_TV_SATELLITE    = 237;\n        KEYCODE_TV_SATELLITE_BS = 238;\n        KEYCODE_TV_SATELLITE_CS = 239;\n        KEYCODE_TV_SATELLITE_SERVICE = 240;\n        KEYCODE_TV_NETWORK      = 241;\n        KEYCODE_TV_ANTENNA_CABLE = 242;\n        KEYCODE_TV_INPUT_HDMI_1 = 243;\n        KEYCODE_TV_INPUT_HDMI_2 = 244;\n        KEYCODE_TV_INPUT_HDMI_3 = 245;\n        KEYCODE_TV_INPUT_HDMI_4 = 246;\n        KEYCODE_TV_INPUT_COMPOSITE_1 = 247;\n        KEYCODE_TV_INPUT_COMPOSITE_2 = 248;\n        KEYCODE_TV_INPUT_COMPONENT_1 = 249;\n        KEYCODE_TV_INPUT_COMPONENT_2 = 250;\n        KEYCODE_TV_INPUT_VGA_1  = 251;\n        KEYCODE_TV_AUDIO_DESCRIPTION = 252;\n        KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253;\n        KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254;\n        KEYCODE_TV_ZOOM_MODE    = 255;\n        KEYCODE_TV_CONTENTS_MENU = 256;\n        KEYCODE_TV_MEDIA_CONTEXT_MENU = 257;\n        KEYCODE_TV_TIMER_PROGRAMMING = 258;\n        KEYCODE_HELP            = 259;\n        KEYCODE_NAVIGATE_PREVIOUS = 260;\n        KEYCODE_NAVIGATE_NEXT   = 261;\n        KEYCODE_NAVIGATE_IN     = 262;\n        KEYCODE_NAVIGATE_OUT    = 263;\n        KEYCODE_STEM_PRIMARY    = 264;\n        KEYCODE_STEM_1          = 265;\n        KEYCODE_STEM_2          = 266;\n        KEYCODE_STEM_3          = 267;\n        KEYCODE_DPAD_UP_LEFT    = 268;\n        KEYCODE_DPAD_DOWN_LEFT  = 269;\n        KEYCODE_DPAD_UP_RIGHT   = 270;\n        KEYCODE_DPAD_DOWN_RIGHT = 271;\n        KEYCODE_MEDIA_SKIP_FORWARD = 272;\n        KEYCODE_MEDIA_SKIP_BACKWARD = 273;\n        KEYCODE_MEDIA_STEP_FORWARD = 274;\n        KEYCODE_MEDIA_STEP_BACKWARD = 275;\n        KEYCODE_SOFT_SLEEP      = 276;\n        KEYCODE_CUT             = 277;\n        KEYCODE_COPY            = 278;\n        KEYCODE_PASTE           = 279;\n        KEYCODE_SYSTEM_NAVIGATION_UP = 280;\n        KEYCODE_SYSTEM_NAVIGATION_DOWN = 281;\n        KEYCODE_SYSTEM_NAVIGATION_LEFT = 282;\n        KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;\n        KEYCODE_ALL_APPS        = 284;\n        KEYCODE_REFRESH         = 285;\n        KEYCODE_THUMBS_UP       = 286;\n        KEYCODE_THUMBS_DOWN     = 287;\n        KEYCODE_PROFILE_SWITCH  = 288;\n}\n\nenum MetaKeyCode {\n        META_UNKNOWN            = 0;\n        META_SHIFT_ON           = 1;\n        META_ALT_ON             = 2;\n        META_SYM_ON             = 4;\n        META_FUNCTION_ON        = 8;\n        META_ALT_LEFT_ON        = 16;\n        META_ALT_RIGHT_ON       = 32;\n        META_ALT_MASK           = 50;\n        META_SHIFT_LEFT_ON      = 64;\n        META_SHIFT_RIGHT_ON     = 128;\n        META_SHIFT_MASK         = 193;\n        META_CTRL_ON            = 4096;\n        META_CTRL_LEFT_ON       = 8192;\n        META_CTRL_RIGHT_ON      = 16384;\n        META_CTRL_MASK          = 28672;\n        META_META_ON            = 65536;\n        META_META_LEFT_ON       = 131072;\n        META_META_RIGHT_ON      = 262144;\n        META_META_MASK          = 458752;\n        META_CAPS_LOCK_ON       = 1048576;\n        META_NUM_LOCK_ON        = 2097152;\n        META_SCROLL_LOCK_ON     = 4194304;\n}\n\nenum Corner {\n        COR_CENTER          = 0;\n        COR_BOTTOMRIGHT     = 1;\n        COR_TOPLEFT         = 2;\n}\n\nenum Direction {\n        DIR_UP              = 0;\n        DIR_LEFT            = 1;\n        DIR_DOWN            = 2;\n        DIR_RIGHT           = 3;\n}\n\nmessage ObjInfo {\n        Bound bounds        = 1;\n        bool checkable      = 2;\n        bool checked        = 3;\n        uint32 childCount   = 4;\n        string className    = 5;\n        bool clickable      = 6;\n        string contentDescription = 7;\n        bool enabled        = 8;\n        bool focusable      = 9;\n        bool focused        = 10;\n        bool longClickable  = 11;\n        string packageName  = 12;\n        string resourceName = 13;\n        bool scrollable     = 14;\n        bool selected       = 15;\n        string text         = 16;\n        Bound visibleBounds = 17;\n}\n\nmessage ToastInfo {\n        uint64 timestamp    = 1;\n        string package      = 2;\n        string message      = 3;\n}\n\nmessage ObjInfoList {\n        repeated ObjInfo objects = 1;\n}\n\nmessage Selector {\n        uint32 mask                     = 1;\n        string text                     = 2;\n        string textContains             = 3;\n        string textMatches              = 4;\n        string textStartsWith           = 5;\n        string className                = 6;\n        string classNameMatches         = 7;\n        string description              = 8;\n        string descriptionContains      = 9;\n        string descriptionMatches       = 10;\n        string descriptionStartsWith    = 11;\n        bool   checkable                = 12;\n        bool   checked                  = 13;\n        bool   clickable                = 14;\n        bool   longClickable            = 15;\n        bool   scrollable               = 16;\n        bool   enabled                  = 17;\n        bool   focusable                = 18;\n        bool   focused                  = 19;\n        bool   selected                 = 20;\n        string packageName              = 21;\n        string packageNameMatches       = 22;\n        string resourceId               = 23;\n        string resourceIdMatches        = 24;\n        uint32 index                    = 25;\n        uint32 instance                 = 26;\n        repeated string childOrSibling  = 27;\n        repeated Selector childOrSiblingSelector  = 28;\n        repeated string fields          = 50;\n}\n\nmessage DeviceInfo {\n        string productName              = 1;\n        uint32 sdkInt                   = 2;\n        uint32 displayHeight            = 3;\n        uint32 displayRotation          = 4;\n        uint32 displaySizeDpX           = 5;\n        uint32 displaySizeDpY           = 6;\n        uint32 displayWidth             = 7;\n        bool screenLocked               = 8;\n        bool screenOn                   = 9;\n        bool naturalOrientation         = 10;\n        string currentPackageName       = 11;\n}\n\nmessage SelectorTakeScreenshotRequest {\n        Selector selector         = 1;\n        uint32 quality            = 3;\n}\nmessage SelectorSetTextRequest {\n        Selector selector         = 1;\n        string text               = 3;\n}\nmessage SelectorClickRequest {\n        Selector selector         = 1;\n        Corner corner             = 3;\n}\nmessage SelectorOnlyRequest {\n        Selector selector         = 1;\n}\nmessage SelectorDragToRequest {\n        Selector selector         = 1;\n        oneof _target {\n                Selector target   = 3;\n                Point point       = 4;\n        }\n        uint32 step               = 5;\n}\nmessage SelectorWaitRequest {\n        Selector selector         = 1;\n        uint32 timeout            = 3;\n}\nmessage SelectorSwipeRequest {\n        Selector selector         = 1;\n        Direction direction       = 3;\n        uint32 step               = 4;\n}\nmessage SelectorFlingRequest {\n        Selector selector         = 1;\n        bool vertical             = 3;\n        uint32 maxSwipes           = 4;\n}\nmessage SelectorScrollRequest {\n        Selector selector         = 1;\n        Selector target           = 2;\n        bool vertical             = 3;\n        uint32 maxSwipes          = 4;\n        uint32 step               = 5;\n}\n\nmessage SelectorPinchRequest {\n        Selector selector         = 1;\n        uint32 percent            = 3;\n        uint32 step               = 4;\n}\n\nmessage ClickPointRequest {\n        Point point     = 1;\n}\n\nmessage DragPointRequest {\n        Point A         = 1;\n        Point B         = 2;\n        uint32 step     = 3;\n}\n\nmessage SwipePointRequest {\n        Point A         = 1;\n        Point B         = 2;\n        uint32 step     = 3;\n}\n\nmessage SwipePointsRequest {\n        repeated Point points = 1;\n        uint32 step     = 2;\n}\n\nmessage OrientationRequest {\n        Orientation orientation = 1;\n}\n\nmessage PressKeyRequest {\n        Key key         = 1;\n        uint32 code     = 2;\n        uint32 meta     = 3;\n}\n\nmessage TakeScreenshotRequest {\n        Bound bound     = 1;\n        uint32 quality  = 2;\n}\n\nmessage ClipboardRequest {\n        string ID       = 1;\n        string value    = 2;\n}\n\nmessage WatcherNameList {\n        repeated string watchers = 1;\n}\n\nmessage WatcherRegistRequest {\n        string name     = 1;\n        repeated Selector selectors = 2;\n        oneof _target {\n                Selector target = 3;\n                Key key = 4;\n        }\n}\n\nenum FindImageMethod {\n        FIM_TEMPLATE            = 0;\n        FIM_FEATURE             = 1;\n}\n\nenum FindImageArea {\n        FIA_WHOLE_SCREEN        = 0;\n        FIA_LEFT                = 1;\n        FIA_TOP_LEFT            = 2;\n        FIA_TOP                 = 3;\n        FIA_TOP_RIGHT           = 4;\n        FIA_RIGHT               = 5;\n        FIA_BOTTOM_RIGHT        = 6;\n        FIA_BOTTOM              = 7;\n        FIA_BOTTOM_LEFT         = 8;\n}\n\nmessage FindImageRequest {\n        bytes partial           = 1;\n        FindImageMethod method  = 2;\n        oneof _area {\n                FindImageArea area = 3;\n                Bound bound     = 4;\n        }\n        uint32 distance         = 5;\n        float threshold         = 6;\n        float scale             = 7;\n}\n\nmessage FindImageResponse {\n        repeated Bound bounds = 1;\n}"
  },
  {
    "path": "lamda/rpc/util.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nenum ToastDuration {\n        TD_SHORT         = 0;\n        TD_LONG          = 1;\n}\n\nenum AudioStreamType {\n        AST_ALARM             = 0;\n        AST_MEDIA             = 1;\n        AST_NOTIFICATION      = 2;\n        AST_RING              = 3;\n        AST_SYSTEM            = 4;\n        AST_VOICE             = 5;\n}\n\nmessage TouchDown {\n        int32 tid      = 1;\n        int32 x        = 2;\n        int32 y        = 3;\n        int32 pressure = 4;\n}\n\nmessage TouchUp {\n        int32 tid      = 1;\n}\n\nmessage TouchMove {\n        int32 tid      = 1;\n        int32 x        = 2;\n        int32 y        = 3;\n        int32 pressure = 4;\n}\n\nmessage TouchWait {\n        uint32 wait     = 1;\n}\n\nmessage TouchAction {\noneof action {\n        TouchDown down     = 1;\n        TouchMove move     = 2;\n        TouchWait wait     = 3;\n        TouchUp up         = 4;\n}\n}\n\nmessage TouchSequence {\n        repeated TouchAction sequence = 1;\n}\n\nmessage PerformTouchRequest {\n        TouchSequence sequence  = 1;\n        bool wait               = 2;\n}\n\nmessage SetPropRequest {\n        string name             = 1;\n        string value            = 2;\n}\n\nmessage CertifiRequest {\n        bytes  cert             = 1;\n}\n\nmessage ShowToastRequest {\n        string           text   = 1;\n        ToastDuration duration  = 2;\n}\n\nmessage ServerInfoResponse {\n        string         uniqueId = 1;\n        string          version = 2;\n        string     architecture = 3;\n        uint64           uptime = 4;\n        bool             secure = 5;\n}\n\nmessage HexPatchRequest {\n        string          pattern = 1;\n        string      replacement = 2;\n        string          path    = 3;\n        int32        maxreplace = 4;\n        bool            dryrun  = 5;\n}\n\nmessage HexPatchItem {\n        string          path  = 1;\n        int32           index = 2;\n        uint64         offset = 3;\n}\n\nmessage HexPatchResponse {\n        int32           count = 1;\n        repeated HexPatchItem replaces = 2;\n}\n\nmessage PlayAudioRequest {\n        string          file = 1;\n        AudioStreamType type = 2;\n        int32           loop = 3;\n        int32           interval = 4;\n}"
  },
  {
    "path": "lamda/rpc/wifi.proto",
    "content": "// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n//\n// Distributed under MIT license.\n// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nsyntax = \"proto3\";\npackage lamda.rpc;\n\nmessage WifiStatus {\n        string id               = 1;\n        string address          = 2;\n        string bssid            = 3;\n        string freq             = 4;\n        string group_cipher     = 5;\n        string ip_address       = 6;\n        string key_mgmt         = 7;\n        string mode             = 8;\n        string pairwise_cipher  = 9;\n        string ssid             = 10;\n        string wifi_generation  = 11;\n        string wpa_state        = 12;\n}\n\nmessage SignalPoll {\n        string RSSI             = 1;\n        string LINKSPEED        = 2;\n        string NOISE            = 3;\n        string FREQUENCY        = 4;\n        string WIDTH            = 5;\n        string AVG_RSSI         = 6;\n        string AVG_BEACON_RSSI  = 7;\n        string CENTER_FRQ1      = 8;\n}\n\nmessage WifiInfo {\n        string id               = 1;\n        string bssid            = 2;\n        string ssid             = 3;\n        string freq             = 4;\n        string noise            = 5;\n        string level            = 6;\n        string tsf              = 7;\n        string flags            = 8;\n}\n\nmessage ScanResult {\n        repeated WifiInfo stations  = 1;\n}\n\nmessage Network {\n        int32  nid              = 1;\n        string bssid            = 2;\n        string ssid             = 3;\n        string flags            = 4;\n}\n\nmessage NetworkList {\n        repeated Network networks   = 1;\n}\n\nmessage NetworkConfig {\n        Network network         = 1;\n        string name             = 2;\n        string value            = 3;\n}\n\nmessage WifiConfig {\n        string name             = 1;\n        string value            = 2;\n}\n\nmessage WifiBlacklist {\n        repeated string bssids   = 1;\n}"
  },
  {
    "path": "lamda/types.py",
    "content": "# Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\nimport io\nimport codecs\n\n__all__ = [\"AttributeDict\", \"BytesIO\"]\n\n\nclass AttributeDict(dict):\n    def __getattr__(self, attr):\n        return self[attr]\n    def __setattr__(self, attr, value):\n        self[attr] = value\n    def remove(self, key):\n        key in self and self.pop(key)\n\n\nclass BytesIO(io.BytesIO):\n    @classmethod\n    def decode_from(cls, data, encoding):\n        return cls(codecs.decode(data, encoding))\n    def encode(self, encoding):\n        return codecs.encode(self.getvalue(), encoding)\n    def decode(self, encoding):\n        return codecs.decode(self.getvalue(), encoding)\n    def save(self, fpath):\n        with open(fpath, \"wb\") as fd:\n            return fd.write(self.getvalue())\n    @classmethod\n    def load(cls, fpath):\n        with open(fpath, \"rb\") as fd:\n            return cls(fd.read())\n"
  },
  {
    "path": "properties.local.example",
    "content": "# properties.local configuration file\n#\n# DO NOT DIRECTLY COPY THIS FILE, PLEASE SELECT THE NECESSARY CONFIGURATION LINES TO CREATE A NEW FILE\n#\n# This file is the LAMDA service configuration file. You can use\n# it to automatically start some services when LAMDA starts.\n# The file should be located in /data or /data/usr (this directory\n# will not exist before the first startup, please create it if it does not exist).\n# Lines starting with # and ; are comment lines and will be ignored.\n# Please ensure that each line follows the format a=b and does not contain spaces.\n# If there are duplicate configuration items across multiple files,\n# the last duplicate will be used.\n\n# Set the default listening port for LAMDA.\nport=65000\n\n# You can change the name displayed on the remote desktop for\n# LAMDA to any name you prefer (up to 10 characters).\nbrandname=FIRERPA\n\n# Set the LAMDA certificate. It will encrypt your remote desktop\n# and API traffic, and enable password authentication. You can obtain\n# the following configuration value by running 'base64 -w0 lamda.pem'.\n# It is the base64-encoded certificate.\ncert=TEFNREEgU1NMIENFUlRJRklDQVRFIChDTj10ZXN0LFBBU1NXRD1hMWMwZTNlYTcwN2E1NGRlN2EwZjk1KQotLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJRXZnSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2d3Z2dTa0FnRUFBb0lCQVFEVmNMWlA5b0xRWkRIRgp5V0pTa2U0Z0crSUpmSCtMWlk0cXUzdS9OckRwSHZCN2k5V01rMWxRL2FMSGI0V3ZqelBLK1RITm9rRzc2MENRClhBTUpWS0dmYXRwcmNLUXdvMGhvWDZ2NlhsTVlZUlNRbW9wN3pSaUtnN3ZxKzV2S09DQ2RKcDFlSVNiZXcyTEgKTmYxL3JZelpwa1Q1bHoxTGZkem00eXJBS3VNa0tyZ3pnTzJRcE9CQVdYdmdiWG9BMDdidDdOODZZOGdNZFUwdAp6Ui9EcmhLTi9JMVdYSk11MU4wQW5UbDJRdEhEb0dCN0UyS0xpdmwybDZJdnRrYWJ4RE55Y2lHbUxOUGlMRklrCllPSHlnMUg3MUJ2NU04NE5TWDc1c2xuOXVNUGUzOVNFVlJoU0ptNHcvT2tXZnA5dGpZRUx3dHphdFhoSWJoS3MKaC9OZU1lOWRBZ01CQUFFQ2dnRUFCYlFuWUdlcFdKYjAydURtSnhLNGx2OFNhL0o1dzJpSldMYjk0dW1SUExRKwpTa1E5c1Zpc0JiQU1JNHc2dWFyNEFBVTh3WGxaTndPekMvM2V6dWNHRXFreEFReXM0VDB4SXRPUkF2WExxVlowCkl1WnpxNW53Si9OeFFzeEtmaWhBZkRLYlRmZjdmcG5MWlV0dlpNbG5LWUhQVExtRlFua3drckwyRE5YdDVVOTYKYXJUUDVOY0x1aDQ2dU93alJUOWhaNytYQi9ubU9LeTV4V3hoNWVMQmJJUTJrS0UvdWViUlVYRGZNdG5tbTh2aApRSG9VM3N5dzlZcE1xRDlWQWppcGlqQXRwbUlwa2w1emRWSE52Q1dCSGk2NjZxKzF6cUpHeFVUODBseHo1N2R1ClRvRFFQc1l0OFFEL3ZjNGkxajd0bDZyRzNQWkJNM05LNVR5ZFYyRnlnUUtCZ1FEYUcycnV3aWxyYUdZRzZNQWwKNEF2WW1BY0hHQWUwQjR0ZmtkdS9QandlRWQyWUF2TjJIQWV1Z1ZUSWg1eFplUlIwNFE4ZVNGenBTaEpwREpkNAp1TEhHcXJ2cmpXL1greEVIc081NnNjNjRiem1weWJWQXBlQnA4NlFGSTk4V3FmZkFyN3FzbzhweFJjNmdTRU1uCk5TcXV1Z2psYU05TlJmcUo4ci82RklmQkxRS0JnUUQ2aGJ0Z2dIdVJoTHhHYWNNaERvOVppcXJlNi9HN1dvZnQKR2FGZmFQM2xZNTNmM0hPdSsyNTZMWDY1ZWFYRTdrbXlQOGRDM2VWL24xT3dvTHdBYm1BZ2pEWVd1N09KdGk4QQpPbG9VNmtnTkVwNWcwOHpVWlBaR3NSMkZMd2VpUkgrQ3ZOdFBZakJydmFIQUtVU2lLa3BKdEpWeFpIdUl6SlpGCjVUZkM2VjNrOFFLQmdEeWp0TlpPKzA4V2hvOVROT0VTNnBnOHBHK1BlY3pPOEN3UkZJU1dYQWFNTnd6bGZTVVEKWS81YmpPUDMrRHRVRTZEdlZkRzRrc1IxeUtxV1NxTFF6dlNLVVpjTEN0YUV3bFplRmQvZEFibDdpdyt1dWdzUQpVMVdCM005bENzaDFWeUdtZWdNM3dyZzlqVlk0NFJyTWlHSnQ3TDFEcDZjM1ZwSDJBUFFac3lpOUFvR0JBTk9pCmpmeWtEYitNNXBDRllEWlkybmpHVURzcUQzZzZyb0Y2R1gxRWNOaU1JeDZ1V1h3RkkvdEsyN2RNTU9JQWUzbDkKcjVPcGFPczdhYlBZMVhsM3hQVTUvYWVPd2NrZ2d1d3FYMWN6NDlKSFhFeG9JSzE4N1NBakY5RWZQYyt6RmhVWAovaDA5MGJIeTdPWXM5cklZRDlIY0lETStzNjJKUjVtY1hsTG1Xay9CQW9HQkFKRVhQV05IWEwra1l6My91R1c3CnRKd0hUQzFlbEJjclcvaHpJMWt4ZEhXem5VaXNTWlcyVnA5b0wwSWNrQXVWQkx6eGUvR1h6OGJRTjZkOWwyZDAKdGtmUmo1TmpDOTUzS2N1cTNSekRmVU40cTcyUlVWTWlFOHVvSTBkVVZpalczN0tVMEhLcm1pbDBocU01eW9iNQpVZlhPQ2Q5SlRRSWx5Y2dNWER6Tm00S3oKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ3FEQ0NBWkNnQXdJQkFnSVJBUHNjMVBRNXBuSDNhNk1GZkdVTXA2WXdEUVlKS29aSWh2Y05BUUVMQlFBdwpFREVPTUF3R0ExVUVDZ3dGVEVGTlJFRXdIaGNOTWpBd01UQXhNREF3TURBeFdoY05Namt4TWpJNU1EQXdNREF4CldqQVBNUTB3Q3dZRFZRUUREQVIwWlhOME1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQTFYQzJUL2FDMEdReHhjbGlVcEh1SUJ2aUNYeC9pMldPS3J0N3Z6YXc2Ujd3ZTR2VmpKTlpVUDJpeDIrRgpyNDh6eXZreHphSkJ1K3RBa0Z3RENWU2huMnJhYTNDa01LTklhRityK2w1VEdHRVVrSnFLZTgwWWlvTzc2dnViCnlqZ2duU2FkWGlFbTNzTml4elg5ZjYyTTJhWkUrWmM5UzMzYzV1TXF3Q3JqSkNxNE00RHRrS1RnUUZsNzRHMTYKQU5PMjdlemZPbVBJREhWTkxjMGZ3NjRTamZ5TlZseVRMdFRkQUowNWRrTFJ3NkJnZXhOaWk0cjVkcGVpTDdaRwptOFF6Y25JaHBpelQ0aXhTSkdEaDhvTlIrOVFiK1RQT0RVbCsrYkpaL2JqRDN0L1VoRlVZVWladU1QenBGbjZmCmJZMkJDOExjMnJWNFNHNFNySWZ6WGpIdlhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBWEQ0L1cKQjBhSW1aWGpQbTRxUnBOazJmUnpjU1g4MGw2TlZaWWxJV3ZYalFxUXdXZnMvSGczZDVzYUpickFmcWVPa1lQdQpjeXJEWFZPdC9RTEVDOTFBSGtjRWJ1R0dPMGNFU2YyOHdUM1UzRnJJb2cxS1VyTURqWFFIb09vZEJpOGdNaVBmCmROcWhMSTdkNDJBTXJKU3dZUTlSUG9vWG9UZ2xDa0d3R291RDhuS0V5MmNHeVMxM3lQcDRseC9TWTR1QkRFU0sKRlErR0ZRTExGQktQZHZNc2x0cHYyQWFMWmR3clF4aFQ2aTU1U1puNStLb3c1TGxYL0RHdUw5UnRPdmZ2T0tzZQpRZ3pOQUg3QkYzbGdvQmJjYk9yZkVQazY1ZEZRN0NXYi91aDZjVmlmSjdxQzkvL0xhdElmb1VQVnJiRXdZL2dRCk5BRXFYclduMGZuYUc0cUEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ2xEQ0NBWHdDQVFBd0RRWUpLb1pJaHZjTkFRRUxCUUF3RURFT01Bd0dBMVVFQ2d3RlRFRk5SRUV3SGhjTgpNakF3TVRBeE1EQXdNREF4V2hjTk1qa3hNakk1TURBd01EQXhXakFRTVE0d0RBWURWUVFLREFWTVFVMUVRVENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTG5xZkJadnJHWmFxZ2s1bXNDUlJwUHoKcC8rNDY0akJrbmxtVEtldE9ja0RUVXE4VjZmSC8yR2ZiNkhqam9ENXBrQ3RENW1TS2thSE5odXhMWHNGZkVmYwpLbG1ubjNacGp5Tk9IRUEvaUFPMkR5RVlhMDh4U2V2TTdXb2piRjdjTmo1L0RZZzdlYjBpMCsvL2JCbGg4bmxPCmdoU1VoQ1RNNVBDb2ZMRFU4c1ZYdVlBaUdVNlV6QnJJQzB2SEVsdERraUpWTHBjQ3RzS2pFWk9za1BkQWM3dTYKL2FBMFA1R29uWjVVa1JEWXBhK2plSlVhYnFXWlFRRWd0bXZqbG1VVWlYd3UwalJuajFuMFQzZlBRRDNnQStMSQp2QUU5dmd2cFk1WFFqNm90cEJ2c1ozTUpKTktjVU1RdTF6T0FOVHpPMThUbEE4S29CTnNCeThaOURRWktYRjhDCkF3RUFBVEFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBZUduL055cUlSSS8wQUdxdkhQOTdLdFE3NlRqNmFjaGIKMzBMSVhXcCtZSFVhTWVBVmpkMlo3alNRcDVtWlpGbCtrMWZiMzM3SWVhR1hvZlZJMjFlSzUyUVgydGVOb0JrQQovVi9PMUh1MzUvK2FpejB4c2RENndXdndvNEZ5MWpsbWFlSmh3ZFFhY0JsREdGQTJqRkp4dUVwYWhmeFp2VXNiCjNqNXpVMFdLVFVDZkVEZ1hGd0J3MTJ4a3UvN1RNZENFYlJzWWFaM3pGVEMyMjZsUWJVRE43d2VxRndTRCt0QjYKUnVoSXhlOCtjRndBc0FXSENsZXJLZ1pucjN0NVFGMDc4cFcyR0h5OENzSjdWM01aVDVsWjQzbFM1TklCOUp6WgpTWXhaL2l6aFJ5aDVxUjczdUFnc0phTDU2QmorY1Fxbm9UcWhMWlZsN0orTTZXaFdLem9qc0E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n\n# Set the remote desktop login password for LAMDA, 6-32 characters.\n# This password will only take effect if a certificate (--certificate)\n# is used. It serves as a backup password to the one generated in\n# the certificate (for easier memorization).\nssl-web-credential=password123\n\n# Set Access-Control-Allow-Origin header of firerpa webui\n# and it's apis to allow you embed firerpa's features\n# see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin\nallow_origin=https://example.com\n\n# Setting this option will also disable the LAMDA Python client API.\n# This option should be set only if the device crashes after starting LAMDA,\n# or if you do not need to use the API. This option will also disable\n# any components that might impact system stability.\ndisable-client-api=false\n\n# Enhanced stealth mode, designed to protect LAMDA and its associated\n# components from detection in cases where there is a risk of damaging\n# itself or third-party components.\n# Use only if necessary (introduced in version 7.65).\nenhanced-stealth-mode=false\n\n# Enhanced automation.\nenhanced-automation=false\n\n# Remote Desktop Touch Fix\n# If there is an offset or no response when you touch the screen on\n# the remote desktop, please set the following configuration:\n# available options are: system | native (default)\ntouch.backend=system\n\n# The switch for get_last_activities, default is false and does not intercept.\n# introduced in 8.20\nintercept-intent=false\n\n# Set the log file location (directory must exists)\nlogfile=/data/local/tmp/server.log\n\n# ---------- OpenVPN Configuration ----------\n# Do not manually write the following configuration. You should use\n# our accompanying OpenVPN server setup solution to set it up and\n# generate this configuration using its built-in commands.\nopenvpn.proto=udp\nopenvpn.cipher=AES-256-GCM\nopenvpn.host=123.123.123.123\nopenvpn.port=1190\nopenvpn.ca=LS0tLS1CRU...\nopenvpn.cert=LS0tLS1CRU...\nopenvpn.key=LS0tLS1CRU...\nopenvpn.tls_encryption=\nopenvpn.tls_key_direction=\nopenvpn.tls_key=\n# ONLY THE FOLLOWING CONFIGURATION ITEMS CAN BE MANUALLY CONFIGURED.\n# Whether to enable global VPN.\nopenvpn.global=false\n# Whether to enable the service true | false\nopenvpn.enable=true\n\n# ---------- Proxy Configuration ----------\n# This configuration is used to make LAMDA automatically connect\n# to the proxy server at startup.\n#\n# Whether to enable the service true | false\ngproxy.enable=true\n# The proxy type can be either http-connect or socks5.\ngproxy.type=http-connect\n# Proxy server address\ngproxy.host=172.1.1.1\n# Proxy server port\ngproxy.port=8080\n# Proxy server login password (leave empty for no authentication)\ngproxy.password=\n# Proxy server login username (leave empty for no authentication)\ngproxy.login=\n\n# ---------- CRONTAB Scheduled Task Service ----------\n# If you do not need scheduled tasks, you can disable this service.\n# Whether to enable the service true | false\ncron.enable=true\n\n# ---------- SSHD Service ----------\n# If you do not need to connect to the device via SSH, you can disable this service.\n# Whether to enable the service true | false\nsshd.enable=true\n\n# ---------- Port Forwarding (frp) Service ----------\n# Whether to enable the service true | false\nfwd.enable=true\n# Port to forward to the remote (0 means randomly allocated)\nfwd.rport=0\n# FRP server address\nfwd.host=123.123.123.123\n# FRP server port\nfwd.port=9911\n# FRP protocol\nfwd.protocol=tcp\n# FRP login authentication\nfwd.token=abc123\n\n# ---------- ADB Service ----------\n# Whether to enable the service true | false\nadb.enable=true\n# Default working directory for built-in ADB (adb shell working directory)\nadb.directory=/data/local/tmp\n# If set to true, the ADB connection will have root privileges,\n# otherwise it will have shell privileges. When this option is set to false,\n# you will be using a native-like adb shell, and you will not be able to use\n# LAMDA's built-in commands. Please note that since ADB does not use TLS connections,\n# traffic may be monitored. For security reasons, when the LAMDA service is\n# started with a certificate, this value will be set to false by default.\n# However, if you specify it in the properties.local file, the configuration\n# in the file will take precedence.\n# You are responsible for ensuring security.\nadb.privileged=true\n\n# ---------- Bridge Proxy Service ----------\n# Whether to enable the service true | false\ntunnel2.enable=true\n# The bridge proxy will require login authentication only if both login and password\n# are set. If either one is left empty, no login authentication will be required.\ntunnel2.login=lamda\n# Login password for the bridge proxy\ntunnel2.password=1234\n# Outbound interface (rmnet|wlan)\n# When the outbound interface is rmnet, the proxy will attempt to forward your requests through mobile data.\n# When the outbound interface is wlan, requests will be forwarded through the wlan interface.\n# If the configuration is empty, the default network will be used to forward the requests.\ntunnel2.iface=rmnet\n\n# ---------- mDNS Broadcast Service ----------\n# Enable or disable true | false\nmdns.enable=false\n# Add TXT metadata for mDNS. When enabled, it will support querying device information\n# such as model, ABI, and device ID using tools like python-zeroconf. Disabled by default.\nmdns.meta=false\n# Set the broadcast domain name using a locally unique ID, default is {DEVICEID-UNIQUE}.lamda.\n# If the name duplicates in the local network, a suffix ID will be automatically added.\nmdns.name=DEVICEID-UNIQUE.lamda\n# Set the broadcast service name, default is lamda, i.e., _lamda._tcp.local.\nmdns.service=lamda\n"
  },
  {
    "path": "scripts/disable_flag_secure.yaml",
    "content": "# EXAMPLE EXTENSION OF FIRERPA (https://github.com/firerpa/lamda)\n# This script is used to disable the android FLAG_SECURE flag.\n# Replace YOUR_APP_ID with the ID (eg. com.android.settings) of your target application.\nenable: true\napplication: \"YOUR_APP_ID\"\nversion: \"N/A\"\nscript: !!binary \"SmF2YS5wZXJmb3JtKGZ1bmN0aW9uICgpIHsKICAgICAgICB2YXIgRkxBR19TRUNVUkUgPSAweDIwMDA7CiAgICAgICAgdmFyIFdpbmRvdyA9IEphdmEudXNlKCJhbmRyb2lkLnZpZXcuV2luZG93Iik7CiAgICAgICAgdmFyIHNldEZsYWdzID0gV2luZG93LnNldEZsYWdzOwoKICAgICAgICBzZXRGbGFncy5pbXBsZW1lbnRhdGlvbiA9IGZ1bmN0aW9uIChmbGFncywgbWFzaykgewogICAgICAgICAgICBjb25zb2xlLmxvZygiRGlzYWJsaW5nIEZMQUdfU0VDVVJFLi4uIik7CiAgICAgICAgICAgIGZsYWdzICY9IH5GTEFHX1NFQ1VSRTsKICAgICAgICAgICAgc2V0RmxhZ3MuY2FsbCh0aGlzLCBmbGFncywgbWFzayk7CiAgICAgICAgfTsKfSk7Cg==\"\nstandup: 0\nspawn: false"
  },
  {
    "path": "scripts/disable_ssl_pinning_simple.yaml",
    "content": "# EXAMPLE EXTENSION OF FIRERPA (https://github.com/firerpa/lamda)\n# This script is used to disable simple ssl pinning (checkServerTrusted).\n# Replace YOUR_APP_ID with the ID (eg. com.android.settings) of your target application.\nenable: true\napplication: \"YOUR_APP_ID\"\nversion: \"N/A\"\nscript: !!binary \"SmF2YS5wZXJmb3JtKGZ1bmN0aW9uKCkgewpKYXZhLnVzZSgiYW5kcm9pZC5zZWN1cml0eS5uZXQuY29uZmlnLlJvb3RUcnVzdE1hbmFnZXIiKS5jaGVja1NlcnZlclRydXN0ZWQub3ZlcmxvYWQoIltMamF2YS5zZWN1cml0eS5jZXJ0Llg1MDlDZXJ0aWZpY2F0ZTsiLCAiamF2YS5sYW5nLlN0cmluZyIsICJqYXZhLm5ldC5Tb2NrZXQiKS5pbXBsZW1lbnRhdGlvbiA9IGZ1bmN0aW9uIChhLCBiLCBjKSB7CiAgICAgICAgcmV0dXJuCn0KfSkK\"\nstandup: 0\nspawn: false"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/python3\n# Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.\nimport setuptools\n\nexec(open(\"lamda/__init__.py\", \"rt\").read())\n\nsetuptools.setup(\n    name            = \"lamda\",\n    version         = \"{}\".format(__version__),\n    description     = \"Android reverse engineering & automation framework (Client API)\",\n    url             = \"https://github.com/firerpa/lamda\",\n    author          = \"rev1si0n\",\n    python_requires = \">=3.6,<3.14\",\n    zip_safe        = False,\n    extras_require  = {\n        \"full\": [\"frida>=17.0.0,<18.0.0\"],\n        \":sys_platform == \\\"win32\\\"\": [\n            \"pyreadline==2.1\",\n        ],\n    },\n    install_requires= [\n        \"grpcio-tools>=1.35.0,<=1.74.0\",\n        \"grpc-interceptor>=0.13.0,<=0.15.4\",\n        \"grpcio>=1.35.0,<=1.74.0\",\n        \"cryptography>=35.0.0\",\n        \"msgpack>=1.0.0\",\n        \"asn1crypto>=1.0.0,<2\",\n        \"pem==23.1.0\",\n    ],\n    classifiers = [\n        \"Environment :: Console\",\n        \"Intended Audience :: Developers\",\n        \"Development Status :: 5 - Production/Stable\",\n        \"Intended Audience :: Information Technology\",\n        \"Intended Audience :: Science/Research\",\n        \"Programming Language :: Python :: 3\",\n        \"Operating System :: Android\",\n        \"Topic :: Security\",\n    ],\n    package_data    = {\n        \"lamda\": [\"*.py\", \"*.proto\"],\n        \"lamda.google.protobuf.compiler\": [\"*.proto\"],\n        \"lamda.google.protobuf\": [\"*.proto\"],\n        \"lamda.rpc\": [\"*.proto\"],\n    },\n    packages        = [\n        \"lamda.google.protobuf.compiler\",\n        \"lamda.google.protobuf\",\n        \"lamda.rpc\",\n        \"lamda\",\n    ],\n)\n"
  },
  {
    "path": "tools/README.md",
    "content": "Document: https://device-farm.com/doc/"
  },
  {
    "path": "tools/adb_pubkey.py",
    "content": "#!/usr/bin/env python3\n#encoding=utf-8\nimport os\nimport argparse\n\nfrom os.path import isfile\nfrom lamda.client import *\n\ncertfile = os.environ.get(\"CERTIFICATE\", None)\nport = int(os.environ.get(\"PORT\", 65000))\n\nandroid_path = os.path.join(\"~\", \".android\")\nabs_android_path = os.path.expanduser(android_path)\nf = \"adbkey.pub\"\n\nargp = argparse.ArgumentParser()\n\nargp.add_argument(\"action\", nargs=1)\nargp.add_argument(\"device\", nargs=1)\n\nargs = argp.parse_args()\n\nd = Device(args.device[0], port=port,\n                certificate=certfile)\ncmd = args.action[0]\n\nos.chdir(abs_android_path)\n\n# try generate pubkey\npubkey = os.popen(\"adb pubkey adbkey\").read()\nopen(\"adbkey.lamda\", \"w\").write(pubkey)\n\nf = (\"adbkey.lamda\", f)[isfile(f)]\n\ncall = getattr(d, \"%s_adb_pubkey\" % cmd)\nexit(not call(f))"
  },
  {
    "path": "tools/cert.py",
    "content": "#!/usr/bin/env python3\nimport os\nimport sys\nimport random\nimport datetime\n\nfrom hashlib import sha256\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography.hazmat.primitives import hashes, serialization\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.x509 import (\n    Name,\n    NameAttribute,\n    CertificateBuilder,\n    CertificateSigningRequestBuilder,\n    DNSName,\n    SubjectAlternativeName,\n    load_pem_x509_certificate,\n    BasicConstraints,\n    KeyUsage,\n)\nfrom cryptography.x509.oid import NameOID\n\nCN = \"lamda\"\nif len(sys.argv) == 2:\n    CN = sys.argv[1]\n\nif os.path.isfile(\"root.key\"):\n    with open(\"root.key\", \"rb\") as f:\n        rk = serialization.load_pem_private_key(\n            f.read(), password=None, backend=default_backend()\n        )\nelse:\n    rk = rsa.generate_private_key(\n        public_exponent=65537, key_size=2048, backend=default_backend()\n    )\n    with open(\"root.key\", \"wb\") as f:\n        f.write(\n            rk.private_bytes(\n                encoding=serialization.Encoding.PEM,\n                format=serialization.PrivateFormat.TraditionalOpenSSL,\n                encryption_algorithm=serialization.NoEncryption(),\n            )\n        )\n\nif os.path.isfile(\"root.crt\"):\n    with open(\"root.crt\", \"rb\") as f:\n        root = load_pem_x509_certificate(f.read(), default_backend())\nelse:\n    subject = issuer = Name(\n        [\n            NameAttribute(NameOID.ORGANIZATION_NAME, \"LAMDA\"),\n            NameAttribute(NameOID.COMMON_NAME, \"FireRPA LAMDA Root Trust\"),\n        ]\n    )\n    root = (\n        CertificateBuilder()\n        .subject_name(subject)\n        .issuer_name(issuer)\n        .not_valid_before(datetime.datetime.utcnow())\n        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=3650))\n        .public_key(rk.public_key())\n        .serial_number(random.randint(1, 2**128))\n        .add_extension(BasicConstraints(ca=True, path_length=None), critical=True)\n        .sign(rk, hashes.SHA256(), default_backend())\n    )\n    with open(\"root.crt\", \"wb\") as f:\n        f.write(root.public_bytes(serialization.Encoding.PEM))\n\n\nif not os.path.isfile(f\"{CN}.pem\"):\n    pk = rsa.generate_private_key(\n        public_exponent=65537, key_size=2048, backend=default_backend()\n    )\n    csr = (\n        CertificateSigningRequestBuilder()\n        .subject_name(Name([NameAttribute(NameOID.COMMON_NAME, CN)]))\n        .sign(pk, hashes.SHA256(), default_backend())\n    )\n\n    cert = (\n        CertificateBuilder()\n        .subject_name(csr.subject)\n        .issuer_name(root.subject)\n        .not_valid_before(datetime.datetime.utcnow())\n        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=3650))\n        .public_key(csr.public_key())\n        .serial_number(random.randint(1, 2**128))\n        .sign(rk, hashes.SHA256(), default_backend())\n    )\n\n    with open(f\"{CN}.pem\", \"wb\") as output:\n        pem_private_key = pk.private_bytes(\n            encoding=serialization.Encoding.PEM,\n            format=serialization.PrivateFormat.TraditionalOpenSSL,\n            encryption_algorithm=serialization.NoEncryption(),\n        )\n        pem_cert = cert.public_bytes(serialization.Encoding.PEM)\n        pem_root = root.public_bytes(serialization.Encoding.PEM)\n\n        d = pk.private_numbers().d\n        pd = d.to_bytes((d.bit_length() + 7) // 8, \"little\")\n        cred = sha256(pd).hexdigest()[::3]\n\n        header = f\"LAMDA SSL CERTIFICATE (CN={CN},PASSWD={cred})\\n\"\n        output.write(header.encode())\n        output.write(pem_private_key.strip())\n        output.write(b\"\\n\")\n        output.write(pem_cert.strip())\n        output.write(b\"\\n\")\n        output.write(pem_root.strip())\n        output.write(b\"\\n\")\n"
  },
  {
    "path": "tools/debugimage.py",
    "content": "#!/usr/bin/env python3\nimport os\nimport argparse\n\nfrom PIL import Image, ImageDraw\nfrom lamda.client import *\n\ncert = os.environ.get(\"CERTIFICATE\", None)\nport = int(os.environ.get(\"PORT\", 65000))\n\nparser = argparse.ArgumentParser()\nparser.add_argument(\"-d\", type=str, dest=\"device\",\n                        help=\"service ip address\", required=True)\nparser.add_argument(\"-p\", \"--port\", type=str,\n                        default=port, help=\"service port\")\nparser.add_argument(\"-f\", \"--method\", type=str,\n                        help=\"find method\", default=\"0\")\nparser.add_argument(\"-i\", \"--image\", type=argparse.FileType(\"rb\"),\n                        help=\"find image path\", required=True)\nparser.add_argument(\"-a\", \"--area\", type=str,\n                        help=\"area\", default=\"0\")\nparser.add_argument(\"-s\", \"--scale\", type=float,\n                        help=\"scale\", default=1.0)\nparser.add_argument(\"-t\", \"--threshold\", type=float,\n                        help=\"threshold\", default=0)\nparser.add_argument(\"-m\", \"--distance\", type=int,\n                        help=\"max distance\", default=0)\nparser.add_argument(\"-cert\", type=str, default=cert,\n                                help=\"ssl cert\")\nargs = parser.parse_args()\n\n\nd = Device(args.device, port=args.port,\n                certificate=args.cert)\nimage = Image.open(d.screenshot(95))\n\ndraw = ImageDraw.Draw(image)\nfor r in d.find_similar_image(args.image.read(),\n                                area=eval(args.area),\n                                method=eval(args.method),\n                                distance=args.distance,\n                                threshold=args.threshold,\n                                scale=args.scale):\n    p1 = r.corner(\"top-left\")\n    p2 = r.corner(\"bottom-right\")\n    draw.rectangle(((p1.x, p1.y), (p2.x, p2.y)),\n                    outline=\"#00ff00\", width=3)\nimage.show()"
  },
  {
    "path": "tools/discover.py",
    "content": "#!/usr/bin/env python3\nimport os\nimport struct\nfrom socket import *\n\nfrom lamda import __version__\nfrom lamda.client import load_proto\n\nprotos, services = load_proto(\"bcast.proto\")\n\nBcastHeader = protos.BcastHeader\nBcastDiscoverInfo = protos.BcastDiscoverInfo\nBcastResponse = protos.BcastResponse\nBcastRequest = protos.BcastRequest\n\nport = int(os.environ.get(\"PORT\", 65000))\n\n\ndef BcastCallMethod(method):\n    req = BcastRequest(method=method)\n    # ASTBCAST + length (body,4byte) + body + feeedeed\n    hdr = BcastHeader(magic=0x54534143,\n                            version=__version__)\n    req.header.CopyFrom(hdr)\n    body = req.SerializeToString()\n    buffer = []\n    r = struct.pack(\"QH\", 0x5453414342545341, len(body))\n    buffer.append(r)\n    r = struct.pack(\"{}s\".format(len(body)), body)\n    buffer.append(r)\n    r = struct.pack(\"I\", 0xeedeeefe)\n    buffer.append(r)\n    r = bytes().join(buffer)\n    return r\n\n\nsock = socket(AF_INET, SOCK_DGRAM)\nsock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)\nmessage = BcastCallMethod(\"DISCOVER\")\nsock.sendto(message, (\"255.255.255.255\", port))\nsock.settimeout(3.0)\n\nwhile True:\n    try:\n        data, remote = sock.recvfrom(4096)\n    except timeout:\n        break\n    fmt = \"<QH{}sI\".format(len(data) - 14)\n    magic, size, body, _magic = struct.unpack(fmt, data)\n    res = BcastResponse.FromString(body)\n    print (remote[0], end=\"\\t\")\n    print (res.discoverinfo.ID, end=\"\\t\")\n    print (res.discoverinfo.device, end=\"\\t\")\n    print (res.discoverinfo.abi, end=\"\\t\")\n    print (res.discoverinfo.sdk, end=\"\\t\")\n    print (res.discoverinfo.version)\n"
  },
  {
    "path": "tools/firerpa.yml",
    "content": "version: \"3\"\n# docker run -d --name firerpa --privileged -v ~/firerpa:/user -e LICENSE= -e DOMAIN=firerpa.local -e WEB_PORT=8000 -e API_PORT=65000 -p 8000:8000 -p 65000:65000 --restart always registry.device-farm.com/firerpa/server:latest\nservices:\n        firerpa:\n                container_name: firerpa\n                image: registry.device-farm.com/firerpa/server:latest\n                privileged: true\n                volumes:\n                        - ~/firerpa:/user\n                environment:\n                        - LICENSE=\n                        - DOMAIN=firerpa.local\n                        - WEB_PORT=8000\n                        - API_PORT=65000\n                ports:\n                        - 8000:8000\n                        - 65000:65000\n                restart: always"
  },
  {
    "path": "tools/frida_script_generate.py",
    "content": "#!/usr/bin/env python3\n# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\n#encoding=utf-8\nimport os\nimport sys\nimport importlib.util\n\n\ndef embed_bridge(source, lang):\n    # https://github.com/frida/frida/issues/3460#issuecomment-3066016776\n    tools_dir = os.path.dirname(importlib.util.find_spec(\"frida_tools\").origin)\n    fmt = '(function() { %s; Object.defineProperty(globalThis, \"%s\", { value: bridge }); })(); %s'\n    bridge = os.path.join(tools_dir, \"bridges\", f\"{lang.lower()}.js\")\n    with open(bridge, \"r\", encoding=\"utf-8\") as f:\n        return fmt % (f.read(), lang, source)\n\n\nif __name__ == \"__main__\":\n    script = sys.argv[1]\n    source = embed_bridge(open(script, \"rt\", encoding=\"utf-8\").read(),\n                                                            \"Java\")\n    open(\"%s-embedded\" % script, \"w\").write(source)"
  },
  {
    "path": "tools/fridarpc.py",
    "content": "#!/usr/bin/env python3\nif __name__ == \"__main__\":\n    import os\n    import time\n    import argparse\n    from lamda.client import *\n\n    cert = os.environ.get(\"CERTIFICATE\", None)\n    port = int(os.environ.get(\"PORT\", 65000))\n\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"-d\", type=str, dest=\"device\",\n                            help=\"service ip address\", required=True)\n    parser.add_argument(\"-a\", type=str, dest=\"package\",\n                            help=\"target application Id\", required=True)\n    parser.add_argument(\"-p\", type=str, dest=\"port\", default=port,\n                            help=\"service port\")\n    parser.add_argument(\"-f\", type=argparse.FileType(\"r\"), dest=\"script\",\n                            help=\"frida script\", required=True)\n    parser.add_argument(\"-delay\", type=int, dest=\"delay\", default=5,\n                            help=\"attach after delay\")\n    parser.add_argument(\"-cert\", type=str, default=cert,\n                                   help=\"ssl cert\")\n    args = parser.parse_args()\n\n    d = Device(args.device, port=args.port,\n                    certificate=args.cert)\n\n    app = d.application(args.package)\n    app.detach_script()\n    app.attach_script(args.script.read(),\n                      standup=args.delay)\n    exit (0)"
  },
  {
    "path": "tools/globalmitm/DNS2SOCKS.c",
    "content": "// \"stdafx.h\"\n#ifndef _STDAFX_H\n#define _STDAFX_H\n\n#ifdef _WIN32\n\n//Windows specific includes\n#define WINVER 0x501\n#define _CRT_SECURE_NO_WARNINGS\n#define _WINSOCK_DEPRECATED_NO_WARNINGS\n#include <WinSock2.h>\n#include <WS2tcpip.h>\n#include <stdio.h>\n#include <time.h>\n#include <stdint.h>\n\n#else //#ifdef _WIN32\n\n//other OS includes and some tricks to fake Win32 API\n#include <pthread.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#include <netdb.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <stdint.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <time.h>\ntypedef int SOCKET;\n#define SOCKET_ERROR (-1)\n#define closesocket(hSock) close(hSock)\n#define WSAGetLastError() errno\n#define CRITICAL_SECTION pthread_mutex_t\n#define InitializeCriticalSection(criticalSection) pthread_mutex_init(criticalSection, NULL)\n#define EnterCriticalSection(criticalSection) pthread_mutex_lock(criticalSection)\n#define LeaveCriticalSection(criticalSection) pthread_mutex_unlock(criticalSection)\n\n#endif //#ifdef _WIN32\n\n#endif //#ifndef _STDAFX_H\n\n// end \"stdafx.h\"\n\n//helpful URLs:\n// http://www.networksorcery.com/enp/protocol/dns.htm\n// http://www.freesoft.org/CIE/RFC/1035/43.htm\n// https://tools.ietf.org/html/rfc2671\n// https://tools.ietf.org/html/rfc3225\n// https://tools.ietf.org/html/rfc7871\n\n//application name and version\n#define APP_NAME \"DNS2SOCKS V2.1\"\n//first output line in console\n#define APP_STRING \"\\n\" APP_NAME \" (free software, use parameter /? to display help)\\n\"\n//log file header line\n#define LOG_HEADER APP_NAME \" log opened\"\n//date/time format string for ISO 8601 format, but leave away the T delimiter as it's bad to read\n#define DATE_TIME_FORMAT \"%u-%02u-%02u %02u:%02u:%02u   \"\n//127.0.0.1 for default SOCKS5 server\n#define DEFAULT_SOCKS_SERVER \"127.0.0.1\"\n//9050 for default SOCKS5 port\n#define DEFAULT_SOCKS_PORT \"9050\"\n//46.182.19.48 for default DNS server supporting TCP (Digitalcourage e.V.)\n#define DEFAULT_DNS_SERVER \"46.182.19.48\"\n//127.0.0.1 for local IP address for listening\n#define DEFAULT_LISTEN_IP \"127.0.0.1\"\n//53 for default DNS port\n#define DEFAULT_DNS_PORT \"53\"\n\n//defines for OutputToLog (bits)\n#define OUTPUT_LINE_BREAK (1)\n#define OUTPUT_DATE_TIME (2)\n#define OUTPUT_CONSOLE (4)\n#define OUTPUT_ALL (OUTPUT_LINE_BREAK|OUTPUT_DATE_TIME|OUTPUT_CONSOLE)\n\n//on some systems a failing send command raises a signal; we want to disable that by passing MSG_NOSIGNAL\n//some systems (Windows) don't define that -> define it to 0\n#ifndef MSG_NOSIGNAL\n\t#define MSG_NOSIGNAL (0)\n#endif\n\n\nunion UClient\n{\n\tstruct sockaddr_storage sAddr;\t//address of requesting client for UDP\n\tSOCKET hSock;\t\t\t\t\t//socket handle for TCP\n};\n\n//entry for DNS request and answer (cache entry)\nstruct SEntry\n{\n\tstruct SEntry* psNext;\t\t\t//next list entry or NULL\n\tuint16_t* u16aAnswer;\t\t\t//pointer to answer or NULL\n\ttime_t iTime;\t\t\t\t\t//time when the answer was deliviered last time\n\tunion UClient client;\t\t\t//information on how to send response back to client\n\tunsigned char uAddrLen;\t\t\t//length of used part of sAddr for UDP, sizeof(SOCKET) for TCP\n\tuint16_t u16aRequest[1];\t\t//extended dynamically at malloc for \"struct SEntry\" (use \"uint16_t\" to ensure according alignment, first element contains length in big endian format)\n};\n\nstatic struct SEntry* g_psFirst=NULL;\t\t\t//list of DNS requests and answers (cache)\nstatic unsigned int g_uCacheCount=0;\t\t\t//amout of entries in list g_psFirst\nstatic int g_bCacheEnabled=1;\t\t\t\t\t//!=0 when cache is enabled\nstatic struct sockaddr_storage g_sDnsSrvAddr;\t//DNS server supporting TCP\nstatic struct sockaddr_storage g_sSocksAddr;\t//SOCKS5 server\nstatic CRITICAL_SECTION g_sCritSect;\t\t\t//to protect the list g_psFirst and g_uCacheCount\nstatic SOCKET g_hSockUdp;\t\t\t\t\t\t//UDP socket\nstatic unsigned char* g_uaUsrPwd=NULL;\t\t\t//authentication package for SOCKS\nstatic int g_iUsrPwdLen;\t\t\t\t\t\t//length of g_caUsrPwd\nstatic int g_iHttpProxyConnectLen=0;\t\t\t//length of CONNECT command in g_caHttpProxyConnect\nstatic char g_caHttpProxyConnect[300];\t\t\t//CONNECT command in case of using HTTP proxy\nstatic uint8_t g_u8aEcsOption[8+16];\t\t\t//EDNS Client Subnet EDNS0 option (maximum necessary bytes reserved, actual amount in g_iEcsOptionLen)\nstatic int g_iEcsOptionLen=0;\t\t\t\t\t//actual amount bytes used of g_u8aEcsOption\nstatic int g_bForceEcs=0;\t\t\t\t\t\t//!=0 to replace existing EDNS Client Subnet option by our one\n\n//OS specific functionality\n#ifdef _WIN32\n\n//for Windows we can create an own console window, so here we use some OS specific functions;\n//also for the file access we use the WIN32 API - we could use the C API as we do for the other OSes\n//but as we need to take special care about the line break (\\r\\n) anyway and WIN32 should be a little\n//bit faster, we use WIN32\n\nstatic HANDLE g_hConsole=NULL;\t\t\t\t\t//handle for console output\nstatic HANDLE g_hLogFile=INVALID_HANDLE_VALUE;\t//handle for log file\n\n//passes a string and its length to a function\n#define STRING_AND_LEN(szString) szString, sizeof(szString)-1\n\nstatic char* GetSysError(int iErrNo)\n{\n\tchar* szBuffer;\n\tsize_t uLen;\n\n\tif(!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, NULL, iErrNo, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&szBuffer, 0, NULL))\n\t{\n\t\tszBuffer=(char*)LocalAlloc(LMEM_FIXED, 14);\n\t\tmemcpy(szBuffer, \"Unknown error\", 14);\n\t}\n\telse\n\t{\n\t\t//remove line breaks at the end\n\t\tuLen=strlen(szBuffer);\n\t\twhile(uLen>0 && (szBuffer[uLen-1]=='\\n' || szBuffer[uLen-1]=='\\r'))\n\t\t\tszBuffer[--uLen]='\\0';\n\t}\n\treturn szBuffer;\n}\n\nstatic void FreeSysError(char* szString)\n{\n\tLocalFree(szString);\n}\n\nstatic void OutputToLog(unsigned int uOutputSettingBits, const char* szFormatString, ...)\n{\n\tva_list pArgs;\n\tint iLenPrefix;\n\tint iLen;\n\tDWORD uDummy;\n\tchar szBuf[1024];\n\n\t//nothing to do?\n\tif((!g_hConsole || !(uOutputSettingBits&OUTPUT_CONSOLE)) && g_hLogFile==INVALID_HANDLE_VALUE)\n\t\treturn;\n\t//add line break?\n\tif(uOutputSettingBits&OUTPUT_LINE_BREAK)\n\t{\n\t\tszBuf[0]='\\r';\n\t\tszBuf[1]='\\n';\n\t\tiLenPrefix=2;\n\t}\n\telse\n\t\tiLenPrefix=0;\n\tif(uOutputSettingBits&OUTPUT_DATE_TIME)\n\t{\n\t\tSYSTEMTIME sTime;\n\n\t\t//add date/time string\n\t\tGetLocalTime(&sTime);\n\t\tiLen=_snprintf_s(szBuf+iLenPrefix, ARRAYSIZE(szBuf)-iLenPrefix, _TRUNCATE, DATE_TIME_FORMAT, sTime.wYear, sTime.wMonth, sTime.wDay, sTime.wHour, sTime.wMinute, sTime.wSecond);\n\t\tif(iLen<0)\t//error?\n\t\t\treturn;\n\t\tiLenPrefix+=iLen;\n\t}\n\t//add log string\n\tva_start(pArgs, szFormatString);\n\tiLen=_vsnprintf_s(szBuf+iLenPrefix, ARRAYSIZE(szBuf)-iLenPrefix, _TRUNCATE, szFormatString, pArgs);\n\tif(iLen<0)\t//error?\n\t\treturn;\n\tiLen+=iLenPrefix;\n\t//output\n\tif(g_hConsole && (uOutputSettingBits&OUTPUT_CONSOLE))\n\t{\n\t\twchar_t wcBuf[1024];\n\n\t\t//convert to wide char to avoid trouble with special characters\n\t\tiLen=MultiByteToWideChar(CP_ACP, 0, szBuf, iLen, wcBuf, ARRAYSIZE(wcBuf));\n\t\tif(iLen)\n\t\t\tWriteConsoleW(g_hConsole, wcBuf, iLen, &uDummy, NULL);\n\t}\n\tif(g_hLogFile)\n\t\tWriteFile(g_hLogFile, szBuf, iLen, &uDummy, NULL);\n}\n\nstatic void OpenLogFile(const char* szFilePath, int bAppend)\n{\n\tg_hLogFile=CreateFile(szFilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, bAppend?OPEN_ALWAYS:CREATE_ALWAYS, 0, NULL);\n\tif(g_hLogFile==INVALID_HANDLE_VALUE)\n\t{\n\t\tchar* szErrMsg=GetSysError(GetLastError());\n\n\t\tOutputToLog(OUTPUT_ALL, \"Failed to open log file \\\"%s\\\": %s\", szFilePath, szErrMsg);\n\t\tFreeSysError(szErrMsg);\n\t}\n\telse\n\t{\n\t\t//append and no new file was created?\n\t\tif(bAppend && GetLastError()==ERROR_ALREADY_EXISTS)\n\t\t{\n\t\t\tDWORD uDummy;\n\n\t\t\t//go to end of file for appending\n\t\t\tSetFilePointer(g_hLogFile, 0, NULL, FILE_END);\n\t\t\t//add line breaks for appending\n\t\t\tWriteFile(g_hLogFile, STRING_AND_LEN(\"\\r\\n\\r\\n\"), &uDummy, NULL);\n\t\t}\n\t\t//output header string in log\n\t\tOutputToLog(OUTPUT_DATE_TIME, LOG_HEADER);\n\t}\n}\n\nstatic void OpenConsole()\n{\n\tif(g_hConsole)\t//already openend?\n\t\treturn;\n\tAllocConsole();\n\tg_hConsole=GetStdHandle(STD_OUTPUT_HANDLE);\n\tif(g_hConsole)\n\t{\n\t\tDWORD uDummy;\n\n\t\tWriteConsole(g_hConsole, STRING_AND_LEN(APP_STRING), &uDummy, NULL);\n\t}\n}\n\nstatic void OutputFatal(const char* szFormatString, ...)\n{\n\tHANDLE hReadConsole;\n\tva_list pArgs;\n\tint iLen;\n\tDWORD uDummy;\n\tINPUT_RECORD sRecord;\n\tchar szBuf[4096];\n\n\tOpenConsole();\n\tif(!g_hConsole)\t//console available?\n\t\treturn;\n\tva_start(pArgs, szFormatString);\n\tiLen=_vsnprintf_s(szBuf, ARRAYSIZE(szBuf), _TRUNCATE, szFormatString, pArgs);\n\tif(iLen<0)\t//error?\n\t\treturn;\n\t//output\n\tWriteConsole(g_hConsole, szBuf, iLen, &uDummy, NULL);\n\tWriteConsole(g_hConsole, STRING_AND_LEN(\"\\nPress any key to close the application...\"), &uDummy, NULL);\n\thReadConsole=GetStdHandle(STD_INPUT_HANDLE);\n\t//wait for key\n\twhile(ReadConsoleInput(hReadConsole, &sRecord, 1, &uDummy) && (sRecord.EventType!=KEY_EVENT || !sRecord.Event.KeyEvent.bKeyDown))\n\t\t;\n}\n\n//thread handling for Windows\n#define THREAD_FUNCTION(threadFunction, pParam) static DWORD __stdcall threadFunction(LPVOID pParam)\n\nint ThreadCreate(LPTHREAD_START_ROUTINE pThreadFunction, void* pParam)\n{\n\tDWORD uId;\n\tHANDLE hThread=CreateThread(NULL, 0, pThreadFunction, pParam, 0, &uId);\n\n\tif(!hThread)\n\t{\n\t\tchar* szErrMsg=GetSysError(GetLastError());\n\n\t\tOutputToLog(OUTPUT_ALL, \"Creating new thread has failed: %s\", szErrMsg);\n\t\tFreeSysError(szErrMsg);\n\t\treturn 0;\t//error\n\t}\n\tCloseHandle(hThread);\n\treturn 1;\t//o.k.\n}\n\n#else //#ifdef _WIN32\n\n//on other OS create no own console window\nstatic int g_bConsole=0;\t\t//console output enabled?\nstatic FILE* g_pLogFile=NULL;\t//log file pointer\n\n#define GetSysError(iErrNo) strerror(iErrNo)\n#define FreeSysError(szString)\t//nothing to do\n\nstatic void OutputToLog(unsigned int uOutputSettingBits, const char* szFormatString, ...)\n{\n\tva_list pArgs;\n\tint iLenPrefix;\n\tint iLen;\n\tchar szBuf[4096];\n\n\t//nothing to do?\n\tif((!g_bConsole || !(uOutputSettingBits&OUTPUT_CONSOLE)) && !g_pLogFile)\n\t\treturn;\n\t//add line break?\n\tif(uOutputSettingBits&OUTPUT_LINE_BREAK)\n\t{\n\t\tszBuf[0]='\\n';\n\t\tiLenPrefix=1;\n\t}\n\telse\n\t\tiLenPrefix=0;\n\tif(uOutputSettingBits&OUTPUT_DATE_TIME)\n\t{\n\t\t//add date/time string\n\t\ttime_t iTime;\n\t\tstruct tm* psTime;\n\n\t\tif(time(&iTime)==(time_t)-1)\n\t\t\treturn;\t//error\n\t\tpsTime=localtime(&iTime);\n\t\tif(!psTime)\t//error?\n\t\t\treturn;\n\t\tiLen=snprintf(szBuf+iLenPrefix, sizeof(szBuf)-iLenPrefix, DATE_TIME_FORMAT, psTime->tm_year+1900, psTime->tm_mon+1, psTime->tm_mday, psTime->tm_hour, psTime->tm_min, psTime->tm_sec);\n\t\tif(iLen<0)\t//error?\n\t\t\treturn;\n\t\tiLenPrefix+=iLen;\n\t}\n\t//add log string\n\tva_start(pArgs, szFormatString);\n\tiLen=vsnprintf(szBuf+iLenPrefix, sizeof(szBuf)-iLenPrefix, szFormatString, pArgs);\n\tva_end(pArgs);\n\tif(iLen<0)\t//error?\n\t\treturn;\n\tiLen+=iLenPrefix;\n\tif(iLen>=sizeof(szBuf))\t//truncated? -> make sure to add \\0\n\t{\n\t\tszBuf[sizeof(szBuf)-1]='\\0';\n\t\tiLen=sizeof(szBuf)-1;\n\t}\n\t//output\n\tif(g_bConsole && (uOutputSettingBits&OUTPUT_CONSOLE))\n\t\tfputs(szBuf, stdout);\n\tif(g_pLogFile)\n\t\tfwrite(szBuf, 1, iLen, g_pLogFile);\n}\n\nstatic void OpenLogFile(const char* szFilePath, int bAppend)\n{\n\tg_pLogFile=fopen(szFilePath, bAppend?\"ab\":\"wb\");\n\tif(!g_pLogFile)\n\t\tOutputToLog(OUTPUT_ALL, \"Failed to open log file \\\"%s\\\": %s\", szFilePath, strerror(errno));\n\telse\n\t{\n\t\t//append and file is not empty?\n\t\tif(bAppend && ftell(g_pLogFile))\n\t\t{\n\t\t\t//add line breaks for appending\n\t\t\tfputs(\"\\n\\n\", g_pLogFile);\n\t\t}\n\t\tsetbuf(g_pLogFile, NULL);\t//disable buffering (no newline needed for actual output)\n\t\t//output header string in log\n\t\tOutputToLog(OUTPUT_DATE_TIME, LOG_HEADER);\n\t}\n}\n\nstatic void OpenConsole()\n{\n\tif(g_bConsole)\t//already openend?\n\t\treturn;\n\tg_bConsole=1;\n\tsetbuf(stdout, NULL);\t//disable buffering (no newline needed for actual output)\n\tprintf(APP_STRING);\n}\n\n//for OutputFatal we can just use a macro to prefix the parameters\n#define OutputFatal\tOpenConsole(); printf\n\n//thread handling via pthread\n#define THREAD_FUNCTION(threadFunction, pParam) static void* threadFunction(void* pParam)\n\nint ThreadCreate(void* (*pThreadFunction)(void*), void* pParam)\n{\n\tpthread_t hThread;\n\tint iErrNo=pthread_create(&hThread, NULL, pThreadFunction, pParam);\n\n\tif(iErrNo)\n\t{\n\t\tOutputToLog(OUTPUT_ALL, \"Creating new thread has failed: error code %d\", iErrNo);\n\t\treturn 0;\t//error\n\t}\n\tpthread_detach(hThread);\n\treturn 1;\t//o.k.\n}\n\n#endif //#ifdef _WIN32\n\n\n//returns the internal length of a socket address (IPv4 / IPv6)\nstatic unsigned int GetAddrLen(const struct sockaddr_storage* psAddr)\n{\n\tif(psAddr->ss_family==AF_INET)\n\t\treturn sizeof(struct sockaddr_in);\n\treturn sizeof(struct sockaddr_in6);\n}\n\n//sends UDP answer\nstatic void SendAnswer(struct SEntry* psEntry)\n{\n\t//ignore error here\n\t//UDP?\n\tif(psEntry->uAddrLen!=sizeof(SOCKET))\n\t\t//+2 because DNS on UDP doesn't include the length\n\t\tsendto(g_hSockUdp, (char*)(psEntry->u16aAnswer+1), ntohs(*psEntry->u16aAnswer), MSG_NOSIGNAL, (struct sockaddr*)&psEntry->client.sAddr, psEntry->uAddrLen);\n\telse\n\t{\n\t\t//TCP\n\t\tsend(*(SOCKET*)&psEntry->client.hSock, (char*)psEntry->u16aAnswer, ntohs(*psEntry->u16aAnswer)+2, MSG_NOSIGNAL);\n\t\tclosesocket(psEntry->client.hSock);\n\t}\n}\n\n//searches for an entry in the cache list and removes it - also closes a socket and outputs an error message\nstatic void RemoveEntry(struct SEntry* psEntry, SOCKET hSock, int bUseCriticalSection)\n{\n\tstruct SEntry** ppsPrev;\n\tstruct SEntry* psEntry2;\n\n\t//close the socket created by the caller\n\tif(hSock!=SOCKET_ERROR)\n\t\tclosesocket(hSock);\n\tif(bUseCriticalSection)\n\t\tEnterCriticalSection(&g_sCritSect);\n\tppsPrev=&g_psFirst;\n\tfor(psEntry2=g_psFirst; psEntry2; psEntry2=psEntry2->psNext)\n\t{\n\t\t//found entry?\n\t\tif(psEntry==psEntry2)\n\t\t{\n\t\t\t//remove entry from list\n\t\t\t*ppsPrev=psEntry->psNext;\n\t\t\t--g_uCacheCount;\n\t\t\tbreak;\n\t\t}\n\t\tppsPrev=&psEntry2->psNext;\n\t}\n\t//free the entry\n\tfree(psEntry->u16aAnswer);\t//should be NULL anyway\n\t//close socket in case of TCP\n\tif(psEntry->uAddrLen==sizeof(SOCKET))\n\t\tclosesocket(psEntry->client.hSock);\n\tfree(psEntry);\n\tif(bUseCriticalSection)\n\t\tLeaveCriticalSection(&g_sCritSect);\n}\n\n//outputs error for TTL check (i32TimeOffset>=0) or searching OPT pseudo-RR (i32TimeOffset<0)\nstatic int InvalidDnsMsgErrorOutput(int32_t i32TimeOffset)\n{\n\tif(i32TimeOffset>=0)\n\t\tOutputToLog(OUTPUT_ALL, \"Invalid DNS answer detected while calculating TTL\");\n\telse\n\t\tOutputToLog(OUTPUT_ALL, \"Invalid DNS requested detected while searching OPT pseudo-RR\");\n\treturn 0;\t//error\n}\n\n//iterates through DNS message parts\n//either to update/check TTL (i32TimeOffset>=0) or to find OPT pseudo-RR (i32TimeOffset<0)\n//returns 0 on error/TTL expiration, 1 on success or offset of OPT pseudo-RR\nstatic int IterateDnsMessage(uint16_t* u16aMessage, int32_t i32TimeOffset)\n{\n\tint32_t i32TimeToLive;\n\tuint16_t u16ContentLen;\n\tuint16_t u16AmountQuestions;\n\tuint16_t u16Len;\n\tuint8_t* pu8Pos;\n\tuint8_t* pu8MessageEnd;\n\tuint8_t u8NameLen;\n\n\tu16Len=ntohs(*u16aMessage);\n\tif(u16Len<=12)\n\t\treturn InvalidDnsMsgErrorOutput(i32TimeOffset);\t//answer has no useful information\n\tpu8Pos=(uint8_t*)u16aMessage+2;\n\tpu8MessageEnd=pu8Pos+u16Len;\n\tu16AmountQuestions=ntohs(((uint16_t*)pu8Pos)[2]);\n\tpu8Pos+=12;\t//go behind header\n\t//ignore questions\n\tfor(; u16AmountQuestions; --u16AmountQuestions)\n\t{\n\t\t//ignore name\n\t\tfor(;;)\n\t\t{\n\t\t\tif(pu8Pos>=pu8MessageEnd)\n\t\t\t\treturn InvalidDnsMsgErrorOutput(i32TimeOffset);\t//failed\n\t\t\tu8NameLen=*pu8Pos;\n\t\t\tif(!u8NameLen)\n\t\t\t\tbreak;\t//end of name\n\t\t\tif(u8NameLen>=0xc0)\t//compression used? (reference to other name via offset)\n\t\t\t{\n\t\t\t\t++pu8Pos;\t//ignore 2nd part of offset\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tpu8Pos+=u8NameLen+1;\n\t\t}\n\t\tpu8Pos+=5;\t//ignore type and class\n\t}\n\t//for finding OPT pseudo-RR the DNS request might end after the questions\n\tif(i32TimeOffset<0 && pu8Pos==pu8MessageEnd)\n\t\treturn 1;\t//found no OPT pseudo-RR\n\t//for all records\n\tdo\n\t{\n\t\t//ignore name of resource record\n\t\tfor(;;)\n\t\t{\n\t\t\tif(pu8Pos>=pu8MessageEnd)\n\t\t\t\treturn InvalidDnsMsgErrorOutput(i32TimeOffset);\t//failed\n\t\t\tu8NameLen=*pu8Pos;\n\t\t\tif(!u8NameLen)\n\t\t\t\tbreak;\t//end of name\n\t\t\tif(u8NameLen>=0xc0)\t//compression used? (reference to other name via offset)\n\t\t\t{\n\t\t\t\t++pu8Pos;\t//ignore 2nd part of offset\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tpu8Pos+=u8NameLen+1;\n\t\t}\n\t\tpu8Pos+=5;\t//jump behind type and class\n\t\tif(pu8Pos>pu8MessageEnd-6)\t//6: TTL + RDLEN\n\t\t\treturn InvalidDnsMsgErrorOutput(i32TimeOffset);\t//failed\n\t\t//type is OPT pseudo-RR which has no real TTL value?\n\t\tif(ntohs(*(uint16_t*)(pu8Pos-4))==41)\n\t\t{\n\t\t\t//searching OPT pseudo-RR?\n\t\t\tif(i32TimeOffset<0)\n\t\t\t\treturn (int)((pu8Pos+6)-(uint8_t*)u16aMessage);\t//return offset to data\n\t\t}\n\t\telse if(i32TimeOffset>=0)\n\t\t{\n\t\t\t//check time to live field (0 means \"omitted\")\n\t\t\ti32TimeToLive=ntohl(*(uint32_t*)pu8Pos);\n\t\t\tif(i32TimeToLive>0)\n\t\t\t{\n\t\t\t\ti32TimeToLive-=i32TimeOffset;\n\t\t\t\tif(i32TimeToLive<=0)\n\t\t\t\t\treturn 0;\t//expired\n\t\t\t\t*(uint32_t*)pu8Pos=htonl(i32TimeToLive);\t//update field\n\t\t\t}\n\t\t\telse if(i32TimeToLive<0)\n\t\t\t\treturn 0;\t//failed, TTL must be positive; mark as expired, however: no error output in this case\n\t\t}\n\t\t//ignore record content\n\t\tu16ContentLen=ntohs(*(uint16_t*)(pu8Pos+4));\n\t\tpu8Pos+=6+u16ContentLen;\n\t\tif(pu8Pos>pu8MessageEnd)\n\t\t\treturn InvalidDnsMsgErrorOutput(i32TimeOffset);\t//failed\n\t}\n\twhile(pu8Pos<pu8MessageEnd);\n\treturn 1;\t//succeeded\n}\n\n//updates the time to live fields of an entry and checks for expiration (returns 0 if expired)\nstatic int CalculateTimeToLive(struct SEntry* psEntry)\n{\n\ttime_t iCurTime;\n\tint32_t i32TimeOffset;\n\n\tif(psEntry->iTime==(time_t)-1)\n\t\treturn 1;\t//getting time failed last time; without a working timer we ignore time to live stuff completely\n\t//calculate amount of seconds since last delivery\n\tif(time(&iCurTime)==(time_t)-1)\n\t\treturn 1;\t//error, can't get current time -> ignore time to live stuff completely\n\ti32TimeOffset=(int32_t)(iCurTime-psEntry->iTime);\n\tif(i32TimeOffset<0)\n\t\treturn 0;\t//time increased; overflow? Mark as expired\n\tpsEntry->iTime=iCurTime;\t//store current time for next delivery\n\treturn IterateDnsMessage(psEntry->u16aAnswer, i32TimeOffset);\n}\n\n//receives a specific amount of bytes\nstatic int ReceiveBytes(SOCKET hSock, unsigned int uAmount, uint16_t* u16aBuf)\n{\n\tunsigned int uPos=0;\n\tint iLen;\n\tchar* szErrMsg;\n\n\tfor(;;)\n\t{\n\t\tiLen=recv(hSock, (char*)u16aBuf+uPos, uAmount, 0);\n\t\tswitch(iLen)\n\t\t{\n\t\tcase SOCKET_ERROR:\n\t\t\tszErrMsg=GetSysError(WSAGetLastError());\n\t\t\tOutputToLog(OUTPUT_ALL, \"Receiving from SOCKS server has failed: %s\", szErrMsg);\n\t\t\tFreeSysError(szErrMsg);\n\t\t\treturn 0;\t//failed\n\t\tcase 0:\n\t\t\tOutputToLog(OUTPUT_ALL, \"The SOCKS server has closed the connection unexpectedly\");\n\t\t\treturn 0;\t//failed\n\t\tdefault:\n\t\t\tuAmount-=iLen;\n\t\t\tif(!uAmount)\n\t\t\t\treturn 1;\t//succeeded\n\t\t\tuPos+=iLen;\n\t\t}\n\t}\n}\n\n//for sending DNS requests where we add/manipulate the ECS option\n//returns 0 on error, -1 to send original message or the new message length otherwise\nstatic int AddEcsOption(uint16_t* u16aRequest, uint16_t* u16aDestBuf)\n{\n\t//try to find OPT pseudo-RR\n\tint iOffset=IterateDnsMessage(u16aRequest, -1);\n\n\tswitch(iOffset)\n\t{\n\tcase 0:\t//error\n\t\treturn 0;\n\tcase 1:\t//no OPT pseudo-RR\n\t{\n\t\t//add it\n\t\tuint16_t u16Len=ntohs(*(uint16_t*)u16aRequest);\n\t\tuint32_t u32NewLen=u16Len+g_iEcsOptionLen+11;\n\t\tuint8_t* u8aDest;\n\n\t\t//new length exceeds maximum?\n\t\tif(u32NewLen>65535)\n\t\t{\n\t\t\tOutputToLog(OUTPUT_ALL, \"Cannot add EDNS client subnet info to DNS request as it would exceed the maximum size\");\n\t\t\treturn 0;\n\t\t}\n\t\t//create new DNS request; starts with length as we use TCP\n\t\t*u16aDestBuf=htons((uint16_t)u32NewLen);\n\t\tmemcpy(u16aDestBuf+1, u16aRequest+1, u16Len);\t//copy original request\n\t\tu16aDestBuf[6]=htons(ntohs(u16aDestBuf[6])+1);\t//increase \"additional count\"\n\t\tu8aDest=((uint8_t*)(u16aDestBuf+1))+u16Len;\t\t//jump after original message\n\t\t//add OPT pseudo-RR\n\t\tu8aDest[0]=0;\t//name -> empty\n\t\tu8aDest[1]=0;\n\t\tu8aDest[2]=41;\t//type -> OPT pseudo-RR\n\t\tu8aDest[3]=0x10;//max UDP size: 4096\n\t\tu8aDest[4]=0;\n\t\t*(uint32_t*)(u8aDest+5)=0;\t//TTL (here: extended RCODE and flags -> all 0 -> no DNSSEC)\n\t\t*(uint16_t*)(u8aDest+9)=htons((uint16_t)g_iEcsOptionLen);\t//option length\n\t\tmemcpy(u8aDest+11, g_u8aEcsOption, g_iEcsOptionLen);\t//add option\n\t\treturn (int)(u32NewLen+2);\t//+2 for length field\n\t}\n\tdefault:\t//found OPT pseudo-RR\n\t{\n\t\t//try to find EDNS Client Subnet option\n\t\tuint8_t* pu8Pos=((uint8_t*)u16aRequest)+iOffset;\t//offset points to option data\n\t\tuint16_t u16OptLen=ntohs(((uint16_t*)pu8Pos)[-1]);\t//length before data\n\t\tuint8_t* pu8OptionsEnd=pu8Pos+u16OptLen;\n\t\tuint16_t u16Len=ntohs(*(uint16_t*)u16aRequest);\n\t\tuint8_t* pu8MessageEnd=((uint8_t*)u16aRequest)+2+u16Len;\n\t\tuint16_t u16LenIncludingOptionsToCopy;\n\t\tuint16_t u16CurOptLen;\n\t\tuint32_t u32NewLen;\n\n\t\t//pu8OptionsEnd beyond end of message? -> error\n\t\tif(pu8OptionsEnd>pu8MessageEnd)\n\t\t\treturn InvalidDnsMsgErrorOutput(-1);\n\t\tfor(;; pu8Pos+=u16CurOptLen)\n\t\t{\n\t\t\tif(pu8Pos>=pu8OptionsEnd)\n\t\t\t{\n\t\t\t\t//no EDNS Client Subnet option -> add it\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tu16CurOptLen=ntohs(*(uint16_t*)(pu8Pos+2))+4;\n\t\t\tif(u16CurOptLen<4 || pu8Pos>pu8OptionsEnd-u16CurOptLen)\t//overflow or beyond end of message?\n\t\t\t\treturn InvalidDnsMsgErrorOutput(-1);\t//failed\n\t\t\t//EDNS Client Subnet option?\n\t\t\tif(ntohs(*(uint16_t*)pu8Pos)==8)\n\t\t\t{\n\t\t\t\tif(!g_bForceEcs)\n\t\t\t\t\treturn -1;\t//special return value to send original DNS message as we shall not replace ECS\n\t\t\t\t//replace existing option\n\t\t\t\t//correct parameters for lower message creation\n\t\t\t\tpu8OptionsEnd=pu8Pos+u16CurOptLen;\t//behind EDNS Client Subnet option\n\t\t\t\tu16OptLen-=u16CurOptLen;\n\t\t\t\tu16Len-=u16CurOptLen;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tu32NewLen=u16Len+g_iEcsOptionLen;\n\t\t//new length exceeds maximum?\n\t\tif(u32NewLen>65535)\n\t\t{\n\t\t\tOutputToLog(OUTPUT_ALL, \"Cannot add EDNS client subnet info to DNS request as it would exceed the maximum size\");\n\t\t\treturn 0;\n\t\t}\n\t\t*u16aDestBuf=htons((uint16_t)u32NewLen);\n\t\tu16LenIncludingOptionsToCopy=(uint16_t)(pu8Pos-(uint8_t*)u16aRequest);\n\t\tmemcpy(u16aDestBuf+1, u16aRequest+1, u16LenIncludingOptionsToCopy-2);\t//copy part of original request without length field\n\t\tmemcpy(((uint8_t*)u16aDestBuf)+u16LenIncludingOptionsToCopy, g_u8aEcsOption, g_iEcsOptionLen);\t//add ECS option\n\t\tmemcpy(((uint8_t*)u16aDestBuf)+u16LenIncludingOptionsToCopy+g_iEcsOptionLen, pu8OptionsEnd, pu8MessageEnd-pu8OptionsEnd);\t//copy rest of original message\n\t\t//correct length of options\n\t\t*(uint16_t*)(((uint8_t*)u16aDestBuf)+iOffset-2)=htons((uint16_t)(u16OptLen+g_iEcsOptionLen));\n\t\treturn (int)(u32NewLen+2);\t//+2 for length field\n\t}\n\t}\n}\n\n//sends CONNECT command to HTTP proxy and checks answer\nstatic int HandleHttpProxy(SOCKET hSock, char* caBuf)\n{\n\tchar* szErrMsg;\n\tchar* szPosEnd;\n\tconst char* szPosStatus;\n\tint iRet;\n\tint iPos;\n\n\tiRet=send(hSock, g_caHttpProxyConnect, g_iHttpProxyConnectLen, MSG_NOSIGNAL);\n\tif(iRet!=g_iHttpProxyConnectLen)\n\t{\n\t\tszErrMsg=(iRet==SOCKET_ERROR)?GetSysError(WSAGetLastError()):\"Invalid amount of sent bytes\";\n\t\tOutputToLog(OUTPUT_ALL, \"Sending to HTTP proxy has failed: %s\", szErrMsg);\n\t\tif(iRet==SOCKET_ERROR)\n\t\t\tFreeSysError(szErrMsg);\n\t\treturn 0;\t//error\n\t}\n\t//receive answer\n\tiPos=0;\n\tfor(;;)\n\t{\n\t\tiRet=recv(hSock, caBuf+iPos, 2000-iPos, 0);\n\t\tif(iRet<=0)\n\t\t{\n\t\t\tif(iRet==SOCKET_ERROR)\n\t\t\t\tOutputToLog(OUTPUT_ALL, \"The HTTP proxy has closed the connection unexpectedly\");\n\t\t\telse\n\t\t\t{\n\t\t\t\tszErrMsg=GetSysError(WSAGetLastError());\n\t\t\t\tOutputToLog(OUTPUT_ALL, \"Receiving from HTTP proxy has failed: %s\", szErrMsg);\n\t\t\t\tFreeSysError(szErrMsg);\n\t\t\t}\n\t\t\treturn 0;\t//error\n\t\t}\n\t\tiPos+=iRet;\n\t\tif(iPos<4)\n\t\t\tcontinue;\t//continue receiving\n\t\t//check header, must begin with HTTP\n\t\tif(caBuf[0]=='H' || caBuf[1]=='T' || caBuf[2]=='T' || caBuf[3]=='P')\n\t\t{\n\t\t\tcaBuf[iPos]='\\0';\t//terminate the string\n\t\t\t//try to find empty line that marks the end of the HTTP proxy answer\n\t\t\tszPosEnd=strstr(caBuf, \"\\r\\n\\r\\n\");\n\t\t\tif(!szPosEnd)\n\t\t\t\tcontinue;\t//continue receiving\n\t\t\t//extract status code -> scan for space character (also stops on any control character)\n\t\t\tszPosStatus=caBuf+4;\n\t\t\twhile(*(unsigned char*)szPosStatus>' ')\n\t\t\t\t++szPosStatus;\n\t\t\tif(*szPosStatus==' ')\n\t\t\t{\n\t\t\t\t//we want: 200 Connection established\n\t\t\t\tif(atoi(++szPosStatus)==200)\n\t\t\t\t\tbreak;\t//header reception complete\n\t\t\t\t//find end of line (must be there, otherwise upper search for empty line would have failed\n\t\t\t\tszPosEnd=strchr(szPosStatus, '\\r');\n\t\t\t\t*szPosEnd='\\0';\n\t\t\t\tOutputToLog(OUTPUT_ALL, \"Connecting DNS server has failed: %s\", szPosStatus);\n\t\t\t\treturn 0;\t//error\n\t\t\t}\n\t\t}\n\t\tOutputToLog(OUTPUT_ALL, \"Invalid answer from HTTP proxy\");\n\t\treturn 0;\t//error\n\t}\n\t//so far there shouldn't be an answer from the DNS server (nothing after \\r\\n\\r\\n)\n\tif(szPosEnd[4])\n\t{\n\t\tOutputToLog(OUTPUT_ALL, \"DNS server answered before request\");\n\t\treturn 0;\t//error\n\t}\n\treturn 1;\t//o.k.\n}\n\n//thread for connecting the SOCKS server and resolving the DNS request\nTHREAD_FUNCTION(DnsThread, pEntry)\n{\n\tuint16_t u16aBuf[32769];\t//max DNS packet length 2(length)+65535(data) bytes - using uint16_t here for alignment\n\tstruct SEntry* psEntry=(struct SEntry*)pEntry;\n\tchar* szErrMsg;\n\tSOCKET hSock=socket(g_sSocksAddr.ss_family, SOCK_STREAM, IPPROTO_TCP);\n\tint iRet;\n\tint iPos;\n\tint iLen;\n\n\tif(hSock==SOCKET_ERROR)\n\t{\n\t\tszErrMsg=GetSysError(WSAGetLastError());\n\t\tOutputToLog(OUTPUT_ALL, \"Creating a TCP socket has failed: %s\", szErrMsg);\n\t\tFreeSysError(szErrMsg);\n\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\treturn 0;\n\t}\n\t//connect SOCKS server or HTTP proxy\n\tif(connect(hSock, (struct sockaddr*)&g_sSocksAddr, GetAddrLen(&g_sSocksAddr))==SOCKET_ERROR)\n\t{\n\t\tszErrMsg=GetSysError(WSAGetLastError());\n\t\tOutputToLog(OUTPUT_ALL, \"Connecting the SOCKS server has failed: %s\", szErrMsg);\n\t\tFreeSysError(szErrMsg);\n\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\treturn 0;\n\t}\n\t//using HTTP proxy instead of SOCKS?\n\tif(g_iHttpProxyConnectLen)\n\t{\n\t\tif(!HandleHttpProxy(hSock, (char*)u16aBuf))\n\t\t{\n\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\treturn 0;\n\t\t}\n\t}\n\telse\n\t{\n\t\t((uint8_t*)u16aBuf)[0]=5;\t//version 5 as we use SOCKS5\n\t\t((uint8_t*)u16aBuf)[1]=1;\t//number of authentication methods supported\n\t\t((uint8_t*)u16aBuf)[2]=g_uaUsrPwd?2:0;\t//user/password authentication or no authentication\n\t\tiLen=send(hSock, (const char*)u16aBuf, 3, MSG_NOSIGNAL);\n\t\tif(iLen!=3)\n\t\t{\n\t\t\tszErrMsg=(iLen==SOCKET_ERROR)?GetSysError(WSAGetLastError()):\"Invalid amount of sent bytes\";\n\t\t\tOutputToLog(OUTPUT_ALL, \"Sending to SOCKS server has failed: %s\", szErrMsg);\n\t\t\tif(iLen==SOCKET_ERROR)\n\t\t\t\tFreeSysError(szErrMsg);\n\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\treturn 0;\n\t\t}\n\t\t//check respons\n\t\tif(!ReceiveBytes(hSock, 2, u16aBuf+8))\n\t\t{\n\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\treturn 0;\n\t\t}\n\t\tif(((uint8_t*)u16aBuf)[16]!=5 || ((uint8_t*)u16aBuf)[17]!=((uint8_t*)u16aBuf)[2])\n\t\t{\n\t\t\tif(((uint8_t*)u16aBuf)[16]!=5)\n\t\t\t\tOutputToLog(OUTPUT_ALL, \"The SOCKS server has answered with a SOCKS version number unequal to 5\");\n\t\t\telse if(g_uaUsrPwd)\n\t\t\t\tOutputToLog(OUTPUT_ALL, \"The SOCKS server does not support user/password authentication\");\n\t\t\telse\n\t\t\t\tOutputToLog(OUTPUT_ALL, \"The SOCKS server wants an authentication\");\n\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\treturn 0;\n\t\t}\n\t\t//send authentication if enabled\n\t\tif(g_uaUsrPwd)\n\t\t{\n\t\t\tiLen=send(hSock, (const char*)g_uaUsrPwd, g_iUsrPwdLen, MSG_NOSIGNAL);\n\t\t\tif(iLen!=g_iUsrPwdLen)\n\t\t\t{\n\t\t\t\tszErrMsg=(iLen==SOCKET_ERROR)?GetSysError(WSAGetLastError()):\"Invalid amount of sent bytes\";\n\t\t\t\tOutputToLog(OUTPUT_ALL, \"Sending to SOCKS server has failed: %s\", szErrMsg);\n\t\t\t\tif(iLen==SOCKET_ERROR)\n\t\t\t\t\tFreeSysError(szErrMsg);\n\t\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\t//check response\n\t\t\tif(!ReceiveBytes(hSock, 2, u16aBuf+8))\n\t\t\t{\n\t\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif(((uint8_t*)u16aBuf)[17])\t//2nd byte of answer must be 0 for \"success\"\n\t\t\t{\n\t\t\t\tOutputToLog(OUTPUT_ALL, \"The SOCKS server authentication has failed (error code %u)\", (unsigned int)((uint8_t*)u16aBuf)[17]);\n\t\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\t//connect DNS server via SOCKS, the DNS server must support TCP\n\t\t((uint8_t*)u16aBuf)[1]=1;\t//establish a TCP/IP stream connection\n\t\t((uint8_t*)u16aBuf)[2]=0;\t//reserved, must be 0x00\n\t\tswitch(g_sDnsSrvAddr.ss_family)\n\t\t{\n\t\tcase AF_UNSPEC:\t//use name\n\t\t\t((uint8_t*)u16aBuf)[3]=3;\t//name\n\t\t\tiLen=(int)((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_flowinfo;\t//length in sin6_flowinfo (see ParseIpAndPort)\n\t\t\t((uint8_t*)u16aBuf)[4]=(uint8_t)iLen;\t//maximum length is 255\n\t\t\tmemcpy(((uint8_t*)u16aBuf)+5, *(char**)&((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_addr, iLen);\t//copy name (see ParseIpAndPort)\n\t\t\t*(uint16_t*)(((uint8_t*)u16aBuf)+iLen+5)=((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port;\t//port\n\t\t\tiPos=iLen+7;\n\t\t\tbreak;\n\t\tcase AF_INET:\t//use IPv4\n\t\t\t((uint8_t*)u16aBuf)[3]=1;\t//IPv4 address\n\t\t\t*(uint32_t*)(u16aBuf+2)=((struct sockaddr_in*)&g_sDnsSrvAddr)->sin_addr.s_addr;\t//address\n\t\t\t*(uint16_t*)(u16aBuf+4)=((struct sockaddr_in*)&g_sDnsSrvAddr)->sin_port;\t\t//port\n\t\t\tiPos=10;\n\t\t\tbreak;\n\t\tdefault:\t//use IPv6\n\t\t\t((uint8_t*)u16aBuf)[3]=4;\t//IPv6 address\n\t\t\tmemcpy(u16aBuf+2, &((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_addr, 16);\t\t//address\n\t\t\t*(uint16_t*)(u16aBuf+10)=((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port;\t\t//port\n\t\t\tiPos=22;\n\t\t}\n\t\tiLen=send(hSock, (const char*)u16aBuf, iPos, MSG_NOSIGNAL);\n\t\tif(iLen!=iPos)\n\t\t{\n\t\t\tszErrMsg=(iLen==SOCKET_ERROR)?GetSysError(WSAGetLastError()):\"Invalid amount of sent bytes\";\n\t\t\tOutputToLog(OUTPUT_ALL, \"Connecting through SOCKS server has failed: %s\", szErrMsg);\n\t\t\tif(iLen==SOCKET_ERROR)\n\t\t\t\tFreeSysError(szErrMsg);\n\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\treturn 0;\n\t\t}\n\t\t//check expected answer (get first 5 bytes to detect address type)\n\t\tif(!ReceiveBytes(hSock, 5, u16aBuf))\n\t\t{\n\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\treturn 0;\n\t\t}\n\t\tif(((uint8_t*)u16aBuf)[0]!=5 || ((uint8_t*)u16aBuf)[1]!=0 || (((uint8_t*)u16aBuf)[3]!=1 && ((uint8_t*)u16aBuf)[3]!=3 && ((uint8_t*)u16aBuf)[3]!=4))\n\t\t{\n\t\t\tszErrMsg=\"Unexpected answer from SOCKS server\";\n\t\t\t//correct version -> try to resolve the error code\n\t\t\tif(((uint8_t*)u16aBuf)[0]==5)\n\t\t\t\tswitch(((uint8_t*)u16aBuf)[1])\n\t\t\t\t{\n\t\t\t\tcase 2:\n\t\t\t\t\tszErrMsg=\"Connection not allowed by ruleset\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tszErrMsg=\"Network unreachable\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tszErrMsg=\"Host unreachable\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 5:\n\t\t\t\t\tszErrMsg=\"Connection refused by destination host\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 6:\n\t\t\t\t\tszErrMsg=\"TTL expired\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 7:\n\t\t\t\t\tszErrMsg=\"Command not supported / protocol error\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase 8:\n\t\t\t\t\tszErrMsg=\"Address type not supported\";\n\t\t\t\t}\n\t\t\tOutputToLog(OUTPUT_ALL, \"Connecting through SOCKS server has failed: %s\", szErrMsg);\n\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\treturn 0;\n\t\t}\n\t\t//get rest of answer\n\t\t//all bytes after that are part of the \"normal\" communication\n\t\tswitch(((uint8_t*)u16aBuf)[3])\n\t\t{\n\t\tcase 1:\t//IPv4\n\t\t\tiLen=5;\n\t\t\tbreak;\n\t\tcase 4:\t//IPv6\n\t\t\tiLen=17;\n\t\t\tbreak;\n\t\tdefault:\t//name\n\t\t\tiLen=2+((uint8_t*)u16aBuf)[4];\t//port length plus length of name\n\t\t}\n\t\tif(!ReceiveBytes(hSock, iLen, u16aBuf))\n\t\t{\n\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\treturn 0;\n\t\t}\n\t}\n\t//send DNS request via SOCKS; TCP variant starts with 2 byte length field\n\t//add ECS?\n\tif(g_iEcsOptionLen)\n\t\tiPos=AddEcsOption(psEntry->u16aRequest, u16aBuf);\n\telse\n\t\tiPos=-1;\t//no ECS -> send original message\n\tswitch(iPos)\n\t{\n\tcase 0:\t\t//error\n\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\treturn 0;\n\tcase -1:\t//send original message\n\t\tiPos=2+ntohs(*psEntry->u16aRequest);\n\t\tiLen=send(hSock, (const char*)psEntry->u16aRequest, iPos, MSG_NOSIGNAL);\n\t\tbreak;\n\tdefault:\t//send message created by AddEcsOption\n\t\tiLen=send(hSock, (const char*)u16aBuf, iPos, MSG_NOSIGNAL);\n\t}\n\t//error or not sent all bytes?\n\tif(iLen!=iPos)\n\t{\n\t\tszErrMsg=(iLen==SOCKET_ERROR)?GetSysError(WSAGetLastError()):\"Invalid amount of sent bytes\";\n\t\tOutputToLog(OUTPUT_ALL, \"Sending through SOCKS server has failed: %s\", szErrMsg);\n\t\tif(iLen==SOCKET_ERROR)\n\t\t\tFreeSysError(szErrMsg);\n\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\treturn 0;\n\t}\n\t//receive answer\n\tiPos=0;\n\tfor(;;)\n\t{\n\t\tiRet=recv(hSock, (char*)u16aBuf+iPos, sizeof(u16aBuf)-iPos, 0);\n\t\tif(iRet<=0)\n\t\t{\n\t\t\tszErrMsg=(iRet==SOCKET_ERROR)?GetSysError(WSAGetLastError()):\"Server has closed the connection unexpectedly\";\n\t\t\tOutputToLog(OUTPUT_ALL, \"Broken answer from DNS server: %s\", szErrMsg);\n\t\t\tif(iRet==SOCKET_ERROR)\n\t\t\t\tFreeSysError(szErrMsg);\n\t\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\t\treturn 0;\n\t\t}\n\t\tiPos+=iRet;\n\t\t//first 2 bytes contain length (Big Endian)\n\t\tif(iPos>=2)\n\t\t{\n\t\t\tiLen=2+ntohs(*u16aBuf);\t//iLen maximum is 2+65535 (smaller than sizeof(u16aBuf))\n\t\t\tif(iPos>=iLen)\n\t\t\t\tbreak;\t//answer completely received\n\t\t}\n\t}\n\t//invalid answer?\n\tif(iPos<=4)\n\t{\n\t\tOutputToLog(OUTPUT_ALL, \"Answer from DNS server too short!\");\n\t\tRemoveEntry(psEntry, hSock, g_bCacheEnabled);\n\t\treturn 0;\n\t}\n\tclosesocket(hSock);\n\tif(g_bCacheEnabled)\n\t{\n\t\t//store answer in cache\n\t\tEnterCriticalSection(&g_sCritSect);\n\t\tpsEntry->u16aAnswer=(uint16_t*)malloc(iLen);\n\t\tmemcpy(psEntry->u16aAnswer, u16aBuf, iLen);\n\t\t//copy ID\n\t\tpsEntry->u16aAnswer[1]=psEntry->u16aRequest[1];\n\t\t//remember current time for time to live calculations\n\t\tpsEntry->iTime=time(NULL);\n\t\t//send DNS answer to original requesting client via UDP or TCP\n\t\tSendAnswer(psEntry);\n\t\tLeaveCriticalSection(&g_sCritSect);\n\t}\n\telse\n\t{\n\t\t//send DNS answer to original requesting client via UDP or TCP\n\t\tpsEntry->u16aAnswer=u16aBuf;\n\t\tSendAnswer(psEntry);\n\t\tfree(psEntry);\n\t}\n\treturn 0;\n}\n\n//searches the cache for the same request and sends the answer if there is a cache hit\n//or creates a thread for forwarding the request to the DNS server via SOCKS\nstatic void HandleDnsRequest(uint16_t* u16aRequest, int iLen, void* pClientAddr, socklen_t iAddrLen)\n{\n\tuint8_t* pu8Pos;\n\tuint8_t* pu8End;\n\tstruct SEntry* psEntry;\n\tunsigned char uLen;\n\n\t//search in cache\n\tEnterCriticalSection(&g_sCritSect);\n\tfor(psEntry=g_psFirst; ; psEntry=psEntry->psNext)\n\t{\n\t\tif(!psEntry)\n\t\t{\n\t\t\t//create new entry (length of struct SEntry up to u16aRequest plus request length plus 2 for length)\n\t\t\tpsEntry=(struct SEntry*)malloc(((uint8_t*)psEntry->u16aRequest-(uint8_t*)psEntry)+iLen+2);\n\t\t\tpsEntry->iTime=(time_t)-1;\n\t\t\tpsEntry->u16aAnswer=NULL;\n\t\t\tpsEntry->uAddrLen=(unsigned char)iAddrLen;\n\t\t\tmemcpy(&psEntry->client, pClientAddr, iAddrLen);\n\t\t\t*psEntry->u16aRequest=htons((uint16_t)iLen);\n\t\t\tmemcpy(psEntry->u16aRequest+1, u16aRequest, iLen);\n\t\t\t//add entry to cache list in case cache is enabled\n\t\t\tpsEntry->psNext=g_psFirst;\n\t\t\tif(g_bCacheEnabled)\n\t\t\t\tg_psFirst=psEntry;\n\t\t\t//create thread to resolve entry\n\t\t\tif(ThreadCreate(DnsThread, psEntry))\n\t\t\t{\n\t\t\t\t++g_uCacheCount;\n\t\t\t\t//output amount of entries and current entry\n\t\t\t\tpu8Pos=(uint8_t*)u16aRequest+12;\n\t\t\t\tpu8End=(uint8_t*)u16aRequest+iLen;\n\t\t\t\twhile(pu8Pos<pu8End)\n\t\t\t\t{\n\t\t\t\t\tuLen=*pu8Pos;\n\t\t\t\t\tif(!uLen)\n\t\t\t\t\t{\n\t\t\t\t\t\tOutputToLog(OUTPUT_ALL, \"%3u %s\", g_uCacheCount, (char*)((uint8_t*)u16aRequest+13));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif(uLen>=0xc0)\t//compression used? (reference to other name via offset)\n\t\t\t\t\t\tbreak;\t\t//no output in this case\n\t\t\t\t\t*pu8Pos='.';\t//replace length by .\n\t\t\t\t\tpu8Pos+=uLen+1;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t//remove entry from cache list\n\t\t\t\tg_psFirst=psEntry->psNext;\n\t\t\t\tfree(psEntry);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\t//cache hit? (do not compare ID in first 2 bytes of request)\n\t\tif((uint16_t)iLen==ntohs(*psEntry->u16aRequest) && memcmp(u16aRequest+1, psEntry->u16aRequest+2, iLen-2)==0)\n\t\t{\n\t\t\t//answer already received?\n\t\t\tif(psEntry->u16aAnswer)\n\t\t\t{\n\t\t\t\t//answer to current address\n\t\t\t\tpsEntry->uAddrLen=(unsigned char)iAddrLen;\n\t\t\t\tmemcpy(&psEntry->client, pClientAddr, iAddrLen);\n\t\t\t\t//check if expired\n\t\t\t\tif(!CalculateTimeToLive(psEntry))\n\t\t\t\t{\n\t\t\t\t\t//expired -> kill last answer\n\t\t\t\t\tfree(psEntry->u16aAnswer);\n\t\t\t\t\tpsEntry->u16aAnswer=NULL;\n\t\t\t\t\t//copy current ID\n\t\t\t\t\tpsEntry->u16aRequest[1]=*u16aRequest;\n\t\t\t\t\t//create thread to resolve request again\n\t\t\t\t\tif(!ThreadCreate(DnsThread, psEntry))\n\t\t\t\t\t\tRemoveEntry(psEntry, (SOCKET)SOCKET_ERROR, 0);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t//use current ID\n\t\t\t\t\tpsEntry->u16aAnswer[1]=*u16aRequest;\n\t\t\t\t\tSendAnswer(psEntry);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t//copy current ID so the thread uses that one if it gets the answer\n\t\t\t\tpsEntry->u16aRequest[1]=*u16aRequest;\n\t\t\t\t//overwrite address; currently we can only handle one request address while waiting for an answer through SOCKS\n\t\t\t\t//current address is TCP? -> need to close it before overwriting it\n\t\t\t\tif(psEntry->uAddrLen==sizeof(SOCKET))\n\t\t\t\t\tclosesocket(psEntry->client.hSock);\n\t\t\t\tpsEntry->uAddrLen=(unsigned char)iAddrLen;\n\t\t\t\tmemcpy(&psEntry->client, pClientAddr, iAddrLen);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\tLeaveCriticalSection(&g_sCritSect);\n}\n\n//outputs an error caused by \"bind\" and closes the according socket\nstatic void OutputBindError(SOCKET hSock, struct sockaddr_storage* psAddr, int bUdp)\n{\n\tchar szAddr[256];\n\tchar szNo[16];\n\tchar* szErrMsg=GetSysError(WSAGetLastError());\n\n\tclosesocket(hSock);\n\tif(getnameinfo((struct sockaddr*)psAddr, GetAddrLen(psAddr), szAddr, sizeof(szAddr), szNo, sizeof(szNo), NI_NUMERICHOST|NI_NUMERICSERV))\n\t{\n\t\t//should never happen\n\t\tstrcpy(szAddr, \"unknown address\");\n\t\tstrcpy(szNo, \"unknown\");\n\t}\n\t//UDP is mandatory, TCP is optional\n\tif(bUdp)\n\t{\n\t\t//need { ... } on *nix as OutputFatal is a multi-line macro there\n\t\tOutputFatal(\"\\nBinding on %s, UDP port %s has failed: %s\\n\", szAddr, szNo, szErrMsg);\n\t}\n\telse\n\t\tOutputToLog(OUTPUT_ALL, \"Binding on %s, TCP port %s has failed: %s\", szAddr, szNo, szErrMsg);\n\tFreeSysError(szErrMsg);\n}\n\n//thread for receiving DNS requests via TCP\nTHREAD_FUNCTION(TcpThread, pAddr)\n{\n\tSOCKET hSockServer;\n\tSOCKET hSock;\n\tuint16_t u16aBuf[32769];\t//maximum possible DNS request size plus one byte - using uint16_t here for alignment; the actual size is stored in the first 2 bytes which can have a maximum value of 0xffff\n\tsocklen_t iAddrLen;\n\tint iCurBufLen;\n\tint iLen;\n\tuint16_t u16ReqLen;\n\tstruct sockaddr_storage sAddr;\n\tchar* szErrMsg;\n\n\thSockServer=socket(((struct sockaddr_storage*)pAddr)->ss_family, SOCK_STREAM, IPPROTO_TCP);\n\tif(hSockServer==SOCKET_ERROR)\n\t{\n\t\tszErrMsg=GetSysError(WSAGetLastError());\n\t\tOutputToLog(OUTPUT_ALL, \"Creating a TCP socket has failed: %s\", szErrMsg);\n\t\tFreeSysError(szErrMsg);\n\t\treturn 0;\n\t}\n\t//bind+listen on local TCP port (get DNS requests)\n\tif(bind(hSockServer, (struct sockaddr*)pAddr, GetAddrLen((struct sockaddr_storage*)pAddr))==SOCKET_ERROR)\n\t{\n\t\tOutputBindError(hSockServer, (struct sockaddr_storage*)pAddr, 0);\n\t\treturn 0;\n\t}\n\tif(listen(hSockServer, 5)==SOCKET_ERROR)\n\t{\n\t\tszErrMsg=GetSysError(WSAGetLastError());\n\t\tclosesocket(hSockServer);\n\t\tOutputToLog(OUTPUT_ALL, \"Listening on TCP socket has failed: %s\", szErrMsg);\n\t\tFreeSysError(szErrMsg);\n\t\treturn 0;\n\t}\n\t//as long as \"accept\" is working\n\tfor(;;)\n\t{\n\t\tiAddrLen=sizeof(sAddr);\n\t\thSock=accept(hSockServer, (struct sockaddr*)&sAddr, &iAddrLen);\n\t\tif(hSock==SOCKET_ERROR)\n\t\t{\n\t\t\tszErrMsg=GetSysError(WSAGetLastError());\n\t\t\tOutputToLog(OUTPUT_ALL, \"Accepting new connection on TCP socket has failed: %s\", szErrMsg);\n\t\t\tFreeSysError(szErrMsg);\n\t\t\tbreak;\n\t\t}\n\t\t//collect the whole DNS request\n\t\tiCurBufLen=0;\n\t\tfor(;;)\n\t\t{\n\t\t\tiLen=recv(hSock, (char*)u16aBuf+iCurBufLen, sizeof(u16aBuf)-iCurBufLen, 0);\n\t\t\tif(iLen<=0)\n\t\t\t{\n\t\t\t\tszErrMsg=GetSysError(WSAGetLastError());\n\t\t\t\tclosesocket(hSock);\n\t\t\t\tOutputToLog(OUTPUT_ALL, \"DNS request on TCP broken: %s\", szErrMsg);\n\t\t\t\tFreeSysError(szErrMsg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tiCurBufLen+=iLen;\n\t\t\t//got the whole DNS request?\n\t\t\tif(iCurBufLen>=2 && (u16ReqLen=ntohs(*u16aBuf))+2>=iCurBufLen)\n\t\t\t{\n\t\t\t\tif(u16ReqLen>12)\t//12 bytes header plus at least one byte of data\n\t\t\t\t\tHandleDnsRequest(u16aBuf+1, u16ReqLen, &hSock, sizeof(hSock));\t//HandleDnsRequest takes care of hSock now\n\t\t\t\telse\n\t\t\t\t\tclosesocket(hSock);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tclosesocket(hSockServer);\n\treturn 0;\n}\n\n//parses parameter /e:IP/Bits\nstatic int ParseEcs(char* szIpAndAmountBits)\n{\n\tstruct addrinfo sHint;\n\tstruct addrinfo* psResult;\n\tchar* szPos=strchr(szIpAndAmountBits, '/');\n\tint iBits;\n\tint iMaxBits;\n\n\tif(!szPos)\n\t\treturn 0;\t//error\n\t*szPos='\\0';\t//overwrite '/' for getaddrinfo below\n\t//prepare hint: only numeric values\n\tmemset(&sHint, 0, sizeof(sHint));\n\tsHint.ai_family=AF_UNSPEC;\n\tsHint.ai_flags=AI_NUMERICHOST;\n\t//now resolve it\n\tif(getaddrinfo(szIpAndAmountBits, NULL, &sHint, &psResult))\n\t{\n\t\t//some getaddrinfo implementations seem to have some trouble, so try the old IPv4 variant additionally\n\t\t//use sHint for that\n\t\t((struct in_addr*)(g_u8aEcsOption+8))->s_addr=inet_addr(szIpAndAmountBits);\n\t\tif(INADDR_NONE==((struct in_addr*)(g_u8aEcsOption+8))->s_addr)\n\t\t{\n\t\t\tOutputFatal(\"\\nInvalid address '%s' specified for EDNS Client Subnet!\\n\", szIpAndAmountBits);\n\t\t\treturn 0;\t//error\n\t\t}\n\t\tg_u8aEcsOption[5]=1;\t//FAMILY IPv4\n\t\tiMaxBits=32;\n\t}\n\telse\n\t{\n\t\tswitch(psResult->ai_family)\n\t\t{\n\t\tcase AF_INET:\n\t\t\tmemcpy(g_u8aEcsOption+8, &((struct sockaddr_in*)psResult->ai_addr)->sin_addr, 4);\n\t\t\tg_u8aEcsOption[5]=1;\t//FAMILY IPv4\n\t\t\tiMaxBits=32;\n\t\t\tbreak;\n\t\tcase AF_INET6:\n\t\t\tmemcpy(g_u8aEcsOption+8, &((struct sockaddr_in6*)psResult->ai_addr)->sin6_addr, 16);\n\t\t\tg_u8aEcsOption[5]=2;\t//FAMILY IPv6\n\t\t\tiMaxBits=128;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tOutputFatal(\"\\nInvalid address '%s' specified for EDNS Client Subnet!\\n\", szIpAndAmountBits);\n\t\t\treturn 0;\t//error\n\t\t}\n\t}\n\t//check amount of bits to use\n\tiBits=atoi(szPos+1);\n\tif(iBits<=0 || iBits>iMaxBits)\n\t{\n\t\tOutputFatal(\"\\nInvalid amount of bits '%s' specified for EDNS Client Subnet!\\n\", szPos+1);\n\t\treturn 0;\t//error\n\t}\n\t//OPTION-CODE\n\tg_u8aEcsOption[0]=0;\n\tg_u8aEcsOption[1]=8;\n\t//OPTION-LENGTH\n\tg_u8aEcsOption[2]=0;\n\tg_iEcsOptionLen=8+(iBits+7)/8;\t//round up to amount bytes\n\tg_u8aEcsOption[3]=(uint8_t)(g_iEcsOptionLen-4);\n\t//mask last byte of address\n\tg_u8aEcsOption[g_iEcsOptionLen-1]&=(uint8_t)(0xff<<(7-((iBits+7)&7)));\n\t//set rest to 0\n\tmemset(g_u8aEcsOption+g_iEcsOptionLen, 0, sizeof(g_u8aEcsOption)-g_iEcsOptionLen);\n\t//1st byte of FAMILY always 0\n\tg_u8aEcsOption[4]=0;\n\t//SOURCE PREFIX-LENGTH\n\tg_u8aEcsOption[6]=(uint8_t)iBits;\n\t//SCOPE PREFIX-LENGTH always 0\n\tg_u8aEcsOption[7]=0;\n\treturn 1;\t//o.k.\n}\n\n//parses a command line parameter of the format IPv4 or IPv4:port or IPv6 or [IPv6]:port\nstatic int ParseIpAndPort(int iFlag, const char* szParamName, const char* szPort, char* szIpAndPort, struct sockaddr_storage* psAddr)\n{\n\tstruct addrinfo sHint;\n\tstruct addrinfo* psResult;\n\tchar* szPos;\n\n\tif(strchr(szIpAndPort, '.'))\n\t{\n\t\t//seems to be IPv4\n\t\tszPos=strchr(szIpAndPort, ':');\n\t\tif(szPos)\n\t\t{\n\t\t\t*szPos='\\0';\t//overwrite ':' for getaddrinfo below\n\t\t\tszPort=szPos+1;\n\t\t}\n\t}\n\telse\n\t{\n\t\t//seems to be IPv6\n\t\t//format [IPv6]:port?\n\t\tif(*szIpAndPort=='[')\n\t\t{\n\t\t\t++szIpAndPort;\n\t\t\tszPos=strchr(szIpAndPort, ']');\n\t\t\tif(szPos)\n\t\t\t{\n\t\t\t\t*szPos='\\0';\t//overwrite ']' for getaddrinfo below\n\t\t\t\t//is there a port specification?\n\t\t\t\tif(szPos[1]==':')\n\t\t\t\t\tszPort=szPos+2;\n\t\t\t}\n\t\t}\n\t}\n\n\t//prepare hint: only numeric values\n\tmemset(&sHint, 0, sizeof(sHint));\n\tsHint.ai_family=AF_UNSPEC;\n\tsHint.ai_flags=iFlag|AI_NUMERICHOST|AI_NUMERICSERV;\n\t//now resolve it\n\tif(getaddrinfo(szIpAndPort, szPort, &sHint, &psResult))\n\t{\n\t\t//some getaddrinfo implementations seem to have some trouble, so try the old IPv4 variant additionally\n\t\t((struct sockaddr_in*)psAddr)->sin_addr.s_addr=inet_addr(szIpAndPort);\n\t\tif(INADDR_NONE!=((struct sockaddr_in*)psAddr)->sin_addr.s_addr)\n\t\t{\n\t\t\t((struct sockaddr_in*)psAddr)->sin_port=htons((uint16_t)atoi(szPort));\n\t\t\tif(((struct sockaddr_in*)psAddr)->sin_port)\n\t\t\t{\n\t\t\t\t((struct sockaddr_in*)psAddr)->sin_family=AF_INET;\n\t\t\t\treturn 1;\t//o.k.\n\t\t\t}\n\t\t}\n\t\t//only for the DNS server also support name\n\t\tif(&g_sDnsSrvAddr==psAddr)\n\t\t{\n\t\t\tsize_t uLen=strlen(szIpAndPort);\n\n\t\t\tif(uLen<256 && uLen)\n\t\t\t{\n\t\t\t\t((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port=htons((uint16_t)atoi(szPort));\n\t\t\t\tif(((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port)\n\t\t\t\t{\n\t\t\t\t\tg_sDnsSrvAddr.ss_family=AF_UNSPEC;\t//this marks usage of name\n\t\t\t\t\t*(char**)&((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_addr=szIpAndPort;\t//use sin6_addr for pointer to name (128 bit -> large enough, alignment should also be fine)\n\t\t\t\t\t((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_flowinfo=(uint8_t)uLen;\t\t//use sin6_flowinfo for length\n\t\t\t\t\treturn 1;\t//o.k.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tOutputFatal(\"\\nInvalid address '%s' and port '%s' specified for %s!\\n\", szIpAndPort, szPort, szParamName);\n\t\treturn 0;\t//error\n\t}\n\tif(psResult->ai_addrlen>sizeof(*psAddr))\n\t{\n\t\t//should never happen\n\t\tOutputFatal(\"\\nAddress '%s' and port '%s' specified for %s too long for internal storage!\\n\", szIpAndPort, szPort, szParamName);\n\t\treturn 0;\t//error\n\t}\n\t//copy 1st result\n\tmemcpy(psAddr, psResult->ai_addr, psResult->ai_addrlen);\n\tfreeaddrinfo(psResult);\n\treturn 1;\t//o.k.\n}\n\n//creates CONNECT command for HTTP proxy containing DNS address\nstatic int CreateHttpProxyConnectCommand()\n{\n\tmemcpy(g_caHttpProxyConnect, \"CONNECT \", 8);\n\tg_iHttpProxyConnectLen=8;\n\tif(g_sDnsSrvAddr.ss_family==AF_UNSPEC)\n\t{\n\t\t//sin6_addr contains pointer to name, see ParseIpAndPort (max. length is 255)\n\t\tint iLenDnsAddr=sprintf(g_caHttpProxyConnect+g_iHttpProxyConnectLen, \"%s:%u\", *(char**)&((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_addr, ntohs(((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port));\n\n\t\tif(iLenDnsAddr<=0)\n\t\t\treturn 0;\t//error\n\t\tg_iHttpProxyConnectLen+=iLenDnsAddr;\n\t}\n\telse\n\t{\n\t\t//add IP and port to g_caHttpProxyConnect\n\t\tchar szPort[6];\n\t\tint iLenPort;\n\n\t\tif(g_sDnsSrvAddr.ss_family==AF_INET6)\n\t\t\tg_caHttpProxyConnect[g_iHttpProxyConnectLen++]='[';\t//enclose with [] for IPv6\n\t\tif(getnameinfo((struct sockaddr*)&g_sDnsSrvAddr, GetAddrLen(&g_sDnsSrvAddr), g_caHttpProxyConnect+g_iHttpProxyConnectLen, 128, szPort, sizeof(szPort), NI_NUMERICHOST|NI_NUMERICSERV))\n\t\t\treturn 0;\t//error\n\t\tg_iHttpProxyConnectLen+=(int)strlen(g_caHttpProxyConnect+g_iHttpProxyConnectLen);\n\t\tif(g_sDnsSrvAddr.ss_family==AF_INET6)\n\t\t\tg_caHttpProxyConnect[g_iHttpProxyConnectLen++]=']';\t//enclose with [] for IPv6\n\t\tg_caHttpProxyConnect[g_iHttpProxyConnectLen++]=':';\n\t\tiLenPort=(int)strlen(szPort);\n\t\tmemcpy(g_caHttpProxyConnect+g_iHttpProxyConnectLen, szPort, iLenPort);\n\t\tg_iHttpProxyConnectLen+=iLenPort;\n\t}\n\t//complete command\n\tmemcpy(g_caHttpProxyConnect+g_iHttpProxyConnectLen, \" HTTP/1.0\\r\\n\\r\\n\", 13);\n\tg_iHttpProxyConnectLen+=13;\n\treturn 1;\t//o.k.\n}\n\nint main(int iArgCount, char** szaArgs)\n{\n\tstatic struct sockaddr_storage sAddr;\t//make it static so the lower array assignment causes no compiler warning\n\tconst char* szUser;\n\tconst char* szPassword;\n\tchar** pszCurArg;\n\tchar* szCurArg;\n\tchar* szLogFilePath;\n\tchar* szErrMsg;\n\tsize_t uUserLen;\n\tsize_t uPasswordLen;\n\tsocklen_t iAddrLen;\n\tint iAddrCount;\n\tint iLen;\n\tint bAppend;\t//append log file?\n\tint bQuiet;\t\t//parameter q specified?\n\tuint16_t u16aBuf[32754];\t//max UDP packet length on Windows plus one byte - using uint16_t here for alignment\n\t//struct array for the three addresses passed via command line\n\tstruct SAddr\n\t{\n\t\tstruct sockaddr_storage* psAddr;\n\t\tconst char* szDefaultAddress;\n\t\tconst char* szDefaultPort;\n\t\tconst char* szName;\n\t\tint iFlag;\n\t} saAddresses[3]=\n\t{\n\t\t{ &g_sSocksAddr, DEFAULT_SOCKS_SERVER, DEFAULT_SOCKS_PORT, \"SOCKS server\", 0 },\n\t\t{ &g_sDnsSrvAddr, DEFAULT_DNS_SERVER, DEFAULT_DNS_PORT, \"DNS server\", 0 },\n\t\t{ &sAddr, DEFAULT_LISTEN_IP, DEFAULT_DNS_PORT, \"listening\", AI_PASSIVE }\t//AI_PASSIVE for \"getaddrinfo\" as we use it for \"bind\"\n\t};\n\t\n\t//parse command line - use e.g. \"/?\" to display the usage\n\tbQuiet=0;\n\tbAppend=0;\n\tiAddrCount=0;\n\tszLogFilePath=NULL;\n\tszUser=NULL;\n\tszPassword=NULL;\n\tfor(pszCurArg=szaArgs+1; --iArgCount; ++pszCurArg)\n\t{\n\t\tszCurArg=*pszCurArg;\n\t\t//no address parameter?\n\t\tif(*szCurArg=='-' || *szCurArg=='/')\n\t\t{\n\t\t\tswitch(szCurArg[1])\n\t\t\t{\n\t\t\tcase 'd':\t//disable cache?\n\t\t\tcase 'D':\n\t\t\t\tif(!szCurArg[2])\n\t\t\t\t{\n\t\t\t\t\tg_bCacheEnabled=0;\n\t\t\t\t\tcontinue;\t//correct parameter, go to next one\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'q':\t//no console output?\n\t\t\tcase 'Q':\n\t\t\t\tif(!szCurArg[2])\n\t\t\t\t{\n\t\t\t\t\tbQuiet=1;\n\t\t\t\t\tcontinue;\t//correct parameter, go to next one\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'l':\t//log output?\n\t\t\tcase 'L':\n\t\t\t\tif(!szLogFilePath)\t//only allowed once\n\t\t\t\t{\n\t\t\t\t\tbAppend=(szCurArg[2]=='a' || szCurArg[2]=='A');\t//append?\n\t\t\t\t\tif(szCurArg[2+bAppend]==':')\n\t\t\t\t\t{\n\t\t\t\t\t\tszLogFilePath=szCurArg+3+bAppend;\n\t\t\t\t\t\tcontinue;\t//correct parameter, go to next one\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'u':\t//user?\n\t\t\tcase 'U':\n\t\t\t\tif(!szUser && szCurArg[2]==':')\t//only allowed once and : must be 2nd char\n\t\t\t\t{\n\t\t\t\t\tszUser=szCurArg+3;\n\t\t\t\t\tcontinue;\t//correct parameter, go to next one\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'p':\t//password?\n\t\t\tcase 'P':\n\t\t\t\tif(!szPassword && szCurArg[2]==':')\t//only allowed once and : must be 2nd char\n\t\t\t\t{\n\t\t\t\t\tszPassword=szCurArg+3;\n\t\t\t\t\tcontinue;\t//correct parameter, go to next one\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 't':\t//HTTP proxy?\n\t\t\tcase 'T':\n\t\t\t\tg_iHttpProxyConnectLen=1;\t//used as boolean here; later it contains the real length of the CONNECT command\n\t\t\t\tcontinue;\t//correct parameter, go to next one\n\t\t\tcase 'e':\t//EDNS Client Subnet\n\t\t\tcase 'E':\n\t\t\t\t//only allowed once\n\t\t\t\tif(!g_iEcsOptionLen)\n\t\t\t\t{\n\t\t\t\t\tg_bForceEcs=(szCurArg[2]=='f' || szCurArg[2]=='F');\t//force?\n\t\t\t\t\tszCurArg+=g_bForceEcs;\n\t\t\t\t\tif(szCurArg[2]==':' && ParseEcs(szCurArg+3))\n\t\t\t\t\t\tcontinue;\t//correct parameter, go to next one\n\t\t\t\t}\n\t\t\t}\n\t\t\t//wrong/unknown parameter; display help\n\t\t}\n\t\telse\n\t\t{\n\t\t\t//try to parse address\n\t\t\tif(iAddrCount<sizeof(saAddresses)/sizeof(*saAddresses))\n\t\t\t{\n\t\t\t\tif(!ParseIpAndPort(saAddresses[iAddrCount].iFlag, saAddresses[iAddrCount].szName, saAddresses[iAddrCount].szDefaultPort, szCurArg, saAddresses[iAddrCount].psAddr))\n\t\t\t\t\treturn 1;\n\t\t\t\t++iAddrCount;\n\t\t\t\tcontinue;\t//correct parameter, go to next one\n\t\t\t}\n\t\t}\n\n\t\t//either correct help request or unknown/wrong parameter -> display usage and stop\n\t\tOutputFatal(\"\\nDNS2SOCKS tunnels DNS requests via SOCKS5 and caches the answers.\\n\\n\\n\"\n\t\t\t\"Usage:\\n\\n\"\n\t\t\t\"DNS2SOCKS [/?] [/t] [/d] [/q] [/l[a]:FilePath] [/u:User /p:Password]\\n\"\n\t\t\t\"          [/e[f]:IP/Bits]\\n\"\n\t\t\t\"          [Socks5ServerIP[:Port]] [DNSServerIPorName[:Port]] [ListenIP[:Port]]\\n\\n\"\n\t\t\t\"/?            to view this help\\n\"\n\t\t\t\"/t            to use a HTTP proxy instead of a SOCKS server\\n\"\n\t\t\t\"              (here: Socks5ServerIP = HttpProxyIP, no support for /u and /p)\\n\"\n\t\t\t\"/d            to disable the cache\\n\"\n\t\t\t\"/q            to suppress the text output\\n\"\n\t\t\t\"/l:FilePath   to create a new log file \\\"FilePath\\\"\\n\"\n\t\t\t\"/la:FilePath  to create a new log file or append to the existing \\\"FilePath\\\"\\n\"\n\t\t\t\"/u:User       user name if your SOCKS server uses user/password authentication\\n\"\n\t\t\t\"/p:Password   password if your SOCKS server uses user/password authentication\\n\"\n\t\t\t\"/e:IP/Bits    to enable and specify EDNS client subnet in DNS queries\\n\"\n\t\t\t\"/ef:IP/Bits   same as above but also replaces EDNS client subnet if it exists\\n\\n\"\n\t\t\t\"Default Socks5ServerIP:Port = %s:%s\\n\"\n\t\t\t\"Default DNSServerIPorName:Port = %s:%s\\n\"\n\t\t\t\"Default ListenIP:Port = %s:%s\\n\",\n\t\t\tDEFAULT_SOCKS_SERVER, DEFAULT_SOCKS_PORT,\n\t\t\tDEFAULT_DNS_SERVER, DEFAULT_DNS_PORT,\n\t\t\tDEFAULT_LISTEN_IP, DEFAULT_DNS_PORT);\n\t\treturn 1;\n\t}\n\tif(szPassword && !szUser)\n\t{\n\t\tOutputFatal(\"\\nPassword specified but no user!\\n\");\n\t\treturn 1;\n\t}\n\tif(!szPassword && szUser)\n\t{\n\t\tOutputFatal(\"\\nUser specified but no password!\\n\");\n\t\treturn 1;\n\t}\n\tif(g_iHttpProxyConnectLen && szPassword)\n\t{\n\t\tOutputFatal(\"\\nAuthentication not supported for HTTP proxy!\\n\");\n\t\treturn 1;\n\t}\n\tif(szUser)\n\t{\n\t\tuUserLen=strlen(szUser);\n\t\tif(uUserLen>255)\n\t\t{\n\t\t\tOutputFatal(\"\\nUser exceeds 255 characters!\\n\");\n\t\t\treturn 1;\n\t\t}\n\t\tuPasswordLen=strlen(szPassword);\n\t\tif(uPasswordLen>255)\n\t\t{\n\t\t\tOutputFatal(\"\\nPassword exceeds 255 characters!\\n\");\n\t\t\treturn 1;\n\t\t}\n\t}\n\telse\n\t{\n\t\t//initialize uUserLen and uPasswordLen - otherwise VC++ 2010 outputs a wrong warning\n\t\tuUserLen=0;\n\t\tuPasswordLen=0;\n\t}\n\n\t//fill unspecified addresses with default values\n\twhile(iAddrCount<sizeof(saAddresses)/sizeof(*saAddresses))\n\t{\n\t\tif(!ParseIpAndPort(saAddresses[iAddrCount].iFlag, saAddresses[iAddrCount].szName, saAddresses[iAddrCount].szDefaultPort, (char*)saAddresses[iAddrCount].szDefaultAddress, saAddresses[iAddrCount].psAddr))\n\t\t\treturn 1;\n\t\t++iAddrCount;\n\t}\n\n\t//create CONNECT command in case of using HTTP proxy\n\tif(g_iHttpProxyConnectLen)\n\t{\n\t\tif(!CreateHttpProxyConnectCommand())\n\t\t{\n\t\t\tOutputFatal(\"\\nFailed to create CONNECT command!\\n\");\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tg_hSockUdp=socket(sAddr.ss_family, SOCK_DGRAM, IPPROTO_UDP);\n\tif(g_hSockUdp==SOCKET_ERROR)\n\t{\n\t\tszErrMsg=GetSysError(WSAGetLastError());\n\t\tOutputFatal(\"\\nCreating a UDP socket has failed: %s\\n\", szErrMsg);\n\t\tFreeSysError(szErrMsg);\n\t\treturn 1;\n\t}\n\t//listen on local UDP port (get DNS requests)\n\tif(bind(g_hSockUdp, (struct sockaddr*)&sAddr, GetAddrLen(&sAddr))==SOCKET_ERROR)\n\t{\n\t\tOutputBindError(g_hSockUdp, &sAddr, 1);\n\t\treturn 1;\n\t}\n\tif(!bQuiet)\n\t\tOpenConsole();\n\t//convert adresses+ports to strings; use u16aBuf as string buffer\n\tif(getnameinfo((struct sockaddr*)&g_sSocksAddr, GetAddrLen(&g_sSocksAddr), (char*)u16aBuf, 256, (char*)u16aBuf+256, 256, NI_NUMERICHOST|NI_NUMERICSERV))\n\t{\n\t\t//should never happen\n\t\tstrcpy((char*)u16aBuf, \"unknown address\");\n\t\tstrcpy((char*)u16aBuf+256, \"unknown\");\n\t}\n\tif(g_sDnsSrvAddr.ss_family==AF_UNSPEC)\t//name for DNS server?\n\t{\n\t\t//sin6_addr contains pointer to name, see ParseIpAndPort (max. length is 255)\n\t\tstrcpy((char*)u16aBuf+512, *(char**)&((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_addr);\n\t\tsprintf((char*)u16aBuf+768, \"%hu\", ntohs(((struct sockaddr_in6*)&g_sDnsSrvAddr)->sin6_port));\n\t}\n\telse if(getnameinfo((struct sockaddr*)&g_sDnsSrvAddr, GetAddrLen(&g_sDnsSrvAddr), (char*)u16aBuf+512, 256, (char*)u16aBuf+768, 256, NI_NUMERICHOST|NI_NUMERICSERV))\n\t{\n\t\t//should never happen\n\t\tstrcpy((char*)u16aBuf+512, \"unknown address\");\n\t\tstrcpy((char*)u16aBuf+768, \"unknown\");\n\t}\n\tif(getnameinfo((struct sockaddr*)&sAddr, GetAddrLen(&sAddr), (char*)u16aBuf+1024, 256, (char*)u16aBuf+1280, 256, NI_NUMERICHOST|NI_NUMERICSERV))\n\t{\n\t\t//should never happen\n\t\tstrcpy((char*)u16aBuf+1024, \"unknown address\");\n\t\tstrcpy((char*)u16aBuf+1280, \"unknown\");\n\t}\n\n\t//use (char*)u16aBuf+1536 as string buffer to output information about ECS\n\tif(g_iEcsOptionLen)\n\t{\n\t\tstruct sockaddr_storage sEcsAddr;\n\t\tsocklen_t uSockLen;\n\n\t\tmemset(&sEcsAddr, 0, sizeof(sEcsAddr));\n\t\t//IPv6?\n\t\tif(g_u8aEcsOption[5]==2)\n\t\t{\n\t\t\tuSockLen=sizeof(struct sockaddr_in6);\n\t\t\t((struct sockaddr_in6*)&sEcsAddr)->sin6_family=AF_INET6;\n\t\t\tmemcpy(&((struct sockaddr_in6*)&sEcsAddr)->sin6_addr, g_u8aEcsOption+8, 16);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tuSockLen=sizeof(struct sockaddr_in);\n\t\t\t((struct sockaddr_in*)&sEcsAddr)->sin_family=AF_INET;\n\t\t\tmemcpy(&((struct sockaddr_in*)&sEcsAddr)->sin_addr, g_u8aEcsOption+8, 4);\n\t\t}\n\t\tif(getnameinfo((struct sockaddr*)&sEcsAddr, uSockLen, (char*)u16aBuf+1536, 256, NULL, 0, NI_NUMERICHOST))\n\t\t{\n\t\t\t//should never happen\n\t\t\tstrcpy((char*)u16aBuf+1536, \"unknown address\");\n\t\t}\n\t\telse\n\t\t\t//add amount of bits\n\t\t\tsprintf((char*)u16aBuf+1536+strlen((char*)u16aBuf+1536), \"/%u\", (unsigned int)g_u8aEcsOption[6]);\n\t\t//add \" forced\" if g_bForceEcs\n\t\tif(g_bForceEcs)\n\t\t\tstrcat((char*)u16aBuf+1536, \" forced\");\n\t}\n\telse\n\t\tstrcpy((char*)u16aBuf+1536, \"disabled\");\n\n\t//output configuration\n\tOutputToLog(OUTPUT_LINE_BREAK|OUTPUT_CONSOLE, \"%s %s port %s\\n\"\n\t\t\"DNS server   %s port %s\\n\"\n\t\t\"listening on %s port %s\\n\"\n\t\t\"cache %s\\n\"\n\t\t\"authentication %s\\n\"\n\t\t\"EDNS client subnet %s\\n\",\n\t\tg_iHttpProxyConnectLen?\"HTTP proxy  \":\"SOCKS server\", (char*)u16aBuf, (char*)u16aBuf+256,\n\t\t(char*)u16aBuf+512, (char*)u16aBuf+768,\n\t\t(char*)u16aBuf+1024, (char*)u16aBuf+1280,\n\t\tg_bCacheEnabled?\"enabled\":\"disabled\",\n\t\tszUser?\"enabled\":\"disabled\",\n\t\t(char*)u16aBuf+1536);\n\n\tInitializeCriticalSection(&g_sCritSect);\n\n\t//log file was requested?\n\tif(szLogFilePath)\n\t\tOpenLogFile(szLogFilePath, bAppend);\n\n\t//create authentication package if user/password was specified\n\tif(szUser)\n\t{\n\t\tg_iUsrPwdLen=(int)(uUserLen+uPasswordLen+3);\n\t\tg_uaUsrPwd=(unsigned char*)malloc(g_iUsrPwdLen);\n\t\tg_uaUsrPwd[0]=1;\t//version 1\n\t\tg_uaUsrPwd[1]=(unsigned char)uUserLen;\n\t\tmemcpy(g_uaUsrPwd+2, szUser, uUserLen);\n\t\tg_uaUsrPwd[uUserLen+2]=(unsigned char)uPasswordLen;\n\t\tmemcpy(g_uaUsrPwd+uUserLen+3, szPassword, uPasswordLen);\n\t}\n\n\t//create thread for TCP connection\n\tThreadCreate(TcpThread, &sAddr);\n\n\t//endless loop\n\tfor(;;)\n\t{\n\t\t//receive DNS request\n\t\tiAddrLen=sizeof(sAddr);\n\t\tiLen=recvfrom(g_hSockUdp, (char*)u16aBuf, sizeof(u16aBuf), 0, (struct sockaddr*)&sAddr, &iAddrLen);\n\t\tif(iLen>12)\t//12 bytes header plus at least one byte of data\n\t\t\tHandleDnsRequest(u16aBuf, iLen, &sAddr, iAddrLen);\n\t}\n\t/*no cleanup code as we have an endless loop\n\t  we would need something like this:\n\tterminate all threads and clear their resources\n\tfree(g_uaUsrPwd);\n\tDeleteCriticalSection(&g_sCritSect);\n\tclosesocket(g_hSockUdp);\n\tfree all cache entries\n\tclose the console\n\tclose the log file\n\treturn 0;*/\n}\n\n#ifdef _WIN32\n\n//entry function for Windows applications - just forwards to \"main\"\nint WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow)\n{\n\tWSADATA sData;\n\tchar* szErrMsg;\n\tint iErrNo;\n\n\thInstance;\n\thPrevInstance;\n\tszCmdLine;\n\tiCmdShow;\n\n\tiErrNo=WSAStartup(0x202, &sData);\n\tif(iErrNo)\n\t{\n\t\tszErrMsg=GetSysError(iErrNo);\n\t\tOutputFatal(\"\\nWSAStartup has failed: %s\\n\", szErrMsg);\n\t\tFreeSysError(szErrMsg);\n\t\treturn 1;\n\t}\n\n\t//call \"main\" with the already parsed command line parameters stored in global variables by CRT\n\tiErrNo=main(__argc, __argv);\n\n\tWSACleanup();\n\treturn iErrNo;\n}\n\n#endif //#ifdef _WIN32\n"
  },
  {
    "path": "tools/globalmitm/Dockerfile",
    "content": "FROM debian:bullseye-slim\n\nLABEL maintainer=\"rev1si0n <lamda.devel@gmail.com>\"\n\nENV VERSION=2.11.4\nENV PLAT=linux-386\n\nENV PYPIMIRROR=https://mirrors.ustc.edu.cn/pypi/web/simple\nENV GOST=https://github.com/ginuerzh/gost/releases/download/v${VERSION}/gost-${PLAT}-${VERSION}.gz\nENV BINDIR=/usr/local/bin\n\nWORKDIR /tmp\nRUN apt-get update && apt-get -y upgrade && apt install -y adb gcc wget dnsutils python3 python3-pip\n\nRUN wget ${GOST} -O - | gzip -d> ${BINDIR}/gost\n\nCOPY startmitm.py ${BINDIR}\nCOPY globalmitm/entry ${BINDIR}\nCOPY globalmitm/DNS2SOCKS.c /tmp\nCOPY requirements.txt /tmp\n\nRUN gcc -pthread DNS2SOCKS.c -o ${BINDIR}/DNS2SOCKS\nRUN pip3 install -i ${PYPIMIRROR} --no-cache-dir -r requirements.txt\n\nRUN chmod 755 ${BINDIR}/*\nENV PATH=${BINDIR}:${PATH}\n\nWORKDIR /root\nEXPOSE 53/udp 8118/tcp 1234/tcp\nENTRYPOINT [ \"entry\" ]"
  },
  {
    "path": "tools/globalmitm/entry",
    "content": "#!/bin/bash\nset -e\nexport GRPC_DNS_RESOLVER=native\nexport PROXYPORT=${PROXYPORT:-8118}\n\ndie () {\n        echo $@; exit 1\n}\n\nIP=${1:-}\n                if [ -z \"${IP}\" ]; then\ndie \"no device provided\"\n                fi\n\nDNS=${DNS:-8.8.8.8}\nPDNS=$(((RANDOM % 10000) + 10000))\n\nif [ ! -z \"${SOCKS5}\" ] || [ ! -z \"${HTTP}\" ]; then\nPIDS=\n\nif [ ! -z \"${SOCKS5}\" ]; then\nPHTTP=$(((RANDOM % 10000) + 10000))\nUPSTREAM=http://127.0.0.1:${PHTTP}\ngost -L=${UPSTREAM} -F=socks5://${SOCKS5} >/dev/null 2>&1 &\nPIDS=\"$PIDS $!\"\nDNS2SOCKS ${SOCKS5} ${DNS} 127.0.0.1:${PDNS} /q &\nPIDS=\"$PIDS $!\"\n\nelif [ ! -z \"${HTTP}\" ]; then\nUPSTREAM=http://${HTTP}\nDNS2SOCKS ${HTTP} ${DNS} 127.0.0.1:${PDNS} /t /q &\nPIDS=\"$PIDS $!\"\n\nelse\n        die\nfi\n\ntrap \"kill -9 ${PIDS} 2>/dev/null\" SIGINT\nif ! dig +time=3 google.com @127.0.0.1 -p ${PDNS}; then\ndie \"BAD PROXY (CANNOT RESOLVE THROUGH PROXY)\"\nfi\n\nset -- \"$@\" --dns 127.0.0.1:${PDNS}\nset -- \"$@\" --mode upstream:${UPSTREAM}\nfi\n\n# common arguments\nset -- \"$@\" --web-host=0.0.0.0\nset -- \"$@\" --web-port=${WEBPORT:-1234}\nset -- \"$@\" --set block_global=false\n\nstartmitm.py $@"
  },
  {
    "path": "tools/id_rsa",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEA4QHmY32OT+F+maERMn1cvBRuIOIXH9yOALG+GMCngtjRJzSR\nn09dInmXE+PjiAqNRWvknVEjFywv0v1v/H2qSRJKR/togPgySjiABhigqDHdirNd\nDh63oN2e+d0yythoLzsQrH5BSVtw05Atpkr7bW4KdfMveWuddvDACnQ3mvCXq50X\nIK3cOlmHXwcJrX55BhEXxgHIqw0upf0A7DC3Afz5xjOA6+K/O2EzZIIJ+sWw7/Ko\n5+3m98Et0zwcxe20uNxzYf7JSMu3490YNckLiQcDrZVRUXNS70HO9HWCXKdjFJPi\nGgPtKUQNHjChwzSCQQGzqitXdwa60i9Sy1U09wIBIwKCAQAzbiYIHLLQbg5PAD5x\n8MS9Rn+SfNIV6UUHeRWCACZJyyh9/WMdGXRfpsNyQreqEQpZArfpcaGe5YdGK0zL\n/3dhKMCFe0sWKhofl+K/kJnAC2XWj2W6FaZQp69PDfz7KiZxMhJwkeMJc/yIIPR9\nx/70cOxyuz4NH+l6Rag8510qujhxn6lGc1+ZNHGeAYmUD7wFz9/QYDZU9C0YcylA\n2+Q5woU3L4b3y3JKqB6/dnsP9uF7R5KBR+qiqOwVm0nhvVU5uwbpQwTXeklopXyN\nrI/NeYcsDoEyb8NquVXX/GkOgY0FhqblGUR9kSdTLHq6VamelHc+dczZgMxcsq1c\nmnnLAoGBAP2FPssFpHJRhSr38Fq5A7mEjQeiPq2WgYV7kCpJT5F5OymcCCETIjEH\n4pTk5zCWUuIBx5LlzSa0XnQSYb0100ZzvDgfm1NPmqdkpPwkkbh2xyoHYTTPJ3WG\nTDur8Qyi83NteveszO1TCAGBTe3zN+2ov9qzl5Y7QHF94GVFDo4NAoGBAOM1Q8eG\n0KeqjutTz/UMtejoFpz0Hi1g32PfdQInHx8MDslYu3Fcpnos3xf59H7+mrRy0fUM\nhh27v7DiUvxUfhlojf0F3kDKeg9VZBslZF3vTCpFdKdFouZ2Cru3lCoaPSau69BC\n6HQw4P+RABrgxc6CeE9FUGEEMss+wTcRItITAoGBAO8ImkpkZ9mAD9gOV6X+5kEz\n1W2Y+USVOEqns9AZPGSW4AKpDvqdAvsHbzvtxAk9RtUXnumW118B1WYf9cEG3SUr\nSxBYUJ8B6ZaDdvxc/mwYOCBQGdK0sCz692t2OwvqGL1J95kQo/W0r8bnoT9wSqzg\n72fN5rI33azVxPHExJSPAoGAGfd1deOFj4E057HOn6muY8LAwXr8InF4nbMjULQD\njoUJARF0gfv1xNHtnFcUoMyj912UVoUWpE/4poBD/5SgsnJZXr7XkmBIdsfuLvz1\nhxQItF+1j3WsN5h2QVbPGsErj2RyuLcwgk66oN1fGQO+1cXEm1hg9SUNHorUQM7C\nJqMCgYEAofxXJ8dOWUaFIHmKLE7Y+0+i3D1yXVIyu/puuaQGbNFHxjcJ9ZdubhLN\nIyzJvngtM7mC90FtUETxvErMGdTzFeKtSKBZsJ8BiLCszRCEuJf5RX6uNrFUQ2pT\nPEmns088Gs4KUDwjTG0zQtj3pNc5zDynDMpFKp96spefqLJqw3s=\n-----END RSA PRIVATE KEY-----"
  },
  {
    "path": "tools/ida.py",
    "content": "#!/usr/bin/env python3\n#encoding=utf-8\nimport os\nimport sys\nimport time\nimport argparse\nimport subprocess\n\nfrom shlex import split as s\nfrom lamda.client import *\n\ncertfile = os.environ.get(\"CERTIFICATE\", None)\nport = int(os.environ.get(\"PORT\", 65000))\n\nargp = argparse.ArgumentParser()\nargp.add_argument(\"-d\", type=str, required=True)\nargp.add_argument(\"-a\", type=str, required=True)\nargs = argp.parse_args()\n\ncertfile = os.environ.get(\"CERTIFICATE\", None)\nd = Device(args.d, certificate=certfile)\n\napp = d.application(args.a)\nd.start_activity(**app.query_launch_activity(), debug=True)\nprint (time.ctime(), \"{} is started as debuggable mode\".format(args.a))\nprint (time.ctime(), \"Waitting for 'Waitting For Debugger' popup\")\nif not d(textContains=\"Waiting\").wait_for_exists(25*1000):\n    print (time.ctime(), \"No debugger prompt detected, please ensure \"\\\n                \"you already run 'setdebuggable' in firerpa terminal.\" )\n    exit (1)\n\npName = app.info().processName\nprocesses = d.enumerate_running_processes()\np = list(filter(lambda p: p.processName == pName,\n                                    processes))[0]\nprint (time.ctime(), \"Found pid: {}\".format(p.pid))\n# Build forward cmd\nprint (time.ctime(), \"Forwarding jwdp pid\")\ncmd = s(\"adb forward tcp:8700 jdwp:%s\" % p.pid)\nforward = subprocess.Popen(cmd, stdout=subprocess.DEVNULL,\n                                stderr=subprocess.DEVNULL,\n                                shell=False)\nforward.wait()\n\nprint (time.ctime(), \"--------------------------------\")\nprint (time.ctime(), \"Now please use your IDA to attach this target\")\nprint (time.ctime(), \"name: {} pid: {}\".format(pName, p.pid))\nprint (time.ctime(), \"and wait for IDA 'Downloading symbols' to finish\")\nprint (time.ctime(), \"when all is finished, press ENTER to continue\")\nprint (time.ctime(), \"--------------------------------\")\n\n_ = input()\n\ncmd = s(\"jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700\")\ndebug = subprocess.Popen(cmd, stdin=sys.stdin,\n                              stdout=sys.stdout,\n                              stderr=sys.stderr,\n                              bufsize=0,\n                              shell=False)\ndebug.wait()"
  },
  {
    "path": "tools/magisk/META-INF/com/google/android/update-binary",
    "content": "#!/sbin/sh\nsource /data/adb/magisk/util_functions.sh\n\nif [ ${MAGISK_VER_CODE} -lt 20400 ]; then\nabort \"Please install Magisk v20.4 +\"\nexit 1\nfi\n\nif [ \"${BOOTMODE}\" != \"true\" ]; then\nabort \"Must install from Magisk app\"\nfi\n\nOUTFD=$2\nZIPFILE=$3\ninstall_module\nexit 0"
  },
  {
    "path": "tools/magisk/META-INF/com/google/android/updater-script",
    "content": "#MAGISK\n"
  },
  {
    "path": "tools/magisk/common/adb_keys",
    "content": ""
  },
  {
    "path": "tools/magisk/common/properties.local",
    "content": "brandname=FIRERPA"
  },
  {
    "path": "tools/magisk/common/server/.keep",
    "content": ""
  },
  {
    "path": "tools/magisk/common/service.sh",
    "content": "#!/system/bin/sh\nbase=${0%/*}\ncert=/data/usr/lamda.pem\nlaunch=\"sh ${base}/server/bin/launch.sh\"\nport=65000\n\nsleep 25\nexport ca_store_remount=true\nif [ -f \"${cert}\" ]; then\n$launch --port=${port} --certificate=${cert}\nelse\n$launch --port=${port}\nfi"
  },
  {
    "path": "tools/magisk/install.sh",
    "content": "#!/system/bin/sh\nABI=$(getprop ro.product.cpu.abi)\nSERVER=$TMPDIR/lamda-server-$ABI.tar.gz\nBB=\"/data/adb/magisk/busybox\"\nUSRDIR=/data/usr\n\nif [ -d \"/data/adb/ksu/\" ]; then\nBB=\"/data/adb/ksu/bin/busybox\"\nfi\n\nif [ -d \"/data/adb/ap/\" ]; then\nBB=\"/data/adb/ap/bin/busybox\"\nfi\n\nexport LATESTARTSERVICE=true\n\nui_print \".____                       ________      _____    \"\nui_print \"|    |    _____     _____   \\______ \\    /  _  \\   \"\nui_print \"|    |    \\__  \\   /     \\   |    |  \\  /  /_\\  \\  \"\nui_print \"|    |___  / __ \\_|  Y Y  \\  |    |   \\/    |    \\ \"\nui_print \"|_______ \\(____  /|__|_|  / /_______  /\\____|__  / \"\nui_print \"        \\/     \\/       \\/          \\/         \\/  \"\nui_print \"                                       installer   \"\n\npushd $(pwd)\ncd $MODPATH\nif [ ! -f $SERVER ]; then\nabort \"lamda-server-${ABI}.tar.gz not in archive, please download and drop it to common/server.\"\nfi\n\nui_print \"- Extracting server files\"\n$BB tar -xzf $SERVER\n\nui_print \"- Placing configs\"\nmkdir -p ${USRDIR}\n\ncp -af $TMPDIR/adb_keys ${USRDIR}/.adb_keys\n\ncp -af $TMPDIR/properties.local ${USRDIR}\ncp -af $TMPDIR/lamda.pem ${USRDIR}\n\nui_print \"- Please reboot your device\"\npopd"
  },
  {
    "path": "tools/magisk/module.prop",
    "content": "id=lamda\nname=LAMDA\nversion=VERSION\nversionCode=VERSIONCODE\nauthor=rev1si0n\ndescription=Android reverse engineering & automation framework"
  },
  {
    "path": "tools/magisk/uninstall.sh",
    "content": "#!/system/bin/sh\nMODPATH=${0%/*}\n\necho \"/data/usr will not be removed, please remove it manually\""
  },
  {
    "path": "tools/objection-1.11.0-command-patch.diff",
    "content": "diff --git a/console/cli.py b/console/cli.py\nindex 1fc22fb..80e52dc 100644\n--- a/console/cli.py\n+++ b/console/cli.py\n@@ -31,8 +31,10 @@ from ..utils.helpers import normalize_gadget_name, print_frida_connection_help,\n @click.option('--serial', '-S', required=False, default=None, help='A device serial to connect to.')\n @click.option('--debug', '-d', required=False, default=False, is_flag=True,\n               help='Enable debug mode with verbose output. (Includes agent source map in stack traces)')\n+@click.option('--certificate', '-c', required=False, default=None, help=\"Frida connection certificate\")\n+@click.option('--token', '-t', required=False, default=None, help=\"Frida connection token\")\n def cli(network: bool, host: str, port: int, api_host: str, api_port: int,\n-        gadget: str, serial: str, debug: bool) -> None:\n+        gadget: str, serial: str, debug: bool, certificate: str, token: str) -> None:\n     \"\"\"\n         \\b\n              _   _         _   _\n@@ -56,6 +58,8 @@ def cli(network: bool, host: str, port: int, api_host: str, api_port: int,\n         state_connection.use_network()\n         state_connection.host = host\n         state_connection.port = port\n+        state_connection.certificate = certificate\n+        state_connection.token = token\n \n     if serial:\n         state_connection.device_serial = serial\ndiff --git a/utils/agent.py b/utils/agent.py\nindex 6d88e3a..fbe9b40 100644\n--- a/utils/agent.py\n+++ b/utils/agent.py\n@@ -126,8 +126,13 @@ class Agent(object):\n                 return device\n \n         if state_connection.get_comms_type() == state_connection.TYPE_REMOTE:\n+            kwargs = {}\n+            if state_connection.certificate:\n+                kwargs[\"certificate\"] = state_connection.certificate\n+            if state_connection.token:\n+                kwargs[\"token\"] = state_connection.token\n             device = frida.get_device_manager().add_remote_device('{host}:{port}'.format(\n-                host=state_connection.host, port=state_connection.port))\n+                host=state_connection.host, port=state_connection.port), **kwargs)\n             click.secho('Using networked device @`{n}`'.format(n=device.name), bold=True)\n \n             return device\n"
  },
  {
    "path": "tools/openvpn/Dockerfile",
    "content": "FROM alpine:3.15\n\nLABEL maintainer=\"rev1si0n <lamda.devel@gmail.com>\"\n\nENV VARS=/etc/openvpn/easy-rsa/vars\nENV OVPNCONFIG=/etc/openvpn/config.ovpn\nENV SOURCESMIRROR=mirrors.ustc.edu.cn\n\nRUN sed -i \"s/dl-cdn.alpinelinux.org/${SOURCESMIRROR}/g\" /etc/apk/repositories\n\nCOPY entry /usr/bin/run\nCOPY ovpn-server-new /usr/bin\nCOPY ovpn-client-profile /usr/bin\nCOPY ovpn-client-revoke /usr/bin\nCOPY ovpn-client-new /usr/bin\n\nCOPY config.ovpn /root\nCOPY vars /root\n\nRUN apk add openvpn easy-rsa bash curl jq\n\nRUN ln -sf /usr/share/easy-rsa/easyrsa /usr/bin/easyrsa\nRUN ln -sf ${VARS} /usr/share/easy-rsa/vars\n\nWORKDIR /etc/openvpn\n"
  },
  {
    "path": "tools/openvpn/config.ovpn",
    "content": "# openvpn Docker image config\n# https://github.com/rev1si0n/lamda/tree/master/tools/openvpn\n#\ntopology subnet\nserver 172.27.27.0 255.255.255.0\n\nproto udp\nport 1190\n\ndev tun\n\n# 不要编辑下面几行 easy-rsa/pki 配置\nca         /etc/openvpn/easy-rsa/pki/ca.crt\ndh         /etc/openvpn/easy-rsa/pki/dh.pem\ncert       /etc/openvpn/easy-rsa/pki/issued/lamda.crt\nkey        /etc/openvpn/easy-rsa/pki/private/lamda.key\ncrl-verify /etc/openvpn/easy-rsa/pki/crl.pem\n\n# TLS crypt 配置二选一\n;tls-auth   /etc/openvpn/ta.key 0\ntls-crypt  /etc/openvpn/ta.key\n\ncipher AES-256-GCM\nkeepalive 15 120\n\npersist-tun\npersist-key\n\nifconfig-pool-persist ipp.txt 10\nclient-to-client\n\nclient-config-dir ccd\nscript-security 2\n\ncompress lz4-v2\npush \"compress lz4-v2\"\n\nmax-clients 254\n\n# 如果你想路由其他服务器上的网段到客户端\n;push \"route 192.168.1.0 255.255.255.0\"\n\n# 如果你想要让每个客户端使用特定 DNS\n;push \"dhcp-option DNS 114.114.114.114\"\n\nverb 4"
  },
  {
    "path": "tools/openvpn/entry",
    "content": "#!/bin/bash\nif [ -f \"${OVPNCONFIG}\" ] && [ -f \"${VARS}\" ]; then\nexec openvpn --config config.ovpn\nfi\n"
  },
  {
    "path": "tools/openvpn/ovpn-client-new",
    "content": "#!/bin/bash\nTC_CLIENT=/etc/openvpn/tls-crypt-v2-client/$1\nTC_SERVER=/etc/openvpn/tls-crypt-v2-server.key\nopenvpn --genkey tls-crypt-v2-client ${TC_CLIENT} --tls-crypt-v2 ${TC_SERVER}\neasyrsa build-client-full \"$1\" nopass"
  },
  {
    "path": "tools/openvpn/ovpn-client-profile",
    "content": "#!/bin/bash\nexport CLIENT=$2\n\nif [ -f \"easy-rsa/pki/issued/${CLIENT}.crt\" ]; then\n\nCRYPT_cfg_n=\"$(grep -Eo '^tls-(auth|crypt|crypt-v2)' config.ovpn | wc -l)\"\nif [ 0\"${CRYPT_cfg_n}\" -gt 1 ]; then\necho \"Multiple tls-auth/crypt/crypt-v2 config detected\"; exit 1\nfi\n\nTLS_ENCRYPTION=\nif [ 0\"${CRYPT_cfg_n}\" -eq 1 ]; then\nTLS_ENCRYPTION=$(grep -Eo '^tls-(auth|crypt|crypt-v2)' config.ovpn)\nTLS_CRYPT=$(echo ${TLS_ENCRYPTION}|tr '-' '_')\nelse\nTLS_CRYPT=tls_none\nfi\n\nPROTO=$(sed -n -e 's/^proto *\\(.\\+\\).*/\\1/p' config.ovpn)\nCIPHER=$(sed -n -e 's/^cipher *\\(.\\+\\).*/\\1/p' config.ovpn)\nPORT=$(sed -n -e 's/^port *\\([[:digit:]]\\+\\).*/\\1/p' config.ovpn)\nWANIP=$(curl -s https://httpbin.org/ip | jq -r .origin)\n\nCA=$(cat easy-rsa/pki/ca.crt)\nCERT=$(openssl x509 -in easy-rsa/pki/issued/${CLIENT}.crt)\nKEY=$(cat easy-rsa/pki/private/${CLIENT}.key)\n\nSERVER_KEY_DIRECTION_n=$(sed -n -e 's/^tls-auth *.* \\([[:digit:]]\\).*/\\1/p' config.ovpn)\nif [ x\"${SERVER_KEY_DIRECTION_n}\"x = xx ]; then\nSERVER_KEY_DIRECTION_n=$(sed -n -e 's/^key-direction *\\(.\\+\\).*/\\1/p' config.ovpn)\nfi\n\nCLIENT_KEY_DIRECTION_n=\nif [ x\"${SERVER_KEY_DIRECTION_n}\"x != xx ]; then\nCLIENT_KEY_DIRECTION_n=$((1^SERVER_KEY_DIRECTION_n))\nfi\n\nCLIENT_KEY_DIRECTION_inline=\nCLIENT_KEY_DIRECTION=KEY_DIRECTION_NONE\nif [ x\"${CLIENT_KEY_DIRECTION_n}\"x != xx ]; then\nCLIENT_KEY_DIRECTION_inline=\"key-direction ${CLIENT_KEY_DIRECTION_n}\"\nCLIENT_KEY_DIRECTION=KEY_DIRECTION_${CLIENT_KEY_DIRECTION_n}\nfi\n\nif [ \"${TLS_ENCRYPTION}\" = \"tls-crypt-v2\" ]; then\nTA=$(cat /etc/openvpn/tls-crypt-v2-client/${CLIENT})\nelif [ ! -z \"${TLS_ENCRYPTION}\" ]; then\nTA=$(sed '/^#/d' ta.key)\nelse\nTA=\nfi\n        if [ \"g$1\" = govpn ]; then\ncat <<EOL >&1\nclient\nfloat\nnobind\n\ndev tun\nproto ${PROTO}\nremote ${WANIP} ${PORT}\nresolv-retry infinite\n\ncipher ${CIPHER}\nkeepalive 15 60\n\nremote-cert-tls server\n\n${CLIENT_KEY_DIRECTION_inline}\n<${TLS_ENCRYPTION}>\n${TA}\n</${TLS_ENCRYPTION}>\n\n<ca>\n${CA}\n</ca>\n\n<cert>\n${CERT}\n</cert>\n\n<key>\n${KEY}\n</key>\nEOL\n        else\ncat <<EOL >&1\nprofile = OpenVPNProfile()\nprofile.all_traffic  = False\nprofile.proto        = OpenVPNProto.${PROTO^^}\nprofile.host         = \"${WANIP}\"\nprofile.port         = ${PORT}\nprofile.cipher       = OpenVPNCipher.$(echo ${CIPHER}|tr '-' '_')\n\n# auto-generated lamda OpenVPN profile, CN=${CLIENT}\nprofile.tls_encryption = OpenVPNEncryption.${TLS_CRYPT^^}\nprofile.tls_key_direction = OpenVPNKeyDirection.${CLIENT_KEY_DIRECTION}\nprofile.tls_key = \"\"\"\n${TA}\n\"\"\"\n\nprofile.ca = \"\"\"\n${CA}\n\"\"\"\n\nprofile.cert = \"\"\"\n${CERT}\n\"\"\"\n\nprofile.key = \"\"\"\n${KEY}\n\"\"\"\n\n\"\"\"\n# auto-generated lamda OpenVPN prop, CN=${CLIENT}\n# copy lines below to properties.local to let openvpn\n# auto connect when lamda started\nopenvpn.proto=${PROTO}\nopenvpn.cipher=${CIPHER}\nopenvpn.global=false\nopenvpn.host=${WANIP}\nopenvpn.port=${PORT}\nopenvpn.ca=$(echo -n \"${CA}\" | base64 -w 0)\nopenvpn.cert=$(echo -n \"${CERT}\" | base64 -w 0)\nopenvpn.key=$(echo -n \"${KEY}\" | base64 -w 0)\nopenvpn.tls_encryption=${TLS_ENCRYPTION}\nopenvpn.tls_key_direction=${CLIENT_KEY_DIRECTION_n}\nopenvpn.tls_key=$(echo -n \"${TA}\" | base64 -w 0)\nopenvpn.enable=true\n\"\"\"\nEOL\n        fi\nfi\n"
  },
  {
    "path": "tools/openvpn/ovpn-client-renew",
    "content": "#!/bin/bash\neasyrsa renew \"$1\""
  },
  {
    "path": "tools/openvpn/ovpn-client-revoke",
    "content": "#!/bin/bash\neasyrsa revoke \"$1\"\neasyrsa gen-crl"
  },
  {
    "path": "tools/openvpn/ovpn-server-new",
    "content": "#!/bin/bash\nmkdir -p /etc/openvpn/ccd\nmkdir -p /etc/openvpn/easy-rsa\n\nif [ ! -f ${OVPNCONFIG} ]; then\ncp /root/config.ovpn ${OVPNCONFIG}\nfi\n\nif [ ! -f ${VARS} ]; then\ncp /root/vars ${VARS}\nfi\n\nset -ex\nopenvpn --genkey --secret /etc/openvpn/ta.key\nopenvpn --genkey tls-crypt-v2-server /etc/openvpn/tls-crypt-v2-server.key\nmkdir -p /etc/openvpn/tls-crypt-v2-client\n\neasyrsa init-pki\neasyrsa build-ca nopass\neasyrsa gen-dh\neasyrsa build-server-full lamda nopass\neasyrsa gen-crl\n"
  },
  {
    "path": "tools/openvpn/vars",
    "content": "# Easy-RSA 3 parameter settings\n\n# NOTE: If you installed Easy-RSA from your distro's package manager, don't edit\n# this file in place -- instead, you should copy the entire easy-rsa directory\n# to another location so future upgrades don't wipe out your changes.\n\n# HOW TO USE THIS FILE\n#\n# vars.example contains built-in examples to Easy-RSA settings. You MUST name\n# this file 'vars' if you want it to be used as a configuration file. If you do\n# not, it WILL NOT be automatically read when you call easyrsa commands.\n#\n# It is not necessary to use this config file unless you wish to change\n# operational defaults. These defaults should be fine for many uses without the\n# need to copy and edit the 'vars' file.\n#\n# All of the editable settings are shown commented and start with the command\n# 'set_var' -- this means any set_var command that is uncommented has been\n# modified by the user. If you're happy with a default, there is no need to\n# define the value to its default.\n\n# NOTES FOR WINDOWS USERS\n#\n# Paths for Windows  *MUST* use forward slashes, or optionally double-escaped\n# backslashes (single forward slashes are recommended.) This means your path to\n# the openssl binary might look like this:\n# \"C:/Program Files/OpenSSL-Win32/bin/openssl.exe\"\n\n# DO YOUR EDITS BELOW THIS POINT\n\n# This variable is used as the base location of configuration files needed by\n# easyrsa.  More specific variables for specific files (e.g., EASYRSA_SSL_CONF)\n# may override this default.\n#\n# The default value of this variable is the location of the easyrsa script\n# itself, which is also where the configuration files are located in the\n# easy-rsa tree.\n\nset_var EASYRSA\t\t\"/etc/openvpn/easy-rsa\"\n\n# If your OpenSSL command is not in the system PATH, you will need to define the\n# path to it here. Normally this means a full path to the executable, otherwise\n# you could have left it undefined here and the shown default would be used.\n#\n# Windows users, remember to use paths with forward-slashes (or escaped\n# back-slashes.) Windows users should declare the full path to the openssl\n# binary here if it is not in their system PATH.\n\nset_var EASYRSA_OPENSSL\t\"openssl\"\n#\n# This sample is in Windows syntax -- edit it for your path if not using PATH:\n#set_var EASYRSA_OPENSSL\t\"C:/Program Files/OpenSSL-Win32/bin/openssl.exe\"\n\n# Edit this variable to point to your soon-to-be-created key directory.  By\n# default, this will be \"$PWD/pki\" (i.e. the \"pki\" subdirectory of the\n# directory you are currently in).\n#\n# WARNING: init-pki will do a rm -rf on this directory so make sure you define\n# it correctly! (Interactive mode will prompt before acting.)\n\nset_var EASYRSA_PKI\t\t\"/etc/openvpn/easy-rsa/pki\"\n\n# Define directory for temporary subdirectories.\n\nset_var EASYRSA_TEMP_DIR\t\"$EASYRSA_PKI\"\n\n# Define X509 DN mode.\n# This is used to adjust what elements are included in the Subject field as the DN\n# (this is the \"Distinguished Name.\")\n# Note that in cn_only mode the Organizational fields further below aren't used.\n#\n# Choices are:\n#   cn_only  - use just a CN value\n#   org      - use the \"traditional\" Country/Province/City/Org/OU/email/CN format\n\nset_var EASYRSA_DN\t        \"cn_only\"\n\n# Organizational fields (used with 'org' mode and ignored in 'cn_only' mode.)\n# These are the default values for fields which will be placed in the\n# certificate.  Don't leave any of these fields blank, although interactively\n# you may omit any specific field by typing the \".\" symbol (not valid for\n# email.)\n\nset_var EASYRSA_REQ_COUNTRY\t\"\"\nset_var EASYRSA_REQ_PROVINCE\t\"\"\nset_var EASYRSA_REQ_CITY\t\"\"\nset_var EASYRSA_REQ_ORG\t        \"\"\nset_var EASYRSA_REQ_OU\t\t\"\"\nset_var EASYRSA_REQ_EMAIL\t\"\"\n\n# Choose a size in bits for your keypairs. The recommended value is 2048.  Using\n# 2048-bit keys is considered more than sufficient for many years into the\n# future. Larger keysizes will slow down TLS negotiation and make key/DH param\n# generation take much longer. Values up to 4096 should be accepted by most\n# software. Only used when the crypto alg is rsa (see below.)\n\nset_var EASYRSA_KEY_SIZE\t2048\n\n# The default crypto mode is rsa; ec can enable elliptic curve support.\n# Note that not all software supports ECC, so use care when enabling it.\n# Choices for crypto alg are: (each in lower-case)\n#  * rsa\n#  * ec\n#  * ed\n\n#set_var EASYRSA_ALGO\t\trsa\n\n# Define the named curve, used in ec & ed modes:\n\n#set_var EASYRSA_CURVE\t\tsecp384r1\n\n# In how many days should the root CA key expire?\n\n# 50 years\nset_var EASYRSA_CA_EXPIRE\t18250\n\n# In how many days should certificates expire?\n\nset_var EASYRSA_CERT_EXPIRE\t3650\n\n# How many days until the next CRL publish date?  Note that the CRL can still be\n# parsed after this timeframe passes. It is only used for an expected next\n# publication date.\nset_var EASYRSA_CRL_DAYS\t3650\n\n# How many days before its expiration date a certificate is allowed to be\n# renewed?\nset_var EASYRSA_CERT_RENEW\t30\n\n# Random serial numbers by default, set to no for the old incremental serial numbers\n#\n#set_var EASYRSA_RAND_SN\t\"yes\"\n\n# Support deprecated \"Netscape\" extensions? (choices \"yes\" or \"no\".) The default\n# is \"no\" to discourage use of deprecated extensions. If you require this\n# feature to use with --ns-cert-type, set this to \"yes\" here. This support\n# should be replaced with the more modern --remote-cert-tls feature.  If you do\n# not use --ns-cert-type in your configs, it is safe (and recommended) to leave\n# this defined to \"no\".  When set to \"yes\", server-signed certs get the\n# nsCertType=server attribute, and also get any NS_COMMENT defined below in the\n# nsComment field.\n\n#set_var EASYRSA_NS_SUPPORT\t\"no\"\n\n# When NS_SUPPORT is set to \"yes\", this field is added as the nsComment field.\n# Set this blank to omit it. With NS_SUPPORT set to \"no\" this field is ignored.\n\n#set_var EASYRSA_NS_COMMENT\t\"Easy-RSA Generated Certificate\"\n\n# A temp file used to stage cert extensions during signing. The default should\n# be fine for most users; however, some users might want an alternative under a\n# RAM-based FS, such as /dev/shm or /tmp on some systems.\n\n#set_var EASYRSA_TEMP_FILE\t\"$EASYRSA_PKI/extensions.temp\"\n\n# !!\n# NOTE: ADVANCED OPTIONS BELOW THIS POINT\n# PLAY WITH THEM AT YOUR OWN RISK\n# !!\n\n# Broken shell command aliases: If you have a largely broken shell that is\n# missing any of these POSIX-required commands used by Easy-RSA, you will need\n# to define an alias to the proper path for the command.  The symptom will be\n# some form of a 'command not found' error from your shell. This means your\n# shell is BROKEN, but you can hack around it here if you really need. These\n# shown values are not defaults: it is up to you to know what you're doing if\n# you touch these.\n#\n#alias awk=\"/alt/bin/awk\"\n#alias cat=\"/alt/bin/cat\"\n\n# X509 extensions directory:\n# If you want to customize the X509 extensions used, set the directory to look\n# for extensions here. Each cert type you sign must have a matching filename,\n# and an optional file named 'COMMON' is included first when present. Note that\n# when undefined here, default behaviour is to look in $EASYRSA_PKI first, then\n# fallback to $EASYRSA for the 'x509-types' dir.  You may override this\n# detection with an explicit dir here.\n#\nset_var EASYRSA_EXT_DIR\t        \"/usr/share/easy-rsa/x509-types\"\n\n# If you want to generate KDC certificates, you need to set the realm here.\n#set_var EASYRSA_KDC_REALM      \"CHANGEME.EXAMPLE.COM\"\n\n# OpenSSL config file:\n# If you need to use a specific openssl config file, you can reference it here.\n# Normally this file is auto-detected from a file named openssl-easyrsa.cnf from the\n# EASYRSA_PKI or EASYRSA dir (in that order.) NOTE that this file is Easy-RSA\n# specific and you cannot just use a standard config file, so this is an\n# advanced feature.\n\nset_var EASYRSA_SSL_CONF\t\"/usr/share/easy-rsa/openssl-easyrsa.cnf\"\n\n# Default CN:\n# This is best left alone. Interactively you will set this manually, and BATCH\n# callers are expected to set this themselves.\n\nset_var EASYRSA_REQ_CN\t\t\"LAMDA\"\n\n# Cryptographic digest to use.\n# Do not change this default unless you understand the security implications.\n# Valid choices include: md5, sha1, sha256, sha224, sha384, sha512\n\n#set_var EASYRSA_DIGEST\t\t\"sha256\"\n\n# Batch mode. Leave this disabled unless you intend to call Easy-RSA explicitly\n# in batch mode without any user input, confirmation on dangerous operations,\n# or most output. Setting this to any non-blank string enables batch mode.\n\nset_var EASYRSA_BATCH\t\t\"yes\"\n"
  },
  {
    "path": "tools/paddle_ocr_http_api.py",
    "content": "#!/usr/bin/env python3\n# THIS IS EXAMPLE HTTP OCR BACKEND,\n#     DO NOT USE IN PRODUCTION\nimport uvicorn\nimport asyncio\nimport paddle as paddle\nfrom paddleocr import PaddleOCR\nfrom fastapi import FastAPI, Request\n\napp = FastAPI()\n\n\n@app.post(\"/ocr\")\nasync def ocr(request: Request):\n    image = await request.body()\n    r = await asyncio.to_thread(ocr.ocr, image)\n    n = bool(r and r[0] and type(r[0][-1])==float)\n    result = (r if n else r[0]) or []\n    output = [[n[0], n[1][0], n[1][1]] for n in result]\n    return output\n\n\nif __name__ == \"__main__\":\n    ocr = PaddleOCR(use_gpu=False, drop_score=0.85,\n                            use_space_char=True)\n    uvicorn.run(app, host=\"0.0.0.0\", port=8000)"
  },
  {
    "path": "tools/requirements.txt",
    "content": "mitmproxy>=9.0.0,<=10.2.0\ndnspython\nhttpx[socks]<0.28.0\npackaging\nPillow"
  },
  {
    "path": "tools/root.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIClDCCAXwCAQAwDQYJKoZIhvcNAQELBQAwEDEOMAwGA1UECgwFTEFNREEwHhcN\nMjAwMTAxMDAwMDAxWhcNMjkxMjI5MDAwMDAxWjAQMQ4wDAYDVQQKDAVMQU1EQTCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALnqfBZvrGZaqgk5msCRRpPz\np/+464jBknlmTKetOckDTUq8V6fH/2Gfb6HjjoD5pkCtD5mSKkaHNhuxLXsFfEfc\nKlmnn3ZpjyNOHEA/iAO2DyEYa08xSevM7WojbF7cNj5/DYg7eb0i0+//bBlh8nlO\nghSUhCTM5PCofLDU8sVXuYAiGU6UzBrIC0vHEltDkiJVLpcCtsKjEZOskPdAc7u6\n/aA0P5GonZ5UkRDYpa+jeJUabqWZQQEgtmvjlmUUiXwu0jRnj1n0T3fPQD3gA+LI\nvAE9vgvpY5XQj6otpBvsZ3MJJNKcUMQu1zOANTzO18TlA8KoBNsBy8Z9DQZKXF8C\nAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAeGn/NyqIRI/0AGqvHP97KtQ76Tj6achb\n30LIXWp+YHUaMeAVjd2Z7jSQp5mZZFl+k1fb337IeaGXofVI21eK52QX2teNoBkA\n/V/O1Hu35/+aiz0xsdD6wWvwo4Fy1jlmaeJhwdQacBlDGFA2jFJxuEpahfxZvUsb\n3j5zU0WKTUCfEDgXFwBw12xku/7TMdCEbRsYaZ3zFTC226lQbUDN7weqFwSD+tB6\nRuhIxe8+cFwAsAWHClerKgZnr3t5QF078pW2GHy8CsJ7V3MZT5lZ43lS5NIB9JzZ\nSYxZ/izhRyh5qR73uAgsJaL56Bj+cQqnoTqhLZVl7J+M6WhWKzojsA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tools/root.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC56nwWb6xmWqoJ\nOZrAkUaT86f/uOuIwZJ5ZkynrTnJA01KvFenx/9hn2+h446A+aZArQ+ZkipGhzYb\nsS17BXxH3CpZp592aY8jThxAP4gDtg8hGGtPMUnrzO1qI2xe3DY+fw2IO3m9ItPv\n/2wZYfJ5ToIUlIQkzOTwqHyw1PLFV7mAIhlOlMwayAtLxxJbQ5IiVS6XArbCoxGT\nrJD3QHO7uv2gND+RqJ2eVJEQ2KWvo3iVGm6lmUEBILZr45ZlFIl8LtI0Z49Z9E93\nz0A94APiyLwBPb4L6WOV0I+qLaQb7GdzCSTSnFDELtczgDU8ztfE5QPCqATbAcvG\nfQ0GSlxfAgMBAAECggEAUVmJj3Ww7Z6JXXz/ung29kE9BxfetBnjYkyBiTMyoELa\nHLQZ9Nf95rURbzh3n/bdKNqxA0UiS3cZlXdrC7QERBtRyqYej/0zfULN+Mzz0o/k\nwdoCBoZuLogecH1si3t3HtSQUsaXh8uICjFtFxQ1OaBBKgksYz/mg2luGiAr1G5F\nyzCjKOpPo9rjgka0OMx5PnHjd9d6rf2ckB9VlLxxEVaYoGnXJTAe+FU0sWIFIfZY\nE1kknQ9F+MegGEZyE/r8kRUm4+81WWPChwWlXT8TpHp1HXtI0j9PY4vlWjDBB2cO\n6w8nPiHT3Hy0BYpa3Vee7a9kHljLa3ZBgq2DMnoaqQKBgQDVGKAuRbQ7CXRinRou\nLh/iDwzdWLVfIB/EpClZqhIQt+YcuqKixvBtlksRewJfC7lbSM5rcNYp+NFq2z1F\nol6EezmVcylAfHf9jHJrY37uR7ilwJnl7FMqNAw6hwRUm2Zhj+PgWPCVO8VpRBFE\nj80fLhD1rrD3TqBBvQGTs6h2OQKBgQDfWPBgFTJsgmp6XLsgLiahAKVVtHQk4lXI\n7WytdNgrf7U9rT9hGsRc5TJ/BagnUKRe3u/oIxnFw3QgFg1vPRWGxd8ckA3F4Z9V\nRgMUJcvnSDoLNctd19sXclC7KAIEAg5VZEEPPXFrmudlbO7AVDZ2/nqWsVxbJ4ie\nPolKMkOnVwKBgHhBRWPXgjYux7c+4R9eeC+iPqhK/0+HWWYnHg6zoy48GWftCwrB\nKb0uK37Z5CSvARM6qwKG0tUszUF4J8O8NYqd34kvTABQPYagU5G2vFLLtrATWMYf\n727JoH0G5LK6X7R+9yIMDviommclOnlujVsjK/75bnShsl5mwxUPNepBAoGBAIW0\nV4odhmchF58S3k7gce2WVmPdOF5QRGyZfaLFPRelRxmaGkQGYyD9cGJMP6LxisTk\njxgX0zjpzh3bNcWu+rEIyYzuy9/3Now08mjgS5W0TlMhrBIMw7Lgk2XHCJXfqTcD\nYlM+RCTXwcidErx+bXCE3VxS5ugG3all5IhPWOLXAoGBAJ+qUklRlmmTds7RhpeH\ndX8UlAW8/AtJfTfWoA5EPLX8jZp2v8FSSKk4OME95jVS6G8mAevHjdpaw1iwrxDX\nszwh9hcWxT4i2HrAwYMI2HM0HqEIjgCx+1kzDrcdsU96rLDhbaRoN9JBMh3yHI05\nH0KRgTsIBx2u3aMQAe3AHZOd\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "tools/rsync.sh",
    "content": "#!/bin/bash\n[ $# -lt 2 ] && exit 1\nDEFAULT_ID_RSA=$(mktemp)\nPORT=${PORT:-65000}\ncase \"$1\" in\n                *':'*)\n                                p1=root@$1\n                                p2=$2\n                ;;\n                *)\n                                p1=$1\n                                p2=root@$2\n                ;;\nesac\numask 077\nif [ ! -f \"${CERTIFICATE}\" ]; then\n# this is the default id_rsa for ssh service\ncat <<EOL >$DEFAULT_ID_RSA\n-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEA4QHmY32OT+F+maERMn1cvBRuIOIXH9yOALG+GMCngtjRJzSR\nn09dInmXE+PjiAqNRWvknVEjFywv0v1v/H2qSRJKR/togPgySjiABhigqDHdirNd\nDh63oN2e+d0yythoLzsQrH5BSVtw05Atpkr7bW4KdfMveWuddvDACnQ3mvCXq50X\nIK3cOlmHXwcJrX55BhEXxgHIqw0upf0A7DC3Afz5xjOA6+K/O2EzZIIJ+sWw7/Ko\n5+3m98Et0zwcxe20uNxzYf7JSMu3490YNckLiQcDrZVRUXNS70HO9HWCXKdjFJPi\nGgPtKUQNHjChwzSCQQGzqitXdwa60i9Sy1U09wIBIwKCAQAzbiYIHLLQbg5PAD5x\n8MS9Rn+SfNIV6UUHeRWCACZJyyh9/WMdGXRfpsNyQreqEQpZArfpcaGe5YdGK0zL\n/3dhKMCFe0sWKhofl+K/kJnAC2XWj2W6FaZQp69PDfz7KiZxMhJwkeMJc/yIIPR9\nx/70cOxyuz4NH+l6Rag8510qujhxn6lGc1+ZNHGeAYmUD7wFz9/QYDZU9C0YcylA\n2+Q5woU3L4b3y3JKqB6/dnsP9uF7R5KBR+qiqOwVm0nhvVU5uwbpQwTXeklopXyN\nrI/NeYcsDoEyb8NquVXX/GkOgY0FhqblGUR9kSdTLHq6VamelHc+dczZgMxcsq1c\nmnnLAoGBAP2FPssFpHJRhSr38Fq5A7mEjQeiPq2WgYV7kCpJT5F5OymcCCETIjEH\n4pTk5zCWUuIBx5LlzSa0XnQSYb0100ZzvDgfm1NPmqdkpPwkkbh2xyoHYTTPJ3WG\nTDur8Qyi83NteveszO1TCAGBTe3zN+2ov9qzl5Y7QHF94GVFDo4NAoGBAOM1Q8eG\n0KeqjutTz/UMtejoFpz0Hi1g32PfdQInHx8MDslYu3Fcpnos3xf59H7+mrRy0fUM\nhh27v7DiUvxUfhlojf0F3kDKeg9VZBslZF3vTCpFdKdFouZ2Cru3lCoaPSau69BC\n6HQw4P+RABrgxc6CeE9FUGEEMss+wTcRItITAoGBAO8ImkpkZ9mAD9gOV6X+5kEz\n1W2Y+USVOEqns9AZPGSW4AKpDvqdAvsHbzvtxAk9RtUXnumW118B1WYf9cEG3SUr\nSxBYUJ8B6ZaDdvxc/mwYOCBQGdK0sCz692t2OwvqGL1J95kQo/W0r8bnoT9wSqzg\n72fN5rI33azVxPHExJSPAoGAGfd1deOFj4E057HOn6muY8LAwXr8InF4nbMjULQD\njoUJARF0gfv1xNHtnFcUoMyj912UVoUWpE/4poBD/5SgsnJZXr7XkmBIdsfuLvz1\nhxQItF+1j3WsN5h2QVbPGsErj2RyuLcwgk66oN1fGQO+1cXEm1hg9SUNHorUQM7C\nJqMCgYEAofxXJ8dOWUaFIHmKLE7Y+0+i3D1yXVIyu/puuaQGbNFHxjcJ9ZdubhLN\nIyzJvngtM7mC90FtUETxvErMGdTzFeKtSKBZsJ8BiLCszRCEuJf5RX6uNrFUQ2pT\nPEmns088Gs4KUDwjTG0zQtj3pNc5zDynDMpFKp96spefqLJqw3s=\n-----END RSA PRIVATE KEY-----\nEOL\nelse\nDEFAULT_ID_RSA=$CERTIFICATE\nfi\nexec rsync -avz ${@:3} -e \"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=ERROR -i $DEFAULT_ID_RSA -p $PORT\" $p1 $p2\n"
  },
  {
    "path": "tools/scp.sh",
    "content": "#!/bin/bash\n[ $# -lt 2 ] && exit 1\nDEFAULT_ID_RSA=$(mktemp)\nPORT=${PORT:-65000}\ncase \"$1\" in\n                *':'*)\n                                p1=root@$1\n                                p2=$2\n                ;;\n                *)\n                                p1=$1\n                                p2=root@$2\n                ;;\nesac\numask 077\nif [ ! -f \"${CERTIFICATE}\" ]; then\n# this is the default id_rsa for ssh service\ncat <<EOL >$DEFAULT_ID_RSA\n-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEA4QHmY32OT+F+maERMn1cvBRuIOIXH9yOALG+GMCngtjRJzSR\nn09dInmXE+PjiAqNRWvknVEjFywv0v1v/H2qSRJKR/togPgySjiABhigqDHdirNd\nDh63oN2e+d0yythoLzsQrH5BSVtw05Atpkr7bW4KdfMveWuddvDACnQ3mvCXq50X\nIK3cOlmHXwcJrX55BhEXxgHIqw0upf0A7DC3Afz5xjOA6+K/O2EzZIIJ+sWw7/Ko\n5+3m98Et0zwcxe20uNxzYf7JSMu3490YNckLiQcDrZVRUXNS70HO9HWCXKdjFJPi\nGgPtKUQNHjChwzSCQQGzqitXdwa60i9Sy1U09wIBIwKCAQAzbiYIHLLQbg5PAD5x\n8MS9Rn+SfNIV6UUHeRWCACZJyyh9/WMdGXRfpsNyQreqEQpZArfpcaGe5YdGK0zL\n/3dhKMCFe0sWKhofl+K/kJnAC2XWj2W6FaZQp69PDfz7KiZxMhJwkeMJc/yIIPR9\nx/70cOxyuz4NH+l6Rag8510qujhxn6lGc1+ZNHGeAYmUD7wFz9/QYDZU9C0YcylA\n2+Q5woU3L4b3y3JKqB6/dnsP9uF7R5KBR+qiqOwVm0nhvVU5uwbpQwTXeklopXyN\nrI/NeYcsDoEyb8NquVXX/GkOgY0FhqblGUR9kSdTLHq6VamelHc+dczZgMxcsq1c\nmnnLAoGBAP2FPssFpHJRhSr38Fq5A7mEjQeiPq2WgYV7kCpJT5F5OymcCCETIjEH\n4pTk5zCWUuIBx5LlzSa0XnQSYb0100ZzvDgfm1NPmqdkpPwkkbh2xyoHYTTPJ3WG\nTDur8Qyi83NteveszO1TCAGBTe3zN+2ov9qzl5Y7QHF94GVFDo4NAoGBAOM1Q8eG\n0KeqjutTz/UMtejoFpz0Hi1g32PfdQInHx8MDslYu3Fcpnos3xf59H7+mrRy0fUM\nhh27v7DiUvxUfhlojf0F3kDKeg9VZBslZF3vTCpFdKdFouZ2Cru3lCoaPSau69BC\n6HQw4P+RABrgxc6CeE9FUGEEMss+wTcRItITAoGBAO8ImkpkZ9mAD9gOV6X+5kEz\n1W2Y+USVOEqns9AZPGSW4AKpDvqdAvsHbzvtxAk9RtUXnumW118B1WYf9cEG3SUr\nSxBYUJ8B6ZaDdvxc/mwYOCBQGdK0sCz692t2OwvqGL1J95kQo/W0r8bnoT9wSqzg\n72fN5rI33azVxPHExJSPAoGAGfd1deOFj4E057HOn6muY8LAwXr8InF4nbMjULQD\njoUJARF0gfv1xNHtnFcUoMyj912UVoUWpE/4poBD/5SgsnJZXr7XkmBIdsfuLvz1\nhxQItF+1j3WsN5h2QVbPGsErj2RyuLcwgk66oN1fGQO+1cXEm1hg9SUNHorUQM7C\nJqMCgYEAofxXJ8dOWUaFIHmKLE7Y+0+i3D1yXVIyu/puuaQGbNFHxjcJ9ZdubhLN\nIyzJvngtM7mC90FtUETxvErMGdTzFeKtSKBZsJ8BiLCszRCEuJf5RX6uNrFUQ2pT\nPEmns088Gs4KUDwjTG0zQtj3pNc5zDynDMpFKp96spefqLJqw3s=\n-----END RSA PRIVATE KEY-----\nEOL\nelse\nDEFAULT_ID_RSA=$CERTIFICATE\nfi\nssh-add $DEFAULT_ID_RSA >/dev/null 2>&1 || true\nexec scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \\\n         -o LogLevel=ERROR -i $DEFAULT_ID_RSA -P $PORT -pr $p1 $p2\n"
  },
  {
    "path": "tools/socks5/Dockerfile",
    "content": "FROM alpine:3.15\n# this will produce large image but IDC\nLABEL maintainer=\"rev1si0n <lamda.devel@gmail.com>\"\n\nENV SOURCESMIRROR=mirrors.ustc.edu.cn\nRUN sed -i \"s/dl-cdn.alpinelinux.org/${SOURCESMIRROR}/g\" /etc/apk/repositories\n\nCOPY entry /usr/bin\n\nRUN apk add bash make g++ git\n\nRUN mkdir -p /tmp/workdir\nWORKDIR /tmp/workdir\n\nRUN wget https://www.inet.no/dante/files/dante-1.4.3.tar.gz -O - | tar -xz\nWORKDIR /tmp/workdir/dante-1.4.3\n\nENV ac_cv_func_sched_setscheduler=no\nRUN ./configure --disable-client && make -j $(nproc)\nRUN cp sockd/sockd /usr/bin\n\nRUN rm -rf /tmp/workdir\n\nWORKDIR /\n\nEXPOSE 1080/tcp 1080/udp 50000:55000/udp\nENTRYPOINT [\"entry\"]\n"
  },
  {
    "path": "tools/socks5/entry",
    "content": "#!/bin/bash\nLOGIN=${LOGIN:-lamda}\nPWD=${PASSWORD:-lamda}\nUDPRANGE=${UDPRANGE:-50000-55000}\nBIND=${BIND:-0.0.0.0}\nPORT=${PORT:-1080}\n\n# the default output interface\nDEFAULT_DEV=$(ip route | awk '/default/ { print $5 }')\nDEV=${DEV:-\"${DEFAULT_DEV}\"}\n\ndeluser --remove-home ${LOGIN}       2>/dev/null\nadduser -D -H -s /bin/false ${LOGIN} 2>/dev/null\necho \"${LOGIN}:${PWD}\" | chpasswd    2>/dev/null\n\necho \"SOCKS5 LOGIN:    ${LOGIN}\"\necho \"SOCKS5 PASSWORD: ${PWD}\"\n\ncat <<EOL >/etc/danted.conf\ninternal: ${BIND} port = ${PORT}\nexternal: ${DEV}\n\nclientmethod: none\nsocksmethod: username\n\nuser.privileged: root\nuser.unprivileged: nobody\n\ntimeout.tcp_fin_wait: 15\ntimeout.negotiate: 15\ntimeout.connect: 15\n\nlogoutput: /proc/self/fd/2\n\nsocks pass {\nfrom: 0.0.0.0/0 to: 0.0.0.0/0\ncommand: bind connect udpassociate\nudp.portrange: ${UDPRANGE}\nlog: error connect\n}\n\nsocks pass {\nfrom: 0.0.0.0/0 to: 0.0.0.0/0\ncommand: bindreply udpreply\nudp.portrange: ${UDPRANGE}\nlog: error connect\n}\n\nclient pass {\nfrom: 0.0.0.0/0 to: 0.0.0.0/0\nlog: error\n}\nEOL\n\nexec sockd -f /etc/danted.conf"
  },
  {
    "path": "tools/ssh.sh",
    "content": "#!/bin/bash\nTARGET=${1:-localhost}\nPORT=${PORT:-65000}\nDEFAULT_ID_RSA=$(mktemp)\n\numask 077\nif [ ! -f \"${CERTIFICATE}\" ]; then\n# this is the default id_rsa for ssh service\ncat <<EOL >$DEFAULT_ID_RSA\n-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEA4QHmY32OT+F+maERMn1cvBRuIOIXH9yOALG+GMCngtjRJzSR\nn09dInmXE+PjiAqNRWvknVEjFywv0v1v/H2qSRJKR/togPgySjiABhigqDHdirNd\nDh63oN2e+d0yythoLzsQrH5BSVtw05Atpkr7bW4KdfMveWuddvDACnQ3mvCXq50X\nIK3cOlmHXwcJrX55BhEXxgHIqw0upf0A7DC3Afz5xjOA6+K/O2EzZIIJ+sWw7/Ko\n5+3m98Et0zwcxe20uNxzYf7JSMu3490YNckLiQcDrZVRUXNS70HO9HWCXKdjFJPi\nGgPtKUQNHjChwzSCQQGzqitXdwa60i9Sy1U09wIBIwKCAQAzbiYIHLLQbg5PAD5x\n8MS9Rn+SfNIV6UUHeRWCACZJyyh9/WMdGXRfpsNyQreqEQpZArfpcaGe5YdGK0zL\n/3dhKMCFe0sWKhofl+K/kJnAC2XWj2W6FaZQp69PDfz7KiZxMhJwkeMJc/yIIPR9\nx/70cOxyuz4NH+l6Rag8510qujhxn6lGc1+ZNHGeAYmUD7wFz9/QYDZU9C0YcylA\n2+Q5woU3L4b3y3JKqB6/dnsP9uF7R5KBR+qiqOwVm0nhvVU5uwbpQwTXeklopXyN\nrI/NeYcsDoEyb8NquVXX/GkOgY0FhqblGUR9kSdTLHq6VamelHc+dczZgMxcsq1c\nmnnLAoGBAP2FPssFpHJRhSr38Fq5A7mEjQeiPq2WgYV7kCpJT5F5OymcCCETIjEH\n4pTk5zCWUuIBx5LlzSa0XnQSYb0100ZzvDgfm1NPmqdkpPwkkbh2xyoHYTTPJ3WG\nTDur8Qyi83NteveszO1TCAGBTe3zN+2ov9qzl5Y7QHF94GVFDo4NAoGBAOM1Q8eG\n0KeqjutTz/UMtejoFpz0Hi1g32PfdQInHx8MDslYu3Fcpnos3xf59H7+mrRy0fUM\nhh27v7DiUvxUfhlojf0F3kDKeg9VZBslZF3vTCpFdKdFouZ2Cru3lCoaPSau69BC\n6HQw4P+RABrgxc6CeE9FUGEEMss+wTcRItITAoGBAO8ImkpkZ9mAD9gOV6X+5kEz\n1W2Y+USVOEqns9AZPGSW4AKpDvqdAvsHbzvtxAk9RtUXnumW118B1WYf9cEG3SUr\nSxBYUJ8B6ZaDdvxc/mwYOCBQGdK0sCz692t2OwvqGL1J95kQo/W0r8bnoT9wSqzg\n72fN5rI33azVxPHExJSPAoGAGfd1deOFj4E057HOn6muY8LAwXr8InF4nbMjULQD\njoUJARF0gfv1xNHtnFcUoMyj912UVoUWpE/4poBD/5SgsnJZXr7XkmBIdsfuLvz1\nhxQItF+1j3WsN5h2QVbPGsErj2RyuLcwgk66oN1fGQO+1cXEm1hg9SUNHorUQM7C\nJqMCgYEAofxXJ8dOWUaFIHmKLE7Y+0+i3D1yXVIyu/puuaQGbNFHxjcJ9ZdubhLN\nIyzJvngtM7mC90FtUETxvErMGdTzFeKtSKBZsJ8BiLCszRCEuJf5RX6uNrFUQ2pT\nPEmns088Gs4KUDwjTG0zQtj3pNc5zDynDMpFKp96spefqLJqw3s=\n-----END RSA PRIVATE KEY-----\nEOL\nelse\nDEFAULT_ID_RSA=$CERTIFICATE\nfi\nssh-add $DEFAULT_ID_RSA >/dev/null 2>&1 || true\nexec ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \\\n         -o LogLevel=ERROR -i $DEFAULT_ID_RSA -p $PORT root@$TARGET ${@:2}\n"
  },
  {
    "path": "tools/startmitm.py",
    "content": "#!/usr/bin/env python3\n# Copyright 2025 rev1si0n (lamda.devel@gmail.com). All rights reserved.\n#\n# Distributed under MIT license.\n# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\n#encoding=utf-8\nimport os\nimport re\nimport sys\nimport time\nimport uuid\nimport logging\nimport asyncio\nimport argparse\nimport subprocess\nimport threading\n\nfrom socket import *\nfrom random import randint\nfrom multiprocessing import Process\nfrom urllib.parse import urlparse\nfrom functools import partial\n\nfrom mitmproxy.certs import CertStore\nfrom mitmproxy.tools.main import mitmweb as web\nfrom mitmproxy.options import CONF_DIR, CONF_BASENAME, KEY_SIZE\nfrom mitmproxy.version import VERSION\n\nfrom packaging.version import parse as ver\n\nfrom lamda import __version__\nfrom lamda.client import *\n\n\nserial = None\ncleaning = False\ndef cleanup(*args, **kwargs):\n    global cleaning\n    if cleaning is True:\n        return\n    cleaning = True\n    log (\"uninstall certificate\")\n    d.uninstall_ca_certificate(ca)\n    log (\"disable proxy\")\n    d.stop_gproxy()\n    os._exit (0)\n\n\ndef add_server(command, spec):\n    spec and command.append(\"--mode\")\n    spec and command.append(spec)\n\n\ndef add_upstream(args, ext):\n    u = urlparse(args.upstream)\n    upstream = \"upstream:{}://{}:{}\".format(u.scheme,\n                                            u.hostname,\n                                            u.port)\n    args.mode = upstream\n    cred = \"{}:{}\".format(u.username, u.password)\n    u.username and ext.append(\"--upstream-auth\")\n    u.username and ext.append(cred)\n\n\ndef log(*args):\n    print (time.ctime(), *args)\n\n\ndef die(*args):\n    print (time.ctime(), *args)\n    sys.exit (1)\n\n\ndef adb(*args):\n    command = [\"adb\"]\n    if serial is not None:\n        command.extend([\"-s\", serial])\n    command.extend(args)\n    log (\" \".join(command))\n    proc = subprocess.Popen(command)\n    return proc\n\n\ndef adb_tcp(action, aport, bport):\n    p = adb(action, \"tcp:{}\".format(aport),\n                    \"tcp:{}\".format(bport))\n    return p\n\n\ndef reverse(aport, bport):\n    return adb_tcp(\"reverse\", aport, bport)\n\n\ndef forward(aport, bport):\n    return adb_tcp(\"forward\", aport, bport)\n\n\ndef get_default_interface_ip_imp(target):\n    s = socket(AF_INET, SOCK_DGRAM)\n    s.connect(( target, lamda ))\n    return s.getsockname()[0]\n\n\ndef get_default_interface_ip(target):\n    default = get_default_interface_ip_imp(target)\n    ip = os.environ.get(\"LANIP\", default)\n    return ip\n\n\nprint (r\"           __                 __            .__  __            \")\nprint (r\"   _______/  |______ ________/  |_    _____ |__|/  |_  _____   \")\nprint (r\"  /  ___/\\   __\\__  \\\\_  __ \\   __\\  /     \\|  \\   __\\/     \\  \")\nprint (r\"  \\___ \\  |  |  / __ \\|  | \\/|  |   |  Y Y  \\  ||  | |  Y Y  \\ \")\nprint (r\" /____  > |__| (____  /__|   |__|   |__|_|  /__||__| |__|_|  / \")\nprint (r\"      \\/            \\/                    \\/               \\/  \")\nprint (r\"                 Android HTTP Traffic Capture                  \")\nprint (r\"%60s\" %                (\"lamda#v%s BY firerpa\" % (__version__)))\n\n\npkgName = None\nargp = argparse.ArgumentParser()\n\nlogin = \"lamda\"\npsw = uuid.uuid4().hex[::3]\ncert = os.environ.get(\"CERTIFICATE\")\nproxy = int(os.environ.get(\"PROXYPORT\",\n                    randint(28080, 58080)))\nwebport = randint(28080, 58080)\nlamda = int(os.environ.get(\"PORT\",\n                    65000))\n\nargp.add_argument(\"device\", nargs=1)\nmod = argp.add_mutually_exclusive_group(required=False)\nmod.add_argument(\"-m\", \"--mode\", default=\"regular\")\nmod.add_argument(\"--upstream\", type=str, default=None,\n                  help=\"Upstream http proxy\")\nargp.add_argument(\"--proxy-dns\", type=str, default=None,\n                  help=\"Resolve dns(tcp) through proxy\")\nargp.add_argument(\"--serial\", type=str, default=None,\n                  help=\"Adb device serial\")\nargs, extras = argp.parse_known_args()\nserial = args.serial\nhost = args.device[0]\n\nif \":\" in host:\n    host, pkgName = host.split(\":\")\n\nserver = get_default_interface_ip(host)\nusb = server in (\"127.0.0.1\", \"::1\")\n\nif cert:\n    log (\"ssl:\", cert)\nif args.upstream:\n    add_upstream(args, extras)\nif usb and forward(lamda, lamda).wait() != 0:\n    die (\"adb forward failed\")\nif usb and reverse(proxy, proxy).wait() != 0:\n    die (\"adb forward failed\")\n\n# Create instance\nd = Device(host, port=lamda,\n                 certificate=cert)\nlogger.setLevel(logging.WARN)\n\n# Concat mitmproxy cert path\nDIR = os.path.expanduser(CONF_DIR)\nCertStore.from_store(DIR, CONF_BASENAME, KEY_SIZE)\nca = os.path.join(DIR, \"mitmproxy-ca-cert.pem\")\n\nlog (\"install cacert: %s\" % ca)\nd.install_ca_certificate(ca)\n\n# Initialize proxy profile\nprofile = GproxyProfile()\nprofile.type = GproxyType.HTTP_CONNECT\nprofile.bypass_local_subnet = True\n\nif args.proxy_dns: profile.nameserver = args.proxy_dns\nif args.proxy_dns: profile.dns_proxy = True\n# Prevent DNS from being intercepted\nif args.proxy_dns: extras.extend([\"--ignore-host\", args.proxy_dns])\n\n# HTTP MITM Proxy not support udp so drop it\nprofile.drop_udp = True\n\nprofile.host = server\nprofile.port = proxy\n\nprofile.login = login\nprofile.password = psw\nlog (\"set proxy: %s:%s@%s:%s/%s\" % (\n                            login, psw,\n                            server, proxy,\n                            pkgName or \"all\"))\nif pkgName is not None:\n    profile.application.set(d.application(pkgName))\nd.start_gproxy(profile)\n\ncommand = []\nadd_server(command, args.mode)\ncommand.append(\"--ssl-insecure\")\n# Simple random auth\ncommand.append(\"--proxyauth\")\ncommand.append(\"{}:{}\".format(login, psw))\n# Random web-port\ncommand.append(\"--web-port\")\ncommand.append(str(webport))\ncommand.append(\"--no-rawtcp\")\ncommand.append(\"--listen-port\")\ncommand.append(str(proxy))\n# Append extra command line\ncommand.extend(extras)\n\nlog (\" \".join(command))\n\nsys.exit = cleanup\nlog (\"press CONTROL + C to stop\")\nproc = Process(target=web, name=\"mitmweb\",\n               args=(command,), daemon=True)\nproc.run()\nsys.exit(0)"
  },
  {
    "path": "tools/startmitm.spec",
    "content": "# -*- mode: python ; coding: utf-8 -*-\nfrom PyInstaller.building.build_main import Analysis\nfrom PyInstaller.building.api import PYZ, EXE\n\nfrom PyInstaller.utils.hooks import collect_data_files\n\n\na = Analysis(\n    [\"startmitm.py\"],\n    pathex=[],\n    binaries=[],\n    datas=collect_data_files(\"lamda\"),\n    hiddenimports=[],\n    hookspath=[],\n    hooksconfig={},\n    excludes=[\"tcl\", \"tk\", \"tkinter\"],\n    win_no_prefer_redirects=False,\n    win_private_assemblies=False,\n    noarchive=False,\n)\n\nexe = EXE(\n    PYZ(a.pure, a.zipped_data),\n    a.scripts,\n    a.binaries,\n    a.zipfiles,\n    a.datas,\n    [],\n    name=\"startmitm\",\n    icon=[\"startmitm.ico\"],\n    debug=False,\n    bootloader_ignore_signals=False,\n    console=True,\n    disable_windowed_traceback=False,\n    argv_emulation=False,\n    target_arch=None,\n)"
  },
  {
    "path": "tools/test-fridarpc.js",
    "content": "Java.perform(function() {\n        var String = Java.use(\"java.lang.String\")\n        rpc.exports = {\n                getMyString: function (paramA, paramB) {\n                        return performRpcJVMCall(function() {\n                                // 可以使用 Frida java 相关功能，Java.use 等\n                                var newParam = String.$new(\"helloWorld\").toString()\n                                return newParam + \":\" + paramA + paramB\n                        })\n                },\n                getMyString1: function (paramA, paramB) {\n                        return performRpcJVMCallOnMain(function() {\n                                // 可以使用 Frida java 相关功能，Java.use 等\n                                // 执行于应用的主进程，适用于涉及到 UI 主线程相关的功能\n                                var newParam = String.$new(\"helloWorld\").toString()\n                                return newParam + \":\" + paramA + paramB\n                        })\n                },\n                getMyString2: function (paramA, paramB) {\n                        return performRpcCall(function() {\n                                // 这里不能使用 Java 相关功能\n                                return paramA + paramB\n                        })\n                },\n        }\n});\n"
  },
  {
    "path": "tools/test.pem",
    "content": "LAMDA SSL CERTIFICATE (CN=test,PASSWD=a1c0e3ea707a54de7a0f95)\n-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVcLZP9oLQZDHF\nyWJSke4gG+IJfH+LZY4qu3u/NrDpHvB7i9WMk1lQ/aLHb4WvjzPK+THNokG760CQ\nXAMJVKGfatprcKQwo0hoX6v6XlMYYRSQmop7zRiKg7vq+5vKOCCdJp1eISbew2LH\nNf1/rYzZpkT5lz1Lfdzm4yrAKuMkKrgzgO2QpOBAWXvgbXoA07bt7N86Y8gMdU0t\nzR/DrhKN/I1WXJMu1N0AnTl2QtHDoGB7E2KLivl2l6IvtkabxDNyciGmLNPiLFIk\nYOHyg1H71Bv5M84NSX75sln9uMPe39SEVRhSJm4w/OkWfp9tjYELwtzatXhIbhKs\nh/NeMe9dAgMBAAECggEABbQnYGepWJb02uDmJxK4lv8Sa/J5w2iJWLb94umRPLQ+\nSkQ9sVisBbAMI4w6uar4AAU8wXlZNwOzC/3ezucGEqkxAQys4T0xItORAvXLqVZ0\nIuZzq5nwJ/NxQsxKfihAfDKbTff7fpnLZUtvZMlnKYHPTLmFQnkwkrL2DNXt5U96\narTP5NcLuh46uOwjRT9hZ7+XB/nmOKy5xWxh5eLBbIQ2kKE/uebRUXDfMtnmm8vh\nQHoU3syw9YpMqD9VAjipijAtpmIpkl5zdVHNvCWBHi666q+1zqJGxUT80lxz57du\nToDQPsYt8QD/vc4i1j7tl6rG3PZBM3NK5TydV2FygQKBgQDaG2ruwilraGYG6MAl\n4AvYmAcHGAe0B4tfkdu/PjweEd2YAvN2HAeugVTIh5xZeRR04Q8eSFzpShJpDJd4\nuLHGqrvrjW/X+xEHsO56sc64bzmpybVApeBp86QFI98WqffAr7qso8pxRc6gSEMn\nNSquugjlaM9NRfqJ8r/6FIfBLQKBgQD6hbtggHuRhLxGacMhDo9Ziqre6/G7Woft\nGaFfaP3lY53f3HOu+256LX65eaXE7kmyP8dC3eV/n1OwoLwAbmAgjDYWu7OJti8A\nOloU6kgNEp5g08zUZPZGsR2FLweiRH+CvNtPYjBrvaHAKUSiKkpJtJVxZHuIzJZF\n5TfC6V3k8QKBgDyjtNZO+08Who9TNOES6pg8pG+PeczO8CwRFISWXAaMNwzlfSUQ\nY/5bjOP3+DtUE6DvVdG4ksR1yKqWSqLQzvSKUZcLCtaEwlZeFd/dAbl7iw+uugsQ\nU1WB3M9lCsh1VyGmegM3wrg9jVY44RrMiGJt7L1Dp6c3VpH2APQZsyi9AoGBANOi\njfykDb+M5pCFYDZY2njGUDsqD3g6roF6GX1EcNiMIx6uWXwFI/tK27dMMOIAe3l9\nr5OpaOs7abPY1Xl3xPU5/aeOwckgguwqX1cz49JHXExoIK187SAjF9EfPc+zFhUX\n/h090bHy7OYs9rIYD9HcIDM+s62JR5mcXlLmWk/BAoGBAJEXPWNHXL+kYz3/uGW7\ntJwHTC1elBcrW/hzI1kxdHWznUisSZW2Vp9oL0IckAuVBLzxe/GXz8bQN6d9l2d0\ntkfRj5NjC953Kcuq3RzDfUN4q72RUVMiE8uoI0dUVijW37KU0HKrmil0hqM5yob5\nUfXOCd9JTQIlycgMXDzNm4Kz\n-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIICqDCCAZCgAwIBAgIRAPsc1PQ5pnH3a6MFfGUMp6YwDQYJKoZIhvcNAQELBQAw\nEDEOMAwGA1UECgwFTEFNREEwHhcNMjAwMTAxMDAwMDAxWhcNMjkxMjI5MDAwMDAx\nWjAPMQ0wCwYDVQQDDAR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEA1XC2T/aC0GQxxcliUpHuIBviCXx/i2WOKrt7vzaw6R7we4vVjJNZUP2ix2+F\nr48zyvkxzaJBu+tAkFwDCVShn2raa3CkMKNIaF+r+l5TGGEUkJqKe80YioO76vub\nyjggnSadXiEm3sNixzX9f62M2aZE+Zc9S33c5uMqwCrjJCq4M4DtkKTgQFl74G16\nANO27ezfOmPIDHVNLc0fw64SjfyNVlyTLtTdAJ05dkLRw6BgexNii4r5dpeiL7ZG\nm8QzcnIhpizT4ixSJGDh8oNR+9Qb+TPODUl++bJZ/bjD3t/UhFUYUiZuMPzpFn6f\nbY2BC8Lc2rV4SG4SrIfzXjHvXQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAXD4/W\nB0aImZXjPm4qRpNk2fRzcSX80l6NVZYlIWvXjQqQwWfs/Hg3d5saJbrAfqeOkYPu\ncyrDXVOt/QLEC91AHkcEbuGGO0cESf28wT3U3FrIog1KUrMDjXQHoOodBi8gMiPf\ndNqhLI7d42AMrJSwYQ9RPooXoTglCkGwGouD8nKEy2cGyS13yPp4lx/SY4uBDESK\nFQ+GFQLLFBKPdvMsltpv2AaLZdwrQxhT6i55SZn5+Kow5LlX/DGuL9RtOvfvOKse\nQgzNAH7BF3lgoBbcbOrfEPk65dFQ7CWb/uh6cVifJ7qC9//LatIfoUPVrbEwY/gQ\nNAEqXrWn0fnaG4qA\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIClDCCAXwCAQAwDQYJKoZIhvcNAQELBQAwEDEOMAwGA1UECgwFTEFNREEwHhcN\nMjAwMTAxMDAwMDAxWhcNMjkxMjI5MDAwMDAxWjAQMQ4wDAYDVQQKDAVMQU1EQTCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALnqfBZvrGZaqgk5msCRRpPz\np/+464jBknlmTKetOckDTUq8V6fH/2Gfb6HjjoD5pkCtD5mSKkaHNhuxLXsFfEfc\nKlmnn3ZpjyNOHEA/iAO2DyEYa08xSevM7WojbF7cNj5/DYg7eb0i0+//bBlh8nlO\nghSUhCTM5PCofLDU8sVXuYAiGU6UzBrIC0vHEltDkiJVLpcCtsKjEZOskPdAc7u6\n/aA0P5GonZ5UkRDYpa+jeJUabqWZQQEgtmvjlmUUiXwu0jRnj1n0T3fPQD3gA+LI\nvAE9vgvpY5XQj6otpBvsZ3MJJNKcUMQu1zOANTzO18TlA8KoBNsBy8Z9DQZKXF8C\nAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAeGn/NyqIRI/0AGqvHP97KtQ76Tj6achb\n30LIXWp+YHUaMeAVjd2Z7jSQp5mZZFl+k1fb337IeaGXofVI21eK52QX2teNoBkA\n/V/O1Hu35/+aiz0xsdD6wWvwo4Fy1jlmaeJhwdQacBlDGFA2jFJxuEpahfxZvUsb\n3j5zU0WKTUCfEDgXFwBw12xku/7TMdCEbRsYaZ3zFTC226lQbUDN7weqFwSD+tB6\nRuhIxe8+cFwAsAWHClerKgZnr3t5QF078pW2GHy8CsJ7V3MZT5lZ43lS5NIB9JzZ\nSYxZ/izhRyh5qR73uAgsJaL56Bj+cQqnoTqhLZVl7J+M6WhWKzojsA==\n-----END CERTIFICATE-----\n"
  }
]