[
  {
    "path": ".dockerignore",
    "content": "# Git-related files\n.gitattributes\n.gitignore\n.git/\n\n# IDE files\n.idea/\n.vscode/\n\n# Build cache\n.gradle/\n\n# Documentation\ndocs/\nLICENSE\nREADME.md\n\n# Miscellaneous\nquick_start.sh\nDockerfile\n.dockerignore\n"
  },
  {
    "path": ".gitattributes",
    "content": "#\n# https://help.github.com/articles/dealing-with-line-endings/\n#\n# These are explicitly windows files and should use crlf\n*.bat           text eol=crlf\n\n"
  },
  {
    "path": ".github/workflows/core-build.yml",
    "content": "name: core-build\n\non:\n  pull_request:\n  workflow_dispatch:\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: google/tsunami-scanner-core\n\njobs:\n  build-image:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Build Docker image\n        id: build\n        uses: docker/build-push-action@v6\n        with:\n          context: .\n          file: core.Dockerfile\n          push: false\n          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          labels: ${{ steps.meta.outputs.labels }}\n"
  },
  {
    "path": ".github/workflows/core-push.yml",
    "content": "name: core-push\n\non:\n  push:\n    branches:\n      - master\n  workflow_dispatch:\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: google/tsunami-scanner-core\n\njobs:\n  build-and-push-image:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n      packages: write\n      attestations: write\n      id-token: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Log in to the Container registry\n        uses: docker/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata for Docker\n        id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n\n      - name: Build and push Docker image\n        id: push\n        uses: docker/build-push-action@v6\n        with:\n          context: .\n          file: core.Dockerfile\n          push: true\n          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          labels: ${{ steps.meta.outputs.labels }}\n\n      - name: Generate artifact attestation\n        uses: actions/attest-build-provenance@v2\n        with:\n          subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          subject-digest: ${{ steps.push.outputs.digest }}\n          push-to-registry: true\n"
  },
  {
    "path": ".github/workflows/devel-push.yml",
    "content": "name: devel-push\n\non:\n  schedule:\n    - cron: \"0 */4 * * *\"\n  workflow_dispatch:\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: google/tsunami-scanner-devel\n\njobs:\n  build-and-push-image:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n      packages: write\n      attestations: write\n      id-token: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Log in to the Container registry\n        uses: docker/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata for Docker\n        id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n\n      - name: Build and push Docker image\n        id: push\n        uses: docker/build-push-action@v6\n        with:\n          context: .\n          file: devel.Dockerfile\n          push: true\n          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          labels: ${{ steps.meta.outputs.labels }}\n\n      - name: Generate artifact attestation\n        uses: actions/attest-build-provenance@v2\n        with:\n          subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          subject-digest: ${{ steps.push.outputs.digest }}\n          push-to-registry: true\n"
  },
  {
    "path": ".github/workflows/full-push.yml",
    "content": "name: full-push\n\non:\n  schedule:\n    - cron: \"0 */4 * * *\"\n  workflow_dispatch:\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: google/tsunami-scanner-full\n\njobs:\n  build-and-push-image:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n      packages: write\n      attestations: write\n      id-token: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Log in to the Container registry\n        uses: docker/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata for Docker\n        id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n\n      - name: Build and push Docker image\n        id: push\n        uses: docker/build-push-action@v6\n        with:\n          context: .\n          push: true\n          file: full.Dockerfile\n          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          labels: ${{ steps.meta.outputs.labels }}\n\n      - name: Generate artifact attestation\n        uses: actions/attest-build-provenance@v2\n        with:\n          subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          subject-digest: ${{ steps.push.outputs.digest }}\n          push-to-registry: true\n"
  },
  {
    "path": ".gitignore",
    "content": "# Gradle\nbuild\ngradle.properties\n.gradle\nlocal.properties\nout\n\n# IntelliJ IDEA\n.idea\n*.iml\n*.ipr\n*.iws\nclasses\n\n# Eclipse\n.classpath\n.factorypath\n.project\n.settings\nbin\neclipsebin\n\n# OS X\n.DS_Store\n\n# Emacs\n*~\n\\#*\\#\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Tsunami\n\n![build](https://github.com/google/tsunami-security-scanner/actions/workflows/core-build.yml/badge.svg)\n\nTsunami is a general purpose network security scanner with an extensible plugin\nsystem for detecting high severity vulnerabilities with high confidence.\n\nTo learn more about Tsunami, visit our\n[documentation](https://google.github.io/tsunami-security-scanner/).\n\nTsunami relies heavily on its plugin system to provide basic scanning\ncapabilities. All publicly available Tsunami plugins are hosted in a separate\n[google/tsunami-security-scanner-plugins](https://github.com/google/tsunami-security-scanner-plugins)\nrepository.\n\n## Quick start\n\nPlease see the documentation on how to\n[build and run Tsunami](https://google.github.io/tsunami-security-scanner/howto/howto)\n\n## Contributing\n\nRead how to\n[contribute to Tsunami](https://google.github.io/tsunami-security-scanner/contribute/).\n\n## License\n\nTsunami is released under the [Apache 2.0 license](LICENSE).\n\n```\nCopyright 2025 Google Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n\n## Disclaimers\n\nTsunami is not an official Google product.\n"
  },
  {
    "path": "build.gradle",
    "content": "// Current gradle version 6.5.\n\nplugins {\n    id 'net.ltgt.errorprone' apply false version \"4.2.0\"\n    id \"com.gradleup.shadow\" version \"8.3.6\"\n}\n\nsubprojects {\n    apply plugin: 'java'\n    apply plugin: 'maven-publish'\n    apply plugin: 'idea'\n\n    apply plugin: 'net.ltgt.errorprone'\n    apply plugin: 'com.gradleup.shadow'\n\n    group = 'com.google.tsunami'\n    version = '0.1.1-SNAPSHOT' // Current Tsunami version\n\n    repositories {\n        maven { // The google mirror is less flaky than mavenCentral()\n            url 'https://maven-central.storage-download.googleapis.com/repos/central/data/'\n        }\n\n        mavenCentral()\n        mavenLocal()\n    }\n\n    if (rootProject.properties.get('errorProne', true)) {\n        dependencies {\n            errorprone \"com.google.errorprone:error_prone_core:2.38.0\"\n            errorproneJavac 'com.google.errorprone:javac:9+181-r4173-1'\n        }\n\n        // Disable ErrorProne for all generated codes.\n        tasks.withType(JavaCompile).configureEach {\n            options.errorprone.disableWarningsInGeneratedCode = false\n            options.errorprone.excludedPaths = '.*/build/generated/.*'\n        }\n    } else {\n        // Disable Error Prone\n        allprojects {\n            afterEvaluate { project ->\n                project.tasks.withType(JavaCompile) {\n                    options.errorprone.enabled = false\n                }\n            }\n        }\n    }\n\n    plugins.withId('java') {\n        sourceCompatibility = JavaVersion.VERSION_21\n        targetCompatibility = JavaVersion.VERSION_21\n\n        java.withJavadocJar()\n        java.withSourcesJar()\n\n        jar.manifest {\n            attributes('Implementation-Title': name,\n                    'Implementation-Version': version,\n                    'Built-By': System.getProperty('user.name'),\n                    'Built-JDK': System.getProperty('java.version'),\n                    'Source-Compatibility': sourceCompatibility,\n                    'Target-Compatibility': targetCompatibility)\n        }\n\n        // Log stacktrace to console when test fails.\n        test {\n            testLogging {\n                exceptionFormat = 'full'\n                showExceptions true\n                showCauses true\n                showStackTraces true\n            }\n            maxHeapSize = '1500m'\n        }\n    }\n\n    plugins.withId('maven-publish') {\n        shadowJar {\n            archiveClassifier = null\n        }\n    }\n}\n"
  },
  {
    "path": "common/README.md",
    "content": "# Tsunami Common Libraries\n\n## Overview\n\nThis module provides a set of common libraries and utilities for Tsunami\nSecurity Scanner.\n"
  },
  {
    "path": "common/build.gradle",
    "content": "description = 'Tsunami: Common'\n\ndependencies {\n    implementation project(':tsunami-proto')\n\n    implementation \"com.beust:jcommander:1.48\"\n    implementation \"com.google.auto.value:auto-value-annotations:1.11.0\"\n    implementation \"com.google.cloud:google-cloud-storage:1.103.1\"\n    implementation \"com.google.code.gson:gson:2.10.1\"\n    implementation \"com.google.flogger:flogger-system-backend:0.9\"\n    implementation \"com.google.flogger:flogger:0.9\"\n    implementation \"com.google.flogger:google-extensions:0.9\"\n    implementation \"com.google.guava:guava:33.0.0-jre\"\n    implementation \"com.google.inject:guice:6.0.0\"\n    implementation \"com.google.inject.extensions:guice-assistedinject:6.0.0\"\n    implementation \"com.google.truth:truth:1.4.4\"\n    implementation \"com.squareup.okhttp3:okhttp:3.12.0\"\n    implementation \"io.github.classgraph:classgraph:4.8.65\"\n    implementation \"org.yaml:snakeyaml:1.26\"\n\n    runtimeOnly \"com.mysql:mysql-connector-j:8.0.33\"\n    runtimeOnly \"org.apache.hive:hive-jdbc:4.0.1\"\n    runtimeOnly \"org.postgresql:postgresql:42.6.0\"\n\n    annotationProcessor \"com.google.auto.value:auto-value:1.10.4\"\n    testAnnotationProcessor \"com.google.auto.value:auto-value:1.10.4\"\n\n    testImplementation \"com.google.guava:guava-testlib:33.0.0-jre\"\n    testImplementation \"com.google.truth:truth:1.4.4\"\n    testImplementation \"com.google.truth.extensions:truth-java8-extension:1.4.4\"\n    testImplementation \"com.google.truth.extensions:truth-proto-extension:1.4.4\"\n    testImplementation \"com.squareup.okhttp3:mockwebserver:3.12.0\"\n    testImplementation \"junit:junit:4.13.2\"\n    testImplementation \"org.mockito:mockito-core:5.18.0\"\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/ErrorCode.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common;\n\n/** Error codes for Tsunami scanner executions. */\npublic enum ErrorCode {\n  CONFIG_ERROR,\n  PLUGIN_EXECUTION_ERROR,\n  WORKFLOW_ERROR,\n  LANGUAGE_SERVER_ERROR,\n\n  UNKNOWN;\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/TsunamiException.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.base.Strings;\n\n/** Base exception definition of all Tsunami execution errors. */\npublic class TsunamiException extends RuntimeException {\n  private final ErrorCode errorCode;\n\n  public TsunamiException() {\n    this(ErrorCode.UNKNOWN);\n  }\n\n  public TsunamiException(ErrorCode errorCode) {\n    this(errorCode, null);\n  }\n\n  public TsunamiException(ErrorCode errorCode, String message) {\n    this(errorCode, message, null);\n  }\n\n  public TsunamiException(ErrorCode errorCode, String message, Throwable cause) {\n    super(buildExceptionMessage(checkNotNull(errorCode), message), cause);\n    this.errorCode = errorCode;\n  }\n\n  private static String buildExceptionMessage(ErrorCode errorCode, String message) {\n    StringBuilder exceptionMessageBuilder = new StringBuilder();\n    exceptionMessageBuilder.append(\"(Tsunami error \").append(errorCode).append(\")\");\n    if (!Strings.isNullOrEmpty(message)) {\n      exceptionMessageBuilder.append(\": \").append(message);\n    }\n    return exceptionMessageBuilder.toString();\n  }\n\n  public ErrorCode getErrorCode() {\n    return errorCode;\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/cli/CliOption.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.cli;\n\n/**\n * A marker interface for a subset of command line options used in Tsunami modules.\n *\n * <p>Client should ALWAYS mark its options with this interface so that they can be identified by\n * {@link io.github.classgraph.ClassGraph}. All implementations of {@link CliOption} should provide\n * a no argument constructor or omit constructors completely.\n */\npublic interface CliOption {\n\n  /**\n   * Performs additional validation logic across options defined in the same {@link CliOption}.\n   *\n   * <p>If validation failed, simply throw a {@link com.beust.jcommander.ParameterException}.\n   */\n  void validate();\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/cli/CliOptionsModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.cli;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.beust.jcommander.JCommander;\nimport com.beust.jcommander.ParameterException;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.inject.AbstractModule;\nimport io.github.classgraph.ClassInfo;\nimport io.github.classgraph.ScanResult;\nimport java.lang.reflect.Constructor;\n\n/**\n * A Guice module that parses CLI arguments for all {@link CliOption} implementations at runtime.\n *\n * <p>This module relies on the {@link io.github.classgraph.ClassGraph} scan results to identify all\n * {@link CliOption} implementations at runtime. Each implementation is bound to a singleton object\n * of that impl and registered to JCommander for CLI parsing.\n */\npublic final class CliOptionsModule extends AbstractModule {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n  private static final String CLI_OPTION_INTERFACE = \"com.google.tsunami.common.cli.CliOption\";\n\n  private final ScanResult scanResult;\n  private final String[] args;\n  private final JCommander jCommander;\n\n  public CliOptionsModule(ScanResult scanResult, String programName, String[] args) {\n    this.scanResult = checkNotNull(scanResult);\n    this.args = checkNotNull(args);\n    this.jCommander = new JCommander();\n\n    jCommander.setProgramName(programName);\n  }\n\n  @Override\n  protected void configure() {\n    // For each CliOption installed at runtime, bind a singleton instance and register the instance\n    // to JCommander for parsing.\n    ImmutableList.Builder<CliOption> cliOptions = ImmutableList.builder();\n    for (ClassInfo classInfo :\n        scanResult\n            .getClassesImplementing(CLI_OPTION_INTERFACE)\n            .filter(classInfo -> !classInfo.isInterface())) {\n      logger.atInfo().log(\"Found CliOption: %s\", classInfo.getName());\n\n      CliOption cliOption = bindCliOption(classInfo.loadClass(CliOption.class));\n      jCommander.addObject(cliOption);\n      cliOptions.add(cliOption);\n    }\n\n    // Parse command arguments or die.\n    try {\n      jCommander.parse(args);\n      cliOptions.build().forEach(CliOption::validate);\n    } catch (ParameterException e) {\n      jCommander.usage();\n      throw e;\n    }\n  }\n\n  private <T> T bindCliOption(Class<T> cliOptionClass) {\n    try {\n      Constructor<T> cliOptionCtor = cliOptionClass.getDeclaredConstructor();\n      // Always create an instance of the CliOption regardless of scope.\n      cliOptionCtor.setAccessible(true);\n      T cliOption = cliOptionCtor.newInstance();\n      bind(cliOptionClass).toInstance(cliOption);\n      return cliOption;\n    } catch (ReflectiveOperationException e) {\n      throw new AssertionError(\n          String.format(\n              \"CliOption '%s' must be constructable via a no-argument constructor\",\n              cliOptionClass.getTypeName()),\n          e);\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/command/CommandExecutionThreadPool.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.command;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport javax.inject.Qualifier;\n\n/** Annotates the thread pool to use for executing native commands. */\n@Qualifier\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface CommandExecutionThreadPool {}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/command/CommandExecutor.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.command;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Joiner;\nimport com.google.common.flogger.GoogleLogger;\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport javax.annotation.Nullable;\n\n/** Helper class that handles running a command line and collecting output and errors. */\n// TODO(b/145315535): reimplement this class so that it is:\n// 1. guice injectable in order to hide Executor interface.\n// 2. unit testable to prevent actually executing commands in test.\npublic class CommandExecutor {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n  private static final Joiner COMMAND_ARGS_JOINER = Joiner.on(\" \");\n\n  private final ProcessBuilder processBuilder;\n  private final String[] args;\n  private Process process;\n  @Nullable private String output;\n  @Nullable private String error;\n\n  public CommandExecutor(String... args) {\n    this.args = checkNotNull(args);\n    this.processBuilder = new ProcessBuilder(args);\n  }\n\n  /*\n   * Executes the command and uses a {@link ThreadPoolExecutor} to collect output and error.\n   *\n   * This is a convenience method for testing purposes only as the executor is not shared and\n   * therefore defeats the purpose of having a cached thread pool.\n   */\n  @VisibleForTesting\n  Process execute() throws IOException, InterruptedException, ExecutionException {\n    // Nmap is a long running process and the collectStream method is a blocking method.\n    // By default CompletableFuture uses ForkJoinPool, which is for suitable short\n    // non-blocking operations.\n    Executor executor = Executors.newCachedThreadPool();\n    return execute(executor);\n  }\n\n  /**\n   * Starts the command and uses the passed executor to collect output and error streams.\n   *\n   * <p>IMPORTANT: The stream collection uses an IO blocking method and the passed executor must be\n   * well suited for the task. {@link ThreadPoolExecutor} is a viable option.\n   *\n   * @param executor The executor to collect output and error streams.\n   * @return Started {@link Process} object.\n   * @throws IOException if an I/O error occurs when starting the command executing process.\n   * @throws InterruptedException if interrupted while waiting for the command's output.\n   * @throws ExecutionException if the command execution failed.\n   */\n  public Process execute(Executor executor)\n      throws IOException, InterruptedException, ExecutionException {\n    logger.atInfo().log(\"Executing the following command: '%s'\", COMMAND_ARGS_JOINER.join(args));\n    process = processBuilder.start();\n    output =\n        CompletableFuture.supplyAsync(() -> collectStream(process.getInputStream()), executor)\n            .get();\n    error =\n        CompletableFuture.supplyAsync(() -> collectStream(process.getErrorStream()), executor)\n            .get();\n    return process;\n  }\n\n  /**\n   * Starts the command and asynchronously collect output and error.\n   *\n   * @return Started {@link Process} object.\n   * @throws IOException if an I/O error occurs when starting the command executing process.\n   * @throws InterruptedException if interrupted while waiting for the command's output.\n   * @throws ExecutionException if the command execution failed.\n   */\n  public Process executeAsync() throws IOException, InterruptedException, ExecutionException {\n    logger.atInfo().log(\"Executing the following command: '%s'\", COMMAND_ARGS_JOINER.join(args));\n    process = processBuilder.inheritIO().start();\n    return process;\n  }\n\n  /**\n   * Starts the command without collecting output and error streams.\n   *\n   * @return Started {@link Process} object.\n   * @throws IOException if an I/O error occurs when starting the command executing process.\n   * @throws InterruptedException if interrupted while starting the command executing process.\n   * @throws ExecutionException if the command execution failed.\n   */\n  public Process executeWithNoStreamCollection()\n      throws IOException, InterruptedException, ExecutionException {\n    logger.atInfo().log(\"Executing the following command: '%s'\", COMMAND_ARGS_JOINER.join(args));\n    process = processBuilder.start();\n    return process;\n  }\n\n  @Nullable\n  public String getOutput() {\n    return output;\n  }\n\n  @Nullable\n  public String getError() {\n    return error;\n  }\n\n  private static String collectStream(InputStream stream) {\n    StringBuilder stringBuilder = new StringBuilder();\n    try {\n      String output;\n      BufferedReader streamReader = new BufferedReader(new InputStreamReader(stream, UTF_8));\n      while ((output = streamReader.readLine()) != null) {\n        stringBuilder.append(output);\n        stringBuilder.append(\"\\n\");\n      }\n    } catch (IOException e) {\n      logger.atWarning().withCause(e).log(\"Error collecting output stream from command execution.\");\n    }\n    return stringBuilder.toString();\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/command/CommandExecutorFactory.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.command;\n\n/** Utility class to simplify the creation and testing of {@link CommandExecutor} instances. */\npublic class CommandExecutorFactory {\n\n  private static CommandExecutor instance;\n\n  /**\n   * Sets an executor instance that will be returned by all future calls to {@link\n   * CommandExecutorFactory#create(String...)}\n   *\n   * @param executor The {@link CommandExecutor} returned by this factory.\n   */\n  public static void setInstance(CommandExecutor executor) {\n    instance = executor;\n  }\n\n  /**\n   * Creates a new {@link CommandExecutor} if none is set.\n   *\n   * @param args List of arguments to pass to the newly created {@link CommandExecutor}.\n   * @return the {@link CommandExecutor} instance created by this factory.\n   */\n  public static CommandExecutor create(String... args) {\n    if (instance == null) {\n      return new CommandExecutor(args);\n    }\n    return instance;\n  }\n\n  private CommandExecutorFactory() {}\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/command/CommandExecutorModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.command;\n\nimport com.google.inject.AbstractModule;\nimport com.google.tsunami.common.concurrent.ThreadPoolModule;\n\n/** Installs dependencies used by {@link CommandExecutor}. */\npublic class CommandExecutorModule extends AbstractModule {\n\n  @Override\n  protected void configure() {\n    install(\n        new ThreadPoolModule.Builder()\n            .setName(\"CommandExecutor\")\n            .setCoreSize(4)\n            .setMaxSize(8)\n            .setQueueCapacity(32)\n            .setDaemon(true)\n            .setPriority(Thread.NORM_PRIORITY)\n            .setAnnotation(CommandExecutionThreadPool.class)\n            .build());\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/concurrent/BaseThreadPoolModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.concurrent;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.base.Preconditions.checkState;\n\nimport com.google.common.base.Strings;\nimport com.google.common.util.concurrent.ListeningExecutorService;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport com.google.common.util.concurrent.ThreadFactoryBuilder;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Key;\nimport java.lang.annotation.Annotation;\nimport java.time.Duration;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.RejectedExecutionHandler;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor.AbortPolicy;\nimport java.util.concurrent.TimeUnit;\nimport javax.inject.Provider;\nimport org.checkerframework.checker.nullness.qual.Nullable;\n\n/**\n * The base module for binding a thread pool.\n *\n * <p>This module is essentially a thin wrapper around {@link ThreadFactoryBuilder} and the\n * corresponding {@link ExecutorService} families. Based on the intended usage, it is expected that\n * subclasses of this module should provides bindings to a concrete thread pool implementation of\n * {@link ExecutorService}. This base module wraps the actual {@link ExecutorService} implementation\n * in order to support {@link com.google.common.util.concurrent.ListenableFuture} usage in the code\n * base.\n *\n * @param <ExecutorServiceT> The expected thread pool implementation, must be a subclass of {@link\n *     ListeningExecutorService}.\n */\nabstract class BaseThreadPoolModule<ExecutorServiceT extends ListeningExecutorService>\n    extends AbstractModule {\n  private final ThreadFactory factory;\n  private final int maxSize;\n  private final int coreSize;\n  private final long keepAliveSeconds;\n  private final @Nullable Duration shutdownDelay;\n  private final Key<ExecutorServiceT> key;\n  private final Class<ExecutorServiceT> executorServiceTypeClass;\n  private final RejectedExecutionHandler rejectedExecutionHandler;\n\n  BaseThreadPoolModule(BaseThreadPoolModuleBuilder<ExecutorServiceT, ?> builder) {\n    checkNotNull(builder);\n\n    this.factory = builder.factoryBuilder.build();\n    this.maxSize = builder.maxSize;\n    this.coreSize = builder.coreSize;\n    this.keepAliveSeconds = builder.keepAliveSeconds;\n    this.shutdownDelay = builder.daemon ? builder.shutdownDelay : null;\n    this.key = builder.key;\n    this.executorServiceTypeClass = builder.executorServiceTypeClass;\n    this.rejectedExecutionHandler = builder.rejectedExecutionHandler;\n  }\n\n  @Override\n  protected final void configure() {\n    configureThreadPool(key);\n  }\n\n  /** Subclasses should override this method for providing Guice bindings. */\n  abstract void configureThreadPool(Key<ExecutorServiceT> key);\n\n  /** Base {@link Provider} implementation for providing the target thread pool. */\n  abstract class BaseThreadPoolProvider implements Provider<ExecutorServiceT> {\n    abstract ExecutorService createThreadPool(\n        int coreSize,\n        int maxSize,\n        long keepAliveSeconds,\n        ThreadFactory factory,\n        RejectedExecutionHandler rejectedExecutionHandler);\n\n    @Override\n    public final ExecutorServiceT get() {\n      ExecutorService service =\n          createThreadPool(coreSize, maxSize, keepAliveSeconds, factory, rejectedExecutionHandler);\n\n      if (shutdownDelay != null) {\n        MoreExecutors.addDelayedShutdownHook(\n            service, shutdownDelay.toMillis(), TimeUnit.MILLISECONDS);\n      }\n      return executorServiceTypeClass.cast(MoreExecutors.listeningDecorator(service));\n    }\n  }\n\n  /** Base Builder for {@link BaseThreadPoolModule}. */\n  abstract static class BaseThreadPoolModuleBuilder<\n      ExecutorServiceT extends ListeningExecutorService,\n      BuilderImplT extends BaseThreadPoolModuleBuilder<ExecutorServiceT, BuilderImplT>> {\n    protected final ThreadFactoryBuilder factoryBuilder = new ThreadFactoryBuilder();\n    protected String name;\n    protected int maxSize;\n    protected int coreSize;\n    protected long keepAliveSeconds = 60L;\n    protected boolean daemon;\n    protected Duration shutdownDelay;\n    protected Key<ExecutorServiceT> key;\n    protected final Class<ExecutorServiceT> executorServiceTypeClass;\n    protected RejectedExecutionHandler rejectedExecutionHandler = new AbortPolicy();\n\n    BaseThreadPoolModuleBuilder(Class<ExecutorServiceT> executorServiceTypeClass) {\n      this.executorServiceTypeClass = checkNotNull(executorServiceTypeClass);\n    }\n\n    abstract BuilderImplT self();\n\n    /**\n     * Sets the name used to name the threads; automatically suffixed with \"-%s\"to incorporate the\n     * thread number\n     *\n     * @param name the name of the thread pool.\n     * @return the Builder instance itself.\n     */\n    public BuilderImplT setName(String name) {\n      checkArgument(!Strings.isNullOrEmpty(name), \"Name should not be empty\");\n      this.name = name;\n      return self();\n    }\n\n    /**\n     * Sets the maximum number of threads allowed in the pool; value should be positive.\n     *\n     * @param maxSize the maximum number of threads allowed in this thread pool.\n     * @return the Builder instance itself.\n     */\n    BuilderImplT setMaxSize(int maxSize) {\n      checkArgument(maxSize > 0, \"Max thread pool size should be positive.\");\n      this.maxSize = maxSize;\n      return self();\n    }\n\n    /**\n     * Sets the number of threads to keep in the pool.\n     *\n     * @param coreSize the minimum number of threads to keep alive in this thread pool.\n     * @return the Builder instance itself.\n     */\n    BuilderImplT setCoreSize(int coreSize) {\n      checkArgument(coreSize >= 0, \"The core pool size should be non-negative.\");\n      this.coreSize = coreSize;\n      return self();\n    }\n\n    /**\n     * Sets the keep alive time in seconds for the threads not in core pool.\n     *\n     * @param keepAliveSeconds the maximum number of seconds an idle thread in this pool can keep\n     *     alive before being terminated.\n     * @return the Builder instance itself.\n     */\n    public BuilderImplT setKeepAliveSeconds(long keepAliveSeconds) {\n      checkArgument(keepAliveSeconds >= 0, \"The keep alive time should be non-negative.\");\n      this.keepAliveSeconds = keepAliveSeconds;\n      return self();\n    }\n\n    /**\n     * Sets whether or not new threads created by the pool will be daemon threads.*\n     *\n     * @param daemon whether threads created in this pool are daemon threads.\n     * @return the Builder instance itself.\n     */\n    public BuilderImplT setDaemon(boolean daemon) {\n      factoryBuilder.setDaemon(daemon);\n      this.daemon = daemon;\n      return self();\n    }\n\n    /**\n     * Sets how long the JVM should wait to exit for daemon threads to complete.\n     *\n     * <p>This has no effect if the pool does not use daemon threads.\n     *\n     * @param shutdownDelay the delay enforced during the thread pool shutdown.\n     * @return the Builder instance itself.\n     */\n    public BuilderImplT setDelayedShutdown(Duration shutdownDelay) {\n      this.shutdownDelay = checkNotNull(shutdownDelay);\n      return self();\n    }\n\n    /**\n     * Sets the priority for threads created by the pool.\n     *\n     * @param priority the priority of the threads created by this pool.\n     * @return the Builder instance itself.\n     */\n    public BuilderImplT setPriority(int priority) {\n      factoryBuilder.setPriority(priority);\n      return self();\n    }\n\n    /**\n     * Sets the binding annotation.\n     *\n     * @param annotation the Guice binding annotation for this thread pool.\n     * @return the Builder instance itself.\n     */\n    public BuilderImplT setAnnotation(Annotation annotation) {\n      key = Key.get(executorServiceTypeClass, checkNotNull(annotation));\n      return self();\n    }\n\n    /**\n     * Sets the binding annotation.\n     *\n     * @param annotationClass the Guice binding annotation class for this thread pool.\n     * @return the Builder instance itself.\n     */\n    public BuilderImplT setAnnotation(Class<? extends Annotation> annotationClass) {\n      key = Key.get(executorServiceTypeClass, checkNotNull(annotationClass));\n      return self();\n    }\n\n    /**\n     * Sets the handler to use when thread execution is blocked due to thread bounds and queue\n     * capacities are reached.\n     *\n     * <p>By default, {@link AbortPolicy} is used for rejected execution, which throws the {@link\n     * java.util.concurrent.RejectedExecutionException}.\n     *\n     * @param rejectedExecutionHandler A handler for tasks that cannot be executed by this thread\n     *     pool.\n     * @return the Builder instance itself.\n     */\n    public BuilderImplT setRejectedExecutionHandler(\n        RejectedExecutionHandler rejectedExecutionHandler) {\n      this.rejectedExecutionHandler = checkNotNull(rejectedExecutionHandler);\n      return self();\n    }\n\n    final void validateAll() {\n      checkState(!Strings.isNullOrEmpty(name), \"Name is required.\");\n      checkState(\n          maxSize > 0,\n          \"Max thread pool size must be positive. Did you forget setting maximum thread pool size\"\n              + \" by calling setMaxSize?\");\n      checkState(\n          coreSize <= maxSize, \"Thread pool core size should be less than or equal to max size.\");\n      checkState(key != null, \"Annotation is required.\");\n\n      validate();\n    }\n\n    abstract void validate();\n\n    public final AbstractModule build() {\n      validateAll();\n      return newModule();\n    }\n\n    abstract AbstractModule newModule();\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/concurrent/ScheduledThreadPoolModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.concurrent;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static java.util.concurrent.TimeUnit.SECONDS;\n\nimport com.google.common.util.concurrent.ListeningScheduledExecutorService;\nimport com.google.inject.Key;\nimport java.util.concurrent.RejectedExecutionHandler;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport java.util.concurrent.ThreadFactory;\nimport javax.inject.Singleton;\n\n/**\n * A helper module for binding a scheduled thread pool. The module will bind a {@link\n * ScheduledExecutorService} and a {@link ListeningScheduledExecutorService} to a singleton thread\n * pool that is annotated with the annotation passed to the builder.\n */\npublic final class ScheduledThreadPoolModule\n    extends BaseThreadPoolModule<ListeningScheduledExecutorService> {\n\n  ScheduledThreadPoolModule(Builder builder) {\n    super(builder);\n  }\n\n  @Override\n  void configureThreadPool(Key<ListeningScheduledExecutorService> key) {\n    bind(key.ofType(ScheduledExecutorService.class)).to(key);\n    bind(key).toProvider(new ScheduledThreadPoolProvider()).in(Singleton.class);\n  }\n\n  private final class ScheduledThreadPoolProvider extends BaseThreadPoolProvider {\n\n    @Override\n    ScheduledThreadPoolExecutor createThreadPool(\n        int coreSize,\n        int maxSize,\n        long keepAliveSeconds,\n        ThreadFactory factory,\n        RejectedExecutionHandler rejectedExecutionHandler) {\n      ScheduledThreadPoolExecutor scheduledThreadPoolExecutor =\n          new ScheduledThreadPoolExecutor(coreSize, factory, rejectedExecutionHandler);\n      scheduledThreadPoolExecutor.setMaximumPoolSize(maxSize);\n      scheduledThreadPoolExecutor.setKeepAliveTime(keepAliveSeconds, SECONDS);\n      return scheduledThreadPoolExecutor;\n    }\n  }\n\n  /**\n   * Builder for {@link ScheduledThreadPoolModule}.\n   *\n   * <p>NOTE: Unlike {@link ThreadPoolModule}, {@link ScheduledThreadPoolExecutor} acts as a\n   * fixed-sized pool using {@code corePoolSize} threads and an unbounded queue. So this builder\n   * only allows users to set a fixed thread pool size.\n   */\n  public static final class Builder\n      extends BaseThreadPoolModuleBuilder<ListeningScheduledExecutorService, Builder> {\n\n    public Builder() {\n      super(ListeningScheduledExecutorService.class);\n    }\n\n    @Override\n    Builder self() {\n      return this;\n    }\n\n    /**\n     * Sets the size of the thread pool.\n     *\n     * @param size the size of the thread pool.\n     * @return the {@link Builder} instance itself.\n     */\n    public Builder setSize(int size) {\n      checkArgument(size > 0, \"Thread pool size should be positive.\");\n      setCoreSize(size);\n      setMaxSize(size);\n      return this;\n    }\n\n    @Override\n    void validate() {}\n\n    @Override\n    ScheduledThreadPoolModule newModule() {\n      return new ScheduledThreadPoolModule(this);\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/concurrent/ThreadPoolModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.concurrent;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.base.Preconditions.checkState;\n\nimport com.google.common.util.concurrent.ListeningExecutorService;\nimport com.google.inject.Key;\nimport com.google.inject.Singleton;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.RejectedExecutionHandler;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport org.checkerframework.checker.nullness.qual.Nullable;\n\n/**\n * A helper module for binding a thread pool. The module will bind an {@link Executor}, {@link\n * ExecutorService} and {@link ListeningExecutorService} annotated with the annotation passed to the\n * builder.\n */\npublic final class ThreadPoolModule extends BaseThreadPoolModule<ListeningExecutorService> {\n  private final BlockingQueue<Runnable> blockingQueue;\n\n  private ThreadPoolModule(Builder builder) {\n    super(checkNotNull(builder));\n\n    this.blockingQueue = builder.getBlockingQueue();\n  }\n\n  @Override\n  void configureThreadPool(Key<ListeningExecutorService> key) {\n    bind(key.ofType(Executor.class)).to(key);\n    bind(key.ofType(ExecutorService.class)).to(key);\n    bind(key).toProvider(new ThreadPoolProvider()).in(Singleton.class);\n  }\n\n  private final class ThreadPoolProvider extends BaseThreadPoolProvider {\n    @Override\n    ThreadPoolExecutor createThreadPool(\n        int coreSize,\n        int maxSize,\n        long keepAliveSeconds,\n        ThreadFactory factory,\n        RejectedExecutionHandler rejectedExecutionHandler) {\n      return new ThreadPoolExecutor(\n          coreSize,\n          maxSize,\n          keepAliveSeconds,\n          TimeUnit.SECONDS,\n          blockingQueue,\n          factory,\n          rejectedExecutionHandler);\n    }\n  }\n\n  /** Builder for {@link ThreadPoolModule}. */\n  public static final class Builder\n      extends BaseThreadPoolModuleBuilder<ListeningExecutorService, Builder> {\n    private int queueCapacity = Integer.MAX_VALUE;\n    private @Nullable BlockingQueue<Runnable> blockingQueue;\n\n    public Builder() {\n      super(ListeningExecutorService.class);\n    }\n\n    @Override\n    Builder self() {\n      return this;\n    }\n\n    /** {@inheritDoc} */\n    @Override\n    public Builder setMaxSize(int maxSize) {\n      return super.setMaxSize(maxSize);\n    }\n\n    /** {@inheritDoc} */\n    @Override\n    public Builder setCoreSize(int coreSize) {\n      return super.setCoreSize(coreSize);\n    }\n\n    /**\n     * Sets the queue capacity for the thread pool.\n     *\n     * <p>NOTE: Users should NOT specify both this value and the {@link BlockingQueue} via {@link\n     * #setBlockingQueue}.\n     *\n     * <p>By default, {@link SynchronousQueue} will be used when {@code queueCapacity} is set to\n     * zero. Otherwise a {@link LinkedBlockingQueue} will be used.\n     *\n     * @param queueCapacity the capacity of the task queue.\n     * @return the Builder instance itself.\n     */\n    public Builder setQueueCapacity(int queueCapacity) {\n      checkArgument(queueCapacity >= 0, \"The queue capacity should be non-negative value.\");\n      this.queueCapacity = queueCapacity;\n      return this;\n    }\n\n    /**\n     * Sets the {@link BlockingQueue} to use for holding tasks before they are executed.\n     *\n     * <p>NOTE: Do NOT set both {@link BlockingQueue} and {@code queueCapacity}. Only use this\n     * method to override the default {@link BlockingQueue} choice. See comments of {@link\n     * #getBlockingQueue} for which {@link BlockingQueue} is used by default.\n     *\n     * @param blockingQueue a {@link BlockingQueue} used for holding tasks before executing.\n     * @return the Builder instance itself.\n     */\n    public Builder setBlockingQueue(BlockingQueue<Runnable> blockingQueue) {\n      this.blockingQueue = checkNotNull(blockingQueue);\n      return this;\n    }\n\n    private BlockingQueue<Runnable> getBlockingQueue() {\n      if (blockingQueue == null) {\n        return queueCapacity == 0\n            ? new SynchronousQueue<>()\n            : new LinkedBlockingQueue<>(queueCapacity);\n      }\n      return blockingQueue;\n    }\n\n    private boolean isBoundedQueue() {\n      return (blockingQueue == null ? queueCapacity : blockingQueue.remainingCapacity())\n          < Integer.MAX_VALUE;\n    }\n\n    @Override\n    void validate() {\n      checkState(\n          blockingQueue == null || queueCapacity == Integer.MAX_VALUE,\n          \"Both custom BlockingQueue and queue capacity are specified.\");\n      if (coreSize < maxSize) {\n        checkState(\n            isBoundedQueue(),\n            \"Finite capacity queue should be set when the core pool size is less than max pool\"\n                + \" size. ThreadPoolExecutor will only create new threads past core size when the\"\n                + \" queue is full.\");\n      }\n    }\n\n    @Override\n    ThreadPoolModule newModule() {\n      return new ThreadPoolModule(this);\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/config/ConfigException.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.config;\n\nimport com.google.tsunami.common.ErrorCode;\nimport com.google.tsunami.common.TsunamiException;\n\n/** Exception when handling Tsunami configs. */\npublic class ConfigException extends TsunamiException {\n  public ConfigException(String message) {\n    super(ErrorCode.CONFIG_ERROR, message);\n  }\n\n  public ConfigException(String message, Throwable cause) {\n    super(ErrorCode.CONFIG_ERROR, message, cause);\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/config/ConfigLoader.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.config;\n\n/** Config loader interface that load Tsunami configs from certain data sources. */\npublic interface ConfigLoader {\n\n  /**\n   * Load a {@link TsunamiConfig} object from certain data source.\n   *\n   * @return the loaded {@link TsunamiConfig} object.\n   */\n  TsunamiConfig loadConfig();\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/config/ConfigModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.config;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.inject.AbstractModule;\nimport io.github.classgraph.ClassInfo;\nimport io.github.classgraph.ScanResult;\n\n/**\n * A Guice module that binds all Tsunami config objects at runtime.\n *\n * <p>This module relies on the {@link io.github.classgraph.ClassGraph} scan results to identify all\n * Tsunami config objects annotated by the {@link\n * com.google.tsunami.common.config.annotations.ConfigProperties} annotation. Each config class is\n * bound to a singleton object whose fields are populated from the Tsunami config file.\n */\npublic final class ConfigModule extends AbstractModule {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n  private static final String CONFIG_PROPERTIES_ANNOTATION =\n      \"com.google.tsunami.common.config.annotations.ConfigProperties\";\n\n  private final ScanResult scanResult;\n  private final TsunamiConfig tsunamiConfig;\n\n  public ConfigModule(ScanResult scanResult, TsunamiConfig tsunamiConfig) {\n    this.scanResult = checkNotNull(scanResult);\n    this.tsunamiConfig = checkNotNull(tsunamiConfig);\n  }\n\n  @Override\n  protected void configure() {\n    bind(TsunamiConfig.class).toInstance(tsunamiConfig);\n\n    for (ClassInfo configClass :\n        scanResult\n            .getClassesWithAnnotation(CONFIG_PROPERTIES_ANNOTATION)\n            .filter(classInfo -> !classInfo.isAbstract())) {\n      logger.atInfo().log(\"Found Tsunami config class: %s\", configClass.getName());\n\n      bindConfigClass(getConfigPrefix(configClass), configClass.loadClass());\n    }\n  }\n\n  private <T> void bindConfigClass(String configPrefix, Class<T> configClass) {\n    T configObject = tsunamiConfig.getConfig(configPrefix, configClass);\n    bind(configClass).toInstance(configObject);\n  }\n\n  private static String getConfigPrefix(ClassInfo configClass) {\n    Object configPrefix =\n        configClass\n            .getAnnotationInfo(CONFIG_PROPERTIES_ANNOTATION)\n            .getParameterValues()\n            .getValue(\"value\");\n    if (!(configPrefix instanceof String)) {\n      throw new AssertionError(\"SHOULD NEVER HAPPEN, ConfigProperties value is not a string.\");\n    }\n\n    return (String) configPrefix;\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/config/TsunamiConfig.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.config;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.base.CaseFormat;\nimport com.google.common.base.Converter;\nimport com.google.common.base.Splitter;\nimport com.google.common.collect.ImmutableMap;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.util.Map;\nimport java.util.Optional;\n\n/** A data holder for all Tsunami config data, including config files and Java system properties. */\npublic final class TsunamiConfig {\n  private static final Converter<String, String> FIELD_NAME_TO_LOWER_UNDERSCORE =\n      CaseFormat.LOWER_CAMEL.converterTo(CaseFormat.LOWER_UNDERSCORE);\n  private static final Splitter CONFIG_PATH_SPLITTER = Splitter.on('.').omitEmptyStrings();\n\n  private final ImmutableMap<String, Object> rawConfigData;\n\n  private TsunamiConfig(ImmutableMap<String, Object> rawConfigData) {\n    this.rawConfigData = checkNotNull(rawConfigData);\n  }\n\n  public ImmutableMap<String, Object> getRawConfigData() {\n    return rawConfigData;\n  }\n\n  public static TsunamiConfig fromYamlData(Map<String, Object> yamlConfig) {\n    return new TsunamiConfig(\n        yamlConfig == null ? ImmutableMap.of() : ImmutableMap.copyOf(yamlConfig));\n  }\n\n  public static Optional<String> getSystemProperty(String propertyName) {\n    return Optional.ofNullable(getSystemProperty(propertyName, null));\n  }\n\n  public static String getSystemProperty(String propertyName, String def) {\n    return System.getProperty(propertyName, def);\n  }\n\n  /**\n   * Get a config object with the given {@code configPrefix} and bind all config values to the\n   * requested {@code clazz}.\n   *\n   * <p>This code uses reflection to create the requested config object. The request type {@code T}\n   * must provide a no-argument or default constructor.\n   *\n   * @param configPrefix the prefix of the config to be read from.\n   * @param clazz the class of the returned config object.\n   * @param <T> actual config object type.\n   * @return an object whose field values are filled by the config data under the given {@code\n   *     configPrefix}.\n   */\n  public <T> T getConfig(String configPrefix, Class<T> clazz) {\n    checkNotNull(configPrefix);\n    checkNotNull(clazz);\n\n    Map<String, Object> configValue = readConfigValue(configPrefix);\n    return newConfigObject(clazz, configValue);\n  }\n\n  @SuppressWarnings(\"unchecked\") // We know Map key is always String from yaml file.\n  public ImmutableMap<String, Object> readConfigValue(String configPrefix) {\n    Map<String, Object> retrievedData = rawConfigData;\n\n    // Config prefixes are dot separated words list, e.g. example.config.prefix.\n    for (String configKey : CONFIG_PATH_SPLITTER.split(configPrefix)) {\n      // Requested data not found under configPrefix.\n      if (!retrievedData.containsKey(configKey)) {\n        return ImmutableMap.of();\n      }\n\n      Object configData = retrievedData.get(configKey);\n      if (!(configData instanceof Map)) {\n        throw new ConfigException(\n            String.format(\n                \"Unexpected data type for config '%s', expected '%s', got '%s'\",\n                configKey, Map.class, configData.getClass()));\n      }\n\n      retrievedData = (Map<String, Object>) configData;\n    }\n\n    return ImmutableMap.copyOf(retrievedData);\n  }\n\n  private static <T> T newConfigObject(Class<T> clazz, Map<String, Object> configValue) {\n    try {\n      Constructor<T> configObjectCtor = clazz.getDeclaredConstructor();\n      // Always create an instance of the config data regardless of scope.\n      configObjectCtor.setAccessible(true);\n      T configObject = configObjectCtor.newInstance();\n\n      // Fill each field of the configObject from configValue using the field name as key.\n      for (Field field : clazz.getDeclaredFields()) {\n        String fieldName = field.getName();\n        if (configValue.containsKey(fieldName)\n            || configValue.containsKey(FIELD_NAME_TO_LOWER_UNDERSCORE.convert(fieldName))) {\n          Object fieldValue =\n              Optional.ofNullable(configValue.get(fieldName))\n                  .orElse(configValue.get(FIELD_NAME_TO_LOWER_UNDERSCORE.convert(fieldName)));\n          field.setAccessible(true);\n          field.set(configObject, fieldValue);\n        }\n      }\n\n      return configObject;\n    } catch (ReflectiveOperationException e) {\n      // This is bad. Config objects cannot be created or config value cannot be assigned to the\n      // field, we throw assertion error and fail the execution.\n      throw new AssertionError(\n          String.format(\n              \"Unable to create new instance of '%s' using config value '%s'\", clazz, configValue),\n          e);\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/config/YamlConfigLoader.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.config;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.common.io.Files;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.util.Map;\nimport org.yaml.snakeyaml.LoaderOptions;\nimport org.yaml.snakeyaml.Yaml;\nimport org.yaml.snakeyaml.constructor.SafeConstructor;\n\n/** A {@link ConfigLoader} implementation that loads Tsunami configs from YAML file. */\npublic final class YamlConfigLoader implements ConfigLoader {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n  private static final String DEFAULT_CONFIG_FILE = \"tsunami.yaml\";\n\n  @Override\n  public TsunamiConfig loadConfig() {\n    Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));\n    Map<String, Object> rawYamlData = yaml.load(configFileReader());\n    return TsunamiConfig.fromYamlData(rawYamlData);\n  }\n\n  private static Reader configFileReader() {\n    String configFile =\n        TsunamiConfig.getSystemProperty(\"tsunami.config.location\").orElse(DEFAULT_CONFIG_FILE);\n\n    try {\n      return Files.newReader(new File(configFile), UTF_8);\n    } catch (FileNotFoundException e) {\n      logger.atWarning().log(\n          \"Unable to read config file '%s', default to empty config.\", configFile);\n      return new StringReader(\"\");\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/config/annotations/ConfigProperties.java",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.config.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * An annotation for marking a Tsunami config object that can be initialized from external config\n * files, e.g. a {@code .yaml} file.\n *\n * <p>This annotation is required for any config object in order for Tsunami initialization logic to\n * identify and automatically populate config properties.\n *\n * Example usage:\n *\n * <pre>{@code\n * {@literal @}ConfigProperties(\"example.config.location\")})\n * public class ExampleConfig {\n *   // ...\n * }\n * }</pre>\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface ConfigProperties {\n\n  /**\n   * The required prefix of the properties that should be bound to the annotated object.\n   *\n   * <p>A valid prefix is defined as dot separated words list (e.g. \"plugin.example.abc\"). Each dot\n   * separated segment represents a section within the config file. For example, given a YAML file\n   *\n   * <pre>{@code\n   * plugin:\n   *   example:\n   *     abc:\n   *       fieldA: valueA\n   *       fieldB: valueB\n   *     xyz:\n   *       fieldC: valueC\n   * }</pre>\n   *\n   * value {@code \"plugin.example.abc\"} will select {@code fieldA} and {@code fieldB} for config\n   * binding for the annotated class.\n   *\n   * @return the prefix of the config properties.\n   */\n  String value();\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/data/NetworkEndpointUtils.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * For any utility update, please consider if Python's network endpoint utils\n * (plugin_server/py/common/data/network_endpoint_utils.py) also needs the modification.\n */\npackage com.google.tsunami.common.data;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.net.HostAndPort;\nimport com.google.common.net.InetAddresses;\nimport com.google.tsunami.proto.AddressFamily;\nimport com.google.tsunami.proto.Hostname;\nimport com.google.tsunami.proto.IpAddress;\nimport com.google.tsunami.proto.NetworkEndpoint;\nimport com.google.tsunami.proto.Port;\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\n\n/** Static utility methods pertaining to {@link NetworkEndpoint} proto buffer. */\npublic final class NetworkEndpointUtils {\n  public static final int MAX_PORT_NUMBER = 65535;\n\n  private NetworkEndpointUtils() {}\n\n  public static boolean hasIpAddress(NetworkEndpoint networkEndpoint) {\n    return networkEndpoint.getType().equals(NetworkEndpoint.Type.IP)\n        || networkEndpoint.getType().equals(NetworkEndpoint.Type.IP_PORT)\n        || networkEndpoint.getType().equals(NetworkEndpoint.Type.IP_HOSTNAME)\n        || networkEndpoint.getType().equals(NetworkEndpoint.Type.IP_HOSTNAME_PORT);\n  }\n\n  public static boolean hasHostname(NetworkEndpoint networkEndpoint) {\n    return networkEndpoint.getType().equals(NetworkEndpoint.Type.HOSTNAME)\n        || networkEndpoint.getType().equals(NetworkEndpoint.Type.HOSTNAME_PORT)\n        || networkEndpoint.getType().equals(NetworkEndpoint.Type.IP_HOSTNAME)\n        || networkEndpoint.getType().equals(NetworkEndpoint.Type.IP_HOSTNAME_PORT);\n  }\n\n  public static boolean hasPort(NetworkEndpoint networkEndpoint) {\n    return networkEndpoint.getType().equals(NetworkEndpoint.Type.IP_PORT)\n        || networkEndpoint.getType().equals(NetworkEndpoint.Type.HOSTNAME_PORT)\n        || networkEndpoint.getType().equals(NetworkEndpoint.Type.IP_HOSTNAME_PORT);\n  }\n\n  public static boolean isIpV6Endpoint(NetworkEndpoint networkEndpoint) {\n    return hasIpAddress(networkEndpoint)\n        && networkEndpoint.getIpAddress().getAddressFamily().equals(AddressFamily.IPV6);\n  }\n\n  /**\n   * Converts the given {@link NetworkEndpoint} to its uri authority representation.\n   *\n   * <p>For example:\n   *\n   * <ul>\n   *   <li>ip_v4 = \"1.2.3.4\" -&gt; uri = \"1.2.3.4\"\n   *   <li>ip_v6 = \"3ffe::1\" -&gt; uri = \"[3ffe::1]\"\n   *   <li>host = \"localhost\" -&gt; url = \"localhost\"\n   *   <li>ip_v4 = \"1.2.3.4\", port = 8888 -&gt; uri = \"1.2.3.4:8888\"\n   *   <li>ip_v6 = \"3ffe::1\", port = 8888 -&gt; uri = \"[3ffe::1]:8888\"\n   *   <li>host = \"localhost\", port = 8888 -&gt; url = \"localhost:8888\"\n   * </ul>\n   *\n   * @param networkEndpoint the {@link NetworkEndpoint} instance to be converted.\n   * @return the URI authority converted from the {@link NetworkEndpoint} instance.\n   */\n  public static String toUriAuthority(NetworkEndpoint networkEndpoint) {\n    return toHostAndPort(networkEndpoint).toString();\n  }\n\n  public static HostAndPort toHostAndPort(NetworkEndpoint networkEndpoint) {\n    switch (networkEndpoint.getType()) {\n      case IP:\n        return HostAndPort.fromHost(networkEndpoint.getIpAddress().getAddress());\n      case IP_PORT:\n        return HostAndPort.fromParts(\n            networkEndpoint.getIpAddress().getAddress(), networkEndpoint.getPort().getPortNumber());\n      case HOSTNAME:\n      case IP_HOSTNAME:\n        return HostAndPort.fromHost(networkEndpoint.getHostname().getName());\n      case HOSTNAME_PORT:\n      case IP_HOSTNAME_PORT:\n        return HostAndPort.fromParts(\n            networkEndpoint.getHostname().getName(), networkEndpoint.getPort().getPortNumber());\n      case UNRECOGNIZED:\n      case TYPE_UNSPECIFIED:\n        throw new AssertionError(\"Type for NetworkEndpoint must be specified.\");\n    }\n\n    throw new AssertionError(\n        String.format(\n            \"Should never happen. Unchecked NetworkEndpoint type: %s\", networkEndpoint.getType()));\n  }\n\n  /**\n   * Creates a {@link NetworkEndpoint} proto buffer object from the given ip address.\n   *\n   * @param ipAddress the IP address of the network endpoint.\n   * @return the created {@link NetworkEndpoint} instance from the given IP address.\n   */\n  public static NetworkEndpoint forIp(String ipAddress) {\n    checkArgument(InetAddresses.isInetAddress(ipAddress), \"'%s' is not an IP address.\", ipAddress);\n\n    return NetworkEndpoint.newBuilder()\n        .setType(NetworkEndpoint.Type.IP)\n        .setIpAddress(\n            IpAddress.newBuilder()\n                .setAddressFamily(ipAddressFamily(ipAddress))\n                .setAddress(ipAddress))\n        .build();\n  }\n\n  /**\n   * Creates a {@link NetworkEndpoint} proto buffer object from the given ip address and port.\n   *\n   * @param ipAddress the IP address of the network endpoint.\n   * @param port the port number of the network endpoint\n   * @return the created {@link NetworkEndpoint} instance from the given IP and port.\n   */\n  public static NetworkEndpoint forIpAndPort(String ipAddress, int port) {\n    checkArgument(InetAddresses.isInetAddress(ipAddress), \"'%s' is not an IP address.\", ipAddress);\n    checkArgument(\n        0 <= port && port <= MAX_PORT_NUMBER,\n        \"Port out of range. Expected [0, %s], actual %s.\",\n        MAX_PORT_NUMBER,\n        port);\n\n    return forIp(ipAddress).toBuilder()\n        .setType(NetworkEndpoint.Type.IP_PORT)\n        .setPort(Port.newBuilder().setPortNumber(port))\n        .build();\n  }\n\n  /**\n   * Creates a {@link NetworkEndpoint} proto buffer object from the given hostname.\n   *\n   * @param hostname the hostname of the network endpoint\n   * @return the created {@link NetworkEndpoint} instance from the hostname.\n   */\n  public static NetworkEndpoint forHostname(String hostname) {\n    checkArgument(\n        !InetAddresses.isInetAddress(hostname), \"Expected hostname, got IP address '%s'\", hostname);\n\n    return NetworkEndpoint.newBuilder()\n        .setType(NetworkEndpoint.Type.HOSTNAME)\n        .setHostname(Hostname.newBuilder().setName(hostname))\n        .build();\n  }\n\n  /**\n   * Creates a {@link NetworkEndpoint} proto buffer object from the given ip address and hostname.\n   *\n   * @param hostname the hostname of the network endpoint\n   * @param ipAddress the IP address of the network endpoint.\n   * @return the created {@link NetworkEndpoint} instance from the IP address and hostname.\n   */\n  public static NetworkEndpoint forIpAndHostname(String ipAddress, String hostname) {\n    return forIp(ipAddress).toBuilder()\n        .setType(NetworkEndpoint.Type.IP_HOSTNAME)\n        .setHostname(Hostname.newBuilder().setName(hostname))\n        .build();\n  }\n\n  /**\n   * Creates a {@link NetworkEndpoint} proto buffer object from the given hostname and port.\n   *\n   * @param hostname the hostname of the network endpoint\n   * @param port the port number of the network endpoint.\n   * @return the created {@link NetworkEndpoint} instance from the hostname and port.\n   */\n  public static NetworkEndpoint forHostnameAndPort(String hostname, int port) {\n    checkArgument(\n        0 <= port && port <= MAX_PORT_NUMBER,\n        \"Port out of range. Expected [0, %s], actual %s.\",\n        MAX_PORT_NUMBER,\n        port);\n\n    return forHostname(hostname).toBuilder()\n        .setType(NetworkEndpoint.Type.HOSTNAME_PORT)\n        .setPort(Port.newBuilder().setPortNumber(port))\n        .build();\n  }\n\n  /**\n   * Returns a {@link NetworkEndpoint} proto buffer object from the given ip address, hostname and\n   * port.\n   *\n   * @param ipAddress the IP address of the network endpoint.\n   * @param hostname the hostname of the network endpoint\n   * @param port the port number of the network endpoint.\n   * @return the created {@link NetworkEndpoint} instance from the parameters.\n   */\n  public static NetworkEndpoint forIpHostnameAndPort(String ipAddress, String hostname, int port) {\n    checkArgument(\n        0 <= port && port <= MAX_PORT_NUMBER,\n        \"Port out of range. Expected [0, %s], actual %s.\",\n        MAX_PORT_NUMBER,\n        port);\n\n    return forIpAndHostname(ipAddress, hostname).toBuilder()\n        .setType(NetworkEndpoint.Type.IP_HOSTNAME_PORT)\n        .setPort(Port.newBuilder().setPortNumber(port))\n        .build();\n  }\n\n  /**\n   * Returns a {@link NetworkEndpoint} proto buffer object from the given {@code networkEndpoint}\n   * and port. The {@code networkEndpoint} parameter cannot contain any port information, otherwise\n   * {@link IllegalArgumentException} is thrown.\n   *\n   * @param networkEndpoint the source {@link NetworkEndpoint} instance without the port number\n   * @param port the port number of the network endpoint.\n   * @return the {@link NetworkEndpoint} instance from the parameters.\n   */\n  public static NetworkEndpoint forNetworkEndpointAndPort(\n      NetworkEndpoint networkEndpoint, int port) {\n    checkNotNull(networkEndpoint);\n    checkArgument(\n        0 <= port && port <= MAX_PORT_NUMBER,\n        \"Port out of range. Expected [0, %s], actual %s.\",\n        MAX_PORT_NUMBER,\n        port);\n\n    switch (networkEndpoint.getType()) {\n      case IP:\n        return networkEndpoint.toBuilder()\n            .setType(NetworkEndpoint.Type.IP_PORT)\n            .setPort(Port.newBuilder().setPortNumber(port))\n            .build();\n      case HOSTNAME:\n        return networkEndpoint.toBuilder()\n            .setType(NetworkEndpoint.Type.HOSTNAME_PORT)\n            .setPort(Port.newBuilder().setPortNumber(port))\n            .build();\n      case IP_HOSTNAME:\n        return networkEndpoint.toBuilder()\n            .setType(NetworkEndpoint.Type.IP_HOSTNAME_PORT)\n            .setPort(Port.newBuilder().setPortNumber(port))\n            .build();\n      case IP_PORT:\n      case HOSTNAME_PORT:\n      case IP_HOSTNAME_PORT:\n      case UNRECOGNIZED:\n      case TYPE_UNSPECIFIED:\n        throw new IllegalArgumentException(\"Invalid NetworkEndpoint type.\");\n    }\n    throw new AssertionError(\n        String.format(\n            \"Should never happen. Unchecked NetworkEndpoint type: %s\", networkEndpoint.getType()));\n  }\n\n  public static AddressFamily ipAddressFamily(String ipAddress) {\n    InetAddress inetAddress = InetAddresses.forString(ipAddress);\n\n    if (inetAddress instanceof Inet4Address) {\n      return AddressFamily.IPV4;\n    } else if (inetAddress instanceof Inet6Address) {\n      return AddressFamily.IPV6;\n    } else {\n      throw new AssertionError(String.format(\"Unknown IP address family for IP '%s'\", ipAddress));\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/data/NetworkServiceUtils.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * For any utility update, please consider if Python's network service utils\n * (plugin_server/py/common/data/network_service_utils.py) also needs the modification.\n */\n\npackage com.google.tsunami.common.data;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.base.Ascii;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.tsunami.proto.AddressFamily;\nimport com.google.tsunami.proto.Hostname;\nimport com.google.tsunami.proto.IpAddress;\nimport com.google.tsunami.proto.NetworkEndpoint;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.Port;\nimport com.google.tsunami.proto.ServiceContext;\nimport com.google.tsunami.proto.TransportProtocol;\nimport com.google.tsunami.proto.WebServiceContext;\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.UnknownHostException;\nimport java.util.Optional;\n\n/** Static utility methods pertaining to {@link NetworkService} proto buffer. */\npublic final class NetworkServiceUtils {\n  // Service names are those described in [RFC6335].\n  private static final ImmutableMap<String, Boolean> IS_PLAIN_HTTP_BY_KNOWN_WEB_SERVICE_NAME =\n      ImmutableMap.<String, Boolean>builder()\n          .put(\"http\", true)\n          .put(\"http-alt\", true) // Some http server are identified as this rather than \"http\".\n          .put(\"http-proxy\", true)\n          .put(\"https\", false)\n          .put(\"radan-http\", true) // Port 8088, Hadoop Yarn web UI identified as this.\n          .put(\"ssl/http\", false)\n          .put(\"ssl/https\", false)\n          .put(\"ssl/http-proxy\", false)\n          .put(\"ssl/tungsten-https\", false) // Port 9443, WSO2 Identity Server & WSO2 API Manager.\n          .put(\"ssl/wso2esb-console\", false) // Port 9444, WSO2 Identity Server Analytics.\n          .build();\n\n  private NetworkServiceUtils() {}\n\n  public static boolean isWebService(Optional<String> serviceName) {\n    return serviceName.isPresent()\n        && IS_PLAIN_HTTP_BY_KNOWN_WEB_SERVICE_NAME.containsKey(\n            Ascii.toLowerCase(serviceName.get()));\n  }\n\n  public static boolean isWebService(NetworkService networkService) {\n    checkNotNull(networkService);\n    // A web-service is a service that is either flagged as http by nmap or one that supports at\n    // least one HTTP method.\n    return (networkService.getSupportedHttpMethodsCount() > 0)\n        || isWebService(Optional.of(networkService.getServiceName()));\n  }\n\n  public static boolean isPlainHttp(NetworkService networkService) {\n    checkNotNull(networkService);\n\n    var isWebService = isWebService(networkService);\n    var isKnownServiceName = IS_PLAIN_HTTP_BY_KNOWN_WEB_SERVICE_NAME.containsKey(\n            Ascii.toLowerCase(networkService.getServiceName()));\n    var doesNotSupportAnySslVersion = networkService.getSupportedSslVersionsCount() == 0;\n\n    if (!isKnownServiceName) {\n      return isWebService && doesNotSupportAnySslVersion;\n    }\n\n    var isKnownPlainHttpService =\n        IS_PLAIN_HTTP_BY_KNOWN_WEB_SERVICE_NAME.getOrDefault(\n            Ascii.toLowerCase(networkService.getServiceName()), false);\n\n    return isKnownPlainHttpService && doesNotSupportAnySslVersion;\n  }\n\n  public static String getServiceName(NetworkService networkService) {\n    if (isWebService(networkService) && networkService.hasSoftware()) {\n      return Ascii.toLowerCase(networkService.getSoftware().getName());\n    }\n    return Ascii.toLowerCase(networkService.getServiceName());\n  }\n\n  public static String getWebServiceName(NetworkService networkService) {\n    if (isWebService(networkService)\n        && networkService.getServiceContext().getWebServiceContext().hasSoftware()) {\n      return Ascii.toLowerCase(\n          networkService.getServiceContext().getWebServiceContext().getSoftware().getName());\n    }\n    return Ascii.toLowerCase(networkService.getServiceName());\n  }\n\n  public static NetworkService buildUriNetworkService(String uriString) {\n    try {\n      URI uri = new URI(uriString);\n      NetworkEndpoint uriEndPoint = buildUriNetworkEndPoint(uri);\n\n      return NetworkService.newBuilder()\n          .setNetworkEndpoint(uriEndPoint)\n          .setTransportProtocol(TransportProtocol.TCP)\n          .setServiceName(uri.getScheme())\n          .setServiceContext(\n              ServiceContext.newBuilder()\n                  .setWebServiceContext(\n                      WebServiceContext.newBuilder().setApplicationRoot(uri.getPath())))\n          .build();\n    } catch (URISyntaxException exception) {\n      throw new AssertionError(\n          String.format(\n              \"Invalid uri syntax passed as target '%s'. Error: %s\", uriString, exception));\n    }\n  }\n\n  private static NetworkEndpoint buildUriNetworkEndPoint(URI uri) {\n    try {\n      String hostname = uri.getHost();\n      String scheme = uri.getScheme();\n      checkArgument(\n          scheme.equals(\"http\") || scheme.equals(\"https\"),\n          \"Uri scheme should be one of the following: 'http', 'https'\");\n\n      int port = uri.getPort();\n      if (port < 0) {\n        port = scheme.equals(\"http\") ? 80 : 443;\n      }\n\n      String ipAddress = InetAddress.getByName(hostname).getHostAddress();\n      InetAddress inetAddress = InetAddress.getByName(uri.getHost());\n      checkArgument(\n          (inetAddress instanceof Inet4Address) || (inetAddress instanceof Inet6Address),\n          \"Invalid address family\");\n      AddressFamily addressFamily =\n          inetAddress instanceof Inet4Address ? AddressFamily.IPV4 : AddressFamily.IPV6;\n\n      return NetworkEndpoint.newBuilder()\n          .setType(NetworkEndpoint.Type.IP_HOSTNAME_PORT)\n          .setPort(Port.newBuilder().setPortNumber(port))\n          .setHostname(Hostname.newBuilder().setName(uri.getHost()))\n          .setIpAddress(\n              IpAddress.newBuilder().setAddressFamily(addressFamily).setAddress(ipAddress))\n          .build();\n    } catch (UnknownHostException exception) {\n      throw new AssertionError(\n          String.format(\"Unable to get valid host from uri. Error: %s\", exception));\n    }\n  }\n\n  /**\n   * Build the root url for a web application service.\n   *\n   * @param networkService a web (http/https) service\n   * @return the root url for the web service, which always ends with a <code>\"/\"</code>.\n   */\n  public static String buildWebApplicationRootUrl(NetworkService networkService) {\n    checkNotNull(networkService);\n\n    if (!isWebService(networkService)) {\n      return \"http://\"\n          + NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint())\n          + \"/\";\n    }\n\n    String rootUrl =\n        (isPlainHttp(networkService) ? \"http://\" : \"https://\")\n            + buildWebUriAuthority(networkService)\n            + buildWebAppRootPath(networkService);\n    return rootUrl.endsWith(\"/\") ? rootUrl : rootUrl + \"/\";\n  }\n\n  private static String buildWebAppRootPath(NetworkService networkService) {\n    String rootPath =\n        networkService.getServiceContext().hasWebServiceContext()\n            ? networkService.getServiceContext().getWebServiceContext().getApplicationRoot()\n            : \"/\";\n    if (!rootPath.startsWith(\"/\")) {\n      rootPath = \"/\" + rootPath;\n    }\n    return rootPath;\n  }\n\n  private static String buildWebUriAuthority(NetworkService networkService) {\n    String uriAuthority = NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint());\n\n    // Remove default ports of the protocol.\n    boolean isPlainHttp = isPlainHttp(networkService);\n    if (isPlainHttp && uriAuthority.endsWith(\":80\")) {\n      uriAuthority = uriAuthority.substring(0, uriAuthority.lastIndexOf(\":80\"));\n    }\n    if (!isPlainHttp && uriAuthority.endsWith(\":443\")) {\n      uriAuthority = uriAuthority.substring(0, uriAuthority.lastIndexOf(\":443\"));\n    }\n\n    return uriAuthority;\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/io/archiving/Archiver.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport com.google.errorprone.annotations.CanIgnoreReturnValue;\n\n/** An {@link Archiver} archives the given data to some data storage. */\npublic interface Archiver {\n\n  /**\n   * Archives the {@code data} associated with the given {@code name}.\n   *\n   * @param name the name that will be associated with the data\n   * @param data the data to be archived in byte array format\n   * @return whether the given data is archived successfully.\n   */\n  @CanIgnoreReturnValue\n  boolean archive(String name, byte[] data);\n\n  /**\n   * Archives the {@code data} associated with the given {@code name}. By default, this method\n   * encodes the {@link CharSequence} {@code data} into a sequence of bytes using {@code UTF_8}\n   * {@link java.nio.charset.StandardCharsets} and calls the {@link #archive(String, byte[])}\n   * method.\n   *\n   * @param name the name that will be associated with the data\n   * @param data the data to be archived in {@link CharSequence} format\n   * @return whether the given data is archived successfully.\n   */\n  @CanIgnoreReturnValue\n  default boolean archive(String name, CharSequence data) {\n    return archive(name, checkNotNull(data).toString().getBytes(UTF_8));\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/io/archiving/GoogleCloudStorageArchiver.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.beust.jcommander.Parameter;\nimport com.beust.jcommander.Parameters;\nimport com.beust.jcommander.validators.PositiveInteger;\nimport com.google.cloud.WriteChannel;\nimport com.google.cloud.storage.BlobInfo;\nimport com.google.cloud.storage.Storage;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.inject.assistedinject.Assisted;\nimport com.google.tsunami.common.cli.CliOption;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport javax.inject.Inject;\n\n/** An {@link Archiver} implementation that archives data into Google Cloud Storage. */\npublic class GoogleCloudStorageArchiver implements Archiver {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n  // For sanity-checking and to parse out the bucket name and object id.\n  // See https://cloud.google.com/storage/docs/bucket-naming\n  public static final Pattern GS_URL_PATTERN = Pattern.compile(\"gs://([^/]{3,63})/(.*)\");\n\n  private final Options options;\n  private final Storage storage;\n\n  /** All command line options for {@link GoogleCloudStorageArchiver}. */\n  @Parameters(separators = \"=\")\n  public static final class Options implements CliOption {\n    @Parameter(\n        names = \"--gcs-archiver-chunk-size-in-bytes\",\n        description = \"The size of the data chunk when GCS archiver uploads data to Cloud Storage.\",\n        validateWith = PositiveInteger.class)\n    int chunkSizeInBytes = 1_000;\n\n    @Parameter(\n        names = \"--gcs-archiver-chunk-upload-threshold-in-bytes\",\n        description = \"The default data size threshold in bytes to enable chunk upload to GCS.\",\n        validateWith = PositiveInteger.class)\n    int chunkUploadThresholdInBytes = 1_000_000;\n\n    @Override\n    public void validate() {}\n  }\n\n  @Inject\n  public GoogleCloudStorageArchiver(Options options, @Assisted Storage storage) {\n    this.options = checkNotNull(options);\n    this.storage = checkNotNull(storage);\n  }\n\n  private static BlobInfo parseBlobInfo(String gcsUrl) {\n    Matcher matcher = GS_URL_PATTERN.matcher(gcsUrl);\n    checkArgument(matcher.matches(), \"Invalid GCS URL: '%s'\", gcsUrl);\n\n    String bucketName = matcher.group(1);\n    String objectName = matcher.group(2);\n    return BlobInfo.newBuilder(bucketName, objectName).build();\n  }\n\n  @Override\n  public boolean archive(String gcsUrl, byte[] data) {\n    BlobInfo blobInfo = parseBlobInfo(gcsUrl);\n\n    if (data.length <= options.chunkUploadThresholdInBytes) {\n      // Create the blob in one request.\n      logger.atInfo().log(\"Archiving data to GCS at '%s' in one request.\", gcsUrl);\n      storage.create(blobInfo, data);\n      return true;\n    }\n\n    // When content is large (1MB or more) it is recommended to write it in chunks via the blob's\n    // channel writer.\n    logger.atInfo().log(\n        \"Content is larger than threshold, archiving data to GCS at '%s' in chunks.\", gcsUrl);\n    try (WriteChannel writer = storage.writer(blobInfo)) {\n      for (int chunkOffset = 0;\n          chunkOffset < data.length;\n          chunkOffset += options.chunkSizeInBytes) {\n        int chunkSize = Math.min(data.length - chunkOffset, options.chunkSizeInBytes);\n        writer.write(ByteBuffer.wrap(data, chunkOffset, chunkSize));\n      }\n      return true;\n    } catch (IOException e) {\n      logger.atSevere().withCause(e).log(\"Unable to archving data to GCS at '%s'.\", gcsUrl);\n      return false;\n    }\n  }\n\n  /** The factory of {@link GoogleCloudStorageArchiver} types for usage with assisted injection. */\n  // TODO(b/145315535): consider wrap the Storage API into a client library. Current implementation\n  // is not easily testable.\n  public interface Factory {\n    GoogleCloudStorageArchiver create(Storage storage);\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/io/archiving/GoogleCloudStorageArchiverModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving;\n\nimport com.google.inject.AbstractModule;\nimport com.google.inject.assistedinject.FactoryModuleBuilder;\n\n/** Installs {@link GoogleCloudStorageArchiver}. */\npublic class GoogleCloudStorageArchiverModule extends AbstractModule {\n\n  @Override\n  protected void configure() {\n    install(new FactoryModuleBuilder().build(GoogleCloudStorageArchiver.Factory.class));\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/io/archiving/RawFileArchiver.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.base.Strings;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.common.io.Files;\nimport java.io.File;\nimport java.io.IOException;\n\n/** An {@link Archiver} implementation that archives data into file systems as raw files. */\npublic class RawFileArchiver implements Archiver {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  @Override\n  public boolean archive(String fileName, byte[] data) {\n    checkArgument(!Strings.isNullOrEmpty(fileName));\n    checkNotNull(data);\n\n    try {\n      logger.atInfo().log(\"Archiving data to file system with filename '%s'.\", fileName);\n      Files.asByteSink(new File(fileName)).write(data);\n      return true;\n    } catch (IOException e) {\n      logger.atWarning().withCause(e).log(\"Failed archiving data to file '%s'.\", fileName);\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/io/archiving/testing/FakeArchiver.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving.testing;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.common.collect.Maps;\nimport com.google.tsunami.common.io.archiving.Archiver;\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.Set;\n\n/** An implementation of {@link Archiver} that stores data in memory for testing purposes. */\npublic final class FakeArchiver implements Archiver {\n  private final Map<String, byte[]> archivedByteArrayData = Maps.newHashMap();\n  private final Map<String, CharSequence> archivedCharSequenceData = Maps.newHashMap();\n  private boolean shouldFail = false;\n\n  @Override\n  public boolean archive(String name, byte[] data) {\n    if (shouldFail) {\n      return false;\n    }\n\n    archivedByteArrayData.put(name, data);\n    return true;\n  }\n\n  @Override\n  public boolean archive(String name, CharSequence data) {\n    if (shouldFail) {\n      return false;\n    }\n\n    archivedCharSequenceData.put(name, data);\n    return true;\n  }\n\n  public void failArchival() {\n    this.shouldFail = true;\n  }\n\n  public byte[] getStoredByteArrays(String name) {\n    if (!archivedByteArrayData.containsKey(name)) {\n      throw new NoSuchElementException(String.format(\"'%s' not found in FakeArchiver\", name));\n    }\n    return archivedByteArrayData.get(name);\n  }\n\n  public CharSequence getStoredCharSequence(String name) {\n    if (!archivedCharSequenceData.containsKey(name)) {\n      throw new NoSuchElementException(String.format(\"'%s' not found in FakeArchiver\", name));\n    }\n    return archivedCharSequenceData.get(name);\n  }\n\n  public void assertNoByteArraysStored() {\n    assertThat(archivedByteArrayData).isEmpty();\n  }\n\n  public void assertNoCharSequencesStored() {\n    assertThat(archivedCharSequenceData).isEmpty();\n  }\n\n  public void assertNoDataStored() {\n    assertNoByteArraysStored();\n    assertNoCharSequencesStored();\n  }\n\n  public void assertByteArraysStored(Map<String, byte[]> expectedData) {\n    assertThat(archivedByteArrayData).containsExactlyEntriesIn(expectedData);\n  }\n\n  public void assertByteArraysStoredForNames(Set<String> expectedNames) {\n    assertThat(archivedByteArrayData.keySet()).containsExactlyElementsIn(expectedNames);\n  }\n\n  public void assertByteArraysStoredWithValues(Collection<byte[]> expectedValues) {\n    assertThat(archivedByteArrayData.values()).containsExactlyElementsIn(expectedValues);\n  }\n\n  public void assertCharSequencesStored(Map<String, CharSequence> expectedData) {\n    assertThat(archivedCharSequenceData).containsExactlyEntriesIn(expectedData);\n  }\n\n  public void assertCharSequencesStoredForNames(Set<String> expectedNames) {\n    assertThat(archivedCharSequenceData.keySet()).containsExactlyElementsIn(expectedNames);\n  }\n\n  public void assertCharSequencesStoredWithValues(Collection<CharSequence> expectedValues) {\n    assertThat(archivedCharSequenceData.values()).containsExactlyElementsIn(expectedValues);\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/io/archiving/testing/FakeGoogleCloudStorageArchivers.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving.testing;\n\nimport com.google.cloud.storage.Storage;\nimport com.google.tsunami.common.io.archiving.GoogleCloudStorageArchiver;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\n\n/** A collection of fake {@link GoogleCloudStorageArchiver} created by {@link FakeFactory}. */\npublic final class FakeGoogleCloudStorageArchivers {\n  private final Map<Storage, FakeArchiver> delegatedArchivers = new HashMap<>();\n\n  public void assertNoDataStored() {\n    for (FakeArchiver delegate : delegatedArchivers.values()) {\n      delegate.assertNoDataStored();\n    }\n  }\n\n  /**\n   * Get the byte array data stored in {@code storage} at {@code gcsUrl}.\n   *\n   * @param storage the instance of the GCS storage.\n   * @param gcsUrl the URL to the GCS storage object.\n   * @return the content of the GCS storage object in byte array format.\n   */\n  public byte[] getStoredByteArrays(Storage storage, String gcsUrl) {\n    if (!delegatedArchivers.containsKey(storage)) {\n      throw new NoSuchElementException(String.format(\"Storage '%s' not found\", storage));\n    }\n    return delegatedArchivers.get(storage).getStoredByteArrays(gcsUrl);\n  }\n\n  /**\n   * Get the {@link CharSequence} data stored in {@code storage} at {@code gcsUrl}.\n   *\n   * @param storage the instance of the GCS storage.\n   * @param gcsUrl the URL to the GCS storage object.\n   * @return the content of the GCS storage object in {@link CharSequence} format.\n   */\n  public CharSequence getStoredCharSequence(Storage storage, String gcsUrl) {\n    if (!delegatedArchivers.containsKey(storage)) {\n      throw new NoSuchElementException(String.format(\"Storage '%s' not found\", storage));\n    }\n    return delegatedArchivers.get(storage).getStoredCharSequence(gcsUrl);\n  }\n\n  final class FakeGoogleCloudStorageArchiver extends GoogleCloudStorageArchiver {\n    private final Storage storage;\n\n    private FakeGoogleCloudStorageArchiver(Storage storage) {\n      super(new Options(), storage);\n      this.storage = storage;\n    }\n\n    @Override\n    public boolean archive(String gcsUrl, byte[] data) {\n      FakeArchiver fakeArchiver =\n          delegatedArchivers.computeIfAbsent(storage, unused -> new FakeArchiver());\n      return fakeArchiver.archive(gcsUrl, data);\n    }\n\n    @Override\n    public boolean archive(String gcsUrl, CharSequence data) {\n      FakeArchiver fakeArchiver =\n          delegatedArchivers.computeIfAbsent(storage, unused -> new FakeArchiver());\n      return fakeArchiver.archive(gcsUrl, data);\n    }\n  }\n\n  final class FakeFactory implements GoogleCloudStorageArchiver.Factory {\n\n    @Override\n    public GoogleCloudStorageArchiver create(Storage storage) {\n      return new FakeGoogleCloudStorageArchiver(storage);\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/io/archiving/testing/FakeGoogleCloudStorageArchiversModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving.testing;\n\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Provides;\nimport com.google.tsunami.common.io.archiving.GoogleCloudStorageArchiver;\nimport javax.inject.Singleton;\n\n/** Installs fake factory for {@link GoogleCloudStorageArchiver}. */\npublic final class FakeGoogleCloudStorageArchiversModule extends AbstractModule {\n\n  @Provides\n  @Singleton\n  GoogleCloudStorageArchiver.Factory provideGoogleCloudStorageArchiverFactory(\n      FakeGoogleCloudStorageArchivers fakeGoogleCloudStorageArchivers) {\n    return fakeGoogleCloudStorageArchivers.new FakeFactory();\n  }\n\n  @Provides\n  @Singleton\n  FakeGoogleCloudStorageArchivers provideFakeGoogleCloudStorageArchivers() {\n    return new FakeGoogleCloudStorageArchivers();\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/io/archiving/testing/FakeRawFileArchiver.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving.testing;\n\nimport com.google.tsunami.common.io.archiving.RawFileArchiver;\n\n/** A fake implementation of the {@link RawFileArchiver}. */\npublic final class FakeRawFileArchiver extends RawFileArchiver {\n  private final FakeArchiver delegate = new FakeArchiver();\n\n  @Override\n  public boolean archive(String fileName, byte[] data) {\n    return delegate.archive(fileName, data);\n  }\n\n  @Override\n  public boolean archive(String fileName, CharSequence data) {\n    return delegate.archive(fileName, data);\n  }\n\n  public byte[] getStoredByteArrays(String fileName) {\n    return delegate.getStoredByteArrays(fileName);\n  }\n\n  public CharSequence getStoredCharSequence(String fileName) {\n    return delegate.getStoredCharSequence(fileName);\n  }\n\n  public void assertNoDataStored() {\n    delegate.assertNoDataStored();\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/io/archiving/testing/FakeRawFileArchiverModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving.testing;\n\nimport com.google.inject.AbstractModule;\nimport com.google.tsunami.common.io.archiving.RawFileArchiver;\nimport javax.inject.Singleton;\n\n/** Installs {@link FakeRawFileArchiver}. */\npublic final class FakeRawFileArchiverModule extends AbstractModule {\n\n  @Override\n  protected void configure() {\n    // This is intentional to create 2 separate bindings. One is for FakeRawFileArchiver itself,\n    // which always injects as a singleton. The other one links the binding for RawFileArchiver to\n    // FakeRawFileArchiver so that the FakeRawFileArchiver singleton instance is injected to\n    // RawFileArchiver. This way the classes on the inheritance chain always get the same instance.\n    //\n    // This is useful in unit test. Test cases now are able to get the same injected instance as the\n    // code under test.\n    bind(FakeRawFileArchiver.class).in(Singleton.class);\n    bind(RawFileArchiver.class).to(FakeRawFileArchiver.class);\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/FuzzingUtils.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net;\n\nimport static com.google.common.base.Strings.isNullOrEmpty;\nimport static com.google.common.collect.ImmutableList.toImmutableList;\nimport static java.util.stream.Collectors.joining;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.common.base.Splitter;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.tsunami.common.net.http.HttpRequest;\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\n\n/** Fuzzing utilities for HTTP request properties. */\npublic final class FuzzingUtils {\n  /* TODO(b/251480660): Refactor to generic fuzzing library. */\n\n  /**\n   * Fuzz GET parameters by replacing values with the provided payload. If no GET parameter is\n   * found, add a new parameter called {@code defaultParameter}.\n   */\n  public static ImmutableList<HttpRequest> fuzzGetParametersWithDefaultParameter(\n      HttpRequest request, String payload, String defaultParameter) {\n    return fuzzGetParameters(request, payload, Optional.of(defaultParameter), ImmutableSet.of());\n  }\n\n  /**\n   * Fuzz GET parameters by replacing values with the provided payload. Payloads are expected to\n   * represent paths. If encountered, file extesions and path prefixes are kept and provided via\n   * additional exploit requests. If no GET parameter is found, return an empty list.\n   */\n  public static ImmutableList<HttpRequest> fuzzGetParametersExpectingPathValues(\n      HttpRequest request, String payload) {\n    return fuzzGetParameters(\n        request, payload, Optional.empty(), ImmutableSet.of(FuzzingModifier.FUZZING_PATHS));\n  }\n\n  /**\n   * Fuzz GET parameters by replacing values with the provided payload. If no GET parameter is\n   * found, return an empty list.\n   */\n  public static ImmutableList<HttpRequest> fuzzGetParameters(HttpRequest request, String payload) {\n    return fuzzGetParameters(request, payload, Optional.empty(), ImmutableSet.of());\n  }\n\n  private static ImmutableList<HttpRequest> fuzzGetParameters(\n      HttpRequest request,\n      String payload,\n      Optional<String> defaultParameter,\n      ImmutableSet<FuzzingModifier> modifiers) {\n    URI parsedUrl = URI.create(request.url());\n    ImmutableList<HttpQueryParameter> queryParams = parseQuery(parsedUrl.getQuery());\n    if (queryParams.isEmpty() && defaultParameter.isPresent()) {\n      return ImmutableList.of(\n          request.toBuilder()\n              .setUrl(\n                  assembleUrlWithQueries(\n                      parsedUrl,\n                      ImmutableList.of(HttpQueryParameter.create(defaultParameter.get(), payload))))\n              .build());\n    }\n    return fuzzParams(queryParams, payload, modifiers).stream()\n        .map(fuzzedParams -> assembleUrlWithQueries(parsedUrl, fuzzedParams))\n        .map(fuzzedUrl -> request.toBuilder().setUrl(fuzzedUrl).build())\n        .collect(toImmutableList());\n  }\n\n  private static ImmutableList<HttpQueryParameter> setFuzzedParams(\n      ImmutableList<HttpQueryParameter> params, int index, String payload) {\n    List<HttpQueryParameter> paramsWithPayload = new ArrayList<>(params);\n    paramsWithPayload.set(index, HttpQueryParameter.create(params.get(index).name(), payload));\n    return ImmutableList.copyOf(paramsWithPayload);\n  }\n\n  private static void fuzzParamsWithExtendedPathPayloads(\n      ImmutableSet.Builder<ImmutableList<HttpQueryParameter>> builder,\n      ImmutableList<HttpQueryParameter> params,\n      int index,\n      String payload) {\n    int dotLocation = params.get(index).value().lastIndexOf('.');\n    if (dotLocation != -1) {\n      builder.add(\n          setFuzzedParams(\n              params, index, payload + \"%00\" + params.get(index).value().substring(dotLocation)));\n    }\n\n    int slashLocation = params.get(index).value().lastIndexOf('/');\n    if (slashLocation != -1) {\n      builder.add(\n          setFuzzedParams(\n              params, index, params.get(index).value().substring(0, slashLocation + 1) + payload));\n    }\n\n    if (dotLocation != -1 && slashLocation != -1 && slashLocation < dotLocation) {\n      builder.add(\n          setFuzzedParams(\n              params,\n              index,\n              params.get(index).value().substring(0, slashLocation + 1)\n                  + payload\n                  + \"%00\"\n                  + params.get(index).value().substring(dotLocation)));\n    }\n  }\n\n  private static ImmutableSet<ImmutableList<HttpQueryParameter>> fuzzParams(\n      ImmutableList<HttpQueryParameter> params,\n      String payload,\n      ImmutableSet<FuzzingModifier> modifiers) {\n    ImmutableSet.Builder<ImmutableList<HttpQueryParameter>> fuzzedParamsBuilder =\n        ImmutableSet.builder();\n\n    for (int i = 0; i < params.size(); i++) {\n      fuzzedParamsBuilder.add(setFuzzedParams(params, i, payload));\n\n      if (modifiers.contains(FuzzingModifier.FUZZING_PATHS)) {\n        fuzzParamsWithExtendedPathPayloads(fuzzedParamsBuilder, params, i, payload);\n      }\n    }\n\n    return fuzzedParamsBuilder.build();\n  }\n\n  public static ImmutableList<HttpQueryParameter> parseQuery(String query) {\n    if (isNullOrEmpty(query)) {\n      return ImmutableList.of();\n    }\n    ImmutableList.Builder<HttpQueryParameter> queryParamsBuilder = ImmutableList.builder();\n    for (String param : Splitter.on('&').split(query)) {\n      int equalPosition = param.indexOf(\"=\");\n      if (equalPosition > -1) {\n        String name = param.substring(0, equalPosition);\n        String value = param.substring(equalPosition + 1);\n        queryParamsBuilder.add(HttpQueryParameter.create(name, value));\n      } else {\n        queryParamsBuilder.add(HttpQueryParameter.create(param, \"\"));\n      }\n    }\n    return queryParamsBuilder.build();\n  }\n\n  private static String assembleUrlWithQueries(\n      URI parsedUrl, ImmutableList<HttpQueryParameter> params) {\n    String query = assembleQueryParams(params);\n    StringBuilder urlBuilder = new StringBuilder();\n    urlBuilder.append(parsedUrl.getScheme()).append(\"://\").append(parsedUrl.getRawAuthority());\n    if (!isNullOrEmpty(parsedUrl.getRawPath())) {\n      urlBuilder.append(parsedUrl.getRawPath());\n    }\n    if (!isNullOrEmpty(query)) {\n      urlBuilder.append('?').append(query);\n    }\n    if (!isNullOrEmpty(parsedUrl.getRawFragment())) {\n      urlBuilder.append('#').append(parsedUrl.getRawFragment());\n    }\n    return urlBuilder.toString();\n  }\n\n  private static String assembleQueryParams(ImmutableList<HttpQueryParameter> params) {\n    return params.stream()\n        .map(param -> String.format(\"%s=%s\", param.name(), param.value()))\n        .collect(joining(\"&\"));\n  }\n\n  /** URL Query parameter name and value pair. */\n  @AutoValue\n  public abstract static class HttpQueryParameter {\n    public abstract String name();\n\n    public abstract String value();\n\n    public static HttpQueryParameter create(String name, String value) {\n      return new AutoValue_FuzzingUtils_HttpQueryParameter(name, value);\n    }\n  }\n\n  enum FuzzingModifier {\n    FUZZING_PATHS;\n  }\n\n  private FuzzingUtils() {}\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/UrlUtils.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport com.google.common.base.Joiner;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.Iterables;\nimport com.google.common.collect.Lists;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.regex.Pattern;\nimport okhttp3.HttpUrl;\n\n/** Utilities for dealing with URLs. */\npublic final class UrlUtils {\n  private static final Joiner PATH_JOINER = Joiner.on(\"/\");\n  private static final Pattern SLASH_PREFIX_PATTERN = Pattern.compile(\"^/+\");\n  private static final Pattern TRAILING_SLASH_PATTERN = Pattern.compile(\"/+$\");\n\n  /**\n   * Enumerates all sub-paths for a given URL. All query parameters and fragments are removed.\n   *\n   * <p>For example:\n   *\n   * <ul>\n   *   <li>given <code>\"http://localhost/\"</code>, it returns <code>[\"http://localhost/\"]</code>\n   *   <li>given <code>\"http://localhost/a/b/\"</code>, it returns <code>\n   *       [\"http://localhost/\", \"http://localhost/a/\", \"http://localhost/a/b/\"]</code>\n   * </ul>\n   *\n   * @param url the URL to be enumerated.\n   * @return all sub-paths URLs for the given URL.\n   */\n  public static ImmutableSet<HttpUrl> allSubPaths(String url) {\n    return allSubPaths(HttpUrl.parse(url));\n  }\n\n  /**\n   * Enumerates all sub-paths for a given URL. All query parameters and fragments are removed.\n   *\n   * <p>For example:\n   *\n   * <ul>\n   *   <li>given <code>\"http://localhost/\"</code>, it returns <code>[\"http://localhost/\"]</code>\n   *   <li>given <code>\"http://localhost/a/b/\"</code>, it returns <code>\n   *       [\"http://localhost/\", \"http://localhost/a/\", \"http://localhost/a/b/\"]</code>\n   * </ul>\n   *\n   * @param url the URL to be enumerated.\n   * @return all sub-paths URLs for the given URL.\n   */\n  public static ImmutableSet<HttpUrl> allSubPaths(HttpUrl url) {\n    if (url == null) {\n      return ImmutableSet.of();\n    }\n\n    // Url at root.\n    List<String> pathSegments = url.encodedPathSegments();\n    if (pathSegments.size() == 1 && pathSegments.get(0).isEmpty()) {\n      return ImmutableSet.of(url.newBuilder().query(null).fragment(null).build());\n    }\n\n    // Url has sub-paths.\n    ImmutableSet.Builder<HttpUrl> allSubUrlsBuilder = ImmutableSet.builder();\n    for (int pathEnd = 0; pathEnd <= pathSegments.size(); pathEnd++) {\n      List<String> subPathSegments = Lists.newArrayList(pathSegments.subList(0, pathEnd));\n      // Ensure sub-path has leading slash.\n      if (subPathSegments.isEmpty() || !subPathSegments.get(0).isEmpty()) {\n        subPathSegments.add(0, \"\");\n      }\n      // Ensure sub-path has trailing slash.\n      if (subPathSegments.size() == 1 || !Iterables.getLast(subPathSegments).isEmpty()) {\n        subPathSegments.add(\"\");\n      }\n      allSubUrlsBuilder.add(\n          url.newBuilder()\n              .encodedPath(PATH_JOINER.join(subPathSegments))\n              .query(null)\n              .fragment(null)\n              .build());\n    }\n    return allSubUrlsBuilder.build();\n  }\n\n  /**\n   * Removes the leading slashes of a URL path.\n   *\n   * @param path the URL path to be transformed.\n   * @return a URL path without leading slash.\n   */\n  public static String removeLeadingSlashes(String path) {\n    return SLASH_PREFIX_PATTERN.matcher(path).replaceFirst(\"\");\n  }\n\n  /**\n   * Removes the trailing slashes of a URL path.\n   *\n   * @param path the URL path to be transformed.\n   * @return a URL path without leading slash.\n   */\n  public static String removeTrailingSlashes(String path) {\n    return TRAILING_SLASH_PATTERN.matcher(path).replaceFirst(\"\");\n  }\n\n  /**\n   * Encodes the given String using URL-encoding.\n   *\n   * @param raw the raw String to be encoded.\n   * @return the URL-encoded version of the provided String if it was valid UTF-8.\n   */\n  public static Optional<String> urlEncode(String raw) {\n    try {\n      return Optional.of(URLEncoder.encode(raw, UTF_8.toString()));\n    } catch (UnsupportedEncodingException e) {\n      return Optional.empty();\n    }\n  }\n\n  private UrlUtils() {}\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/db/ConnectionProvider.java",
    "content": "/*\n * Copyright 2023 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.db;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\n\n/** A client library that communicates with different databases via jdbc. */\npublic class ConnectionProvider implements ConnectionProviderInterface {\n  public ConnectionProvider() {}\n\n  @Override\n  public Connection getConnection(String url, String user, String password) throws SQLException {\n    return DriverManager.getConnection(url, user, password);\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/db/ConnectionProviderInterface.java",
    "content": "/*\n * Copyright 2023 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.db;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\n\n/** A client interface that communicates with different databases. */\npublic interface ConnectionProviderInterface {\n  public Connection getConnection(String url, String user, String password) throws SQLException;\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/HttpClient.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.tsunami.proto.NetworkService;\nimport java.io.IOException;\nimport java.time.Duration;\nimport org.checkerframework.checker.nullness.qual.Nullable;\n\n/** A client library that communicates with remote servers via the HTTP protocol. */\npublic abstract class HttpClient {\n  public static final String TSUNAMI_USER_AGENT = \"TsunamiSecurityScanner\";\n\n  /**\n   * Gets log id.\n   *\n   * @return log id string.\n   */\n  public abstract String getLogId();\n\n  /**\n   * NOTE: This is a temporary hack to workaround OkHttp's hardcoded URL canonicalization algorithm.\n   * We should rewrite the entire library using a more flexible backend.\n   *\n   * <p>Sends the given HTTP request as is, blocking until full response is received.\n   *\n   * @param httpRequest the HTTP request to be sent by this client.\n   * @return the response returned from the HTTP server.\n   * @throws IOException if an I/O error occurs during the HTTP request.\n   */\n  public abstract HttpResponse sendAsIs(HttpRequest httpRequest) throws IOException;\n\n  /**\n   * Sends the given HTTP request using this client, blocking until full response is received.\n   *\n   * @param httpRequest the HTTP request to be sent by this client.\n   * @return the response returned from the HTTP server.\n   * @throws IOException if an I/O error occurs during the HTTP request.\n   */\n  public abstract HttpResponse send(HttpRequest httpRequest) throws IOException;\n\n  /**\n   * Sends the given HTTP request using this client blocking until full response is received. If\n   * {@code networkService} is not null, the host header is set according to the service's header\n   * field even if it resolves to a different ip.\n   *\n   * @param httpRequest the HTTP request to be sent by this client.\n   * @param networkService the {@link NetworkService} proto to be used for the HOST header.\n   * @return the response returned from the HTTP server.\n   * @throws IOException if an I/O error occurs during the HTTP request.\n   */\n  public abstract HttpResponse send(\n      HttpRequest httpRequest, @Nullable NetworkService networkService) throws IOException;\n\n  /**\n   * Sends the given HTTP request using this client asynchronously.\n   *\n   * @param httpRequest the HTTP request to be sent by this client.\n   * @return the future for the response to be returned from the HTTP server.\n   */\n  public abstract ListenableFuture<HttpResponse> sendAsync(HttpRequest httpRequest);\n\n  /**\n   * Sends the given HTTP request using this client asynchronously. If {@code networkService} is not\n   * null, the host header is set according to the service's header field even if it resolves to a\n   * different ip.\n   *\n   * @param httpRequest the HTTP request to be sent by this client.\n   * @param networkService the {@link NetworkService} proto to be used for the HOST header.\n   * @return the future for the response to be returned from the HTTP server.\n   */\n  public abstract ListenableFuture<HttpResponse> sendAsync(\n      HttpRequest httpRequest, @Nullable NetworkService networkService);\n\n  public abstract <T extends HttpClient> Builder<T> modify();\n\n  /** Base builder for implementations of HttpClient */\n  public abstract static class Builder<T extends HttpClient> {\n\n    public abstract Builder<T> setFollowRedirects(boolean followRedirects);\n\n    public abstract Builder<T> setLogId(String logId);\n\n    public abstract Builder<T> setConnectTimeout(Duration connectionTimeout);\n\n    public abstract T build();\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/HttpClientCliOptions.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport com.beust.jcommander.Parameter;\nimport com.beust.jcommander.ParameterException;\nimport com.beust.jcommander.Parameters;\nimport com.google.tsunami.common.cli.CliOption;\nimport org.checkerframework.checker.nullness.qual.Nullable;\n\n/** Command line argument for {@link HttpClient}. */\n@Parameters(separators = \"=\")\npublic final class HttpClientCliOptions implements CliOption {\n\n  @Parameter(\n      names = \"--http-client-trust-all-certificates\",\n      arity = 1,\n      description = \"Whether the HTTP client should trust all certificates on HTTPS traffic.\")\n  public Boolean trustAllCertificates;\n\n  @Parameter(\n      names = \"--http-client-call-timeout-seconds\",\n      description =\n          \"[Depreciated] Set to be the same as the timeout specified by\"\n              + \" --http-client-connect-timeout-seconds.\")\n  Integer callTimeoutSeconds;\n\n  @Parameter(\n      names = \"--http-client-connect-timeout-seconds\",\n      description =\n          \"The timeout in seconds for new HTTP connections. See\"\n              + \" https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/connect-timeout/\"\n              + \" for more details.\")\n  Integer connectTimeoutSeconds;\n\n  @Parameter(\n      names = \"--http-client-read-timeout-seconds\",\n      description =\n          \"[Depreciated] Set to be the same as the timeout specified by\"\n              + \" --http-client-connect-timeout-seconds\")\n  Integer readTimeoutSeconds;\n\n  @Parameter(\n      names = \"--http-client-write-timeout-seconds\",\n      description =\n          \"[Depreciated] Set to be the same as the timeout specified by\"\n              + \" --http-client-connect-timeout-seconds.\")\n  Integer writeTimeoutSeconds;\n\n  @Parameter(\n      names = \"--http-client-user-agent\",\n      description = \"User-Agent to use in HTTP requests.\")\n  public String userAgent = HttpClient.TSUNAMI_USER_AGENT;\n\n  @Override\n  public void validate() {\n    validateTimeout(\"--http-client-call-timeout-seconds\", callTimeoutSeconds);\n    validateTimeout(\"--http-client-connect-timeout-seconds\", connectTimeoutSeconds);\n    validateTimeout(\"--http-client-read-timeout-seconds\", readTimeoutSeconds);\n    validateTimeout(\"--http-client-write-timeout-seconds\", writeTimeoutSeconds);\n  }\n\n  private static void validateTimeout(String flagName, @Nullable Integer value) {\n    if (value != null && value < 0) {\n      throw new ParameterException(\n          String.format(\"%s cannot be a negative number, received %d.\", flagName, value));\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/HttpClientConfigProperties.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport com.google.tsunami.common.config.annotations.ConfigProperties;\n\n/** Configuration properties for {@link HttpClient}. */\n@ConfigProperties(\"common.net.http\")\npublic final class HttpClientConfigProperties {\n  /** Whether the HTTP client should trust all certificates on HTTPS traffic. */\n  Boolean trustAllCertificates;\n\n  /**\n   * The timeout in seconds for complete HTTP calls. See\n   * https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/call-timeout/ for\n   * more details.\n   */\n  Integer callTimeoutSeconds;\n\n  /**\n   * The timeout in seconds for new HTTP connections. See\n   * https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/connect-timeout/\n   * for more details.\n   */\n  Integer connectTimeoutSeconds;\n\n  /**\n   * The timeout in seconds for the read operations for HTTP connections. See\n   * https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/read-timeout/ for\n   * more details.\n   */\n  Integer readTimeoutSeconds;\n\n  /**\n   * The timeout in seconds for the write operations for HTTP connections. See\n   * https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/write-timeout/ for\n   * more details.\n   */\n  Integer writeTimeoutSeconds;\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/HttpClientModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.base.Strings.isNullOrEmpty;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\n\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Provides;\nimport com.google.tsunami.common.net.http.javanet.ConnectionFactory;\nimport com.google.tsunami.common.net.http.javanet.DefaultConnectionFactory;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.security.GeneralSecurityException;\nimport java.security.SecureRandom;\nimport java.security.cert.X509Certificate;\nimport java.time.Duration;\nimport javax.inject.Qualifier;\nimport javax.inject.Singleton;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509TrustManager;\nimport okhttp3.ConnectionPool;\nimport okhttp3.Dispatcher;\nimport okhttp3.OkHttpClient;\n\n/** Guice module for installing {@link HttpClient} library. */\npublic final class HttpClientModule extends AbstractModule {\n  // This TrustManager does NOT validate certificate chains.\n  private static final X509TrustManager TRUST_ALL_CERTS_MANAGER =\n      new X509TrustManager() {\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String authType) {}\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String authType) {}\n\n        @Override\n        public X509Certificate[] getAcceptedIssuers() {\n          return new X509Certificate[0];\n        }\n      };\n  // Maximum number of requests for each host (URL's host name) to execute concurrently.\n  private static final int OKHTTPCLIENT_MAX_REQUESTS_PER_HOST = 5;\n\n  // Maximum number of idle connections to each to keep in the pool.\n  private final int connectionPoolMaxIdle;\n  // Duration to keep the connection alive in the pool before closing it.\n  private final Duration connectionPoolKeepAliveDuration;\n  // Maximum number of requests to execute concurrently.\n  private final int maxRequests;\n  // Whether or not to follow redirect from server.\n  private final boolean followRedirects;\n  // A log ID to print in front of the logs.\n  private final String logId;\n\n  public HttpClientModule(Builder builder) {\n    checkNotNull(builder);\n    this.connectionPoolMaxIdle = builder.connectionPoolMaxIdle;\n    this.connectionPoolKeepAliveDuration = builder.connectionPoolKeepAliveDuration;\n    this.maxRequests = builder.maxRequests;\n    this.followRedirects = builder.followRedirects;\n    this.logId = builder.logId;\n  }\n\n  @Provides\n  @Singleton\n  ConnectionPool provideConnectionPool() {\n    return new ConnectionPool(\n        connectionPoolMaxIdle, connectionPoolKeepAliveDuration.toMillis(), MILLISECONDS);\n  }\n\n  @Provides\n  @Singleton\n  Dispatcher provideDispatcher() {\n    Dispatcher dispatcher = new Dispatcher();\n    dispatcher.setMaxRequests(maxRequests);\n    dispatcher.setMaxRequestsPerHost(OKHTTPCLIENT_MAX_REQUESTS_PER_HOST);\n    return dispatcher;\n  }\n\n  @Provides\n  @Singleton\n  @TrustAllCertsSocketFactory\n  SSLSocketFactory provideTrustAllCertsSocketFactory() throws GeneralSecurityException {\n    SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n    sslContext.init(null, new TrustManager[] {TRUST_ALL_CERTS_MANAGER}, new SecureRandom());\n    return sslContext.getSocketFactory();\n  }\n\n  // Missing features:\n  // 1. Custom cookie handler.\n  @Provides\n  @Singleton\n  OkHttpClient provideOkHttpClient(\n      ConnectionPool connectionPool,\n      Dispatcher dispatcher,\n      @TrustAllCertsSocketFactory SSLSocketFactory trustAllCertsSocketFactory,\n      @TrustAllCertificates boolean trustAllCertificates,\n      @ConnectTimeoutSeconds int connectTimeoutSeconds) {\n    OkHttpClient.Builder clientBuilder =\n        new OkHttpClient.Builder()\n            .callTimeout(Duration.ofSeconds(connectTimeoutSeconds))\n            .connectTimeout(Duration.ofSeconds(connectTimeoutSeconds))\n            .readTimeout(Duration.ofSeconds(connectTimeoutSeconds))\n            .writeTimeout(Duration.ofSeconds(connectTimeoutSeconds))\n            .connectionPool(connectionPool)\n            .dispatcher(dispatcher)\n            .followRedirects(followRedirects);\n    if (trustAllCertificates) {\n      clientBuilder\n          .sslSocketFactory(trustAllCertsSocketFactory, TRUST_ALL_CERTS_MANAGER)\n          .hostnameVerifier((hostname, session) -> true);\n    }\n    return clientBuilder.build();\n  }\n\n  @Provides\n  @Singleton\n  HttpClient provideOkHttpHttpClient(\n      OkHttpClient okHttpClient,\n      @TrustAllCertificates boolean trustAllCertificates,\n      ConnectionFactory connectionFactory,\n      @LogId String logId,\n      @ConnectTimeout Duration connectTimeout,\n      @UserAgent String userAgent) {\n    return new OkHttpHttpClient(\n        okHttpClient, trustAllCertificates, connectionFactory, logId, connectTimeout, userAgent);\n  }\n\n  @Provides\n  @Singleton\n  ConnectionFactory provideJavaNetConnectionFactory(\n      @TrustAllCertificates boolean trustAllCertificates,\n      @TrustAllCertsSocketFactory SSLSocketFactory trustAllCertsSocketFactory,\n      @ConnectTimeoutSeconds int connectTimeoutSeconds,\n      @ReadTimeoutSeconds int readTimeoutSeconds) {\n    return new DefaultConnectionFactory(\n        trustAllCertificates,\n        trustAllCertsSocketFactory,\n        Duration.ofSeconds(connectTimeoutSeconds),\n        Duration.ofSeconds(readTimeoutSeconds));\n  }\n\n  @Provides\n  @TrustAllCertificates\n  boolean shouldTrustAllCertificates(\n      HttpClientCliOptions httpClientCliOptions,\n      HttpClientConfigProperties httpClientConfigProperties) {\n    if (httpClientCliOptions.trustAllCertificates != null) {\n      return httpClientCliOptions.trustAllCertificates;\n    }\n    if (httpClientConfigProperties.trustAllCertificates != null) {\n      return httpClientConfigProperties.trustAllCertificates;\n    }\n    return true;\n  }\n\n  @Provides\n  @LogId\n  String provideLogid() {\n    return logId;\n  }\n\n  @Provides\n  @FollowRedirects\n  boolean provideFollowRedirects() {\n    return followRedirects;\n  }\n\n  @Provides\n  @MaxRequests\n  int provideMaxRequests() {\n    return maxRequests;\n  }\n\n  @Provides\n  @CallTimeoutSeconds\n  int provideCallTimeoutSeconds(\n      HttpClientCliOptions httpClientCliOptions,\n      HttpClientConfigProperties httpClientConfigProperties) {\n    if (httpClientCliOptions.callTimeoutSeconds != null) {\n      return httpClientCliOptions.callTimeoutSeconds;\n    }\n    if (httpClientConfigProperties.callTimeoutSeconds != null) {\n      return httpClientConfigProperties.callTimeoutSeconds;\n    }\n    // Default call timeout specified in\n    // https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/call-timeout/.\n    return 0;\n  }\n\n  @Provides\n  @ConnectTimeoutSeconds\n  int provideConnectTimeoutSeconds(\n      HttpClientCliOptions httpClientCliOptions,\n      HttpClientConfigProperties httpClientConfigProperties) {\n    if (httpClientCliOptions.connectTimeoutSeconds != null) {\n      return httpClientCliOptions.connectTimeoutSeconds;\n    }\n    if (httpClientConfigProperties.connectTimeoutSeconds != null) {\n      return httpClientConfigProperties.connectTimeoutSeconds;\n    }\n    // Default connect timeout specified in\n    // https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/connect-timeout/.\n    return 10;\n  }\n\n  @Provides\n  @ConnectTimeout\n  Duration provideConnectTimeout(@ConnectTimeoutSeconds int connectionTimeoutSeconds) {\n    return Duration.ofSeconds(connectionTimeoutSeconds);\n  }\n\n  @Provides\n  @ReadTimeoutSeconds\n  int provideReadTimeoutSeconds(\n      HttpClientCliOptions httpClientCliOptions,\n      HttpClientConfigProperties httpClientConfigProperties) {\n    if (httpClientCliOptions.readTimeoutSeconds != null) {\n      return httpClientCliOptions.readTimeoutSeconds;\n    }\n    if (httpClientConfigProperties.readTimeoutSeconds != null) {\n      return httpClientConfigProperties.readTimeoutSeconds;\n    }\n    // Default read timeout specified in\n    // https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/read-timeout/.\n    return 10;\n  }\n\n  @Provides\n  @WriteTimeoutSeconds\n  int provideWriteTimeoutSeconds(\n      HttpClientCliOptions httpClientCliOptions,\n      HttpClientConfigProperties httpClientConfigProperties) {\n    if (httpClientCliOptions.writeTimeoutSeconds != null) {\n      return httpClientCliOptions.writeTimeoutSeconds;\n    }\n    if (httpClientConfigProperties.writeTimeoutSeconds != null) {\n      return httpClientConfigProperties.writeTimeoutSeconds;\n    }\n    // Default write timeout specified in\n    // https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/write-timeout/.\n    return 10;\n  }\n\n  @Provides\n  @UserAgent\n  String provideUserAgent(HttpClientCliOptions httpClientCliOptions) {\n    if (!isNullOrEmpty(httpClientCliOptions.userAgent)) {\n      return httpClientCliOptions.userAgent;\n    }\n    return HttpClient.TSUNAMI_USER_AGENT;\n  }\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface TrustAllCertificates {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface TrustAllCertsSocketFactory {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface LogId {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface CallTimeoutSeconds {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface ConnectTimeoutSeconds {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface ConnectTimeout {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface ReadTimeoutSeconds {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface WriteTimeoutSeconds {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface FollowRedirects {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface MaxRequests {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface UserAgent {}\n\n  /** Builder for {@link HttpClientModule}. */\n  public static final class Builder {\n    private static final int DEFAULT_CONNECTION_POOL_MAX_IDLE = 5;\n    private static final Duration DEFAULT_CONNECTION_POOL_KEEP_ALIVE_DURATION =\n        Duration.ofMinutes(5);\n    private static final int DEFAULT_MAX_REQUESTS = 64;\n    private static final boolean DEFAULT_FOLLOW_REDIRECTS = true;\n    private static final String DEFAULT_LOG_ID = \"\";\n\n    private int connectionPoolMaxIdle = DEFAULT_CONNECTION_POOL_MAX_IDLE;\n    private Duration connectionPoolKeepAliveDuration = DEFAULT_CONNECTION_POOL_KEEP_ALIVE_DURATION;\n    private int maxRequests = DEFAULT_MAX_REQUESTS;\n    private boolean followRedirects = DEFAULT_FOLLOW_REDIRECTS;\n    private String logId = DEFAULT_LOG_ID;\n\n    /**\n     * Sets the maximum number of idle connections to each to keep in the pool.\n     *\n     * @param maxIdle maximum number of idel connecteds.\n     * @return the {@link Builder} instance itself.\n     */\n    public Builder setConnectionPoolMaxIdle(int maxIdle) {\n      checkArgument(maxIdle > 0);\n      this.connectionPoolMaxIdle = maxIdle;\n      return this;\n    }\n\n    /**\n     * Sets the duration to keep the connection alive in the connection pool before closing it.\n     *\n     * @param keepAliveDuration the duration to keep the connection alive.\n     * @return the {@link Builder} instance itself.\n     */\n    public Builder setConnectionPoolKeepAliveDuration(Duration keepAliveDuration) {\n      checkNotNull(keepAliveDuration);\n      checkArgument(!keepAliveDuration.isNegative());\n      this.connectionPoolKeepAliveDuration = keepAliveDuration;\n      return this;\n    }\n\n    /**\n     * Sets the maximum number of requests to execute concurrently.\n     *\n     * @param maxRequests the maximum number of concurrent requests.\n     * @return the {@link Builder} instance itself.\n     */\n    public Builder setMaxRequests(int maxRequests) {\n      checkArgument(maxRequests > 0);\n      this.maxRequests = maxRequests;\n      return this;\n    }\n\n    /**\n     * Sets whether or not to follow redirect from server. If unset, by default redirects will be\n     * followed.\n     *\n     * @param followRedirects whether the HTTP client should follow redirect responses from the\n     *     server.\n     * @return the {@link Builder} instance itself.\n     */\n    public Builder setFollowRedirects(boolean followRedirects) {\n      this.followRedirects = followRedirects;\n      return this;\n    }\n\n    /**\n     * Sets the log ID to print in front of the logs.\n     *\n     * @param logId the log ID to print in front of the logs.\n     * @return the {@link Builder} instance itself.\n     */\n    public Builder setLogId(String logId) {\n      this.logId = logId;\n      return this;\n    }\n\n    public HttpClientModule build() {\n      return new HttpClientModule(this);\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/HttpHeaders.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.common.base.Ascii;\nimport com.google.common.base.CharMatcher;\nimport com.google.common.base.MoreObjects;\nimport com.google.common.collect.ImmutableBiMap;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableListMultimap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.errorprone.annotations.Immutable;\nimport java.lang.reflect.Field;\nimport java.util.Optional;\n\n/** Immutable HTTP headers. */\n@Immutable\n@AutoValue\npublic abstract class HttpHeaders {\n  private static final ImmutableBiMap<String, String> LOWER_TO_KNOWN = createKnownHeaders();\n  private static final ImmutableSet<String> KNOWN = LOWER_TO_KNOWN.values();\n\n  /** Canonicalize a header name. */\n  private static String canonicalize(String headerName) {\n    if (KNOWN.contains(headerName)) {\n      return headerName;\n    }\n    String lower = Ascii.toLowerCase(headerName);\n    String known = LOWER_TO_KNOWN.get(lower);\n    return MoreObjects.firstNonNull(known, lower);\n  }\n\n  private static ImmutableBiMap<String, String> createKnownHeaders() {\n    ImmutableBiMap.Builder<String, String> builder = ImmutableBiMap.builder();\n    addFields(builder, com.google.common.net.HttpHeaders.class);\n    return builder.build();\n  }\n\n  /**\n   * Loops over all of the public String fields in the given class and puts them into the BiMap\n   * (lower case to original string value).\n   */\n  private static void addFields(ImmutableBiMap.Builder<String, String> builder, Class<?> clazz) {\n    try {\n      for (Field field : clazz.getFields()) {\n        if (field.getType().equals(String.class)) {\n          String known = (String) field.get(null);\n          String lower = Ascii.toLowerCase(known);\n          builder.put(lower, known);\n        }\n      }\n    } catch (ReflectiveOperationException e) {\n      throw new IllegalStateException(e);\n    }\n  }\n\n  abstract ImmutableListMultimap<String, String> rawHeaders();\n\n  /**\n   * Gets a set of all HTTP header names.\n   *\n   * @return all HTTP header names.\n   */\n  public ImmutableSet<String> names() {\n    return rawHeaders().keySet();\n  }\n\n  /**\n   * Returns the first value for the header with the given name, or empty Optional if none exists.\n   *\n   * @param name case-insensitive header name\n   * @return the first value for the given header name.\n   */\n  public Optional<String> get(String name) {\n    checkNotNull(name, \"Name cannot be null.\");\n\n    ImmutableList<String> values = getAll(name);\n    return values.isEmpty() ? Optional.empty() : Optional.of(values.get(0));\n  }\n\n  /**\n   * Returns all the values for the header with the given name. Values are in the same order they\n   * were added to the builder.\n   *\n   * @param name case-insensitive header name\n   * @return All values for the given header name.\n   */\n  public ImmutableList<String> getAll(String name) {\n    checkNotNull(name, \"Name cannot be null.\");\n\n    // We first check the multimap using whatever string is passed in. Usually\n    // this will be a constant from HttpHeaders, which is pre-canonicalized.\n    // Only if the lookup fails do we then canonicalize and try again.\n    ImmutableList<String> values = rawHeaders().get(name);\n    if (!values.isEmpty()) {\n      return values;\n    }\n    String fixedName = canonicalize(name);\n    if (fixedName.equals(name)) {\n      return values; // Name was already canonicalized, so return the empty list.\n    }\n    return rawHeaders().get(fixedName);\n  }\n\n  public static Builder builder() {\n    return new AutoValue_HttpHeaders.Builder();\n  }\n\n  /** Builder for {@link HttpHeaders}. */\n  @AutoValue.Builder\n  public abstract static class Builder {\n    /** RFC 2616 section 4.2. */\n    private static final CharMatcher HEADER_NAME_MATCHER =\n        CharMatcher.inRange('!', '~').and(CharMatcher.isNot(':'));\n    /** RFC 2616 section 4.2. */\n    private static final CharMatcher HEADER_VALUE_MATCHER =\n        CharMatcher.inRange((char) 0, (char) 31) // No control characters\n            .or(CharMatcher.is((char) 127)) // or DEL\n            .negate()\n            .or(CharMatcher.is('\\t')); // except horizontal-tab\n\n    abstract ImmutableListMultimap.Builder<String, String> rawHeadersBuilder();\n\n    public Builder addHeader(String name, String value) {\n      checkNotNull(name, \"Name cannot be null.\");\n      checkNotNull(value, \"Value cannot be null.\");\n      checkArgument(isLegalHeaderName(name), \"Illegal header name %s\", name);\n      checkArgument(isLegalHeaderValue(value), \"Illegal header value %s\", value);\n      rawHeadersBuilder().put(canonicalize(name), value);\n      return this;\n    }\n\n    public Builder addHeader(String name, String value, boolean canonicalize) {\n      checkNotNull(name, \"Name cannot be null.\");\n      checkNotNull(value, \"Value cannot be null.\");\n      if (canonicalize) {\n        return addHeader(name, value);\n      } else {\n        rawHeadersBuilder().put(name, value);\n        return this;\n      }\n    }\n\n    public abstract HttpHeaders build();\n\n    private static boolean isLegalHeaderName(String str) {\n      return HEADER_NAME_MATCHER.matchesAllOf(str);\n    }\n\n    private static boolean isLegalHeaderValue(String value) {\n      return HEADER_VALUE_MATCHER.matchesAllOf(value);\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/HttpMethod.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\n/** Represents HTTP methods. */\npublic enum HttpMethod {\n  // Add more http methods here if necessary.\n  GET(\"GET\"),\n  HEAD(\"HEAD\"),\n  POST(\"POST\"),\n  PUT(\"PUT\"),\n  DELETE(\"DELETE\");\n\n  private final String string;\n\n  HttpMethod(String string) {\n    this.string = string;\n  }\n\n  @Override\n  public String toString() {\n    return string;\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/HttpRequest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.base.Preconditions.checkState;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.common.base.Strings;\nimport com.google.errorprone.annotations.Immutable;\nimport com.google.protobuf.ByteString;\nimport java.util.Optional;\nimport okhttp3.HttpUrl;\n\n/** Immutable HTTP request. */\n@Immutable\n@AutoValue\n@AutoValue.CopyAnnotations\n@SuppressWarnings(\"Immutable\")\npublic abstract class HttpRequest {\n  public abstract HttpMethod method();\n  public abstract String url();\n  public abstract HttpHeaders headers();\n  public abstract Optional<ByteString> requestBody();\n\n  public abstract Builder toBuilder();\n\n  /**\n   * Creates a {@link Builder} object for configuring {@link HttpRequest}.\n   *\n   * @return a {@link Builder} instance for the {@link HttpRequest} object.\n   */\n  public static Builder builder() {\n    return new AutoValue_HttpRequest.Builder();\n  }\n\n  /**\n   * Create a new HTTP GET request with the given {@code url}.\n   *\n   * @param url the url of the GET request.\n   * @return a {@link Builder} object for configuring {@link HttpRequest}.\n   */\n  public static Builder get(String url) {\n    checkArgument(!Strings.isNullOrEmpty(url));\n    return builder().setMethod(HttpMethod.GET).setUrl(url);\n  }\n\n  /**\n   * Create a new HTTP GET request with the given {@code uri}.\n   *\n   * @param uri the url of the GET request.\n   * @return a {@link Builder} object for configuring {@link HttpRequest}.\n   */\n  public static Builder get(HttpUrl uri) {\n    checkNotNull(uri);\n    return builder().setMethod(HttpMethod.GET).setUrl(uri);\n  }\n\n  /**\n   * Create a new HTTP HEAD request with the given {@code url}.\n   *\n   * @param url the url of the HEAD request.\n   * @return a {@link Builder} object for configuring {@link HttpRequest}.\n   */\n  public static Builder head(String url) {\n    checkArgument(!Strings.isNullOrEmpty(url));\n    return builder().setMethod(HttpMethod.HEAD).setUrl(url);\n  }\n\n  /**\n   * Create a new HTTP HEAD request with the given {@code uri}.\n   *\n   * @param uri the url of the HEAD request.\n   * @return a {@link Builder} object for configuring {@link HttpRequest}.\n   */\n  public static Builder head(HttpUrl uri) {\n    checkNotNull(uri);\n    return builder().setMethod(HttpMethod.HEAD).setUrl(uri);\n  }\n\n  /**\n   * Create a new HTTP POST request with the given {@code url}.\n   *\n   * @param url the url of the POST request.\n   * @return a {@link Builder} object for configuring {@link HttpRequest}.\n   */\n  public static Builder post(String url) {\n    checkArgument(!Strings.isNullOrEmpty(url));\n    return builder().setMethod(HttpMethod.POST).setUrl(url);\n  }\n\n  /**\n   * Create a new HTTP POST request with the given {@code uri}.\n   *\n   * @param uri the url of the POST request.\n   * @return a {@link Builder} object for configuring {@link HttpRequest}.\n   */\n  public static Builder post(HttpUrl uri) {\n    checkNotNull(uri);\n    return builder().setMethod(HttpMethod.POST).setUrl(uri);\n  }\n\n  /**\n   * Create a new HTTP PUT request with the given {@code url}.\n   *\n   * @param url the url of the PUT request.\n   * @return a {@link Builder} object for configuring {@link HttpRequest}.\n   */\n  public static Builder put(String url) {\n    checkArgument(!Strings.isNullOrEmpty(url));\n    return put(HttpUrl.parse(url));\n  }\n\n  /**\n   * Create a new HTTP PUT request with the given {@code uri}.\n   *\n   * @param uri the url of the PUT request.\n   * @return a {@link Builder} object for configuring {@link HttpRequest}.\n   */\n  public static Builder put(HttpUrl uri) {\n    checkNotNull(uri);\n    return builder().setMethod(HttpMethod.PUT).setUrl(uri);\n  }\n\n  /**\n   * Create a new HTTP DELETE request with the given {@code url}.\n   *\n   * @param url the url of the DELETE request.\n   * @return a {@link Builder} object for configuring {@link HttpRequest}.\n   */\n  public static Builder delete(String url) {\n    checkArgument(!Strings.isNullOrEmpty(url));\n    return builder().setMethod(HttpMethod.DELETE).setUrl(url);\n  }\n\n  /**\n   * Create a new HTTP DELETE request with the given {@code uri}.\n   *\n   * @param uri the url of the DELETE request.\n   * @return a {@link Builder} object for configuring {@link HttpRequest}.\n   */\n  public static Builder delete(HttpUrl uri) {\n    checkNotNull(uri);\n    return builder().setMethod(HttpMethod.DELETE).setUrl(uri);\n  }\n\n  /** Builder for {@link HttpRequest}. */\n  @AutoValue.Builder\n  public abstract static class Builder {\n    public abstract Builder setMethod(HttpMethod method);\n    public abstract Builder setUrl(String url);\n    public Builder setUrl(HttpUrl url) {\n      setUrl(url.toString());\n      return this;\n    }\n    public abstract Builder setHeaders(HttpHeaders httpHeaders);\n    public abstract Builder setRequestBody(ByteString requestBody);\n    public abstract Builder setRequestBody(Optional<ByteString> requestBody);\n\n    public Builder withEmptyHeaders() {\n      setHeaders(HttpHeaders.builder().build());\n      return this;\n    }\n\n    abstract HttpRequest autoBuild();\n    public HttpRequest build() {\n      HttpRequest httpRequest = autoBuild();\n\n      switch (httpRequest.method()) {\n        case GET:\n        case HEAD:\n          checkState(\n              !httpRequest.requestBody().isPresent(),\n              \"A request body is not allowed for HTTP GET/HEAD request\");\n          break;\n        case POST:\n        case PUT:\n        case DELETE:\n          break;\n      }\n\n      return httpRequest;\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/HttpResponse.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.auto.value.extension.memoized.Memoized;\nimport com.google.errorprone.annotations.Immutable;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonParser;\nimport com.google.gson.JsonPrimitive;\nimport com.google.protobuf.ByteString;\nimport java.util.Optional;\nimport okhttp3.HttpUrl;\n\n/** Immutable HTTP response. */\n@Immutable\n@AutoValue\n@AutoValue.CopyAnnotations\n// HttpUrl is immutable even if not marked as such.\n@SuppressWarnings(\"Immutable\")\npublic abstract class HttpResponse {\n  public abstract HttpStatus status();\n  public abstract HttpHeaders headers();\n  public abstract Optional<ByteString> bodyBytes();\n  // The URL that produced this response.\n  // TODO(b/173574468): Provide the full redirection request not just the Url.\n  public abstract Optional<HttpUrl> responseUrl();\n\n  /**\n   * Gets the body of the HTTP response as a UTF-8 encoded String.\n   *\n   * @return HTTP response body as a Java {@link String}.\n   */\n  @Memoized\n  public Optional<String> bodyString() {\n    return bodyBytes().map(ByteString::toStringUtf8);\n  }\n\n  /**\n   * Tries to parse the response body as json and returns the parsing result as {@link JsonElement}.\n   * If parsing failed, an empty optional is returned.\n   *\n   * @return HTTP response body as a Gson {@link JsonElement} object.\n   */\n  @Memoized\n  public Optional<JsonElement> bodyJson() {\n    try {\n      return bodyString().map(JsonParser::parseString);\n    } catch (RuntimeException e) {\n      // Do best-effort parsing and ignore Json parsing errors\n      return Optional.empty();\n    }\n  }\n\n  /**\n   * Tries to determine if a given field in Json response is equal to a specific value. If parsing\n   * failed, {@link com.google.gson.JsonSyntaxException} or {@link IllegalStateException} will be\n   * thrown.\n   *\n   * @return boolean\n   */\n  public boolean jsonFieldEqualsToValue(String fieldname, String value) {\n    Optional<JsonPrimitive> jsonPrimitive =\n        bodyJson()\n            .map(JsonElement::getAsJsonObject)\n            .map(object -> object.getAsJsonPrimitive(fieldname));\n    return jsonPrimitive.isPresent() && jsonPrimitive.get().getAsString().equals(value);\n  }\n\n  public static Builder builder() {\n    return new AutoValue_HttpResponse.Builder();\n  }\n\n  /** Builder for {@link HttpResponse}. */\n  @AutoValue.Builder\n  public abstract static class Builder {\n    public abstract Builder setStatus(HttpStatus httpStatus);\n    public abstract Builder setHeaders(HttpHeaders httpHeaders);\n    public abstract Builder setBodyBytes(ByteString bodyBytes);\n    public abstract Builder setBodyBytes(Optional<ByteString> bodyBytes);\n    public abstract Builder setResponseUrl(HttpUrl url);\n    public abstract Builder setResponseUrl(Optional<HttpUrl> url);\n\n    public abstract HttpResponse build();\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/HttpStatus.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport static com.google.common.collect.ImmutableMap.toImmutableMap;\n\nimport com.google.common.collect.ImmutableMap;\nimport java.util.Arrays;\nimport java.util.function.Function;\n\n/**\n * HTTP Status Codes defined in RFC 2616, RFC 6585, RFC 4918 and RFC 7538.\n *\n * @see <a href=\"http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html\"\n *     target=\"_top\">http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html</a>\n * @see <a href=\"http://tools.ietf.org/html/rfc6585\"\n *     target=\"_top\">http://tools.ietf.org/html/rfc6585</a>\n * @see <a href=\"https://tools.ietf.org/html/rfc4918\"\n *     target=\"_top\">https://tools.ietf.org/html/rfc4918</a>\n * @see <a href=\"https://tools.ietf.org/html/rfc7538\"\n *     target=\"_top\">https://tools.ietf.org/html/rfc7538</a>\n */\npublic enum HttpStatus {\n  // Default\n  HTTP_STATUS_UNSPECIFIED(0, \"Status Unspecified\"),\n\n  // Informational 1xx\n  CONTINUE(100, \"Continue\"),\n  SWITCHING_PROTOCOLS(101, \"Switching Protocols\"),\n\n  // Successful 2xx\n  OK(200, \"Ok\"),\n  CREATED(201, \"Created\"),\n  ACCEPTED(202, \"Accepted\"),\n  NON_AUTHORITATIVE_INFORMATION(203, \"Non-Authoritative Information\"),\n  NO_CONTENT(204, \"No Content\"),\n  RESET_CONTENT(205, \"Reset Content\"),\n  PARTIAL_CONTENT(206, \"Partial Content\"),\n  MULTI_STATUS(207, \"Multi-Status\"),\n\n  // Redirection 3xx\n  MULTIPLE_CHOICES(300, \"Multiple Choices\"),\n  MOVED_PERMANENTLY(301, \"Moved Permanently\"),\n  FOUND(302, \"Found\"),\n  SEE_OTHER(303, \"See Other\"),\n  NOT_MODIFIED(304, \"Not Modified\"),\n  USE_PROXY(305, \"Use Proxy\"),\n  TEMPORARY_REDIRECT(307, \"Temporary Redirect\"),\n  PERMANENT_REDIRECT(308, \"Permanent Redirect\"),\n\n  // Client Error 4xx\n  BAD_REQUEST(400, \"Bad Request\"),\n  UNAUTHORIZED(401, \"Unauthorized\"),\n  PAYMENT_REQUIRED(402, \"Payment Required\"),\n  FORBIDDEN(403, \"Forbidden\"),\n  NOT_FOUND(404, \"Not Found\"),\n  METHOD_NOT_ALLOWED(405, \"Method Not Allowed\"),\n  NOT_ACCEPTABLE(406, \"Not Acceptable\"),\n  PROXY_AUTHENTICATION_REQUIRED(407, \"Proxy Authentication Required\"),\n  REQUEST_TIMEOUT(408, \"Request Timeout\"),\n  CONFLICT(409, \"Conflict\"),\n  GONE(410, \"Gone\"),\n  LENGTH_REQUIRED(411, \"Length Required\"),\n  PRECONDITION_FAILED(412, \"Precondition Failed\"),\n  REQUEST_ENTITY_TOO_LARGE(413, \"Request Entity Too Large\"),\n  REQUEST_URI_TOO_LONG(414, \"Request Uri Too Long\"),\n  UNSUPPORTED_MEDIA_TYPE(415, \"Unsupported Media Type\"),\n  REQUEST_RANGE_NOT_SATISFIABLE(416, \"Request Range Not Satisfiable\"),\n  EXPECTATION_FAILED(417, \"Expectation Failed\"),\n  UNPROCESSABLE_ENTITY(422, \"Unprocessable Entity\"),\n  LOCKED(423, \"Locked\"),\n  FAILED_DEPENDENCY(424, \"Failed Dependency\"),\n  PRECONDITION_REQUIRED(428, \"Precondition Required\"),\n  TOO_MANY_REQUESTS(429, \"Too Many Requests\"),\n  REQUEST_HEADER_FIELDS_TOO_LARGE(431, \"Request Header Fields Too Large\"),\n\n  // Server Error 5xx\n  INTERNAL_SERVER_ERROR(500, \"Internal Server Error\"),\n  NOT_IMPLEMENTED(501, \"Not Implemented\"),\n  BAD_GATEWAY(502, \"Bad Gateway\"),\n  SERVICE_UNAVAILABLE(503, \"Service Unavailable\"),\n  GATEWAY_TIMEOUT(504, \"Gateway Timeout\"),\n  HTTP_VERSION_NOT_SUPPORTED(505, \"Http Version Not Supported\"),\n  INSUFFICIENT_STORAGE(507, \"Insufficient Storage\"),\n  NETWORK_AUTHENTICATION_REQUIRED(511, \"Network Authentication Required\"),\n\n  /*\n   * IE returns this code for 204 due to its use of URLMon, which returns this\n   * code for 'Operation Aborted'. The status text is 'Unknown', the response\n   * headers are ''. Known to occur on IE 6 on XP through IE9 on Win7.\n   */\n  QUIRK_IE_NO_CONTENT(1223, \"Quirk IE No Content\");\n\n  /** Status indexed by code. */\n  private static final ImmutableMap<Integer, HttpStatus> BY_CODE =\n      Arrays.stream(HttpStatus.values())\n          .collect(toImmutableMap(HttpStatus::code, Function.identity()));\n\n  /**\n   * Creates the {@link HttpStatus} from the given status code, or null if there is no known status\n   * with that code.\n   *\n   * @param code the HTTP status code.\n   * @return the matching {@link HttpStatus} from the given status code.\n   */\n  public static HttpStatus fromCode(int code) {\n    HttpStatus status = BY_CODE.get(code);\n    return status == null ? HTTP_STATUS_UNSPECIFIED : status;\n  }\n\n  private final int code;\n  private final String name;\n\n  HttpStatus(int code, String name) {\n    this.code = code;\n    this.name = name;\n  }\n\n  public int code() {\n    return code;\n  }\n\n  public boolean isRedirect() {\n    switch (this) {\n      case MULTIPLE_CHOICES:\n      case MOVED_PERMANENTLY:\n      case FOUND:\n      case SEE_OTHER:\n      case TEMPORARY_REDIRECT:\n      case PERMANENT_REDIRECT:\n        return true;\n      default:\n        return false;\n    }\n  }\n\n  public boolean isSuccess() {\n    return code >= 200 && code < 300;\n  }\n\n  @Override\n  public String toString() {\n    return name;\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/OkHttpHttpClient.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.base.Strings.isNullOrEmpty;\nimport static com.google.common.net.HttpHeaders.USER_AGENT;\nimport static com.google.common.util.concurrent.MoreExecutors.directExecutor;\n\nimport com.google.common.base.Ascii;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.common.io.ByteSource;\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.common.util.concurrent.SettableFuture;\nimport com.google.errorprone.annotations.CanIgnoreReturnValue;\nimport com.google.protobuf.ByteString;\nimport com.google.tsunami.common.net.http.javanet.ConnectionFactory;\nimport com.google.tsunami.proto.NetworkService;\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport javax.net.ssl.HttpsURLConnection;\nimport okhttp3.Call;\nimport okhttp3.Callback;\nimport okhttp3.Dns;\nimport okhttp3.Headers;\nimport okhttp3.MediaType;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\nimport org.checkerframework.checker.nullness.qual.Nullable;\n\n/**\n * A client library that communicates with remote servers via the HTTP protocol using {@link\n * OkHttpClient}.\n */\nfinal class OkHttpHttpClient extends HttpClient {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  private final OkHttpClient okHttpClient;\n  private final boolean trustAllCertificates;\n  private final ConnectionFactory connectionFactory;\n  private final String logId;\n  private final Duration connectionTimeout;\n  private final String userAgent;\n\n  OkHttpHttpClient(\n      OkHttpClient okHttpClient,\n      boolean trustAllCertificates,\n      ConnectionFactory connectionFactory,\n      String logId,\n      Duration connectionTimeout,\n      String userAgent) {\n    this.okHttpClient = checkNotNull(okHttpClient);\n    this.trustAllCertificates = trustAllCertificates;\n    this.connectionFactory = checkNotNull(connectionFactory);\n    this.logId = logId;\n    this.connectionTimeout = connectionTimeout;\n    this.userAgent = isNullOrEmpty(userAgent) ? TSUNAMI_USER_AGENT : userAgent;\n  }\n\n  /**\n   * Gets log id.\n   *\n   * @return log id string.\n   */\n  @Override\n  public String getLogId() {\n    return this.logId;\n  }\n\n  /**\n   * NOTE: This is a temporary hack to workaround OkHttp's hardcoded URL canonicalization algorithm.\n   * We should rewrite the entire library using a more flexible backend.\n   *\n   * <p>Sends the given HTTP request as is, blocking until full response is received.\n   *\n   * @param httpRequest the HTTP request to be sent by this client.\n   * @return the response returned from the HTTP server.\n   * @throws IOException if an I/O error occurs during the HTTP request.\n   */\n  @Override\n  public HttpResponse sendAsIs(HttpRequest httpRequest) throws IOException {\n    HttpURLConnection connection = connectionFactory.openConnection(httpRequest.url());\n    connection.setRequestMethod(httpRequest.method().toString());\n    httpRequest.headers().names().stream()\n        .filter(headerName -> !Ascii.equalsIgnoreCase(headerName, USER_AGENT))\n        .forEach(\n            headerName ->\n                httpRequest\n                    .headers()\n                    .getAll(headerName)\n                    .forEach(\n                        headerValue -> connection.setRequestProperty(headerName, headerValue)));\n    connection.setRequestProperty(USER_AGENT, this.userAgent);\n\n    if (ImmutableSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE)\n        .contains(httpRequest.method())) {\n      connection.setDoOutput(true);\n      ByteSource.wrap(httpRequest.requestBody().orElse(ByteString.EMPTY).toByteArray())\n          .copyTo(connection.getOutputStream());\n    }\n\n    int responseCode = connection.getResponseCode();\n    HttpHeaders.Builder responseHeadersBuilder = HttpHeaders.builder();\n    for (Map.Entry<String, List<String>> headerEntry : connection.getHeaderFields().entrySet()) {\n      String headerName = headerEntry.getKey();\n      if (!isNullOrEmpty(headerName)) {\n        for (String headerValue : headerEntry.getValue()) {\n          if (!isNullOrEmpty(headerValue)) {\n            responseHeadersBuilder.addHeader(headerName, headerValue);\n          }\n        }\n      }\n    }\n    return HttpResponse.builder()\n        .setStatus(HttpStatus.fromCode(responseCode))\n        .setHeaders(responseHeadersBuilder.build())\n        .setBodyBytes(ByteString.readFrom(connection.getInputStream()))\n        .build();\n  }\n\n  /**\n   * Sends the given HTTP request using this client, blocking until full response is received.\n   *\n   * @param httpRequest the HTTP request to be sent by this client.\n   * @return the response returned from the HTTP server.\n   * @throws IOException if an I/O error occurs during the HTTP request.\n   */\n  @Override\n  public HttpResponse send(HttpRequest httpRequest) throws IOException {\n    return send(httpRequest, null);\n  }\n\n  /**\n   * Sends the given HTTP request using this client blocking until full response is received. If\n   * {@code networkService} is not null, the host header is set according to the service's header\n   * field even if it resolves to a different ip.\n   *\n   * @param httpRequest the HTTP request to be sent by this client.\n   * @param networkService the {@link NetworkService} proto to be used for the HOST header.\n   * @return the response returned from the HTTP server.\n   * @throws IOException if an I/O error occurs during the HTTP request.\n   */\n  @Override\n  public HttpResponse send(HttpRequest httpRequest, @Nullable NetworkService networkService)\n      throws IOException {\n    logger.atInfo().log(\n        \"%sSending HTTP '%s' request to '%s'.\", logId, httpRequest.method(), httpRequest.url());\n\n    OkHttpClient callHttpClient = clientWithHostnameAsProxy(networkService);\n    try (Response okHttpResponse =\n        callHttpClient.newCall(buildOkHttpRequest(httpRequest, this.userAgent)).execute()) {\n      return parseResponse(okHttpResponse);\n    }\n  }\n\n  /**\n   * Sends the given HTTP request using this client asynchronously.\n   *\n   * @param httpRequest the HTTP request to be sent by this client.\n   * @return the future for the response to be returned from the HTTP server.\n   */\n  @Override\n  public ListenableFuture<HttpResponse> sendAsync(HttpRequest httpRequest) {\n    return sendAsync(httpRequest, null);\n  }\n\n  /**\n   * Sends the given HTTP request using this client asynchronously. If {@code networkService} is not\n   * null, the host header is set according to the service's header field even if it resolves to a\n   * different ip.\n   *\n   * @param httpRequest the HTTP request to be sent by this client.\n   * @param networkService the {@link NetworkService} proto to be used for the HOST header.\n   * @return the future for the response to be returned from the HTTP server.\n   */\n  @Override\n  public ListenableFuture<HttpResponse> sendAsync(\n      HttpRequest httpRequest, @Nullable NetworkService networkService) {\n    logger.atInfo().log(\n        \"%sSending async HTTP '%s' request to '%s'.\",\n        logId, httpRequest.method(), httpRequest.url());\n    OkHttpClient callHttpClient = clientWithHostnameAsProxy(networkService);\n    SettableFuture<HttpResponse> responseFuture = SettableFuture.create();\n    Call requestCall = callHttpClient.newCall(buildOkHttpRequest(httpRequest, this.userAgent));\n\n    try {\n      requestCall.enqueue(\n          new Callback() {\n            @Override\n            public void onFailure(Call call, IOException e) {\n              responseFuture.setException(e);\n            }\n\n            @Override\n            public void onResponse(Call call, Response response) {\n              try (ResponseBody unused = response.body()) {\n                responseFuture.set(parseResponse(response));\n              } catch (Throwable t) {\n                responseFuture.setException(t);\n              }\n            }\n          });\n    } catch (Throwable t) {\n      responseFuture.setException(t);\n    }\n\n    // Makes sure cancellation state is propagated to OkHttp.\n    responseFuture.addListener(\n        () -> {\n          if (responseFuture.isCancelled()) {\n            requestCall.cancel();\n          }\n        },\n        directExecutor());\n    return responseFuture;\n  }\n\n  /*\n   * Returns a modified HTTP client that's configured to connect to the {@code networkService}'s IP\n   * and use its hostname in the host header, when both a hostname and an IP address is specified.\n   * Returns an unmodified HTTP client otherwise.\n   */\n  private OkHttpClient clientWithHostnameAsProxy(NetworkService networkService) {\n    if (networkService == null) {\n      return this.okHttpClient;\n    }\n    String serviceIp = networkService.getNetworkEndpoint().getIpAddress().getAddress();\n    String serviceHostname = networkService.getNetworkEndpoint().getHostname().getName();\n    return this.okHttpClient\n        .newBuilder()\n        .dns(\n            hostname -> {\n              if (hostname.equals(serviceHostname)) {\n                hostname = serviceIp;\n              }\n              return Dns.SYSTEM.lookup(hostname);\n            })\n        .hostnameVerifier(\n            (hostname, session) -> {\n              if (trustAllCertificates) {\n                return true;\n              }\n              if (hostname.equals(serviceHostname)) {\n                return true;\n              }\n              return HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session);\n            })\n        .build();\n  }\n\n  private static Request buildOkHttpRequest(HttpRequest httpRequest, String userAgent) {\n    Request.Builder okRequestBuilder = new Request.Builder().url(httpRequest.url());\n\n    httpRequest.headers().names().stream()\n        .filter(headerName -> !Ascii.equalsIgnoreCase(headerName, USER_AGENT))\n        .forEach(\n            headerName ->\n                httpRequest\n                    .headers()\n                    .getAll(headerName)\n                    .forEach(headerValue -> okRequestBuilder.addHeader(headerName, headerValue)));\n    okRequestBuilder.addHeader(USER_AGENT, userAgent);\n\n    switch (httpRequest.method()) {\n      case GET:\n        okRequestBuilder.get();\n        break;\n      case HEAD:\n        okRequestBuilder.head();\n        break;\n      case PUT:\n        okRequestBuilder.put(buildRequestBody(httpRequest));\n        break;\n      case POST:\n        okRequestBuilder.post(buildRequestBody(httpRequest));\n        break;\n      case DELETE:\n        okRequestBuilder.delete(buildRequestBody(httpRequest));\n        break;\n    }\n\n    return okRequestBuilder.build();\n  }\n\n  private static RequestBody buildRequestBody(HttpRequest httpRequest) {\n    MediaType mediaType =\n        MediaType.parse(\n            httpRequest.headers().get(com.google.common.net.HttpHeaders.CONTENT_TYPE).orElse(\"\"));\n    return RequestBody.create(\n        mediaType, httpRequest.requestBody().orElse(ByteString.EMPTY).toByteArray());\n  }\n\n  private static HttpResponse parseResponse(Response okResponse) throws IOException {\n    logger.atInfo().log(\n        \"Received HTTP response with code '%d' for request to '%s'.\",\n        okResponse.code(), okResponse.request().url());\n\n    HttpResponse.Builder httpResponseBuilder =\n        HttpResponse.builder()\n            .setStatus(HttpStatus.fromCode(okResponse.code()))\n            .setHeaders(convertHeaders(okResponse.headers()))\n            .setResponseUrl(okResponse.request().url());\n    if (!okResponse.request().method().equals(HttpMethod.HEAD.name())\n        && okResponse.body() != null) {\n      httpResponseBuilder.setBodyBytes(ByteString.copyFrom(okResponse.body().bytes()));\n    }\n    return httpResponseBuilder.build();\n  }\n\n  private static HttpHeaders convertHeaders(Headers headers) {\n    HttpHeaders.Builder headersBuilder = HttpHeaders.builder();\n    for (int i = 0; i < headers.size(); i++) {\n      headersBuilder.addHeader(headers.name(i), headers.value(i));\n    }\n    return headersBuilder.build();\n  }\n\n  /**\n   * Returns a {@link Builder} that allows client code to modify the configurations of the internal\n   * http client.\n   *\n   * @return the {@link Builder} for modifying this client instance.\n   */\n  @Override\n  @SuppressWarnings(\"unchecked\") // safe covariant cast\n  public Builder<OkHttpHttpClient> modify() {\n    return new OkHttpHttpClientBuilder(this);\n  }\n\n  /** Builder for {@link OkHttpHttpClient}. */\n  // TODO(b/145315535): add more configurable options into the builder.\n  public static class OkHttpHttpClientBuilder extends Builder<OkHttpHttpClient> {\n    private final OkHttpClient okHttpClient;\n    private boolean followRedirects;\n    private boolean trustAllCertificates;\n    private final ConnectionFactory connectionFactory;\n    private String logId;\n    private Duration connectionTimeout;\n    private String userAgent;\n\n    private OkHttpHttpClientBuilder(OkHttpHttpClient okHttpHttpClient) {\n      this.okHttpClient = okHttpHttpClient.okHttpClient;\n      this.followRedirects = okHttpClient.followRedirects();\n      this.trustAllCertificates = okHttpHttpClient.trustAllCertificates;\n      this.connectionFactory = okHttpHttpClient.connectionFactory;\n      this.logId = okHttpHttpClient.logId;\n      this.connectionTimeout = okHttpHttpClient.connectionTimeout;\n      this.userAgent = okHttpHttpClient.userAgent;\n    }\n\n    @Override\n    public OkHttpHttpClientBuilder setFollowRedirects(boolean followRedirects) {\n      this.followRedirects = followRedirects;\n      return this;\n    }\n\n    @Override\n    public OkHttpHttpClientBuilder setLogId(String logId) {\n      this.logId = logId;\n      return this;\n    }\n\n    @Override\n    public OkHttpHttpClientBuilder setConnectTimeout(Duration connectionTimeout) {\n      this.connectionTimeout = connectionTimeout;\n      return this;\n    }\n\n    @CanIgnoreReturnValue\n    public OkHttpHttpClientBuilder setUserAgent(String userAgent) {\n      this.userAgent = userAgent;\n      return this;\n    }\n\n    @Override\n    public OkHttpHttpClient build() {\n      return new OkHttpHttpClient(\n          okHttpClient.newBuilder().followRedirects(followRedirects).build(),\n          trustAllCertificates,\n          connectionFactory,\n          logId,\n          connectionTimeout,\n          userAgent);\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/javanet/ConnectionFactory.java",
    "content": "/*\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http.javanet;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\n\n/** Given an URL, produces an {@link HttpURLConnection}. */\npublic interface ConnectionFactory {\n\n  /**\n   * Creates a new {@link HttpURLConnection} from the given {@code url}.\n   *\n   * @param url the URL to which the connection will be made\n   * @return the created connection object, which will still be in the pre-connected state\n   * @throws IOException if there was a problem producing the connection\n   */\n  HttpURLConnection openConnection(String url) throws IOException;\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/http/javanet/DefaultConnectionFactory.java",
    "content": "/*\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http.javanet;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.time.Duration;\nimport javax.net.ssl.HttpsURLConnection;\nimport javax.net.ssl.SSLSocketFactory;\n\n/**\n * Default implementation of {@link ConnectionFactory}, which simply attempts to open the connection\n * and optionally allows trusting all certifications.\n */\npublic class DefaultConnectionFactory implements ConnectionFactory {\n  private final boolean trustAllCertificates;\n  private final SSLSocketFactory trustAllCertsSocketFactory;\n  private final Duration connectTimeout;\n  private final Duration readTimeout;\n\n  public DefaultConnectionFactory(\n      boolean trustAllCertificates,\n      SSLSocketFactory trustAllCertsSocketFactory,\n      Duration connectTimeout,\n      Duration readTimeout) {\n    this.trustAllCertificates = trustAllCertificates;\n    this.trustAllCertsSocketFactory = checkNotNull(trustAllCertsSocketFactory);\n    this.connectTimeout = checkNotNull(connectTimeout);\n    this.readTimeout = checkNotNull(readTimeout);\n  }\n\n  @Override\n  public HttpURLConnection openConnection(String url) throws IOException {\n    HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();\n    connection.setConnectTimeout((int) connectTimeout.toMillis());\n    connection.setReadTimeout((int) readTimeout.toMillis());\n\n    if (connection instanceof HttpsURLConnection && trustAllCertificates) {\n      HttpsURLConnection secureConnection = (HttpsURLConnection) connection;\n      secureConnection.setSSLSocketFactory(trustAllCertsSocketFactory);\n      secureConnection.setHostnameVerifier((hostname, session) -> true);\n    }\n    return connection;\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/socket/DefaultTsunamiSocketFactory.java",
    "content": "/*\n * Copyright 2026 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.socket;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.flogger.GoogleLogger;\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.time.Duration;\nimport javax.net.SocketFactory;\nimport javax.net.ssl.SSLSocket;\nimport javax.net.ssl.SSLSocketFactory;\n\n/**\n * Default implementation of {@link TsunamiSocketFactory} that creates TCP sockets.\n *\n * <p>This implementation wraps the standard Java {@link SocketFactory} and {@link SSLSocketFactory}\n * to ensure that all created sockets have proper timeout settings configured, preventing plugins\n * from hanging indefinitely.\n */\npublic final class DefaultTsunamiSocketFactory implements TsunamiSocketFactory {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  private final SocketFactory socketFactory;\n  private final SSLSocketFactory sslSocketFactory;\n  private final Duration defaultConnectTimeout;\n  private final Duration defaultReadTimeout;\n\n  /**\n   * Creates a new DefaultTsunamiSocketFactory.\n   *\n   * @param socketFactory the underlying socket factory to use for plain TCP connections\n   * @param sslSocketFactory the underlying SSL socket factory to use for SSL/TLS connections\n   * @param defaultConnectTimeout the default timeout for establishing connections\n   * @param defaultReadTimeout the default timeout for read operations\n   */\n  public DefaultTsunamiSocketFactory(\n      SocketFactory socketFactory,\n      SSLSocketFactory sslSocketFactory,\n      Duration defaultConnectTimeout,\n      Duration defaultReadTimeout) {\n    this.socketFactory = checkNotNull(socketFactory);\n    this.sslSocketFactory = checkNotNull(sslSocketFactory);\n    this.defaultConnectTimeout = checkNotNull(defaultConnectTimeout);\n    this.defaultReadTimeout = checkNotNull(defaultReadTimeout);\n\n    checkArgument(!defaultConnectTimeout.isNegative(), \"Connect timeout cannot be negative\");\n    checkArgument(!defaultReadTimeout.isNegative(), \"Read timeout cannot be negative\");\n\n    logger.atInfo().log(\n        \"TsunamiSocketFactory initialized with connect timeout: %s, read timeout: %s\",\n        defaultConnectTimeout, defaultReadTimeout);\n  }\n\n  @Override\n  public Socket createSocket(String host, int port) throws IOException {\n    return createSocket(host, port, defaultConnectTimeout, defaultReadTimeout);\n  }\n\n  @Override\n  public Socket createSocket(String host, int port, Duration timeout) throws IOException {\n    return createSocket(host, port, timeout, timeout);\n  }\n\n  @Override\n  public Socket createSocket(String host, int port, Duration connectTimeout, Duration readTimeout)\n      throws IOException {\n    checkNotNull(host);\n    checkArgument(port > 0 && port <= 65535, \"Port must be between 1 and 65535\");\n    checkNotNull(connectTimeout);\n    checkNotNull(readTimeout);\n\n    logger.atFine().log(\n        \"Creating socket to %s:%d with connect timeout %s, read timeout %s\",\n        host, port, connectTimeout, readTimeout);\n\n    Socket socket = socketFactory.createSocket();\n    configureAndConnect(socket, new InetSocketAddress(host, port), connectTimeout, readTimeout);\n    return socket;\n  }\n\n  @Override\n  public Socket createSocket(InetAddress address, int port) throws IOException {\n    return createSocket(address, port, defaultConnectTimeout, defaultReadTimeout);\n  }\n\n  @Override\n  public Socket createSocket(InetAddress address, int port, Duration timeout) throws IOException {\n    return createSocket(address, port, timeout, timeout);\n  }\n\n  @Override\n  public Socket createSocket(\n      InetAddress address, int port, Duration connectTimeout, Duration readTimeout)\n      throws IOException {\n    checkNotNull(address);\n    checkArgument(port > 0 && port <= 65535, \"Port must be between 1 and 65535\");\n    checkNotNull(connectTimeout);\n    checkNotNull(readTimeout);\n\n    logger.atFine().log(\n        \"Creating socket to %s:%d with connect timeout %s, read timeout %s\",\n        address.getHostAddress(), port, connectTimeout, readTimeout);\n\n    Socket socket = socketFactory.createSocket();\n    configureAndConnect(socket, new InetSocketAddress(address, port), connectTimeout, readTimeout);\n    return socket;\n  }\n\n  @Override\n  public Socket createUnconnectedSocket() throws IOException {\n    Socket socket = socketFactory.createSocket();\n    socket.setSoTimeout((int) defaultReadTimeout.toMillis());\n    logger.atFine().log(\"Created unconnected socket with read timeout %s\", defaultReadTimeout);\n    return socket;\n  }\n\n  @Override\n  public SSLSocket createSslSocket(String host, int port) throws IOException {\n    return createSslSocket(host, port, defaultConnectTimeout, defaultReadTimeout);\n  }\n\n  @Override\n  public SSLSocket createSslSocket(String host, int port, Duration timeout) throws IOException {\n    return createSslSocket(host, port, timeout, timeout);\n  }\n\n  @Override\n  public SSLSocket createSslSocket(\n      String host, int port, Duration connectTimeout, Duration readTimeout) throws IOException {\n    checkNotNull(host);\n    checkArgument(port > 0 && port <= 65535, \"Port must be between 1 and 65535\");\n    checkNotNull(connectTimeout);\n    checkNotNull(readTimeout);\n\n    logger.atFine().log(\n        \"Creating SSL socket to %s:%d with connect timeout %s, read timeout %s\",\n        host, port, connectTimeout, readTimeout);\n\n    // Create a plain socket first, connect with timeout, then wrap with SSL\n    Socket plainSocket = socketFactory.createSocket();\n    configureAndConnect(\n        plainSocket, new InetSocketAddress(host, port), connectTimeout, readTimeout);\n\n    SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(plainSocket, host, port, true);\n    sslSocket.setSoTimeout((int) readTimeout.toMillis());\n    sslSocket.startHandshake();\n\n    return sslSocket;\n  }\n\n  @Override\n  public SSLSocket createSslSocket(InetAddress address, int port) throws IOException {\n    return createSslSocket(address, port, defaultConnectTimeout, defaultReadTimeout);\n  }\n\n  @Override\n  public SSLSocket createSslSocket(InetAddress address, int port, Duration timeout)\n      throws IOException {\n    return createSslSocket(address, port, timeout, timeout);\n  }\n\n  @Override\n  public SSLSocket createSslSocket(\n      InetAddress address, int port, Duration connectTimeout, Duration readTimeout)\n      throws IOException {\n    checkNotNull(address);\n    checkArgument(port > 0 && port <= 65535, \"Port must be between 1 and 65535\");\n    checkNotNull(connectTimeout);\n    checkNotNull(readTimeout);\n\n    logger.atFine().log(\n        \"Creating SSL socket to %s:%d with connect timeout %s, read timeout %s\",\n        address.getHostAddress(), port, connectTimeout, readTimeout);\n\n    // Create a plain socket first, connect with timeout, then wrap with SSL\n    Socket plainSocket = socketFactory.createSocket();\n    configureAndConnect(\n        plainSocket, new InetSocketAddress(address, port), connectTimeout, readTimeout);\n\n    SSLSocket sslSocket =\n        (SSLSocket)\n            sslSocketFactory.createSocket(plainSocket, address.getHostAddress(), port, true);\n    sslSocket.setSoTimeout((int) readTimeout.toMillis());\n    sslSocket.startHandshake();\n\n    return sslSocket;\n  }\n\n  @Override\n  public SSLSocket wrapWithSsl(Socket socket, String host, int port, boolean autoClose)\n      throws IOException {\n    checkNotNull(socket);\n    checkNotNull(host);\n    checkArgument(port > 0 && port <= 65535, \"Port must be between 1 and 65535\");\n\n    logger.atFine().log(\"Wrapping existing socket with SSL for host %s:%d\", host, port);\n\n    SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, host, port, autoClose);\n    // Preserve the timeout from the original socket if set, otherwise use default\n    int originalTimeout = socket.getSoTimeout();\n    if (originalTimeout > 0) {\n      sslSocket.setSoTimeout(originalTimeout);\n    } else {\n      sslSocket.setSoTimeout((int) defaultReadTimeout.toMillis());\n    }\n    sslSocket.startHandshake();\n\n    return sslSocket;\n  }\n\n  @Override\n  public Duration getDefaultConnectTimeout() {\n    return defaultConnectTimeout;\n  }\n\n  @Override\n  public Duration getDefaultReadTimeout() {\n    return defaultReadTimeout;\n  }\n\n  /**\n   * Configures socket options and connects to the specified address with timeout.\n   *\n   * @param socket the socket to configure and connect\n   * @param address the address to connect to\n   * @param connectTimeout the timeout for establishing the connection\n   * @param readTimeout the timeout for read operations\n   * @throws IOException if an I/O error occurs\n   */\n  private void configureAndConnect(\n      Socket socket, InetSocketAddress address, Duration connectTimeout, Duration readTimeout)\n      throws IOException {\n    // Set read timeout before connecting\n    socket.setSoTimeout((int) readTimeout.toMillis());\n\n    // Enable TCP keep-alive to detect dead connections\n    socket.setKeepAlive(true);\n\n    // Disable Nagle's algorithm for better latency in security scanning\n    socket.setTcpNoDelay(true);\n\n    // Connect with timeout\n    socket.connect(address, (int) connectTimeout.toMillis());\n\n    logger.atFine().log(\n        \"Socket connected to %s with SO_TIMEOUT=%dms\", address, readTimeout.toMillis());\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/socket/TsunamiSocketFactory.java",
    "content": "/*\n * Copyright 2026 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.socket;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.net.Socket;\nimport java.time.Duration;\nimport javax.net.ssl.SSLSocket;\n\n/**\n * A socket factory API for creating TCP sockets with enforced default configurations.\n *\n * <p>This API provides a normalized way to create sockets from Tsunami plugins, ensuring that\n * proper timeouts are always configured. This prevents plugins from hanging indefinitely when\n * servers don't respond.\n *\n * <p>Plugins should use this factory instead of directly creating sockets through {@link\n * javax.net.SocketFactory} or {@link javax.net.ssl.SSLSocketFactory} to ensure consistent behavior\n * and proper timeout handling.\n *\n * <p>Example usage:\n *\n * <pre>{@code\n * @Inject\n * TsunamiSocketFactory socketFactory;\n *\n * // Create a socket with default timeouts\n * Socket socket = socketFactory.createSocket(\"example.com\", 80);\n *\n * // Create a socket with custom timeouts\n * Socket socket = socketFactory.createSocket(\"example.com\", 80,\n *     Duration.ofSeconds(5), Duration.ofSeconds(10));\n *\n * // Create an SSL socket\n * SSLSocket sslSocket = socketFactory.createSslSocket(\"example.com\", 443);\n * }</pre>\n */\npublic interface TsunamiSocketFactory {\n\n  /**\n   * Creates a TCP socket connected to the specified host and port with default timeouts.\n   *\n   * <p>The socket will be configured with the default connect and read timeouts specified in the\n   * Tsunami configuration. If no configuration is provided, sensible defaults will be used.\n   *\n   * @param host the host to connect to\n   * @param port the port to connect to\n   * @return a connected socket with enforced timeouts\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  Socket createSocket(String host, int port) throws IOException;\n\n  /**\n   * Creates a TCP socket connected to the specified host and port with a single timeout.\n   *\n   * <p>The specified timeout will be used for both connection establishment and read operations.\n   *\n   * @param host the host to connect to\n   * @param port the port to connect to\n   * @param timeout the timeout for both connection and read operations\n   * @return a connected socket with the specified timeout\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  Socket createSocket(String host, int port, Duration timeout) throws IOException;\n\n  /**\n   * Creates a TCP socket connected to the specified host and port with custom timeouts.\n   *\n   * @param host the host to connect to\n   * @param port the port to connect to\n   * @param connectTimeout the timeout for establishing the connection\n   * @param readTimeout the timeout for read operations (SO_TIMEOUT)\n   * @return a connected socket with the specified timeouts\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  Socket createSocket(String host, int port, Duration connectTimeout, Duration readTimeout)\n      throws IOException;\n\n  /**\n   * Creates a TCP socket connected to the specified address and port with default timeouts.\n   *\n   * @param address the IP address to connect to\n   * @param port the port to connect to\n   * @return a connected socket with enforced timeouts\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  Socket createSocket(InetAddress address, int port) throws IOException;\n\n  /**\n   * Creates a TCP socket connected to the specified address and port with a single timeout.\n   *\n   * <p>The specified timeout will be used for both connection establishment and read operations.\n   *\n   * @param address the IP address to connect to\n   * @param port the port to connect to\n   * @param timeout the timeout for both connection and read operations\n   * @return a connected socket with the specified timeout\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  Socket createSocket(InetAddress address, int port, Duration timeout) throws IOException;\n\n  /**\n   * Creates a TCP socket connected to the specified address and port with custom timeouts.\n   *\n   * @param address the IP address to connect to\n   * @param port the port to connect to\n   * @param connectTimeout the timeout for establishing the connection\n   * @param readTimeout the timeout for read operations (SO_TIMEOUT)\n   * @return a connected socket with the specified timeouts\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  Socket createSocket(InetAddress address, int port, Duration connectTimeout, Duration readTimeout)\n      throws IOException;\n\n  /**\n   * Creates an unconnected TCP socket with default timeouts configured.\n   *\n   * <p>The returned socket will have SO_TIMEOUT set to the default read timeout. The caller is\n   * responsible for connecting the socket, which should be done with a timeout.\n   *\n   * @return an unconnected socket with default read timeout configured\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  Socket createUnconnectedSocket() throws IOException;\n\n  /**\n   * Creates an SSL/TLS socket connected to the specified host and port with default timeouts.\n   *\n   * <p>The socket will be configured with the default connect and read timeouts. SSL/TLS handshake\n   * will be performed automatically.\n   *\n   * @param host the host to connect to\n   * @param port the port to connect to\n   * @return a connected SSL socket with enforced timeouts\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  SSLSocket createSslSocket(String host, int port) throws IOException;\n\n  /**\n   * Creates an SSL/TLS socket connected to the specified host and port with a single timeout.\n   *\n   * <p>The specified timeout will be used for both connection establishment and read operations.\n   *\n   * @param host the host to connect to\n   * @param port the port to connect to\n   * @param timeout the timeout for both connection and read operations\n   * @return a connected SSL socket with the specified timeout\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  SSLSocket createSslSocket(String host, int port, Duration timeout) throws IOException;\n\n  /**\n   * Creates an SSL/TLS socket connected to the specified host and port with custom timeouts.\n   *\n   * @param host the host to connect to\n   * @param port the port to connect to\n   * @param connectTimeout the timeout for establishing the connection\n   * @param readTimeout the timeout for read operations (SO_TIMEOUT)\n   * @return a connected SSL socket with the specified timeouts\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  SSLSocket createSslSocket(String host, int port, Duration connectTimeout, Duration readTimeout)\n      throws IOException;\n\n  /**\n   * Creates an SSL/TLS socket connected to the specified address and port with default timeouts.\n   *\n   * @param address the IP address to connect to\n   * @param port the port to connect to\n   * @return a connected SSL socket with enforced timeouts\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  SSLSocket createSslSocket(InetAddress address, int port) throws IOException;\n\n  /**\n   * Creates an SSL/TLS socket connected to the specified address and port with a single timeout.\n   *\n   * <p>The specified timeout will be used for both connection establishment and read operations.\n   *\n   * @param address the IP address to connect to\n   * @param port the port to connect to\n   * @param timeout the timeout for both connection and read operations\n   * @return a connected SSL socket with the specified timeout\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  SSLSocket createSslSocket(InetAddress address, int port, Duration timeout) throws IOException;\n\n  /**\n   * Creates an SSL/TLS socket connected to the specified address and port with custom timeouts.\n   *\n   * @param address the IP address to connect to\n   * @param port the port to connect to\n   * @param connectTimeout the timeout for establishing the connection\n   * @param readTimeout the timeout for read operations (SO_TIMEOUT)\n   * @return a connected SSL socket with the specified timeouts\n   * @throws IOException if an I/O error occurs while creating the socket\n   */\n  SSLSocket createSslSocket(\n      InetAddress address, int port, Duration connectTimeout, Duration readTimeout)\n      throws IOException;\n\n  /**\n   * Wraps an existing socket with SSL/TLS.\n   *\n   * <p>This method is useful when you need to upgrade a plain TCP connection to SSL/TLS, such as\n   * when implementing STARTTLS protocols.\n   *\n   * @param socket the existing socket to wrap\n   * @param host the hostname for SNI (Server Name Indication)\n   * @param port the port number\n   * @param autoClose whether the underlying socket should be closed when the SSL socket is closed\n   * @return an SSL socket wrapping the existing socket\n   * @throws IOException if an I/O error occurs while creating the SSL socket\n   */\n  SSLSocket wrapWithSsl(Socket socket, String host, int port, boolean autoClose) throws IOException;\n\n  /**\n   * Returns the default connect timeout configured for this factory.\n   *\n   * @return the default connect timeout\n   */\n  Duration getDefaultConnectTimeout();\n\n  /**\n   * Returns the default read timeout configured for this factory.\n   *\n   * @return the default read timeout\n   */\n  Duration getDefaultReadTimeout();\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/socket/TsunamiSocketFactoryCliOptions.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.socket;\n\nimport com.beust.jcommander.Parameter;\nimport com.beust.jcommander.ParameterException;\nimport com.beust.jcommander.Parameters;\nimport com.google.tsunami.common.cli.CliOption;\nimport org.checkerframework.checker.nullness.qual.Nullable;\n\n/**\n * Command line arguments for {@link TsunamiSocketFactory}.\n *\n * <p>These options allow users to override socket timeout settings from the command line. CLI\n * options take precedence over configuration file settings.\n */\n@Parameters(separators = \"=\")\npublic final class TsunamiSocketFactoryCliOptions implements CliOption {\n\n  @Parameter(\n      names = \"--socket-connect-timeout-seconds\",\n      description =\n          \"The timeout in seconds for establishing TCP connections. This timeout applies to the\"\n              + \" socket connect operation. Default is 10 seconds.\")\n  public Integer connectTimeoutSeconds;\n\n  @Parameter(\n      names = \"--socket-read-timeout-seconds\",\n      description =\n          \"The timeout in seconds for read operations on sockets (SO_TIMEOUT). If no data is\"\n              + \" received within this time, a SocketTimeoutException will be thrown. Default is 30\"\n              + \" seconds.\")\n  public Integer readTimeoutSeconds;\n\n  @Parameter(\n      names = \"--socket-trust-all-certificates\",\n      arity = 1,\n      description =\n          \"Whether SSL/TLS connections should trust all certificates. When true, accepts any SSL\"\n              + \" certificate without validation. Useful for scanning targets with self-signed\"\n              + \" certificates. Default is true.\")\n  public Boolean trustAllCertificates;\n\n  @Parameter(\n      names = \"--socket-disable-timeouts\",\n      arity = 1,\n      description =\n          \"Disable all socket timeouts, allowing connections to wait indefinitely. WARNING: This\"\n              + \" can cause plugins to hang forever if servers do not respond. Use with caution.\"\n              + \" Default is false.\")\n  public Boolean disableTimeouts;\n\n  @Override\n  public void validate() {\n    // Skip timeout validation if timeouts are disabled\n    if (disableTimeouts != null && disableTimeouts) {\n      return;\n    }\n    validateTimeout(\"--socket-connect-timeout-seconds\", connectTimeoutSeconds);\n    validateTimeout(\"--socket-read-timeout-seconds\", readTimeoutSeconds);\n  }\n\n  private static void validateTimeout(String flagName, @Nullable Integer value) {\n    if (value != null && value < 0) {\n      throw new ParameterException(\n          String.format(\"%s cannot be a negative number, received %d.\", flagName, value));\n    }\n    if (value != null && value == 0) {\n      throw new ParameterException(\n          String.format(\n              \"%s cannot be zero. Use a positive value for timeouts or pass the\"\n                  + \" --socket-disable-timeouts flag to disable timeouts entirely. Received %d.\",\n              flagName, value));\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/socket/TsunamiSocketFactoryConfigProperties.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.socket;\n\nimport com.google.tsunami.common.config.annotations.ConfigProperties;\n\n/**\n * Configuration properties for {@link TsunamiSocketFactory}.\n *\n * <p>These properties can be set in the Tsunami configuration file (e.g., tsunami.yaml) under the\n * {@code common.net.socket} prefix:\n *\n * <pre>{@code\n * common:\n *   net:\n *     socket:\n *       connect_timeout_seconds: 10\n *       read_timeout_seconds: 30\n *       trust_all_certificates: true\n *       disable_timeouts: false\n * }</pre>\n */\n@ConfigProperties(\"common.net.socket\")\npublic final class TsunamiSocketFactoryConfigProperties {\n\n  /**\n   * The timeout in seconds for establishing TCP connections.\n   *\n   * <p>This timeout applies to the socket connect operation. If the connection cannot be\n   * established within this time, a {@link java.net.SocketTimeoutException} will be thrown.\n   *\n   * <p>Default value is 10 seconds if not specified.\n   */\n  Integer connectTimeoutSeconds;\n\n  /**\n   * The timeout in seconds for read operations on sockets.\n   *\n   * <p>This timeout is set as the socket's SO_TIMEOUT option. If no data is received within this\n   * time, a {@link java.net.SocketTimeoutException} will be thrown.\n   *\n   * <p>Default value is 30 seconds if not specified. This is intentionally longer than the connect\n   * timeout to allow for slow servers or large data transfers.\n   */\n  Integer readTimeoutSeconds;\n\n  /**\n   * Whether SSL/TLS connections should trust all certificates.\n   *\n   * <p>When set to true, the socket factory will accept any SSL certificate without validation.\n   * This is useful for security scanning where targets may have self-signed or expired\n   * certificates.\n   *\n   * <p><strong>Warning:</strong> This should only be used in controlled environments. Setting this\n   * to true disables certificate validation which could expose the scanner to man-in-the-middle\n   * attacks.\n   *\n   * <p>Default value is true for security scanning purposes.\n   */\n  Boolean trustAllCertificates;\n\n  /**\n   * Whether to disable all socket timeouts.\n   *\n   * <p>When set to true, sockets will wait indefinitely for connections and data. This means\n   * plugins may hang forever if a server does not respond.\n   *\n   * <p><strong>Warning:</strong> Disabling timeouts is dangerous and can cause the scanner to hang\n   * indefinitely. Only use this option if you have a specific need to wait for slow or unresponsive\n   * servers.\n   *\n   * <p>Default value is false (timeouts are enforced).\n   */\n  Boolean disableTimeouts;\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/net/socket/TsunamiSocketFactoryModule.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.socket;\n\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Provides;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.security.GeneralSecurityException;\nimport java.security.SecureRandom;\nimport java.security.cert.X509Certificate;\nimport java.time.Duration;\nimport javax.inject.Qualifier;\nimport javax.inject.Singleton;\nimport javax.net.SocketFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509TrustManager;\n\n/**\n * Guice module for installing {@link TsunamiSocketFactory}.\n *\n * <p>This module provides a {@link TsunamiSocketFactory} instance that creates sockets with\n * enforced timeout configurations. It integrates with Tsunami's configuration system to allow\n * customization of default timeouts through both configuration files and command-line options.\n *\n * <p>Example usage in a plugin:\n *\n * <pre>{@code\n * public class MyPlugin implements VulnDetector {\n *   private final TsunamiSocketFactory socketFactory;\n *\n *   @Inject\n *   MyPlugin(TsunamiSocketFactory socketFactory) {\n *     this.socketFactory = socketFactory;\n *   }\n *\n *   public void doSomething() throws IOException {\n *     // Socket will have enforced timeouts\n *     Socket socket = socketFactory.createSocket(\"example.com\", 80);\n *     // ...\n *   }\n * }\n * }</pre>\n */\npublic final class TsunamiSocketFactoryModule extends AbstractModule {\n\n  // Default timeout values\n  private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 10;\n  private static final int DEFAULT_READ_TIMEOUT_SECONDS = 30;\n  private static final boolean DEFAULT_TRUST_ALL_CERTIFICATES = true;\n  private static final boolean DEFAULT_DISABLE_TIMEOUTS = false;\n\n  // This TrustManager does NOT validate certificate chains.\n  private static final X509TrustManager TRUST_ALL_CERTS_MANAGER =\n      new X509TrustManager() {\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String authType) {}\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String authType) {}\n\n        @Override\n        public X509Certificate[] getAcceptedIssuers() {\n          return new X509Certificate[0];\n        }\n      };\n\n  @Override\n  protected void configure() {\n    // Module configuration is handled by @Provides methods\n  }\n\n  @Provides\n  @Singleton\n  TsunamiSocketFactory provideTsunamiSocketFactory(\n      @TrustAllCertificates boolean trustAllCertificates,\n      @DisableTimeouts boolean disableTimeouts,\n      @ConnectTimeoutSeconds int connectTimeoutSeconds,\n      @ReadTimeoutSeconds int readTimeoutSeconds)\n      throws GeneralSecurityException {\n    SocketFactory socketFactory = SocketFactory.getDefault();\n    SSLSocketFactory sslSocketFactory;\n\n    if (trustAllCertificates) {\n      SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n      sslContext.init(null, new TrustManager[] {TRUST_ALL_CERTS_MANAGER}, new SecureRandom());\n      sslSocketFactory = sslContext.getSocketFactory();\n    } else {\n      sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();\n    }\n\n    // When timeouts are disabled, use 0 which means infinite timeout in Java\n    Duration connectTimeout =\n        disableTimeouts ? Duration.ZERO : Duration.ofSeconds(connectTimeoutSeconds);\n    Duration readTimeout = disableTimeouts ? Duration.ZERO : Duration.ofSeconds(readTimeoutSeconds);\n\n    return new DefaultTsunamiSocketFactory(\n        socketFactory, sslSocketFactory, connectTimeout, readTimeout);\n  }\n\n  @Provides\n  @DisableTimeouts\n  boolean provideDisableTimeouts(\n      TsunamiSocketFactoryCliOptions cliOptions,\n      TsunamiSocketFactoryConfigProperties configProperties) {\n    if (cliOptions.disableTimeouts != null) {\n      return cliOptions.disableTimeouts;\n    }\n    if (configProperties.disableTimeouts != null) {\n      return configProperties.disableTimeouts;\n    }\n    return DEFAULT_DISABLE_TIMEOUTS;\n  }\n\n  @Provides\n  @TrustAllCertificates\n  boolean provideTrustAllCertificates(\n      TsunamiSocketFactoryCliOptions cliOptions,\n      TsunamiSocketFactoryConfigProperties configProperties) {\n    if (cliOptions.trustAllCertificates != null) {\n      return cliOptions.trustAllCertificates;\n    }\n    if (configProperties.trustAllCertificates != null) {\n      return configProperties.trustAllCertificates;\n    }\n    return DEFAULT_TRUST_ALL_CERTIFICATES;\n  }\n\n  @Provides\n  @ConnectTimeoutSeconds\n  int provideConnectTimeoutSeconds(\n      TsunamiSocketFactoryCliOptions cliOptions,\n      TsunamiSocketFactoryConfigProperties configProperties) {\n    if (cliOptions.connectTimeoutSeconds != null) {\n      return cliOptions.connectTimeoutSeconds;\n    }\n    if (configProperties.connectTimeoutSeconds != null) {\n      return configProperties.connectTimeoutSeconds;\n    }\n    return DEFAULT_CONNECT_TIMEOUT_SECONDS;\n  }\n\n  @Provides\n  @ReadTimeoutSeconds\n  int provideReadTimeoutSeconds(\n      TsunamiSocketFactoryCliOptions cliOptions,\n      TsunamiSocketFactoryConfigProperties configProperties) {\n    if (cliOptions.readTimeoutSeconds != null) {\n      return cliOptions.readTimeoutSeconds;\n    }\n    if (configProperties.readTimeoutSeconds != null) {\n      return configProperties.readTimeoutSeconds;\n    }\n    return DEFAULT_READ_TIMEOUT_SECONDS;\n  }\n\n  /** Qualifier for whether to disable all socket timeouts. */\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  public @interface DisableTimeouts {}\n\n  /** Qualifier for whether to trust all SSL certificates. */\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  public @interface TrustAllCertificates {}\n\n  /** Qualifier for the connect timeout in seconds. */\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  public @interface ConnectTimeoutSeconds {}\n\n  /** Qualifier for the read timeout in seconds. */\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  public @interface ReadTimeoutSeconds {}\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/reflection/ClassGraphModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.reflection;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.inject.AbstractModule;\nimport io.github.classgraph.ScanResult;\n\n/** Guice module for providing ClassGraph bindings. */\npublic final class ClassGraphModule extends AbstractModule {\n  private final ScanResult scanResult;\n\n  public ClassGraphModule(ScanResult scanResult) {\n    this.scanResult = checkNotNull(scanResult);\n  }\n\n  @Override\n  protected void configure() {\n    bind(ScanResult.class).annotatedWith(RuntimeClassGraphScanResult.class).toInstance(scanResult);\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/reflection/RuntimeClassGraphScanResult.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.reflection;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.ElementType.METHOD;\nimport static java.lang.annotation.ElementType.PARAMETER;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport javax.inject.Qualifier;\n\n/** Annotation for the ClassGraph {@link io.github.classgraph.ScanResult} at runtime. */\n@Qualifier\n@Retention(RetentionPolicy.RUNTIME)\n@Target({PARAMETER, METHOD, FIELD})\npublic @interface RuntimeClassGraphScanResult {}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/server/CompactRunRequestHelper.java",
    "content": "/*\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.server;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.tsunami.proto.MatchedPlugin;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.RunCompactRequest;\nimport com.google.tsunami.proto.RunCompactRequest.PluginNetworkServiceTarget;\nimport com.google.tsunami.proto.RunRequest;\nimport java.util.HashMap;\n\n/**\n * CompactRunRequestHelper is a helper class to compress/uncompress the RunRequest into/from the\n * compact representation.\n */\npublic final class CompactRunRequestHelper {\n\n  private CompactRunRequestHelper() {}\n\n  public static RunCompactRequest compress(RunRequest runRequest) {\n    var builder = RunCompactRequest.newBuilder().setTarget(runRequest.getTarget());\n    HashMap<NetworkService, Integer> serviceIndexMap = new HashMap<>();\n    int pluginIndex = -1;\n    for (MatchedPlugin matchedPlugin : runRequest.getPluginsList()) {\n      pluginIndex++;\n      builder.addPlugins(matchedPlugin.getPlugin());\n      for (NetworkService service : matchedPlugin.getServicesList()) {\n        Integer serviceIndex = serviceIndexMap.get(service);\n        if (serviceIndex == null) {\n          serviceIndex = serviceIndexMap.size();\n          serviceIndexMap.put(service, serviceIndex);\n          builder.addServices(service);\n        }\n\n        builder.addScanTargets(\n            PluginNetworkServiceTarget.newBuilder()\n                .setPluginIndex(pluginIndex)\n                .setServiceIndex(serviceIndex)\n                .build());\n      }\n    }\n    return builder.build();\n  }\n\n  public static RunRequest uncompress(RunCompactRequest runCompactRequest) {\n    ImmutableList.Builder<MatchedPlugin> matchedPlugins = ImmutableList.builder();\n    for (var target : runCompactRequest.getScanTargetsList()) {\n      var plugin = runCompactRequest.getPlugins(target.getPluginIndex());\n      var networkService = runCompactRequest.getServices(target.getServiceIndex());\n      matchedPlugins.add(\n          MatchedPlugin.newBuilder().setPlugin(plugin).addServices(networkService).build());\n    }\n\n    return RunRequest.newBuilder()\n        .setTarget(runCompactRequest.getTarget())\n        .addAllPlugins(matchedPlugins.build())\n        .build();\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/server/LanguageServerCommand.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.server;\n\nimport com.google.auto.value.AutoValue;\nimport java.time.Duration;\nimport javax.annotation.Nullable;\n\n/** Command to spawn a language server and associated command lines. */\n@AutoValue\npublic abstract class LanguageServerCommand {\n  public static LanguageServerCommand create(\n      String serverCommand,\n      @Nullable String serverAddress,\n      String port,\n      String logId,\n      String outputDir,\n      boolean trustAllSsl,\n      Duration timeoutSeconds,\n      String callbackAddress,\n      int callbackPort,\n      String pollingUri,\n      int deadlineRunSeconds) {\n    return new AutoValue_LanguageServerCommand(\n        serverCommand,\n        serverAddress,\n        port,\n        logId,\n        outputDir,\n        trustAllSsl,\n        timeoutSeconds,\n        callbackAddress,\n        callbackPort,\n        pollingUri,\n        deadlineRunSeconds);\n  }\n\n  public abstract String serverCommand();\n\n  public abstract String serverAddress();\n\n  public abstract String port();\n\n  public abstract String logId();\n\n  public abstract String outputDir();\n\n  public abstract boolean trustAllSslCert();\n\n  public abstract Duration timeoutSeconds();\n\n  public abstract String callbackAddress();\n\n  public abstract int callbackPort();\n\n  public abstract String pollingUri();\n\n  public abstract int deadlineRunSeconds();\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/time/SystemUtcClockModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.time;\n\nimport com.google.inject.AbstractModule;\nimport java.time.Clock;\n\n/** Binds {@link java.time.Clock} to a {@code Clock.systemUTC()}. */\npublic class SystemUtcClockModule extends AbstractModule {\n\n  @Override\n  protected void configure() {\n    bind(Clock.class).annotatedWith(UtcClock.class).toInstance(Clock.systemUTC());\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/time/UtcClock.java",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.time;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport javax.inject.Qualifier;\n\n/** Annotation for the UTC {@link java.time.Clock} used in Tsunami. */\n@Qualifier\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface UtcClock {}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/time/testing/FakeUtcClock.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.time.testing;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport java.time.Clock;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.ZoneId;\nimport java.time.ZoneOffset;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Implementation of a {@link java.time.Clock} that returns a settable {@link Instant} value.\n *\n * <p>By default, the clock is set to the {@link Instant} when the clock is created. Clock can be\n * set to a specific instant by {@link #setNow}.\n */\npublic final class FakeUtcClock extends Clock {\n\n  private final AtomicReference<Instant> nowReference = new AtomicReference<>();\n\n  private FakeUtcClock(Instant now) {\n    nowReference.set(checkNotNull(now));\n  }\n\n  /**\n   * Create a {@link FakeUtcClock} instance initialized to UTC now.\n   *\n   * <p>To create a fake UTC clock at a specific instant, calling {@code setNow()} as in:\n   *\n   * <pre>{@code FakeUtcClock fakeUtcClock = FakeUtcClock.create().setNow(TARGET_INSTANT);}</pre>\n   *\n   * @return a {@link FakeUtcClock} instance.\n   */\n  public static FakeUtcClock create() {\n    return new FakeUtcClock(Instant.now());\n  }\n\n  /**\n   * Sets the return value of {@link #instant()}.\n   *\n   * @param now the instant that this clock points to\n   * @return this\n   */\n  public FakeUtcClock setNow(Instant now) {\n    nowReference.set(checkNotNull(now));\n    return this;\n  }\n\n  /**\n   * Advances the clock by the given duration.\n   *\n   * <p>NOTE: this method can be called with a negative duration if the clock needs to go back in\n   * time.\n   *\n   * @param increment the duration to advance the clock by\n   * @return this\n   */\n  public FakeUtcClock advance(Duration increment) {\n    checkNotNull(increment);\n    nowReference.getAndUpdate(now -> now.plus(increment));\n    return this;\n  }\n\n  @Override\n  public Instant instant() {\n    return nowReference.get();\n  }\n\n  @Override\n  public ZoneId getZone() {\n    return ZoneOffset.UTC;\n  }\n\n  @Override\n  public Clock withZone(ZoneId zone) {\n    throw new UnsupportedOperationException(\"Setting ZoneId to FakeUtcClock is not supported\");\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (obj instanceof FakeUtcClock) {\n      FakeUtcClock other = (FakeUtcClock) obj;\n      return nowReference.get().equals(other.nowReference.get());\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return nowReference.get().hashCode();\n  }\n\n  @Override\n  public String toString() {\n    return String.format(\"FakeUtcClock(now = %s)\", nowReference.get());\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/time/testing/FakeUtcClockModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.time.testing;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.inject.AbstractModule;\nimport com.google.tsunami.common.time.UtcClock;\nimport java.time.Clock;\n\n/** Binds {@link java.time.Clock} to a {@link FakeUtcClock}. */\npublic class FakeUtcClockModule extends AbstractModule {\n  private final FakeUtcClock fakeUtcClock;\n\n  public FakeUtcClockModule() {\n    this.fakeUtcClock = FakeUtcClock.create();\n  }\n\n  public FakeUtcClockModule(FakeUtcClock fakeUtcClock) {\n    this.fakeUtcClock = checkNotNull(fakeUtcClock);\n  }\n\n  @Override\n  protected void configure() {\n    bind(Clock.class).annotatedWith(UtcClock.class).toInstance(fakeUtcClock);\n    bind(FakeUtcClock.class).annotatedWith(UtcClock.class).toInstance(fakeUtcClock);\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/version/ComparisonUtility.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport java.util.List;\n\n/** Utility for version related comparison. */\nfinal class ComparisonUtility {\n\n  private ComparisonUtility() {}\n\n  /**\n   * Compares two lists. If one list is shorter than the other, {@code fillValue} is used for\n   * comparison.\n   *\n   * <p>For example, comparing {@code [1, 2, 3]} and {@code [1, 2, 3, 4]} with {@code fillValue}\n   * equals to 10 is equivalent of comparing {@code [1, 2, 3, 10]} with {@code [1, 2, 3, 4]}.\n   *\n   * @param left list for comparison.\n   * @param right list for comparison.\n   * @param fillValue value to use if one list is shorter than the other.\n   * @param <T> element type of the list.\n   * @return 0 if two lists equals, -1 if {@code left} is less than {@code right}, 1 otherwise.\n   */\n  static <T extends Comparable<? super T>> int compareListWithFillValue(\n      List<T> left, List<T> right, T fillValue) {\n    int longest = Math.max(left.size(), right.size());\n    for (int i = 0; i < longest; i++) {\n      T leftElement = fillValue;\n      T rightElement = fillValue;\n\n      if (i < left.size()) {\n        leftElement = left.get(i);\n      }\n      if (i < right.size()) {\n        rightElement = right.get(i);\n      }\n\n      int compareResult = leftElement.compareTo(rightElement);\n      if (compareResult != 0) {\n        return compareResult;\n      }\n    }\n\n    return 0;\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/version/KnownQualifier.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.base.Ascii;\nimport java.util.Arrays;\n\n/**\n * A list of all the known text token qualifiers from our vulnerability feeds and the order here\n * represents the precedence of these identifiers.\n */\nenum KnownQualifier implements Comparable<KnownQualifier> {\n  ALPHA(\"alpha\"),\n  BETA(\"beta\"),\n  PRE(\"pre\"),\n  R(\"r\"),\n  RC(\"rc\"),\n  ABSENT(\"\"),\n  P(\"p\"),\n  PATCH(\"patch\"),\n  PATCHED(\"patched\");\n\n  private final String qualifierText;\n\n  KnownQualifier(String qualifierText) {\n    this.qualifierText = qualifierText;\n  }\n\n  String getQualifierText() {\n    return this.qualifierText;\n  }\n\n  static boolean isKnownQualifier(String string) {\n    checkNotNull(string);\n    return Arrays.stream(KnownQualifier.values())\n        .anyMatch(knownQualifier -> Ascii.equalsIgnoreCase(knownQualifier.qualifierText, string));\n  }\n\n  static KnownQualifier fromText(String string) {\n    checkNotNull(string);\n    return Arrays.stream(KnownQualifier.values())\n        .filter(knownQualifier -> Ascii.equalsIgnoreCase(knownQualifier.qualifierText, string))\n        .findFirst()\n        .orElseThrow(\n            () ->\n                new IllegalArgumentException(\n                    String.format(\"%s is not a valid KnownQualifier text.\", string)));\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/version/Segment.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.errorprone.annotations.Immutable;\nimport java.util.Arrays;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\n/**\n * The segment of a version number, separated by the semantic separators from the original version\n * string.\n *\n * <p>For example, there are 2 segments in version string \"2.1.1-alpha.1\": \"2.1.1\" and \"alpha.1\".\n *\n * <p>The first element of a Segment should always be a {@link KnownQualifier} token. If no {@link\n * KnownQualifier} is found in the segment, an ABSENT qualifier is added.\n */\n@AutoValue\n@Immutable\nabstract class Segment implements Comparable<Segment> {\n  static final Segment NULL = Segment.fromTokenList(ImmutableList.of(Token.EMPTY));\n\n  private static final ImmutableSet<String> TOKENIZER_DELIMITERS =\n      ImmutableSet.of(\"\\\\.\", \"\\\\+\", \"-\", \":\", \"_\", \"~\");\n  private static final Pattern TOKENIZER_SPLIT_REGEX =\n      Pattern.compile(\n          // Additional split on boundaries between number and text.\n          \"(?<=\\\\D)(?=\\\\d)|(?<=\\\\d)(?=\\\\D)|\"\n              // We keep the delimiter for comparison.\n              + TOKENIZER_DELIMITERS.stream()\n                  .map(delimiter -> String.format(\"((?<=%1$s)|(?=%1$s))\", delimiter))\n                  .collect(Collectors.joining(\"|\")));\n  private static final ImmutableSet<String> EXCLUDED_TOKENS = ImmutableSet.of(\".\", \"gg\", \"N/A\");\n\n  /** All the tokens within this segment. */\n  abstract ImmutableList<Token> tokens();\n\n  static Segment fromTokenList(ImmutableList<Token> tokens) {\n    ImmutableList.Builder<Token> finalTokensBuilder = new ImmutableList.Builder<>();\n\n    // Make sure Segment always starts with a KnownQualifier. See http://b/135912609 for details.\n    if (tokens.isEmpty() || !tokens.get(0).isKnownQualifier()) {\n      finalTokensBuilder.add(Token.fromKnownQualifier(KnownQualifier.ABSENT));\n    }\n    finalTokensBuilder.addAll(tokens);\n\n    return new AutoValue_Segment(finalTokensBuilder.build());\n  }\n\n  static Segment fromString(String segmentString) {\n    return parseFromString(segmentString);\n  }\n\n  private static Segment parseFromString(String segmentString) {\n    ImmutableList<String> rawTokens =\n        Arrays.stream(TOKENIZER_SPLIT_REGEX.split(segmentString))\n            .filter(token -> !token.isEmpty())\n            .filter(token -> !EXCLUDED_TOKENS.contains(token))\n            .collect(ImmutableList.toImmutableList());\n\n    if (rawTokens.isEmpty()) {\n      return Segment.NULL;\n    }\n\n    ImmutableList.Builder<Token> tokensBuilder = new ImmutableList.Builder<>();\n\n    // Make sure Segment always starts with a KnownQualifier. See http://b/135912609 for details.\n    if (!KnownQualifier.isKnownQualifier(rawTokens.get(0))) {\n      tokensBuilder.add(Token.fromKnownQualifier(KnownQualifier.ABSENT));\n    }\n\n    // Parses token.\n    for (String rawToken : rawTokens) {\n      try {\n        long numericToken = Long.parseLong(rawToken);\n        tokensBuilder.add(Token.fromNumeric(numericToken));\n      } catch (NumberFormatException e) {\n        tokensBuilder.add(Token.fromText(rawToken));\n      }\n    }\n\n    return Segment.fromTokenList(tokensBuilder.build());\n  }\n\n  @Override\n  public int compareTo(Segment other) {\n    return ComparisonUtility.compareListWithFillValue(this.tokens(), other.tokens(), Token.EMPTY);\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/version/Token.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport com.google.auto.value.AutoOneOf;\nimport com.google.common.base.Ascii;\nimport com.google.errorprone.annotations.Immutable;\n\n/**\n * Represents a token from a version string.\n *\n * <p>A token is the smallest meaningful piece of a version string. For example, there are 3 tokens\n * in version 2.1.1: [token(2), token(1), token(1)].\n */\n@Immutable\n@AutoOneOf(Token.Kind.class)\nabstract class Token implements Comparable<Token> {\n  static final Token EMPTY = Token.fromKnownQualifier(KnownQualifier.ABSENT);\n\n  /** All types of a token, required by the AutoOneOf annotation. */\n  public enum Kind {\n    NUMERIC,\n    TEXT\n  }\n\n  abstract Kind getKind();\n\n  abstract long getNumeric();\n  static Token fromNumeric(long numeric) {\n    return AutoOneOf_Token.numeric(numeric);\n  }\n  boolean isNumeric() {\n    return getKind().equals(Kind.NUMERIC);\n  }\n\n  abstract String getText();\n  static Token fromText(String string) {\n    return AutoOneOf_Token.text(Ascii.toLowerCase(string));\n  }\n  boolean isText() {\n    return getKind().equals(Kind.TEXT);\n  }\n\n  static Token fromKnownQualifier(KnownQualifier knownQualifier) {\n    return AutoOneOf_Token.text(knownQualifier.getQualifierText());\n  }\n  boolean isKnownQualifier() {\n    return isText() && KnownQualifier.isKnownQualifier(getText());\n  }\n  boolean isEmptyToken() {\n    return isText() && getText().isEmpty();\n  }\n\n  @Override\n  public int compareTo(Token other) {\n    // Empty tokens are always the same.\n    if (this.isEmptyToken() && other.isEmptyToken()) {\n      return 0;\n    }\n\n    /*\n     * If the tokens under comparison are one Empty token and one Numeric token, then Empty token\n     * should always be less than Numeric token, e.g. version 2.1 is less than 2.1.1 (i.e.\n     * 2.1.empty < 2.1.1, empty < 1).\n     */\n    if (this.isEmptyToken() && other.isNumeric()) {\n      return -1;\n    }\n    if (this.isNumeric() && other.isEmptyToken()) {\n      return 1;\n    }\n\n    /*\n     * For Numeric and Text tokens, we follow the specification from http://semver.org#spec-item-11:\n     * 1. Numeric tokens are compared numerically.\n     * 2. Text tokens are compared lexically, case insensitively.\n     * 3. Numeric identifiers always have lower precedence than non-numeric identifiers.\n     *\n     * For all the known qualifiers, we apply the comparison rules defined by KnownQualifier.\n     */\n    if (this.isNumeric() && other.isNumeric()) {\n      return Long.compare(this.getNumeric(), other.getNumeric());\n    }\n\n    if (this.isKnownQualifier() && other.isKnownQualifier()) {\n      return KnownQualifier.fromText(this.getText())\n          .compareTo(KnownQualifier.fromText(other.getText()));\n    }\n\n    if (this.isText() && other.isText()) {\n      return this.getText().compareToIgnoreCase(other.getText());\n    }\n\n    // Cross type comparison, Numeric token is always less than Text tokens.\n    return this.getKind().compareTo(other.getKind());\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/version/Version.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.base.Preconditions.checkArgument;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.auto.value.extension.memoized.Memoized;\nimport com.google.common.base.Strings;\nimport com.google.common.collect.ImmutableList;\nimport com.google.errorprone.annotations.Immutable;\nimport java.util.Arrays;\nimport java.util.regex.Pattern;\n\n/**\n * Software version class that support 3 types. Type {@link Type#NORMAL} with a version number, type\n * {@link Type#MAXIMUM} and {@link Type#MINIMUM} for use in version range to indicate unbounded\n * ranges.\n *\n * <p>Version is suitable for unstructured version string and supports comparison operations using\n * extra logic that detects semantic qualifier.\n *\n * <p>The current logic support comparison of versions that respect order, like:\n *\n * <pre>\n *   10\n *   1.1\n *   1.1a\n *   1.1a1\n *   20170101\n *   2017.01.01\n *   1.1rc1\n *   1.1-1p1\n *   1.1patch1\n *   1.1gg1.0\n *   1-1\n *   1:1.1\n *   1.1.alpha\n *   1.1.beta.1\n *   1.1.alpha.beta\n * </pre>\n *\n * Known limitation of this approach are versions with no order, like commit hashes.\n */\n@AutoValue\n@Immutable\npublic abstract class Version implements Comparable<Version> {\n  private static final Pattern EPOCH_PATTERN = Pattern.compile(\"\\\\d+[:|_].*\");\n  private static final Pattern SEMANTIC_SEGMENT_SEPARATORS = Pattern.compile(\"[-:_~]\");\n  private static final Version MAXIMUM =\n      builder().setVersionType(Type.MAXIMUM).setVersionString(\"\").build();\n  private static final Version MINIMUM =\n      builder().setVersionType(Type.MINIMUM).setVersionString(\"\").build();\n\n  /**\n   * Software version class that support 3 types. Type {@link Type#NORMAL} with a version number,\n   * type {@link Type#MAXIMUM} and {@link Type#MINIMUM} for use in version range to indicate\n   * unbounded ranges.\n   */\n  public enum Type {\n    NORMAL,\n    MINIMUM,\n    MAXIMUM\n  }\n\n  abstract Type versionType();\n  public abstract String versionString();\n\n  @Memoized\n  ImmutableList<Segment> segments() {\n    String normalizedString = versionString();\n    // Add a default epoch of 0 if one is missing.\n    if (!EPOCH_PATTERN.matcher(normalizedString).matches()) {\n      normalizedString = \"0:\" + normalizedString;\n    }\n\n    return Arrays.stream(SEMANTIC_SEGMENT_SEPARATORS.split(normalizedString))\n        .filter(segment -> !segment.isEmpty())\n        .map(Segment::fromString)\n        .filter(segment -> !segment.equals(Segment.NULL))\n        .collect(ImmutableList.toImmutableList());\n  }\n\n  public static Builder builder() {\n    return new AutoValue_Version.Builder();\n  }\n\n  public static Version fromString(String versionString) {\n    checkArgument(!Strings.isNullOrEmpty(versionString));\n    Version version = builder().setVersionType(Type.NORMAL).setVersionString(versionString).build();\n    if (!EPOCH_PATTERN.matcher(versionString).matches()) {\n      versionString = \"0:\" + versionString;\n    }\n\n    boolean isValid =\n        version.segments().stream()\n            .flatMap(segment -> segment.tokens().stream())\n            .anyMatch(\n                token ->\n                    (token.isNumeric() && token.getNumeric() != 0)\n                        || (token.isText() && !token.getText().isEmpty()));\n    if (!isValid) {\n      throw new IllegalArgumentException(\n          String.format(\n              \"Input version string %s is not valid, it should contain at least one non-empty\"\n                  + \" field.\",\n              versionString));\n    }\n    return version;\n  }\n\n  public static Version maximum() {\n    return MAXIMUM;\n  }\n\n  public boolean isMaximum() {\n    return versionType().equals(Type.MAXIMUM);\n  }\n\n  public static Version minimum() {\n    return MINIMUM;\n  }\n\n  public boolean isMinimum() {\n    return versionType().equals(Type.MINIMUM);\n  }\n\n  /**\n   * Compare this Version object with the other one using their meaningful segments.\n   *\n   * <p>IMPORTANT: This compareTo implementation is NOT consistent with {@link\n   * Version#equals(Object)} method, i.e. this.compareTo(that) == 0 does not imply\n   * this.equals(that). The reason is that the raw version string is tokenized and certain tokens\n   * are ignored. Tokenized strings are used for {@link #compareTo} comparison while raw version\n   * string is used for {@link #equals} comparison. Be careful when using {@link Version} object in\n   * collections like {@code HashMap} or {@code TreeMap}.\n   *\n   * @param other the other {@link Version} object to be compared with.\n   * @return 0 if the segments of the two {@link Version} objects are the same, -1 if this {@link\n   *     Version} is less than {@code other}, 1 if this {@link Version} is greater than {@code\n   *     other}.\n   */\n  @Override\n  public int compareTo(Version other) {\n    if ((this.isMinimum() && other.isMinimum()) || (this.isMaximum() && other.isMaximum())) {\n      return 0;\n    }\n\n    if (this.isMinimum() || other.isMaximum()) {\n      return -1;\n    }\n\n    if (this.isMaximum() || other.isMinimum()) {\n      return 1;\n    }\n\n    return ComparisonUtility.compareListWithFillValue(\n        this.segments(), other.segments(), Segment.NULL);\n  }\n\n  public boolean isLessThan(Version version) {\n    return this.compareTo(version) < 0;\n  }\n\n  /** Builder for {@link Version}. */\n  @AutoValue.Builder\n  public abstract static class Builder {\n    public abstract Builder setVersionType(Type value);\n    public abstract Builder setVersionString(String value);\n\n    public abstract Version build();\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/version/VersionRange.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.base.Preconditions.checkArgument;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.common.base.CharMatcher;\nimport com.google.common.base.Strings;\nimport com.google.errorprone.annotations.Immutable;\n\n/** Immutable version range, e.g. [1.0, 2.0), (,3.0), etc. */\n@AutoValue\n@Immutable\npublic abstract class VersionRange {\n  /** The inclusiveness of the range endpoint. */\n  public enum Inclusiveness {\n    INCLUSIVE,\n    EXCLUSIVE\n  }\n\n  public abstract Version minVersion();\n  public abstract Inclusiveness minVersionInclusiveness();\n  public abstract Version maxVersion();\n  public abstract Inclusiveness maxVersionInclusiveness();\n\n  public static Builder builder() {\n    return new AutoValue_VersionRange.Builder();\n  }\n\n  /** Builder for {@link VersionRange}. */\n  @AutoValue.Builder\n  public abstract static class Builder {\n    public abstract Builder setMinVersion(Version value);\n    public abstract Builder setMinVersionInclusiveness(Inclusiveness value);\n    public abstract Builder setMaxVersion(Version value);\n    public abstract Builder setMaxVersionInclusiveness(Inclusiveness value);\n\n    public abstract VersionRange build();\n  }\n\n  /**\n   * Parses the given {@code rangeString} and generates a {@link VersionRange} object.\n   *\n   * <p>Valid strings for a version range are like:\n   *\n   * <ul>\n   *   <li>(,1.0]: from negative infinity to version 1.0 (inclusive).\n   *   <li>(,1.0): from negative infinity to version 1.0 (exclusive).\n   *   <li>[1.0,): from version 1.0 (inclusive) to positive infinity.\n   *   <li>(1.0,): from version 1.0 (exclusive) to positive infinity.\n   *   <li>[1.0,2.0): from version 1.0 (inclusive) to version 2.0 (exclusive).\n   * </ul>\n   *\n   * @param rangeString the string representation of a version range.\n   * @return the parsed {@link VersionRange} object from the given string.\n   */\n  public static VersionRange parse(String rangeString) {\n    validateRangeString(rangeString);\n\n    Inclusiveness minVersionInclusiveness =\n        rangeString.startsWith(\"[\") ? Inclusiveness.INCLUSIVE : Inclusiveness.EXCLUSIVE;\n    Inclusiveness maxVersionInclusiveness =\n        rangeString.endsWith(\"]\") ? Inclusiveness.INCLUSIVE : Inclusiveness.EXCLUSIVE;\n\n    int commaIndex = rangeString.indexOf(',');\n\n    String minVersionString = rangeString.substring(1, commaIndex).trim();\n    Version minVersion;\n    if (minVersionString.isEmpty()) {\n      minVersionInclusiveness = Inclusiveness.EXCLUSIVE;\n      minVersion = Version.minimum();\n    } else {\n      minVersion = Version.fromString(minVersionString);\n    }\n\n    String maxVersionString =\n        rangeString.substring(commaIndex + 1, rangeString.length() - 1).trim();\n    Version maxVersion;\n    if (maxVersionString.isEmpty()) {\n      maxVersionInclusiveness = Inclusiveness.EXCLUSIVE;\n      maxVersion = Version.maximum();\n    } else {\n      maxVersion = Version.fromString(maxVersionString);\n    }\n\n    if (!minVersion.isLessThan(maxVersion)) {\n      throw new IllegalArgumentException(\n          String.format(\n              \"Min version in range must be less than max version in range, got '%s'\",\n              rangeString));\n    }\n\n    return builder()\n        .setMinVersion(minVersion)\n        .setMinVersionInclusiveness(minVersionInclusiveness)\n        .setMaxVersion(maxVersion)\n        .setMaxVersionInclusiveness(maxVersionInclusiveness)\n        .build();\n  }\n\n  public static boolean isValidVersionRange(String rangeString) {\n    try {\n      parse(rangeString);\n      return true;\n    } catch (Throwable t) {\n      return false;\n    }\n  }\n\n  private static void validateRangeString(String rangeString) {\n    checkArgument(!Strings.isNullOrEmpty(rangeString), \"Range string cannot be empty.\");\n\n    // Version range string must start with '[' or '('.\n    if (!rangeString.startsWith(\"[\") && !rangeString.startsWith(\"(\")) {\n      throw new IllegalArgumentException(\n          String.format(\"Version range must start with '[' or '(', got '%s'\", rangeString));\n    }\n\n    // Version range string must end with ']' or ')'.\n    if (!rangeString.endsWith(\"]\") && !rangeString.endsWith(\")\")) {\n      throw new IllegalArgumentException(\n          String.format(\"Version range must end with ']' or ')', got '%s'\", rangeString));\n    }\n\n    // Remove the leading and ending parenthesis and brackets.\n    String trimmedRange = rangeString.substring(1, rangeString.length() - 1).trim();\n\n    // No more parenthesis and brackets in the string.\n    if (CharMatcher.anyOf(\"[()]\").matchesAnyOf(trimmedRange)) {\n      throw new IllegalArgumentException(\n          String.format(\n              \"Parenthesis and/or brackets not allowed within version range, got '%s'\",\n              rangeString));\n    }\n\n    // Only one comma that separates the minimum and maximum.\n    if (CharMatcher.is(',').countIn(trimmedRange) != 1) {\n      throw new IllegalArgumentException(\n          String.format(\"Invalid range of versions, got '%s'\", rangeString));\n    }\n\n    // Version range of minimum to maximum is not supported.\n    if (trimmedRange.equals(\",\")) {\n      throw new IllegalArgumentException(\n          String.format(\"Infinity range is not supported, got '%s'\", rangeString));\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/main/java/com/google/tsunami/common/version/VersionSet.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.common.base.CharMatcher;\nimport com.google.common.collect.ImmutableList;\nimport com.google.errorprone.annotations.Immutable;\n\n/** Immutable set of discrete versions and version ranges. */\n@AutoValue\n@Immutable\npublic abstract class VersionSet {\n  public abstract ImmutableList<Version> versions();\n  public abstract ImmutableList<VersionRange> versionRanges();\n\n  public static Builder builder() {\n    return new AutoValue_VersionSet.Builder();\n  }\n\n  /** Builder for {@link VersionSet}. */\n  @AutoValue.Builder\n  public abstract static class Builder {\n    abstract ImmutableList.Builder<Version> versionsBuilder();\n    public Builder addVersion(Version version) {\n      versionsBuilder().add(version);\n      return this;\n    }\n\n    abstract ImmutableList.Builder<VersionRange> versionRangesBuilder();\n    public Builder addVersionRange(VersionRange versionRange) {\n      versionRangesBuilder().add(versionRange);\n      return this;\n    }\n\n    public abstract VersionSet build();\n  }\n\n  public static VersionSet parse(ImmutableList<String> versionAndRangesList) {\n    checkNotNull(versionAndRangesList);\n    checkArgument(!versionAndRangesList.isEmpty(), \"Versions and ranges list cannot be empty.\");\n\n    VersionSet.Builder versionSetBuilder = VersionSet.builder();\n\n    for (String versionOrRangeString : versionAndRangesList) {\n      if (isDiscreteVersion(versionOrRangeString)) {\n        versionSetBuilder.addVersion(Version.fromString(versionOrRangeString));\n      } else if (VersionRange.isValidVersionRange(versionOrRangeString)) {\n        versionSetBuilder.addVersionRange(VersionRange.parse(versionOrRangeString));\n      } else {\n        throw new IllegalArgumentException(\n            String.format(\n                \"String '%s' is neither a discrete string nor a version range.\",\n                versionOrRangeString));\n      }\n    }\n\n    return versionSetBuilder.build();\n  }\n\n  private static boolean isDiscreteVersion(String versionOrRangeString) {\n    return CharMatcher.anyOf(\"[()], \").matchesNoneOf(versionOrRangeString);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/cli/CliOptionsModuleTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.beust.jcommander.Parameter;\nimport com.beust.jcommander.ParameterException;\nimport com.beust.jcommander.Parameters;\nimport com.google.common.base.Strings;\nimport com.google.inject.CreationException;\nimport com.google.inject.Guice;\nimport com.google.inject.Injector;\nimport io.github.classgraph.ClassGraph;\nimport io.github.classgraph.ScanResult;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link CliOptionsModule}. */\n@RunWith(JUnit4.class)\npublic class CliOptionsModuleTest {\n  @Test\n  public void configure_whenValidArgs_parsesSuccessfully() {\n    try (ScanResult scanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(\n                TestOption.class.getTypeName(), TestOptionWithRequiredParam.class.getTypeName())\n            .scan()) {\n      Injector injector =\n          Guice.createInjector(\n              new CliOptionsModule(\n                  scanResult, \"test\", new String[] {\"--test=testoption\", \"--test_required=abc\"}));\n\n      TestOption testOption = injector.getInstance(TestOption.class);\n      TestOptionWithRequiredParam testOptionWithRequiredParam =\n          injector.getInstance(TestOptionWithRequiredParam.class);\n\n      assertThat(testOption.test).isEqualTo(\"testoption\");\n      assertThat(testOptionWithRequiredParam.testRequired).isEqualTo(\"abc\");\n    }\n  }\n\n  @Test\n  public void configure_whenMissingRequiredArgs_throwsException() {\n    try (ScanResult scanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(\n                TestOption.class.getTypeName(), TestOptionWithRequiredParam.class.getTypeName())\n            .scan()) {\n      CreationException ex =\n          assertThrows(\n              CreationException.class,\n              () ->\n                  Guice.createInjector(\n                      new CliOptionsModule(scanResult, \"test\", new String[] {\"--test=test\"})));\n      assertThat(ex).hasCauseThat().isInstanceOf(ParameterException.class);\n    }\n  }\n\n  @Test\n  public void configure_whenInvalidArgs_throwsException() {\n    try (ScanResult scanResult =\n        new ClassGraph().enableAllInfo().whitelistClasses(TestOption.class.getTypeName()).scan()) {\n      CreationException ex =\n          assertThrows(\n              CreationException.class,\n              () ->\n                  Guice.createInjector(\n                      new CliOptionsModule(scanResult, \"test\", new String[] {\"--test=invalid\"})));\n      assertThat(ex).hasCauseThat().isInstanceOf(ParameterException.class);\n    }\n  }\n\n  @Test\n  public void configure_whenUnknownArgs_throwsException() {\n    try (ScanResult scanResult =\n        new ClassGraph().enableAllInfo().whitelistClasses(TestOption.class.getTypeName()).scan()) {\n      CreationException ex =\n          assertThrows(\n              CreationException.class,\n              () ->\n                  Guice.createInjector(\n                      new CliOptionsModule(\n                          scanResult, \"test\", new String[] {\"--test=test\", \"--unknown=unknown\"})));\n      assertThat(ex).hasCauseThat().isInstanceOf(ParameterException.class);\n    }\n  }\n\n  @Test\n  public void configure_whenCliOptionNoCorrectCtor_throwsException() {\n    try (ScanResult scanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(TestOptionWithoutNoArgumentCtor.class.getTypeName())\n            .scan()) {\n      assertThrows(\n          AssertionError.class,\n          () ->\n              Guice.createInjector(\n                  new CliOptionsModule(scanResult, \"test\", new String[] {})));\n    }\n  }\n\n  @Parameters(separators = \"=\")\n  private static final class TestOption implements CliOption {\n    @Parameter(names = \"--test\", description = \"A test option\")\n    String test;\n\n    @Override\n    public void validate() {\n      if (Strings.isNullOrEmpty(test)) {\n        throw new ParameterException(\"Empty test param\");\n      }\n\n      if (!test.startsWith(\"test\")) {\n        throw new ParameterException(\"test param value must start with 'test'\");\n      }\n    }\n  }\n\n  @Parameters(separators = \"=\")\n  private static final class TestOptionWithRequiredParam implements CliOption {\n    @Parameter(names = \"--test_required\", description = \"A required option\", required = true)\n    String testRequired;\n\n    @Override\n    public void validate() {}\n  }\n\n  @Parameters(separators = \"=\")\n  private static final class TestOptionWithoutNoArgumentCtor implements CliOption {\n    String testOption;\n\n    TestOptionWithoutNoArgumentCtor(String testOption) {\n      this.testOption = testOption;\n    }\n\n    @Override\n    public void validate() {}\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/command/CommandExecutorFactoryTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.command;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.Mockito;\n\n/** Tests for {@link CommandExecutorFactory}. */\n@RunWith(JUnit4.class)\npublic final class CommandExecutorFactoryTest {\n\n  @Test\n  public void getInstance_whenNoPreviousInstanceIsProvided_createsNewProcessExecutor() {\n    CommandExecutor executor = CommandExecutorFactory.create(\"fakeArgs\");\n    assertThat(executor).isNotNull();\n  }\n\n  @Test\n  public void getInstance_whenPreviousInstanceIsProvided_returnsProvidedInstance() {\n    CommandExecutor executor = Mockito.mock(CommandExecutor.class);\n    CommandExecutorFactory.setInstance(executor);\n\n    assertThat(CommandExecutorFactory.create(\"fakeArgs\")).isSameInstanceAs(executor);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/command/CommandExecutorTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.command;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport java.io.IOException;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link CommandExecutor}. */\n@RunWith(JUnit4.class)\npublic final class CommandExecutorTest {\n\n  @Test\n  public void execute_always_startsProcessAndReturnsProcessInstance()\n      throws IOException, InterruptedException, ExecutionException {\n    CommandExecutor executor = new CommandExecutor(\"/bin/sh\", \"-c\", \"echo 1\");\n\n    Process process = executor.execute();\n    process.waitFor();\n\n    assertThat(process.exitValue()).isEqualTo(0);\n  }\n\n  @Test\n  public void executeAsync_always_startsProcessAndReturnsProcessInstance()\n      throws IOException, InterruptedException, ExecutionException {\n    CommandExecutor executor = new CommandExecutor(\"/bin/sh\", \"-c\", \"echo 1\");\n\n    Process process = executor.executeAsync();\n    process.waitFor();\n\n    assertThat(process.exitValue()).isEqualTo(0);\n  }\n\n  @Test\n  public void executeWithNoStreamCollection_always_startsProcessAndReturnsProcessInstance()\n      throws IOException, InterruptedException, ExecutionException {\n    CommandExecutor executor = new CommandExecutor(\"/bin/sh\", \"-c\", \"echo 1\");\n\n    Process process = executor.executeWithNoStreamCollection();\n    process.waitFor();\n\n    assertThat(process.exitValue()).isEqualTo(0);\n  }\n\n  @Test\n  public void getOutput_always_returnsExpect()\n      throws IOException, InterruptedException, ExecutionException {\n    CommandExecutor executor = new CommandExecutor(\"/bin/sh\", \"-c\", \"echo 1\");\n\n    Process process = executor.execute();\n    process.waitFor();\n\n    assertThat(executor.getOutput()).isEqualTo(\"1\\n\");\n  }\n\n  @Test\n  public void getOutput_withMultipleGetOutputCalls_returnsExpect()\n      throws IOException, InterruptedException, ExecutionException {\n    CommandExecutor executor = new CommandExecutor(\"/bin/sh\", \"-c\", \"echo 1\");\n\n    Process process = executor.execute();\n    process.waitFor();\n\n    assertThat(executor.getOutput()).isEqualTo(\"1\\n\");\n    assertThat(executor.getOutput()).isEqualTo(\"1\\n\");\n  }\n\n  @Test\n  public void getError_always_returnsExpect()\n      throws IOException, InterruptedException, ExecutionException {\n    CommandExecutor executor = new CommandExecutor(\"/bin/sh\", \"-c\", \"echo 1 1>&2\");\n\n    Process process = executor.execute();\n    process.waitFor();\n\n    assertThat(executor.getError()).isEqualTo(\"1\\n\");\n  }\n\n  @Test\n  public void getError_withMultipleGetOutputCalls_returnsExpect()\n      throws IOException, InterruptedException, ExecutionException {\n    CommandExecutor executor = new CommandExecutor(\"/bin/sh\", \"-c\", \"echo 1 1>&2\");\n\n    Process process = executor.execute();\n    process.waitFor();\n\n    assertThat(executor.getError()).isEqualTo(\"1\\n\");\n    assertThat(executor.getError()).isEqualTo(\"1\\n\");\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/concurrent/BaseThreadPoolModuleTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.concurrent;\n\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.util.concurrent.ListeningExecutorService;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Key;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport javax.inject.Qualifier;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link ThreadPoolModule}. */\n@RunWith(JUnit4.class)\npublic final class BaseThreadPoolModuleTest {\n\n  /** Internal annotation used for tests. */\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @interface TestThreadPool {}\n\n  static final class TestThreadPoolModule extends BaseThreadPoolModule<ListeningExecutorService> {\n\n    TestThreadPoolModule(Builder builder) {\n      super(builder);\n    }\n\n    @Override\n    void configureThreadPool(Key<ListeningExecutorService> key) {}\n\n    static final class Builder\n        extends BaseThreadPoolModuleBuilder<ListeningExecutorService, Builder> {\n\n      Builder() {\n        super(ListeningExecutorService.class);\n      }\n\n      @Override\n      Builder self() {\n        return this;\n      }\n\n      @Override\n      void validate() {}\n\n      @Override\n      AbstractModule newModule() {\n        return new TestThreadPoolModule(this);\n      }\n    }\n  }\n\n  @Test\n  public void build_whenNoName_throwsIllegalStateException() {\n    assertThrows(IllegalStateException.class, () -> new TestThreadPoolModule.Builder().build());\n  }\n\n  @Test\n  public void build_whenEmptyName_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> new TestThreadPoolModule.Builder().setName(\"\").build());\n  }\n\n  @Test\n  public void build_whenNegativeCoreSize_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> new TestThreadPoolModule.Builder().setName(\"test\").setCoreSize(-1).build());\n  }\n\n  @Test\n  public void build_whenNegativeMaxSize_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> new TestThreadPoolModule.Builder().setName(\"test\").setMaxSize(-1).build());\n  }\n\n  @Test\n  public void build_whenNegativeKeepAliveSeconds_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () ->\n            new TestThreadPoolModule.Builder()\n                .setName(\"test\")\n                .setMaxSize(1)\n                .setKeepAliveSeconds(-1)\n                .build());\n  }\n\n  @Test\n  public void build_whenCoreSizeLessThanMaxSize_throwsIllegalStateException() {\n    assertThrows(\n        IllegalStateException.class,\n        () ->\n            new TestThreadPoolModule.Builder()\n                .setName(\"test\")\n                .setCoreSize(2)\n                .setMaxSize(1)\n                .setAnnotation(TestThreadPool.class)\n                .build());\n  }\n\n  @Test\n  public void build_whenNoAnnotation_throwsIllegalStateException() {\n    assertThrows(\n        IllegalStateException.class,\n        () -> new TestThreadPoolModule.Builder().setName(\"test\").build());\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/concurrent/ScheduledThreadPoolModuleTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.concurrent;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.common.util.concurrent.ListeningScheduledExecutorService;\nimport com.google.inject.Guice;\nimport com.google.inject.Injector;\nimport com.google.inject.Key;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.util.concurrent.ScheduledExecutorService;\nimport javax.inject.Qualifier;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link ScheduledThreadPoolModule}. */\n@RunWith(JUnit4.class)\npublic final class ScheduledThreadPoolModuleTest {\n\n  /** Internal annotation used for tests. */\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @interface TestThreadPool {}\n\n  @Test\n  public void configure_always_bindsListeningScheduledExecutorService() {\n    Injector injector =\n        Guice.createInjector(\n            new ScheduledThreadPoolModule.Builder()\n                .setName(\"test\")\n                .setSize(1)\n                .setAnnotation(TestThreadPool.class)\n                .build());\n\n    assertThat(\n            injector.getInstance(Key.get(ScheduledExecutorService.class, TestThreadPool.class)))\n        .isInstanceOf(ListeningScheduledExecutorService.class);\n  }\n\n  @Test\n  public void configure_always_bindsSingleton() {\n    Injector injector =\n        Guice.createInjector(\n            new ScheduledThreadPoolModule.Builder()\n                .setName(\"test\")\n                .setSize(1)\n                .setAnnotation(TestThreadPool.class)\n                .build());\n\n    ScheduledExecutorService scheduledExecutorService =\n        injector.getInstance(Key.get(ScheduledExecutorService.class, TestThreadPool.class));\n    ListeningScheduledExecutorService listeningScheduledExecutorService =\n        injector.getInstance(\n            Key.get(ListeningScheduledExecutorService.class, TestThreadPool.class));\n\n    assertThat(scheduledExecutorService).isSameInstanceAs(listeningScheduledExecutorService);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/concurrent/ThreadPoolModuleTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.concurrent;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.util.concurrent.ListeningExecutorService;\nimport com.google.inject.Guice;\nimport com.google.inject.Injector;\nimport com.google.inject.Key;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.PriorityBlockingQueue;\nimport javax.inject.Qualifier;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link ThreadPoolModule}. */\n@RunWith(JUnit4.class)\npublic final class ThreadPoolModuleTest {\n\n  /** Internal annotation used for tests. */\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @interface TestThreadPool {}\n\n  @Test\n  public void configure_always_bindsListeningExecutorService() {\n    Injector injector =\n        Guice.createInjector(\n            new ThreadPoolModule.Builder()\n                .setName(\"test\")\n                .setCoreSize(1)\n                .setMaxSize(1)\n                .setAnnotation(TestThreadPool.class)\n                .build());\n\n    assertThat(injector.getInstance(Key.get(Executor.class, TestThreadPool.class)))\n        .isInstanceOf(ListeningExecutorService.class);\n    assertThat(injector.getInstance(Key.get(ExecutorService.class, TestThreadPool.class)))\n        .isInstanceOf(ListeningExecutorService.class);\n  }\n\n  @Test\n  public void configure_always_bindsSingleton() {\n    Injector injector =\n        Guice.createInjector(\n            new ThreadPoolModule.Builder()\n                .setName(\"test\")\n                .setCoreSize(1)\n                .setMaxSize(1)\n                .setAnnotation(TestThreadPool.class)\n                .build());\n\n    Executor executor = injector.getInstance(Key.get(Executor.class, TestThreadPool.class));\n    ExecutorService executorService =\n        injector.getInstance(Key.get(ExecutorService.class, TestThreadPool.class));\n    ListeningExecutorService listeningExecutorService =\n        injector.getInstance(Key.get(ListeningExecutorService.class, TestThreadPool.class));\n\n    assertThat(executor).isSameInstanceAs(listeningExecutorService);\n    assertThat(executorService).isSameInstanceAs(listeningExecutorService);\n  }\n\n  @Test\n  public void build_whenNegativeQueueCapacity_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () ->\n            new ThreadPoolModule.Builder()\n                .setName(\"test\")\n                .setMaxSize(1)\n                .setQueueCapacity(-1)\n                .build());\n  }\n\n  @Test\n  public void build_whenBothQueueCapacityAndBlockingQueueSet_throwsIllegalStateException() {\n    assertThrows(\n        IllegalStateException.class,\n        () ->\n            new ThreadPoolModule.Builder()\n                .setMaxSize(1)\n                .setQueueCapacity(1)\n                .setBlockingQueue(new PriorityBlockingQueue<>(1))\n                .build());\n  }\n\n  @Test\n  public void build_whenUnBoundedQueue_throwsIllegalStateException() {\n    assertThrows(\n        IllegalStateException.class,\n        () ->\n            new ThreadPoolModule.Builder()\n                .setName(\"test\")\n                .setCoreSize(1)\n                .setMaxSize(2)\n                .setAnnotation(TestThreadPool.class)\n                .build());\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/config/ConfigModuleTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.config;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.google.inject.Guice;\nimport com.google.inject.Injector;\nimport com.google.tsunami.common.config.annotations.ConfigProperties;\nimport io.github.classgraph.ClassGraph;\nimport io.github.classgraph.ScanResult;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link ConfigModule}. */\n@RunWith(JUnit4.class)\npublic final class ConfigModuleTest {\n\n  @Test\n  public void configure_always_bindsGivenTsunamiConfigObject() {\n    TsunamiConfig tsunamiConfig = TsunamiConfig.fromYamlData(ImmutableMap.of());\n    try (ScanResult scanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(TestConfigWithoutPrefix.class.getTypeName())\n            .scan()) {\n      Injector injector = Guice.createInjector(new ConfigModule(scanResult, tsunamiConfig));\n\n      TsunamiConfig boundTsunamiConfig = injector.getInstance(TsunamiConfig.class);\n\n      assertThat(boundTsunamiConfig).isSameInstanceAs(tsunamiConfig);\n    }\n  }\n\n  @Test\n  public void configure_whenValidConfigData_bindsSuccessfully() {\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(ImmutableMap.of(\"string_config\", \"testString\"));\n    try (ScanResult scanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(TestConfigWithoutPrefix.class.getTypeName())\n            .scan()) {\n      Injector injector = Guice.createInjector(new ConfigModule(scanResult, tsunamiConfig));\n\n      TestConfigWithoutPrefix testConfig = injector.getInstance(TestConfigWithoutPrefix.class);\n\n      assertThat(testConfig.stringConfig).isEqualTo(\"testString\");\n    }\n  }\n\n  @Test\n  public void configure_whenMissingMatchedConfigData_bindsObjectWithDefaultValue() {\n    TsunamiConfig tsunamiConfig = TsunamiConfig.fromYamlData(ImmutableMap.of());\n    try (ScanResult scanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(TestConfigWithoutPrefix.class.getTypeName())\n            .scan()) {\n      Injector injector = Guice.createInjector(new ConfigModule(scanResult, tsunamiConfig));\n\n      TestConfigWithoutPrefix testConfig = injector.getInstance(TestConfigWithoutPrefix.class);\n\n      assertThat(testConfig.stringConfig).isNull();\n    }\n  }\n\n  @Test\n  public void configure_whenValidConfigDataWithPrefix_bindsSuccessfully() {\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(\n            ImmutableMap.of(\n                \"test\", ImmutableMap.of(\"prefix\", ImmutableMap.of(\"string_config\", \"testString\"))));\n    try (ScanResult scanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(TestConfigWithPrefix.class.getTypeName())\n            .scan()) {\n      Injector injector = Guice.createInjector(new ConfigModule(scanResult, tsunamiConfig));\n\n      TestConfigWithPrefix testConfig = injector.getInstance(TestConfigWithPrefix.class);\n\n      assertThat(testConfig.stringConfig).isEqualTo(\"testString\");\n    }\n  }\n\n  @Test\n  public void configure_whenInvalidConfigClass_throwsException() {\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(ImmutableMap.of(\"string_config\", \"testString\"));\n    try (ScanResult scanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(InvalidConfig.class.getTypeName())\n            .scan()) {\n      assertThrows(\n          AssertionError.class,\n          () -> Guice.createInjector(new ConfigModule(scanResult, tsunamiConfig)));\n    }\n  }\n\n  @ConfigProperties(\"\")\n  private static final class TestConfigWithoutPrefix {\n    String stringConfig;\n  }\n\n  @ConfigProperties(\"test.prefix\")\n  private static final class TestConfigWithPrefix {\n    String stringConfig;\n  }\n\n  @ConfigProperties(\"\")\n  private static final class InvalidConfig {\n    String stringConfig;\n\n    InvalidConfig(String stringConfig) {\n      this.stringConfig = stringConfig;\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/config/TsunamiConfigTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.config;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport java.util.List;\nimport java.util.Map;\nimport org.junit.After;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link TsunamiConfig}. */\n@RunWith(JUnit4.class)\npublic final class TsunamiConfigTest {\n  private static final String TEST_PROPERTY = \"test.property\";\n\n  @After\n  public void tearDown() {\n    System.clearProperty(TEST_PROPERTY);\n  }\n\n  @Test\n  public void fromYamlData_always_createTsunamiConfigFromMapData() {\n    TsunamiConfig tsunamiConfig = TsunamiConfig.fromYamlData(ImmutableMap.of(\"test\", \"value\"));\n\n    assertThat(tsunamiConfig.getRawConfigData()).containsEntry(\"test\", \"value\");\n  }\n\n  @Test\n  public void fromYamlData_whenNullYamlData_createEmptyConfigData() {\n    TsunamiConfig tsunamiConfig = TsunamiConfig.fromYamlData(null);\n\n    assertThat(tsunamiConfig.getRawConfigData()).isEmpty();\n  }\n\n  @Test\n  public void getSystemProperty_whenPropertyExists_returnsPropertyValue() {\n    System.setProperty(TEST_PROPERTY, \"Test value\");\n\n    assertThat(TsunamiConfig.getSystemProperty(TEST_PROPERTY)).hasValue(\"Test value\");\n  }\n\n  @Test\n  public void getSystemProperty_whenPropertyNotExists_returnsEmptyOptional() {\n    assertThat(TsunamiConfig.getSystemProperty(TEST_PROPERTY)).isEmpty();\n  }\n\n  @Test\n  public void getSystemProperty_whenPropertyNotExistsWithDefaultValue_returnsDefaultValue() {\n    assertThat(TsunamiConfig.getSystemProperty(TEST_PROPERTY, \"Default\")).isEqualTo(\"Default\");\n  }\n\n  @Test\n  public void getConfig_whenValidConfigData_returnsBoundConfigObject() {\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(ImmutableMap.of(\"string\", \"string_value\", \"number\", 1234));\n\n    SimpleConfig config = tsunamiConfig.getConfig(\"\", SimpleConfig.class);\n\n    assertThat(config.string).isEqualTo(\"string_value\");\n    assertThat(config.number).isEqualTo(1234);\n  }\n\n  @Test\n  public void getConfig_whenValidConfigDataAndPrefix_returnsBoundConfigObject() {\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(\n            ImmutableMap.of(\n                \"test\",\n                ImmutableMap.of(\n                    \"prefix\", ImmutableMap.of(\"string\", \"string_value\", \"number\", 1234))));\n\n    SimpleConfig config = tsunamiConfig.getConfig(\"test.prefix\", SimpleConfig.class);\n\n    assertThat(config.string).isEqualTo(\"string_value\");\n    assertThat(config.number).isEqualTo(1234);\n  }\n\n  @Test\n  public void getConfig_whenRequestedConfigNotExists_returnsObjectWithDefaultValue() {\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(\n            ImmutableMap.of(\n                \"test\",\n                ImmutableMap.of(\n                    \"prefix\", ImmutableMap.of(\"string\", \"string_value\", \"number\", 1234))));\n\n    SimpleConfig config = tsunamiConfig.getConfig(\"not.exist.prefix\", SimpleConfig.class);\n\n    assertThat(config.string).isNull();\n    assertThat(config.number).isEqualTo(0);\n  }\n\n  @Test\n  public void getConfig_whenConfigHasComplicateDataStructure_returnsValidObject() {\n    ImmutableList<String> stringsField = ImmutableList.of(\"a\", \"b\", \"c\");\n    ImmutableMap<String, List<Long>> complicateField =\n        ImmutableMap.of(\"keyA\", ImmutableList.of(1L, 2L), \"keyB\", ImmutableList.of(123L));\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(\n            ImmutableMap.of(\n                \"strings\", stringsField, \"complicateField\", complicateField));\n\n    CollectionConfig config = tsunamiConfig.getConfig(\"\", CollectionConfig.class);\n\n    assertThat(config.strings).isEqualTo(stringsField);\n    assertThat(config.complicateField).isEqualTo(complicateField);\n  }\n\n  @Test\n  public void getConfig_whenConfigDataUseLowerUnderscoreCase_returnsValidObject() {\n    ImmutableList<String> stringsField = ImmutableList.of(\"a\", \"b\", \"c\");\n    ImmutableMap<String, List<Long>> complicateField =\n        ImmutableMap.of(\"keyA\", ImmutableList.of(1L, 2L), \"keyB\", ImmutableList.of(123L));\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(\n            ImmutableMap.of(\n                \"strings\", stringsField, \"complicate_field\", complicateField));\n\n    CollectionConfig config = tsunamiConfig.getConfig(\"\", CollectionConfig.class);\n\n    assertThat(config.strings).isEqualTo(stringsField);\n    assertThat(config.complicateField).isEqualTo(complicateField);\n  }\n\n  @Test\n  public void getConfig_whenRequestedConfigHasInvalidType_throwsException() {\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(\n            ImmutableMap.of(\"test\", ImmutableMap.of(\"prefix\", \"invalid_type\")));\n\n    assertThrows(\n        ConfigException.class, () -> tsunamiConfig.getConfig(\"test.prefix\", SimpleConfig.class));\n  }\n\n  @Test\n  public void getConfig_whenUnassignableConfigValue_throwsException() {\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(\n            ImmutableMap.of(\"string\", \"string_value\", \"number\", \"incompatible_value\"));\n\n    assertThrows(\n        IllegalArgumentException.class, () -> tsunamiConfig.getConfig(\"\", SimpleConfig.class));\n  }\n\n  @Test\n  public void getConfig_whenInvalidConfigObject_throwsException() {\n    TsunamiConfig tsunamiConfig =\n        TsunamiConfig.fromYamlData(ImmutableMap.of(\"string\", \"string_value\"));\n\n    assertThrows(AssertionError.class, () -> tsunamiConfig.getConfig(\"\", InvalidConfig.class));\n  }\n\n  private static final class SimpleConfig {\n    String string;\n    long number;\n  }\n\n  private static final class CollectionConfig {\n    List<String> strings;\n    Map<String, List<Long>> complicateField;\n  }\n\n  private static final class InvalidConfig {\n    String field;\n\n    InvalidConfig(String field) {\n      this.field = field;\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/config/YamlConfigLoaderTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.config;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport com.google.common.io.Files;\nimport java.io.File;\nimport java.io.IOException;\nimport org.junit.After;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link YamlConfigLoader}. */\n@RunWith(JUnit4.class)\npublic final class YamlConfigLoaderTest {\n  private static final String YAML_DATA = \"test: \\\"data\\\"\\ntest2: 123\";\n\n  @After\n  public void tearDown() {\n    System.clearProperty(\"tsunami.config.location\");\n  }\n\n  @Test\n  public void loadConfig_whenValidYamlFile_loadsConfigFromFile() throws IOException {\n    File configFile = File.createTempFile(\"YamlConfigLoaderTest\", \".yaml\");\n    Files.asCharSink(configFile, UTF_8).write(YAML_DATA);\n    System.setProperty(\"tsunami.config.location\", configFile.getAbsolutePath());\n\n    TsunamiConfig tsunamiConfig = new YamlConfigLoader().loadConfig();\n\n    assertThat(tsunamiConfig.getRawConfigData()).containsExactly(\"test\", \"data\", \"test2\", 123);\n  }\n\n  @Test\n  public void loadConfig_whenYamlFileNotFound_usesEmptyConfig() {\n    TsunamiConfig tsunamiConfig = new YamlConfigLoader().loadConfig();\n\n    assertThat(tsunamiConfig.getRawConfigData()).isEmpty();\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/data/NetworkEndpointUtilsTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.data;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.net.HostAndPort;\nimport com.google.tsunami.proto.AddressFamily;\nimport com.google.tsunami.proto.Hostname;\nimport com.google.tsunami.proto.IpAddress;\nimport com.google.tsunami.proto.NetworkEndpoint;\nimport com.google.tsunami.proto.Port;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link NetworkEndpointUtils}. */\n@RunWith(JUnit4.class)\npublic class NetworkEndpointUtilsTest {\n\n  @Test\n  public void isIpV6Endpoint_withIpV4Endpoint_returnsFalse() {\n    NetworkEndpoint ipV4Endpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP)\n            .setIpAddress(\n                IpAddress.newBuilder().setAddress(\"1.2.3.4\").setAddressFamily(AddressFamily.IPV4))\n            .build();\n    assertThat(NetworkEndpointUtils.isIpV6Endpoint(ipV4Endpoint)).isFalse();\n  }\n\n  @Test\n  public void isIpV6Endpoint_withIpV4AndPortEndpoint_returnsFalse() {\n    NetworkEndpoint ipV4AndPortEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP_PORT)\n            .setPort(Port.newBuilder().setPortNumber(8888))\n            .setIpAddress(\n                IpAddress.newBuilder().setAddress(\"1.2.3.4\").setAddressFamily(AddressFamily.IPV4))\n            .build();\n    assertThat(NetworkEndpointUtils.isIpV6Endpoint(ipV4AndPortEndpoint)).isFalse();\n  }\n\n  @Test\n  public void isIpV6Endpoint_withIpV6Endpoint_returnsFalse() {\n    NetworkEndpoint ipV6Endpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP)\n            .setIpAddress(\n                IpAddress.newBuilder().setAddress(\"3ffe::1\").setAddressFamily(AddressFamily.IPV6))\n            .build();\n    assertThat(NetworkEndpointUtils.isIpV6Endpoint(ipV6Endpoint)).isTrue();\n  }\n\n  @Test\n  public void isIpV6Endpoint_withIpV6AndPortEndpoint_returnsFalse() {\n    NetworkEndpoint ipV6AndPortEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP_PORT)\n            .setPort(Port.newBuilder().setPortNumber(8888))\n            .setIpAddress(\n                IpAddress.newBuilder().setAddress(\"3ffe::1\").setAddressFamily(AddressFamily.IPV6))\n            .build();\n    assertThat(NetworkEndpointUtils.isIpV6Endpoint(ipV6AndPortEndpoint)).isTrue();\n  }\n\n  @Test\n  public void isIpV6Endpoint_withHostnameEndpoint_returnsFalse() {\n    NetworkEndpoint hostnameEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.HOSTNAME)\n            .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n            .build();\n    assertThat(NetworkEndpointUtils.isIpV6Endpoint(hostnameEndpoint)).isFalse();\n  }\n\n  @Test\n  public void isIpV6Endpoint_withHostnameAndPortEndpoint_returnsFalse() {\n    NetworkEndpoint hostnameAndPortEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.HOSTNAME_PORT)\n            .setPort(Port.newBuilder().setPortNumber(8888))\n            .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n            .build();\n    assertThat(NetworkEndpointUtils.isIpV6Endpoint(hostnameAndPortEndpoint)).isFalse();\n  }\n\n  @Test\n  public void toUriString_withIpV4Endpoint_returnsIpAddress() {\n    NetworkEndpoint ipV4Endpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP)\n            .setIpAddress(\n                IpAddress.newBuilder().setAddress(\"1.2.3.4\").setAddressFamily(AddressFamily.IPV4))\n            .build();\n    assertThat(NetworkEndpointUtils.toUriAuthority(ipV4Endpoint)).isEqualTo(\"1.2.3.4\");\n  }\n\n  @Test\n  public void toUriString_withIpV6Endpoint_returnsIpAddressWithBracket() {\n    NetworkEndpoint ipV6Endpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP)\n            .setIpAddress(\n                IpAddress.newBuilder().setAddress(\"3ffe::1\").setAddressFamily(AddressFamily.IPV6))\n            .build();\n    assertThat(NetworkEndpointUtils.toUriAuthority(ipV6Endpoint)).isEqualTo(\"[3ffe::1]\");\n  }\n\n  @Test\n  public void toUriString_withIpV4AndPortEndpoint_returnsIpAddressAndPort() {\n    NetworkEndpoint ipV4AndPortEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP_PORT)\n            .setPort(Port.newBuilder().setPortNumber(8888))\n            .setIpAddress(\n                IpAddress.newBuilder().setAddress(\"1.2.3.4\").setAddressFamily(AddressFamily.IPV4))\n            .build();\n    assertThat(NetworkEndpointUtils.toUriAuthority(ipV4AndPortEndpoint)).isEqualTo(\"1.2.3.4:8888\");\n  }\n\n  @Test\n  public void toUriString_withIpV6AndPortEndpoint_returnsIpAddressWithBracketAndPort() {\n    NetworkEndpoint ipV6Endpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP_PORT)\n            .setPort(Port.newBuilder().setPortNumber(8888))\n            .setIpAddress(\n                IpAddress.newBuilder().setAddress(\"3ffe::1\").setAddressFamily(AddressFamily.IPV6))\n            .build();\n    assertThat(NetworkEndpointUtils.toUriAuthority(ipV6Endpoint)).isEqualTo(\"[3ffe::1]:8888\");\n  }\n\n  @Test\n  public void toUriString_withHostnameEndpoint_returnsHostname() {\n    NetworkEndpoint hostnameEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.HOSTNAME)\n            .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n            .build();\n    assertThat(NetworkEndpointUtils.toUriAuthority(hostnameEndpoint)).isEqualTo(\"localhost\");\n  }\n\n  @Test\n  public void toUriString_withHostnameAndPortEndpoint_returnsHostnameAndPort() {\n    NetworkEndpoint hostnameAndPortEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.HOSTNAME_PORT)\n            .setPort(Port.newBuilder().setPortNumber(8888))\n            .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n            .build();\n    assertThat(NetworkEndpointUtils.toUriAuthority(hostnameAndPortEndpoint))\n        .isEqualTo(\"localhost:8888\");\n  }\n\n  @Test\n  public void toHostAndPort_withIpAddress_returnsHostWithIp() {\n    NetworkEndpoint ipV4Endpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP)\n            .setIpAddress(\n                IpAddress.newBuilder().setAddress(\"1.2.3.4\").setAddressFamily(AddressFamily.IPV4))\n            .build();\n    assertThat(NetworkEndpointUtils.toHostAndPort(ipV4Endpoint))\n        .isEqualTo(HostAndPort.fromHost(\"1.2.3.4\"));\n  }\n\n  @Test\n  public void toHostAndPort_withIpAddressAndPort_returnsHostWithIpAndPort() {\n    NetworkEndpoint ipV4AndPortEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP_PORT)\n            .setPort(Port.newBuilder().setPortNumber(8888))\n            .setIpAddress(\n                IpAddress.newBuilder().setAddress(\"1.2.3.4\").setAddressFamily(AddressFamily.IPV4))\n            .build();\n    assertThat(NetworkEndpointUtils.toHostAndPort(ipV4AndPortEndpoint))\n        .isEqualTo(HostAndPort.fromParts(\"1.2.3.4\", 8888));\n  }\n\n  @Test\n  public void toHostAndPort_withHostname_returnsHostWithHostname() {\n    NetworkEndpoint hostnameEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.HOSTNAME)\n            .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n            .build();\n    assertThat(NetworkEndpointUtils.toHostAndPort(hostnameEndpoint))\n        .isEqualTo(HostAndPort.fromHost(\"localhost\"));\n  }\n\n  @Test\n  public void toHostAndPort_withHostnameAndPort_returnsHostWithHostnameAndPort() {\n    NetworkEndpoint hostnameAndPortEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.HOSTNAME_PORT)\n            .setPort(Port.newBuilder().setPortNumber(8888))\n            .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n            .build();\n    assertThat(NetworkEndpointUtils.toHostAndPort(hostnameAndPortEndpoint))\n        .isEqualTo(HostAndPort.fromParts(\"localhost\", 8888));\n  }\n\n  @Test\n  public void forIp_withIpV4Address_returnsIpV4NetworkEndpoint() {\n    assertThat(NetworkEndpointUtils.forIp(\"1.2.3.4\"))\n        .isEqualTo(\n            NetworkEndpoint.newBuilder()\n                .setType(NetworkEndpoint.Type.IP)\n                .setIpAddress(\n                    IpAddress.newBuilder()\n                        .setAddressFamily(AddressFamily.IPV4)\n                        .setAddress(\"1.2.3.4\"))\n                .build());\n  }\n\n  @Test\n  public void forIp_withIpV6Address_returnsIpV6NetworkEndpoint() {\n    assertThat(NetworkEndpointUtils.forIp(\"3ffe::1\"))\n        .isEqualTo(\n            NetworkEndpoint.newBuilder()\n                .setType(NetworkEndpoint.Type.IP)\n                .setIpAddress(\n                    IpAddress.newBuilder()\n                        .setAddressFamily(AddressFamily.IPV6)\n                        .setAddress(\"3ffe::1\"))\n                .build());\n  }\n\n  @Test\n  public void forIpAndPort_withIpV4AddressAndPort_returnsIpV4AndPortNetworkEndpoint() {\n    assertThat(NetworkEndpointUtils.forIpAndPort(\"1.2.3.4\", 8888))\n        .isEqualTo(\n            NetworkEndpoint.newBuilder()\n                .setType(NetworkEndpoint.Type.IP_PORT)\n                .setPort(Port.newBuilder().setPortNumber(8888))\n                .setIpAddress(\n                    IpAddress.newBuilder()\n                        .setAddressFamily(AddressFamily.IPV4)\n                        .setAddress(\"1.2.3.4\"))\n                .build());\n  }\n\n  @Test\n  public void forIpAndPort_withIpV6AddressAndPort_returnsIpV6AndPortNetworkEndpoint() {\n    assertThat(NetworkEndpointUtils.forIpAndPort(\"3ffe::1\", 8888))\n        .isEqualTo(\n            NetworkEndpoint.newBuilder()\n                .setType(NetworkEndpoint.Type.IP_PORT)\n                .setPort(Port.newBuilder().setPortNumber(8888))\n                .setIpAddress(\n                    IpAddress.newBuilder()\n                        .setAddressFamily(AddressFamily.IPV6)\n                        .setAddress(\"3ffe::1\"))\n                .build());\n  }\n\n  @Test\n  public void forHostname_withHostname_returnsHostnameNetworkEndpoint() {\n    assertThat(NetworkEndpointUtils.forHostname(\"localhost\"))\n        .isEqualTo(\n            NetworkEndpoint.newBuilder()\n                .setType(NetworkEndpoint.Type.HOSTNAME)\n                .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n                .build());\n  }\n\n  @Test\n  public void forHostnameAndPort_withHostnameAndPort_returnsHostnameAndPortNetworkEndpoint() {\n    assertThat(NetworkEndpointUtils.forHostnameAndPort(\"localhost\", 8888))\n        .isEqualTo(\n            NetworkEndpoint.newBuilder()\n                .setType(NetworkEndpoint.Type.HOSTNAME_PORT)\n                .setPort(Port.newBuilder().setPortNumber(8888))\n                .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n                .build());\n  }\n\n  @Test\n  public void forIpAndHostname_returnsIpAndHostnameNetworkEndpoint() {\n    assertThat(NetworkEndpointUtils.forIpAndHostname(\"1.2.3.4\", \"host.com\"))\n        .isEqualTo(\n            NetworkEndpoint.newBuilder()\n                .setType(NetworkEndpoint.Type.IP_HOSTNAME)\n                .setIpAddress(\n                    IpAddress.newBuilder()\n                        .setAddressFamily(AddressFamily.IPV4)\n                        .setAddress(\"1.2.3.4\"))\n                .setHostname(Hostname.newBuilder().setName(\"host.com\"))\n                .build());\n  }\n\n  @Test\n  public void forIpHostnameAndPort_returnsIpHostnameAndPortNetworkEndpoint() {\n    assertThat(NetworkEndpointUtils.forIpHostnameAndPort(\"1.2.3.4\", \"host.com\", 8888))\n        .isEqualTo(\n            NetworkEndpoint.newBuilder()\n                .setType(NetworkEndpoint.Type.IP_HOSTNAME_PORT)\n                .setIpAddress(\n                    IpAddress.newBuilder()\n                        .setAddressFamily(AddressFamily.IPV4)\n                        .setAddress(\"1.2.3.4\"))\n                .setHostname(Hostname.newBuilder().setName(\"host.com\"))\n                .setPort(Port.newBuilder().setPortNumber(8888))\n                .build());\n  }\n\n  @Test\n  public void forNetworkEndpointAndPort_withIpEndpointAndPort_returnsIpAndPort() {\n    NetworkEndpoint ipEndpoint = NetworkEndpointUtils.forIp(\"1.2.3.4\");\n\n    assertThat(NetworkEndpointUtils.forNetworkEndpointAndPort(ipEndpoint, 8888))\n        .isEqualTo(\n            NetworkEndpoint.newBuilder()\n                .setType(NetworkEndpoint.Type.IP_PORT)\n                .setPort(Port.newBuilder().setPortNumber(8888))\n                .setIpAddress(\n                    IpAddress.newBuilder()\n                        .setAddressFamily(AddressFamily.IPV4)\n                        .setAddress(\"1.2.3.4\"))\n                .build());\n  }\n\n  @Test\n  public void forNetworkEndpointAndPort_withHostnameEndpointAndPort_returnsHostnameAndPort() {\n    NetworkEndpoint hostnameEndpoint = NetworkEndpointUtils.forHostname(\"localhost\");\n\n    assertThat(NetworkEndpointUtils.forNetworkEndpointAndPort(hostnameEndpoint, 8888))\n        .isEqualTo(\n            NetworkEndpoint.newBuilder()\n                .setType(NetworkEndpoint.Type.HOSTNAME_PORT)\n                .setPort(Port.newBuilder().setPortNumber(8888))\n                .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n                .build());\n  }\n\n  @Test\n  public void forIp_withInvalidIp_throwsIllegalArgumentException() {\n    assertThrows(IllegalArgumentException.class, () -> NetworkEndpointUtils.forIp(\"abc\"));\n  }\n\n  @Test\n  public void forIpAndPort_withInvalidIp_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class, () -> NetworkEndpointUtils.forIpAndPort(\"abc\", 8888));\n  }\n\n  @Test\n  public void forIpAndPort_withInvalidPort_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class, () -> NetworkEndpointUtils.forIpAndPort(\"abc\", -1));\n    assertThrows(\n        IllegalArgumentException.class, () -> NetworkEndpointUtils.forIpAndPort(\"abc\", 65536));\n  }\n\n  @Test\n  public void forHostname_withIpAddress_throwsIllegalArgumentException() {\n    assertThrows(IllegalArgumentException.class, () -> NetworkEndpointUtils.forHostname(\"1.2.3.4\"));\n    assertThrows(IllegalArgumentException.class, () -> NetworkEndpointUtils.forHostname(\"3ffe::1\"));\n  }\n\n  @Test\n  public void forHostnameAndPort_withIpAddress_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> NetworkEndpointUtils.forHostnameAndPort(\"1.2.3.4\", 8888));\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> NetworkEndpointUtils.forHostnameAndPort(\"3ffe::1\", 8888));\n  }\n\n  @Test\n  public void forHostnameAndPort_withInvalidPort_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class, () -> NetworkEndpointUtils.forHostnameAndPort(\"abc\", -1));\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> NetworkEndpointUtils.forHostnameAndPort(\"abc\", 65536));\n  }\n\n  @Test\n  public void forNetworkEndpointAndPort_withInvalidEndpointType_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () ->\n            NetworkEndpointUtils.forNetworkEndpointAndPort(\n                NetworkEndpointUtils.forIpAndPort(\"1.2.3.4\", 80), 8888));\n    assertThrows(\n        IllegalArgumentException.class,\n        () ->\n            NetworkEndpointUtils.forNetworkEndpointAndPort(\n                NetworkEndpointUtils.forHostnameAndPort(\"localhost\", 80), 8888));\n  }\n\n  @Test\n  public void forNetworkEndpointAndPort_withInvalidPort_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () ->\n            NetworkEndpointUtils.forNetworkEndpointAndPort(\n                NetworkEndpointUtils.forIp(\"1.2.3.4\"), -1));\n    assertThrows(\n        IllegalArgumentException.class,\n        () ->\n            NetworkEndpointUtils.forNetworkEndpointAndPort(\n                NetworkEndpointUtils.forHostname(\"localhost\"), 65536));\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/data/NetworkServiceUtilsTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.data;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;\nimport static com.google.tsunami.common.data.NetworkEndpointUtils.forIpAndPort;\n\nimport com.google.tsunami.proto.AddressFamily;\nimport com.google.tsunami.proto.Hostname;\nimport com.google.tsunami.proto.IpAddress;\nimport com.google.tsunami.proto.NetworkEndpoint;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.Port;\nimport com.google.tsunami.proto.ServiceContext;\nimport com.google.tsunami.proto.Software;\nimport com.google.tsunami.proto.TransportProtocol;\nimport com.google.tsunami.proto.WebServiceContext;\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\nimport java.net.URL;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link NetworkServiceUtils}. */\n@RunWith(JUnit4.class)\npublic final class NetworkServiceUtilsTest {\n\n  @Test\n  public void isWebService_whenHttpService_returnsTrue() {\n    assertThat(\n            NetworkServiceUtils.isWebService(\n                NetworkService.newBuilder().setServiceName(\"http\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isWebService_whenHttpAltService_returnsTrue() {\n    assertThat(\n        NetworkServiceUtils.isWebService(\n            NetworkService.newBuilder().setServiceName(\"http-alt\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isWebService_whenHttpProxyService_returnsTrue() {\n    assertThat(\n        NetworkServiceUtils.isWebService(\n            NetworkService.newBuilder().setServiceName(\"http-proxy\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isWebService_whenHttpsService_returnsTrue() {\n    assertThat(\n            NetworkServiceUtils.isWebService(\n                NetworkService.newBuilder().setServiceName(\"https\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isWebService_whenRadanHttpService_returnsTrue() {\n    assertThat(\n        NetworkServiceUtils.isWebService(\n            NetworkService.newBuilder().setServiceName(\"radan-http\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isWebService_whenSslHttpService_returnsTrue() {\n    assertThat(\n            NetworkServiceUtils.isWebService(\n                NetworkService.newBuilder().setServiceName(\"ssl/http\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isWebService_whenSslHttpsService_returnsTrue() {\n    assertThat(\n            NetworkServiceUtils.isWebService(\n                NetworkService.newBuilder().setServiceName(\"ssl/https\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isWebService_whenCapitalizedHttpService_ignoresCaseAndReturnsTrue() {\n    assertThat(\n            NetworkServiceUtils.isWebService(\n                NetworkService.newBuilder().setServiceName(\"HTTP\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isWebService_whenHasAtLeastOneHttpMethod_returnsTrue() {\n    assertThat(\n            NetworkServiceUtils.isWebService(\n                NetworkService.newBuilder()\n                    .setServiceName(\"irrelevantService\")\n                    .addSupportedHttpMethods(\"IrrelevantMethodName\")\n                    .build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isWebService_whenNonWebService_returnsFalse() {\n    assertThat(\n            NetworkServiceUtils.isWebService(\n                NetworkService.newBuilder().setServiceName(\"ssh\").build()))\n        .isFalse();\n  }\n\n  @Test\n  public void isPlainHttp_whenPlainHttpService_returnsTrue() {\n    assertThat(\n            NetworkServiceUtils.isPlainHttp(\n                NetworkService.newBuilder().setServiceName(\"http\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isPlainHttp_whenHttpAltService_returnsTrue() {\n    assertThat(\n        NetworkServiceUtils.isPlainHttp(\n            NetworkService.newBuilder().setServiceName(\"http-alt\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isPlainHttp_whenHttpsService_returnsFalse() {\n    assertThat(\n            NetworkServiceUtils.isPlainHttp(\n                NetworkService.newBuilder().setServiceName(\"https\").build()))\n        .isFalse();\n  }\n\n  @Test\n  public void isPlainHttp_whenRadanHttpService_returnsTrue() {\n    assertThat(\n        NetworkServiceUtils.isPlainHttp(\n            NetworkService.newBuilder().setServiceName(\"radan-http\").build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isPlainHttp_whenNonWebService_returnsFalse() {\n    assertThat(\n            NetworkServiceUtils.isPlainHttp(\n                NetworkService.newBuilder().setServiceName(\"ssh\").build()))\n        .isFalse();\n  }\n\n  @Test\n  public void isPlainHttp_whenHttpServiceButHasSslVersions_returnsFalse() {\n    assertThat(\n            NetworkServiceUtils.isPlainHttp(\n                NetworkService.newBuilder()\n                    .setServiceName(\"http\")\n                    .addSupportedSslVersions(\"SSLV3\")\n                    .build()))\n        .isFalse();\n  }\n\n  @Test\n  public void isPlainHttp_whenNonHttpServiceButHasSslVersions_returnsFalse() {\n    assertThat(\n            NetworkServiceUtils.isPlainHttp(\n                NetworkService.newBuilder()\n                    .setServiceName(\"ssh\")\n                    .addSupportedSslVersions(\"SSLV3\")\n                    .build()))\n        .isFalse();\n  }\n\n  @Test\n  public void isPlainHttp_whenHttpServiceFromHttpMethodsWithoutSslVersions_returnsTrue() {\n    assertThat(\n            NetworkServiceUtils.isPlainHttp(\n                NetworkService.newBuilder()\n                    .setServiceName(\"ssh\")\n                    .addSupportedHttpMethods(\"GET\")\n                    .build()))\n        .isTrue();\n  }\n\n  @Test\n  public void isPlainHttp_whenHttpServiceWithSslVersions_returnsFalse() {\n    assertThat(\n            NetworkServiceUtils.isPlainHttp(\n                NetworkService.newBuilder()\n                    .setServiceName(\"http\")\n                    .addSupportedSslVersions(\"SSLV3\")\n                    .build()))\n        .isFalse();\n  }\n\n  @Test\n  public void getServiceName_whenNonWebService_returnsServiceName() {\n    assertThat(\n            NetworkServiceUtils.getServiceName(\n                NetworkService.newBuilder()\n                    .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 22))\n                    .setServiceName(\"ssh\")\n                    .build()))\n        .isEqualTo(\"ssh\");\n  }\n\n  @Test\n  public void getServiceName_whenWebServiceNoSoftware_returnsServiceName() {\n    assertThat(\n        NetworkServiceUtils.getServiceName(\n            NetworkService.newBuilder()\n                .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 22))\n                .setServiceName(\"http\")\n                .build()))\n        .isEqualTo(\"http\");\n  }\n\n  @Test\n  public void getServiceName_whenWebServiceWithSoftware_returnsServiceName() {\n    assertThat(\n            NetworkServiceUtils.getServiceName(\n                NetworkService.newBuilder()\n                    .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 22))\n                    .setServiceName(\"http\")\n                    .setSoftware(Software.newBuilder().setName(\"WordPress\"))\n                    .build()))\n        .isEqualTo(\"wordpress\");\n  }\n\n  @Test\n  public void getWebServiceName_whenWebServiceWithSoftware_returnsWebServiceName() {\n    assertThat(\n            NetworkServiceUtils.getWebServiceName(\n                NetworkService.newBuilder()\n                    .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 8080))\n                    .setServiceName(\"http\")\n                    .setServiceContext(\n                        ServiceContext.newBuilder()\n                            .setWebServiceContext(\n                                WebServiceContext.newBuilder()\n                                    .setSoftware(Software.newBuilder().setName(\"jenkins\"))))\n                    .build()))\n        .isEqualTo(\"jenkins\");\n  }\n\n  @Test\n  public void getServiceName_whenWebServiceNoContext_returnsServiceName() {\n    assertThat(\n            NetworkServiceUtils.getWebServiceName(\n                NetworkService.newBuilder()\n                    .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 8080))\n                    .setServiceName(\"http\")\n                    .setSoftware(Software.newBuilder().setName(\"nothttp\"))\n                    .build()))\n        .isEqualTo(\"http\");\n  }\n\n  @Test\n  public void buildWebApplicationRootUrl_whenHttpWithoutRoot_buildsExpectedUrl() {\n    assertThat(\n            NetworkServiceUtils.buildWebApplicationRootUrl(\n                NetworkService.newBuilder()\n                    .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 8080))\n                    .setServiceName(\"http\")\n                    .build()))\n        .isEqualTo(\"http://127.0.0.1:8080/\");\n  }\n\n  @Test\n  public void buildWebApplicationRootUrl_whenHttpsWithoutRoot_buildsExpectedUrl() {\n    assertThat(\n        NetworkServiceUtils.buildWebApplicationRootUrl(\n            NetworkService.newBuilder()\n                .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 8443))\n                .setServiceName(\"ssl/https\")\n                .setServiceContext(\n                    ServiceContext.newBuilder()\n                        .setWebServiceContext(\n                            WebServiceContext.newBuilder().setApplicationRoot(\"test_root\")))\n                .build()))\n        .isEqualTo(\"https://127.0.0.1:8443/test_root/\");\n  }\n\n  @Test\n  public void buildWebApplicationRootUrl_whenHttpWithRootPath_buildsUrlWithExpectedRoot() {\n    assertThat(\n            NetworkServiceUtils.buildWebApplicationRootUrl(\n                NetworkService.newBuilder()\n                    .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 8080))\n                    .setServiceName(\"http\")\n                    .setServiceContext(\n                        ServiceContext.newBuilder()\n                            .setWebServiceContext(\n                                WebServiceContext.newBuilder().setApplicationRoot(\"/test_root\")))\n                    .build()))\n        .isEqualTo(\"http://127.0.0.1:8080/test_root/\");\n  }\n\n  @Test\n  public void buildWebApplicationRootUrl_whenRootPathNoLeadingSlash_appendsLeadingSlash() {\n    assertThat(\n        NetworkServiceUtils.buildWebApplicationRootUrl(\n            NetworkService.newBuilder()\n                .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 8080))\n                .setServiceName(\"http\")\n                .setServiceContext(\n                    ServiceContext.newBuilder()\n                        .setWebServiceContext(\n                            WebServiceContext.newBuilder().setApplicationRoot(\"test_root\")))\n                .build()))\n        .isEqualTo(\"http://127.0.0.1:8080/test_root/\");\n  }\n\n  @Test\n  public void buildWebApplicationRootUrl_whenHttpServiceOnPort80_removesTrailingPortFromUrl() {\n    assertThat(\n        NetworkServiceUtils.buildWebApplicationRootUrl(\n            NetworkService.newBuilder()\n                .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 80))\n                .setServiceName(\"http\")\n                .setServiceContext(\n                    ServiceContext.newBuilder()\n                        .setWebServiceContext(\n                            WebServiceContext.newBuilder().setApplicationRoot(\"test_root\")))\n                .build()))\n        .isEqualTo(\"http://127.0.0.1/test_root/\");\n  }\n\n  @Test\n  public void buildWebApplicationRootUrl_whenHttpsServiceOnPort443_removesTrailingPortFromUrl() {\n    assertThat(\n        NetworkServiceUtils.buildWebApplicationRootUrl(\n            NetworkService.newBuilder()\n                .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 443))\n                .setServiceName(\"ssl/https\")\n                .setServiceContext(\n                    ServiceContext.newBuilder()\n                        .setWebServiceContext(\n                            WebServiceContext.newBuilder().setApplicationRoot(\"test_root\")))\n                .build()))\n        .isEqualTo(\"https://127.0.0.1/test_root/\");\n  }\n\n  @Test\n  public void buildWebApplicationRootUrl_whenNotWebService_returnsHttpUrl() {\n    assertThat(\n        NetworkServiceUtils.buildWebApplicationRootUrl(\n            NetworkService.newBuilder()\n                .setNetworkEndpoint(forIpAndPort(\"127.0.0.1\", 2121))\n                .setServiceName(\"unknown\")\n                .build()))\n        .isEqualTo(\"http://127.0.0.1:2121/\");\n  }\n\n  @Test\n  public void buildUriNetworkService_returnsNetworkService() throws IOException {\n\n    URL url = new URL(\"https://localhost/function1\");\n    String hostname = url.getHost();\n    String ipaddress = InetAddress.getByName(hostname).getHostAddress();\n    InetAddress inetAddress = InetAddress.getByName(url.getHost());\n    AddressFamily addressFamily =\n        inetAddress instanceof Inet4Address ? AddressFamily.IPV4 : AddressFamily.IPV6;\n\n    NetworkEndpoint networkEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP_HOSTNAME_PORT)\n            .setIpAddress(\n                IpAddress.newBuilder().setAddressFamily(addressFamily).setAddress(ipaddress))\n            .setPort(Port.newBuilder().setPortNumber(443))\n            .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n            .build();\n\n    NetworkService networkService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(networkEndpoint)\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .setServiceContext(\n                ServiceContext.newBuilder()\n                    .setWebServiceContext(\n                        WebServiceContext.newBuilder().setApplicationRoot(\"/function1\")))\n            .build();\n\n    assertThat(NetworkServiceUtils.buildUriNetworkService(\"https://localhost/function1\"))\n        .isEqualTo(networkService);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/io/archiving/ArchiverTestUtils.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving;\n\n/** Utilities for all {@link Archiver} unit tests. */\nfinal class ArchiverTestUtils {\n  private ArchiverTestUtils() {}\n\n  /** Returns a byte array of length size that has values 0 .. size - 1. */\n  static byte[] newPreFilledByteArray(int size) {\n    return newPreFilledByteArray(0, size);\n  }\n\n  /** Returns a byte array of length size that has values offset .. offset + size - 1. */\n  static byte[] newPreFilledByteArray(int offset, int size) {\n    byte[] array = new byte[size];\n    for (int i = 0; i < size; i++) {\n      array[i] = (byte) (offset + i);\n    }\n    return array;\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/io/archiving/GoogleCloudStorageArchiverTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.tsunami.common.io.archiving.ArchiverTestUtils.newPreFilledByteArray;\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport com.google.cloud.WriteChannel;\nimport com.google.cloud.storage.BlobInfo;\nimport com.google.cloud.storage.Storage;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Guice;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport javax.inject.Inject;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnit;\nimport org.mockito.junit.MockitoRule;\n\n/** Tests for {@link GoogleCloudStorageArchiver}. */\n@RunWith(JUnit4.class)\npublic final class GoogleCloudStorageArchiverTest {\n  private static final String BUCKET_ID = \"test_bucket\";\n  private static final String OBJECT_ID = \"test/object\";\n\n  @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();\n\n  @Mock Storage mockStorage;\n  @Mock WriteChannel mockWriter;\n\n  @Captor ArgumentCaptor<BlobInfo> blobInfoCaptor;\n  @Captor ArgumentCaptor<byte[]> byteDataCaptor;\n  @Captor ArgumentCaptor<ByteBuffer> byteBufferCaptor;\n\n  @Inject private GoogleCloudStorageArchiver.Options options;\n  @Inject private GoogleCloudStorageArchiver.Factory archiverFactory;\n\n  @Before\n  public void setUp() {\n    Guice.createInjector(\n            new AbstractModule() {\n              @Override\n              protected void configure() {\n                bind(GoogleCloudStorageArchiver.Options.class)\n                    .toInstance(new GoogleCloudStorageArchiver.Options());\n                install(new GoogleCloudStorageArchiverModule());\n              }\n            })\n        .injectMembers(this);\n  }\n\n  @Test\n  public void archive_withSmallSizeString_createsBlobInOneRequest() {\n    GoogleCloudStorageArchiver archiver = archiverFactory.create(mockStorage);\n    String dataToArchive = \"TEST DATA\";\n\n    boolean succeeded = archiver.archive(buildGcsUrl(BUCKET_ID, OBJECT_ID), dataToArchive);\n\n    assertThat(succeeded).isTrue();\n    verify(mockStorage, times(1)).create(blobInfoCaptor.capture(), byteDataCaptor.capture());\n    assertThat(blobInfoCaptor.getValue())\n        .isEqualTo(BlobInfo.newBuilder(BUCKET_ID, OBJECT_ID).build());\n    assertThat(byteDataCaptor.getValue()).isEqualTo(dataToArchive.getBytes(UTF_8));\n  }\n\n  @Test\n  public void archive_withSmallSizeBlob_createsBlobInOneRequest() {\n    GoogleCloudStorageArchiver archiver = archiverFactory.create(mockStorage);\n    byte[] dataToArchive = newPreFilledByteArray(10);\n\n    boolean succeeded = archiver.archive(buildGcsUrl(BUCKET_ID, OBJECT_ID), dataToArchive);\n\n    assertThat(succeeded).isTrue();\n    verify(mockStorage, times(1)).create(blobInfoCaptor.capture(), byteDataCaptor.capture());\n    assertThat(blobInfoCaptor.getValue())\n        .isEqualTo(BlobInfo.newBuilder(BUCKET_ID, OBJECT_ID).build());\n    assertThat(byteDataCaptor.getValue()).isEqualTo(dataToArchive);\n  }\n\n  @Test\n  public void archive_withLargeSizeString_createsBlobWithWriter() throws IOException {\n    options.chunkSizeInBytes = 8;\n    options.chunkUploadThresholdInBytes = 16;\n    doReturn(mockWriter)\n        .when(mockStorage)\n        .writer(eq(BlobInfo.newBuilder(BUCKET_ID, OBJECT_ID).build()));\n    GoogleCloudStorageArchiver archiver = archiverFactory.create(mockStorage);\n    String dataToArchive = \"THIS IS A LONG DATA\";\n    int numOfChunks = (int) Math.ceil((double) dataToArchive.length() / options.chunkSizeInBytes);\n\n    boolean succeeded = archiver.archive(buildGcsUrl(BUCKET_ID, OBJECT_ID), dataToArchive);\n\n    assertThat(succeeded).isTrue();\n    verify(mockWriter, times(numOfChunks)).write(byteBufferCaptor.capture());\n    assertThat(byteBufferCaptor.getAllValues())\n        .containsExactly(\n            ByteBuffer.wrap(dataToArchive.getBytes(UTF_8), 0, 8),\n            ByteBuffer.wrap(dataToArchive.getBytes(UTF_8), 8, 8),\n            ByteBuffer.wrap(dataToArchive.getBytes(UTF_8), 16, 3));\n  }\n\n  @Test\n  public void archive_withLargeSizeBlob_createsBlobWithWriter() throws IOException {\n    options.chunkSizeInBytes = 8;\n    options.chunkUploadThresholdInBytes = 16;\n    doReturn(mockWriter)\n        .when(mockStorage)\n        .writer(eq(BlobInfo.newBuilder(BUCKET_ID, OBJECT_ID).build()));\n    GoogleCloudStorageArchiver archiver = archiverFactory.create(mockStorage);\n    byte[] dataToArchive = newPreFilledByteArray(20);\n    int numOfChunks = (int) Math.ceil((double) dataToArchive.length / options.chunkSizeInBytes);\n\n    boolean succeeded = archiver.archive(buildGcsUrl(BUCKET_ID, OBJECT_ID), dataToArchive);\n\n    assertThat(succeeded).isTrue();\n    verify(mockWriter, times(numOfChunks)).write(byteBufferCaptor.capture());\n    assertThat(byteBufferCaptor.getAllValues())\n        .containsExactly(\n            ByteBuffer.wrap(dataToArchive, 0, 8),\n            ByteBuffer.wrap(dataToArchive, 8, 8),\n            ByteBuffer.wrap(dataToArchive, 16, 4));\n  }\n\n  @Test\n  public void archive_withLargeSizeBlobAndWriteError_returnsFalse() throws IOException {\n    options.chunkSizeInBytes = 8;\n    options.chunkUploadThresholdInBytes = 16;\n    doReturn(mockWriter)\n        .when(mockStorage)\n        .writer(eq(BlobInfo.newBuilder(BUCKET_ID, OBJECT_ID).build()));\n    doThrow(IOException.class).when(mockWriter).write(any());\n    GoogleCloudStorageArchiver archiver = archiverFactory.create(mockStorage);\n    byte[] dataToArchive = newPreFilledByteArray(20);\n\n    assertThat(archiver.archive(buildGcsUrl(BUCKET_ID, OBJECT_ID), dataToArchive)).isFalse();\n  }\n\n  @Test\n  public void archive_withInvalidGcsUrl_throwsIllegalArgumentException() {\n    GoogleCloudStorageArchiver archiver = archiverFactory.create(mockStorage);\n\n    assertThrows(IllegalArgumentException.class, () -> archiver.archive(\"invalid_url\", \"\"));\n  }\n\n  private static final String buildGcsUrl(String bucketId, String objectId) {\n    return String.format(\"gs://%s/%s\", bucketId, objectId);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/io/archiving/RawFileArchiverTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.io.archiving;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.tsunami.common.io.archiving.ArchiverTestUtils.newPreFilledByteArray;\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport com.google.common.io.Files;\nimport java.io.File;\nimport java.io.IOException;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link RawFileArchiver}. */\n@RunWith(JUnit4.class)\npublic final class RawFileArchiverTest {\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void archive_whenValidTargetFileAndByteArrayData_archivesGivenDataWithGivenName()\n      throws IOException {\n    File tempFile = temporaryFolder.newFile();\n    byte[] data = newPreFilledByteArray(200);\n\n    RawFileArchiver rawFileArchiver = new RawFileArchiver();\n\n    assertThat(rawFileArchiver.archive(tempFile.getAbsolutePath(), data)).isTrue();\n    assertThat(Files.toByteArray(tempFile)).isEqualTo(data);\n  }\n\n  @Test\n  public void archive_whenInvalidTargetFileAndByteArrayData_returnsFalse() throws IOException {\n    File tempFile = temporaryFolder.newFile();\n    byte[] data = newPreFilledByteArray(200);\n\n    RawFileArchiver rawFileArchiver = new RawFileArchiver();\n\n    assertThat(rawFileArchiver.archive(tempFile.getParent(), data)).isFalse();\n    assertThat(tempFile.length()).isEqualTo(0);\n  }\n\n  @Test\n  public void archive_whenValidTargetFileAndCharSequenceData_archivesGivenDataWithGivenName()\n      throws IOException {\n    File tempFile = temporaryFolder.newFile();\n    String data = \"file data\";\n\n    RawFileArchiver rawFileArchiver = new RawFileArchiver();\n\n    assertThat(rawFileArchiver.archive(tempFile.getAbsolutePath(), data)).isTrue();\n    assertThat(Files.asCharSource(tempFile, UTF_8).read()).isEqualTo(data);\n  }\n\n  @Test\n  public void archive_whenInvalidTargetFileAndCharSequenceData_returnsFalse() throws IOException {\n    File tempFile = temporaryFolder.newFile();\n    String data = \"file data\";\n\n    RawFileArchiver rawFileArchiver = new RawFileArchiver();\n\n    assertThat(rawFileArchiver.archive(tempFile.getParent(), data)).isFalse();\n    assertThat(tempFile.length()).isEqualTo(0);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/net/FuzzingUtilsTest.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.tsunami.common.net.http.HttpRequest;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic final class FuzzingUtilsTest {\n  private static final HttpRequest REQUEST_WITHOUT_GET_PARAMETERS =\n      HttpRequest.get(\"https://google.com\").withEmptyHeaders().build();\n  private static final HttpRequest REQUEST_WITH_GET_PARAMETERS =\n      HttpRequest.get(\"https://google.com?key=value&other=test\").withEmptyHeaders().build();\n\n  @Test\n  public void fuzzGetParametersWithDefaultParameter_whenNoGetParameters_addsDefaultParameter() {\n    HttpRequest requestWithDefaultParameter =\n        HttpRequest.get(\"https://google.com?default=<payload>\").withEmptyHeaders().build();\n\n    assertThat(\n            FuzzingUtils.fuzzGetParametersWithDefaultParameter(\n                REQUEST_WITHOUT_GET_PARAMETERS, \"<payload>\", \"default\"))\n        .contains(requestWithDefaultParameter);\n  }\n\n  @Test\n  public void fuzzGetParametersWithDefaultParameter_whenGetParameters_doesNotAddDefaultParameter() {\n    HttpRequest requestWithDefaultParameter =\n        HttpRequest.get(\"https://google.com?default=<payload>\").withEmptyHeaders().build();\n\n    assertThat(\n            FuzzingUtils.fuzzGetParametersWithDefaultParameter(\n                REQUEST_WITH_GET_PARAMETERS, \"<payload>\", \"default\"))\n        .doesNotContain(requestWithDefaultParameter);\n  }\n\n  @Test\n  public void fuzzGetParametersWithDefaultParameter_whenGetParameters_fuzzesAllParameters() {\n    ImmutableList<HttpRequest> requestsWithFuzzedGetParameters =\n        ImmutableList.of(\n            HttpRequest.get(\"https://google.com?key=<payload>&other=test\")\n                .withEmptyHeaders()\n                .build(),\n            HttpRequest.get(\"https://google.com?key=value&other=<payload>\")\n                .withEmptyHeaders()\n                .build());\n\n    assertThat(\n            FuzzingUtils.fuzzGetParametersWithDefaultParameter(\n                REQUEST_WITH_GET_PARAMETERS, \"<payload>\", \"default\"))\n        .containsAtLeastElementsIn(requestsWithFuzzedGetParameters);\n  }\n\n  @Test\n  public void\n      fuzzGetParametersExpectingPathValues_whenGetParameterValueHasFileExtension_appendsFileExtensionToPayload() {\n    HttpRequest requestWithFileExtension =\n        HttpRequest.get(\"https://google.com?key=value.jpg\").withEmptyHeaders().build();\n    HttpRequest requestWithFuzzedGetParameterWithFileExtension =\n        HttpRequest.get(\"https://google.com?key=<payload>%00.jpg\").withEmptyHeaders().build();\n\n    assertThat(\n            FuzzingUtils.fuzzGetParametersExpectingPathValues(\n                requestWithFileExtension, \"<payload>\"))\n        .contains(requestWithFuzzedGetParameterWithFileExtension);\n  }\n\n  @Test\n  public void\n      fuzzGetParametersExpectingPathValues_whenGetParameterValueHasPathPrefix_prefixesPayload() {\n    HttpRequest requestWithPathPrefix =\n        HttpRequest.get(\"https://google.com?key=resources/value\").withEmptyHeaders().build();\n    HttpRequest requestWithFuzzedGetParameterWithPathPrefix =\n        HttpRequest.get(\"https://google.com?key=resources/<payload>\").withEmptyHeaders().build();\n\n    assertThat(\n            FuzzingUtils.fuzzGetParametersExpectingPathValues(requestWithPathPrefix, \"<payload>\"))\n        .contains(requestWithFuzzedGetParameterWithPathPrefix);\n  }\n\n  @Test\n  public void\n      fuzzGetParametersExpectingPathValues_whenGetParameterValueHasPathPrefixAndFileExtension_prefixesPayloadAndAppendsFileExtension() {\n    HttpRequest requestWithPathPrefixAndFileExtension =\n        HttpRequest.get(\"https://google.com?key=resources/value.jpg\").withEmptyHeaders().build();\n    HttpRequest requestWithFuzzedGetParameterWithPathPrefixAndFileExtension =\n        HttpRequest.get(\"https://google.com?key=resources/<payload>%00.jpg\")\n            .withEmptyHeaders()\n            .build();\n\n    assertThat(\n            FuzzingUtils.fuzzGetParametersExpectingPathValues(\n                requestWithPathPrefixAndFileExtension, \"<payload>\"))\n        .contains(requestWithFuzzedGetParameterWithPathPrefixAndFileExtension);\n  }\n\n  @Test\n  public void\n      fuzzGetParametersExpectingPathValues_whenGetParameterValueHasPathPrefixOrFileExtension_prefixesPayloadOrAppendsFileExtension() {\n    HttpRequest requestWithPathPrefixOrFileExtension =\n        HttpRequest.get(\"https://google.com?key=resources./value\").withEmptyHeaders().build();\n    HttpRequest requestWithFuzzedGetParameterWithPathPrefixAndFileExtension =\n        HttpRequest.get(\"https://google.com?key=resources./<payload>%00./value\")\n            .withEmptyHeaders()\n            .build();\n\n    assertThat(\n            FuzzingUtils.fuzzGetParametersExpectingPathValues(\n                requestWithPathPrefixOrFileExtension, \"<payload>\"))\n        .doesNotContain(requestWithFuzzedGetParameterWithPathPrefixAndFileExtension);\n  }\n\n  @Test\n  public void fuzzGetParameters_whenNoGetParameters_returnsEmptyList() {\n    assertThat(FuzzingUtils.fuzzGetParameters(REQUEST_WITHOUT_GET_PARAMETERS, \"<payload>\"))\n        .isEmpty();\n  }\n\n  @Test\n  public void fuzzGetParameters_whenGetParameters_fuzzesAllParameters() {\n    ImmutableList<HttpRequest> requestsWithFuzzedGetParameters =\n        ImmutableList.of(\n            HttpRequest.get(\"https://google.com?key=<payload>&other=test\")\n                .withEmptyHeaders()\n                .build(),\n            HttpRequest.get(\"https://google.com?key=value&other=<payload>\")\n                .withEmptyHeaders()\n                .build());\n\n    assertThat(FuzzingUtils.fuzzGetParameters(REQUEST_WITH_GET_PARAMETERS, \"<payload>\"))\n        .containsAtLeastElementsIn(requestsWithFuzzedGetParameters);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/net/UrlUtilsTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.tsunami.common.net.UrlUtils.allSubPaths;\nimport static com.google.tsunami.common.net.UrlUtils.removeLeadingSlashes;\nimport static com.google.tsunami.common.net.UrlUtils.removeTrailingSlashes;\nimport static com.google.tsunami.common.net.UrlUtils.urlEncode;\n\nimport okhttp3.HttpUrl;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link UrlUtils}. */\n@RunWith(JUnit4.class)\npublic final class UrlUtilsTest {\n\n  @Test\n  public void allSubPaths_whenInvalidUrl_returnsEmptyList() {\n    assertThat(allSubPaths(\"invalid_url\")).isEmpty();\n  }\n\n  @Test\n  public void allSubPaths_whenNoSubPathNoTrailingSlash_returnsSingleUrl() {\n    assertThat(allSubPaths(\"http://localhost\")).containsExactly(HttpUrl.parse(\"http://localhost/\"));\n  }\n\n  @Test\n  public void allSubPaths_whenNoSubPathWithTrailingSlash_returnsSingleUrl() {\n    assertThat(allSubPaths(\"http://localhost/\"))\n        .containsExactly(HttpUrl.parse(\"http://localhost/\"));\n  }\n\n  @Test\n  public void allSubPaths_whenValidQueryParamsAndFragments_removesParamsAndFragments() {\n    assertThat(allSubPaths(\"http://localhost/?param=value&param2=value2#abc\"))\n        .containsExactly(HttpUrl.parse(\"http://localhost/\"));\n  }\n\n  @Test\n  public void allSubPaths_whenSingleSubPathsNoTrailingSlash_returnsExpectedUrl() {\n    assertThat(allSubPaths(\"http://localhost/a\"))\n        .containsExactly(HttpUrl.parse(\"http://localhost/\"), HttpUrl.parse(\"http://localhost/a/\"));\n  }\n\n  @Test\n  public void allSubPaths_whenSingleSubPathsWithTrailingSlash_returnsExpectedUrl() {\n    assertThat(allSubPaths(\"http://localhost/a/\"))\n        .containsExactly(HttpUrl.parse(\"http://localhost/\"), HttpUrl.parse(\"http://localhost/a/\"));\n  }\n\n  @Test\n  public void allSubPaths_whenMultipleSubPathsNoTrailingSlash_returnsExpectedUrl() {\n    assertThat(allSubPaths(\"http://localhost/a/b/c\"))\n        .containsExactly(\n            HttpUrl.parse(\"http://localhost/\"),\n            HttpUrl.parse(\"http://localhost/a/\"),\n            HttpUrl.parse(\"http://localhost/a/b/\"),\n            HttpUrl.parse(\"http://localhost/a/b/c/\"));\n  }\n\n  @Test\n  public void allSubPaths_whenMultipleSubPathsWithTrailingSlash_returnsExpectedUrl() {\n    assertThat(allSubPaths(\"http://localhost/a/b/c/\"))\n        .containsExactly(\n            HttpUrl.parse(\"http://localhost/\"),\n            HttpUrl.parse(\"http://localhost/a/\"),\n            HttpUrl.parse(\"http://localhost/a/b/\"),\n            HttpUrl.parse(\"http://localhost/a/b/c/\"));\n  }\n\n  @Test\n  public void allSubPaths_whenMultipleSubPathsWithParamsAndFragments_returnsExpectedUrl() {\n    assertThat(allSubPaths(\"http://localhost/a/b/c/?param=value&param2=value2#abc\"))\n        .containsExactly(\n            HttpUrl.parse(\"http://localhost/\"),\n            HttpUrl.parse(\"http://localhost/a/\"),\n            HttpUrl.parse(\"http://localhost/a/b/\"),\n            HttpUrl.parse(\"http://localhost/a/b/c/\"));\n  }\n\n  @Test\n  public void removeLeadingSlashes_whenNoLeadingSlashes_returnsOriginal() {\n    assertThat(removeLeadingSlashes(\"a/b/c/\")).isEqualTo(\"a/b/c/\");\n  }\n\n  @Test\n  public void removeLeadingSlashes_whenSingleLeadingSlash_removesLeadingSlashes() {\n    assertThat(removeLeadingSlashes(\"/a/b/c/\")).isEqualTo(\"a/b/c/\");\n  }\n\n  @Test\n  public void removeLeadingSlashes_whenMultipleLeadingSlashes_removesLeadingSlashes() {\n    assertThat(removeLeadingSlashes(\"/////a/b/c/\")).isEqualTo(\"a/b/c/\");\n  }\n\n  @Test\n  public void removeTrailingSlashes_whenNoTrailingSlashes_returnsOriginal() {\n    assertThat(removeTrailingSlashes(\"/a/b/c\")).isEqualTo(\"/a/b/c\");\n  }\n\n  @Test\n  public void removeTrailingSlashes_whenSingleTrailingSlash_removesTrailingSlashes() {\n    assertThat(removeTrailingSlashes(\"/a/b/c/\")).isEqualTo(\"/a/b/c\");\n  }\n\n  @Test\n  public void removeTrailingSlashes_whenMultipleTrailingSlashes_removesTrailingSlashes() {\n    assertThat(removeTrailingSlashes(\"/a/b/c/////\")).isEqualTo(\"/a/b/c\");\n  }\n\n  @Test\n  public void urlEncode_whenEmptyString_returnsOriginal() {\n    assertThat(urlEncode(\"\")).hasValue(\"\");\n  }\n\n  @Test\n  public void urlEncode_whenNothingToEncode_returnsOriginal() {\n    assertThat(urlEncode(\"abcdefghijklmnopqrstuvwxyz\")).hasValue(\"abcdefghijklmnopqrstuvwxyz\");\n    assertThat(urlEncode(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\")).hasValue(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\");\n    assertThat(urlEncode(\"0123456789\")).hasValue(\"0123456789\");\n    assertThat(urlEncode(\"-_.*\")).hasValue(\"-_.*\");\n  }\n\n  @Test\n  public void urlEncode_whenNotEncoded_returnsEncoded() {\n    assertThat(urlEncode(\" \")).hasValue(\"+\");\n    assertThat(urlEncode(\"()[]{}<>\")).hasValue(\"%28%29%5B%5D%7B%7D%3C%3E\");\n    assertThat(urlEncode(\"?!@#$%^&=+,;:'\\\"`/\\\\|~\"))\n        .hasValue(\"%3F%21%40%23%24%25%5E%26%3D%2B%2C%3B%3A%27%22%60%2F%5C%7C%7E\");\n  }\n\n  @Test\n  public void urlEncode_whenAlreadyEncoded_encodesAgain() {\n    assertThat(urlEncode(\"%2F\")).hasValue(\"%252F\");\n    assertThat(urlEncode(\"%252F\")).hasValue(\"%25252F\");\n  }\n\n  @Test\n  public void urlEncode_whenComplexEncoding_encodesCorrectly() {\n    assertThat(urlEncode(\"£\")).hasValue(\"%C2%A3\");\n    assertThat(urlEncode(\"つ\")).hasValue(\"%E3%81%A4\");\n    assertThat(urlEncode(\"äëïöüÿ\")).hasValue(\"%C3%A4%C3%AB%C3%AF%C3%B6%C3%BC%C3%BF\");\n    assertThat(urlEncode(\"ÄËÏÖÜŸ\")).hasValue(\"%C3%84%C3%8B%C3%8F%C3%96%C3%9C%C5%B8\");\n  }\n\n  @Test\n  public void urlEncode_whenUnicode_encodesOriginal() {\n    // EURO sign\n    assertThat(urlEncode(\"\\u20AC\")).hasValue(\"%E2%82%AC\");\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/net/http/HttpClientModuleTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.tsunami.common.net.http.HttpRequest.get;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.assertTrue;\n\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Guice;\nimport com.google.inject.Injector;\nimport com.google.inject.Key;\nimport com.google.tsunami.common.net.http.HttpClientModule.ConnectTimeout;\nimport com.google.tsunami.common.net.http.HttpClientModule.FollowRedirects;\nimport com.google.tsunami.common.net.http.HttpClientModule.MaxRequests;\nimport java.io.IOException;\nimport java.security.GeneralSecurityException;\nimport java.security.KeyStore;\nimport java.time.Duration;\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLHandshakeException;\nimport javax.net.ssl.SSLSocketFactory;\nimport okhttp3.mockwebserver.MockResponse;\nimport okhttp3.mockwebserver.MockWebServer;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link HttpClientModule}. */\n@RunWith(JUnit4.class)\npublic final class HttpClientModuleTest {\n  private static final String TESTING_KEYSTORE = \"testdata/tsunami_test_server.p12\";\n  private static final char[] TESTING_KEYSTORE_PASSWORD = \"tsunamitest\".toCharArray();\n\n  private final HttpClientCliOptions cliOptions = new HttpClientCliOptions();\n  private final HttpClientConfigProperties configProperties = new HttpClientConfigProperties();\n\n  @Test\n  public void provideHttpClient_always_createsSingleton() {\n    Injector injector =\n        Guice.createInjector(new HttpClientModule.Builder().setMaxRequests(10).build());\n\n    HttpClient httpClient = injector.getInstance(HttpClient.class);\n    HttpClient httpClient2 = injector.getInstance(HttpClient.class);\n\n    assertThat(httpClient).isSameInstanceAs(httpClient2);\n  }\n\n  @Test\n  public void setConnectionPoolMaxIdle_whenNonPositiveMaxIdle_throwsIllegalArgumentException() {\n    HttpClientModule.Builder builder = new HttpClientModule.Builder();\n\n    assertThrows(IllegalArgumentException.class, () -> builder.setConnectionPoolMaxIdle(-1));\n    assertThrows(IllegalArgumentException.class, () -> builder.setConnectionPoolMaxIdle(0));\n  }\n\n  @Test\n  public void\n      setConnectionPoolKeepAliveDuration_whenNegativeDuration_throwsIllegalArgumentException() {\n    HttpClientModule.Builder builder = new HttpClientModule.Builder();\n\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> builder.setConnectionPoolKeepAliveDuration(Duration.ofMillis(-1)));\n  }\n\n  @Test\n  public void setMaxRequests_whenPositiveRequests_setsValueToDispatcher() {\n    Injector injector =\n        Guice.createInjector(new HttpClientModule.Builder().setMaxRequests(10).build());\n\n    assertThat(injector.getInstance(Key.get(int.class, MaxRequests.class))).isEqualTo(10);\n  }\n\n  @Test\n  public void setMaxRequests_whenNonPositiveRequests_throwsIllegalArgumentException() {\n    HttpClientModule.Builder builder = new HttpClientModule.Builder();\n\n    assertThrows(IllegalArgumentException.class, () -> builder.setMaxRequests(-1));\n    assertThrows(IllegalArgumentException.class, () -> builder.setMaxRequests(0));\n  }\n\n  @Test\n  public void setFollowRedirects_always_setsValueToClient() {\n    Injector injector =\n        Guice.createInjector(new HttpClientModule.Builder().setFollowRedirects(true).build());\n\n    assertTrue(injector.getInstance(Key.get(Boolean.class, FollowRedirects.class)));\n  }\n\n  @Test\n  public void setTrustAllCertificates_whenFalseAndCertIsInvalid_throws()\n      throws GeneralSecurityException, IOException {\n    cliOptions.trustAllCertificates = false;\n    configProperties.trustAllCertificates = false;\n    HttpClient httpClient =\n        Guice.createInjector(getTestingGuiceModuleWithConfigs()).getInstance(HttpClient.class);\n    MockWebServer mockWebServer = startMockWebServerWithSsl();\n\n    // The certificate used in test is a self-signed one. HttpClient will reject it unless the\n    // certificate is explicitly trusted.\n    assertThrows(\n        SSLHandshakeException.class,\n        () -> httpClient.send(get(mockWebServer.url(\"/\")).withEmptyHeaders().build()));\n\n    // Note: b/314642696 - After this point, the socket in mockWebServer was closed when the\n    //                     exception was raised. So working with the mockWebServer would be\n    //                     hazardous.\n  }\n\n  @Test\n  public void setTrustAllCertificates_whenBothCliAndConfigValuesAreSet_cliValueTakesPrecedence()\n      throws GeneralSecurityException, IOException {\n    cliOptions.trustAllCertificates = false;\n    configProperties.trustAllCertificates = true;\n    HttpClient httpClient =\n        Guice.createInjector(getTestingGuiceModuleWithConfigs()).getInstance(HttpClient.class);\n    MockWebServer mockWebServer = startMockWebServerWithSsl();\n\n    // The certificate used in test is a self-signed one. HttpClient will reject it unless the\n    // certificate is explicitly trusted.\n    assertThrows(\n        SSLHandshakeException.class,\n        () -> httpClient.send(get(mockWebServer.url(\"/\")).withEmptyHeaders().build()));\n\n    // Note: b/314642696 - After this point, the socket in mockWebServer was closed when the\n    //                     exception was raised. So working with the mockWebServer would be\n    //                     hazardous.\n  }\n\n  @Test\n  public void setTrustAllCertificates_whenCliOptionEnabledAndCertIsInvalid_ignoresCertError()\n      throws GeneralSecurityException, IOException {\n    cliOptions.trustAllCertificates = true;\n    HttpClient httpClient =\n        Guice.createInjector(getTestingGuiceModuleWithConfigs()).getInstance(HttpClient.class);\n    MockWebServer mockWebServer = startMockWebServerWithSsl();\n\n    HttpResponse response = httpClient.send(get(mockWebServer.url(\"/\")).withEmptyHeaders().build());\n    assertThat(response.bodyString()).hasValue(\"body\");\n\n    mockWebServer.shutdown();\n  }\n\n  @Test\n  public void\n      setTrustAllCertificates_whenConfigPropropertyEnabledAndCertIsInvalid_ignoresCertError()\n          throws GeneralSecurityException, IOException {\n    configProperties.trustAllCertificates = true;\n    HttpClient httpClient =\n        Guice.createInjector(getTestingGuiceModuleWithConfigs()).getInstance(HttpClient.class);\n    MockWebServer mockWebServer = startMockWebServerWithSsl();\n\n    HttpResponse response = httpClient.send(get(mockWebServer.url(\"/\")).withEmptyHeaders().build());\n    assertThat(response.bodyString()).hasValue(\"body\");\n\n    mockWebServer.shutdown();\n  }\n\n  @Test\n  public void setConnectTimeoutSeconds_whenSpecifiedUsingCliOptions_setsValueFromCli() {\n    cliOptions.connectTimeoutSeconds = 50;\n    Injector injector = Guice.createInjector(getTestingGuiceModuleWithConfigs());\n\n    assertThat(injector.getInstance(Key.get(Duration.class, ConnectTimeout.class)))\n        .isEqualTo(Duration.ofSeconds(50));\n  }\n\n  @Test\n  public void setConnectTimeoutSeconds_whenSpecifiedUsingConfigProperties_setsValueFromConfig() {\n    configProperties.connectTimeoutSeconds = 50;\n\n    Injector injector = Guice.createInjector(getTestingGuiceModuleWithConfigs());\n\n    assertThat(injector.getInstance(Key.get(Duration.class, ConnectTimeout.class)))\n        .isEqualTo(Duration.ofSeconds(50));\n  }\n\n  @Test\n  public void setConnectTimeoutSeconds_whenBothCliAndConfigAreSet_cliTakesPrecedence() {\n    cliOptions.connectTimeoutSeconds = 50;\n    configProperties.connectTimeoutSeconds = 30;\n\n    Injector injector = Guice.createInjector(getTestingGuiceModuleWithConfigs());\n\n    assertThat(injector.getInstance(Key.get(Duration.class, ConnectTimeout.class)))\n        .isEqualTo(Duration.ofSeconds(50));\n  }\n\n  @Test\n  public void setConnectTimeoutSeconds_whenBothCliAndConfigAreNotSet_setsDefaultValue() {\n    Injector injector = Guice.createInjector(getTestingGuiceModuleWithConfigs());\n\n    assertThat(injector.getInstance(Key.get(Duration.class, ConnectTimeout.class)))\n        .isEqualTo(Duration.ofSeconds(10));\n  }\n\n  private AbstractModule getTestingGuiceModuleWithConfigs() {\n    return new AbstractModule() {\n      @Override\n      protected void configure() {\n        install(new HttpClientModule.Builder().build());\n        bind(HttpClientCliOptions.class).toInstance(cliOptions);\n        bind(HttpClientConfigProperties.class).toInstance(configProperties);\n      }\n    };\n  }\n\n  private MockWebServer startMockWebServerWithSsl() throws GeneralSecurityException, IOException {\n    MockWebServer mockWebServer = new MockWebServer();\n    mockWebServer.useHttps(getTestingSslSocketFactory(), false);\n    mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(\"body\"));\n    mockWebServer.start();\n    return mockWebServer;\n  }\n\n  private SSLSocketFactory getTestingSslSocketFactory()\n      throws GeneralSecurityException, IOException {\n    final KeyManagerFactory keyManagerFactory =\n        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());\n    KeyStore keyStore = KeyStore.getInstance(\"PKCS12\");\n    keyStore.load(getClass().getResourceAsStream(TESTING_KEYSTORE), TESTING_KEYSTORE_PASSWORD);\n    keyManagerFactory.init(keyStore, TESTING_KEYSTORE_PASSWORD);\n    SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n    sslContext.init(keyManagerFactory.getKeyManagers(), null, null);\n    return sslContext.getSocketFactory();\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/net/http/HttpHeadersTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.collect.ImmutableListMultimap;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link HttpHeaders}. */\n@RunWith(JUnit4.class)\npublic class HttpHeadersTest {\n\n  @Test\n  public void builderAddHeader_always_putsInHeadersMap() {\n    HttpHeaders httpHeaders = HttpHeaders.builder().addHeader(\"test_header\", \"test_value\").build();\n    assertThat(httpHeaders.rawHeaders())\n        .containsExactlyEntriesIn(ImmutableListMultimap.of(\"test_header\", \"test_value\"));\n  }\n\n  @Test\n  public void builderAddHeader_withKnownHeader_canonicalizesHeaderName() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT.toUpperCase(), \"test_value\")\n            .build();\n    assertThat(httpHeaders.rawHeaders())\n        .containsExactlyEntriesIn(\n            ImmutableListMultimap.of(com.google.common.net.HttpHeaders.ACCEPT, \"test_value\"));\n  }\n\n  @Test\n  public void builderAddHeader_whenEnableCanonicalization_canonicalizesHeaderName() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(\"TEST_Header\", \"test_value\", true)\n            .build();\n    assertThat(httpHeaders.rawHeaders())\n        .containsExactlyEntriesIn(\n            ImmutableListMultimap.of(\"test_header\", \"test_value\"));\n  }\n\n  @Test\n  public void builderAddHeader_whenDisableCanonicalization_addsHeaderNameAsIs() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(\"TEST_Header\", \"test_value\", false)\n            .build();\n    assertThat(httpHeaders.rawHeaders())\n        .containsExactlyEntriesIn(\n            ImmutableListMultimap.of(\"TEST_Header\", \"test_value\"));\n  }\n\n  @Test\n  public void builderAddHeader_withNullName_throwsNullPointerException() {\n    assertThrows(\n        NullPointerException.class, () -> HttpHeaders.builder().addHeader(null, \"test_value\"));\n  }\n\n  @Test\n  public void builderAddHeader_withNullValue_throwsNullPointerException() {\n    assertThrows(\n        NullPointerException.class, () -> HttpHeaders.builder().addHeader(\"test_header\", null));\n  }\n\n  @Test\n  public void builderAddHeader_withIllegalHeaderName_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class, () -> HttpHeaders.builder().addHeader(\":::\", \"test_value\"));\n  }\n\n  @Test\n  public void builderAddHeader_withIllegalHeaderValue_throwsIllegalArgumentException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> HttpHeaders.builder().addHeader(\"test_header\", String.valueOf((char) 11)));\n  }\n\n  @Test\n  public void names_always_returnsAllHeaderNames() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"*/*\")\n            .addHeader(com.google.common.net.HttpHeaders.CONTENT_TYPE, \"text/html; charset=UTF-8\")\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"text/html\")\n            .build();\n\n    assertThat(httpHeaders.names())\n        .containsExactly(\n            com.google.common.net.HttpHeaders.ACCEPT,\n            com.google.common.net.HttpHeaders.CONTENT_TYPE);\n  }\n\n  @Test\n  public void get_whenRequestedHeaderExists_returnsRequestedHeader() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"*/*\")\n            .addHeader(com.google.common.net.HttpHeaders.CONTENT_TYPE, \"text/html; charset=UTF-8\")\n            .build();\n\n    assertThat(httpHeaders.get(com.google.common.net.HttpHeaders.ACCEPT)).hasValue(\"*/*\");\n  }\n\n  @Test\n  public void get_whenMultipleValuesExist_returnsFirstValue() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"*/*\")\n            .addHeader(com.google.common.net.HttpHeaders.CONTENT_TYPE, \"text/html; charset=UTF-8\")\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"text/html\")\n            .build();\n\n    assertThat(httpHeaders.get(com.google.common.net.HttpHeaders.ACCEPT)).hasValue(\"*/*\");\n  }\n\n  @Test\n  public void get_whenRequestedHeaderDoesNotExist_returnsEmpty() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"*/*\")\n            .addHeader(com.google.common.net.HttpHeaders.CONTENT_TYPE, \"text/html; charset=UTF-8\")\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"text/html\")\n            .build();\n\n    assertThat(httpHeaders.get(com.google.common.net.HttpHeaders.COOKIE)).isEmpty();\n  }\n\n  @Test\n  public void get_withNullHeaderName_throwsNullPointerException() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"*/*\")\n            .addHeader(com.google.common.net.HttpHeaders.CONTENT_TYPE, \"text/html; charset=UTF-8\")\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"text/html\")\n            .build();\n\n    assertThrows(NullPointerException.class, () -> httpHeaders.get(null));\n  }\n\n  @Test\n  public void getAll_always_returnsAllRequestedValues() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"*/*\")\n            .addHeader(com.google.common.net.HttpHeaders.CONTENT_TYPE, \"text/html; charset=UTF-8\")\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"text/html\")\n            .build();\n\n    assertThat(httpHeaders.getAll(com.google.common.net.HttpHeaders.ACCEPT))\n        .containsExactly(\"*/*\", \"text/html\");\n  }\n\n  @Test\n  public void getAll_withKnownHeaderValue_canonicalizesRequestedHeader() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"*/*\")\n            .addHeader(com.google.common.net.HttpHeaders.CONTENT_TYPE, \"text/html; charset=UTF-8\")\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"text/html\")\n            .build();\n\n    assertThat(httpHeaders.getAll(com.google.common.net.HttpHeaders.ACCEPT.toUpperCase()))\n        .containsExactly(\"*/*\", \"text/html\");\n  }\n\n  @Test\n  public void getAll_whenRequestValueDoesNotExist_returnsEmptyList() {\n    HttpHeaders httpHeaders =\n        HttpHeaders.builder()\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"*/*\")\n            .addHeader(com.google.common.net.HttpHeaders.CONTENT_TYPE, \"text/html; charset=UTF-8\")\n            .addHeader(com.google.common.net.HttpHeaders.ACCEPT, \"text/html\")\n            .build();\n\n    assertThat(httpHeaders.getAll(com.google.common.net.HttpHeaders.COOKIE)).isEmpty();\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/net/http/HttpRequestTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.protobuf.ByteString;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link HttpRequest}. */\n@RunWith(JUnit4.class)\npublic class HttpRequestTest {\n\n  @Test\n  public void get_always_buildsHttpGetRequest() {\n    HttpRequest httpRequest = HttpRequest.get(\"http://localhost/url\").withEmptyHeaders().build();\n\n    assertThat(httpRequest.method()).isEqualTo(HttpMethod.GET);\n    assertThat(httpRequest.url()).isEqualTo(\"http://localhost/url\");\n  }\n\n  @Test\n  public void head_always_buildsHttpHeadRequest() {\n    HttpRequest httpRequest = HttpRequest.head(\"http://localhost/url\").withEmptyHeaders().build();\n\n    assertThat(httpRequest.method()).isEqualTo(HttpMethod.HEAD);\n    assertThat(httpRequest.url()).isEqualTo(\"http://localhost/url\");\n  }\n\n  @Test\n  public void post_always_buildsHttpPostRequest() {\n    HttpRequest httpRequest = HttpRequest.post(\"http://localhost/url\").withEmptyHeaders().build();\n\n    assertThat(httpRequest.method()).isEqualTo(HttpMethod.POST);\n    assertThat(httpRequest.url()).isEqualTo(\"http://localhost/url\");\n  }\n\n  @Test\n  public void delete_always_buildsHttpDeleteRequest() {\n    HttpRequest httpRequest = HttpRequest.delete(\"http://localhost/url\").withEmptyHeaders().build();\n\n    assertThat(httpRequest.method()).isEqualTo(HttpMethod.DELETE);\n    assertThat(httpRequest.url()).isEqualTo(\"http://localhost/url\");\n  }\n\n  @Test\n  public void build_whenGetRequestHasRequestBody_throwsIllegalStateException() {\n    assertThrows(\n        IllegalStateException.class,\n        () ->\n            HttpRequest.builder()\n                .setMethod(HttpMethod.GET)\n                .setUrl(\"http://localhost\")\n                .setHeaders(HttpHeaders.builder().build())\n                .setRequestBody(ByteString.EMPTY)\n                .build());\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/net/http/HttpResponseTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.assertTrue;\n\nimport com.google.protobuf.ByteString;\nimport okhttp3.HttpUrl;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link HttpResponse}. */\n@RunWith(JUnit4.class)\npublic final class HttpResponseTest {\n\n  private static final HttpUrl TEST_URL = HttpUrl.parse(\"https://example.com/\");\n\n  @Test\n  public void bodyJson_whenValidResponseBody_returnsParsedJson() {\n    HttpResponse httpResponse =\n        HttpResponse.builder()\n            .setStatus(HttpStatus.OK)\n            .setHeaders(HttpHeaders.builder().build())\n            .setBodyBytes(ByteString.copyFromUtf8(\"{ \\\"test_value\\\": 1 }\"))\n            .setResponseUrl(TEST_URL)\n            .build();\n\n    assertThat(httpResponse.bodyJson()).isPresent();\n    assertThat(httpResponse.bodyJson().get().isJsonObject()).isTrue();\n    assertThat(\n            httpResponse\n                .bodyJson()\n                .get()\n                .getAsJsonObject()\n                .getAsJsonPrimitive(\"test_value\")\n                .getAsInt())\n        .isEqualTo(1);\n  }\n\n  @Test\n  public void bodyJson_whenEmptyResponseBody_returnsEmptyOptional() {\n    HttpResponse httpResponse =\n        HttpResponse.builder()\n            .setStatus(HttpStatus.OK)\n            .setHeaders(HttpHeaders.builder().build())\n            .setResponseUrl(TEST_URL)\n            .build();\n\n    assertThat(httpResponse.bodyJson()).isEmpty();\n  }\n\n  @Test\n  public void bodyJson_whenNonJsonResponseBody_returnsEmptyOptional() {\n    HttpResponse httpResponse =\n        HttpResponse.builder()\n            .setStatus(HttpStatus.OK)\n            .setHeaders(HttpHeaders.builder().build())\n            .setBodyBytes(ByteString.copyFromUtf8(\"not a json\"))\n            .setResponseUrl(TEST_URL)\n            .build();\n\n    assertThat(httpResponse.bodyJson()).isEmpty();\n  }\n\n  @Test\n  public void bodyJson_whenEmptyBodyResponseBody_throwsJsonSyntaxException() {\n    HttpResponse httpResponse =\n        HttpResponse.builder()\n            .setStatus(HttpStatus.OK)\n            .setHeaders(HttpHeaders.builder().build())\n            .setBodyBytes(ByteString.copyFromUtf8(\"\"))\n            .setResponseUrl(TEST_URL)\n            .build();\n\n    assertThrows(\n        IllegalStateException.class, () -> httpResponse.jsonFieldEqualsToValue(\"field\", \"value\"));\n  }\n\n  @Test\n  public void jsonFieldEqualsToValue_whenEmptyJsonResponseBody_returnsFalse() {\n    HttpResponse httpResponse =\n        HttpResponse.builder()\n            .setStatus(HttpStatus.OK)\n            .setHeaders(HttpHeaders.builder().build())\n            .setBodyBytes(ByteString.copyFromUtf8(\"{}\"))\n            .setResponseUrl(TEST_URL)\n            .build();\n\n    assertFalse(httpResponse.jsonFieldEqualsToValue(\"field\", \"value\"));\n  }\n\n  @Test\n  public void jsonFieldEqualsToValue_whenNonJsonResponseBody_returnsEmptyOptional() {\n    HttpResponse httpResponse =\n        HttpResponse.builder()\n            .setStatus(HttpStatus.OK)\n            .setHeaders(HttpHeaders.builder().build())\n            .setBodyBytes(ByteString.copyFromUtf8(\"not a json\"))\n            .setResponseUrl(TEST_URL)\n            .build();\n\n    assertThat(httpResponse.bodyJson()).isEmpty();\n  }\n\n  @Test\n  public void jsonFieldEqualsToValue_whenJsonFieldContainsValue_returnsTrue() {\n    HttpResponse httpResponse =\n        HttpResponse.builder()\n            .setStatus(HttpStatus.OK)\n            .setHeaders(HttpHeaders.builder().build())\n            .setBodyBytes(ByteString.copyFromUtf8(\"{\\\"field\\\": \\\"value\\\"}\"))\n            .setResponseUrl(TEST_URL)\n            .build();\n\n    assertTrue(httpResponse.jsonFieldEqualsToValue(\"field\", \"value\"));\n  }\n\n  @Test\n  public void bodyJson_whenHttpStatusInvalid_parseSucceeds() {\n    HttpResponse httpResponse =\n        HttpResponse.builder()\n            .setStatus(HttpStatus.HTTP_STATUS_UNSPECIFIED)\n            .setHeaders(HttpHeaders.builder().build())\n            .setResponseUrl(TEST_URL)\n            .build();\n\n    assertFalse(httpResponse.status().isSuccess());\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/net/http/OkHttpHttpClientTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.http;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.base.Strings.nullToEmpty;\nimport static com.google.common.net.HttpHeaders.ACCEPT;\nimport static com.google.common.net.HttpHeaders.CONTENT_LENGTH;\nimport static com.google.common.net.HttpHeaders.CONTENT_TYPE;\nimport static com.google.common.net.HttpHeaders.HOST;\nimport static com.google.common.net.HttpHeaders.LOCATION;\nimport static com.google.common.net.HttpHeaders.USER_AGENT;\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.tsunami.common.net.http.HttpRequest.get;\nimport static com.google.tsunami.common.net.http.HttpRequest.head;\nimport static com.google.tsunami.common.net.http.HttpRequest.post;\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.net.MediaType;\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Guice;\nimport com.google.protobuf.ByteString;\nimport com.google.tsunami.common.data.NetworkEndpointUtils;\nimport com.google.tsunami.proto.NetworkService;\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.net.URL;\nimport java.security.GeneralSecurityException;\nimport java.security.KeyStore;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutionException;\nimport javax.inject.Inject;\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLException;\nimport javax.net.ssl.SSLSocketFactory;\nimport okhttp3.HttpUrl;\nimport okhttp3.mockwebserver.Dispatcher;\nimport okhttp3.mockwebserver.MockResponse;\nimport okhttp3.mockwebserver.MockWebServer;\nimport okhttp3.mockwebserver.RecordedRequest;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link OkHttpHttpClient}. */\n@RunWith(JUnit4.class)\npublic final class OkHttpHttpClientTest {\n  private static final String TESTING_KEYSTORE = \"testdata/tsunami_test_server.p12\";\n  private static final char[] TESTING_KEYSTORE_PASSWORD = \"tsunamitest\".toCharArray();\n\n  private MockWebServer mockWebServer;\n  @Inject private HttpClient httpClient;\n\n  @Before\n  public void setUp() {\n    mockWebServer = new MockWebServer();\n    Guice.createInjector(new HttpClientModule.Builder().build()).injectMembers(this);\n  }\n\n  @After\n  public void tearDown() throws IOException {\n    mockWebServer.shutdown();\n  }\n\n  @Test\n  public void sendAsIs_always_returnsExpectedHttpResponse()\n      throws IOException, InterruptedException {\n    mockWebServer.setDispatcher(new SendAsIsTestDispatcher());\n    mockWebServer.start();\n    String expectedResponseBody = SendAsIsTestDispatcher.buildBody(\"GET\", \"\");\n\n    HttpUrl baseUrl = mockWebServer.url(\"/\");\n    String requestUrl =\n        new URL(\n                baseUrl.scheme(),\n                baseUrl.host(),\n                baseUrl.port(),\n                \"/send-as-is/%2e%2e/%2e%2e/etc/passwd\")\n            .toString();\n\n    HttpResponse response = httpClient.sendAsIs(get(requestUrl).withEmptyHeaders().build());\n\n    assertThat(mockWebServer.takeRequest().getPath())\n        .isEqualTo(\"/send-as-is/%2e%2e/%2e%2e/etc/passwd\");\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n                        // MockWebServer always adds this response header.\n                        .addHeader(CONTENT_LENGTH, String.valueOf(expectedResponseBody.length()))\n                        .build())\n                .setBodyBytes(ByteString.copyFrom(expectedResponseBody, UTF_8))\n                .build());\n  }\n\n  @Test\n  public void sendAsIs_withPostRequest_returnsExpectedHttpResponse()\n      throws IOException, InterruptedException {\n    mockWebServer.setDispatcher(new SendAsIsTestDispatcher());\n    mockWebServer.start();\n    String requestBody = \"POST BODY\";\n    String expectedResponseBody = SendAsIsTestDispatcher.buildBody(\"POST\", requestBody);\n\n    HttpUrl baseUrl = mockWebServer.url(\"/\");\n    String requestUrl =\n        new URL(baseUrl.scheme(), baseUrl.host(), baseUrl.port(), \"/send-as-is/%2e%2e/%2e%2e/path\")\n            .toString();\n\n    HttpResponse response =\n        httpClient.sendAsIs(\n            post(requestUrl)\n                .setRequestBody(ByteString.copyFrom(requestBody, UTF_8))\n                .withEmptyHeaders()\n                .build());\n\n    assertThat(mockWebServer.takeRequest().getPath()).isEqualTo(\"/send-as-is/%2e%2e/%2e%2e/path\");\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n                        // MockWebServer always adds this response header.\n                        .addHeader(CONTENT_LENGTH, String.valueOf(expectedResponseBody.length()))\n                        .build())\n                .setBodyBytes(ByteString.copyFrom(expectedResponseBody, UTF_8))\n                .build());\n  }\n\n  @Test\n  public void send_always_canonicalizesRequestUrl() throws IOException, InterruptedException {\n    String responseBody = \"test response\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    HttpUrl baseUrl = mockWebServer.url(\"/\");\n    String requestUrl =\n        new URL(baseUrl.scheme(), baseUrl.host(), baseUrl.port(), \"/%2e%2e/%2e%2e/etc/passwd\")\n            .toString();\n\n    httpClient.send(get(requestUrl).withEmptyHeaders().build());\n\n    assertThat(mockWebServer.takeRequest().getPath()).isEqualTo(\"/etc/passwd\");\n  }\n\n  @Test\n  public void send_whenGetRequest_returnsExpectedHttpResponse() throws IOException {\n    String responseBody = \"test response\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    String requestUrl = mockWebServer.url(\"/test/get\").toString();\n\n    HttpResponse response = httpClient.send(get(requestUrl).withEmptyHeaders().build());\n\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n                        // MockWebServer always adds this response header.\n                        .addHeader(CONTENT_LENGTH, String.valueOf(responseBody.length()))\n                        .build())\n                .setBodyBytes(ByteString.copyFrom(responseBody, UTF_8))\n                .setResponseUrl(HttpUrl.parse(requestUrl))\n                .build());\n  }\n\n  @Test\n  public void sendAsync_whenGetRequest_returnsExpectedHttpResponse()\n      throws IOException, ExecutionException, InterruptedException {\n    String responseBody = \"test response\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    String requestUrl = mockWebServer.url(\"/test/get\").toString();\n\n    HttpResponse response = httpClient.sendAsync(get(requestUrl).withEmptyHeaders().build()).get();\n\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n                        // MockWebServer always adds this response header.\n                        .addHeader(CONTENT_LENGTH, String.valueOf(responseBody.length()))\n                        .build())\n                .setBodyBytes(ByteString.copyFrom(responseBody, UTF_8))\n                .setResponseUrl(HttpUrl.parse(requestUrl))\n                .build());\n  }\n\n  @Test\n  public void send_whenHeadRequest_returnsHttpResponseWithoutBody() throws IOException {\n    String responseBody = \"test response\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    String requestUrl = mockWebServer.url(\"/test/head\").toString();\n\n    HttpResponse response = httpClient.send(head(requestUrl).withEmptyHeaders().build());\n\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n                        // MockWebServer always adds this response header.\n                        .addHeader(CONTENT_LENGTH, String.valueOf(responseBody.length()))\n                        .build())\n                .setBodyBytes(Optional.empty())\n                .setResponseUrl(HttpUrl.parse(requestUrl))\n                .build());\n  }\n\n  @Test\n  public void sendAsync_whenHeadRequest_returnsHttpResponseWithoutBody()\n      throws IOException, ExecutionException, InterruptedException {\n    String responseBody = \"test response\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    String requestUrl = mockWebServer.url(\"/test/head\").toString();\n\n    HttpResponse response = httpClient.sendAsync(head(requestUrl).withEmptyHeaders().build()).get();\n\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n                        // MockWebServer always adds this response header.\n                        .addHeader(CONTENT_LENGTH, String.valueOf(responseBody.length()))\n                        .build())\n                .setBodyBytes(Optional.empty())\n                .setResponseUrl(HttpUrl.parse(requestUrl))\n                .build());\n  }\n\n  @Test\n  public void send_whenPostRequest_returnsExpectedHttpResponse() throws IOException {\n    String responseBody = \"{ \\\"test\\\": \\\"json\\\" }\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    String requestUrl = mockWebServer.url(\"/test/post\").toString();\n\n    HttpResponse response =\n        httpClient.send(\n            post(requestUrl)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(ACCEPT, MediaType.JSON_UTF_8.toString())\n                        .build())\n                .build());\n\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString())\n                        // MockWebServer always adds this response header.\n                        .addHeader(CONTENT_LENGTH, String.valueOf(responseBody.length()))\n                        .build())\n                .setBodyBytes(ByteString.copyFrom(responseBody, UTF_8))\n                .setResponseUrl(HttpUrl.parse(requestUrl))\n                .build());\n  }\n\n  @Test\n  public void sendAsync_whenPostRequest_returnsExpectedHttpResponse()\n      throws IOException, ExecutionException, InterruptedException {\n    String responseBody = \"{ \\\"test\\\": \\\"json\\\" }\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    String requestUrl = mockWebServer.url(\"/test/post\").toString();\n\n    HttpResponse response =\n        httpClient\n            .sendAsync(\n                post(requestUrl)\n                    .setHeaders(\n                        HttpHeaders.builder()\n                            .addHeader(ACCEPT, MediaType.JSON_UTF_8.toString())\n                            .build())\n                    .build())\n            .get();\n\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString())\n                        // MockWebServer always adds this response header.\n                        .addHeader(CONTENT_LENGTH, String.valueOf(responseBody.length()))\n                        .build())\n                .setBodyBytes(ByteString.copyFrom(responseBody, UTF_8))\n                .setResponseUrl(HttpUrl.parse(requestUrl))\n                .build());\n  }\n\n  @Test\n  public void send_whenPostRequestWithEmptyHeaders_returnsExpectedHttpResponse()\n      throws IOException {\n    String responseBody = \"{ \\\"test\\\": \\\"json\\\" }\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    String requestUrl = mockWebServer.url(\"/test/post\").toString();\n\n    HttpResponse response = httpClient.send(post(requestUrl).withEmptyHeaders().build());\n\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString())\n                        // MockWebServer always adds this response header.\n                        .addHeader(CONTENT_LENGTH, String.valueOf(responseBody.length()))\n                        .build())\n                .setBodyBytes(ByteString.copyFrom(responseBody, UTF_8))\n                .setResponseUrl(HttpUrl.parse(requestUrl))\n                .build());\n  }\n\n  @Test\n  public void sendAsync_whenPostRequestWithEmptyHeaders_returnsExpectedHttpResponse()\n      throws IOException, ExecutionException, InterruptedException {\n    String responseBody = \"{ \\\"test\\\": \\\"json\\\" }\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    String requestUrl = mockWebServer.url(\"/test/post\").toString();\n\n    HttpResponse response = httpClient.sendAsync(post(requestUrl).withEmptyHeaders().build()).get();\n\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString())\n                        // MockWebServer always adds this response header.\n                        .addHeader(CONTENT_LENGTH, String.valueOf(responseBody.length()))\n                        .build())\n                .setBodyBytes(ByteString.copyFrom(responseBody, UTF_8))\n                .setResponseUrl(HttpUrl.parse(requestUrl))\n                .build());\n  }\n\n  @Test\n  public void send_whenFollowRedirect_returnsFinalHttpResponse() throws IOException {\n    String responseBody = \"test response\";\n    mockWebServer.setDispatcher(new RedirectDispatcher(responseBody));\n    mockWebServer.start();\n\n    HttpResponse response =\n        httpClient\n            .modify()\n            .setFollowRedirects(true)\n            .build()\n            .send(\n                get(mockWebServer.url(RedirectDispatcher.REDIRECT_PATH).toString())\n                    .withEmptyHeaders()\n                    .build());\n\n    HttpUrl redirectDestinationUrl =\n        HttpUrl.parse(mockWebServer.url(RedirectDispatcher.REDIRECT_DESTINATION_PATH).toString());\n\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_LENGTH, String.valueOf(responseBody.length()))\n                        .build())\n                .setBodyBytes(ByteString.copyFrom(responseBody, UTF_8))\n                .setResponseUrl(redirectDestinationUrl)\n                .build());\n  }\n\n  @Test\n  public void sendAsync_whenFollowRedirect_returnsFinalHttpResponse()\n      throws IOException, ExecutionException, InterruptedException {\n    String responseBody = \"test response\";\n    mockWebServer.setDispatcher(new RedirectDispatcher(responseBody));\n    mockWebServer.start();\n\n    HttpUrl redirectDestinationUrl =\n        HttpUrl.parse(mockWebServer.url(RedirectDispatcher.REDIRECT_DESTINATION_PATH).toString());\n\n    HttpResponse response =\n        httpClient\n            .modify()\n            .setFollowRedirects(true)\n            .build()\n            .sendAsync(\n                get(mockWebServer.url(RedirectDispatcher.REDIRECT_PATH).toString())\n                    .withEmptyHeaders()\n                    .build())\n            .get();\n\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.OK)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_LENGTH, String.valueOf(responseBody.length()))\n                        .build())\n                .setBodyBytes(ByteString.copyFrom(responseBody, UTF_8))\n                .setResponseUrl(redirectDestinationUrl)\n                .build());\n  }\n\n  @Test\n  public void send_whenNotFollowRedirect_returnsFinalHttpResponse() throws IOException {\n    String responseBody = \"test response\";\n    mockWebServer.setDispatcher(new RedirectDispatcher(responseBody));\n    mockWebServer.start();\n\n    String redirectingUrl = mockWebServer.url(RedirectDispatcher.REDIRECT_PATH).toString();\n\n    HttpResponse response =\n        httpClient\n            .modify()\n            .setFollowRedirects(false)\n            .build()\n            .send(get(redirectingUrl).withEmptyHeaders().build());\n\n    assertThat(response.status()).isEqualTo(HttpStatus.FOUND);\n    assertThat(response.headers())\n        .isEqualTo(\n            HttpHeaders.builder()\n                .addHeader(CONTENT_LENGTH, \"0\")\n                .addHeader(LOCATION, RedirectDispatcher.REDIRECT_DESTINATION_PATH)\n                .build());\n    assertThat(response.bodyString()).hasValue(\"\");\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.FOUND)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_LENGTH, \"0\")\n                        .addHeader(LOCATION, RedirectDispatcher.REDIRECT_DESTINATION_PATH)\n                        .build())\n                .setBodyBytes(ByteString.EMPTY)\n                .setResponseUrl(HttpUrl.parse(redirectingUrl))\n                .build());\n  }\n\n  @Test\n  public void sendAsync_whenNotFollowRedirect_returnsFinalHttpResponse()\n      throws IOException, ExecutionException, InterruptedException {\n    String responseBody = \"test response\";\n    mockWebServer.setDispatcher(new RedirectDispatcher(responseBody));\n    mockWebServer.start();\n\n    String redirectingUrl = mockWebServer.url(RedirectDispatcher.REDIRECT_PATH).toString();\n\n    HttpResponse response =\n        httpClient\n            .modify()\n            .setFollowRedirects(false)\n            .build()\n            .sendAsync(get(redirectingUrl).withEmptyHeaders().build())\n            .get();\n\n    assertThat(response.status()).isEqualTo(HttpStatus.FOUND);\n    assertThat(response.headers())\n        .isEqualTo(\n            HttpHeaders.builder()\n                .addHeader(CONTENT_LENGTH, \"0\")\n                .addHeader(LOCATION, RedirectDispatcher.REDIRECT_DESTINATION_PATH)\n                .build());\n    assertThat(response.bodyString()).hasValue(\"\");\n    assertThat(response)\n        .isEqualTo(\n            HttpResponse.builder()\n                .setStatus(HttpStatus.FOUND)\n                .setHeaders(\n                    HttpHeaders.builder()\n                        .addHeader(CONTENT_LENGTH, \"0\")\n                        .addHeader(LOCATION, RedirectDispatcher.REDIRECT_DESTINATION_PATH)\n                        .build())\n                .setBodyBytes(ByteString.EMPTY)\n                .setResponseUrl(HttpUrl.parse(redirectingUrl))\n                .build());\n  }\n\n  @Test\n  public void send_whenNoUserAgentInRequest_setsCorrectUserAgentHeader() throws IOException {\n    mockWebServer.setDispatcher(new UserAgentTestDispatcher());\n    mockWebServer.start();\n\n    HttpResponse response =\n        httpClient.send(\n            get(mockWebServer.url(UserAgentTestDispatcher.USERAGENT_TEST_PATH).toString())\n                .withEmptyHeaders()\n                .build());\n\n    assertThat(response.status()).isEqualTo(HttpStatus.OK);\n  }\n\n  @Test\n  public void send_whenUserAgentSetInRequest_overridesUserAgentHeader() throws IOException {\n    mockWebServer.setDispatcher(new UserAgentTestDispatcher());\n    mockWebServer.start();\n\n    HttpResponse response =\n        httpClient.send(\n            get(mockWebServer.url(UserAgentTestDispatcher.USERAGENT_TEST_PATH).toString())\n                .setHeaders(\n                    HttpHeaders.builder().addHeader(USER_AGENT, \"User Agent In Request\").build())\n                .build());\n\n    assertThat(response.status()).isEqualTo(HttpStatus.OK);\n  }\n\n  @Test\n  public void send_whenRequestFailed_throwsException() {\n    assertThrows(\n        IOException.class,\n        () -> httpClient.send(get(\"http://unknownhost/path\").withEmptyHeaders().build()));\n  }\n\n  @Test\n  public void sendAsync_whenRequestFailed_returnsFutureWithException() {\n    ListenableFuture<HttpResponse> responseFuture =\n        httpClient.sendAsync(get(\"http://unknownhost/path\").withEmptyHeaders().build());\n\n    ExecutionException ex = assertThrows(ExecutionException.class, responseFuture::get);\n    assertThat(ex).hasCauseThat().isInstanceOf(IOException.class);\n  }\n\n  @Test\n  public void send_whenHostnameAndIpInRequest_useHostnameAsProxy() throws IOException {\n    InetAddress loopbackAddress = InetAddress.getLoopbackAddress();\n    String host = \"host.com\";\n    mockWebServer.setDispatcher(new HostnameTestDispatcher(host));\n    mockWebServer.start(loopbackAddress, 0);\n    int port = mockWebServer.url(\"/\").port();\n\n    NetworkService networkService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(\n                NetworkEndpointUtils.forIpHostnameAndPort(\n                    loopbackAddress.getHostAddress(), host, port))\n            .build();\n\n    // The request to host.com should be sent through mockWebServer's IP.\n    HttpResponse response =\n        httpClient.send(\n            get(String.format(\"http://host.com:%d/test/get\", port)).withEmptyHeaders().build(),\n            networkService);\n\n    assertThat(response.status()).isEqualTo(HttpStatus.OK);\n  }\n\n  @Test\n  public void send_whenInvalidCertificatesAreIgnored_getResponseWithoutException()\n      throws GeneralSecurityException, IOException {\n    InetAddress loopbackAddress = InetAddress.getLoopbackAddress();\n    String host = \"host.com\";\n    MockWebServer mockWebServer = startMockWebServerWithSsl(loopbackAddress);\n    int port = mockWebServer.url(\"/\").port();\n    NetworkService networkService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(\n                NetworkEndpointUtils.forIpHostnameAndPort(\n                    loopbackAddress.getHostAddress(), host, port))\n            .build();\n\n    HttpClientCliOptions cliOptions = new HttpClientCliOptions();\n    HttpClientConfigProperties configProperties = new HttpClientConfigProperties();\n    cliOptions.trustAllCertificates = configProperties.trustAllCertificates = true;\n    HttpClient httpClient =\n        Guice.createInjector(\n                new AbstractModule() {\n                  @Override\n                  protected void configure() {\n                    install(new HttpClientModule.Builder().build());\n                    bind(HttpClientCliOptions.class).toInstance(cliOptions);\n                    bind(HttpClientConfigProperties.class).toInstance(configProperties);\n                  }\n                })\n            .getInstance(HttpClient.class);\n\n    HttpResponse response =\n        httpClient.send(\n            get(String.format(\"https://%s:%d\", host, port)).withEmptyHeaders().build(),\n            networkService);\n    assertThat(response.bodyString()).hasValue(\"body\");\n\n    mockWebServer.shutdown();\n  }\n\n  @Test\n  public void send_whenInvalidCertificatesAreNotIgnored_throws()\n      throws GeneralSecurityException, IOException {\n    InetAddress loopbackAddress = InetAddress.getLoopbackAddress();\n    String host = \"host.com\";\n    MockWebServer mockWebServer = startMockWebServerWithSsl(loopbackAddress);\n    int port = mockWebServer.url(\"/\").port();\n    NetworkService networkService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(\n                NetworkEndpointUtils.forIpHostnameAndPort(\n                    loopbackAddress.getHostAddress(), host, port))\n            .build();\n\n    HttpClientCliOptions cliOptions = new HttpClientCliOptions();\n    HttpClientConfigProperties configProperties = new HttpClientConfigProperties();\n    cliOptions.trustAllCertificates = configProperties.trustAllCertificates = false;\n    HttpClient httpClient =\n        Guice.createInjector(\n                new AbstractModule() {\n                  @Override\n                  protected void configure() {\n                    install(new HttpClientModule.Builder().build());\n                    bind(HttpClientCliOptions.class).toInstance(cliOptions);\n                    bind(HttpClientConfigProperties.class).toInstance(configProperties);\n                  }\n                })\n            .getInstance(HttpClient.class);\n\n    assertThrows(\n        SSLException.class,\n        () ->\n            httpClient.send(\n                get(String.format(\"https://%s:%d\", host, port)).withEmptyHeaders().build(),\n                networkService));\n\n    mockWebServer.shutdown();\n  }\n\n  @Test\n  public void send_default_userAgent() throws IOException, InterruptedException {\n    String responseBody = \"test response\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    HttpUrl baseUrl = mockWebServer.url(\"/\");\n    httpClient.send(get(baseUrl.toString()).withEmptyHeaders().build());\n\n    assertThat(mockWebServer.takeRequest().getHeader(USER_AGENT))\n        .isEqualTo(HttpClient.TSUNAMI_USER_AGENT);\n  }\n\n  @Test\n  public void send_overridden_userAgent() throws IOException, InterruptedException {\n    String responseBody = \"test response\";\n    mockWebServer.enqueue(\n        new MockResponse()\n            .setResponseCode(HttpStatus.OK.code())\n            .setHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n            .setBody(responseBody));\n    mockWebServer.start();\n\n    final String userAgentOverride = \"User Agent In Override\";\n\n    HttpClientCliOptions cliOptions = new HttpClientCliOptions();\n    cliOptions.userAgent = userAgentOverride;\n    HttpClientConfigProperties configProperties = new HttpClientConfigProperties();\n    cliOptions.trustAllCertificates = configProperties.trustAllCertificates = true;\n    HttpClient httpClient =\n        Guice.createInjector(\n                new AbstractModule() {\n                  @Override\n                  protected void configure() {\n                    install(new HttpClientModule.Builder().build());\n                    bind(HttpClientCliOptions.class).toInstance(cliOptions);\n                    bind(HttpClientConfigProperties.class).toInstance(configProperties);\n                  }\n                })\n            .getInstance(HttpClient.class);\n\n    HttpUrl baseUrl = mockWebServer.url(\"/\");\n    httpClient.send(get(baseUrl.toString()).withEmptyHeaders().build());\n\n    assertThat(mockWebServer.takeRequest().getHeader(USER_AGENT)).isEqualTo(userAgentOverride);\n  }\n\n  private MockWebServer startMockWebServerWithSsl(InetAddress serverAddress)\n      throws GeneralSecurityException, IOException {\n    MockWebServer mockWebServer = new MockWebServer();\n    mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(\"body\"));\n    mockWebServer.useHttps(getTestingSslSocketFactory(), false);\n    mockWebServer.start(serverAddress, 0);\n    return mockWebServer;\n  }\n\n  private SSLSocketFactory getTestingSslSocketFactory()\n      throws GeneralSecurityException, IOException {\n    final KeyManagerFactory keyManagerFactory =\n        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());\n    KeyStore keyStore = KeyStore.getInstance(\"PKCS12\");\n    keyStore.load(getClass().getResourceAsStream(TESTING_KEYSTORE), TESTING_KEYSTORE_PASSWORD);\n    keyManagerFactory.init(keyStore, TESTING_KEYSTORE_PASSWORD);\n    SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n    sslContext.init(keyManagerFactory.getKeyManagers(), null, null);\n    return sslContext.getSocketFactory();\n  }\n\n  static final class RedirectDispatcher extends Dispatcher {\n    static final String REDIRECT_PATH = \"/redirect\";\n    static final String REDIRECT_DESTINATION_PATH = \"/redirect-dest\";\n\n    private final String responseBody;\n\n    RedirectDispatcher(String responseBody) {\n      this.responseBody = checkNotNull(responseBody);\n    }\n\n    @Override\n    public MockResponse dispatch(RecordedRequest recordedRequest) {\n      switch (recordedRequest.getPath()) {\n        case REDIRECT_PATH:\n          return new MockResponse()\n              .setResponseCode(HttpStatus.FOUND.code())\n              .setHeader(LOCATION, \"/redirect-dest\");\n        case REDIRECT_DESTINATION_PATH:\n          return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(responseBody);\n        default:\n          return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code());\n      }\n    }\n  }\n\n  static final class UserAgentTestDispatcher extends Dispatcher {\n    static final String USERAGENT_TEST_PATH = \"/useragent-test\";\n\n    @Override\n    public MockResponse dispatch(RecordedRequest recordedRequest) {\n      if (recordedRequest.getPath().equals(USERAGENT_TEST_PATH)\n          && nullToEmpty(recordedRequest.getHeader(USER_AGENT)).equals(\"TsunamiSecurityScanner\")) {\n        return new MockResponse().setResponseCode(HttpStatus.OK.code());\n      }\n      return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code());\n    }\n  }\n\n  static final class HostnameTestDispatcher extends Dispatcher {\n    private final String expectedHost;\n\n    HostnameTestDispatcher(String expectedHost) {\n      this.expectedHost = checkNotNull(expectedHost);\n    }\n\n    @Override\n    public MockResponse dispatch(RecordedRequest recordedRequest) {\n      if (nullToEmpty(recordedRequest.getHeader(HOST)).startsWith(expectedHost)) {\n        return new MockResponse().setResponseCode(HttpStatus.OK.code());\n      }\n      return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code());\n    }\n  }\n\n  static final class SendAsIsTestDispatcher extends Dispatcher {\n    static final String SEND_AS_IS_PATH = \"/send-as-is/\";\n\n    static String buildBody(String method, String requestBody) {\n      return String.format(\"Method: %s\\nRequest Body: %s\", method, requestBody);\n    }\n\n    @Override\n    public MockResponse dispatch(RecordedRequest recordedRequest) {\n      if (recordedRequest.getPath().startsWith(SEND_AS_IS_PATH)) {\n        return new MockResponse()\n            .setHeader(CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())\n            .setBody(buildBody(recordedRequest.getMethod(), recordedRequest.getBody().readUtf8()))\n            .setResponseCode(HttpStatus.OK.code());\n      }\n      return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code());\n    }\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/net/socket/DefaultTsunamiSocketFactoryTest.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.socket;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.time.Duration;\nimport javax.net.SocketFactory;\nimport javax.net.ssl.SSLSocket;\nimport javax.net.ssl.SSLSocketFactory;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.ArgumentCaptor;\n\n/** Unit tests for {@link DefaultTsunamiSocketFactory}. */\n@RunWith(JUnit4.class)\npublic final class DefaultTsunamiSocketFactoryTest {\n\n  private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(10);\n  private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofSeconds(30);\n\n  private SocketFactory mockSocketFactory;\n  private SSLSocketFactory mockSslSocketFactory;\n  private Socket mockSocket;\n  private SSLSocket mockSslSocket;\n  private DefaultTsunamiSocketFactory tsunamiSocketFactory;\n\n  @Before\n  public void setUp() throws IOException {\n    mockSocketFactory = mock(SocketFactory.class);\n    mockSslSocketFactory = mock(SSLSocketFactory.class);\n    mockSocket = mock(Socket.class);\n    mockSslSocket = mock(SSLSocket.class);\n\n    when(mockSocketFactory.createSocket()).thenReturn(mockSocket);\n    when(mockSslSocketFactory.createSocket(any(Socket.class), anyString(), anyInt(), anyBoolean()))\n        .thenReturn(mockSslSocket);\n\n    tsunamiSocketFactory =\n        new DefaultTsunamiSocketFactory(\n            mockSocketFactory, mockSslSocketFactory, DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT);\n  }\n\n  @Test\n  public void constructor_withNullSocketFactory_throwsException() {\n    assertThrows(\n        NullPointerException.class,\n        () ->\n            new DefaultTsunamiSocketFactory(\n                null, mockSslSocketFactory, DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT));\n  }\n\n  @Test\n  public void constructor_withNullSslSocketFactory_throwsException() {\n    assertThrows(\n        NullPointerException.class,\n        () ->\n            new DefaultTsunamiSocketFactory(\n                mockSocketFactory, null, DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT));\n  }\n\n  @Test\n  public void constructor_withNegativeConnectTimeout_throwsException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () ->\n            new DefaultTsunamiSocketFactory(\n                mockSocketFactory,\n                mockSslSocketFactory,\n                Duration.ofSeconds(-1),\n                DEFAULT_READ_TIMEOUT));\n  }\n\n  @Test\n  public void constructor_withNegativeReadTimeout_throwsException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () ->\n            new DefaultTsunamiSocketFactory(\n                mockSocketFactory,\n                mockSslSocketFactory,\n                DEFAULT_CONNECT_TIMEOUT,\n                Duration.ofSeconds(-1)));\n  }\n\n  @Test\n  public void createSocket_withHostAndPort_setsTimeoutsAndConnects() throws IOException {\n    var unused = tsunamiSocketFactory.createSocket(\"example.com\", 80);\n\n    verify(mockSocket).setSoTimeout((int) DEFAULT_READ_TIMEOUT.toMillis());\n    verify(mockSocket).setKeepAlive(true);\n    verify(mockSocket).setTcpNoDelay(true);\n\n    ArgumentCaptor<InetSocketAddress> addressCaptor =\n        ArgumentCaptor.forClass(InetSocketAddress.class);\n    ArgumentCaptor<Integer> timeoutCaptor = ArgumentCaptor.forClass(Integer.class);\n    verify(mockSocket).connect(addressCaptor.capture(), timeoutCaptor.capture());\n\n    assertThat(addressCaptor.getValue().getHostString()).isEqualTo(\"example.com\");\n    assertThat(addressCaptor.getValue().getPort()).isEqualTo(80);\n    assertThat(timeoutCaptor.getValue()).isEqualTo((int) DEFAULT_CONNECT_TIMEOUT.toMillis());\n  }\n\n  @Test\n  public void createSocket_withCustomTimeouts_usesProvidedValues() throws IOException {\n    Duration customConnect = Duration.ofSeconds(5);\n    Duration customRead = Duration.ofSeconds(15);\n\n    var unused = tsunamiSocketFactory.createSocket(\"example.com\", 443, customConnect, customRead);\n\n    verify(mockSocket).setSoTimeout((int) customRead.toMillis());\n\n    ArgumentCaptor<Integer> timeoutCaptor = ArgumentCaptor.forClass(Integer.class);\n    verify(mockSocket).connect(any(), timeoutCaptor.capture());\n    assertThat(timeoutCaptor.getValue()).isEqualTo((int) customConnect.toMillis());\n  }\n\n  @Test\n  public void createSocket_withInetAddress_setsTimeoutsAndConnects() throws IOException {\n    InetAddress address = InetAddress.getLoopbackAddress();\n\n    var unused = tsunamiSocketFactory.createSocket(address, 8080);\n\n    verify(mockSocket).setSoTimeout((int) DEFAULT_READ_TIMEOUT.toMillis());\n    verify(mockSocket).setKeepAlive(true);\n    verify(mockSocket).setTcpNoDelay(true);\n\n    ArgumentCaptor<InetSocketAddress> addressCaptor =\n        ArgumentCaptor.forClass(InetSocketAddress.class);\n    verify(mockSocket).connect(addressCaptor.capture(), anyInt());\n\n    assertThat(addressCaptor.getValue().getAddress()).isEqualTo(address);\n    assertThat(addressCaptor.getValue().getPort()).isEqualTo(8080);\n  }\n\n  @Test\n  public void createSocket_withInvalidPort_throwsException() {\n    assertThrows(\n        IllegalArgumentException.class, () -> tsunamiSocketFactory.createSocket(\"example.com\", 0));\n\n    assertThrows(\n        IllegalArgumentException.class, () -> tsunamiSocketFactory.createSocket(\"example.com\", -1));\n\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> tsunamiSocketFactory.createSocket(\"example.com\", 65536));\n  }\n\n  @Test\n  public void createSocket_withNullHost_throwsException() {\n    assertThrows(\n        NullPointerException.class, () -> tsunamiSocketFactory.createSocket((String) null, 80));\n  }\n\n  @Test\n  public void createUnconnectedSocket_setsReadTimeout() throws IOException {\n    Socket socket = tsunamiSocketFactory.createUnconnectedSocket();\n\n    assertThat(socket).isEqualTo(mockSocket);\n    verify(mockSocket).setSoTimeout((int) DEFAULT_READ_TIMEOUT.toMillis());\n  }\n\n  @Test\n  public void createSslSocket_withHostAndPort_createsAndConfigures() throws IOException {\n    var unused = tsunamiSocketFactory.createSslSocket(\"secure.example.com\", 443);\n\n    // Verify plain socket is created and connected first\n    verify(mockSocketFactory).createSocket();\n    verify(mockSocket).connect(any(InetSocketAddress.class), anyInt());\n\n    // Verify SSL wrapping\n    verify(mockSslSocketFactory).createSocket(mockSocket, \"secure.example.com\", 443, true);\n    verify(mockSslSocket).setSoTimeout((int) DEFAULT_READ_TIMEOUT.toMillis());\n    verify(mockSslSocket).startHandshake();\n  }\n\n  @Test\n  public void createSslSocket_withCustomTimeouts_usesProvidedValues() throws IOException {\n    Duration customConnect = Duration.ofSeconds(5);\n    Duration customRead = Duration.ofSeconds(15);\n\n    var unused =\n        tsunamiSocketFactory.createSslSocket(\"secure.example.com\", 443, customConnect, customRead);\n\n    verify(mockSocket).setSoTimeout((int) customRead.toMillis());\n    verify(mockSslSocket).setSoTimeout((int) customRead.toMillis());\n\n    ArgumentCaptor<Integer> connectTimeoutCaptor = ArgumentCaptor.forClass(Integer.class);\n    verify(mockSocket).connect(any(), connectTimeoutCaptor.capture());\n    assertThat(connectTimeoutCaptor.getValue()).isEqualTo((int) customConnect.toMillis());\n  }\n\n  @Test\n  public void createSslSocket_withInetAddress_createsAndConfigures() throws IOException {\n    InetAddress address = InetAddress.getLoopbackAddress();\n\n    var unused = tsunamiSocketFactory.createSslSocket(address, 443);\n\n    verify(mockSocketFactory).createSocket();\n    verify(mockSocket).connect(any(InetSocketAddress.class), anyInt());\n    verify(mockSslSocketFactory).createSocket(mockSocket, address.getHostAddress(), 443, true);\n    verify(mockSslSocket).startHandshake();\n  }\n\n  @Test\n  public void wrapWithSsl_wrapsExistingSocket() throws IOException {\n    when(mockSocket.getSoTimeout()).thenReturn(5000);\n\n    var unused = tsunamiSocketFactory.wrapWithSsl(mockSocket, \"example.com\", 443, true);\n\n    verify(mockSslSocketFactory).createSocket(mockSocket, \"example.com\", 443, true);\n    verify(mockSslSocket).setSoTimeout(5000);\n    verify(mockSslSocket).startHandshake();\n  }\n\n  @Test\n  public void wrapWithSsl_withZeroOriginalTimeout_usesDefault() throws IOException {\n    when(mockSocket.getSoTimeout()).thenReturn(0);\n\n    var unused = tsunamiSocketFactory.wrapWithSsl(mockSocket, \"example.com\", 443, true);\n\n    verify(mockSslSocket).setSoTimeout((int) DEFAULT_READ_TIMEOUT.toMillis());\n  }\n\n  @Test\n  public void wrapWithSsl_withNullSocket_throwsException() {\n    assertThrows(\n        NullPointerException.class,\n        () -> tsunamiSocketFactory.wrapWithSsl(null, \"example.com\", 443, true));\n  }\n\n  @Test\n  public void wrapWithSsl_withInvalidPort_throwsException() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> tsunamiSocketFactory.wrapWithSsl(mockSocket, \"example.com\", 0, true));\n  }\n\n  @Test\n  public void getDefaultConnectTimeout_returnsConfiguredValue() {\n    assertThat(tsunamiSocketFactory.getDefaultConnectTimeout()).isEqualTo(DEFAULT_CONNECT_TIMEOUT);\n  }\n\n  @Test\n  public void getDefaultReadTimeout_returnsConfiguredValue() {\n    assertThat(tsunamiSocketFactory.getDefaultReadTimeout()).isEqualTo(DEFAULT_READ_TIMEOUT);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/net/socket/TsunamiSocketFactoryCliOptionsTest.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.socket;\n\nimport static org.junit.Assert.assertThrows;\n\nimport com.beust.jcommander.ParameterException;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Unit tests for {@link TsunamiSocketFactoryCliOptions}. */\n@RunWith(JUnit4.class)\npublic final class TsunamiSocketFactoryCliOptionsTest {\n\n  @Test\n  public void validate_withNullValues_passes() {\n    TsunamiSocketFactoryCliOptions options = new TsunamiSocketFactoryCliOptions();\n\n    // Should not throw\n    options.validate();\n  }\n\n  @Test\n  public void validate_withPositiveConnectTimeout_passes() {\n    TsunamiSocketFactoryCliOptions options = new TsunamiSocketFactoryCliOptions();\n    options.connectTimeoutSeconds = 10;\n\n    // Should not throw\n    options.validate();\n  }\n\n  @Test\n  public void validate_withPositiveReadTimeout_passes() {\n    TsunamiSocketFactoryCliOptions options = new TsunamiSocketFactoryCliOptions();\n    options.readTimeoutSeconds = 30;\n\n    // Should not throw\n    options.validate();\n  }\n\n  @Test\n  public void validate_withNegativeConnectTimeout_throwsException() {\n    TsunamiSocketFactoryCliOptions options = new TsunamiSocketFactoryCliOptions();\n    options.connectTimeoutSeconds = -1;\n\n    assertThrows(ParameterException.class, options::validate);\n  }\n\n  @Test\n  public void validate_withNegativeReadTimeout_throwsException() {\n    TsunamiSocketFactoryCliOptions options = new TsunamiSocketFactoryCliOptions();\n    options.readTimeoutSeconds = -5;\n\n    assertThrows(ParameterException.class, options::validate);\n  }\n\n  @Test\n  public void validate_withZeroConnectTimeout_throwsException() {\n    TsunamiSocketFactoryCliOptions options = new TsunamiSocketFactoryCliOptions();\n    options.connectTimeoutSeconds = 0;\n\n    assertThrows(ParameterException.class, options::validate);\n  }\n\n  @Test\n  public void validate_withZeroReadTimeout_throwsException() {\n    TsunamiSocketFactoryCliOptions options = new TsunamiSocketFactoryCliOptions();\n    options.readTimeoutSeconds = 0;\n\n    assertThrows(ParameterException.class, options::validate);\n  }\n\n  @Test\n  public void validate_withTrustAllCertificates_passes() {\n    TsunamiSocketFactoryCliOptions options = new TsunamiSocketFactoryCliOptions();\n    options.trustAllCertificates = true;\n\n    // Should not throw\n    options.validate();\n  }\n\n  @Test\n  public void validate_withAllValidOptions_passes() {\n    TsunamiSocketFactoryCliOptions options = new TsunamiSocketFactoryCliOptions();\n    options.connectTimeoutSeconds = 10;\n    options.readTimeoutSeconds = 30;\n    options.trustAllCertificates = false;\n\n    // Should not throw\n    options.validate();\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/net/socket/TsunamiSocketFactoryModuleTest.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.net.socket;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Guice;\nimport com.google.inject.Injector;\nimport com.google.inject.Key;\nimport com.google.tsunami.common.net.socket.TsunamiSocketFactoryModule.ConnectTimeoutSeconds;\nimport com.google.tsunami.common.net.socket.TsunamiSocketFactoryModule.ReadTimeoutSeconds;\nimport com.google.tsunami.common.net.socket.TsunamiSocketFactoryModule.TrustAllCertificates;\nimport java.time.Duration;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Unit tests for {@link TsunamiSocketFactoryModule}. */\n@RunWith(JUnit4.class)\npublic final class TsunamiSocketFactoryModuleTest {\n\n  private TsunamiSocketFactoryCliOptions cliOptions;\n  private TsunamiSocketFactoryConfigProperties configProperties;\n\n  @Before\n  public void setUp() {\n    cliOptions = new TsunamiSocketFactoryCliOptions();\n    configProperties = new TsunamiSocketFactoryConfigProperties();\n  }\n\n  @Test\n  public void provideTsunamiSocketFactory_returnsNonNullFactory() throws Exception {\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    TsunamiSocketFactory factory = injector.getInstance(TsunamiSocketFactory.class);\n\n    assertThat(factory).isNotNull();\n    assertThat(factory).isInstanceOf(DefaultTsunamiSocketFactory.class);\n  }\n\n  @Test\n  public void provideTsunamiSocketFactory_withDefaultConfig_usesDefaultTimeouts() throws Exception {\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    TsunamiSocketFactory factory = injector.getInstance(TsunamiSocketFactory.class);\n\n    assertThat(factory.getDefaultConnectTimeout()).isEqualTo(Duration.ofSeconds(10));\n    assertThat(factory.getDefaultReadTimeout()).isEqualTo(Duration.ofSeconds(30));\n  }\n\n  @Test\n  public void provideConnectTimeoutSeconds_withCliOption_usesCliValue() {\n    cliOptions.connectTimeoutSeconds = 20;\n    configProperties.connectTimeoutSeconds = 15;\n\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    assertThat(injector.getInstance(Key.get(int.class, ConnectTimeoutSeconds.class))).isEqualTo(20);\n  }\n\n  @Test\n  public void provideConnectTimeoutSeconds_withConfigOnly_usesConfigValue() {\n    configProperties.connectTimeoutSeconds = 15;\n\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    assertThat(injector.getInstance(Key.get(int.class, ConnectTimeoutSeconds.class))).isEqualTo(15);\n  }\n\n  @Test\n  public void provideConnectTimeoutSeconds_withNoConfig_usesDefault() {\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    assertThat(injector.getInstance(Key.get(int.class, ConnectTimeoutSeconds.class))).isEqualTo(10);\n  }\n\n  @Test\n  public void provideReadTimeoutSeconds_withCliOption_usesCliValue() {\n    cliOptions.readTimeoutSeconds = 60;\n    configProperties.readTimeoutSeconds = 45;\n\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    assertThat(injector.getInstance(Key.get(int.class, ReadTimeoutSeconds.class))).isEqualTo(60);\n  }\n\n  @Test\n  public void provideReadTimeoutSeconds_withConfigOnly_usesConfigValue() {\n    configProperties.readTimeoutSeconds = 45;\n\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    assertThat(injector.getInstance(Key.get(int.class, ReadTimeoutSeconds.class))).isEqualTo(45);\n  }\n\n  @Test\n  public void provideReadTimeoutSeconds_withNoConfig_usesDefault() {\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    assertThat(injector.getInstance(Key.get(int.class, ReadTimeoutSeconds.class))).isEqualTo(30);\n  }\n\n  @Test\n  public void provideTrustAllCertificates_withCliOptionTrue_returnsTrue() {\n    cliOptions.trustAllCertificates = true;\n    configProperties.trustAllCertificates = false;\n\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    assertThat(injector.getInstance(Key.get(boolean.class, TrustAllCertificates.class))).isTrue();\n  }\n\n  @Test\n  public void provideTrustAllCertificates_withCliOptionFalse_returnsFalse() {\n    cliOptions.trustAllCertificates = false;\n\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    assertThat(injector.getInstance(Key.get(boolean.class, TrustAllCertificates.class))).isFalse();\n  }\n\n  @Test\n  public void provideTrustAllCertificates_withConfigOnly_usesConfigValue() {\n    configProperties.trustAllCertificates = false;\n\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    assertThat(injector.getInstance(Key.get(boolean.class, TrustAllCertificates.class))).isFalse();\n  }\n\n  @Test\n  public void provideTrustAllCertificates_withNoConfig_usesDefaultTrue() {\n    Injector injector = Guice.createInjector(getTestingModule());\n\n    assertThat(injector.getInstance(Key.get(boolean.class, TrustAllCertificates.class))).isTrue();\n  }\n\n  @Test\n  public void provideTsunamiSocketFactory_withCustomConfig_usesCustomValues() throws Exception {\n    cliOptions.connectTimeoutSeconds = 5;\n    cliOptions.readTimeoutSeconds = 15;\n\n    Injector injector = Guice.createInjector(getTestingModule());\n    TsunamiSocketFactory factory = injector.getInstance(TsunamiSocketFactory.class);\n\n    assertThat(factory.getDefaultConnectTimeout()).isEqualTo(Duration.ofSeconds(5));\n    assertThat(factory.getDefaultReadTimeout()).isEqualTo(Duration.ofSeconds(15));\n  }\n\n  private AbstractModule getTestingModule() {\n    return new AbstractModule() {\n      @Override\n      protected void configure() {\n        install(new TsunamiSocketFactoryModule());\n        bind(TsunamiSocketFactoryCliOptions.class).toInstance(cliOptions);\n        bind(TsunamiSocketFactoryConfigProperties.class).toInstance(configProperties);\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/server/CompactRunRequestHelperTest.java",
    "content": "/*\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.server;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.tsunami.proto.Hostname;\nimport com.google.tsunami.proto.MatchedPlugin;\nimport com.google.tsunami.proto.NetworkEndpoint;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.PluginDefinition;\nimport com.google.tsunami.proto.PluginInfo;\nimport com.google.tsunami.proto.RunCompactRequest;\nimport com.google.tsunami.proto.RunCompactRequest.PluginNetworkServiceTarget;\nimport com.google.tsunami.proto.RunRequest;\nimport com.google.tsunami.proto.TargetInfo;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic final class CompactRunRequestHelperTest {\n\n  @Test\n  public void compressingRunRequest_isMoreCompact() {\n    NetworkService service1 = NetworkService.newBuilder().setServiceName(\"service1\").build();\n    NetworkService service2 = NetworkService.newBuilder().setServiceName(\"service2\").build();\n    PluginDefinition plugin1 =\n        PluginDefinition.newBuilder()\n            .setInfo(PluginInfo.newBuilder().setName(\"plugin1\").build())\n            .build();\n    PluginDefinition plugin2 =\n        PluginDefinition.newBuilder()\n            .setInfo(PluginInfo.newBuilder().setName(\"plugin2\").build())\n            .build();\n    PluginDefinition plugin3 =\n        PluginDefinition.newBuilder()\n            .setInfo(PluginInfo.newBuilder().setName(\"plugin3\").build())\n            .build();\n    MatchedPlugin matchedPlugin1 =\n        MatchedPlugin.newBuilder().addServices(service1).setPlugin(plugin1).build();\n    MatchedPlugin matchedPlugin2 =\n        MatchedPlugin.newBuilder().addServices(service2).setPlugin(plugin2).build();\n    MatchedPlugin matchedPlugin3 =\n        MatchedPlugin.newBuilder().addServices(service1).setPlugin(plugin3).build();\n    ImmutableList<MatchedPlugin> expectedMatchedPlugins =\n        ImmutableList.of(matchedPlugin1, matchedPlugin2, matchedPlugin3);\n    TargetInfo expectedTargetInfo =\n        TargetInfo.newBuilder()\n            .addNetworkEndpoints(\n                NetworkEndpoint.newBuilder()\n                    .setHostname(Hostname.newBuilder().setName(\"example.com\").build())\n                    .build())\n            .build();\n    RunRequest expectedUncompressedRunRequest =\n        RunRequest.newBuilder()\n            .setTarget(expectedTargetInfo)\n            .addAllPlugins(expectedMatchedPlugins)\n            .build();\n    var actualCompressedRunRequest =\n        CompactRunRequestHelper.compress(expectedUncompressedRunRequest);\n\n    var expectedCompressedRunRequest =\n        RunCompactRequest.newBuilder()\n            .setTarget(expectedTargetInfo)\n            .addServices(service1)\n            .addServices(service2)\n            .addPlugins(plugin1)\n            .addPlugins(plugin2)\n            .addPlugins(plugin3)\n            .addScanTargets(\n                PluginNetworkServiceTarget.newBuilder()\n                    .setPluginIndex(0)\n                    .setServiceIndex(0)\n                    .build())\n            .addScanTargets(\n                PluginNetworkServiceTarget.newBuilder()\n                    .setPluginIndex(1)\n                    .setServiceIndex(1)\n                    .build())\n            .addScanTargets(\n                PluginNetworkServiceTarget.newBuilder()\n                    .setPluginIndex(2)\n                    .setServiceIndex(0)\n                    .build())\n            .build();\n    assertThat(actualCompressedRunRequest).isEqualTo(expectedCompressedRunRequest);\n\n    // And now uncompressing it again:\n    var actualUncompressedRunRequest =\n        CompactRunRequestHelper.uncompress(actualCompressedRunRequest);\n\n    // It should match the original setup\n    assertThat(actualUncompressedRunRequest).isEqualTo(expectedUncompressedRunRequest);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/time/SystemUtcClockModuleTest.java",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.time;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.inject.Guice;\nimport com.google.inject.Key;\nimport java.time.Clock;\nimport java.time.ZoneOffset;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link SystemUtcClockModule}. */\n@RunWith(JUnit4.class)\npublic class SystemUtcClockModuleTest {\n\n  @Test\n  public void configure_always_bindsClockToSystemUtc() {\n    Clock clock =\n        Guice.createInjector(new SystemUtcClockModule())\n            .getInstance(Key.get(Clock.class, UtcClock.class));\n\n    assertThat(clock).isNotNull();\n    assertThat(clock.getZone()).isEqualTo(ZoneOffset.UTC);\n    // A hacky way of testing the instance is a SystemClock.\n    assertThat(clock.toString()).contains(\"SystemClock\");\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/time/testing/FakeUtcClockModuleTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.time.testing;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.inject.Guice;\nimport com.google.inject.Injector;\nimport com.google.inject.Key;\nimport com.google.tsunami.common.time.UtcClock;\nimport java.time.Clock;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link FakeUtcClockModule}. */\n@RunWith(JUnit4.class)\npublic class FakeUtcClockModuleTest {\n\n  @Test\n  public void constructor_withNullFakeClock_throwsNullPointerException() {\n    assertThrows(NullPointerException.class, () -> new FakeUtcClockModule(null));\n  }\n\n  @Test\n  public void configure_always_bindsToSameInstance() {\n    FakeUtcClock fakeUtcClock = FakeUtcClock.create();\n\n    Injector injector = Guice.createInjector(new FakeUtcClockModule(fakeUtcClock));\n\n    assertThat(injector.getInstance(Key.get(Clock.class, UtcClock.class)))\n        .isSameInstanceAs(fakeUtcClock);\n    assertThat(injector.getInstance(Key.get(Clock.class, UtcClock.class)))\n        .isSameInstanceAs(fakeUtcClock);\n    assertThat(injector.getInstance(Key.get(FakeUtcClock.class, UtcClock.class)))\n        .isSameInstanceAs(fakeUtcClock);\n    assertThat(injector.getInstance(Key.get(FakeUtcClock.class, UtcClock.class)))\n        .isSameInstanceAs(fakeUtcClock);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/time/testing/FakeUtcClockTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.time.testing;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link FakeUtcClock}. */\n@RunWith(JUnit4.class)\npublic class FakeUtcClockTest {\n  private static final Instant TEST_INSTANT = Instant.ofEpochMilli(1927081738591L);\n  private static final Duration TEST_DURATION = Duration.ofSeconds(20);\n\n  @Test\n  public void setNow_always_setsClockToGivenInstant() {\n    assertThat(FakeUtcClock.create().setNow(TEST_INSTANT).instant()).isEqualTo(TEST_INSTANT);\n  }\n\n  @Test\n  public void setNow_whenCallWithNull_throwsNullPointerException() {\n    assertThrows(NullPointerException.class, () -> FakeUtcClock.create().setNow(null));\n  }\n\n  @Test\n  public void advance_always_advancesGivenDuration() {\n    FakeUtcClock fakeUtcClock = FakeUtcClock.create().setNow(TEST_INSTANT);\n\n    FakeUtcClock advancedClock = fakeUtcClock.advance(TEST_DURATION);\n\n    assertThat(advancedClock.instant()).isEqualTo(TEST_INSTANT.plus(TEST_DURATION));\n  }\n\n  @Test\n  public void advance_whenCalledWithNull_throwsNullPointerException() {\n    assertThrows(NullPointerException.class, () -> FakeUtcClock.create().advance(null));\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/version/ComparisonUtilityTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.common.collect.Lists;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link ComparisonUtility}. */\n@RunWith(JUnit4.class)\npublic final class ComparisonUtilityTest {\n\n  @Test\n  public void compareWithFillValue_bothEmptyListWithFillValueEqualToZero_returnsZero() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(), Lists.newArrayList(), 0))\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void compareWithFillValue_bothEmptyListWithPositiveFillValue_returnsZero() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(), Lists.newArrayList(), 1))\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void compareWithFillValue_bothEmptyListWithNegativeFilValue_returnsZero() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(), Lists.newArrayList(), -1))\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void compareWithFillValue_oneEmptyListAndSmallFillValue_returnsNegative() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(), Lists.newArrayList(1, 2, 3), 0))\n        .isLessThan(0);\n  }\n\n  @Test\n  public void compareWithFillValue_oneEmptyListAndLargeFillValue_returnsPositive() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(), Lists.newArrayList(1, 2, 3), 100))\n        .isGreaterThan(0);\n  }\n\n  @Test\n  public void compareWithFillValue_nonEmptyListSameSizeGreaterValue_returnsPositive() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(1, 3, 4), Lists.newArrayList(1, 2, 3), 100))\n        .isGreaterThan(0);\n  }\n\n  @Test\n  public void compareWithFillValue_nonEmptyListSameSizeEqualValue_returnsZero() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(1, 2, 3), Lists.newArrayList(1, 2, 3), 100))\n        .isEqualTo(0);\n  }\n\n  @Test\n  public void compareWithFillValue_nonEmptyListSameSizeLessThanValue_returnsNegative() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(1, 1, 3), Lists.newArrayList(1, 2, 3), 100))\n        .isLessThan(0);\n  }\n\n  @Test\n  public void compareWithFillValue_nonEmptyListVariedSizeWithPositiveFillValue_returnsNegative() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(1, 1), Lists.newArrayList(1, 2, 3), 100))\n        .isLessThan(0);\n  }\n\n  @Test\n  public void compareWithFillValue_nonEmptyListVariedSizeWithZeroFillValue_returnsPositive() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(1, 3), Lists.newArrayList(1, 2, 3), 0))\n        .isGreaterThan(0);\n  }\n\n  @Test\n  public void compareWithFillValue_nonEmptyListVariedSizeWithZeroFillValue_returnsNegative() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(1, 2), Lists.newArrayList(1, 2, 3), 0))\n        .isLessThan(0);\n  }\n\n  @Test\n  public void compareWithFillValue_nonEmptyListVariedSizeWithPositiveFillValue_returnsPositive() {\n    assertThat(\n            ComparisonUtility.compareListWithFillValue(\n                Lists.newArrayList(1, 2), Lists.newArrayList(1, 2, 3), 100))\n        .isGreaterThan(0);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/version/EqualsTestCase.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.errorprone.annotations.Immutable;\n\n@Immutable(containerOf = \"T\")\n@AutoValue\nabstract class EqualsTestCase<T> {\n  abstract T first();\n  abstract T second();\n\n  static <T> EqualsTestCase<T> create(T first, T second) {\n    return new AutoValue_EqualsTestCase<>(first, second);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/version/KnownQualifierTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.collect.ImmutableList;\nimport java.util.Arrays;\nimport java.util.EnumSet;\nimport org.junit.Test;\nimport org.junit.experimental.theories.DataPoints;\nimport org.junit.experimental.theories.FromDataPoints;\nimport org.junit.experimental.theories.Theories;\nimport org.junit.experimental.theories.Theory;\nimport org.junit.runner.RunWith;\n\n/** Tests for {@link KnownQualifier}. */\n@RunWith(Theories.class)\npublic final class KnownQualifierTest {\n\n  @DataPoints(\"ValidKnownQualifierText\")\n  public static ImmutableList<String> validTextsForKnownQualifiers() {\n    return Arrays.stream(KnownQualifier.values())\n        .map(KnownQualifier::getQualifierText)\n        .collect(ImmutableList.toImmutableList());\n  }\n\n  @Test\n  public void compareTo_always_hasTheCorrectOrder() {\n    // KnownQualifier should promise the following order to callers.\n    assertThat(EnumSet.allOf(KnownQualifier.class))\n        .containsExactly(\n            KnownQualifier.ALPHA,\n            KnownQualifier.BETA,\n            KnownQualifier.PRE,\n            KnownQualifier.R,\n            KnownQualifier.RC,\n            KnownQualifier.ABSENT,\n            KnownQualifier.P,\n            KnownQualifier.PATCH,\n            KnownQualifier.PATCHED)\n        .inOrder();\n  }\n\n  @Theory\n  public void isKnownQualifier_validText_returnsTrue(\n      @FromDataPoints(\"ValidKnownQualifierText\") String validText) {\n    assertThat(KnownQualifier.isKnownQualifier(validText)).isTrue();\n  }\n\n  @Theory\n  public void isKnownQualifier_invalidText_returnsFalse() {\n    assertThat(KnownQualifier.isKnownQualifier(\"random\")).isFalse();\n  }\n\n  @Theory\n  public void fromText_validText_returnsKnownQualifier(\n      @FromDataPoints(\"ValidKnownQualifierText\") String validText) {\n    assertThat(KnownQualifier.fromText(validText)).isIn(EnumSet.allOf(KnownQualifier.class));\n  }\n\n  @Test\n  public void fromText_invalidText_throwsIllegalArgumentException() {\n    assertThrows(IllegalArgumentException.class, () -> KnownQualifier.fromText(\"random\"));\n  }\n\n  @Test\n  public void fromText_invalidTextUsingComposedValidTokens_throwsIllegalArgumentException() {\n    assertThrows(IllegalArgumentException.class, () -> KnownQualifier.fromText(\"alpha-beta\"));\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/version/LessThanTestCase.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.errorprone.annotations.Immutable;\n\n@Immutable(containerOf = \"T\")\n@AutoValue\nabstract class LessThanTestCase<T> {\n  abstract T smaller();\n  abstract T larger();\n\n  static <T> LessThanTestCase<T> create(T smaller, T larger) {\n    return new AutoValue_LessThanTestCase<>(smaller, larger);\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/version/SegmentTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.common.collect.ImmutableList;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport org.junit.Test;\nimport org.junit.experimental.theories.DataPoints;\nimport org.junit.experimental.theories.FromDataPoints;\nimport org.junit.experimental.theories.Theories;\nimport org.junit.experimental.theories.Theory;\nimport org.junit.runner.RunWith;\n\n/** Tests for {@link Segment}. */\n@RunWith(Theories.class)\npublic final class SegmentTest {\n\n  @Test\n  public void fromTokenList_startsWithKnownQualifier_buildsFromInput() {\n    ImmutableList<Token> tokens =\n        ImmutableList.of(Token.fromKnownQualifier(KnownQualifier.ALPHA), Token.fromNumeric(1L));\n    Segment segment = Segment.fromTokenList(tokens);\n\n    assertThat(segment.tokens()).containsExactlyElementsIn(tokens);\n  }\n\n  @Test\n  public void fromTokenList_noKnownQualifier_addsKnownQualifier() {\n    ImmutableList<Token> tokens = ImmutableList.of(Token.fromText(\"abc\"), Token.fromNumeric(1L));\n    Segment segment = Segment.fromTokenList(tokens);\n\n    assertThat(segment.tokens())\n        .containsExactlyElementsIn(\n            Stream.concat(\n                    Stream.of(Token.fromKnownQualifier(KnownQualifier.ABSENT)), tokens.stream())\n                .collect(Collectors.toList()));\n  }\n\n  @Test\n  public void fromString_emptyString_returnsNullSegment() {\n    assertThat(Segment.fromString(\"\")).isEqualTo(Segment.NULL);\n  }\n\n  @Test\n  public void fromString_allExcludedTokens_returnsNullSegment() {\n    assertThat(Segment.fromString(\"gg.N/A\")).isEqualTo(Segment.NULL);\n  }\n\n  @Test\n  public void fromString_allEmptyTokens_returnsNullSegment() {\n    assertThat(Segment.fromString(\"...\")).isEqualTo(Segment.NULL);\n  }\n\n  @Test\n  public void fromString_textAndNumeric_returnsSeparatedTextAndNumber() {\n    assertThat(Segment.fromString(\"abc1.0\").tokens())\n        .containsExactly(\n            Token.fromKnownQualifier(KnownQualifier.ABSENT),\n            Token.fromText(\"abc\"),\n            Token.fromNumeric(1L),\n            Token.fromNumeric(0L));\n\n    assertThat(Segment.fromString(\"gg1.0\").tokens())\n        .containsExactly(\n            Token.fromKnownQualifier(KnownQualifier.ABSENT),\n            Token.fromNumeric(1L),\n            Token.fromNumeric(0L));\n  }\n\n  @Test\n  public void fromString_noKnownQualifier_addsKnownQualifier() {\n    assertThat(Segment.fromString(\"2.1.1\").tokens())\n        .containsExactly(\n            Token.fromKnownQualifier(KnownQualifier.ABSENT),\n            Token.fromNumeric(2L),\n            Token.fromNumeric(1L),\n            Token.fromNumeric(1L));\n  }\n\n  @Test\n  public void fromString_startsWithKnownQualifier_parsesNumericAndTextTokens() {\n    assertThat(Segment.fromString(\"alpha.1~text\").tokens())\n        .containsExactly(\n            Token.fromKnownQualifier(KnownQualifier.ALPHA),\n            Token.fromNumeric(1L),\n            Token.fromText(\"~\"),\n            Token.fromText(\"text\"));\n  }\n\n  @DataPoints(\"Equals\")\n  public static ImmutableList<EqualsTestCase<Segment>> equalTestCases() {\n    return ImmutableList.of(\n        EqualsTestCase.create(Segment.fromString(\"\"), Segment.fromString(\"\")),\n        EqualsTestCase.create(Segment.fromString(\"\"), Segment.fromString(\"gg.N/A\")),\n        EqualsTestCase.create(Segment.fromString(\"1.1\"), Segment.fromString(\"1.1\")),\n        EqualsTestCase.create(Segment.fromString(\"1.1-\"), Segment.fromString(\"1.1-gg\")));\n  }\n\n  @Theory\n  public void compareTo_equalTestCase_returnsZero(\n      @FromDataPoints(\"Equals\") EqualsTestCase<Segment> testCase) {\n    assertThat(testCase.first()).isEquivalentAccordingToCompareTo(testCase.second());\n  }\n\n  @DataPoints(\"LessThan\")\n  public static ImmutableList<LessThanTestCase<Segment>> lessThanTestCases() {\n    return ImmutableList.of(\n        // Null token.\n        LessThanTestCase.create(Segment.fromString(\"2.1\"), Segment.fromString(\"2.1.1\")),\n        LessThanTestCase.create(Segment.fromString(\"alpha\"), Segment.fromString(\"\")),\n\n        // Numeric token.\n        LessThanTestCase.create(Segment.fromString(\"2.1\"), Segment.fromString(\"2.2\")),\n        LessThanTestCase.create(Segment.fromString(\"0.9\"), Segment.fromString(\"1.0\")),\n\n        // Known qualifiers.\n        LessThanTestCase.create(Segment.fromString(\"alpha\"), Segment.fromString(\"beta\")),\n        LessThanTestCase.create(Segment.fromString(\"alpha.beta\"), Segment.fromString(\"alpha\")),\n        LessThanTestCase.create(Segment.fromString(\"alpha.beta\"), Segment.fromString(\"alpha.rc\")),\n\n        // Text token.\n        LessThanTestCase.create(Segment.fromString(\"abc\"), Segment.fromString(\"def\")),\n        LessThanTestCase.create(Segment.fromString(\"abc.def\"), Segment.fromString(\"abc.ghi\")),\n        LessThanTestCase.create(Segment.fromString(\"abc\"), Segment.fromString(\"DEF\")),\n\n        // Mixed type.\n        LessThanTestCase.create(Segment.fromString(\"2.1.1\"), Segment.fromString(\"2.1.abc\")));\n  }\n\n  @Theory\n  public void compareTo_lessThanTestCase_hasCorrectSymmetryResult(\n      @FromDataPoints(\"LessThan\") LessThanTestCase<Segment> testCase) {\n    assertThat(testCase.smaller()).isLessThan(testCase.larger());\n    assertThat(testCase.larger()).isGreaterThan(testCase.smaller());\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/version/TokenTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.common.collect.ImmutableList;\nimport org.junit.Test;\nimport org.junit.experimental.theories.DataPoints;\nimport org.junit.experimental.theories.FromDataPoints;\nimport org.junit.experimental.theories.Theories;\nimport org.junit.experimental.theories.Theory;\nimport org.junit.runner.RunWith;\n\n/** Tests for {@link Token}. */\n@RunWith(Theories.class)\npublic final class TokenTest {\n\n  @Test\n  public void fromNumeric_always_returnsNumericToken() {\n    Token numericToken = Token.fromNumeric(123L);\n    assertThat(numericToken.isNumeric()).isTrue();\n    assertThat(numericToken.getNumeric()).isEqualTo(123L);\n  }\n\n  @Test\n  public void fromText_always_returnsTextToken() {\n    Token textToken = Token.fromText(\"abc\");\n    assertThat(textToken.isText()).isTrue();\n    assertThat(textToken.isKnownQualifier()).isFalse();\n    assertThat(textToken.getText()).isEqualTo(\"abc\");\n  }\n\n  @Test\n  public void fromKnownQualifier_always_returnsTextToken() {\n    Token textToken = Token.fromKnownQualifier(KnownQualifier.ALPHA);\n    assertThat(textToken.isText()).isTrue();\n    assertThat(textToken.isKnownQualifier()).isTrue();\n    assertThat(textToken.getText()).isEqualTo(KnownQualifier.ALPHA.getQualifierText());\n  }\n\n  @DataPoints(\"Equal\")\n  public static ImmutableList<EqualsTestCase<Token>> equalTestCases() {\n    return ImmutableList.of(\n        EqualsTestCase.create(Token.EMPTY, Token.fromKnownQualifier(KnownQualifier.ABSENT)),\n        EqualsTestCase.create(Token.EMPTY, Token.fromText(\"\")),\n        EqualsTestCase.create(Token.fromNumeric(1L), Token.fromNumeric(1L)),\n        EqualsTestCase.create(\n            Token.fromKnownQualifier(KnownQualifier.P), Token.fromKnownQualifier(KnownQualifier.P)),\n        EqualsTestCase.create(Token.fromKnownQualifier(KnownQualifier.P), Token.fromText(\"p\")),\n        EqualsTestCase.create(Token.fromKnownQualifier(KnownQualifier.P), Token.fromText(\"P\")),\n        EqualsTestCase.create(Token.fromText(\"abc\"), Token.fromText(\"ABC\")));\n  }\n\n  @Theory\n  public void isEqualTo_equalTestCase_returnsZero(\n      @FromDataPoints(\"Equal\") EqualsTestCase<Token> testCase) {\n    assertThat(testCase.first()).isEquivalentAccordingToCompareTo(testCase.second());\n  }\n\n  @DataPoints(\"LessThan\")\n  public static ImmutableList<LessThanTestCase<Token>> lessThanTestCases() {\n    return ImmutableList.of(\n        // Empty token and Numeric token test cases.\n        LessThanTestCase.create(Token.EMPTY, Token.fromNumeric(1L)),\n        LessThanTestCase.create(Token.EMPTY, Token.fromNumeric(0L)),\n\n        // Empty token and KnownQualifier test cases.\n        LessThanTestCase.create(Token.fromKnownQualifier(KnownQualifier.ALPHA), Token.EMPTY),\n        LessThanTestCase.create(Token.fromKnownQualifier(KnownQualifier.RC), Token.EMPTY),\n        LessThanTestCase.create(Token.EMPTY, Token.fromKnownQualifier(KnownQualifier.P)),\n        LessThanTestCase.create(Token.EMPTY, Token.fromKnownQualifier(KnownQualifier.PATCH)),\n\n        // Empty token and Text token test case.\n        LessThanTestCase.create(Token.EMPTY, Token.fromText(\"abc\")),\n        LessThanTestCase.create(Token.EMPTY, Token.fromText(\"123\")),\n\n        // Numeric token only test case.\n        LessThanTestCase.create(Token.fromNumeric(0L), Token.fromNumeric(1L)),\n\n        // KnownQualifier only test case.\n        LessThanTestCase.create(\n            Token.fromKnownQualifier(KnownQualifier.ALPHA),\n            Token.fromKnownQualifier(KnownQualifier.ABSENT)),\n        LessThanTestCase.create(\n            Token.fromKnownQualifier(KnownQualifier.ABSENT),\n            Token.fromKnownQualifier(KnownQualifier.PATCH)),\n\n        // Text only test case.\n        LessThanTestCase.create(Token.fromText(\"abc\"), Token.fromText(\"def\")),\n        LessThanTestCase.create(Token.fromText(\"abc\"), Token.fromText(\"dEf\")),\n        LessThanTestCase.create(\n            Token.fromText(\"abc\"), Token.fromKnownQualifier(KnownQualifier.ALPHA)),\n\n        // Numeric and Text test case\n        LessThanTestCase.create(Token.fromNumeric(1L), Token.fromText(\"abc\")),\n        LessThanTestCase.create(\n            Token.fromNumeric(1L), Token.fromKnownQualifier(KnownQualifier.ALPHA)));\n  }\n\n  @Theory\n  public void compareTo_lessThanTestCase_hasCorrectSymmetryResult(\n      @FromDataPoints(\"LessThan\") LessThanTestCase<Token> testCase) {\n    // Test symmetry.\n    assertThat(testCase.smaller()).isLessThan(testCase.larger());\n    assertThat(testCase.larger()).isGreaterThan(testCase.smaller());\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/version/VersionRangeTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.tsunami.common.version.VersionRange.Inclusiveness;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link VersionRange}. */\n@RunWith(JUnit4.class)\npublic class VersionRangeTest {\n\n  @Test\n  public void parse_withNegativeInfinityRange_returnsCorrectVersionRange() {\n    VersionRange versionRange = VersionRange.parse(\"(,1.0]\");\n\n    assertThat(versionRange)\n        .isEqualTo(\n            VersionRange.builder()\n                .setMinVersion(Version.minimum())\n                .setMinVersionInclusiveness(Inclusiveness.EXCLUSIVE)\n                .setMaxVersion(Version.fromString(\"1.0\"))\n                .setMaxVersionInclusiveness(Inclusiveness.INCLUSIVE)\n                .build());\n  }\n\n  @Test\n  public void parse_withPositiveInfinityRange_returnsCorrectVersionRange() {\n    VersionRange versionRange = VersionRange.parse(\"(1.0,)\");\n\n    assertThat(versionRange)\n        .isEqualTo(\n            VersionRange.builder()\n                .setMinVersion(Version.fromString(\"1.0\"))\n                .setMinVersionInclusiveness(Inclusiveness.EXCLUSIVE)\n                .setMaxVersion(Version.maximum())\n                .setMaxVersionInclusiveness(Inclusiveness.EXCLUSIVE)\n                .build());\n  }\n\n  @Test\n  public void parse_withRegularRange_returnsCorrectVersionRange() {\n    VersionRange versionRange = VersionRange.parse(\"(1.0,2.0]\");\n\n    assertThat(versionRange)\n        .isEqualTo(\n            VersionRange.builder()\n                .setMinVersion(Version.fromString(\"1.0\"))\n                .setMinVersionInclusiveness(Inclusiveness.EXCLUSIVE)\n                .setMaxVersion(Version.fromString(\"2.0\"))\n                .setMaxVersionInclusiveness(Inclusiveness.INCLUSIVE)\n                .build());\n  }\n\n  @Test\n  public void parse_withEmptyRangeString_throwsIllegalArgumentException() {\n    IllegalArgumentException exception =\n        assertThrows(IllegalArgumentException.class, () -> VersionRange.parse(\"\"));\n    assertThat(exception).hasMessageThat().isEqualTo(\"Range string cannot be empty.\");\n  }\n\n  @Test\n  public void parse_withRangeNotStartingWithParenthesis_throwsIllegalArgumentException() {\n    IllegalArgumentException exception =\n        assertThrows(IllegalArgumentException.class, () -> VersionRange.parse(\",1.0]\"));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"Version range must start with '[' or '(', got ',1.0]'\");\n  }\n\n  @Test\n  public void parse_withRangeNotEndingWithParenthesis_throwsIllegalArgumentException() {\n    IllegalArgumentException exception =\n        assertThrows(IllegalArgumentException.class, () -> VersionRange.parse(\"(,1.0\"));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"Version range must end with ']' or ')', got '(,1.0'\");\n  }\n\n  @Test\n  public void parse_withTooManyParenthesis_throwsIllegalArgumentException() {\n    IllegalArgumentException exception =\n        assertThrows(IllegalArgumentException.class, () -> VersionRange.parse(\"(,1.0]]\"));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"Parenthesis and/or brackets not allowed within version range, got '(,1.0]]'\");\n  }\n\n  @Test\n  public void parse_withTooManyCommas_throwsIllegalArgumentException() {\n    IllegalArgumentException exception =\n        assertThrows(IllegalArgumentException.class, () -> VersionRange.parse(\"(,,,1.0]\"));\n    assertThat(exception).hasMessageThat().isEqualTo(\"Invalid range of versions, got '(,,,1.0]'\");\n  }\n\n  @Test\n  public void parse_withoutComma_throwsIllegalArgumentException() {\n    IllegalArgumentException exception =\n        assertThrows(IllegalArgumentException.class, () -> VersionRange.parse(\"[1.0]\"));\n    assertThat(exception).hasMessageThat().isEqualTo(\"Invalid range of versions, got '[1.0]'\");\n  }\n\n  @Test\n  public void parse_withMinimalToMaximalRange_throwsIllegalArgumentException() {\n    IllegalArgumentException exception =\n        assertThrows(IllegalArgumentException.class, () -> VersionRange.parse(\"(,)\"));\n    assertThat(exception).hasMessageThat().isEqualTo(\"Infinity range is not supported, got '(,)'\");\n  }\n\n  @Test\n  public void parse_withTheSameRangeEnds_throwsIllegalArgumentException() {\n    IllegalArgumentException exception =\n        assertThrows(IllegalArgumentException.class, () -> VersionRange.parse(\"[1.0,1.0]\"));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"Min version in range must be less than max version in range, got '[1.0,1.0]'\");\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/version/VersionSetTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.collect.ImmutableList;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link VersionSet}. */\n@RunWith(JUnit4.class)\npublic class VersionSetTest {\n\n  @Test\n  public void parse_withValidVersionsAndVersionRanges_returnsParsedVersionSet() {\n    VersionSet versionSet =\n        VersionSet.parse(ImmutableList.of(\"1.0\", \"1.3\", \"(1.4, 1.7]\", \"1.9\", \"[2.0,)\"));\n\n    assertThat(versionSet.versions())\n        .containsExactly(\n            Version.fromString(\"1.0\"), Version.fromString(\"1.3\"), Version.fromString(\"1.9\"));\n    assertThat(versionSet.versionRanges())\n        .containsExactly(VersionRange.parse(\"(1.4,1.7]\"), VersionRange.parse(\"[2.0,)\"));\n  }\n\n  @Test\n  public void parse_withEmptyInputList_throwsIllegalArgumentException() {\n    IllegalArgumentException exception =\n        assertThrows(IllegalArgumentException.class, () -> VersionSet.parse(ImmutableList.of()));\n    assertThat(exception).hasMessageThat().isEqualTo(\"Versions and ranges list cannot be empty.\");\n  }\n\n  @Test\n  public void parse_withInvalidVersion_throwsIllegalArgumentException() {\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class, () -> VersionSet.parse(ImmutableList.of(\"1,0\", \"abc\")));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"String '1,0' is neither a discrete string nor a version range.\");\n  }\n}\n"
  },
  {
    "path": "common/src/test/java/com/google/tsunami/common/version/VersionTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.common.version;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.collect.ImmutableList;\nimport org.junit.Test;\nimport org.junit.experimental.theories.DataPoints;\nimport org.junit.experimental.theories.FromDataPoints;\nimport org.junit.experimental.theories.Theories;\nimport org.junit.experimental.theories.Theory;\nimport org.junit.runner.RunWith;\n\n/** Tests for {@link Version}. */\n@RunWith(Theories.class)\npublic final class VersionTest {\n\n  @Test\n  public void create_whenNormalVersionAndValueIsNull_throwsException() {\n    assertThrows(IllegalArgumentException.class, () -> Version.fromString(null));\n  }\n\n  @Test\n  public void create_whnNormalVersionAndValueIsEmpty_throwsExceptionIfStringIsNull() {\n    assertThrows(IllegalArgumentException.class, () -> Version.fromString(\"\"));\n  }\n\n  @Test\n  public void create_whenNormalVersion_returnsTypeNormal() {\n    Version version = Version.fromString(\"1.1\");\n\n    assertThat(version.versionType()).isEqualTo(Version.Type.NORMAL);\n  }\n\n  @Test\n  public void createMaximum_always_returnsTypeMaximum() {\n    Version version = Version.maximum();\n\n    assertThat(version.versionType()).isEqualTo(Version.Type.MAXIMUM);\n  }\n\n  @Test\n  public void createMaximum_always_returnsTypeMinimum() {\n    Version version = Version.minimum();\n\n    assertThat(version.versionType()).isEqualTo(Version.Type.MINIMUM);\n  }\n\n  @DataPoints(\"InvalidVersion\")\n  public static ImmutableList<String> invalidVersionTestCases() {\n    return ImmutableList.of(\"\", \"N/A\", \"...\", \"-\");\n  }\n\n  @Theory\n  public void fromString_invalidVersionString_throwsIllegalArgumentException(\n      @FromDataPoints(\"InvalidVersion\") String version) {\n    assertThrows(IllegalArgumentException.class, () -> Version.fromString(version));\n  }\n\n  @Test\n  public void fromString_validString_storesInputAsRawString() {\n    assertThat(Version.fromString(\"1.0\").versionString()).isEqualTo(\"1.0\");\n  }\n\n  @Test\n  public void fromString_noEpoch_appendsZeroEpoch() {\n    assertThat(Version.fromString(\"1.0\").segments())\n        .containsExactly(Segment.fromString(\"0\"), Segment.fromString(\"1.0\"));\n  }\n\n  @Test\n  public void fromString_withEpoch_epochIsParsed() {\n    assertThat(Version.fromString(\"1:1.0\").segments())\n        .containsExactly(Segment.fromString(\"1\"), Segment.fromString(\"1.0\"));\n  }\n\n  @Test\n  public void fromString_withMultipleSegments_segmentsParsedCorrectly() {\n    assertThat(Version.fromString(\"1:9.7.0.dfsg.P1-gg3.0\").segments())\n        .containsExactly(\n            Segment.fromString(\"1\"),\n            Segment.fromString(\"9.7.0.dfsg.P1\"),\n            Segment.fromString(\"3.0\"));\n  }\n\n  @DataPoints(\"Equals\")\n  public static ImmutableList<EqualsTestCase<Version>> equalsTestCases() {\n    return ImmutableList.of(\n        EqualsTestCase.create(Version.fromString(\"1.0\"), Version.fromString(\"1.0\")),\n        EqualsTestCase.create(Version.fromString(\"1.0\"), Version.fromString(\"0:1.0\")),\n        EqualsTestCase.create(Version.fromString(\"1.0-\"), Version.fromString(\"1.0-gg\")),\n        EqualsTestCase.create(Version.maximum(), Version.maximum()),\n        EqualsTestCase.create(Version.minimum(), Version.minimum()));\n  }\n\n  @Theory\n  public void compareTo_equalsTestCase_returnsZero(\n      @FromDataPoints(\"Equals\") EqualsTestCase<Version> testCase) {\n    assertThat(testCase.first()).isEquivalentAccordingToCompareTo(testCase.second());\n  }\n\n  @DataPoints(\"LessThan\")\n  public static ImmutableList<LessThanTestCase<Version>> lessThanTestCases() {\n    return ImmutableList.of(\n        LessThanTestCase.create(Version.minimum(), Version.fromString(\"1.0\")),\n        LessThanTestCase.create(Version.minimum(), Version.maximum()),\n        LessThanTestCase.create(Version.fromString(\"1.0\"), Version.maximum()),\n        LessThanTestCase.create(Version.fromString(\"0.9\"), Version.fromString(\"1.0\")),\n        LessThanTestCase.create(Version.fromString(\"1.0-0\"), Version.fromString(\"1.0-110313082\")),\n        LessThanTestCase.create(\n            Version.fromString(\"0.161-gg2.0\"), Version.fromString(\"0.165-gg1.0\")),\n        LessThanTestCase.create(Version.fromString(\"0.87-gg1.2\"), Version.fromString(\"0.87-gg1.3\")),\n        LessThanTestCase.create(\n            Version.fromString(\"2017b-gg1.0\"), Version.fromString(\"2018b-gg0.\")),\n        LessThanTestCase.create(Version.fromString(\"18-4\"), Version.fromString(\"19-1\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1:9.7.0.dfsg.P1-gg3.0\"),\n            Version.fromString(\"1:9.8.0.dfsg.P0-gg1.0\")),\n        LessThanTestCase.create(Version.fromString(\"5.7-gg2.0\"), Version.fromString(\"5.8-gg1.0\")),\n        LessThanTestCase.create(\n            Version.fromString(\"2.4.6-12\"), Version.fromString(\"2.4.6-12+patched1\")),\n        LessThanTestCase.create(Version.fromString(\"20170727-1\"), Version.fromString(\"20170801-1\")),\n        LessThanTestCase.create(\n            Version.fromString(\"3.12-443-ga51ea6dc8202-gg2.2\"),\n            Version.fromString(\"3.13-440-ga51eadfe472-gg1.0\")),\n        LessThanTestCase.create(Version.fromString(\"1.0a\"), Version.fromString(\"1.0b\")),\n        LessThanTestCase.create(Version.fromString(\"1a\"), Version.fromString(\"2\")),\n        LessThanTestCase.create(\n            Version.fromString(\"4d01146f1679dd90bba45adb60d24ad11fe1155e\"),\n            Version.fromString(\"e40b437401966fe06b0c4d5430c35e4494675c90\")),\n        LessThanTestCase.create(Version.fromString(\"7.10\"), Version.fromString(\"1_7.9\")),\n        LessThanTestCase.create(Version.fromString(\"9.10\"), Version.fromString(\"1_9.11\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1.0.0-alpha\"), Version.fromString(\"1.0.0-alpha.1\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1.0.0-alpha.1\"), Version.fromString(\"1.0.0-alpha.beta\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1.0.0-alpha.beta\"), Version.fromString(\"1.0.0-beta\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1.0.0-beta\"), Version.fromString(\"1.0.0-beta.2\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1.0.0-beta.2\"), Version.fromString(\"1.0.0-beta.11\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1.0.0-beta.11\"), Version.fromString(\"1.0.0-rc.1\")),\n        LessThanTestCase.create(Version.fromString(\"1.0.0-rc.1\"), Version.fromString(\"1.0.0\")),\n        LessThanTestCase.create(Version.fromString(\"1.0.0-alpha\"), Version.fromString(\"1.0.0\")),\n        LessThanTestCase.create(Version.fromString(\"1.0.0-alpha.1\"), Version.fromString(\"1.0.0\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1.0.0-alpha.beta\"), Version.fromString(\"1.0.0\")),\n        LessThanTestCase.create(Version.fromString(\"1.0.0-beta\"), Version.fromString(\"1.0.0\")),\n        LessThanTestCase.create(Version.fromString(\"1.0.0-beta.2\"), Version.fromString(\"1.0.0\")),\n        LessThanTestCase.create(Version.fromString(\"1.0.0-beta.11\"), Version.fromString(\"1.0.0\")),\n        LessThanTestCase.create(Version.fromString(\"7.6-0\"), Version.fromString(\"7.6p2-4\")),\n        LessThanTestCase.create(Version.fromString(\"1.0-1\"), Version.fromString(\"1.0.3-3\")),\n        LessThanTestCase.create(Version.fromString(\"1.2.2-2\"), Version.fromString(\"1.3\")),\n        LessThanTestCase.create(Version.fromString(\"1.2.2\"), Version.fromString(\"1.3\")),\n        LessThanTestCase.create(Version.fromString(\"0-pre\"), Version.fromString(\"0-pree\")),\n        LessThanTestCase.create(Version.fromString(\"1.1.6r-1\"), Version.fromString(\"1.1.6r2-2\")),\n        LessThanTestCase.create(Version.fromString(\"2.6b-2\"), Version.fromString(\"2.6b2-1\")),\n        LessThanTestCase.create(\n            Version.fromString(\"98.1-pre2-b6-2\"), Version.fromString(\"98.1p5-1\")),\n        LessThanTestCase.create(Version.fromString(\"0.4-1\"), Version.fromString(\"0.4a6-2\")),\n        LessThanTestCase.create(Version.fromString(\"1:3.0.5-2\"), Version.fromString(\"1:3.0.5.1\")),\n        LessThanTestCase.create(Version.fromString(\"10.3\"), Version.fromString(\"1:0.4\")),\n        LessThanTestCase.create(Version.fromString(\"1:1.25-4\"), Version.fromString(\"1:1.25-8\")),\n        LessThanTestCase.create(Version.fromString(\"1.18.35\"), Version.fromString(\"1.18.36\")),\n        LessThanTestCase.create(Version.fromString(\"1.18.35\"), Version.fromString(\"0:1.18.36\")),\n        LessThanTestCase.create(\n            Version.fromString(\"9:1.18.36:5.4-20\"), Version.fromString(\"10:0.5.1-22\")),\n        LessThanTestCase.create(\n            Version.fromString(\"9:1.18.36:5.4-20\"), Version.fromString(\"9:1.18.36:5.5-1\")),\n        LessThanTestCase.create(\n            Version.fromString(\"9:1.18.36:5.4-20\"), Version.fromString(\"9:1.18.37:4.3-22\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1.18.36-0.17.35-18\"), Version.fromString(\"1.18.36-19\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1:1.2.13-3\"), Version.fromString(\"1:1.2.13-3.1\")),\n        LessThanTestCase.create(Version.fromString(\"2.0.7pre1-4\"), Version.fromString(\"2.0.7r-1\")),\n        LessThanTestCase.create(Version.fromString(\"0.2\"), Version.fromString(\"1.0-0\")),\n        LessThanTestCase.create(Version.fromString(\"1.0\"), Version.fromString(\"1.0-0+b1\")),\n        LessThanTestCase.create(Version.fromString(\"1.2.3\"), Version.fromString(\"1.2.3-1\")),\n        LessThanTestCase.create(Version.fromString(\"1.2.3\"), Version.fromString(\"1.2.4\")),\n        LessThanTestCase.create(Version.fromString(\"1.2.3\"), Version.fromString(\"1.2.4\")),\n        LessThanTestCase.create(Version.fromString(\"1.2.3\"), Version.fromString(\"1.2.24\")),\n        LessThanTestCase.create(Version.fromString(\"0.8.7\"), Version.fromString(\"0.10.0\")),\n        LessThanTestCase.create(Version.fromString(\"2.3\"), Version.fromString(\"3.2\")),\n        LessThanTestCase.create(Version.fromString(\"1.3.2\"), Version.fromString(\"1.3.2a\")),\n        LessThanTestCase.create(Version.fromString(\"0.5.0~git\"), Version.fromString(\"0.5.0~git2\")),\n        LessThanTestCase.create(Version.fromString(\"2a\"), Version.fromString(\"21\")),\n        LessThanTestCase.create(Version.fromString(\"1.3.2a\"), Version.fromString(\"1.3.2b\")),\n        LessThanTestCase.create(Version.fromString(\"1.2.4\"), Version.fromString(\"1:1.2.3\")),\n        LessThanTestCase.create(Version.fromString(\"1:1.2.3\"), Version.fromString(\"1:1.2.4\")),\n        LessThanTestCase.create(Version.fromString(\"1.2a+~bCd3\"), Version.fromString(\"1.2a++\")),\n        LessThanTestCase.create(Version.fromString(\"1.2a+~\"), Version.fromString(\"1.2a+~bCd3\")),\n        LessThanTestCase.create(Version.fromString(\"304-2\"), Version.fromString(\"5:2\")),\n        LessThanTestCase.create(Version.fromString(\"5:2\"), Version.fromString(\"304:2\")),\n        LessThanTestCase.create(Version.fromString(\"3:2\"), Version.fromString(\"25:2\")),\n        LessThanTestCase.create(Version.fromString(\"1:2:123\"), Version.fromString(\"1:12:3\")),\n        LessThanTestCase.create(Version.fromString(\"1.2-3-5\"), Version.fromString(\"1.2-5\")),\n        LessThanTestCase.create(Version.fromString(\"5.005\"), Version.fromString(\"5.10.0\")),\n        LessThanTestCase.create(Version.fromString(\"3.10.2\"), Version.fromString(\"3a9.8\")),\n        LessThanTestCase.create(Version.fromString(\"3~10\"), Version.fromString(\"3a9.8\")),\n        LessThanTestCase.create(\n            Version.fromString(\"1.4+OOo3.0.0~\"), Version.fromString(\"1.4+OOo3.0.0-4\")),\n        LessThanTestCase.create(Version.fromString(\"3.0~rc1-1\"), Version.fromString(\"3.0-1\")),\n        LessThanTestCase.create(Version.fromString(\"2.4.7-1\"), Version.fromString(\"2.4.7-z\")),\n        LessThanTestCase.create(Version.fromString(\"1.00\"), Version.fromString(\"1.002-1+b2\")),\n        LessThanTestCase.create(Version.fromString(\"5.36-r0\"), Version.fromString(\"5.36\")),\n        LessThanTestCase.create(Version.fromString(\"5.36-r0\"), Version.fromString(\"5.36-gg1.0\")),\n        LessThanTestCase.create(\n            Version.fromString(\"5.36-r0\"), Version.fromString(\"5.36-r0-gg1.0\")));\n  }\n\n  @Theory\n  public void compareTo_lessThanTestCase_hasCorrectSymmetryResult(\n      @FromDataPoints(\"LessThan\") LessThanTestCase<Version> testCase) {\n    assertThat(testCase.smaller()).isLessThan(testCase.larger());\n    assertThat(testCase.larger()).isGreaterThan(testCase.smaller());\n  }\n}\n"
  },
  {
    "path": "common/src/test/resources/com/google/tsunami/common/net/http/testdata/README.md",
    "content": "# HTTP Lib Testdata\n\n## tsunami_test_server.p12\n\nThis is a PKCS12 self-signed server key/cert file. This file was generated using\nthe following commands with the password `tsunamitest`:\n\n```shell\n$ openssl req -new -x509 -nodes -sha1 -days 3650 \\\n    -out /tmp/tsunami_test_server.crt \\\n    -keyout /tmp/tsunami_test_server.key\n# Password is \"tsunamitest\" without the quotes.\n$ openssl pkcs12 -export -clcerts \\\n    -in /tmp/tsunami_test_server.crt \\\n    -inkey /tmp/tsunami_test_server.key \\\n    -out tsunami_test_server.p12\n```\n"
  },
  {
    "path": "core.Dockerfile",
    "content": "# Stage 1: Build phase\n\nFROM ghcr.io/google/tsunami-scanner-devel:latest AS build\n\n## build the core engine\nWORKDIR /usr/repos/tsunami-security-scanner\nCOPY . .\nRUN mkdir -p /usr/tsunami\nRUN gradle shadowJar\nRUN find . -name 'tsunami-main-*.jar' -exec cp {} /usr/tsunami/tsunami.jar \\;\nRUN cp ./tsunami_tcs.yaml /usr/tsunami/tsunami.yaml\nRUN cp plugin/src/main/resources/com/google/tsunami/plugin/payload/payload_definitions.yaml /usr/tsunami/payload_definitions.yaml\nRUN cp -r plugin_server/py/ /usr/tsunami/py_server\n\n## We perform a hotpatch of the path pointing to the payload definitions file\n## for easier usage in the Dockerized environment.\nRUN sed -i \"s%'../../plugin/src/main/resources/com/google/tsunami/plugin/payload/payload_definitions.yaml'%'/usr/tsunami/payload_definitions.yaml'%g\" \\\n      /usr/tsunami/py_server/plugin/payload/payload_utility.py\n\n## generate the protos for Python plugins\nWORKDIR /usr/repos/tsunami-security-scanner/\nRUN python3 -m grpc_tools.protoc \\\n  -I/usr/repos/tsunami-security-scanner/proto \\\n  --python_out=/usr/tsunami/py_server/ \\\n  --grpc_python_out=/usr/tsunami/py_server/ \\\n  /usr/repos/tsunami-security-scanner/proto/*.proto\n\n# Stage 2: Release\n\nFROM scratch AS release\n\nCOPY --from=build /usr/tsunami/tsunami.jar /usr/tsunami/\nCOPY --from=build /usr/tsunami/tsunami.yaml /usr/tsunami/\nCOPY --from=build /usr/tsunami/payload_definitions.yaml /usr/tsunami/payload_definitions.yaml\n\n# Python server and the virtual environment\nCOPY --from=build /usr/tsunami/py_venv/ /usr/tsunami/py_venv\nCOPY --from=build /usr/tsunami/py_server/ /usr/tsunami/py_server\n"
  },
  {
    "path": "devel.Dockerfile",
    "content": "FROM ubuntu:latest\n\nRUN apt-get update \\\n && apt-get install -y --no-install-recommends git openjdk-21-jdk ca-certificates wget unzip python3 python3-venv \\\n && rm -rf /var/lib/apt/lists/* \\\n && rm -rf /usr/share/doc && rm -rf /usr/share/man \\\n && apt-get clean\n\n# Install a specific version of protoc for the templated plugins\nWORKDIR /usr/dependencies\nRUN mkdir /usr/dependencies/protoc \\\n    && wget https://github.com/protocolbuffers/protobuf/releases/download/v25.5/protoc-25.5-linux-x86_64.zip -O /usr/dependencies/protoc.zip \\\n    && unzip /usr/dependencies/protoc.zip -d /usr/dependencies/protoc/\nENV PATH=\"${PATH}:/usr/dependencies/protoc/bin\"\n\n# Install a specific version of Gradle\nWORKDIR /usr/dependencies\nRUN wget https://services.gradle.org/distributions/gradle-8.14.2-bin.zip -O /usr/dependencies/gradle.zip \\\n    && unzip /usr/dependencies/gradle.zip -d /usr/dependencies/ \\\n    && mv /usr/dependencies/gradle-8.14.2/ /usr/dependencies/gradle/\nENV PATH=\"${PATH}:/usr/dependencies/gradle/bin\"\n\n# Prepare the virtualenv for Python plugins\n# This is one of the few dependencies that will get carried to the final docker\n# images.\nWORKDIR /usr/tsunami/py_venv/\nCOPY plugin_server/py/requirements.in /usr/tsunami/py_venv/requirements.in\nCOPY plugin_server/py/requirements.txt /usr/tsunami/py_venv/requirements.txt\nRUN python3 -m venv /usr/tsunami/py_venv\nENV PATH=\"/usr/tsunami/py_venv/bin:${PATH}\"\nRUN pip install --require-hashes -r /usr/tsunami/py_venv/requirements.txt\n"
  },
  {
    "path": "docs/_config.yml",
    "content": "remote_theme: pages-themes/cayman@v0.2.0\nurl: https://google.github.io\nbaseurl: /tsunami-security-scanner\npaginate: 5\npaginate_path: \"/blog/page:num/\"\nplugins:\n- jekyll-remote-theme\n- jekyll-paginate\n"
  },
  {
    "path": "docs/_data/nav.yml",
    "content": "- title: \"What's new\"\n  path: /\n\n- title: \"All articles\"\n  path: /blog/\n\n- title: \"Documentation\"\n  path: /howto/\n"
  },
  {
    "path": "docs/_includes/nav.html",
    "content": "{% for nav in site.data.nav %}\n  {% if nav.subcategories != null %}\n    {% for subcategory in nav.subcategories %}\n      <a class=\"btn\" href=\"{{ subcategory.path | relative_url }}\">{{ subcategory.title }}</a>\n    {% endfor %}\n  {% elsif nav.title == page.title %}\n    <a class=\"btn\" href=\"{{ nav.path | relative_url }}\">{{ nav.title }}</a>\n  {% else %}\n    <a class=\"btn\" href=\"{{ nav.path | relative_url }}\">{{ nav.title }}</a>\n  {% endif %}\n{% endfor %}\n"
  },
  {
    "path": "docs/_layouts/default.html",
    "content": "<!DOCTYPE html>\n<html lang=\"{{ site.lang | default: \"en-US\" }}\">\n  <head>\n    <meta charset=\"UTF-8\">\n\n{% seo %}\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\">\n    <link rel=\"preload\" href=\"https://fonts.googleapis.com/css?family=Open+Sans:400,700&display=swap\" as=\"style\" type=\"text/css\" crossorigin>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"theme-color\" content=\"#157878\">\n    <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">\n    <link rel=\"stylesheet\" href=\"{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}\">\n    {% include head-custom.html %}\n  </head>\n  <body>\n    <a id=\"skip-to-content\" href=\"#content\">Skip to the content.</a>\n\n    <header class=\"page-header\" role=\"banner\">\n      <h1 class=\"project-name\">{{ page.title | default: site.title | default: site.github.repository_name }}</h1>\n      <h2 class=\"project-tagline\">{{ page.excerpt | default: site.description | default: site.github.project_tagline }}</h2>\n      {% include nav.html %}\n      {% if site.github.is_project_page %}\n        <a href=\"{{ site.github.repository_url }}\" class=\"btn\">View on GitHub</a>\n      {% endif %}\n      {% if site.show_downloads %}\n        <a href=\"{{ site.github.zip_url }}\" class=\"btn\">Download .zip</a>\n        <a href=\"{{ site.github.tar_url }}\" class=\"btn\">Download .tar.gz</a>\n      {% endif %}\n    </header>\n\n    <main id=\"content\" class=\"main-content\" role=\"main\">\n      {{ content }}\n\n      <footer class=\"site-footer\">\n        {% if site.github.is_project_page %}\n          <span class=\"site-footer-owner\"><a href=\"{{ site.github.repository_url }}\">{{ site.github.repository_name }}</a> is maintained by <a href=\"{{ site.github.owner_url }}\">{{ site.github.owner_name }}</a>.</span>\n        {% endif %}\n        <span class=\"site-footer-credits\">This page was generated by <a href=\"https://pages.github.com\">GitHub Pages</a>.</span>\n      </footer>\n    </main>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/_layouts/home.html",
    "content": "---\nlayout: none\n---\n\n{{ site.posts.first }}\n"
  },
  {
    "path": "docs/_layouts/post.html",
    "content": "---\nlayout: default\n---\n<h3>\n  Posted on {{ page.date | date_to_long_string: \"ordinal\" }}  by\n  {% for author in page.authors %}\n    {{ author.name }}\n  {% endfor %}\n</h3>\n\n{{ content }}\n"
  },
  {
    "path": "docs/_posts/2024-03-19-tsunami-network-scanner-ai-security.md",
    "content": "---\nauthors:\n- name: Annie Mao\nexcerpt: 'Interested in creating an AI-related plugin for the Tsunami network scanner and\ngetting rewarded for your efforts? See this post for details!'\ntitle: 'Tsunami Network Scanner & AI Security'\n---\n\nYou may already be familiar with the\n[Tsunami Network Scanner](https://github.com/google/tsunami-security-scanner)\nfrom our\n[Patch Rewards program](https://bughunters.google.com/about/rules/4928084514701312/patch-rewards-program-rules#tsunami-patch-rewards),\nwhich rewards external contributors for creating new\n[detector plugins](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google).\nNow with AI being on everyone's minds, we want to double down on securing open\nsource AI infrastructure via Tsunami.\n\nOn our\n[GitHub page](https://github.com/google/tsunami-security-scanner-plugins/issues),\nyou can find a list of AI-relevant **plugin & web fingerprint** implementation\nrequests tagged as \"help wanted\". **Anyone** can contribute to a Tsunami plugin\nfrom this list, and the implementation will be reviewed & rewarded under our\nTsunami Patch Rewards program, with rewards ranging from $500 to $3,133.7\n([details](https://bughunters.google.com/about/rules/4928084514701312/patch-rewards-program-rules#reward-amounts-tsunami-)).\n\nHere are the rules of engagement for implementing AI-related plugins:\n\n*   **First come, first served**: Each contributor can pick up any of the\n    unassigned plugins, but please only take one **at a time**.\n*   **Reassignment of inactive plugins**: If an assigned plugin has not been\n    worked on for **over a week**, then the Tsunami review panel will unassign\n    the contributor from the plugin. The plugin request is returned to the\n    free-for-all pool.\n*   **Vulnerability Research**: As a first step, the contributor has to provide\n    detailed vulnerability research & an implementation design for the plugin to\n    the review panel, and then wait for confirmation from the review panel\n    before moving on to the implementation stage.\n*   **Testbed Requirement**: All test containers or configurations for each\n    plugin have to be submitted to\n    [google/security-testbeds](https://github.com/google/security-testbeds).\n*   **Review Priority**: If a contributor already has a different plugin in the\n    review queue, we will prioritize reviewing the ML plugin, unless the\n    originally provided plugin is critical.\n\nFinally, we welcome you to propose new plugins that address critical security\nissues in AI-serving frameworks and related tools on our\n[GitHub page](https://github.com/google/tsunami-security-scanner-plugins/issues).\nFor faster acceptance, when sharing your proposal, please provide context on how\na given service is used in the AI ecosystem.\n\nWe're looking forward to collaborating with you to keep AI infrastructure\nsecure!\n"
  },
  {
    "path": "docs/_posts/2025-06-18-changes-to-tsunami.md",
    "content": "---\nauthors:\n\n- name: Pierre Precourt\nexcerpt: ‘Templated plugins are now the default for writing plugins and making the reward program more efficient.'\ntitle: 'Changes to Tsunami'\n---\n\n# Changes to Tsunami\n\n## Improving the situation on the Patch Reward Program (PRP)\n\nWhether you have been a long time contributor or a newcomer to Tsunami, you\nmight have noticed that it takes a long time before a contribution is merged. We\nthought it might be valuable to provide some context into why this happens:\n\n-   First and foremost, the Tsunami team is a rather small team (1-2 people)\n    with varying priorities.\n-   Even though we are working with partners to reduce the time to review\n    contributions, it still takes us a lot of time to merge contributions. That\n    is because the external version of Tsunami and the one we are using\n    internally are slightly different, most notably their build systems.\n\nNow, this does not mean that we should not strive to make the situation better.\nOne bet that we are taking is templated plugins (introduced later in this\narticle). Without going into details, this new type of plugin should abstract\nthe differences between the two build systems, in a way that should make merging\nplugins much easier for us and, thus, much faster for contributors.\n\n## Tsunami templated plugins\n\nTo cite\n[our official documentation](https://google.github.io/tsunami-security-scanner/howto/new-detector/templated/00-getting-started):\n\n> In the past, if you wanted to write a Tsunami detector, you would need to\n> implement your detector using Java or Python. For each, you would have to\n> write a set of tests and ensure that everything is compiling and working as\n> intended.\n\n> This process proved to be very time consuming; especially as most Tsunami\n> detectors are simply sending an HTTP request and checking the response code\n> and body content. That is why we introduced templated plugins.\n\n> We have abstracted most of the code required to write a plugin. All you need\n> to do is to write a .textproto file that describes the behavior of your plugin\n> and a _test.textproto file that describes the tests for the plugin.\n\nIn short, templated plugins allow contributors to write Tsunami plugins as if\nthey were configuration files.\n\n### Why this change?\n\nFirst and foremost, this makes writing Tsunami plugins much easier. It reduces\nadherence to the build system and to the language, which makes it accessible to\nmore contributors. Because the plugins are now in a structured format, we can\nalso perform analysis on detectors and find common mistakes in these detectors:\nthis should overall improve the quality of plugins.\n\nBut that is only from the contributors’ perspective. From a maintainer\nperspective, that also means that we can work more efficiently on changes at\nscale in the Tsunami engine. Currently, whenever we need to make a change to the\nengine, we often have to change about 100 plugins.\n\nFinally, we are having very active discussions internally to rewrite Tsunami\nentirely in Golang. If we were to decide to take this path, templated plugins\nwould help us make the migration easier.\n\n### Why not YAML?\n\nThe most frequently asked question about templated plugins is: Why not use YAML\ninstead of textproto? First, we believe that\n[YAML has a lot of issues](https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell).\n\nBut the most important reason is that textprotos are checked at compilation time\nand enforce a strong structure to our plugins (on top of being smaller).\n\n### How does this affect the Patch Reward Program (PRP)?\n\nTemplated plugins are now the default for writing plugins. **We will stop\naccepting Java and Python plugins unless there is a good reason for it (we\nunderstand that templated plugins can be limiting in some use cases)**.\n\nOther than that, no big changes. The rewards will stay the same and so will the\nqueue system. If our bet shows to be successful and templated plugins really\nreduce the time for plugins to be merged, we plan to increase the contributor\nqueue as well. This would mean that a contributor could work on more plugins in\nparallel. Stay tuned.\n\n### Will older plugins be rewritten?\n\nMost likely. We have not yet come-up with a detailed plan on how to do it, but\nwe would like to rewrite as many plugins as possible to unify them.\n\n## Tsunami releases\n\n## Tsunami version 1.0.0\n\nFor a long time, Tsunami has been in Alpha release. Internally, we have been\nusing Tsunami consistently and at scale for a while now. Thus we believe that\nTsunami is ready to be officially released in version 1.0.0.\n\n### Maven releases\n\nFor now, we are releasing most of Tsunami’s library to maven on repo central. If\nyou are depending on these artifacts, you will soon have to migrate away. We are\nplanning to change the way we are publishing Tsunami’s dependencies. The plan is\nnot finalized yet, but most likely we will publish Tsunami directly on GitHub.\n"
  },
  {
    "path": "docs/_posts/2025-10-16-october-update-tsunami-prp.md",
    "content": "# October update - Tsunami reward program\n\n## Improving the PRP situation\n\nSince our\n[last update in June](https://google.github.io/tsunami-security-scanner/2025/06/18/changes-to-tsunami.html),\nwe have made good progress on merging incoming pull requests. Not only do we now\nhave a very low amount of requests to process, but most of them are now\nimplemented with the\n[new templated language system](https://google.github.io/tsunami-security-scanner/howto/new-detector/templated/00-getting-started)\nwhich is usually faster for us to merge.\n\n**A big thank you to all of our contributors for their patience\\!**\n\n## An update on the payouts\n\nNote:\n[Our official rules](https://bughunters.google.com/about/rules/open-source/5067456626688000/tsunami-patch-rewards-program-rules)\nhave been updated accordingly.\n\nWe recently came to realize that our current payout system made the decision for\nthe reward difficult. To ensure everyone is rewarded fairly and adequately, we\nhave decided to simplify the payout system:\n\nType of detector                                     | Reward (up to dollars)\n:--------------------------------------------------: | :--------------------:\nWishlist detector                                    | 3177.13\nExposed interface detector Weak credentials detector | 2000\nOther detectors                                      | 1500\n\n### What is a wishlist detector?\n\nThis is a detector for a vulnerability that Google cares deeply about. We\nunderstand that this is outside of the control of the contributors but this is\ngenerally based on internal priorities.\n\nWe will generally make it explicit that a contribution falls in that category\nbut on the other hand, we might request that the detector is completed in a\nfaster timeline (less than a week) to justify the higher payout. Sometimes we\nwill release a wishlist to the public – if you pick up an item from that\nwishlist, you are guaranteed to fall into this category.\n\n### What happened to fingerprints?\n\nWe are not accepting new fingerprinting contributions for now. **Note that pull\nrequests already opened will be processed and paid as previously agreed upon.**\n\nWe are currently working on completely changing the way Tsunami performs\nfingerprinting. Amongst other things, we are experimenting with rewriting that\nspecific portion of the scanner in Golang to measure how well the language\nmatches our needs.\n\n## An insight into our triage decisions\n\nWe also understand that it might be difficult to understand how and why we\ndecide to accept some contributions and not others, so we wanted to provide some\nvisibility into that process.\n\nFirst and foremost, the goal of Tsunami is to find impactful vulnerabilities.\n**This generally means that we want to identify security issues that have a\nstrong impact; this generally translates to remote code execution (RCE).**\n\n**The questions that we are always asking ourselves:**\n\n*   Can this be turned into a full-chain to remote code execution?\n*   Can the full-chain be implemented in the detector? Or be reliable enough\n    that it can ascertain the full chain exploitability?\n\nHere is an example table for common vulnerability types:\n\n| Category                 | Decision        |\n| :----------------------: | :-------------: |\n| XSS                      | Rejected        |\n| CSRF                     | Rejected        |\n| SSRF                     | Likely rejected |\n| SQLi                     | Likely rejected |\n| Local file include       | It depends      |\n| Path traversal           | It depends      |\n| XXE                      | It depends      |\n| Remote file include      | Likely accepted |\n| File upload              | Likely accepted |\n| Exposed interface        | Likely accepted |\n| Authentication bypass    | Likely accepted |\n| Weak credentials         | Likely accepted |\n| OS command injection     | Likely accepted |\n\nAs mentioned before, that decision depends heavily on the ability to create a\nfull chain of exploitation that leads to remote code execution.\n\n## Tsunami versioning\n\nAs we previously announced, we are slowly dropping Maven releases in favor of\nour Docker images and direct dependencies to GitHub. We are already not\npublishing any new artifacts to Maven and encourage you **strongly** to migrate\nto building with the GitHub code.\n\nThis change slightly increases overall maintenance of plugins for larger changes\nof the core but ensures that issues do not go unnoticed and also makes\ndependencies management a lot easier for us.\n"
  },
  {
    "path": "docs/about/index.md",
    "content": "# About Tsunami\n\n## <a name=\"why_tsunami\"></a>Why Tsunami?\n\nWhen security vulnerabilities or misconfigurations are actively exploited by\nattackers, organizations need to react quickly in order to protect potentially\nvulnerable assets. As attackers increasingly invest in automation, the time\nwindow to react to a newly released, high severity vulnerability is usually\nmeasured in hours. This poses a significant challenge for large organizations\nwith thousands or even millions of internet-connected systems. In such\nhyperscale environments, security vulnerabilities must be detected and ideally\nremediated in a fully automated fashion. To do so, information security teams\nneed to have the ability to implement and roll out detectors for novel security\nissues at scale in a very short amount of time. Furthermore, it is important\nthat the detection quality is consistently very high. To solve these challenges,\nwe created Tsunami - an extensible network scanning engine for detecting high\nseverity vulnerabilities with high confidence in an unauthenticated manner.\n\n## <a name=\"goal\"></a>Goals and Philosophy\n\n*   Tsunami supports small manually curated set of vulnerabilities\n*   Tsunami detects high severity, RCE-like vulnerabilities, which often\n    actively exploited in the wild\n*   Tsunami generates scan results with high confidence and minimal\n    false-positive rate.\n*   Tsunami detectors are easy to implement.\n*   Tsunami is easy to scale, executes fast and scans non-intrusively.\n\n## <a name=\"naming\"></a>Naming\n\nThe name \"Tsunami\" comes from the fact that this scanner is meant be used as part of a larger system to warn owners about automated \"attack waves\". Automated attacks are similar to tsunamis in the way that they come suddenly, without prior warning and can cause a lot of damage to organizations if no precautions are taken. The term \"Tsunami Early Warning System Security Scanning Engine\" is quite long and thus the name got abbreviated to Tsunami Scanning Engine, or Tsunami. Hence, the name is not an analogy to tsunamis itself, but to a system that detects them and warns everyone about them.\n"
  },
  {
    "path": "docs/assets/css/style.scss",
    "content": "---\n---\n\n@import '{{ site.theme }}';\n\n.pagination {\n  text-align: center;\n  background-color: #eee;\n  border-radius: 0.3rem;\n  padding: 3px;\n  margin-top: 0.75rem;\n  margin-bottom: 0.75rem;\n}\n"
  },
  {
    "path": "docs/blog/index.html",
    "content": "---\ntitle: Posts\nlayout: default\n---\n\n{% for post in paginator.posts %}\n  <h1><a href=\"{{ post.url | relative_url }}\">{{ post.title }}</a></h1>\n  <h3 class=\"author\">\n    Posted on <span class=\"date\">{{ post.date | date_to_long_string: \"ordinal\" }}</span>\n  </h3>\n  <div class=\"content\">\n    {{ post.excerpt }}\n  </div>\n{% endfor %}\n\n<div class=\"pagination\">\n  {% if paginator.previous_page %}\n    <a href=\"{{ paginator.previous_page_path | relative_url }}\">\n      Previous\n    </a> ::\n  {% endif %}\n  <span class=\"page_number \">\n    {{ paginator.page }} of {{ paginator.total_pages }}\n  </span>\n  {% if paginator.next_page %}\n    :: <a href=\"{{ paginator.next_page_path | relative_url }}\">Next</a>\n  {% endif %}\n</div>\n"
  },
  {
    "path": "docs/contribute/code-of-conduct.md",
    "content": "# Google Open Source Community Guidelines\n\nAt Google, we recognize and celebrate the creativity and collaboration of open\nsource contributors and the diversity of skills, experiences, cultures, and\nopinions they bring to the projects and communities they participate in.\n\nEvery one of Google's open source projects and communities are inclusive\nenvironments, based on treating all individuals respectfully, regardless of\ngender identity and expression, sexual orientation, disabilities,\nneurodiversity, physical appearance, body size, ethnicity, nationality, race,\nage, religion, or similar personal characteristic.\n\nWe value diverse opinions, but we value respectful behavior more.\n\nRespectful behavior includes:\n\n*   Being considerate, kind, constructive, and helpful.\n*   Not engaging in demeaning, discriminatory, harassing, hateful, sexualized,\n    or physically threatening behavior, speech, and imagery.\n*   Not engaging in unwanted physical contact.\n\nSome Google open source projects [may adopt][] an explicit project code of\nconduct, which may have additional detailed expectations for participants. Most\nof those projects will use our [modified Contributor Covenant][].\n\n[may adopt]: https://opensource.google/docs/releasing/preparing/#conduct\n[modified Contributor Covenant]: https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/\n\n## Resolve peacefully\n\nWe do not believe that all conflict is necessarily bad; healthy debate and\ndisagreement often yields positive results. However, it is never okay to be\ndisrespectful.\n\nIf you see someone behaving disrespectfully, you are encouraged to address the\nbehavior directly with those involved. Many issues can be resolved quickly and\neasily, and this gives people more control over the outcome of their dispute. If\nyou are unable to resolve the matter for any reason, or if the behavior is\nthreatening or harassing, report it. We are dedicated to providing an\nenvironment where participants feel welcome and safe.\n\n## Reporting problems\n\nSome Google open source projects may adopt a project-specific code of conduct.\nIn those cases, a Google employee will be identified as the Project Steward, who\nwill receive and handle reports of code of conduct violations. In the event that\na project hasn’t identified a Project Steward, you can report problems by\nemailing opensource@google.com.\n\nWe will investigate every complaint, but you may not receive a direct response.\nWe will use our discretion in determining when and how to follow up on reported\nincidents, which may range from not taking action to permanent expulsion from\nthe project and project-sponsored spaces. We will notify the accused of the\nreport and provide them an opportunity to discuss it before any action is taken.\nThe identity of the reporter will be omitted from the details of the report\nsupplied to the accused. In potentially harmful situations, such as ongoing\nharassment or threats to anyone's safety, we may take action without notice.\n\n*This document was adapted from the [IndieWeb Code of Conduct][] and can also be\nfound at <https://opensource.google/conduct/>.*\n\n[IndieWeb Code of Conduct]: https://indieweb.org/code-of-conduct\n"
  },
  {
    "path": "docs/contribute/contributing.md",
    "content": "# How to Contribute\n\nWe'd love to accept your patches and contributions to this project. There are\njust a few small guidelines you need to follow.\n\n## Contributor License Agreement\n\nContributions to this project must be accompanied by a Contributor License\nAgreement. You (or your employer) retain the copyright to your contribution;\nthis simply gives us permission to use and redistribute your contributions as\npart of the project. Head over to <https://cla.developers.google.com/> to see\nyour current agreements on file or to sign a new one.\n\nYou generally only need to submit a CLA once, so if you've already submitted one\n(even if it was for a different project), you probably don't need to do it\nagain.\n\n## Code reviews\n\nAll submissions, including submissions by project members, require review. We\nuse GitHub pull requests for this purpose. Consult\n[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more\ninformation on using pull requests.\n\n## Community Guidelines\n\nThis project follows\n[Google's Open Source Community Guidelines](https://opensource.google/conduct/).\n"
  },
  {
    "path": "docs/contribute/index.md",
    "content": "# Contributing to Tsunami\n\n{% include_relative contributing.md %}\n\n{% include_relative code-of-conduct.md %}\n"
  },
  {
    "path": "docs/howto/common-patterns.md",
    "content": "# Common detector patterns\n\n### Running only for a specific service\n\n*Use case: I want my detector to only run for web applications or for\napplication X.*\n\nThere exist currently two way in Tsunami to filter the service type:\n\n1. Using annotations (preferred)\n\nThe\n[`@ForWebService`](https://github.com/google/tsunami-security-scanner/blob/master/plugin/src/main/java/com/google/tsunami/plugin/annotations/ForWebService.java)\nand\n[`@ForServiceName({\"X\", \"Y\"})`](https://github.com/google/tsunami-security-scanner/blob/master/plugin/src/main/java/com/google/tsunami/plugin/annotations/ForServiceName.java)\nannotations can be used to instruct the core engine of Tsunami to only run this\nplugin if the service was a web service or a service with name `X` or `Y`. The\nname of the service is obtained during the discovery phase. It currently is the\nexact same service name as NMAP would report (e.g. `http`, `https`, `ssh`).\n\n2. Using filtering (web service only)\n\nThe\n[`NetworkServiceUtils.isWebService()`](https://github.com/google/tsunami-security-scanner/blob/483f9ea5b7c69e8802353e0dcd293c2f35eaa4aa/common/src/main/java/com/google/tsunami/common/data/NetworkServiceUtils.java#L69)\ncan be used when performing filtering to ensure only `NetworkService` that were\nidentified as web service will be processed.\n\nExample usage:\n\n```java\nsomeNetworkServiceCollection.stream()\n  .filter(NetworkServiceUtils::isWebService)\n  // {...}\n```\n\n### Building URLs\n\n*Use case: My detector targets a web application. How do I build the URL?*\n\nWhen writing your plugins, there are a few things that you should NOT have to\ncare about and that the Tsunami core engine should resolve for you:\n\n- Is the service using HTTP or HTTPS?\n- How do I construct the URL from the `NetworkService`?\n- What if NMAP fails to identify the service as HTTP and I still want to build\n  an URL?\n\nAll of these concerns are addressed in the core engine of Tsunami and you can\nsimply use the URL building API:\n[`NetworkServiceUtils.buildWebApplicationRootUrl()`](https://github.com/google/tsunami-security-scanner/blob/483f9ea5b7c69e8802353e0dcd293c2f35eaa4aa/common/src/main/java/com/google/tsunami/common/data/NetworkServiceUtils.java#L173)\n\n#### DO\n\n```java\nString myUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService)\n  + MY_VULNERABLE_ENDPOINT;\n```\n\n#### DO NOT\n\nThe following **SHOULD NOT BE USED**:\n\n1. Defining a `buildTarget` intermediate function is redundant and most of the\ntime not necessary:\n\n```java\n private static StringBuilder buildTarget(NetworkService networkService) {\n    StringBuilder targetUrlBuilder = new StringBuilder();\n    if (NetworkServiceUtils.isWebService(networkService)) {\n      targetUrlBuilder.append(NetworkServiceUtils.buildWebApplicationRootUrl(networkService));\n    } else {\n      targetUrlBuilder\n          .append(\"http://\")\n          .append(toUriAuthority(networkService.getNetworkEndpoint()))\n          .append(\"/\");\n    }\n    targetUrlBuilder.append(MY_VULNERABLE_ENDPOINT);\n    return targetUrlBuilder;\n  }\n```\n\n2. Using `String.Format` does not make use of the information obtained during\nthe discovery phase and is error prone:\n\n```java\nvar uriAuthority = NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint());\nvar loginPageUrl = String.format(\"http://%s/%s\", uriAuthority, MY_VULNERABLE_ENDPOINT);\n```\n\n### Adding command line arguments consumed by the detector\n\n*Use case: I need command line arguments for my detector*\n\nTsunami uses [jCommander](https://jcommander.org/) for command line argument\nparsing. In order to add new CLI arguments for your plugin, first define the\ndata class for holding all the arguments. You can follow the jCommander tutorial\nto learn more about this tool.\n\nFor example:\n\n```java\n@Parameters(separators = \"=\")\npublic final class MyPluginArgs implements CliOption {\n  @Parameter(names = \"--param\", description = \"Description for param.\")\n  public String param;\n\n  @Override\n  public void validate() {\n    // Validate the command line value.\n  }\n}\n```\n\nThen, the CLI flags will be parsed and an instance of this class will be created\nby the main scanner at start-up time. In order to use this class in your plugin,\nyou can directly inject this data class into your plugin's constructor.\n\n```java\npublic final class MyVulnDetector implements VulnDetector {\n  private final MyPluginArgs args;\n\n  @Inject\n  MyVulnDetector(MyPluginArgs args) {\n    this.args = checkNotNull(args);\n  }\n\n  // {...}\n}\n```\n\n### Adding configuration properties consumed by the detector\n\n*Use case: How do I add configurable option for my detector?*\n\nTsunami supports loading configs from a YAML file and uses\n[snakeyaml](https://bitbucket.org/asomov/snakeyaml/wiki/Documentation) to parse\nthe YAML config files. In order to add configuration properties to your plugin,\nfirst you need to define a data class for holding all the configuration values.\n\nNOTE: Currently Tsunami only supports standard Java data types for\nconfigurations like `String`, numbers (`int`, `long`, `float`, `double`, etc),\n`List` and `Map`.\n\n```java\n// All config classes must be annotated by this ConfigProperties annotation in\n// order for Tsunami to recognize the config class.\n@ConfigProperties(prefix = \"my.plugin.configs\")\npublic class MyPluginConfigs {\n  String stringValue;\n  long longValue;\n  List<String> listValues;\n  Map<String, String> mapValues;\n}\n```\n\nThen, similar to the command line arguments, you can inject an instance of this\ndata class into your plugin's constructor.\n\n```java\npublic final class MyVulnDetector implements VulnDetector {\n  private final MyPluginConfigs configs;\n\n  @Inject\n  MyVulnDetector(MyPluginConfigs configs) {\n    this.configs = checkNotNull(configs);\n  }\n\n  // {...}\n}\n```\n\nThe scanner will parse the configuration file when it starts, create an instance\nof the data class from the config data, and inject the instance into your\nplugin.\n\nFollowing is an example config file for the previously defined `MyPluginConfigs`\nobject.\n\n```yaml\nmy:\n  plugin:\n    configs:\n      # Config name can be exact match.\n      stringValue: \"example value\"\n      # Or matching via snake_case.\n      long_value: 123\n      list_values:\n      - \"a\"\n      - \"b\"\n      - \"c\"\n      mapValues:\n        key1: \"value1\"\n        key2: \"value2\"\n```\n\nTo use the configuration file, you need to set the `tsunami.config.location`\noption when calling Tsunami.\n\n### Creating TCP sockets with proper timeouts\n\n*Use case: My detector needs to create raw TCP or SSL sockets to communicate\nwith a service.*\n\nWhen writing detectors that need to create raw TCP or SSL sockets, you **must**\nuse the `TsunamiSocketFactory` API instead of directly creating sockets through\n`javax.net.SocketFactory` or `javax.net.ssl.SSLSocketFactory`. This ensures that\nall sockets have proper timeout configurations, preventing plugins from hanging\nindefinitely when servers don't respond.\n\n#### Injecting the socket factory\n\n```java\npublic final class MyVulnDetector implements VulnDetector {\n  private final TsunamiSocketFactory socketFactory;\n\n  @Inject\n  MyVulnDetector(TsunamiSocketFactory socketFactory) {\n    this.socketFactory = checkNotNull(socketFactory);\n  }\n\n  // {...}\n}\n```\n\n#### Creating a plain TCP socket with default timeouts\n\n```java\nprivate final TsunamiSocketFactory socketFactory;\n\n// Socket will have connect timeout of 10s and read timeout of 30s (configurable)\nSocket socket = socketFactory.createSocket(\"example.com\", 80);\ntry {\n  // Use the socket...\n  OutputStream out = socket.getOutputStream();\n  InputStream in = socket.getInputStream();\n  // ...\n} finally {\n  socket.close();\n}\n```\n\n#### Creating a socket with custom timeouts\n\n```java\nSocket socket = socketFactory.createSocket(\n    \"example.com\",\n    80,\n    Duration.ofSeconds(5),   // Connect timeout\n    Duration.ofSeconds(15)   // Read timeout\n);\n```\n\n#### Creating an SSL/TLS socket\n\n```java\n// Create an SSL socket with default timeouts\nSSLSocket sslSocket = socketFactory.createSslSocket(\"secure.example.com\", 443);\n\n// Or with custom timeouts\nSSLSocket sslSocket = socketFactory.createSslSocket(\n    \"secure.example.com\",\n    443,\n    Duration.ofSeconds(5),\n    Duration.ofSeconds(15)\n);\n```\n\n#### Upgrading a plain socket to SSL (STARTTLS)\n\n```java\n// First, create a plain socket\nSocket plainSocket = socketFactory.createSocket(\"mail.example.com\", 25);\n\n// Send STARTTLS command...\n// ...\n\n// Then upgrade to SSL\nSSLSocket sslSocket = socketFactory.wrapWithSsl(\n    plainSocket,\n    \"mail.example.com\",\n    25,\n    true  // autoClose\n);\n```\n\n#### Configuring socket timeouts\n\nSocket timeouts can be configured via:\n\n1.  **Configuration file** (tsunami.yaml):\n\n```yaml\ncommon:\n  net:\n    socket:\n      connect_timeout_seconds: 10\n      read_timeout_seconds: 30\n      trust_all_certificates: true\n```\n\n1.  **Command line options**:\n\n```bash\n--socket-connect-timeout-seconds=10\n--socket-read-timeout-seconds=30\n--socket-trust-all-certificates=true\n```\n\nCLI options take precedence over configuration file settings.\n\n#### DO NOT create sockets directly\n\n**NEVER** create sockets directly like this:\n\n```java\n// BAD: No timeout configured, socket may hang forever\nSocket socket = new Socket(\"example.com\", 80);\n\n// BAD: Even with SocketFactory, timeout is missing\nSocket socket = SocketFactory.getDefault().createSocket(\"example.com\", 80);\n```\n\nAlways use `TsunamiSocketFactory` to ensure proper timeout handling.\n"
  },
  {
    "path": "docs/howto/howto.md",
    "content": "# Build and run Tsunami\n\n## Tsunami docker's environment\n\nWe provide a set of Docker images to help you build and use Tsunami. We provide\na minimal (scratch) image for:\n\n- The core engine only;\n- The callback server only;\n- Each category of plugin;\n\nUsing these minimal images is not recommended, instead we recommend composing\non top of them.\n\n![docker-images](img/docker-images.png)\n\n## Running the latest version of Tsunami\n\nIf you just want to run the latest version of Tsunami, without having to\nrecompile anything, you can directly use the latest full image of Tsunami.\n\n```sh\n# Important: If you built a local version of the container, do not pull as it\n# will overwrite your changes. Otherwise, do pull as you would be using a stale\n# version of the image.\n$ docker pull ghcr.io/google/tsunami-scanner-full\n\n# Run the image\n$ docker run -it --rm ghcr.io/google/tsunami-scanner-full bash\n\n# If you want to use Python plugins\n(docker) $ tsunami-py-server >/tmp/py_server.log 2>&1 &\n\n# If you want to use the callback server\n(docker) $ tsunami-tcs >/tmp/tcs_server.log 2>&1 &\n\n# Run Tsunami\n# Note: If you did not start the python server, omit the `--python-` arguments.\n(docker) $ tsunami --ip-v4-target=127.0.0.1 --python-plugin-server-address=127.0.0.1 --python-plugin-server-port=34567\n```\n\nThis images contains everything necessary under the `/usr/tsunami` directory.\n\nTo use the callback server, you might have to setup port forwarding with your\ndocker container when starting it. We encourage you to refer to the `-p` option\nof Docker.\n\nA few tips:\n\n- Only scan one port: `--port-ranges-target`\n- Only run your detector: `--detectors-include=\"detector-name\"`; where detector\nname is the name defined in `PluginInfo` section for Java and Python plugins and\nthe `info.name` section on templated plugins.\n\n## Using docker to build Tsunami\n\nIn this section, we go through the different ways to compile the core engine\nor a plugin locally so that you can test your changes.\n\nIt assumes that you have cloned both the `tsunami-security-scanner` and\n`tsunami-security-scanner-plugins` repositories.\n\n### Rebuilding the core engine\n\nIf you need to make changes to the core engine during the development cycle, you\nwill have to perform the following actions to test your change:\n\n- Rebuild the core engine container;\n\n```sh\n# Build the core engine container\n$ cd tsunami-security-scanner\n$ docker build -t ghcr.io/google/tsunami-scanner-core:latest -f core.Dockerfile .\n```\n\n- Rebuild all plugins to ensure your change is compatible\n\nIMPORTANT: Your changes must be committed via git to be picked. They do not need\nto be pushed to GitHub, they can be local only.\n\nIn the following example, we will use docker volumes to mount our changes to\n`/usr/tsunami/repos/tsunami-security-scanner`. This assumes that our\n`tsunami-security-scanner` and `tsunami-security-scanner-plugins` clones are in\n`/tsunami/` on our host. Also, our changes are committed to the `master` branch.\nYou can change the commands accordingly if your repositories path or branch are\ndifferent.\n\n```sh\n$ cd tsunami-security-scanner-plugins\n```\n\nFirst, we need to change the `Dockerfile` to use our changes:\n\n```diff\n-ENV GITREPO_TSUNAMI_CORE=\"https://github.com/google/tsunami-security-scanner.git\"\n-ENV GITBRANCH_TSUNAMI_CORE=\"stable\"\n+ENV GITREPO_TSUNAMI_CORE=\"/usr/tsunami/repos/tsunami-security-scanner\"\n+ENV GITBRANCH_TSUNAMI_CORE=\"master\"\n```\n\nWe also need to instruct docker to bind our changes in `/usr/tsunami/repos`:\n\n```diff\n- RUN gradle build\n+ RUN --mount=type=bind,source=/tsunami-security-scanner,target=/usr/tsunami/repos/tsunami-security-scanner \\\n+     gradle build\n+\n```\n\nThen we can rebuild all plugins in one swoop:\n\n```sh\n$ docker build -t ghcr.io/google/tsunami-plugins-all:latest --build-arg=TSUNAMI_PLUGIN_FOLDER=tsunami-security-scanner-plugins -f tsunami-security-scanner-plugins/Dockerfile /tsunami/\n```\n\n- Rebuild the `-full` container;\n\n```sh\n$ cd tsunami-security-scanner\n```\n\nWe need to change the `full.Dockerfile` to use our newly created container:\n\n```diff\n# Plugins\n- FROM ghcr.io/google/tsunami-plugins-google:latest AS plugins-google\n- FROM ghcr.io/google/tsunami-plugins-templated:latest AS plugins-templated\n- FROM ghcr.io/google/tsunami-plugins-doyensec:latest AS plugins-doyensec\n- FROM ghcr.io/google/tsunami-plugins-community:latest AS plugins-community\n- FROM ghcr.io/google/tsunami-plugins-govtech:latest AS plugins-govtech\n- FROM ghcr.io/google/tsunami-plugins-facebook:latest AS plugins-facebook\n- FROM ghcr.io/google/tsunami-plugins-python:latest AS plugins-python\n+ FROM ghcr.io/google/tsunami-plugins-all:latest AS plugins-all\n\n{...}\n\n- COPY --from=plugins-google /usr/tsunami/plugins/ /usr/tsunami/plugins/\n- COPY --from=plugins-templated /usr/tsunami/plugins/ /usr/tsunami/plugins/\n- COPY --from=plugins-doyensec /usr/tsunami/plugins/ /usr/tsunami/plugins/\n- COPY --from=plugins-community /usr/tsunami/plugins/ /usr/tsunami/plugins/\n- COPY --from=plugins-govtech /usr/tsunami/plugins/ /usr/tsunami/plugins/\n- COPY --from=plugins-facebook /usr/tsunami/plugins/ /usr/tsunami/plugins/\n- COPY --from=plugins-python /usr/tsunami/py_plugins/ /usr/tsunami/py_plugins/\n+ COPY --from=plugins-all /usr/tsunami/plugins/ /usr/tsunami/plugins/\n```\n\nAnd then rebuild it:\n\n```sh\n$ docker build -t ghcr.io/google/tsunami-scanner-full:latest -f full.Dockerfile .\n```\n\n- Run the scanner to check that everything works.\n\nSee the \"Running the latest version of Tsunami\" section on this page to run\nTsunami with the newly built image. DO NOT perform a docker pull.\n\n### Rebuilding a whole category of plugins\n\nTsunami groups plugins per categories. From the root folder of the plugin\nrepository, you can see that the categories are `google`, `community`,\n`templated` and so on.\n\nOur docker images are built separately for each category. The same Dockerfile\nis used, but it is parameterized to use a different folder with\n`TSUNAMI_PLUGIN_FOLDER`.\n\n```sh\n$ cd tsunami-security-scanner-plugins\n$ build -t ghcr.io/google/tsunami-plugins-category:latest --build-arg TSUNAMI_PLUGIN_FOLDER=category .\n\n# For example with the community category:\n$ build -t ghcr.io/google/tsunami-plugins-community:latest --build-arg TSUNAMI_PLUGIN_FOLDER=community .\n```\n\nFor **Python plugins**, you need to use the dedicated Dockerfile, which only\nsupports bundling all plugins:\n\n```sh\n$ cd tsunami-security-scanner-plugins\n$ build -t ghcr.io/google/tsunami-plugins-python:latest -f python.Dockerfile .\n```\n\nOnce you have rebuilt the categories that you need, you can rebuild the `-full`\nimage:\n\n```sh\n$ cd tsunami-security-scanner\n$ docker build -t ghcr.io/google/tsunami-scanner-full:latest -f full.Dockerfile .\n```\n\nThen follow \"Running the latest version of Tsunami\" to use this new image. DO\nNOT perform a `docker pull`.\n\n### Building an image for one plugin\n\nNow, if during development you only wish to build your plugin, you can do so\nby creating a new local-only category.\n\nBefore you start, you will need to change the definition of the\n`full.Dockerfile` file:\n\n- Add a `FROM` directive in the Plugins section:\n\n```diff\nFROM ghcr.io/google/tsunami-plugins-python:latest AS plugins-python\n+ FROM ghcr.io/google/tsunami-plugins-local:latest AS plugins-local\n```\n\n- Add a `COPY` directive in the section that copies everything:\n\n```diff\nCOPY --from=plugins-python /usr/tsunami/py_plugins/ /usr/tsunami/py_plugins/\n+ COPY --from=plugins-local /usr/tsunami/plugins/ /usr/tsunami/plugins/\n```\n\nThen, you can build the actual image containing only your plugin:\n\n```sh\n$ cd tsunami-security-scanner-plugins\n$ build -t ghcr.io/google/tsunami-plugins-local:latest --build-arg TSUNAMI_PLUGIN_FOLDER=path/to/my/plugin .\n```\n\nFinally, compile the `-full` image:\n\n```sh\n$ cd tsunami-security-scanner\n$ docker build -t ghcr.io/google/tsunami-scanner-full:latest -f full.Dockerfile .\n```\n\nThen follow \"Running the latest version of Tsunami\" to use this new image. DO\nNOT perform a `docker pull`.\n\n**Python plugins** do not support building only one plugin. See building the\nwhole category instead.\n\n## Building Tsunami without docker\n\nWe do not provide support for building Tsunami outside of our docker\nenvironment.\n\nYou can use the Dockerfile provided in the repositories to build your own\ntoolchain if you so wish.\n"
  },
  {
    "path": "docs/howto/index.md",
    "content": "# Tsunami documentation\n\nWelcome to the Tsunami community, we are thrilled that you want to contribute.\nThis page contains information to get you started with your first contributions.\n\n## Contributing to Google code\n\n- [Contributing rules]({{ site.baseurl }}/contribute/index)\n\n## Understanding Tsunami\n\n- [About Tsunami]({{ site.baseurl }}/about/index)\n- [How tsunami works]({{ site.baseurl }}/howto/orchestration)\n\n## Building and running Tsunami\n\n- [How to build and run Tsumami]({{ site.baseurl }}/howto/howto)\n\n## Writing plugins\n\n### Vulnerability detectors\n\n- [Using our custom configuration format]({{ site.baseurl }}/howto/new-detector/templated/00-getting-started) (preferred approach)\n- [Using Java]({{ site.baseurl }}/howto/new-detector/new-detector-java)\n- Using Python *(not documented yet)*\n\n### Weak credentials detectors\n\nNot documented yet.\n\n### Fingerprinting plugins\n\nNot documented yet.\n\n## Other guides\n\n- [Common detector patterns for Java plugins]({{ site.baseurl }}/howto/common-patterns)\n  (i.e. \"How do I do that?!\")\n"
  },
  {
    "path": "docs/howto/new-detector/new-detector-java.md",
    "content": "# Writing a Tsunami detector (Java)\n\nNOTE: We now expect you to write plugins using the templated format first. Only\nresort to Java or Python if the plugin cannot be written with the templated\nformat.\n\n## Overview\n\nEach Tsunami detector needs the following pieces which we will create in this\ntutorial:\n\n*   A plugin name that is unique among all enabled Tsunami plugins.\n*   A set of build rules for [Gradle](https://gradle.org/) (external build)\n*   A `VulnDetector` that implements the vulnerability detection logic.\n*   A `PluginBootstrapModule` that provides necessary Guice bindings for the\n    detector.\n*   An optional `CliOption` that captures all the supported command line flags\n    for the detector.\n*   An optional `ConfigProperties` that captures all the supported configuration\n    for the detector.\n\n## 1. Fork the examples\n\nTsunami provides a few example implementations of a `VulnDetector` plugin. The\nexamples live in the\n[examples directory](https://github.com/google/tsunami-security-scanner-plugins/tree/master/examples)\n\n*   Update Java package names. The example `VulnDetector` plugin is defined\n    under `com.google.tsunami.plugins.example` package. Refactor the package and\n    class name according to your detector implementation.\n*   Give a meaningful description to the Gradle build rule at `build.gradle`. We\n    will work on the Gradle build rule later.\n*   Rewrite the `README.md` file to have a good explanation of your\n    `VulnDetector` plugin.\n\n## 2. Putting the detector together\n\n### 2.a - `PluginInfo` annotation\n\nAll Tsunami plugins must be annotated by the `PluginInfo` annotation. Otherwise\nit cannot be identified by Tsunami scanner at runtime. This annotation provides\nthe general information about the plugin to the core scanner.\n\nFollowing is an example usage of the `PluginInfo` annotation from our\n`WordPressInstallPageDetector`.\n\n```java\n@PluginInfo(\n    // VULN_DETECTION PluginType is required for a VulnDetector plugin.\n    type = PluginType.VULN_DETECTION,\n    // This gives a human readable name for your VulnDetector. Can be different\n    // from your class name.\n    name = \"WordPressInstallPageDetector\",\n    // The current version of your plugin.\n    version = \"0.1\",\n    // A detailed description about what this plugin does.\n    description =\n        \"This detector checks whether a WordPress install is unfinished. An unfinished WordPress\"\n            + \" installation exposes the /wp-admin/install.php page, which allows attacker to set\"\n            + \" the admin password and possibly compromise the system.\",\n    // The author of this plugin.\n    author = \"Tsunami Team (tsunami-dev@google.com)\",\n    // The guice module that bootstraps this plugin.\n    bootstrapModule = WordPressInstallPageDetectorBootstrapModule.class)\n```\n\n### 2.b - Define the `VulnDetector` plugin\n\nEach vulnerability detector plugin is an implementation of the `VulnDetector`\ninterface. For this step we only explain the placeholder code, later you'll need\nto provide implementations for the class itself.\n\nFollowing is an example placeholder code from the\n`WordPressInstallPageDetector`:\n\n```java\n// ...\n// annotations\n// ...\npublic final class WordPressInstallPageDetector implements VulnDetector {\n  // See https://github.com/google/flogger for details.\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  // Tsunami uses Guice (https://github.com/google/guice) to manage the\n  // dependencies.\n  @Inject\n  WordPressInstallPageDetector(\n    // Tsunami provides a UtcClock for production and FakeUtcClock for\n    // testing purposes.\n    @UtcClock Clock utcClock,\n    // You can also inject other useful dependencies to your plugin code, e.g.\n    // inject HttpClient if you need to interact with a web server.\n    HttpClient httpClient) {\n  }\n\n  // The entrypoint of the VulnDetector. We will explain this later.\n  @Override\n  public DetectionReportList detect(\n    TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n    // implement me.\n  }\n}\n```\n\n### 2.c - Implement the main detection logic\n\nThe main logic of the detection is expected to happen in the `detect` method,\nwhich expects two arguments:\n\n1.  [The `TargetInfo` proto](https://github.com/google/tsunami-security-scanner/blob/master/proto/reconnaissance.proto).\n   This proto contains information that were gathered during the fingerprinting\n   and discovery phase.\n1.  [The `NetworkService` list](https://github.com/google/tsunami-security-scanner/blob/master/proto/network_service.proto).\n    This list contains all the identified network services that match the\n    service filtering annotations.\n\nThe main detection logic usually iterates over all the elements of the\n`matchedServices` parameter and checks whether the `NetworkService` is\nvulnerable to the vulnerability your plugin checks for. If any service is\nvulnerable, you'll need to build a `DetectionReport` proto that explains the\nidentified vulnerability.\n\nFollowing is an example implementation from our `WordPressInstallPageDetector`:\n\n```java\n@Override\npublic DetectionReportList detect(\n    TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n  return DetectionReportList.newBuilder()\n      .addAllDetectionReports(\n          matchedServices.stream()\n              // The WordPressInstallPageDetector only works for web services.\n              .filter(NetworkServiceUtils::isWebService)\n              // Detection logic that checks whether a web service exposes\n              // a wordpress installation page. Omitted here for simplicity.\n              .filter(this::isServiceVulnerable)\n              // Build a DetectionReport when the web service is vulnerable.\n              .map(networkService -> buildDetectionReport(targetInfo, networkService))\n              .collect(toImmutableList()))\n      .build();\n}\n\nprivate DetectionReport buildDetectionReport(\n    TargetInfo scannedTarget, NetworkService vulnerableNetworkService) {\n  return DetectionReport.newBuilder()\n      .setTargetInfo(scannedTarget)\n      .setNetworkService(vulnerableNetworkService)\n      .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli()))\n      .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED)\n      .setVulnerability(\n          Vulnerability.newBuilder()\n              .setMainId(\n                  VulnerabilityId.newBuilder()\n                      .setPublisher(\"GOOGLE\")\n                      .setValue(\"UNFINISHED_WORD_PRESS_INSTALLATION\"))\n              .setSeverity(Severity.CRITICAL)\n              .setTitle(\"Unfinished WordPress Installation\")\n              .setDescription(\n                  \"An unfinished WordPress installation exposes the /wp-admin/install.php page,\"\n                      + \" which allows attacker to set the admin password and possibly\"\n                      + \" compromise the system.\"))\n      .build();\n}\n```\n\n## 3. Preparing the `PluginBootstrapModule`\n\nEach Tsunami plugin must have a companion `PluginBootstrapModule` that provides\nthe required Guice bindings and registers the plugin to the core engine.\n\nCreating a `PluginBootstrampModule` is rather simple if you only need to\nregister the plugin. You only need to call `registerPlugin` within the\n`configurePlugin` method (e.g.\n[`ExampleVulnDetectorBootstrapModule`](https://github.com/google/tsunami-security-scanner-plugins/tree/master/examples/example_vuln_detector/src/main/java/com/google/tsunami/plugins/example/ExampleVulnDetectorBootstrapModule.java)).\n\nA more complete example is the\n[GenericWeakCredentialDetectorBootstrapModule](https://github.com/google/tsunami-security-scanner-plugins/blob/master/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java)\n"
  },
  {
    "path": "docs/howto/new-detector/templated/00-getting-started.md",
    "content": "\n# Getting started with templated plugins\n\nIn this documentation, you will learn how to write a plugin for Tsunami in the\ntemplated format.\n\n## What will be covered\n\n- [Introduction](01-introduction)\n\n  * What is a templated plugin?\n  * How does it work?\n  * How do I know how to write a templated plugin?\n  * Execution workflow of a templated plugin\n\n\n- [Bootstrapping the plugin](02-bootstrapping)\n\n  * Understanding the vulnerability\n  * Providing information about our plugin and the vulnerability\n  * Configuring the plugin\n\n\n- [Writing the first actions](03-first-actions)\n\n- [Putting it together in workflows](04-workflows)\n\n- [Using variables](05-variables)\n\n  * Using variables\n  * Extracting information to local variables\n  * Predefined variables\n\n\n- [Using the callback server](06-callback-server)\n\n- [Using cleanup actions](07-cleanup-actions)\n\n- [Writing unit tests](08-writing-unit-tests)\n\n- Glossaries\n\n  * [Predefined variables](glossary-predefined-variables)\n  * [Magic tests URIs when mocking HTTP](glossary-tests-magic-uri)\n\n\n- Appendixes\n\n  * [Convention: How to name a plugin](appendix-naming-plugin)\n  * [Convention: How to name an action](appendix-naming-actions)\n  * [Convention: Naming tests](appendix-naming-tests)\n  * [Using the linter](appendix-using-linter)\n\n## Let's get started!\n\n[Introduction](01-introduction)\n"
  },
  {
    "path": "docs/howto/new-detector/templated/01-introduction.md",
    "content": "\n# Introduction\n\n## What is a templated plugin?\n\nIn the past, if you wanted to write a Tsunami detector, you would need to\nimplement your detector using Java or Python. For each, you would have to write\na set of tests and ensure that everything is compiling and working as intended.\n\nThis process proved to be very time consuming; especially as most Tsunami\ndetectors are simply sending an HTTP request and checking the response code and\nbody content. That is why we introduced templated plugins.\n\n## How does it work?\n\nWe have abstracted most of the code required to write a plugin. All you need to\ndo is to write a `.textproto` file that describes the behavior of your\nplugin and a `_test.textproto` file that describes the tests for the plugin.\n\nA `.textproto` is a human-readable text representation of a protocol buffer\nmessage. If you are not familiar with protocol buffers, we recommend checking\nthe [official documentation](https://protobuf.dev/), but for our use case, you\ncan think of it as a strongly typed JSON or YAML.\n\nThe `.textproto` files are compiled into binary format and embedded as resources\nto a meta plugin that we will refer to as the templated Tsunami plugin. At\nruntime, the templated plugin will interpret the behavior described in each file\nand dynamically create a new detector for it.\n\n![High-level-overview](img/templated-how-it-works.png)\n\n## How do I know how to write a templated plugin?\n\nWe have tried to cover as much as possible in this documentation. But the\nconfiguration language is bound to evolve with time. If you have any doubt, the\nsource of truth will always be the\n[proto definition](https://github.com/google/tsunami-security-scanner-plugins/tree/master/templated/templateddetector/proto)\nwhich we aim at keeping as straightforward and commented as possible.\n\n## Execution workflow of a templated plugin\n\nEach plugin is defined by a set of two high-level concepts: **Actions** and\n**workflows**.\n\n- **Actions** are the basic unit of execution in a templated plugin. They are\nresponsible for performing one specific... well... action and returning a\nboolean value that defines whether it was successful. An example action, in\nplain English, could be:\n\n> Send an HTTP request to the target and verify that the returned status code\n> is 200\n\n- Once you have defined a set of actions, you need to be able to define an order\nin which they will be executed: this is the role of **workflows**.\n\nTo write a plugin, we need to define a set of actions and put them in a\nspecific order with workflows. But how are things executed? The engine processes\nplugins in the following way:\n\n1. Extracts all the workflows and actions defined in the plugin;\n2. Performs very basic checks to ensure that everything is well defined;\n3. Goes through all the defined workflows and execute the first that matches\nthe conditions it was defined with;\n4. Executes every action of the workflow in order until one fails or all actions\nare successful;\n5. If any action failed, there is no vulnerability. If all actions were\nsuccessful, the vulnerability is present.\n\nSteps 4 and 5 are repeated for every network service found during the port\nscanning phase of Tsunami. We call steps 4 and 5 a **run of the workflow**.\n\n## What is next\n\n[Bootstrapping the plugin](02-bootstrapping)\n"
  },
  {
    "path": "docs/howto/new-detector/templated/02-bootstrapping.md",
    "content": "\n# Bootstrapping the plugin\n\n*This section assumes that you already know how to\n[build and run Tsunami](https://google.github.io/tsunami-security-scanner/howto/howto).*\n\n## Before starting\n\nBefore we start, we encourage you to take a quick look at the setup guide for\nthe linter of the configuration language. Your plugin will be expected to pass\nall the checks in this linter. Warnings will have to be justified before being\nmerged as well.\n\n[See the instructions for the linter](appendix-using-linter)\n\n## Our vulnerable application\n\nFor this tutorial series, we will write a simple detector for a non-existing\nvulnerability `CVE-0123-12345`. We want our detector to:\n\n- *Fingerprint the application*: we only want to send other requests if we are\nsure that we are dealing with the potentially vulnerable application; So we are\ngoing to send an HTTP request to `/version` and check for the presence of the\nbanner `MyVulnerableApp` in the `Server` header;\n\n- *Exploit the application*: once we are sure that we are dealing with the right\napplication, we are going to send a `POST` request to `/exploit` with a custom\npayload `%{ print(\"tsunami_%d_marker\", 1250*1+3) }%` in the `process` field.\nThen we are going to verify that it gets executed by verifying that the answer\ncontains `tsunami_1253_marker`;\n\n*Note: In this example, we simulate a template injection vulnerability that is\ngoing to cause `%{ print(\"tsunami_%d_marker\", 1250*1+3) }%` to be interpreted.\nThe vulnerable language or the syntax have no importance. The result of\n`1250*1+3` is going to be replaced at the `%d` placeholder, resulting in\n`tsunami_1253_marker` being printed in the response.*\n\n## Providing information about our plugin\n\nLet's create a new file in our local copy of the\n[Tsunami plugin repository](https://github.com/google/tsunami-security-scanner-plugins)\nas `templated/templateddetector/plugins/cve/0123/MyVulnerableApp_CVE_0123_12345.textproto`.\n\nLet's add the header that will help linters to understand which proto definition\nwe are using:\n\n```proto\n# proto-file: third_party/tsunami_plugins/templated/templateddetector/proto/templated_plugin.proto\n# proto-message: TemplatedPlugin\n```\n\nThen we provide basic information about our plugin. Note that the name of the\nplugin is used to **uniquely identify it and thus should be unique across all\nplugins**.\n\n```proto\ninfo: {\n  type: VULN_DETECTION\n  name: \"MyVulnerableApp_CVE_0123_12345\"\n  author: \"Some developper <myemail@provider.com>\"\n  version: \"1.0\"\n}\n```\n\nNow is a good time to read about our\n[naming conventions for plugins](appendix-naming-plugin).\n\n## Information about the vulnerability\n\nLet's add information about the vulnerability itself. These are the information\nthat will be used to generate the vulnerability report and notify about the\nvulnerability.\n\nImportant: whenever a CVE is associated with the vulnerability the `related_id`\nfield must be filled with the associated CVE reference.\n\n```proto\nfinding: {\n  main_id: {\n    publisher: \"GOOGLE\"\n    value: \"SOME_PLUGIN_DETECTION\"\n  }\n  title: \"Example templated plugin\"\n  description: \"This is an example templated plugin.\"\n  recommendation: \"No recommendation, this is an example.\"\n  related_id: {\n    publisher: \"CVE\"\n    value: \"CVE-0123-12345\"\n  }\n}\n```\n\n## Configuring the plugin\n\nThe next section in our file, is the `config` section. It allows some basic\ntuning of the plugin. For now, let's keep it empty:\n\n```proto\nconfig: {}\n```\n\nBut it is important to keep in mind that this section allows:\n\n- To enable `debug` mode. In debug mode, the plugin will generate highly verbose\ndebugging messages. For example, it will log every HTTP request and response;\n- To switch the plugin to be `disabled`. Note that even when disabled (unless\nexplicitly specified in the test file) the tests for that plugin will still be\nexecuted.\n\nIf we were to run our plugin now, we would see the plugin being registered but\nwe would get a non-blocking error at runtime because we have no workflow or\naction defined:\n\n```sh\n{ ... }\nDec 31, 2024 11:07:58 AM com.google.tsunami.plugin.PluginBootstrapModule registerDynamicPlugin\nINFO: Dynamic plugin registered: MyVulnerableApp_CVE_0123_12345\n{ ... }\nDec 31, 2024 11:08:15 AM com.google.tsunami.plugins.detectors.templateddetector.TemplatedDetector detect\nINFO: Starting detector: MyVulnerableApp_CVE_0123_12345\nDec 31, 2024 11:08:15 AM com.google.tsunami.plugins.detectors.templateddetector.TemplatedDetector detect\nSEVERE: No workflow matched the current setup. Is plugin 'MyVulnerableApp_CVE_0123_12345' misconfigured?\n{ ... }\n```\n\n## What is next\n\n[Writing the first actions](03-first-actions)\n"
  },
  {
    "path": "docs/howto/new-detector/templated/03-first-actions.md",
    "content": "\n# Writing the first actions\n\nEach action is defined by a name and a subtype. A subtype defines what the\naction is able to do: For example \"HTTP request\" is a subtype that allows to\nsend an HTTP request and inspect responses.\n\nLet's start building our fingerprinting step with a very simple action:\n\n```proto\nactions: {\n  name: \"fingerprinting\"\n  http_request: {\n    method: GET\n    uri: \"/version\"\n  }\n}\n```\n\nIn that action, named `fingerprinting` we simply send a `GET` HTTP request to\n`/version` on the currently targeted service.\n\n*Note that Tsunami will only send HTTP requests to services that have been\nidentified as HTTP services during the port scanning phase.*\n\nBut that action will always succeed because it does not verify anything in the\nresponse. Let's use the `response` field and add a condition on the status code:\n\n```proto\nactions: {\n  name: \"fingerprinting\"\n  http_request: {\n    method: GET\n    uri: \"/version\"\n    response: {\n      http_status: 200\n    }\n  }\n}\n```\n\nNow we want to ensure the header contains `MyVulnerableApp`: we need to use\n**expectations**. We can either use `expect_all` or `expect_any`, which perform\nchecks on the response that must respectively \"all be true\" or\n\"at least one true\". Here, we have only one expectation, so we will use\n`expect_all` with one `conditions`:\n\n```proto\nactions: {\n  name: \"fingerprinting\"\n  http_request: {\n    method: GET\n    uri: \"/version\"\n    response: {\n      http_status: 200\n      expect_all: {\n        conditions: [\n          { header: { name: \"Server\" } contains: \"MyVulnerableApp\" }\n        ]\n      }\n    }\n  }\n}\n```\n\nThe condition is that the `header` with `name` `Server` `contains` the string\n`MyVulnerableApp`.\n\nNow, let's build a `POST` request for our exploitation step:\n\n```proto\nactions: {\n  name: \"exploitation\"\n  http_request: {\n    method: POST\n    uri: \"/exploit\"\n    data: \"process=%{ print(\\\"tsunami_%d_marker\\\", 1250*1+3) }%\"\n    response: {\n      http_status: 200\n      expect_all: {\n        conditions: [\n          { body: {} contains: \"tsunami_1253_marker\" }\n        ]\n      }\n    }\n  }\n}\n```\n\nThis action is very similar to the previous one but:\n\n- It sends a `POST` request instead of a `GET` one;\n- As part of the `POST` it sends\n`process=%{ print(\\\"tsunami_%d_marker\\\", 1250*1+3) }%` as data;\n- The expectation has been changed to check that the response body\ncontains `tsunami_1253_marker`.\n\n## Redirects\n\nNote that by default, the HTTP client will follow any HTTP redirects.\nIf you wish to change that behavior, you can configure your HTTP request using\nthe `client_options` stanza. For example, with our previous request:\n\n```proto\nactions: {\n  name: \"exploitation\"\n  http_request: {\n    method: POST\n    uri: \"/exploit\"\n    data: \"process=%{ print(\\\"tsunami_%d_marker\\\", 1250*1+3) }%\"\n    client_options: {\n      disable_follow_redirects: true\n    }\n    response: {\n      http_status: 200\n      expect_all: {\n        conditions: [\n          { body: {} contains: \"tsunami_1253_marker\" }\n        ]\n      }\n    }\n  }\n}\n```\n\n## What is next\n\n[Putting it together in workflows](04-workflows)\n"
  },
  {
    "path": "docs/howto/new-detector/templated/04-workflows.md",
    "content": "\n# Assembling the workflow\n\nA workflow is simply a list of the actions to be executed, in order:\n\n```proto\nworkflows: {\n  actions: [\n    \"fingerprinting\",\n    \"exploitation\"\n  ]\n}\n```\n\nAnd that's it! We have written our first templated plugin. What happens if we\nrun it against a default HTTP server?\n\n```sh\n{ ... }\nDec 31, 2024 11:29:58 AM com.google.tsunami.plugin.PluginBootstrapModule registerDynamicPlugin\nINFO: Dynamic plugin registered: MyVulnerableApp_CVE_0123_12345\n{ ... }\nDec 31, 2024 11:30:15 AM com.google.tsunami.plugins.detectors.templateddetector.TemplatedDetector detect\nINFO: Starting detector: MyVulnerableApp_CVE_0123_12345\nDec 31, 2024 11:30:15 AM com.google.tsunami.common.net.http.OkHttpHttpClient send\nINFO: Sending HTTP 'GET' request to 'http://127.0.0.1:9090/version'.\nDec 31, 2024 11:30:15 AM com.google.tsunami.common.net.http.OkHttpHttpClient parseResponse\nINFO: Received HTTP response with code '404' for request to 'http://127.0.0.1:9090/version'.\nDec 31, 2024 11:30:15 AM com.google.tsunami.plugins.detectors.templateddetector.TemplatedDetector runWorkflowForService\nINFO: No vulnerability found because action 'Fingerprint' failed.\nDec 31, 2024 11:30:15 AM com.google.tsunami.plugin.PluginExecutorImpl buildSucceededResult\nINFO: MyVulnerableApp_CVE_0123_12345 plugin execution finished in 119 (ms)\n{ ... }\n```\n\nAs expected, a default HTTP server does not have a `Server` header that contains\n`MyVulnerableApp`.\n\n## What is next\n\n[Using variables](05-variables)\n"
  },
  {
    "path": "docs/howto/new-detector/templated/05-variables.md",
    "content": "\n# Using variables\n\nIn our action, we have hardcoded our payload, which is not super convenient and\nreadable:\n\n```proto\nactions: {\n  name: \"exploitation\"\n  http_request: {\n    method: POST\n    uri: \"/exploit\"\n    data: \"process=%{ print(\\\"tsunami_%d_marker\\\", 1250*1+3) }%\"\n    response: {\n      http_status: 200\n      expect_all: {\n        conditions: [\n          { body: {} contains: \"tsunami_1253_marker\" }\n        ]\n      }\n    }\n  }\n}\n```\n\nLet's try to move our payload into a variable. For this, we will define\nvariables in our workflow:\n\n```proto\nworkflows: {\n  variables: [\n    { name: \"payload\" value: \"%{ print(\\\"tsunami_%d_marker\\\", 1250*1+3) }%\" },\n    { name: \"payload_result\" value: \"tsunami_1253_marker\" }\n  ]\n\n  actions: [\n    \"fingerprinting\",\n    \"exploitation\"\n  ]\n}\n```\n\nWhich can then be used in the action:\n\n{% raw %}\n```proto\nactions: {\n  name: \"exploitation\"\n  http_request: {\n    method: POST\n    uri: \"/exploit\"\n    data: \"process={{ payload }}\"\n    response: {\n      http_status: 200\n      expect_all: {\n        conditions: [\n          { body: {} contains: \"{{ payload_result }}\" }\n        ]\n      }\n    }\n  }\n}\n```\n{% endraw %}\n\nIMPORTANT: The syntax for variables is requires **exactly** one space before and\nafter the variable name, between the brackets. Otherwise, substitution will not\nhappen.\n\n## Extracting information to local variables\n\nExpectations are sufficient if we need to check that the response has a very\nspecific content. But what if we need to extract some information from the\nresponse for later use? For example, what if we have an action that creates a\njob and we need the job name in a follow-up action to trigger the vulnerability?\n\nIn that case we can use **extractions** and **local variables**. But what are\nlocal variables? There are two types of variables:\n\n- **Workflow variables**: Defined at the workflow level they mostly define\nstatic content. They will exist for the whole lifecycle of the workflow and will\nbe reset to their defined value between workflow runs. Note that workflow\nvariables are technically mutable, but we strongly recommend not mutating\nthem (i.e. not using them in extractions). We used this type of variable in the\nprevious example.\n- **Local variables**: These variables are more dynamic. They are defined from\n**extractions** and are only valid for the current workflow run. They are mostly\nused to extract information that will be used in a later action.\n\nFor our previous example, let us assume that the `/version` page returns a CSRF\ntoken that we will need to use in the `/exploit` request. We can use a local\nvariable to store that value:\n\n```proto\nactions: {\n  name: \"fingerprinting\"\n  http_request: {\n    method: GET\n    uri: \"/version\"\n    response: {\n      http_status: 200\n      expect_all: {\n        conditions: [\n          { header: { name: \"Server\" } contains: \"MyVulnerableApp\" }\n        ]\n      }\n      extract_all: {\n        patterns: [\n          {\n            from_body: {}\n            regexp: \"CSRFToken=([a-zA-Z0-9_]+)\"\n            variable_name: \"csrf_token\"\n          },\n        ]\n      }\n    }\n  }\n}\n```\n\nIn that example, we:\n\n- still verify that the server has the `Server` header that we expect;\n- also try to `extract_all` `patterns` `from_body` given regular expression\n`regexp` and store the result in the variable `variable_name` (here\n\"csrf_token\").\n\nThe extraction system offers **exactly** one capture group and extracting\nseveral information should be set into different patterns.\n\nWARNING: The use of extractions with `extract_any` should be done carefully. We\nvery strongly recommend to only use `extract_any` with the\n**same variable name** between extractions. Doing otherwise makes the plugin\npotentially flaky and difficult to debug.\n\n## Predefined variables\n\nNow would be a good time to look at the\n[list of variables](glossary-predefined-variables) that Tsunami provides.\n\n## What is next\n\n[Using the callback server](06-callback-server)\n"
  },
  {
    "path": "docs/howto/new-detector/templated/06-callback-server.md",
    "content": "\n# Using the callback server\n\nIf you wrote plugins for Tsunami before, you know about the callback server. If\nyou have not, the callback server is a mechanism that allows us to check for\nvulnerabilities via an out-of-band mechanism. You can read more about it on the\n[dedicated GitHub repository](https://github.com/google/tsunami-security-scanner-callback-server).\n\nIn a nutshell, the callback server works this way:\n\n1. A secret is generated (and stored in `T_CBS_SECRET`);\n2. This secret is hashed;\n3. The exploit uses the hashed secret and the URL of the callback server\n(`T_CBS_URI`) to trigger an out-of-band communication with the callback server;\n4. The secret can be used to ask the callback server if a communication using\nthe hashed secret has been logged (i.e. if the vulnerability has been\ntriggered);\n\nCurrently, the templated plugin system does not support payload generation,\nso communication with the callback server has to be handled semi-manually.\n\nEvery workflow run will automatically generate a new secret, its hash and the\nadequate trigger URL. That means that, as part of your exploit, you will need to\nmake sure a request to the callback server is made with the trigger URL. The\ntrigger URL is stored in the `T_CBS_URI` variable. For example, in our previous\nexample we could change the payload to:\n\n{% raw %}\n```proto\n{ name: \"payload\" value: \"%{ import os; os.system('curl {{ T_CBS_URI }}') }%\" }\n```\n{% endraw %}\n\n`T_CBS_URI` would be replaced with the trigger URL and the callback server\nwould receive a request on that endpoint if the vulnerability is triggered.\nChecking if the vulnerability was triggered then simply requires defining a new\naction:\n\n```proto\nactions: {\n  name: \"check_callback_server_logs\"\n  callback_server: { action_type: CHECK }\n}\n```\n\nAnd, voila! But wait... How do we know, in our plugin, if the callback server\nis currently running? Do we not want to support both use cases? That is one of\nthe features offered by workflows: `condition` allows you to define a condition\nfor the workflow to be executed. With our example:\n\n{% raw %}\n```proto\n# Workflow that uses the callback server.\nworkflows: {\n  condition: REQUIRES_CALLBACK_SERVER\n  variables: [\n    { name: \"payload\" value: \"%{ import os; os.system('curl {{ T_CBS_URI }}') }%\" }\n    ## note: empty string is always present in the body. This cancels out the\n    ## body content expectation.\n    { name: \"payload_result\" value: \"\" }\n  ]\n\n  actions: [\n    \"fingerprinting\",\n    \"exploitation\",\n    \"check_callback_server_logs\"\n  ]\n}\n\n# Workflow that does not use the callback server.\nworkflows: {\n  variables: [\n    { name: \"payload\" value: \"%{ print(\\\"tsunami_%d_marker\\\", 1250*1+3) }%\" },\n    { name: \"payload_result\" value: \"tsunami_1253_marker\" }\n  ]\n\n  actions: [\n    \"fingerprinting\",\n    \"exploitation\"\n  ]\n}\n```\n{% endraw %}\n\nNote: Because workflow are interpreted in order, the one that is more\nrestrictive needs to be defined first. Otherwise, the less restrictive workflow\nwould always be the one running.\n\n## What is next\n\n[Using cleanup actions](07-cleanup-actions)\n"
  },
  {
    "path": "docs/howto/new-detector/templated/07-cleanup-actions.md",
    "content": "\n# Cleanup actions\n\nIn our example, none of the defined actions will modify the target. But what if\nwe had some actions that we want to clean-up after? Cleanup actions are the\nsolution.\n\nLet us assume an arbitrary example that would cause a change on the target:\n\n```proto\nactions: {\n  name: \"create_docker_container\"\n  http_request: {\n    method: POST\n    uri: \"/api/v1/create\"\n    data: \"name=MySuperContainer\"\n    response: {\n      http_status: 200\n    }\n  }\n}\n```\n\nIn that example, if the request is successful, a new container will be created\non the target, but we want to make sure to delete that container afterwards.\nBecause cleanup actions are normal actions, we can start by writing the deletion\nrequest:\n\n```proto\nactions: {\n  name: \"cleanup_container\"\n  http_request: {\n    method: POST\n    uri: \"/api/v1/delete\"\n    data: \"name=MySuperContainer\"\n    response: {\n      http_status: 200\n    }\n  }\n}\n```\n\nThis action will ensure the container is deleted. But now, how do we make sure\nit is executed after and only if the container has been created? We register\nit as a cleanup of the initial action:\n\n```proto\nactions: {\n  name: \"create_docker_container\"\n  http_request: {\n    method: POST\n    uri: \"/api/v1/create\"\n    data: \"name=MySuperContainer\"\n    response: {\n      http_status: 200\n    }\n  }\n  cleanup_actions: \"cleanup_container\"\n}\n```\n\nNote the newly added `cleanup_actions` entry. This will ensure the following:\n\n- If `create_docker_container` is not executed or fail, the cleanup action will\nnot be executed;\n- If the `create_docker_container` is executed successfully, the cleanup action\nwill always be executed at the end of the current workflow run;\n\n## What is next\n\n[Writing unit tests](08-writing-unit-tests)\n"
  },
  {
    "path": "docs/howto/new-detector/templated/08-writing-unit-tests.md",
    "content": "\n# Writing unit tests\n\nFor every workflow, we expect to see associated unit tests for each of your\ncontributions. If unit tests are not as reliable as integration testing,\nespecially in the context of the scanner, they provide some insurance that\nchanges are not breaking detectors.\n\n## Configuration of the unit test\n\nThe tests for a specific plugin are defined in a test file that is named exactly\nlike the detector, with the `_test` suffix. For example, for a\n`NodeRED_ExposedUI.textproto` you can find its\n`NodeRED_ExposedUI_test.textproto` counterpart.\n\nOnce created, the file needs to contain a minimal configuration:\n\n```proto\nconfig: {\n  tested_plugin: \"nameOfTheTestedPlugin\"\n}\n```\n\nWhere `nameOfTheTestedPlugin` is the name of the plugin. The `tested_plugin`\nconfiguration indicates to the test engine which detector to bind the test to.\nWithout this option, your test will not work.\n\nYou can additionally define the `disabled` configuration option, but in most\ncases you will not need to use it.\n\n## Defining tests\n\nOnce you have configured the general option for your unit tests, you need to\nactually define each tests.\n\nMost test will rely on a mock server to simulate the target application that\nwe wrote a detector for. But before we dig deeper into mock servers, each\ntest needs to have a name and whether the vulnerability will be found, for\nexample:\n\n```proto\ntests: {\n  name: \"whenVulnerable_returnsTrue\"\n  expect_vulnerability: true\n}\n\ntests: {\n  name: \"whenNotVulnerable_returnsFalse\"\n  expect_vulnerability: false\n}\n```\n\nSee our [convention on naming tests](appendix-naming-tests).\n\n## Using mock servers\n\nNow we can start simulating the behavior of our vulnerable application. Two mock\ncapabilities are currently integrated in the templated plugin system:\n\n- An HTTP server;\n- A fake Callback server;\n\nSeveral mocks can be used at the same time.\n\nLet us take a simplified version of our previously defined plugin:\n\n```proto\nactions: {\n  name: \"exploitation\"\n  http_request: {\n    method: POST\n    uri: \"/exploit\"\n    data: \"process=%{ import os; os.system('curl {{ T_CBS_URI }}') }%\"\n    response: {\n      http_status: 200\n    }\n  }\n}\n\nactions: {\n  name: \"check_callback_server_logs\"\n  callback_server: { action_type: CHECK }\n}\n\nworkflows: {\n  condition: REQUIRES_CALLBACK_SERVER\n  actions: [\n    \"exploitation\",\n    \"check_callback_server_logs\"\n  ]\n}\n```\n\nTo validate vulnerability detection of this plugin, we will need:\n\n- To simulate the callback server to return true. This can easily be done with\nthe `mock_callback_server` directive;\n- To simulate the answer to `/exploit` with the HTTP server which can easily be\ndone with the `mock_http_server` directive;\n\n```proto\ntests: {\n  name: \"whenVulnerable_returnsTrue\"\n  expect_vulnerability: true\n\n  mock_callback_server: {\n    enabled: true\n    has_interaction: true\n  }\n\n  mock_http_server: {\n    mock_responses: [\n      {\n        uri: \"/exploit\"\n        status: 200\n      },\n    ]\n  }\n}\n```\n\nAnd that is it. If we wanted to check a case where the server is not vulnerable\nwe could use the following test case:\n\n```proto\ntests: {\n  name: \"whenNotVulnerable_returnFalse\"\n  expect_vulnerability: false\n\n  mock_http_server: {\n    mock_responses: [\n      {\n        uri: \"/exploit\"\n        status: 403\n      },\n    ]\n  }\n}\n```\n\nNote that we do not need the callback server anymore as the workflow will fail\nbefore. Additionally, the `/exploit` endpoint now returns a `403`.\n\n## Adding a bit of magic to our world\n\nWhen mocking HTTP responses, Tsunami provides a few magic endpoints (to be\nused in the `uri` field).\n\nYou can view them in the [glossary](glossary-tests-magic-uri).\n\n## Generating things for you\n\nFinally, under the hood, Tsunami will also generate unit tests for you. This\nhelps us detecting flaky detectors: for example when a detector fails to pass\nthe \"echo server\" test (when the server is just repeating the request, a\ndetector should not raise a vulnerability).\n\n## Congratulations!\n\nCongratulations, you have finished writing your first templated plugin!\n"
  },
  {
    "path": "docs/howto/new-detector/templated/appendix-naming-actions.md",
    "content": "# Convention: How to name actions\n\nActions must be named using the `[a-zA-Z0-9_]` character set. For example\n`this_is_my_action`. This naming convention helps improving discoverability of\nactions.\n"
  },
  {
    "path": "docs/howto/new-detector/templated/appendix-naming-plugin.md",
    "content": "# Convention: How to name a plugin\n\nThe plugin name and filename should be identical as it makes for easier\ndiscoverability. Plugins should be named using the following convention:\n\n- All plugins should be named using the following character set: `[a-zA-Z0-9_]`\nso no spaces or special characters.\n- If the vulnerability has an associated CVE:\n`VulnerableApplicationName_CVE_YYYY_NNNNN` and the plugin should be placed in\nthe `cve/YYYY/` directory.\n- If the vulnerability does not have an associated CVE:\n`VulnerableApplicationName_YYYY_VulnerabilityName`; if a vulnerability has no\nname you can try to describe it, for example `PreauthRCE`. The vulnerability\nwill then be placed in the directory that matches the type of vulnerability,\nfor example `rce/YYYY/VulnerableApplicationName_YYYY_VulnerabilityName`.\n- When the name of a plugin contains an acronym (e.g. `HTTP`, `UI`, `RCE`),\nthat acronym must be in uppercase.\n"
  },
  {
    "path": "docs/howto/new-detector/templated/appendix-naming-tests.md",
    "content": "# How to name unit tests\n\nUse `condition_outcome` as the naming schema for your tests. That explicitly\nmeans that you should not prefix the test function name with \"test\".\n\nExample: `whenVulnerable_returnsTrue`.\n"
  },
  {
    "path": "docs/howto/new-detector/templated/appendix-using-linter.md",
    "content": "\n# Using the linter\n\nFor all plugins written using our configuration format, we expect the plugins to\nbe linted.\n\nThe linter ensures that the plugin has the right format but also performs a\nseries of checks that make sure it is behaving correctly.\n\n## Installing the linter\n\n### Using our docker image\n\nThe linter is bundled in our docker image and will automatically run.\n\n### Custom setup\n\nThe linter is a Go binary that can very easily be installed:\n\n```\n$ go install github.com/google/tsunami-security-scanner-plugins/templated/utils/linter@latest\n$ linter <path to the file>\n```\n\nNote that depending on your current configuration, you might have to extend your\n`PATH`. See the\n[Golang documentation](https://go.dev/doc/tutorial/compile-install) for details.\n"
  },
  {
    "path": "docs/howto/new-detector/templated/glossary-predefined-variables.md",
    "content": "# Predefined variables\n\nTsunami will provide a predefined set of variable to the environment that you\ncan make use of in your actions. We try to maintain a strong naming convention\nfor these :\n\n- `T_` stands for Tsunami and identifies a variable that is provided by the\ncore engine;\n- `_UTL_` stands for utility and provides various utility variables;\n- `_NS_` stands for network service and provide information about the currently\nscanned network service;\n- `_CBS_` stands for callback server and provides information about the\ncallback server.\n\nHere is the list of variables that are provided:\n\n- `T_UTL_CURRENT_TIMESTAMP_MS`: Provides the current timestamp in milliseconds.\nNote that the timestamp is computed at the beginning of a workflow run. It will\nthus be different between services but always return the same value within one\nrun;\n- `T_NS_BASEURL`: The base URL of the network service being scanned. For example\n`http://127.0.0.1:9090` or `http://hostname.lan:1000`;\n- `T_NS_PROTOCOL`: The protocol used by the network service being scanned (e.g.\n`tcp`);\n- `T_NS_HOSTNAME`: The hostname of the network service being scanned. Note that\nthis variable is only available if Tsunami was invoked with a hostname target\n(e.g. `hostname.lan`);\n- `T_NS_PORT`: The port of the network service being scanned (e.g. `1000`);\n- `T_NS_IP`: The IP of the network service being scanned (e.g. `127.0.0.1`);\n- `T_CBS_URI`: The callback server URL used to trigger the callback server. This\nis the main variable used when using the callback server. It contains the\naddress and hashed secret (e.g. `http://tsunami-callback.lan/8fe7d878787d65`\nwhere `8fe7d878787d65` is the **hashed** secret);\n- `T_CBS_SECRET`: The callback server secret generated for the current workflow\nrun; note that it is not hashed and is not relevant in most cases (e.g.\n`somesecret`);\n- `T_CBS_ADDRESS`: Address of the callback server (e.g. `tsunami-callback.lan`);\n- `T_CBS_PORT`: Port of the callback server (e.g. `80`);\n"
  },
  {
    "path": "docs/howto/new-detector/templated/glossary-tests-magic-uri.md",
    "content": "## Magic tests URIs\n\nThe following URIs are considered \"magic\" in tests when using the mock HTTP\nserver:\n\n- `TSUNAMI_MAGIC_ANY_URI`: Will match any URI; so this answer will match any\nrequest;\n- `TSUNAMI_MAGIC_ECHO_SERVER`: Force Tsunami to repeat the request in the\nresponse. This is used internally to detect flaky detectors;\n"
  },
  {
    "path": "docs/howto/orchestration.md",
    "content": "# Tsunami Scan Orchestration\n\n## Overview\n\nTsunami follows a hardcoded 2-step process when scanning a publicly\nexposed network endpoint:\n\n*   **Reconnaissance**: First, Tsunami identifies open ports and\n    subsequently fingerprints protocols, services and other software running on\n    the target host via a set of fingerprinting plugins. To not reinvent the\n    wheel, Tsunami leverages existing tools such as [nmap](https://nmap.org/)\n    for some of these tasks.\n*   **Vulnerability verification**: Based on the information gathered in step 1,\n    Tsunami selects all vulnerability verification plugins matching the\n    identified services and executes them in order to verify vulnerabilities\n    without false positives.\n\n## Overall Scanning Workflow\n\nFollowing diagram shows the overall workflow for a Tsunami scan.\n\n![orchestration](img/orchestration.svg)\n\n## Reconnaissance\n\nIn the reconnaissance step, Tsunami probes the scan target and gathers as much\ninformation about the scan target as possible, including:\n\n*   open ports,\n*   protocols,\n*   network services & their banners,\n*   potential software & corresponding version.\n\nTsunami performs the Reconnaissance step in 2 separate phases.\n\n### Port Scanning Phase\n\nIn the port scanning phase, Tsunami performs port sweeping in order to identify\nopen ports, protocols and network services on the scan target. The output of\nPort Scanning is a `PortScanReport` protobuf that contains all the identified\n`NetworkService`s from the port scanner.\n\n`PortScanner` is a special type of Tsunami plugins design for Port Scanning\npurpose. This allows users to swap the port scanning implementations. To not\nreinvent the wheel, users could choose a Tsunami plugin wrapper around existing\ntools like [nmap](https://nmap.org/) or\n[masscan](https://github.com/robertdavidgraham/masscan). You may find useful\n`PortScanner` implementations in\n[tsunami-security-scanner-plugins](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google/portscan)\nrepo.\n\n### Fingerprinting Phase\n\nUsually port scanners only provide very basic service detection capability. When\nthe scan target hosts complicated network services, like web servers, the\nscanner needs to perform further fingerprinting work to learn more about the\nexposed network services.\n\nFor example, the scan target might choose to serve multiple web applications on\nthe same TCP port 443 using nginx for reverse proxy, `/blog` for WordPress, and\n`/forum` for phpBB, etc. Port scanner will only be able to tell port 443 is\nrunning nginx. A Web Application Fingerprinter with a comprehensive crawler is\nrequired to identify these applications.\n\n`ServiceFingerprinter` is a special type of Tsunami plugin that allows users to\ndefine fingerprinters for a specific network service. By using filtering\nannotations, Tsunami will be able to automatically invoke appropriate\n`ServiceFingerprinter`s when it identifies matching network services.\n\nTsunami only performs service fingerprinting for web services,\nusing the\n[`WebServiceFingerprinter`](https://github.com/google/tsunami-security-scanner-plugins/blob/71c57f6bc151a3d97675d74c904a175172c77df4/google/fingerprinters/web/src/main/java/com/google/tsunami/plugins/fingerprinters/web/WebServiceFingerprinter.java)\nplugin.\n\n### Reconnaissance Report\n\nAt the end of the reconnaissance step, Tsunami compiles both the port scanner\noutputs and service fingerprinter outputs into a single `ReconnaissanceReport`\nprotobuf for Vulnerability Verification.\n\n## Vulnerability Verification\n\nIn the Vulnerability Verification step, Tsunami executes the `VulnDetector`\nplugins in parallel to verify certain vulnerabilities on the scan target based\non the information gathered in the Reconnaissance step. `VulnDetector`'s\ndetection logic could either be implemented as plain Java code, or as a separate\nbinary / script using a different language like python or go. External binaries\nand scripts have to be executed as separate processes outside of Tsunami using\nTsunami's command execution util.\n\n### Detector Selection\n\nUsually one `VulnDetector` only verifies one vulnerability and the vulnerability\noften only affects one type of network service or software. In order to avoid\ndoing wasteful work, Tsunami allows plugins to be annotated by some filtering\nannotations to limit the scope of the plugin.\n\nThen before the Vulnerability Verification step starts, Tsunami will select\nmatching `VulnDetector`s to run based on the exposed network services and\nrunning software on the scan target. Non-matching `VulnDetector`s will stay\ninactive throughout the entire scan.\n"
  },
  {
    "path": "docs/index.md",
    "content": ""
  },
  {
    "path": "full.Dockerfile",
    "content": "# Core engine\nFROM ghcr.io/google/tsunami-scanner-core:latest AS core\n\n# Callback server\nFROM ghcr.io/google/tsunami-security-scanner-callback-server:latest AS tcs\n\n# Plugins\nFROM ghcr.io/google/tsunami-plugins-google:latest AS plugins-google\nFROM ghcr.io/google/tsunami-plugins-templated:latest AS plugins-templated\nFROM ghcr.io/google/tsunami-plugins-doyensec:latest AS plugins-doyensec\nFROM ghcr.io/google/tsunami-plugins-community:latest AS plugins-community\nFROM ghcr.io/google/tsunami-plugins-govtech:latest AS plugins-govtech\nFROM ghcr.io/google/tsunami-plugins-facebook:latest AS plugins-facebook\nFROM ghcr.io/google/tsunami-plugins-python:latest AS plugins-python\n\n# Release a full version\nFROM ubuntu:latest AS release\n\nRUN apt-get update \\\n    && apt-get install -y --no-install-recommends ca-certificates openjdk-21-jre golang python3 python3-venv \\\n    && rm -rf /var/lib/apt/lists/* \\\n    && rm -rf /usr/share/doc && rm -rf /usr/share/man \\\n    && apt-get clean \\\n    && mkdir logs/\n\nCOPY --from=core /usr/tsunami/ /usr/tsunami/\nCOPY --from=tcs /usr/tsunami/ /usr/tsunami/\n\nCOPY --from=plugins-google /usr/tsunami/plugins/ /usr/tsunami/plugins/\nCOPY --from=plugins-templated /usr/tsunami/plugins/ /usr/tsunami/plugins/\nCOPY --from=plugins-doyensec /usr/tsunami/plugins/ /usr/tsunami/plugins/\nCOPY --from=plugins-community /usr/tsunami/plugins/ /usr/tsunami/plugins/\nCOPY --from=plugins-govtech /usr/tsunami/plugins/ /usr/tsunami/plugins/\nCOPY --from=plugins-facebook /usr/tsunami/plugins/ /usr/tsunami/plugins/\nCOPY --from=plugins-python /usr/tsunami/py_plugins/ /usr/tsunami/py_plugins/\n\n# Install the linter\nRUN go install github.com/google/tsunami-security-scanner-plugins/templated/utils/linter@latest \\\n    && ln -s /root/go/bin/linter /usr/bin/tsunami-linter\n\n# Symlink the Python plugins so that they are discoverable by Python.\nRUN ln -s /usr/tsunami/py_plugins/ /usr/tsunami/py_server/py_plugins\n\n# Create the __init__.py files to ensure all plugins are discoverable.\nRUN find /usr/tsunami/py_plugins/ \\\n        -type d \\\n        ! -name '__pycache__' \\\n        -exec touch '{}/__init__.py' \\;\n\n# Create wrapper scripts\nWORKDIR /usr/tsunami\nRUN echo '#!/bin/bash\\njava -cp /usr/tsunami/tsunami.jar:/usr/tsunami/plugins/* -Dtsunami.config.location=/usr/tsunami/tsunami.yaml com.google.tsunami.main.cli.TsunamiCli $*\\n' > /usr/bin/tsunami \\\n    && chmod +x /usr/bin/tsunami \\\n    && echo '#!/bin/bash\\njava -cp /usr/tsunami/tsunami-tcs.jar com.google.tsunami.callbackserver.main.TcsMain --custom-config=/usr/tsunami/tcs_config.yaml $*\\n' > /usr/bin/tsunami-tcs \\\n    && chmod +x /usr/bin/tsunami-tcs \\\n    && echo '#!/bin/bash\\n/usr/tsunami/py_venv/bin/python3 /usr/tsunami/py_server/plugin_server.py $*\\n' > /usr/bin/tsunami-py-server \\\n    && chmod +x /usr/bin/tsunami-py-server\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/google/tsunami-security-scanner\n\ngo 1.22.0\n"
  },
  {
    "path": "main/README.md",
    "content": "# Tsunami Main\n\n## Overview\n\nThis module provides the entry point for starting up Tsunami Security Scanner.\n"
  },
  {
    "path": "main/build.gradle",
    "content": "plugins {\n    id 'application'\n    id 'com.gradleup.shadow' version \"8.3.6\"\n}\n\ndescription = 'Tsunami: main'\n\ndependencies {\n    implementation project(':tsunami-common')\n    implementation project(':tsunami-plugin')\n    implementation project(':tsunami-proto')\n    implementation project(':tsunami-workflow')\n\n    implementation \"com.beust:jcommander:1.48\"\n    implementation \"com.doyensec:libajp:1.0.0\"\n    implementation \"com.google.cloud:google-cloud-storage:1.103.1\"\n    implementation \"com.google.flogger:flogger:0.9\"\n    implementation \"com.google.flogger:google-extensions:0.9\"\n    implementation \"com.google.guava:guava:33.0.0-jre\"\n    implementation \"com.google.inject:guice:6.0.0\"\n    implementation \"com.google.protobuf:protobuf-java:3.25.5\"\n    implementation \"io.github.classgraph:classgraph:4.8.65\"\n    implementation \"io.grpc:grpc-netty:1.60.0\"\n    implementation \"javax.inject:javax.inject:1\"\n    implementation \"org.jsoup:jsoup:1.9.2\"\n\n    runtimeOnly \"org.glassfish.jaxb:jaxb-runtime:2.3.1\"\n\n    testImplementation \"com.google.truth:truth:1.4.4\"\n    testImplementation \"com.google.truth.extensions:truth-java8-extension:1.4.4\"\n    testImplementation \"com.google.truth.extensions:truth-proto-extension:1.4.4\"\n    testImplementation \"junit:junit:4.13.2\"\n    testImplementation \"org.mockito:mockito-core:5.18.0\"\n}\n\napplication {\n    mainClassName = 'com.google.tsunami.main.cli.TsunamiCli'\n}\n\nshadowJar {\n    exclude '*.proto'\n}\n\ntasks.named(\"distZip\") {\n    dependsOn(\":tsunami-main:shadowJar\")\n}\n\ntasks.named(\"distTar\") {\n    dependsOn(\":tsunami-main:shadowJar\")\n}\n\ntasks.named(\"startScripts\") {\n    dependsOn(\":tsunami-main:shadowJar\")\n}\n\ntasks.named(\"startShadowScripts\") {\n    dependsOn(\":tsunami-main:jar\")\n}\n\ntasks.named(\"compileJava\") {\n    dependsOn(\":tsunami-plugin:shadowJar\")\n}\n\ntasks.named('compileJava') {\n    dependsOn(':tsunami-proto:shadowJar')\n    dependsOn(':tsunami-workflow:shadowJar')\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/LanguageServerOptions.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli;\n\nimport com.beust.jcommander.Parameter;\nimport com.beust.jcommander.ParameterException;\nimport com.beust.jcommander.Parameters;\nimport com.google.common.collect.ImmutableList;\nimport com.google.tsunami.common.cli.CliOption;\nimport com.google.tsunami.common.data.NetworkEndpointUtils;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.List;\n\n/** Command line arguments for Tsunami language servers. */\n@Parameters(separators = \"=\")\npublic final class LanguageServerOptions implements CliOption {\n\n  @Parameter(\n      names = \"--plugin-server-paths\",\n      description = \"The filename of the language server to run language-specific plugins.\")\n  public List<String> pluginServerFilenames = ImmutableList.of();\n\n  @Parameter(\n      names = \"--plugin-server-ports\",\n      description =\n          \"The port of the plugin server to open connection with. If not enough ports were\"\n              + \" specified for the number of language servers specified, an open port will be\"\n              + \" chosen.\")\n  public List<String> pluginServerPorts = ImmutableList.of();\n\n  @Parameter(\n      names = \"--plugin-server-rpc-deadline-seconds\",\n      description = \"The RPC deadline in seconds for the plugin servers.\")\n  public List<Integer> pluginServerRpcDeadlineSeconds = ImmutableList.of();\n\n  @Parameter(\n      names = {\"--remote-plugin-server-addresses\", \"--python-plugin-server-address\"},\n      description = \"The address for remote language server (e.g. Python).\")\n  public List<String> remotePluginServerAddress = ImmutableList.of();\n\n  @Parameter(\n      names = {\"--remote-plugin-server-ports\", \"--python-plugin-server-port\"},\n      description = \"The port of the remote plugin server to open connection with.\")\n  public List<Integer> remotePluginServerPort = ImmutableList.of();\n\n  @Parameter(\n      names = \"--remote-plugin-server-rpc-deadline-seconds\",\n      description = \"The RPC deadline in seconds for this plugin server.\")\n  public List<Integer> remotePluginServerRpcDeadlineSeconds = ImmutableList.of();\n\n  @Override\n  public void validate() {\n    if (!pluginServerFilenames.isEmpty() || !pluginServerPorts.isEmpty()) {\n      if (pluginServerFilenames != null && !pluginServerFilenames.isEmpty()) {\n        for (String pluginServerFilename : pluginServerFilenames) {\n          if (!Files.exists(Paths.get(pluginServerFilename))) {\n            throw new ParameterException(\n                String.format(\"Language server path %s does not exist\", pluginServerFilename));\n          }\n        }\n      }\n\n      if (pluginServerPorts != null && !pluginServerPorts.isEmpty()) {\n        for (String pluginServerPort : pluginServerPorts) {\n          try {\n            int port = Integer.parseInt(pluginServerPort);\n            if (!(port <= NetworkEndpointUtils.MAX_PORT_NUMBER && port > 0)) {\n              throw new ParameterException(\n                  String.format(\n                      \"Port out of range. Expected [0, %s], actual %s.\",\n                      NetworkEndpointUtils.MAX_PORT_NUMBER, pluginServerPort));\n            }\n          } catch (NumberFormatException e) {\n            throw new ParameterException(\n                String.format(\"Port number must be an integer. Got %s instead.\", pluginServerPort),\n                e);\n          }\n        }\n      }\n\n      var pathCounts = pluginServerFilenames == null ? 0 : pluginServerFilenames.size();\n      var portCounts = pluginServerPorts == null ? 0 : pluginServerPorts.size();\n      if (pathCounts != portCounts) {\n        throw new ParameterException(\n            String.format(\n                \"Number of plugin server paths must be equal to number of plugin server ports.\"\n                    + \" Paths: %s. Ports: %s.\",\n                pathCounts, portCounts));\n      }\n\n      if (!pluginServerRpcDeadlineSeconds.isEmpty()) {\n        if (pluginServerRpcDeadlineSeconds.size() != pathCounts) {\n          throw new ParameterException(\n              String.format(\n                  \"Number of plugin server rpc deadlines must be equal to number of plugin server\"\n                      + \" ports. Paths: %s. Ports: %s. Deadlines: %s\",\n                  pathCounts, portCounts, pluginServerRpcDeadlineSeconds.size()));\n        }\n      }\n    }\n\n    if (!remotePluginServerAddress.isEmpty()) {\n      var addrCounts = remotePluginServerAddress.size();\n      var portCounts = remotePluginServerPort.size();\n      if (addrCounts != portCounts) {\n        throw new ParameterException(\n            String.format(\n                \"Number of remote plugin server paths must be equal to number of plugin server \"\n                    + \"ports. Addresses: %s. Ports: %s.\",\n                addrCounts, portCounts));\n      }\n\n      if (!remotePluginServerRpcDeadlineSeconds.isEmpty()) {\n        if (remotePluginServerRpcDeadlineSeconds.size() != addrCounts) {\n          throw new ParameterException(\n              String.format(\n                  \"Number of plugin server rpc deadlines must be equal to number of plugin server\"\n                      + \" ports. Paths: %s. Ports: %s. Deadlines: %s\",\n                  addrCounts, portCounts, pluginServerRpcDeadlineSeconds.size()));\n        }\n      }\n\n      for (int port : remotePluginServerPort) {\n        if (!(port <= NetworkEndpointUtils.MAX_PORT_NUMBER && port > 0)) {\n          throw new ParameterException(\n              String.format(\n                  \"Remote plugin server port out of range. Expected [0, %s], actual %s.\",\n                  NetworkEndpointUtils.MAX_PORT_NUMBER, port));\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/ScanResultsArchiver.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.tsunami.common.io.archiving.GoogleCloudStorageArchiver.GS_URL_PATTERN;\n\nimport com.beust.jcommander.Parameter;\nimport com.beust.jcommander.ParameterException;\nimport com.beust.jcommander.Parameters;\nimport com.google.cloud.storage.Storage;\nimport com.google.cloud.storage.StorageOptions;\nimport com.google.common.base.Strings;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.protobuf.InvalidProtocolBufferException;\nimport com.google.protobuf.util.JsonFormat;\nimport com.google.tsunami.common.cli.CliOption;\nimport com.google.tsunami.common.io.archiving.Archiver;\nimport com.google.tsunami.common.io.archiving.GoogleCloudStorageArchiver;\nimport com.google.tsunami.common.io.archiving.RawFileArchiver;\nimport com.google.tsunami.main.cli.option.OutputDataFormat;\nimport com.google.tsunami.proto.ScanResults;\nimport javax.inject.Inject;\n\nclass ScanResultsArchiver {\n\n  @Parameters(separators = \"=\")\n  static final class Options implements CliOption {\n\n    @Parameter(\n        names = \"--scan-results-local-output-filename\",\n        description = \"The local output filename of the scanning results.\")\n    public String localOutputFilename;\n\n    @Parameter(\n        names = \"--scan-results-local-output-format\",\n        description = \"The format of the scanning results saved as local file.\")\n    public OutputDataFormat localOutputFormat;\n\n    @Parameter(\n        names = \"--scan-results-gcs-output-file-url\",\n        description = \"The GCS file url for the uploaded scanning results.\")\n    public String gcsOutputFileUrl;\n\n    @Parameter(\n        names = \"--scan-results-gcs-output-format\",\n        description = \"The format of the scanning results uploaded to GCS bucket.\")\n    public OutputDataFormat gcsOutputFormat;\n\n    @Parameter(\n        names = \"--scan-results-logging-enabled\",\n        description = \"Enable logging of the scan results.\",\n        arity = 1)\n    public Boolean loggingEnabled = false;\n\n    @Override\n    public void validate() {\n      if (!Strings.isNullOrEmpty(gcsOutputFileUrl)\n          && !GS_URL_PATTERN.matcher(gcsOutputFileUrl).matches()) {\n        throw new ParameterException(String.format(\"Malformed GCS URL: '%s'\", gcsOutputFileUrl));\n      }\n    }\n  }\n\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  private final Options options;\n  private final RawFileArchiver rawFileArchiver;\n  private final GoogleCloudStorageArchiver.Factory googleCloudStorageArchiverFactory;\n\n  @Inject\n  // TODO(b/145315535): inject archivers using multibinder instead.\n  ScanResultsArchiver(\n      Options options,\n      RawFileArchiver rawFileArchiver,\n      GoogleCloudStorageArchiver.Factory googleCloudStorageArchiverFactory) {\n    this.options = checkNotNull(options);\n    this.rawFileArchiver = checkNotNull(rawFileArchiver);\n    this.googleCloudStorageArchiverFactory = checkNotNull(googleCloudStorageArchiverFactory);\n  }\n\n  Storage getGcsStorage() {\n    return StorageOptions.getDefaultInstance().getService();\n  }\n\n  void archive(ScanResults scanResults) throws InvalidProtocolBufferException {\n    if (!Strings.isNullOrEmpty(options.localOutputFilename)) {\n      archive(rawFileArchiver, options.localOutputFilename, options.localOutputFormat, scanResults);\n    }\n\n    if (!Strings.isNullOrEmpty(options.gcsOutputFileUrl)) {\n      GoogleCloudStorageArchiver archiver =\n          googleCloudStorageArchiverFactory.create(getGcsStorage());\n      archive(archiver, options.gcsOutputFileUrl, options.gcsOutputFormat, scanResults);\n    }\n\n    if (options.loggingEnabled) {\n      logger.atInfo().log(\"Scan results for RVD efficacy: %s\", scanResults);\n    }\n  }\n\n  private static void archive(\n      Archiver archiver, String location, OutputDataFormat outputFormat, ScanResults scanResults)\n      throws InvalidProtocolBufferException {\n    switch (outputFormat) {\n      case BIN_PROTO:\n        archiver.archive(location, scanResults.toByteArray());\n        break;\n      case JSON:\n        archiver.archive(location, JsonFormat.printer().print(scanResults));\n        break;\n    }\n  }\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/ScanResultsArchiverModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli;\n\nimport com.google.inject.AbstractModule;\n\n/** Installs {@link ScanResultsArchiver}. */\nfinal class ScanResultsArchiverModule extends AbstractModule {\n\n  @Override\n  protected void configure() {\n  }\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/TsunamiCli.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname;\nimport static com.google.tsunami.common.data.NetworkEndpointUtils.forIp;\nimport static com.google.tsunami.common.data.NetworkEndpointUtils.forIpAndHostname;\nimport static com.google.tsunami.common.data.NetworkServiceUtils.buildUriNetworkService;\n\nimport com.google.common.base.Stopwatch;\nimport com.google.common.base.Strings;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Lists;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Guice;\nimport com.google.inject.Injector;\nimport com.google.tsunami.common.cli.CliOptionsModule;\nimport com.google.tsunami.common.command.CommandExecutorModule;\nimport com.google.tsunami.common.config.ConfigLoader;\nimport com.google.tsunami.common.config.ConfigModule;\nimport com.google.tsunami.common.config.TsunamiConfig;\nimport com.google.tsunami.common.config.YamlConfigLoader;\nimport com.google.tsunami.common.io.archiving.GoogleCloudStorageArchiverModule;\nimport com.google.tsunami.common.net.http.HttpClientCliOptions;\nimport com.google.tsunami.common.net.http.HttpClientModule;\nimport com.google.tsunami.common.net.socket.TsunamiSocketFactoryModule;\nimport com.google.tsunami.common.reflection.ClassGraphModule;\nimport com.google.tsunami.common.server.LanguageServerCommand;\nimport com.google.tsunami.common.time.SystemUtcClockModule;\nimport com.google.tsunami.main.cli.option.MainCliOptions;\nimport com.google.tsunami.main.cli.server.RemoteServerLoader;\nimport com.google.tsunami.main.cli.server.RemoteServerLoaderModule;\nimport com.google.tsunami.plugin.PluginExecutionModule;\nimport com.google.tsunami.plugin.PluginLoadingModule;\nimport com.google.tsunami.plugin.RemoteVulnDetectorLoadingModule;\nimport com.google.tsunami.plugin.payload.PayloadGeneratorModule;\nimport com.google.tsunami.proto.ScanResults;\nimport com.google.tsunami.proto.ScanStatus;\nimport com.google.tsunami.proto.ScanTarget;\nimport com.google.tsunami.workflow.AdvisoriesWorkflow;\nimport com.google.tsunami.workflow.DefaultScanningWorkflow;\nimport com.google.tsunami.workflow.ScanningWorkflowException;\nimport io.github.classgraph.ClassGraph;\nimport io.github.classgraph.ScanResult;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.security.SecureRandom;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutionException;\nimport javax.inject.Inject;\n\n/** Command line interface for the Tsunami Security Scanner. */\npublic final class TsunamiCli {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  private final DefaultScanningWorkflow scanningWorkflow;\n  private final AdvisoriesWorkflow advisoriesWorkflow;\n  private final ScanResultsArchiver scanResultsArchiver;\n  private final MainCliOptions mainCliOptions;\n  private final RemoteServerLoader remoteServerLoader;\n\n  @Inject\n  TsunamiCli(\n      DefaultScanningWorkflow scanningWorkflow,\n      AdvisoriesWorkflow advisoriesWorkflow,\n      ScanResultsArchiver scanResultsArchiver,\n      MainCliOptions mainCliOptions,\n      RemoteServerLoader remoteServerLoader) {\n    this.scanningWorkflow = checkNotNull(scanningWorkflow);\n    this.advisoriesWorkflow = checkNotNull(advisoriesWorkflow);\n    this.scanResultsArchiver = checkNotNull(scanResultsArchiver);\n    this.mainCliOptions = checkNotNull(mainCliOptions);\n    this.remoteServerLoader = checkNotNull(remoteServerLoader);\n  }\n\n  public boolean run()\n      throws ExecutionException, InterruptedException, ScanningWorkflowException, IOException {\n    String logId = mainCliOptions.getLogId();\n    // TODO(b/171405612): Find a way to print the log ID at every log line.\n    logger.atInfo().log(\"%sTsunamiCli starting...\", logId);\n\n    ImmutableList<Process> languageServerProcesses = remoteServerLoader.runServerProcesses();\n    if (mainCliOptions.dumpAdvisoriesPath != null && !mainCliOptions.dumpAdvisoriesPath.isEmpty()) {\n      logger.atInfo().log(\"No scan will be performed. Dumping advisories.\");\n      advisoriesWorkflow.run(mainCliOptions.dumpAdvisoriesPath);\n      return true;\n    }\n\n    ScanResults scanResults = scanningWorkflow.run(buildScanTarget());\n    languageServerProcesses.forEach(Process::destroy);\n\n    logger.atInfo().log(\"Tsunami scan finished, saving results.\");\n    saveResults(scanResults);\n\n    if (hasSuccessfulResults(scanResults)) {\n      logger.atInfo().log(\"TsunamiCli finished...\");\n      return true;\n    } else {\n      logger.atInfo().log(\n          \"Tsunami scan has failed status, message = %s.\", scanResults.getStatusMessage());\n      return false;\n    }\n  }\n\n  private static boolean hasSuccessfulResults(ScanResults scanResults) {\n    return scanResults.getScanStatus().equals(ScanStatus.SUCCEEDED)\n        || scanResults.getScanStatus().equals(ScanStatus.PARTIALLY_SUCCEEDED);\n  }\n\n  private ScanTarget buildScanTarget() {\n    ScanTarget.Builder scanTargetBuilder = ScanTarget.newBuilder();\n\n    String ip = null;\n    if (mainCliOptions.ipV4Target != null) {\n      ip = mainCliOptions.ipV4Target;\n    } else if (mainCliOptions.ipV6Target != null) {\n      ip = mainCliOptions.ipV6Target;\n    }\n    if (ip != null && mainCliOptions.hostnameTarget != null) {\n      scanTargetBuilder.setNetworkEndpoint(forIpAndHostname(ip, mainCliOptions.hostnameTarget));\n    } else if (ip != null) {\n      scanTargetBuilder.setNetworkEndpoint(forIp(ip));\n    } else if (mainCliOptions.uriTarget != null) {\n      scanTargetBuilder.setNetworkService(buildUriNetworkService(mainCliOptions.uriTarget));\n    } else {\n      scanTargetBuilder.setNetworkEndpoint(forHostname(mainCliOptions.hostnameTarget));\n    }\n\n    return scanTargetBuilder.build();\n  }\n\n  private void saveResults(ScanResults scanResults) throws IOException {\n    scanResultsArchiver.archive(scanResults);\n  }\n\n  private static final class TsunamiCliFirstStageModule extends AbstractModule {\n    private final ScanResult classScanResult;\n    private final String[] args;\n    private final TsunamiConfig tsunamiConfig;\n\n    TsunamiCliFirstStageModule(\n        ScanResult classScanResult, String[] args, TsunamiConfig tsunamiConfig) {\n      this.classScanResult = checkNotNull(classScanResult);\n      this.args = checkNotNull(args);\n      this.tsunamiConfig = checkNotNull(tsunamiConfig);\n    }\n\n    @Override\n    protected void configure() {\n      install(new ClassGraphModule(classScanResult));\n      install(new ConfigModule(classScanResult, tsunamiConfig));\n      install(new CliOptionsModule(classScanResult, \"TsunamiCli\", args));\n    }\n  }\n\n  private static final class TsunamiCliModule extends AbstractModule {\n    private final ScanResult classScanResult;\n    private final Injector parentInjector;\n    private final TsunamiConfig tsunamiConfig;\n\n    TsunamiCliModule(\n        Injector parentInjector, ScanResult classScanResult, TsunamiConfig tsunamiConfig) {\n      this.classScanResult = checkNotNull(classScanResult);\n      this.parentInjector = checkNotNull(parentInjector);\n      this.tsunamiConfig = checkNotNull(tsunamiConfig);\n    }\n\n    @Override\n    protected void configure() {\n      MainCliOptions mco = parentInjector.getInstance(MainCliOptions.class);\n      LanguageServerOptions lso = parentInjector.getInstance(LanguageServerOptions.class);\n      HttpClientCliOptions hcco = parentInjector.getInstance(HttpClientCliOptions.class);\n      ScanResultsArchiver.Options srao =\n          parentInjector.getInstance(ScanResultsArchiver.Options.class);\n\n      ImmutableList<LanguageServerCommand> commands = extractPluginServerArgs(mco, lso, hcco, srao);\n\n      install(new SystemUtcClockModule());\n      install(new CommandExecutorModule());\n      install(new HttpClientModule.Builder().setLogId(mco.getLogId()).build());\n      install(new TsunamiSocketFactoryModule());\n      install(new GoogleCloudStorageArchiverModule());\n      install(new ScanResultsArchiverModule());\n      install(new PluginExecutionModule());\n      install(new PluginLoadingModule(classScanResult));\n      install(new PayloadGeneratorModule(new SecureRandom()));\n      install(new RemoteServerLoaderModule(commands));\n      install(new RemoteVulnDetectorLoadingModule(commands));\n    }\n\n    private ImmutableList<LanguageServerCommand> extractPluginServerArgs(\n        MainCliOptions mco,\n        LanguageServerOptions lso,\n        HttpClientCliOptions hcco,\n        ScanResultsArchiver.Options srao) {\n      List<LanguageServerCommand> commands = Lists.newArrayList();\n      Boolean trustAllSslCertCli = hcco.trustAllCertificates;\n      var logId = mco.getLogId();\n      var paths = lso.pluginServerFilenames;\n      var ports = lso.pluginServerPorts;\n      var rpcDeadline = lso.pluginServerRpcDeadlineSeconds;\n      var remoteServerAddresses = lso.remotePluginServerAddress;\n      var remoteServerPorts = lso.remotePluginServerPort;\n      var remoteRpcDeadlines = lso.remotePluginServerRpcDeadlineSeconds;\n      if (paths.isEmpty() && remoteServerAddresses.isEmpty()) {\n        return ImmutableList.of();\n      }\n\n      Map<String, Object> callbackConfig = tsunamiConfig.readConfigValue(\"plugin.callbackserver\");\n      Map<String, Object> httpClientConfig = tsunamiConfig.readConfigValue(\"common.net.http\");\n      boolean trustAllSslCertConfig =\n          (boolean) httpClientConfig.getOrDefault(\"trust_all_certificates\", false);\n\n      String lngOutputDir = extractOutputDir(srao);\n      boolean lngTrustAllSslCertCli =\n          trustAllSslCertCli != null ? trustAllSslCertCli.booleanValue() : trustAllSslCertConfig;\n      Duration lngConnectDuration =\n          Duration.ofSeconds((int) httpClientConfig.getOrDefault(\"connect_timeout_seconds\", 0));\n      String lngCallbackAddress = (String) callbackConfig.getOrDefault(\"callback_address\", \"\");\n      Integer lngCallbackPort = (Integer) callbackConfig.getOrDefault(\"callback_port\", 0);\n      String lngPollingUri = (String) callbackConfig.getOrDefault(\"polling_uri\", \"\");\n\n      for (int i = 0; i < paths.size(); ++i) {\n        commands.add(\n            LanguageServerCommand.create(\n                paths.get(i),\n                \"\",\n                ports.get(i),\n                logId,\n                lngOutputDir,\n                lngTrustAllSslCertCli,\n                lngConnectDuration,\n                lngCallbackAddress,\n                lngCallbackPort,\n                lngPollingUri,\n                rpcDeadline.isEmpty() ? 0 : rpcDeadline.get(i)));\n      }\n      for (int i = 0; i < remoteServerAddresses.size(); ++i) {\n        commands.add(\n            LanguageServerCommand.create(\n                \"\",\n                remoteServerAddresses.get(i),\n                remoteServerPorts.get(i).toString(),\n                logId,\n                lngOutputDir,\n                lngTrustAllSslCertCli,\n                lngConnectDuration,\n                lngCallbackAddress,\n                lngCallbackPort,\n                lngPollingUri,\n                remoteRpcDeadlines.isEmpty() ? 0 : remoteRpcDeadlines.get(i)));\n      }\n      return ImmutableList.copyOf(commands);\n    }\n\n    private String extractOutputDir(ScanResultsArchiver.Options sra) {\n      if (!Strings.isNullOrEmpty(sra.localOutputFilename)) {\n        return Path.of(sra.localOutputFilename).getParent().toString();\n      }\n      return \"\";\n    }\n  }\n\n  public static int doMain(String[] args) {\n    Stopwatch stopwatch = Stopwatch.createStarted();\n\n    TsunamiConfig tsunamiConfig = loadConfig();\n\n    try (ScanResult scanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .blacklistPackages(\"com.google.tsunami.plugin.testing\")\n            .scan()) {\n      logger.atInfo().log(\"Full classpath scan took %s\", stopwatch);\n\n      Injector firstStageInjector =\n          Guice.createInjector(new TsunamiCliFirstStageModule(scanResult, args, tsunamiConfig));\n\n      Injector injector =\n          firstStageInjector.createChildInjector(\n              new TsunamiCliModule(firstStageInjector, scanResult, tsunamiConfig));\n\n      // Exit with non-zero code if scan failed.\n      if (!injector.getInstance(TsunamiCli.class).run()) {\n        return 1;\n      }\n      logger.atInfo().log(\"Full Tsunami scan took %s.\", stopwatch.stop());\n      return 0;\n    } catch (Throwable e) {\n      logger.atSevere().withCause(e).log(\"Exiting due to workflow execution exceptions.\");\n      if (e instanceof InterruptedException) {\n        Thread.currentThread().interrupt();\n      }\n      return 1;\n    }\n  }\n\n  public static void main(String[] args) {\n    System.exit(doMain(args));\n  }\n\n  private static TsunamiConfig loadConfig() {\n    try (ScanResult scanResult = new ClassGraph().enableAllInfo().scan()) {\n      ConfigLoader configLoader;\n      Optional<String> loaderClass = TsunamiConfig.getSystemProperty(\"tsunami.config.loader\");\n      if (loaderClass.isPresent()\n          && scanResult.getAllClassesAsMap().containsKey(loaderClass.get())) {\n        configLoader =\n            scanResult\n                .getClassInfo(loaderClass.get())\n                .loadClass(ConfigLoader.class)\n                .getConstructor()\n                .newInstance();\n      } else {\n        configLoader = new YamlConfigLoader();\n      }\n\n      return configLoader.loadConfig();\n    } catch (ReflectiveOperationException e) {\n      throw new LinkageError(\"Error loading config.\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/option/MainCliOptions.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.option;\n\nimport com.beust.jcommander.Parameter;\nimport com.beust.jcommander.ParameterException;\nimport com.beust.jcommander.Parameters;\nimport com.google.tsunami.common.cli.CliOption;\nimport com.google.tsunami.main.cli.option.validator.IpV4Validator;\nimport com.google.tsunami.main.cli.option.validator.IpV6Validator;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/** Command line arguments for Tsunami. */\n@Parameters(separators = \"=\")\npublic final class MainCliOptions implements CliOption {\n\n  @Parameter(\n      names = \"--ip-v4-target\",\n      description = \"The IP v4 address of the scanning target.\",\n      validateWith = IpV4Validator.class)\n  public String ipV4Target;\n\n  @Parameter(\n      names = \"--ip-v6-target\",\n      description = \"The IP v6 address of the scanning target.\",\n      validateWith = IpV6Validator.class)\n  public String ipV6Target;\n\n  @Parameter(names = \"--hostname-target\", description = \"The hostname of the scanning target.\")\n  public String hostnameTarget;\n\n  @Parameter(names = \"--log-id\", description = \"A log ID to print in front of the logs.\")\n  public String logId;\n\n  @Parameter(\n      names = \"--uri-target\",\n      description =\n          \"The URI of the scanning target that supports both http & https schemes. When this\"\n              + \" parameter is set, port scan is automatically skipped.\")\n  public String uriTarget;\n\n  @Parameter(\n      names = \"--dump-advisories\",\n      description =\n          \"Disable scanning. Reports the list of currently enabled advisories to the specified\"\n              + \" file, in textproto format.\")\n  public String dumpAdvisoriesPath;\n\n  @Override\n  public void validate() {\n    if (dumpAdvisoriesPath != null && !dumpAdvisoriesPath.isEmpty()) {\n      return;\n    }\n\n    List<String> portScanEnabledTargets = new ArrayList<>();\n    List<String> portScanDisabledTargets = new ArrayList<>();\n    if (ipV4Target != null) {\n      portScanEnabledTargets.add(\"--ip-v4-target\");\n    }\n    if (ipV6Target != null) {\n      portScanEnabledTargets.add(\"--ip-v6-target\");\n    }\n    if (hostnameTarget != null) {\n      portScanEnabledTargets.add(\"--hostname-target\");\n    }\n    if (uriTarget != null) {\n      portScanDisabledTargets.add(\"--uri-target\");\n    }\n\n    if (portScanEnabledTargets.isEmpty() && portScanDisabledTargets.isEmpty()) {\n      throw new ParameterException(\n          \"One of the following parameters is expected: --ip-v4-target, --ip-v6-target,\"\n              + \" --hostname-target, --uri-target\");\n    }\n    if (!portScanEnabledTargets.isEmpty() && !portScanDisabledTargets.isEmpty()) {\n      throw new ParameterException(\n          \"Parameters that require port scan (--ip-v4-target, --ip-v6-target, --hostname-target)\"\n              + \" should not be passed along with parameters that skip port scan (--uri-target)\");\n    }\n  }\n\n  /** Returns the log ID to print in front of the logs. */\n  public String getLogId() {\n    return (logId == null) ? \"\" : (logId + \": \");\n  }\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/option/OutputDataFormat.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.option;\n\nimport com.google.common.base.Ascii;\nimport java.util.Optional;\n\n/** Output format of Tsunami's scanning results. */\npublic enum OutputDataFormat {\n  BIN_PROTO,\n  JSON;\n\n  /**\n   * Parses the given {@code value} into {@link OutputDataFormat} enum.\n   *\n   * @param value the string representation of the {@link OutputDataFormat} enum.\n   * @return the parsed {@link OutputDataFormat} enum.\n   */\n  public static Optional<OutputDataFormat> parse(String value) {\n    for (OutputDataFormat outputDataFormat : OutputDataFormat.values()) {\n      if (Ascii.equalsIgnoreCase(outputDataFormat.name(), value)) {\n        return Optional.of(outputDataFormat);\n      }\n    }\n\n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/option/validator/IpV4Validator.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.option.validator;\n\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\n\n/** Command line flag validator for an IP v4 address. */\npublic class IpV4Validator extends IpValidator {\n\n  @Override\n  protected int ipVersion() {\n    return 4;\n  }\n\n  @Override\n  protected boolean shouldAccept(InetAddress inetAddress) {\n    return inetAddress instanceof Inet4Address;\n  }\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/option/validator/IpV6Validator.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.option.validator;\n\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\n\n/** Command line flag validator for an IP v6 address. */\npublic class IpV6Validator extends IpValidator {\n\n  @Override\n  protected int ipVersion() {\n    return 6;\n  }\n\n  @Override\n  protected boolean shouldAccept(InetAddress inetAddress) {\n    return inetAddress instanceof Inet6Address;\n  }\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/option/validator/IpValidator.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.option.validator;\n\nimport com.beust.jcommander.IParameterValidator;\nimport com.beust.jcommander.ParameterException;\nimport com.google.common.base.Strings;\nimport com.google.common.net.InetAddresses;\nimport java.net.InetAddress;\n\n/** Base command line flag validator for an IP address. */\npublic abstract class IpValidator implements IParameterValidator {\n\n  @Override\n  public void validate(String name, String value) {\n    if (Strings.isNullOrEmpty(value)\n        || !InetAddresses.isInetAddress(value)\n        || !shouldAccept(InetAddresses.forString(value))) {\n      throw new ParameterException(\n          String.format(\n              \"Parameter %s should point to a valid IP v%d address, got '%s'\",\n              name, ipVersion(), value));\n    }\n  }\n\n  protected abstract int ipVersion();\n\n  protected abstract boolean shouldAccept(InetAddress inetAddress);\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/server/RemoteServerLoader.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.server;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.collect.ImmutableList.toImmutableList;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\nimport com.google.common.base.Strings;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.tsunami.common.command.CommandExecutor;\nimport com.google.tsunami.common.command.CommandExecutorFactory;\nimport com.google.tsunami.common.server.LanguageServerCommand;\nimport java.io.IOException;\nimport java.lang.annotation.Retention;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutionException;\nimport javax.inject.Inject;\nimport javax.inject.Qualifier;\n\n/** Loader to run language servers. */\npublic class RemoteServerLoader {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  private final List<LanguageServerCommand> commands;\n\n  @Inject\n  RemoteServerLoader(@LanguageServerCommands List<LanguageServerCommand> commands) {\n    this.commands = checkNotNull(commands);\n  }\n\n  public ImmutableList<Process> runServerProcesses() {\n    logger.atInfo().log(\"Starting language server processes (if any)...\");\n    return commands.stream()\n        // Filter out commands that don't need server start up\n        .filter(command -> !Strings.isNullOrEmpty(command.serverCommand()))\n        .map(\n            command ->\n                runProcess(\n                    CommandExecutorFactory.create(\n                        command.serverCommand(),\n                        getCommand(\"--port=\", command.port()),\n                        getCommand(\"--log_id=\", command.logId()),\n                        getCommand(\"--log_output=\", command.outputDir()),\n                        \"--trust_all_ssl_cert=\" + command.trustAllSslCert(),\n                        getCommand(\"--timeout_seconds=\", command.timeoutSeconds().getSeconds()),\n                        getCommand(\"--callback_address=\", command.callbackAddress()),\n                        getCommand(\"--callback_port=\", command.callbackPort()),\n                        getCommand(\"--polling_uri=\", command.pollingUri()))))\n        .filter(Optional::isPresent)\n        .map(Optional::get)\n        .collect(toImmutableList());\n  }\n\n  private String getCommand(String flag, Object command) {\n    return command.toString().isEmpty() || command.toString().equals(\"0\") ? \"\" : flag + command;\n  }\n\n  private Optional<Process> runProcess(CommandExecutor executor) {\n    try {\n      return Optional.of(executor.executeAsync());\n    } catch (IOException | InterruptedException | ExecutionException e) {\n      logger.atWarning().withCause(e).log(\"Could not execute language server binary.\");\n    }\n    return Optional.empty();\n  }\n\n  /** Guice interface for injecting {@link LanguageServerCommand} object lists. */\n  @Qualifier\n  @Retention(RUNTIME)\n  public @interface LanguageServerCommands {}\n}\n"
  },
  {
    "path": "main/src/main/java/com/google/tsunami/main/cli/server/RemoteServerLoaderModule.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.server;\n\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Provides;\nimport com.google.tsunami.common.server.LanguageServerCommand;\nimport java.util.List;\n\n/** Installs {@link RemoteServerLoaderModule}. */\npublic final class RemoteServerLoaderModule extends AbstractModule {\n\n  private final ImmutableList<LanguageServerCommand> commands;\n\n  public RemoteServerLoaderModule(ImmutableList<LanguageServerCommand> commands) {\n    this.commands = commands;\n  }\n\n  @Provides\n  @RemoteServerLoader.LanguageServerCommands\n  List<LanguageServerCommand> provideLanguageServerCommands() {\n    return commands;\n  }\n}\n"
  },
  {
    "path": "main/src/test/java/com/google/tsunami/main/cli/LanguageServerOptionsTest.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli;\n\nimport static org.junit.Assert.assertThrows;\n\nimport com.beust.jcommander.ParameterException;\nimport com.google.common.collect.ImmutableList;\nimport com.google.tsunami.common.data.NetworkEndpointUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic final class LanguageServerOptionsTest {\n\n  @Test\n  public void validate_whenPluginServerFilenameDoesNotExist_throwsParameterException() {\n    LanguageServerOptions options = new LanguageServerOptions();\n\n    options.pluginServerFilenames = ImmutableList.of(\"nonexistingfile\");\n\n    assertThrows(ParameterException.class, options::validate);\n  }\n\n  @Test\n  public void validate_whenPortNumberNotInteger_throwsParameterException() {\n    LanguageServerOptions options = new LanguageServerOptions();\n\n    options.pluginServerPorts = ImmutableList.of(\"test\");\n\n    assertThrows(ParameterException.class, options::validate);\n  }\n\n  @Test\n  public void validate_whenPortNumberOutOfRange_throwsParameterException() {\n    LanguageServerOptions options = new LanguageServerOptions();\n\n    options.pluginServerPorts = ImmutableList.of(\"34567\", \"-1\");\n\n    assertThrows(\n        \"Port out of range. Expected [0, \"\n            + NetworkEndpointUtils.MAX_PORT_NUMBER\n            + \"]\"\n            + \", actual -1\",\n        ParameterException.class,\n        options::validate);\n  }\n\n  @Test\n  public void validate_whenPythonPluginServerPortNumberOutOfRange_throwsParameterException() {\n    LanguageServerOptions options = new LanguageServerOptions();\n    options.remotePluginServerAddress = ImmutableList.of(\"127.0.0.1\");\n    options.remotePluginServerPort = ImmutableList.of(-1);\n\n    assertThrows(\n        \"Remote plugin server port out of range. Expected [0, \"\n            + NetworkEndpointUtils.MAX_PORT_NUMBER\n            + \"]\"\n            + \", actual -1\",\n        ParameterException.class,\n        options::validate);\n  }\n\n  @Test\n  public void validate_whenPythonPluginServerInvalidNumberOfDeadlines_throwsParameterException() {\n    LanguageServerOptions options = new LanguageServerOptions();\n    options.remotePluginServerAddress = ImmutableList.of(\"127.0.0.1\");\n    options.remotePluginServerPort = ImmutableList.of(10000);\n    options.remotePluginServerRpcDeadlineSeconds = ImmutableList.of(100, 200);\n\n    assertThrows(\n        \"Number of plugin server rpc deadlines must be equal to number of plugin server. ports.\"\n            + \" Paths: 1. Ports: 1. Deadlines: 2\",\n        ParameterException.class,\n        options::validate);\n  }\n\n  @Test\n  public void validate_whenPythonPluginServerValidNumberOfDeadlines_succeeds() {\n    LanguageServerOptions options = new LanguageServerOptions();\n    options.remotePluginServerAddress = ImmutableList.of(\"127.0.0.1\");\n    options.remotePluginServerPort = ImmutableList.of(10000);\n    options.remotePluginServerRpcDeadlineSeconds = ImmutableList.of(150);\n\n    options.validate();\n  }\n}\n"
  },
  {
    "path": "main/src/test/java/com/google/tsunami/main/cli/ScanResultsArchiverTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.spy;\n\nimport com.beust.jcommander.ParameterException;\nimport com.google.cloud.storage.Storage;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Guice;\nimport com.google.inject.Provides;\nimport com.google.protobuf.InvalidProtocolBufferException;\nimport com.google.protobuf.util.JsonFormat;\nimport com.google.tsunami.common.io.archiving.testing.FakeGoogleCloudStorageArchivers;\nimport com.google.tsunami.common.io.archiving.testing.FakeGoogleCloudStorageArchiversModule;\nimport com.google.tsunami.common.io.archiving.testing.FakeRawFileArchiver;\nimport com.google.tsunami.common.io.archiving.testing.FakeRawFileArchiverModule;\nimport com.google.tsunami.main.cli.option.OutputDataFormat;\nimport com.google.tsunami.proto.ScanResults;\nimport com.google.tsunami.proto.ScanStatus;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport javax.inject.Inject;\nimport javax.inject.Qualifier;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnit;\nimport org.mockito.junit.MockitoRule;\n\n/** Tests for {@link ScanResultsArchiver}. */\n@RunWith(JUnit4.class)\npublic final class ScanResultsArchiverTest {\n  private static final ScanResults SCAN_RESULTS =\n      ScanResults.newBuilder().setScanStatus(ScanStatus.SUCCEEDED).build();\n\n  @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();\n\n  @Mock Storage mockStorage;\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  private @interface SpyArchiver {}\n\n  @Inject private FakeRawFileArchiver fakeRawFileArchiver;\n  @Inject private FakeGoogleCloudStorageArchivers fakeGoogleCloudStorageArchivers;\n  @Inject private ScanResultsArchiver.Options options;\n  @Inject @SpyArchiver private ScanResultsArchiver scanResultsArchiver;\n\n  @Before\n  public void setUp() {\n    Guice.createInjector(\n            new AbstractModule() {\n              @Override\n              protected void configure() {\n                bind(ScanResultsArchiver.Options.class)\n                    .toInstance(new ScanResultsArchiver.Options());\n                install(new ScanResultsArchiverModule());\n                install(new FakeRawFileArchiverModule());\n                install(new FakeGoogleCloudStorageArchiversModule());\n              }\n\n              // TODO(b/145315535): wrap GCS API into a client library to get rid of this spy.\n              @Provides\n              @SpyArchiver\n              ScanResultsArchiver getScanResultsArchiverSpy(ScanResultsArchiver delegate) {\n                return spy(delegate);\n              }\n            })\n        .injectMembers(this);\n  }\n\n  @Test\n  public void optionsValidate_whenInvalidGcsUrl_throwsParameterException() {\n    options.gcsOutputFileUrl = \"invalid_url\";\n    assertThrows(ParameterException.class, options::validate);\n  }\n\n  @Test\n  public void optionsValidate_defaultLoggingEnabled_isFalse() {\n    assertThat(options.loggingEnabled).isFalse();\n  }\n\n  @Test\n  public void archive_withNoStorageEnabled_storesNothing() throws InvalidProtocolBufferException {\n    options.localOutputFilename = \"\";\n    options.gcsOutputFileUrl = \"\";\n\n    scanResultsArchiver.archive(SCAN_RESULTS);\n\n    fakeRawFileArchiver.assertNoDataStored();\n    fakeGoogleCloudStorageArchivers.assertNoDataStored();\n  }\n\n  @Test\n  public void archive_withLocalFileEnabledForJsonOutput_storesStringDataLocally()\n      throws InvalidProtocolBufferException {\n    options.localOutputFilename = \"/tmp/result.json\";\n    options.localOutputFormat = OutputDataFormat.JSON;\n    options.gcsOutputFileUrl = \"\";\n\n    scanResultsArchiver.archive(SCAN_RESULTS);\n\n    assertThat(\n            parseJsonScanResults(\n                fakeRawFileArchiver.getStoredCharSequence(\"/tmp/result.json\").toString()))\n        .isEqualTo(SCAN_RESULTS);\n    fakeGoogleCloudStorageArchivers.assertNoDataStored();\n  }\n\n  @Test\n  public void archive_withLocalFileEnabledForBinProtoOutput_storesBytesDataLocally()\n      throws InvalidProtocolBufferException {\n    options.localOutputFilename = \"/tmp/test.binproto\";\n    options.localOutputFormat = OutputDataFormat.BIN_PROTO;\n    options.gcsOutputFileUrl = \"\";\n\n    scanResultsArchiver.archive(SCAN_RESULTS);\n\n    assertThat(\n            ScanResults.parseFrom(\n                fakeRawFileArchiver.getStoredByteArrays(\"/tmp/test.binproto\")))\n        .isEqualTo(SCAN_RESULTS);\n    fakeGoogleCloudStorageArchivers.assertNoDataStored();\n  }\n\n  @Test\n  public void archive_withGcsEnabledForJsonOutput_uploadsStringDataToGcs()\n      throws InvalidProtocolBufferException {\n    options.localOutputFilename = \"\";\n    options.gcsOutputFileUrl = \"gs://test/object/result.json\";\n    options.gcsOutputFormat = OutputDataFormat.JSON;\n    doReturn(mockStorage).when(scanResultsArchiver).getGcsStorage();\n\n    scanResultsArchiver.archive(SCAN_RESULTS);\n\n    assertThat(\n            parseJsonScanResults(\n                fakeGoogleCloudStorageArchivers\n                    .getStoredCharSequence(mockStorage, \"gs://test/object/result.json\")\n                    .toString()))\n        .isEqualTo(SCAN_RESULTS);\n    fakeRawFileArchiver.assertNoDataStored();\n  }\n\n  @Test\n  public void archive_withGcsEnabledForBinProtoOutput_uploadsBytesDataToGcs()\n      throws InvalidProtocolBufferException {\n    options.localOutputFilename = \"\";\n    options.gcsOutputFileUrl = \"gs://test/object/result.binproto\";\n    options.gcsOutputFormat = OutputDataFormat.BIN_PROTO;\n    doReturn(mockStorage).when(scanResultsArchiver).getGcsStorage();\n\n    scanResultsArchiver.archive(SCAN_RESULTS);\n\n    assertThat(\n            ScanResults.parseFrom(\n                fakeGoogleCloudStorageArchivers.getStoredByteArrays(\n                    mockStorage, \"gs://test/object/result.binproto\")))\n        .isEqualTo(SCAN_RESULTS);\n    fakeRawFileArchiver.assertNoDataStored();\n  }\n\n  @Test\n  public void archive_withLocalAndGcsOptionEnabled_archivesToBothLocation()\n      throws InvalidProtocolBufferException {\n    options.localOutputFilename = \"/tmp/result.json\";\n    options.localOutputFormat = OutputDataFormat.JSON;\n    options.gcsOutputFileUrl = \"gs://test/object/result.binproto\";\n    options.gcsOutputFormat = OutputDataFormat.BIN_PROTO;\n    doReturn(mockStorage).when(scanResultsArchiver).getGcsStorage();\n\n    scanResultsArchiver.archive(SCAN_RESULTS);\n\n    assertThat(\n            parseJsonScanResults(\n                fakeRawFileArchiver.getStoredCharSequence(\"/tmp/result.json\").toString()))\n        .isEqualTo(SCAN_RESULTS);\n    assertThat(\n        ScanResults.parseFrom(\n            fakeGoogleCloudStorageArchivers.getStoredByteArrays(\n                mockStorage, \"gs://test/object/result.binproto\")))\n        .isEqualTo(SCAN_RESULTS);\n  }\n\n  private static ScanResults parseJsonScanResults(String jsonScanResults)\n      throws InvalidProtocolBufferException {\n    ScanResults.Builder scanResultsBuilder = ScanResults.newBuilder();\n    JsonFormat.parser().merge(jsonScanResults, scanResultsBuilder);\n    return scanResultsBuilder.build();\n  }\n}\n"
  },
  {
    "path": "main/src/test/java/com/google/tsunami/main/cli/TsunamiCliTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Guice;\nimport com.google.tsunami.common.cli.CliOptionsModule;\nimport com.google.tsunami.common.config.ConfigModule;\nimport com.google.tsunami.common.config.TsunamiConfig;\nimport com.google.tsunami.common.data.NetworkEndpointUtils;\nimport com.google.tsunami.common.net.http.HttpClientModule;\nimport com.google.tsunami.common.time.testing.FakeUtcClockModule;\nimport com.google.tsunami.main.cli.server.RemoteServerLoaderModule;\nimport com.google.tsunami.plugin.payload.PayloadGeneratorModule;\nimport com.google.tsunami.plugin.testing.FailedVulnDetectorBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakePluginExecutionModule;\nimport com.google.tsunami.plugin.testing.FakePortScanner;\nimport com.google.tsunami.plugin.testing.FakePortScannerBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakePortScannerBootstrapModule2;\nimport com.google.tsunami.plugin.testing.FakeServiceFingerprinter;\nimport com.google.tsunami.plugin.testing.FakeServiceFingerprinterBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakeVulnDetector;\nimport com.google.tsunami.plugin.testing.FakeVulnDetector2;\nimport com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModule2;\nimport com.google.tsunami.proto.AddressFamily;\nimport com.google.tsunami.proto.DetectionReport;\nimport com.google.tsunami.proto.Hostname;\nimport com.google.tsunami.proto.IpAddress;\nimport com.google.tsunami.proto.NetworkEndpoint;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.Port;\nimport com.google.tsunami.proto.ReconnaissanceReport;\nimport com.google.tsunami.proto.ScanFinding;\nimport com.google.tsunami.proto.ScanResults;\nimport com.google.tsunami.proto.ScanStatus;\nimport com.google.tsunami.proto.ServiceContext;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.TransportProtocol;\nimport com.google.tsunami.proto.WebServiceContext;\nimport com.google.tsunami.workflow.ScanningWorkflowException;\nimport io.github.classgraph.ClassGraph;\nimport io.github.classgraph.ScanResult;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.SecureRandom;\nimport java.util.concurrent.ExecutionException;\nimport java.util.stream.Stream;\nimport javax.inject.Inject;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnit;\nimport org.mockito.junit.MockitoRule;\n\n/** Tests for {@link TsunamiCli}. */\n@RunWith(JUnit4.class)\npublic final class TsunamiCliTest {\n  private static final String IP_TARGET = \"127.0.0.1\";\n  private static final String HOSTNAME_TARGET = \"localhost\";\n  private static final String URI_TARGET = \"https://localhost/function1\";\n\n  @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();\n  @Rule public TemporaryFolder tempFolder = new TemporaryFolder();\n\n  @Mock ScanResultsArchiver scanResultsArchiver;\n\n  @Captor ArgumentCaptor<ScanResults> scanResultsCaptor;\n\n  @Inject private TsunamiCli tsunamiCli;\n\n  private boolean runCli(ImmutableMap<String, Object> rawConfigData, String... args)\n      throws InterruptedException, ExecutionException, ScanningWorkflowException, IOException {\n    try (ScanResult scanResult = new ClassGraph().enableAllInfo().scan()) {\n      Guice.createInjector(\n              new AbstractModule() {\n                @Override\n                protected void configure() {\n                  bind(ScanResultsArchiver.class).toInstance(scanResultsArchiver);\n                  install(new HttpClientModule.Builder().build());\n                  install(new PayloadGeneratorModule(new SecureRandom()));\n                  install(new ConfigModule(scanResult, TsunamiConfig.fromYamlData(rawConfigData)));\n                  install(new CliOptionsModule(scanResult, \"TsunamiCliTest\", args));\n                  install(new FakeUtcClockModule());\n                  install(new FakePluginExecutionModule());\n                  install(new FakePortScannerBootstrapModule());\n                  install(new FakePortScannerBootstrapModule2());\n                  install(new FakeServiceFingerprinterBootstrapModule());\n                  install(new FakeVulnDetectorBootstrapModule());\n                  install(new FakeVulnDetectorBootstrapModule2());\n                  install(new RemoteServerLoaderModule(ImmutableList.of()));\n                }\n              })\n          .injectMembers(this);\n      return tsunamiCli.run();\n    }\n  }\n\n  @Test\n  public void run_whenIpTarget_generatesAndArchivesCorrectResult()\n      throws InterruptedException, ExecutionException, ScanningWorkflowException, IOException {\n    NetworkService expectedNetworkService =\n        FakeServiceFingerprinter.addWebServiceContext(\n            FakePortScanner.getFakeNetworkService(NetworkEndpointUtils.forIp(IP_TARGET)));\n\n    boolean scanSucceeded = runCli(ImmutableMap.of(), \"--ip-v4-target=\" + IP_TARGET);\n\n    assertThat(scanSucceeded).isTrue();\n    TargetInfo targetInfo =\n        TargetInfo.newBuilder().addNetworkEndpoints(NetworkEndpointUtils.forIp(IP_TARGET)).build();\n    verify(scanResultsArchiver, times(1)).archive(scanResultsCaptor.capture());\n    ScanResults storedScanResult = scanResultsCaptor.getValue();\n    assertThat(storedScanResult.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);\n    assertThat(storedScanResult.getScanFindingsList())\n        .containsExactlyElementsIn(\n            Stream.of(\n                    FakeVulnDetector.getFakeDetectionReport(targetInfo, expectedNetworkService),\n                    FakeVulnDetector2.getFakeDetectionReport(targetInfo, expectedNetworkService))\n                .map(TsunamiCliTest::buildScanFindingFromDetectionReport)\n                .toArray());\n    assertThat(storedScanResult.getReconnaissanceReport())\n        .isEqualTo(\n            ReconnaissanceReport.newBuilder()\n                .setTargetInfo(\n                    TargetInfo.newBuilder()\n                        .addNetworkEndpoints(NetworkEndpointUtils.forIp(IP_TARGET)))\n                .addNetworkServices(\n                    FakeServiceFingerprinter.addWebServiceContext(\n                        FakePortScanner.getFakeNetworkService(\n                            NetworkEndpointUtils.forIp(IP_TARGET))))\n                .build());\n  }\n\n  @Test\n  public void run_whenHostnameTarget_generatesAndArchivesCorrectResult()\n      throws InterruptedException, ExecutionException, ScanningWorkflowException, IOException {\n    NetworkService expectedNetworkService =\n        FakeServiceFingerprinter.addWebServiceContext(\n            FakePortScanner.getFakeNetworkService(\n                NetworkEndpointUtils.forHostname(HOSTNAME_TARGET)));\n\n    boolean scanSucceeded = runCli(ImmutableMap.of(), \"--hostname-target=\" + HOSTNAME_TARGET);\n\n    assertThat(scanSucceeded).isTrue();\n\n    TargetInfo targetInfo =\n        TargetInfo.newBuilder()\n            .addNetworkEndpoints(NetworkEndpointUtils.forHostname(HOSTNAME_TARGET))\n            .build();\n    verify(scanResultsArchiver, times(1)).archive(scanResultsCaptor.capture());\n    ScanResults storedScanResult = scanResultsCaptor.getValue();\n    assertThat(storedScanResult.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);\n    assertThat(storedScanResult.getScanFindingsList())\n        .containsExactlyElementsIn(\n            Stream.of(\n                    FakeVulnDetector.getFakeDetectionReport(targetInfo, expectedNetworkService),\n                    FakeVulnDetector2.getFakeDetectionReport(targetInfo, expectedNetworkService))\n                .map(TsunamiCliTest::buildScanFindingFromDetectionReport)\n                .toArray());\n    assertThat(storedScanResult.getReconnaissanceReport())\n        .isEqualTo(\n            ReconnaissanceReport.newBuilder()\n                .setTargetInfo(\n                    TargetInfo.newBuilder()\n                        .addNetworkEndpoints(NetworkEndpointUtils.forHostname(HOSTNAME_TARGET)))\n                .addNetworkServices(\n                    FakeServiceFingerprinter.addWebServiceContext(\n                        FakePortScanner.getFakeNetworkService(\n                            NetworkEndpointUtils.forHostname(HOSTNAME_TARGET))))\n                .build());\n  }\n\n  @Test\n  public void run_whenUriTarget_generatesCorrectResult()\n      throws InterruptedException, ExecutionException, IOException {\n\n    boolean scanSucceeded = runCli(ImmutableMap.of(), \"--uri-target=\" + URI_TARGET);\n    assertThat(scanSucceeded).isTrue();\n\n    URL url = new URL(URI_TARGET);\n    String hostname = url.getHost();\n    String ipaddress = InetAddress.getByName(hostname).getHostAddress();\n    InetAddress inetAddress = InetAddress.getByName(url.getHost());\n    AddressFamily addressFamily =\n        inetAddress instanceof Inet4Address ? AddressFamily.IPV4 : AddressFamily.IPV6;\n\n    NetworkEndpoint networkEndpoint =\n        NetworkEndpoint.newBuilder()\n            .setType(NetworkEndpoint.Type.IP_HOSTNAME_PORT)\n            .setHostname(Hostname.newBuilder().setName(\"localhost\"))\n            .setPort(Port.newBuilder().setPortNumber(443))\n            .setIpAddress(\n                IpAddress.newBuilder().setAddressFamily(addressFamily).setAddress(ipaddress))\n            .build();\n\n    verify(scanResultsArchiver, times(1)).archive(scanResultsCaptor.capture());\n    ScanResults storedScanResult = scanResultsCaptor.getValue();\n    assertThat(storedScanResult.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);\n    assertThat(storedScanResult.getReconnaissanceReport())\n        .isEqualTo(\n            ReconnaissanceReport.newBuilder()\n                .setTargetInfo(TargetInfo.newBuilder().addNetworkEndpoints(networkEndpoint))\n                .addNetworkServices(\n                    NetworkService.newBuilder()\n                        .setNetworkEndpoint(networkEndpoint)\n                        .setTransportProtocol(TransportProtocol.TCP)\n                        .setServiceName(\"https\")\n                        .setServiceContext(\n                            ServiceContext.newBuilder()\n                                .setWebServiceContext(\n                                    WebServiceContext.newBuilder()\n                                        .setApplicationRoot(url.getPath()))))\n                .build());\n  }\n\n  @Test\n  public void run_whenIpAndHostnameTarget_generatesCorrectResult()\n      throws InterruptedException, ExecutionException, IOException {\n\n    boolean scanSucceeded =\n        runCli(\n            ImmutableMap.of(),\n            \"--ip-v4-target=\" + IP_TARGET,\n            \"--hostname-target=\" + HOSTNAME_TARGET);\n\n    assertThat(scanSucceeded).isTrue();\n\n    verify(scanResultsArchiver, times(1)).archive(scanResultsCaptor.capture());\n    ScanResults storedScanResult = scanResultsCaptor.getValue();\n    assertThat(storedScanResult.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);\n    assertThat(storedScanResult.getReconnaissanceReport())\n        .isEqualTo(\n            ReconnaissanceReport.newBuilder()\n                .setTargetInfo(\n                    TargetInfo.newBuilder()\n                        .addNetworkEndpoints(\n                            NetworkEndpointUtils.forIpAndHostname(IP_TARGET, HOSTNAME_TARGET)))\n                .addNetworkServices(\n                    FakeServiceFingerprinter.addWebServiceContext(\n                        FakePortScanner.getFakeNetworkService(\n                            NetworkEndpointUtils.forIpAndHostname(IP_TARGET, HOSTNAME_TARGET))))\n                .build());\n  }\n\n  @Test\n  public void run_whenScanFailed_generatesFailedScanResults()\n      throws InterruptedException, ExecutionException, IOException {\n\n    try (ScanResult scanResult = new ClassGraph().enableAllInfo().scan()) {\n      Guice.createInjector(\n              new AbstractModule() {\n                @Override\n                protected void configure() {\n                  bind(ScanResultsArchiver.class).toInstance(scanResultsArchiver);\n                  install(new HttpClientModule.Builder().build());\n                  install(new PayloadGeneratorModule(new SecureRandom()));\n                  install(\n                      new ConfigModule(scanResult, TsunamiConfig.fromYamlData(ImmutableMap.of())));\n                  install(\n                      new CliOptionsModule(\n                          scanResult,\n                          \"TsunamiCliTest\",\n                          new String[] {\n                            \"--ip-v4-target=\" + IP_TARGET, \"--hostname-target=\" + HOSTNAME_TARGET\n                          }));\n                  install(new FakeUtcClockModule());\n                  install(new FakePluginExecutionModule());\n                  install(new FakePortScannerBootstrapModule());\n                  install(new FailedVulnDetectorBootstrapModule());\n                  install(new RemoteServerLoaderModule(ImmutableList.of()));\n                }\n              })\n          .injectMembers(this);\n\n      boolean scanSucceeded = tsunamiCli.run();\n\n      assertThat(scanSucceeded).isFalse();\n\n      verify(scanResultsArchiver, times(1)).archive(scanResultsCaptor.capture());\n      ScanResults storedScanResult = scanResultsCaptor.getValue();\n      assertThat(storedScanResult.getScanStatus()).isEqualTo(ScanStatus.FAILED);\n      assertThat(storedScanResult.getStatusMessage()).isEqualTo(\"All VulnDetectors failed.\");\n    }\n  }\n\n  @Test\n  public void run_whenAdvisoryMode_generatesAdvisories()\n      throws InterruptedException, ExecutionException, ScanningWorkflowException, IOException {\n    File tempFile = tempFolder.newFile(\"advisories.csv\");\n    Path tempPath = tempFile.toPath();\n    boolean scanSucceeded = runCli(ImmutableMap.of(), \"--dump-advisories=\" + tempPath.toString());\n\n    String advisories = Files.readString(tempPath);\n    String expectedAdvisories =\n        \"\"\"\n        vulnerabilities {\n          main_id {\n            publisher: \"GOOGLE\"\n            value: \"FakeVuln1\"\n          }\n          severity: CRITICAL\n          title: \"FakeTitle1\"\n          description: \"FakeDescription1\"\n        }\n        vulnerabilities {\n          main_id {\n            publisher: \"GOOGLE\"\n            value: \"FakeVuln2\"\n          }\n          severity: MEDIUM\n          title: \"FakeTitle2\"\n          description: \"FakeDescription2\"\n        }\n        \"\"\";\n    assertThat(scanSucceeded).isTrue();\n    assertThat(advisories).isEqualTo(expectedAdvisories);\n  }\n\n  private static ScanFinding buildScanFindingFromDetectionReport(DetectionReport detectionReport) {\n    return ScanFinding.newBuilder()\n        .setTargetInfo(detectionReport.getTargetInfo())\n        .setNetworkService(detectionReport.getNetworkService())\n        .setVulnerability(detectionReport.getVulnerability())\n        .build();\n  }\n}\n"
  },
  {
    "path": "main/src/test/java/com/google/tsunami/main/cli/option/MainCliOptionsTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.option;\n\nimport static org.junit.Assert.assertThrows;\n\nimport com.beust.jcommander.ParameterException;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link MainCliOptions}. */\n@RunWith(JUnit4.class)\npublic class MainCliOptionsTest {\n\n  @Test\n  public void validate_whenDumpAdvisoriesPathPassed_doesNotThrowParameterException() {\n    MainCliOptions cliOptions = new MainCliOptions();\n\n    cliOptions.dumpAdvisoriesPath = \"path/to/dump/advisories\";\n    cliOptions.validate();\n  }\n\n  @Test\n  public void validate_whenMissingScanTarget_throwsParameterException() {\n    MainCliOptions cliOptions = new MainCliOptions();\n\n    assertThrows(ParameterException.class, cliOptions::validate);\n  }\n\n  @Test\n  public void validate_whenUriTargetPassedWithHostnameTarget_throwsParameterException() {\n    MainCliOptions cliOptions = new MainCliOptions();\n\n    cliOptions.hostnameTarget = \"localhost\";\n    cliOptions.uriTarget = \"https://localhost/function1\";\n\n    assertThrows(ParameterException.class, cliOptions::validate);\n  }\n}\n"
  },
  {
    "path": "main/src/test/java/com/google/tsunami/main/cli/option/OutputDataFormatTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.option;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link OutputDataFormat}. */\n@RunWith(JUnit4.class)\npublic class OutputDataFormatTest {\n\n  @Test\n  public void parse_whenStringMatchesExactly_returnsParsedOutputDataFormat() {\n    assertThat(OutputDataFormat.parse(\"BIN_PROTO\")).hasValue(OutputDataFormat.BIN_PROTO);\n    assertThat(OutputDataFormat.parse(\"JSON\")).hasValue(OutputDataFormat.JSON);\n  }\n\n  @Test\n  public void parse_whenStringMatchesIgnoringCases_returnsParsedOutputDataFormat() {\n    assertThat(OutputDataFormat.parse(\"bin_proto\")).hasValue(OutputDataFormat.BIN_PROTO);\n    assertThat(OutputDataFormat.parse(\"Json\")).hasValue(OutputDataFormat.JSON);\n  }\n\n  @Test\n  public void parse_whenStringNotMatch_returnsEmpty() {\n    assertThat(OutputDataFormat.parse(\"xml\")).isEmpty();\n  }\n}\n"
  },
  {
    "path": "main/src/test/java/com/google/tsunami/main/cli/option/validator/IpV4ValidatorTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.option.validator;\n\nimport com.google.common.collect.ImmutableList;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link IpV4Validator}. */\n@RunWith(JUnit4.class)\npublic class IpV4ValidatorTest extends IpValidatorTest {\n\n  @Override\n  protected String flagName() {\n    return \"ip-v4-target\";\n  }\n\n  @Override\n  protected IpValidator getValidator() {\n    return new IpV4Validator();\n  }\n\n  @Override\n  protected ImmutableList<String> validIps() {\n    return ImmutableList.of(\"127.0.0.1\", \"8.8.8.8\");\n  }\n\n  @Override\n  protected ImmutableList<String> invalidIps() {\n    return ImmutableList.of(\"\", \"bogus_string\", \"1234\", \"2002:af4:9b91::\", \"www.google.com\");\n  }\n}\n"
  },
  {
    "path": "main/src/test/java/com/google/tsunami/main/cli/option/validator/IpV6ValidatorTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.option.validator;\n\nimport com.google.common.collect.ImmutableList;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link IpV6Validator}. */\n@RunWith(JUnit4.class)\npublic class IpV6ValidatorTest extends IpValidatorTest {\n\n  @Override\n  protected String flagName() {\n    return \"ip-v6-target\";\n  }\n\n  @Override\n  protected IpValidator getValidator() {\n    return new IpV6Validator();\n  }\n\n  @Override\n  protected ImmutableList<String> validIps() {\n    return ImmutableList.of(\n        \"0:0:0:0:0:0:0:1\",\n        \"fe80::a\",\n        \"fe80::1\",\n        \"fe80::2\",\n        \"fe80::42\",\n        \"fe80::3dd0:7f8e:57b7:34d5\",\n        \"fe80:3dd0:7f8e:57b7:0:0:0:0\",\n        \"::4:0:0:0:ffff\",\n        \"0:0:3::ffff\",\n        \"7::0.128.0.127\");\n  }\n\n  @Override\n  protected ImmutableList<String> invalidIps() {\n    return ImmutableList.of(\n        \"\", \"bogus_string\", \"1234\", \"127.0.0.1\", \"www.google.com\", \"[1:2e]\", \"[fe80:a\");\n  }\n}\n"
  },
  {
    "path": "main/src/test/java/com/google/tsunami/main/cli/option/validator/IpValidatorTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.option.validator;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.beust.jcommander.ParameterException;\nimport com.google.common.collect.ImmutableList;\nimport org.junit.Test;\n\n/** Base class for IP validators. */\npublic abstract class IpValidatorTest {\n\n  @Test\n  public void validate_withValidIpValue_doesNotThrows() {\n    for (String validIp : validIps()) {\n      try {\n        getValidator().validate(flagName(), validIp);\n      } catch (ParameterException e) {\n        throw new AssertionError(\"Unexpected ParameterException for IP: \" + validIp, e);\n      }\n    }\n  }\n\n  @Test\n  public void validate_withInvalidIpValue_throwsParameterException() {\n    for (String invalidIp : invalidIps()) {\n      ParameterException exception =\n          assertThrows(\n              ParameterException.class, () -> getValidator().validate(flagName(), invalidIp));\n      assertThat(exception)\n          .hasMessageThat()\n          .isEqualTo(\n              String.format(\n                  \"Parameter %s should point to a valid IP v%d address, got '%s'\",\n                  flagName(), getValidator().ipVersion(), invalidIp));\n    }\n  }\n\n  protected abstract String flagName();\n\n  protected abstract IpValidator getValidator();\n\n  protected abstract ImmutableList<String> validIps();\n\n  protected abstract ImmutableList<String> invalidIps();\n}\n"
  },
  {
    "path": "main/src/test/java/com/google/tsunami/main/cli/server/RemoteServerLoaderTest.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.main.cli.server;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.inject.Guice;\nimport com.google.tsunami.common.server.LanguageServerCommand;\nimport java.time.Duration;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic final class RemoteServerLoaderTest {\n\n  @Test\n  public void runServerProcess_whenPathExistsAndNormalPort_returnsValidProcessList() {\n    ImmutableList<LanguageServerCommand> commands =\n        ImmutableList.of(\n            LanguageServerCommand.create(\n                \"/bin/sh\",\n                \"\",\n                \"34567\",\n                \"34\",\n                \"/output-here\",\n                false,\n                Duration.ofSeconds(10),\n                \"157.34.0.2\",\n                8080,\n                \"157.34.0.2:8881\",\n                0));\n\n    RemoteServerLoader loader =\n        Guice.createInjector(new RemoteServerLoaderModule(commands))\n            .getInstance(RemoteServerLoader.class);\n    var processList = loader.runServerProcesses();\n    assertThat(processList).hasSize(1);\n    assertThat(processList.get(0)).isNotNull();\n  }\n\n  @Test\n  public void runServerProcess_whenServerAddressExistsAndNormalPort_returnsEmptyProcessList() {\n    ImmutableList<LanguageServerCommand> commands =\n        ImmutableList.of(\n            LanguageServerCommand.create(\n                \"\",\n                \"127.0.0.1\",\n                \"34567\",\n                \"34\",\n                \"/output-here\",\n                false,\n                Duration.ofSeconds(10),\n                \"157.34.0.2\",\n                8080,\n                \"157.34.0.2:8881\",\n                0));\n\n    RemoteServerLoader loader =\n        Guice.createInjector(new RemoteServerLoaderModule(commands))\n            .getInstance(RemoteServerLoader.class);\n    var processList = loader.runServerProcesses();\n    assertThat(processList).isEmpty();\n  }\n}\n"
  },
  {
    "path": "plugin/README.md",
    "content": "# Tsunami Plugin Module\n\n## Overview\n\nThis module provides plugin development and management APIs for Tsunami\nSecurity Scanner.\n"
  },
  {
    "path": "plugin/build.gradle",
    "content": "\ndescription = 'Tsunami: Plugin'\n\ndef tcsRepoBranch = System.getenv(\"GITBRANCH_TSUNAMI_TCS\") ?: \"stable\"\n\ndependencies {\n    implementation project(':tsunami-common')\n    implementation project(':tsunami-proto')\n    implementation(\"com.google.tsunami:tcs-common\") {\n        version { branch = \"${tcsRepoBranch}\" }\n    }\n    implementation(\"com.google.tsunami:tcs-proto\") {\n        version { branch = \"${tcsRepoBranch}\" }\n    }\n\n    implementation \"com.beust:jcommander:1.48\"\n    implementation \"com.google.auto.value:auto-value-annotations:1.11.0\"\n    implementation \"com.google.code.gson:gson:2.10.1\"\n    implementation \"com.google.flogger:flogger:0.9\"\n    implementation \"com.google.flogger:google-extensions:0.9\"\n    implementation \"com.google.guava:guava:33.0.0-jre\"\n    implementation \"com.google.http-client:google-http-client:1.44.1\"\n    implementation \"com.google.inject:guice:6.0.0\"\n    implementation \"com.google.protobuf:protobuf-java-util:3.25.5\"\n    implementation \"com.google.protobuf:protobuf-java:3.25.5\"\n    implementation \"com.squareup.okhttp3:mockwebserver:3.12.0\"\n    implementation \"io.github.classgraph:classgraph:4.8.65\"\n    implementation \"io.grpc:grpc-context:1.60.0\"\n    implementation \"io.grpc:grpc-core:1.60.0\"\n    implementation \"io.grpc:grpc-netty:1.60.0\"\n    implementation \"io.grpc:grpc-services:1.60.0\"\n    implementation \"io.grpc:grpc-testing:1.60.0\"\n    implementation \"javax.inject:javax.inject:1\"\n    implementation \"org.yaml:snakeyaml:1.26\"\n\n    annotationProcessor \"com.google.auto.value:auto-value:1.10.4\"\n\n    testImplementation \"com.google.guava:guava-testlib:33.0.0-jre\"\n    testImplementation \"com.google.truth:truth:1.4.4\"\n    testImplementation \"com.google.truth.extensions:truth-java8-extension:1.4.4\"\n    testImplementation \"com.google.truth.extensions:truth-proto-extension:1.4.4\"\n    testImplementation \"com.squareup.okhttp3:mockwebserver:3.12.0\"\n    testImplementation \"junit:junit:4.13.2\"\n}\n\ntasks.named(\"compileJava\") {\n    dependsOn(\":tsunami-common:shadowJar\")\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/LanguageServerException.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.tsunami.common.ErrorCode;\nimport com.google.tsunami.common.TsunamiException;\n\n/** Exception for language server errors. */\npublic final class LanguageServerException extends TsunamiException {\n\n  public LanguageServerException(String message) {\n    super(ErrorCode.LANGUAGE_SERVER_ERROR, message);\n  }\n\n  public LanguageServerException(String message, Throwable cause) {\n    super(ErrorCode.LANGUAGE_SERVER_ERROR, message, cause);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginBootstrapModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.multibindings.MapBinder;\n\n/**\n * Base class for bootstrapping a {@link TsunamiPlugin}.\n *\n * <p>A valid {@link PluginBootstrapModule} subclass must be defined for each {@link TsunamiPlugin}.\n * This is enforced by the {@link com.google.tsunami.plugin.annotations.PluginInfo} annotation.\n */\npublic abstract class PluginBootstrapModule extends AbstractModule {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  private MapBinder<PluginDefinition, TsunamiPlugin> tsunamiPluginBinder;\n\n  @Override\n  protected final void configure() {\n    tsunamiPluginBinder =\n        MapBinder.newMapBinder(binder(), PluginDefinition.class, TsunamiPlugin.class);\n    configurePlugin();\n  }\n\n  /**\n   * All bootstrapping logic for a {@link TsunamiPlugin} should be implemented in this method.\n   * {@link PluginBootstrapModule#registerPlugin(Class)} must also be called in order to register\n   * the plugin to Tsunami.\n   */\n  protected abstract void configurePlugin();\n\n  /**\n   * Register a {@link TsunamiPlugin} to Tsunami's plugin module using Guice's multibinding feature.\n   *\n   * @param tsunamiPluginClazz the {@link Class} for the {@link TsunamiPlugin} to be registered.\n   */\n  protected final void registerPlugin(Class<? extends TsunamiPlugin> tsunamiPluginClazz) {\n    checkNotNull(tsunamiPluginClazz);\n\n    tsunamiPluginBinder\n        .addBinding(PluginDefinition.forPlugin(tsunamiPluginClazz))\n        .to(tsunamiPluginClazz);\n    logger.atInfo().log(\"Plugin %s is registered.\", tsunamiPluginClazz);\n  }\n\n  /**\n   * Register a {@link TsunamiPlugin} that is dynamically created at runtime.\n   *\n   * @param name the name of the plugin\n   * @param author the author of the plugin\n   * @param requiresCallbackServer whether the plugin requires a callback server\n   * @param tsunamiPlugin the {@link TsunamiPlugin} to be registered\n   */\n  protected final void registerDynamicPlugin(\n      PluginType pluginType,\n      String name,\n      String author,\n      boolean requiresCallbackServer,\n      boolean isForWebService,\n      TsunamiPlugin tsunamiPlugin) {\n    var pluginDef =\n        PluginDefinition.forDynamicPlugin(\n            pluginType, name, author, isForWebService, requiresCallbackServer);\n    tsunamiPluginBinder.addBinding(pluginDef).toInstance(tsunamiPlugin);\n    logger.atInfo().log(\"Dynamic plugin registered: %s\", pluginDef.name());\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginDefinition.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.base.Preconditions.checkState;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.auto.value.extension.memoized.Memoized;\nimport com.google.tsunami.plugin.annotations.ForOperatingSystemClass;\nimport com.google.tsunami.plugin.annotations.ForServiceName;\nimport com.google.tsunami.plugin.annotations.ForSoftware;\nimport com.google.tsunami.plugin.annotations.ForWebService;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.plugin.annotations.RequiresCallbackServer;\nimport java.util.Optional;\n\n/** A data class that captures all the definition details about a {@link TsunamiPlugin}. */\n@AutoValue\nabstract class PluginDefinition {\n  abstract PluginType type();\n\n  abstract String name();\n\n  abstract String author();\n\n  abstract String version();\n\n  abstract Optional<ForServiceName> targetServiceName();\n\n  abstract Optional<ForSoftware> targetSoftware();\n\n  abstract boolean isForWebService();\n\n  abstract Optional<ForOperatingSystemClass> targetOperatingSystemClass();\n\n  abstract boolean requiresCallbackServer();\n\n  /**\n   * Unique identifier for the plugin.\n   *\n   * @return a string representation of the plugin identifier.\n   */\n  @Memoized\n  public String id() {\n    return String.format(\"/%s/%s/%s/%s\", author(), type(), name(), version());\n  }\n\n  /**\n   * Factory method for creating a {@link PluginDefinition} from the {@link TsunamiPlugin} class.\n   *\n   * @param pluginClazz the {@link Class} of the Tsunami plugin\n   * @return a {@link PluginDefinition} built from all the definition details about the plugin.\n   */\n  public static PluginDefinition forPlugin(Class<? extends TsunamiPlugin> pluginClazz) {\n    Optional<PluginInfo> pluginInfo =\n        Optional.ofNullable(pluginClazz.getAnnotation(PluginInfo.class));\n    Optional<ForServiceName> targetServiceName =\n        Optional.ofNullable(pluginClazz.getAnnotation(ForServiceName.class));\n    Optional<ForSoftware> targetSoftware =\n        Optional.ofNullable(pluginClazz.getAnnotation(ForSoftware.class));\n    boolean isForWebService = pluginClazz.isAnnotationPresent(ForWebService.class);\n    Optional<ForOperatingSystemClass> targetOperatingSystemClass =\n        Optional.ofNullable(pluginClazz.getAnnotation(ForOperatingSystemClass.class));\n    boolean requiresCallbackServer = pluginClazz.isAnnotationPresent(RequiresCallbackServer.class);\n\n    checkState(\n        pluginInfo.isPresent(),\n        \"A @PluginInfo annotation is required when creating a PluginDefinition for plugin: %s\",\n        pluginClazz);\n\n    return new AutoValue_PluginDefinition(\n        pluginInfo.get().type(),\n        pluginInfo.get().name(),\n        pluginInfo.get().author(),\n        pluginInfo.get().version(),\n        targetServiceName,\n        targetSoftware,\n        isForWebService,\n        targetOperatingSystemClass,\n        requiresCallbackServer);\n  }\n\n  /**\n   * Factory method for creating a {@link PluginDefinition} for {@link RemoteVulnDetector}\n   * implementations using the {@link PluginInfo} class.\n   *\n   * @param remotePluginInfo the {@link PluginInfo} of the remote Tsunami plugin\n   * @return a {@link PluginDefinition} built from the plugin info.\n   */\n  public static PluginDefinition forRemotePlugin(PluginInfo remotePluginInfo) {\n    checkNotNull(remotePluginInfo);\n    return new AutoValue_PluginDefinition(\n        remotePluginInfo.type(),\n        remotePluginInfo.name(),\n        remotePluginInfo.author(),\n        remotePluginInfo.version(),\n        Optional.empty(),\n        Optional.empty(),\n        false,\n        Optional.empty(),\n        false);\n  }\n\n  /**\n   * Factory method for creating a {@link PluginDefinition} for dynamic plugins. A dynamic plugin is\n   * a plugin that is created at runtime and for which we cannot rely on the PluginInfo annotation.\n   *\n   * <p>Note that for dynamic plugins, we drop the notion of version and forces it to be 1.0.\n   *\n   * @param pluginName the name of the plugin\n   * @param pluginAuthor the author of the plugin\n   * @param isForWebService whether the plugin is for web service\n   * @param requiresCallbackServer whether the plugin requires a callback server\n   * @return a {@link PluginDefinition} built from the plugin info.\n   */\n  public static PluginDefinition forDynamicPlugin(\n      PluginType pluginType,\n      String pluginName,\n      String pluginAuthor,\n      boolean isForWebService,\n      boolean requiresCallbackServer) {\n    return new AutoValue_PluginDefinition(\n        pluginType,\n        pluginName,\n        pluginAuthor,\n        \"1.0\",\n        Optional.empty(),\n        Optional.empty(),\n        isForWebService,\n        Optional.empty(),\n        requiresCallbackServer);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginExecutionException.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.tsunami.common.ErrorCode;\nimport com.google.tsunami.common.TsunamiException;\n\n/** Exception when executing a Tsunami plugin. */\npublic final class PluginExecutionException extends TsunamiException {\n\n  public PluginExecutionException(String message) {\n    super(ErrorCode.PLUGIN_EXECUTION_ERROR, message);\n  }\n\n  public PluginExecutionException(String message, Throwable cause) {\n    super(ErrorCode.PLUGIN_EXECUTION_ERROR, message, cause);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginExecutionModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.inject.AbstractModule;\nimport com.google.tsunami.common.concurrent.ScheduledThreadPoolModule;\n\n/** Installs dependencies used for plugin executions. */\npublic final class PluginExecutionModule extends AbstractModule {\n\n  @Override\n  protected void configure() {\n    install(new PluginExecutorModule());\n\n    install(\n        new ScheduledThreadPoolModule.Builder()\n            .setName(\"PluginExecution\")\n            .setSize(16)\n            .setDaemon(true)\n            .setPriority(Thread.NORM_PRIORITY)\n            .setAnnotation(PluginExecutionThreadPool.class)\n            .build());\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginExecutionResult.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.common.base.Stopwatch;\nimport com.google.tsunami.plugin.PluginExecutor.PluginExecutorConfig;\nimport java.util.Optional;\n\n/** The result of executing the Tsunami plugin core logic by the {@link PluginExecutor}. */\n@AutoValue\npublic abstract class PluginExecutionResult<T> {\n  /** All possible execution status of a Tsunami plugin. */\n  public enum ExecutionStatus {\n    SUCCEEDED,\n    FAILED,\n    TIMED_OUT\n  }\n\n  public boolean isSucceeded() {\n    return executionStatus().equals(ExecutionStatus.SUCCEEDED);\n  }\n\n  public abstract ExecutionStatus executionStatus();\n  public abstract Optional<T> resultData();\n  public abstract Stopwatch executionStopwatch();\n  public abstract Optional<PluginExecutionException> exception();\n  public abstract PluginExecutorConfig<T> executorConfig();\n\n  public static <T> Builder<T> builder() {\n    return new AutoValue_PluginExecutionResult.Builder<>();\n  }\n\n  /** Builder for {@link PluginExecutionResult}. */\n  @AutoValue.Builder\n  public abstract static class Builder<T> {\n    public abstract Builder<T> setExecutionStatus(ExecutionStatus executionStatus);\n    public abstract Builder<T> setResultData(T resultData);\n    public abstract Builder<T> setExecutionStopwatch(Stopwatch executionStopwatch);\n    public abstract Builder<T> setException(PluginExecutionException exception);\n    public abstract Builder<T> setExecutorConfig(PluginExecutorConfig<T> executorConfig);\n\n    public abstract PluginExecutionResult<T> build();\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginExecutionThreadPool.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport javax.inject.Qualifier;\n\n/** Annotates the thread pool to use for executing Tsunami plugins. */\n@Qualifier\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface PluginExecutionThreadPool {}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginExecutor.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.tsunami.plugin.PluginManager.PluginMatchingResult;\nimport java.util.concurrent.Callable;\n\n/** The interface of the component to execute Tsunami plugins. */\npublic interface PluginExecutor {\n  /**\n   * Executes a plugin's core business logic implemented in non-block manner.\n   *\n   * @param executorConfig The configuration of the execution, cannot be null.\n   * @param <T> type of the plugin execution result.\n   * @return The future of the execution result.\n   */\n  <T> ListenableFuture<PluginExecutionResult<T>> executeAsync(\n      PluginExecutorConfig<T> executorConfig);\n\n  /** Configures a plugin's core business logic to be executed by the {@link PluginExecutor}. */\n  @AutoValue\n  abstract class PluginExecutorConfig<T> {\n    @SuppressWarnings(\"rawtypes\")  // AutoValue bug for not handling generic correctly in this case.\n    public abstract PluginMatchingResult matchedPlugin();\n    public abstract Callable<T> pluginExecutionLogic();\n\n    public static <T> Builder<T> builder() {\n      return new AutoValue_PluginExecutor_PluginExecutorConfig.Builder<>();\n    }\n\n    @AutoValue.Builder\n    public abstract static class Builder<T> {\n      @SuppressWarnings(\"rawtypes\")  // AutoValue bug for not handling generic correctly.\n      public abstract Builder<T> setMatchedPlugin(PluginMatchingResult matchedPlugin);\n      public abstract Builder<T> setPluginExecutionLogic(Callable<T> pluginExecutionLogic);\n\n      public abstract PluginExecutorConfig<T> build();\n    }\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginExecutorImpl.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.util.concurrent.MoreExecutors.directExecutor;\n\nimport com.google.common.base.Stopwatch;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.common.util.concurrent.FluentFuture;\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.common.util.concurrent.ListeningScheduledExecutorService;\nimport com.google.tsunami.plugin.PluginExecutionResult.ExecutionStatus;\nimport java.time.Duration;\nimport javax.inject.Inject;\n\nclass PluginExecutorImpl implements PluginExecutor {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n  private final ListeningScheduledExecutorService pluginExecutionThreadPool;\n  private final Stopwatch executionStopwatch;\n\n  @Inject\n  PluginExecutorImpl(\n      @PluginExecutionThreadPool ListeningScheduledExecutorService pluginExecutionThreadPool) {\n    this(pluginExecutionThreadPool, Stopwatch.createUnstarted());\n  }\n\n  PluginExecutorImpl(\n      ListeningScheduledExecutorService pluginExecutionThreadPool, Stopwatch executionStopwatch) {\n    this.pluginExecutionThreadPool = checkNotNull(pluginExecutionThreadPool);\n    this.executionStopwatch = checkNotNull(executionStopwatch);\n  }\n\n  @Override\n  public <T> ListenableFuture<PluginExecutionResult<T>> executeAsync(\n      PluginExecutorConfig<T> executorConfig) {\n    // Executes the core plugin logic within the thread pool.\n    return FluentFuture.from(\n            pluginExecutionThreadPool.submit(\n                () -> {\n                  executionStopwatch.start();\n                  return executorConfig.pluginExecutionLogic().call();\n                }))\n        // Terminate plugin if it runs over 1 hour.\n        .withTimeout(Duration.ofHours(1), pluginExecutionThreadPool)\n        // If execution succeeded, build successful execution result.\n        .transform(resultData -> buildSucceededResult(resultData, executorConfig), directExecutor())\n        // If execution failed, build failed execution result.\n        .catching(\n            Throwable.class,\n            exception -> buildFailedResult(exception, executorConfig),\n            directExecutor());\n  }\n\n  private <T> PluginExecutionResult<T> buildSucceededResult(\n      T resultData, PluginExecutorConfig<T> executorConfig) {\n    if (executionStopwatch.isRunning()) {\n      executionStopwatch.stop();\n    }\n    logger.atInfo().log(\n        \"%s plugin execution finished in %d (ms)\",\n        executorConfig.matchedPlugin().pluginDefinition().name(),\n        executionStopwatch.elapsed().toMillis());\n    return PluginExecutionResult.<T>builder()\n        .setExecutionStatus(ExecutionStatus.SUCCEEDED)\n        .setResultData(resultData)\n        .setExecutionStopwatch(executionStopwatch)\n        .setExecutorConfig(executorConfig)\n        .build();\n  }\n\n  private <T> PluginExecutionResult<T> buildFailedResult(\n      Throwable t, PluginExecutorConfig<T> executorConfig) {\n    logger.atWarning().log(\n        \"Plugin '%s' failed: %s\", executorConfig.matchedPlugin().pluginId(), t.getMessage());\n    if (executionStopwatch.isRunning()) {\n      executionStopwatch.stop();\n    }\n    return PluginExecutionResult.<T>builder()\n        .setExecutionStatus(ExecutionStatus.FAILED)\n        .setExecutionStopwatch(executionStopwatch)\n        .setException(wrapException(t, executorConfig))\n        .setExecutorConfig(executorConfig)\n        .build();\n  }\n\n  private static <T> PluginExecutionException wrapException(\n      Throwable t, PluginExecutorConfig<T> executorConfig) {\n    if (t instanceof PluginExecutionException) {\n      return (PluginExecutionException) t;\n    } else {\n      return new PluginExecutionException(\n          String.format(\n              \"Plugin execution error on '%s'.\", executorConfig.matchedPlugin().pluginId()),\n          t);\n    }\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginExecutorModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.inject.AbstractModule;\n\n/** Install bindings for {@link PluginExecutor}. */\npublic final class PluginExecutorModule extends AbstractModule {\n\n  @Override\n  protected void configure() {\n    bind(PluginExecutor.class).to(PluginExecutorImpl.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginLoadingModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.inject.AbstractModule;\nimport io.github.classgraph.AnnotationClassRef;\nimport io.github.classgraph.ClassInfo;\nimport io.github.classgraph.ClassInfoList;\nimport io.github.classgraph.ScanResult;\nimport java.lang.reflect.Constructor;\n\n/**\n * A Guice module that loads all {@link TsunamiPlugin TsunamiPlugins} at runtime.\n *\n * <p>This module relies on the {@link io.github.classgraph.ClassGraph} scan results to identify all\n * installed {@link TsunamiPlugin TsunamiPlugins} and bootstrap each {@link TsunamiPlugin plugin}\n * using the corresponding {@link PluginBootstrapModule} instantiated via reflection.\n */\npublic final class PluginLoadingModule extends AbstractModule {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n  private static final String TSUNAMI_PLUGIN_INTERFACE = \"com.google.tsunami.plugin.TsunamiPlugin\";\n  private static final String PLUGIN_INFO_ANNOTATION =\n      \"com.google.tsunami.plugin.annotations.PluginInfo\";\n  private static final String BOOTSTRAP_MODULE_PARAM_NAME = \"bootstrapModule\";\n\n  private final boolean bootstrapModuleAlwaysAccessible;\n  private final ScanResult classScanResult;\n\n  public PluginLoadingModule(ScanResult classScanResult) {\n    this(false, classScanResult);\n  }\n\n  @VisibleForTesting\n  PluginLoadingModule(boolean bootstrapModuleAlwaysAccessible, ScanResult classScanResult) {\n    this.bootstrapModuleAlwaysAccessible = bootstrapModuleAlwaysAccessible;\n    this.classScanResult = checkNotNull(classScanResult);\n  }\n\n  @Override\n  protected void configure() {\n    ClassInfoList tsunamiPluginClasses =\n        classScanResult\n            .getClassesImplementing(TSUNAMI_PLUGIN_INTERFACE)\n            .filter(\n                classInfo ->\n                    !classInfo.isInterface()\n                        && !classInfo.implementsInterface(\n                            \"com.google.tsunami.plugin.RemoteVulnDetector\"));\n    for (ClassInfo tsunamiPluginClass : tsunamiPluginClasses) {\n      logger.atInfo().log(\"Found plugin class: %s\", tsunamiPluginClass.getName());\n      // PluginInfo annotation is required for TsunamiPlugin.\n      if (!tsunamiPluginClass.hasAnnotation(PLUGIN_INFO_ANNOTATION)) {\n        throw new IllegalStateException(\n            String.format(\n                \"Tsunami plugin '%s' must be annotated with PluginInfo\",\n                tsunamiPluginClass.getSimpleName()));\n        }\n      install(newPluginBootstrapModule(tsunamiPluginClass));\n    }\n  }\n\n  private PluginBootstrapModule newPluginBootstrapModule(ClassInfo tsunamiPluginClass) {\n    // Retrieves the bootstrap module from the PluginInfo annotation.\n    Object bootstrapModuleValue =\n        tsunamiPluginClass\n            .getAnnotationInfo(PLUGIN_INFO_ANNOTATION)\n            .getParameterValues()\n            .getValue(BOOTSTRAP_MODULE_PARAM_NAME);\n    if (!(bootstrapModuleValue instanceof AnnotationClassRef)) {\n      throw new AssertionError(\n          String.format(\n              \"Invalid bootstrapModule parameter type for Tsunami plugin '%s'\",\n              tsunamiPluginClass.getSimpleName()));\n    }\n    ClassInfo bootstrapModuleClassInfo = ((AnnotationClassRef) bootstrapModuleValue).getClassInfo();\n    if (bootstrapModuleClassInfo == null) {\n      throw new AssertionError(\n          String.format(\n              \"bootstrapModule class for plugin '%s' not found in classpath\",\n              tsunamiPluginClass.getSimpleName()));\n    }\n\n    // Instantiate the bootstrap module via reflection.\n    try {\n      Constructor<? extends PluginBootstrapModule> pluginBootstrapModuleConstructor =\n          bootstrapModuleClassInfo.loadClass(PluginBootstrapModule.class).getDeclaredConstructor();\n      if (bootstrapModuleAlwaysAccessible) {\n        pluginBootstrapModuleConstructor.setAccessible(true);\n      }\n      return pluginBootstrapModuleConstructor.newInstance();\n    } catch (ReflectiveOperationException e) {\n      throw new AssertionError(\n          String.format(\n              \"PluginBootstrapModule '%s' for plugin '%s' must be publicly constructable via a\"\n                  + \" no-argument constructor\",\n              bootstrapModuleClassInfo.getSimpleName(), tsunamiPluginClass.getSimpleName()),\n          e);\n    }\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginManager.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.collect.ImmutableList.toImmutableList;\nimport static com.google.common.collect.ImmutableSet.toImmutableSet;\nimport static com.google.tsunami.common.data.NetworkServiceUtils.isWebService;\nimport static java.util.Arrays.stream;\n\nimport com.google.auto.value.AutoValue;\nimport com.google.common.base.Ascii;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.Streams;\nimport com.google.tsunami.plugin.annotations.ForOperatingSystemClass;\nimport com.google.tsunami.proto.MatchedPlugin;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.ReconnaissanceReport;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.TargetOperatingSystemClass;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport javax.inject.Inject;\nimport javax.inject.Provider;\n\n/**\n * Plugin manager manages all the registered plugins and provides map like interfaces for retrieving\n * plugins from the registry.\n */\npublic class PluginManager {\n  private final Map<PluginDefinition, Provider<TsunamiPlugin>> tsunamiPlugins;\n  private final TcsClient tcsClient;\n  private final ImmutableSet<String> detectorsInclude;\n  private final ImmutableSet<String> detectorsExclude;\n\n  @Inject\n  PluginManager(\n      Map<PluginDefinition, Provider<TsunamiPlugin>> tsunamiPlugins,\n      TcsClient tcsClient,\n      PluginManagerCliOptions pluginManagerCliOptions) {\n    this.tsunamiPlugins = tsunamiPlugins;\n    this.tcsClient = checkNotNull(tcsClient);\n    detectorsInclude = getDetectorNames(pluginManagerCliOptions.detectorsInclude);\n    detectorsExclude = getDetectorNames(pluginManagerCliOptions.detectorsExclude);\n  }\n\n  private static ImmutableSet<String> getDetectorNames(String detectorNames) {\n    if (detectorNames == null) {\n      return ImmutableSet.of();\n    } else {\n      return stream(detectorNames.split(\",\")).map(String::trim).collect(toImmutableSet());\n    }\n  }\n\n  /**\n   * Retrieves all {@link PortScanner} plugins.\n   *\n   * @return a list of all the installed {@link PortScanner} plugins.\n   */\n  public ImmutableList<PluginMatchingResult<PortScanner>> getPortScanners() {\n    return tsunamiPlugins.entrySet().stream()\n        .filter(entry -> entry.getKey().type().equals(PluginType.PORT_SCAN))\n        .map(\n            entry ->\n                PluginMatchingResult.<PortScanner>builder()\n                    .setPluginDefinition(entry.getKey())\n                    .setTsunamiPlugin((PortScanner) entry.getValue().get())\n                    .build())\n        .collect(toImmutableList());\n  }\n\n  /**\n   * Retrieves the first {@link PortScanner} plugin if present.\n   *\n   * @return the first installed {@link PortScanner} plugin.\n   */\n  public Optional<PluginMatchingResult<PortScanner>> getPortScanner() {\n    ImmutableList<PluginMatchingResult<PortScanner>> allPortScanners = getPortScanners();\n\n    return allPortScanners.isEmpty() ? Optional.empty() : Optional.of(allPortScanners.get(0));\n  }\n\n  /**\n   * Retrieves a {@link ServiceFingerprinter} plugin for the given {@link NetworkService}.\n   *\n   * @param networkService the target {@link NetworkService} to be fingerprinted.\n   * @return the matched {@link ServiceFingerprinter} plugin for the given network service.\n   */\n  public Optional<PluginMatchingResult<ServiceFingerprinter>> getServiceFingerprinter(\n      NetworkService networkService) {\n    return tsunamiPlugins.entrySet().stream()\n        .filter(entry -> entry.getKey().type().equals(PluginType.SERVICE_FINGERPRINT))\n        .filter(entry -> hasMatchingServiceName(networkService, entry.getKey()))\n        .map(\n            entry ->\n                PluginMatchingResult.<ServiceFingerprinter>builder()\n                    .setPluginDefinition(entry.getKey())\n                    .setTsunamiPlugin((ServiceFingerprinter) entry.getValue().get())\n                    .addMatchedService(networkService)\n                    .build())\n        .findFirst();\n  }\n\n  public ImmutableList<PluginMatchingResult<VulnDetector>> getVulnDetectors(\n      ReconnaissanceReport reconnaissanceReport) {\n    return tsunamiPlugins.entrySet().stream()\n        .filter(entry -> isVulnDetector(entry.getKey()))\n        .filter(entry -> matchCurrentCallbackServerSetup(entry.getKey()))\n        .filter(entry -> filterPluginByCliOptions(entry.getKey()))\n        .map(entry -> matchAllVulnDetectors(entry.getKey(), entry.getValue(), reconnaissanceReport))\n        .flatMap(Streams::stream)\n        .collect(toImmutableList());\n  }\n\n  public ImmutableList<VulnDetector> getAllVulnDetectors() {\n    return tsunamiPlugins.entrySet().stream()\n        .filter(entry -> isVulnDetector(entry.getKey()))\n        .map(\n            entry -> {\n              if (entry.getKey().type().equals(PluginType.VULN_DETECTION)) {\n                return (VulnDetector) entry.getValue().get();\n              }\n\n              return (RemoteVulnDetector) entry.getValue().get();\n            })\n        .collect(toImmutableList());\n  }\n\n  private static boolean isPluginListed(\n      PluginDefinition pluginDefinition, ImmutableSet<String> pluginNames, boolean defaultValue) {\n    if (pluginNames.isEmpty()) {\n      return defaultValue;\n    }\n    return pluginNames.contains(pluginDefinition.name());\n  }\n\n  private boolean filterPluginByCliOptions(PluginDefinition pluginDefinition) {\n    return isPluginListed(pluginDefinition, detectorsInclude, true)\n        && !isPluginListed(pluginDefinition, detectorsExclude, false);\n  }\n\n  private static boolean isVulnDetector(PluginDefinition pluginDefinition) {\n    return pluginDefinition.type().equals(PluginType.VULN_DETECTION)\n        || pluginDefinition.type().equals(PluginType.REMOTE_VULN_DETECTION);\n  }\n\n  private boolean matchCurrentCallbackServerSetup(PluginDefinition pluginDefinition) {\n    if (tcsClient.isCallbackServerEnabled()) {\n      return true;\n    }\n\n    return !pluginDefinition.requiresCallbackServer();\n  }\n\n  private static Optional<PluginMatchingResult<VulnDetector>> matchAllVulnDetectors(\n      PluginDefinition pluginDefinition,\n      Provider<TsunamiPlugin> vulnDetectorProvider,\n      ReconnaissanceReport reconnaissanceReport) {\n    if (pluginDefinition.type().equals(PluginType.REMOTE_VULN_DETECTION)) {\n      return matchRemoteVulnDetectors(pluginDefinition, vulnDetectorProvider, reconnaissanceReport);\n    }\n    return matchVulnDetectors(pluginDefinition, vulnDetectorProvider, reconnaissanceReport);\n  }\n\n  private static Optional<PluginMatchingResult<VulnDetector>> matchVulnDetectors(\n      PluginDefinition pluginDefinition,\n      Provider<TsunamiPlugin> vulnDetectorProvider,\n      ReconnaissanceReport reconnaissanceReport) {\n    List<NetworkService> matchedNetworkServices;\n    var allNetworkServices = reconnaissanceReport.getNetworkServicesList();\n    if (pluginDefinition.targetOperatingSystemClass().isPresent()) {\n      allNetworkServices =\n          allNetworkServices.stream()\n              .filter(\n                  networkService ->\n                      hasMatchingOperatingSystem(\n                          reconnaissanceReport.getTargetInfo(), pluginDefinition))\n              .collect(toImmutableList());\n    }\n    if (!pluginDefinition.targetServiceName().isPresent()\n        && !pluginDefinition.targetSoftware().isPresent()\n        && !pluginDefinition.isForWebService()) {\n      // No filtering annotation applied, just match all network services from reconnaissance.\n      matchedNetworkServices = allNetworkServices;\n    } else {\n      // At least one filtering annotation applied, check services to see if any one matches.\n      matchedNetworkServices =\n          allNetworkServices.stream()\n              .filter(\n                  networkService ->\n                      hasMatchingServiceName(networkService, pluginDefinition)\n                          || hasMatchingSoftware(networkService, pluginDefinition))\n              .collect(toImmutableList());\n    }\n\n    return matchedNetworkServices.isEmpty()\n        ? Optional.empty()\n        : Optional.of(\n            PluginMatchingResult.<VulnDetector>builder()\n                .setPluginDefinition(pluginDefinition)\n                .setTsunamiPlugin((VulnDetector) vulnDetectorProvider.get())\n                .addAllMatchedServices(matchedNetworkServices)\n                .build());\n  }\n\n  private static Optional<PluginMatchingResult<VulnDetector>> matchRemoteVulnDetectors(\n      PluginDefinition pluginDefinition,\n      Provider<TsunamiPlugin> tsunamiPlugin,\n      ReconnaissanceReport reconnaissanceReport) {\n    var remoteVulnDetector = (RemoteVulnDetector) tsunamiPlugin.get();\n    var builder =\n        PluginMatchingResult.<VulnDetector>builder()\n            .setTsunamiPlugin(remoteVulnDetector)\n            // PluginDefinition class for the RemoteVulnDetector.\n            .setPluginDefinition(pluginDefinition)\n            .addAllMatchedServices(reconnaissanceReport.getNetworkServicesList());\n    for (com.google.tsunami.proto.PluginDefinition remotePluginDefinition :\n        remoteVulnDetector.getAllPlugins()) {\n      var matchedPluginBuilder = MatchedPlugin.newBuilder();\n      var allNetworkServices = reconnaissanceReport.getNetworkServicesList();\n      if (remotePluginDefinition.hasTargetOperatingSystemClass()) {\n        // Prefiltering based on the Operating System, so the other potential filters are applied\n        // like if we were in an AND condition.\n        allNetworkServices =\n            allNetworkServices.stream()\n                .filter(\n                    networkService ->\n                        hasMatchingOperatingSystem(\n                            reconnaissanceReport.getTargetInfo(), remotePluginDefinition))\n                .collect(toImmutableList());\n      }\n      if (!remotePluginDefinition.hasTargetServiceName()\n          && !remotePluginDefinition.hasTargetSoftware()\n          && !remotePluginDefinition.getForWebService()) {\n        matchedPluginBuilder.setPlugin(remotePluginDefinition).addAllServices(allNetworkServices);\n      } else {\n        matchedPluginBuilder\n            .setPlugin(remotePluginDefinition)\n            .addAllServices(\n                allNetworkServices.stream()\n                    .filter(\n                        networkService ->\n                            hasMatchingServiceName(networkService, remotePluginDefinition)\n                                || hasMatchingSoftware(networkService, remotePluginDefinition))\n                    .collect(toImmutableList()));\n      }\n      remoteVulnDetector.addMatchedPluginToDetect(matchedPluginBuilder.build());\n    }\n    return Optional.of(builder.build());\n  }\n\n  private static boolean hasMatchingOperatingSystem(\n      TargetInfo targetInfo, PluginDefinition pluginDefinition) {\n    return hasMatchingOperatingSystem(\n        targetInfo,\n        getTargetOperatingSystemClass(pluginDefinition.targetOperatingSystemClass().get()));\n  }\n\n  private static boolean hasMatchingOperatingSystem(\n      TargetInfo targetInfo, com.google.tsunami.proto.PluginDefinition pluginDefinition) {\n    return hasMatchingOperatingSystem(targetInfo, pluginDefinition.getTargetOperatingSystemClass());\n  }\n\n  // Determines if the target info has a matching operating system to the plugin definition.\n  // If the target info has no info about the operating system, it will return false.\n  private static boolean hasMatchingOperatingSystem(\n      TargetInfo targetInfo, TargetOperatingSystemClass pluginOs) {\n    for (var osGuess : targetInfo.getOperatingSystemClassesList()) {\n      var osGuessAccuracy = osGuess.getAccuracy();\n      var minAccuracyWanted = pluginOs.getMinAccuracy();\n      if (minAccuracyWanted != 0 && minAccuracyWanted > osGuessAccuracy) {\n        continue;\n      }\n      if (pluginOs.getVendorList().contains(osGuess.getVendor())) {\n        return true;\n      }\n      if (pluginOs.getOsFamilyList().contains(osGuess.getOsFamily())) {\n        return true;\n      }\n    }\n\n    // None of the OS guesses matched.\n    return false;\n  }\n\n  private static boolean hasMatchingServiceName(\n      NetworkService networkService, PluginDefinition pluginDefinition) {\n    String serviceName = networkService.getServiceName();\n    boolean hasServiceNameMatch =\n        pluginDefinition.targetServiceName().isPresent()\n            && (serviceName.isEmpty()\n                || stream(pluginDefinition.targetServiceName().get().value())\n                    .anyMatch(\n                        targetServiceName ->\n                            Ascii.equalsIgnoreCase(targetServiceName, serviceName)));\n    boolean hasWebServiceMatch = pluginDefinition.isForWebService() && isWebService(networkService);\n    return hasServiceNameMatch || hasWebServiceMatch;\n  }\n\n  private static boolean hasMatchingServiceName(\n      NetworkService networkService, com.google.tsunami.proto.PluginDefinition pluginDefinition) {\n    String serviceName = networkService.getServiceName();\n    boolean hasServiceNameMatch =\n        pluginDefinition.hasTargetServiceName()\n            && (serviceName.isEmpty()\n                || pluginDefinition.getTargetServiceName().getValueList().stream()\n                    .anyMatch(\n                        targetServiceName ->\n                            Ascii.equalsIgnoreCase(targetServiceName, serviceName)));\n    boolean hasWebServiceMatch =\n        pluginDefinition.getForWebService() && isWebService(networkService);\n    return hasServiceNameMatch || hasWebServiceMatch;\n  }\n\n  private static boolean hasMatchingSoftware(\n      NetworkService networkService, PluginDefinition pluginDefinition) {\n    String softwareName = networkService.getSoftware().getName();\n    return pluginDefinition.targetSoftware().isPresent()\n        && (softwareName.isEmpty()\n            || Ascii.equalsIgnoreCase(\n                pluginDefinition.targetSoftware().get().name(), softwareName));\n  }\n\n  private static boolean hasMatchingSoftware(\n      NetworkService networkService, com.google.tsunami.proto.PluginDefinition pluginDefinition) {\n    String softwareName = networkService.getSoftware().getName();\n    return pluginDefinition.hasTargetSoftware()\n        && (softwareName.isEmpty()\n            || Ascii.equalsIgnoreCase(\n                pluginDefinition.getTargetSoftware().getName(), softwareName));\n  }\n\n  /** Matched {@link TsunamiPlugin}s based on certain criteria. */\n  @AutoValue\n  public abstract static class PluginMatchingResult<T extends TsunamiPlugin> {\n    public abstract PluginDefinition pluginDefinition();\n\n    public abstract T tsunamiPlugin();\n\n    public abstract ImmutableList<NetworkService> matchedServices();\n\n    public String pluginId() {\n      return pluginDefinition().id();\n    }\n\n    public static <T extends TsunamiPlugin> Builder<T> builder() {\n      return new AutoValue_PluginManager_PluginMatchingResult.Builder<T>();\n    }\n\n    /** Builder for {@link PluginMatchingResult}. */\n    @SuppressWarnings(\"CanIgnoreReturnValueSuggester\")\n    @AutoValue.Builder\n    public abstract static class Builder<T extends TsunamiPlugin> {\n      public abstract Builder<T> setPluginDefinition(PluginDefinition value);\n\n      public abstract Builder<T> setTsunamiPlugin(T value);\n\n      abstract ImmutableList.Builder<NetworkService> matchedServicesBuilder();\n\n      public Builder<T> addMatchedService(NetworkService networkService) {\n        matchedServicesBuilder().add(networkService);\n        return this;\n      }\n\n      public Builder<T> addAllMatchedServices(Iterable<NetworkService> networkServices) {\n        matchedServicesBuilder().addAll(networkServices);\n        return this;\n      }\n\n      public abstract PluginMatchingResult<T> build();\n    }\n  }\n\n  private static TargetOperatingSystemClass getTargetOperatingSystemClass(\n      ForOperatingSystemClass target) {\n    var builder = TargetOperatingSystemClass.newBuilder().setMinAccuracy(target.minAccuracy());\n    for (String vendor : target.vendor()) {\n      builder.addVendor(vendor);\n    }\n    for (String osfamily : target.osfamily()) {\n      builder.addOsFamily(osfamily);\n    }\n    return builder.build();\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginManagerCliOptions.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.beust.jcommander.Parameter;\nimport com.beust.jcommander.Parameters;\nimport com.google.tsunami.common.cli.CliOption;\n\n/**\n * Command line arguments for the PluginManager.\n *\n * <p>Which detectors to include/exclude? Matching is executed against the name of the detector\n * (e.g. RsyncRceDetector).\n *\n * <p>To execute one single detector, override `detectors-include`, e.g.:\n * `--detectors-include=RsyncRceDetector`.\n *\n * <p>To disable a single detector, override `detectors-exclude`, e.g.\n * `--detectors-exclude=RsyncRceDetector`.\n */\n@Parameters(separators = \"=\")\npublic final class PluginManagerCliOptions implements CliOption {\n  @Parameter(\n      names = \"--detectors-include\",\n      description =\n          \"Comma separated list of detector names to include in the scan. By default, all detectors\"\n              + \" are included.\")\n  public String detectorsInclude;\n\n  @Parameter(\n      names = \"--detectors-exclude\",\n      description =\n          \"Comma separated list of detector names to exclude from the scan. By default no detectors\"\n              + \" are skipped.\")\n  public String detectorsExclude;\n\n  // Validations are done in {@link PayloadGeneratorModule}.\n  @Override\n  public void validate() {}\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginServiceClient.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.tsunami.proto.ListPluginsRequest;\nimport com.google.tsunami.proto.ListPluginsResponse;\nimport com.google.tsunami.proto.PluginServiceGrpc;\nimport com.google.tsunami.proto.PluginServiceGrpc.PluginServiceFutureStub;\nimport com.google.tsunami.proto.RunCompactRequest;\nimport com.google.tsunami.proto.RunRequest;\nimport com.google.tsunami.proto.RunResponse;\nimport io.grpc.Channel;\nimport io.grpc.Deadline;\nimport io.grpc.health.v1.HealthCheckRequest;\nimport io.grpc.health.v1.HealthCheckResponse;\nimport io.grpc.health.v1.HealthGrpc;\nimport io.grpc.health.v1.HealthGrpc.HealthFutureStub;\n\n/**\n * Client side gRPC handler for the PluginService RPC protocol. Main handler for all gRPC calls to\n * the language-specific servers.\n */\npublic final class PluginServiceClient {\n\n  private final HealthFutureStub healthService;\n  private final PluginServiceFutureStub pluginService;\n\n  PluginServiceClient(Channel channel) {\n    this.healthService = HealthGrpc.newFutureStub(checkNotNull(channel));\n    this.pluginService = PluginServiceGrpc.newFutureStub(checkNotNull(channel));\n  }\n\n  /**\n   * Sends a run request to the gRPC language server with a specified deadline.\n   *\n   * @param request The main request containing plugins to run.\n   * @param deadline The timeout of the service.\n   * @return The future of the run response.\n   */\n  public ListenableFuture<RunResponse> runWithDeadline(RunRequest request, Deadline deadline) {\n    return pluginService.withDeadline(deadline).run(request);\n  }\n\n  /**\n   * Sends a runCompact request to the gRPC language server with a specified deadline.\n   *\n   * @param request The main request containing plugins to run.\n   * @param deadline The timeout of the service.\n   * @return The future of the run response.\n   */\n  public ListenableFuture<RunResponse> runCompactWithDeadline(\n      RunCompactRequest request, Deadline deadline) {\n    return pluginService.withDeadline(deadline).runCompact(request);\n  }\n\n  /**\n   * Sends a list plugins request to the gRPC language server with a specified deadline.\n   *\n   * @param request The main request to notify the language server to send their plugins.\n   * @param deadline The timeout of the service.\n   * @return The future of the run response.\n   */\n  public ListenableFuture<ListPluginsResponse> listPluginsWithDeadline(\n      ListPluginsRequest request, Deadline deadline) {\n    return pluginService.withDeadline(deadline).listPlugins(request);\n  }\n\n  /**\n   * Sends a health check request to retrieve the status of the language server.\n   *\n   * @param request The health check request to send to the language server.\n   * @param deadline The maximum time to keep this call alive.\n   * @return The language server's status via {@link HealthCheckResponse}.\n   */\n  public ListenableFuture<HealthCheckResponse> checkHealthWithDeadline(\n      HealthCheckRequest request, Deadline deadline) {\n    return healthService.withDeadline(deadline).check(request);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PluginType.java",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\n/** The types of a Tsunami plugin. */\npublic enum PluginType {\n  /** A plugin that identifies the open ports of the scanning target. */\n  PORT_SCAN,\n\n  /**\n   * A plugin that performs service specific fingerprints and identifies running software and\n   * versions.\n   */\n  SERVICE_FINGERPRINT,\n\n  /** A plugin that detects certain vulnerabilities on an exposed network service. */\n  VULN_DETECTION,\n\n  /** A plugin that contains vulnerability detectors from language servers. */\n  REMOTE_VULN_DETECTION\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/PortScanner.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.tsunami.proto.PortScanningReport;\nimport com.google.tsunami.proto.ScanTarget;\n\n/**\n * A {@link TsunamiPlugin} that performs the port scanning tasks.\n *\n * <p>A port scanner in general should perform network probing to identify network services (active\n * ports) running on a target and/or gather any interesting information about the scanning target\n * like its architecture, operating systems, etc.\n */\npublic interface PortScanner extends TsunamiPlugin {\n\n  /**\n   * Performs the port scan on the given {@code scanTarget}.\n   *\n   * @param scanTarget the target to be scanned.\n   * @return a {@link PortScanningReport} that captures the full port scanning results.\n   */\n  PortScanningReport scan(ScanTarget scanTarget);\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/RemoteVulnDetector.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.tsunami.proto.MatchedPlugin;\nimport com.google.tsunami.proto.PluginDefinition;\n\n/**\n * A special {@link VulnDetector} to execute vulnerability detector plugins from their specified\n * language server.\n */\npublic interface RemoteVulnDetector extends VulnDetector {\n\n  /**\n   * Retrieve all plugins from the language server through an RPC call.\n   *\n   * @return List of all plugin definitions. If the language server throws an error, an empty list\n   *     will be returned.\n   */\n  ImmutableList<PluginDefinition> getAllPlugins();\n\n  /**\n   * Add a {@link MatchedPlugin} to allow this {@link RemoteVulnDetector} to run detection for this\n   * plugin through the language server.\n   *\n   * @param pluginToRun The plugin to allow this {@link RemoteVulnDetector} to run.\n   */\n  void addMatchedPluginToDetect(MatchedPlugin pluginToRun);\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/RemoteVulnDetectorImpl.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static java.util.concurrent.TimeUnit.SECONDS;\n\nimport com.google.api.client.util.BackOff;\nimport com.google.api.client.util.ExponentialBackOff;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Sets;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.common.util.concurrent.Uninterruptibles;\nimport com.google.tsunami.common.server.CompactRunRequestHelper;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.ListPluginsRequest;\nimport com.google.tsunami.proto.MatchedPlugin;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.PluginDefinition;\nimport com.google.tsunami.proto.RunRequest;\nimport com.google.tsunami.proto.RunResponse;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.Vulnerability;\nimport io.grpc.Channel;\nimport io.grpc.Deadline;\nimport io.grpc.health.v1.HealthCheckRequest;\nimport io.grpc.health.v1.HealthCheckResponse;\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\n\n/** Facilitates communication with remote detectors. */\npublic final class RemoteVulnDetectorImpl implements RemoteVulnDetector {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  // Default duration deadline for the detect() RPC call\n  // Remote detectors, especially ones using the callback server, require additional buffer to send\n  // requests and responses.\n  private static final Duration DEFAULT_DEADLINE_DETECT = Duration.ofSeconds(150);\n  // For all other operations, including health check:\n  private static final Duration DEFAULT_DEADLINE = Duration.ofSeconds(10);\n\n  private final PluginServiceClient service;\n  private final Set<MatchedPlugin> pluginsToRun;\n  private final ExponentialBackOff backoff;\n  private final int maxAttempts;\n  private final Duration detectDeadline;\n  private boolean wantCompactRunRequest = false;\n\n  RemoteVulnDetectorImpl(\n      Channel channel, ExponentialBackOff backoff, int maxAttempts, Duration detectDeadline) {\n    this.service = new PluginServiceClient(checkNotNull(channel));\n    this.pluginsToRun = Sets.newHashSet();\n    this.backoff = backoff;\n    this.maxAttempts = maxAttempts;\n    this.detectDeadline = detectDeadline != null ? detectDeadline : DEFAULT_DEADLINE_DETECT;\n  }\n\n  @Override\n  public DetectionReportList detect(\n      TargetInfo target, ImmutableList<NetworkService> matchedServices) {\n    try {\n      if (checkHealthWithBackoffs()) {\n        var runRequest =\n            RunRequest.newBuilder().setTarget(target).addAllPlugins(pluginsToRun).build();\n        logger.atInfo().log(\"Detecting with language server plugins...\");\n        RunResponse runResponse;\n        if (this.wantCompactRunRequest) {\n          var runCompactRequest = CompactRunRequestHelper.compress(runRequest);\n          runResponse =\n              service\n                  .runCompactWithDeadline(\n                      runCompactRequest, Deadline.after(detectDeadline.toSeconds(), SECONDS))\n                  .get();\n        } else {\n          runResponse =\n              service\n                  .runWithDeadline(runRequest, Deadline.after(detectDeadline.toSeconds(), SECONDS))\n                  .get();\n        }\n        return runResponse.getReports();\n      }\n    } catch (InterruptedException | ExecutionException e) {\n      throw new LanguageServerException(\"Failed to get response from language server.\", e);\n    }\n    return DetectionReportList.getDefaultInstance();\n  }\n\n  @Override\n  public ImmutableList<Vulnerability> getAdvisories() {\n    // TODO: b/422968545 - The remote detectors also need to support getAdvisories().\n    return ImmutableList.of();\n  }\n\n  @Override\n  public ImmutableList<PluginDefinition> getAllPlugins() {\n    try {\n      if (checkHealthWithBackoffs()) {\n        logger.atInfo().log(\"Getting language server plugins...\");\n        var listPluginsResponse =\n            service\n                .listPluginsWithDeadline(\n                    ListPluginsRequest.getDefaultInstance(),\n                    Deadline.after(DEFAULT_DEADLINE.toSeconds(), SECONDS))\n                .get();\n        // Note: each plugin service client has a dedicated RemoteVulnDetectorImpl instance,\n        // so we can safely set this flag here.\n        this.wantCompactRunRequest = listPluginsResponse.getWantCompactRunRequest();\n        return ImmutableList.copyOf(listPluginsResponse.getPluginsList());\n      } else {\n        return ImmutableList.of();\n      }\n    } catch (InterruptedException | ExecutionException e) {\n      throw new LanguageServerException(\"Failed to get response from language server.\", e);\n    }\n  }\n\n  private boolean checkHealthWithBackoffs() {\n    // After starting the language server, this is our first attempt to establish a connection\n    // between the Java and the language server.\n    // Sometimes the language server may need longer time to ramp up its health service, so we need\n    // to implement exponential retries to manage those circumstances.\n    backoff.reset();\n    int attempt = 0;\n    while (attempt < maxAttempts) {\n      try {\n        var healthy =\n            service\n                .checkHealthWithDeadline(\n                    HealthCheckRequest.getDefaultInstance(),\n                    Deadline.after(DEFAULT_DEADLINE.toSeconds(), SECONDS))\n                .get()\n                .getStatus()\n                .equals(HealthCheckResponse.ServingStatus.SERVING);\n        if (!healthy) {\n          logger.atWarning().log(\"Language server is not serving.\");\n        }\n        return healthy;\n      } catch (InterruptedException | ExecutionException e) {\n        attempt++;\n        try {\n          long backOffMillis = backoff.nextBackOffMillis();\n          if (backOffMillis != BackOff.STOP) {\n            Uninterruptibles.sleepUninterruptibly(backOffMillis, TimeUnit.MILLISECONDS);\n          }\n        } catch (IOException ioe) {\n          // ignore\n          logger.atWarning().log(\"Failed to sleep for %s\", ioe.getCause().getMessage());\n        }\n        if (attempt == maxAttempts) {\n          throw new LanguageServerException(\"Language service is not registered.\", e.getCause());\n        }\n      }\n    }\n    return false;\n  }\n\n  @Override\n  public void addMatchedPluginToDetect(MatchedPlugin plugin) {\n    this.pluginsToRun.add(plugin);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/RemoteVulnDetectorLoadingModule.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\nimport com.google.api.client.util.ExponentialBackOff;\nimport com.google.auto.value.AutoAnnotation;\nimport com.google.auto.value.AutoBuilder;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Strings;\nimport com.google.common.collect.ImmutableList;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.multibindings.MapBinder;\nimport com.google.tsunami.common.server.LanguageServerCommand;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport io.grpc.Channel;\nimport io.grpc.netty.NegotiationType;\nimport io.grpc.netty.NettyChannelBuilder;\nimport java.time.Duration;\n\n/** A Guice module that loads all {@link RemoteVulnDetector RemoteVulnDetectors} at runtime. */\npublic final class RemoteVulnDetectorLoadingModule extends AbstractModule {\n  private static final int MAX_MESSAGE_SIZE =\n      10 * 1000 * 1000; // Max incoming gRPC message size 10MB.\n  private static final int INITIAL_WAIT_TIME_MS = 200;\n  private static final int MAX_WAIT_TIME_MS = 30000;\n  private static final int WAIT_TIME_MULTIPLIER = 5;\n  private static final int MAX_ATTEMPTS = 5;\n  // Exponential delay attempts (>125 seconds before taking randomization factor into account):\n  // ~200ms\n  // ~1000ms\n  // ~5000ms\n  // ~25000ms\n  // ~1250000ms\n  private static final ExponentialBackOff BACKOFF =\n      new ExponentialBackOff.Builder()\n          .setInitialIntervalMillis(INITIAL_WAIT_TIME_MS)\n          .setRandomizationFactor(0.1)\n          .setMultiplier(WAIT_TIME_MULTIPLIER)\n          .setMaxElapsedTimeMillis(MAX_WAIT_TIME_MS)\n          .build();\n  private final ImmutableList<LanguageServerCommand> availableServerPorts;\n\n  public RemoteVulnDetectorLoadingModule(ImmutableList<LanguageServerCommand> serverPorts) {\n    this.availableServerPorts = checkNotNull(serverPorts);\n  }\n\n  @Override\n  protected void configure() {\n    MapBinder<PluginDefinition, TsunamiPlugin> tsunamiPluginBinder =\n        MapBinder.newMapBinder(binder(), PluginDefinition.class, TsunamiPlugin.class);\n    availableServerPorts.forEach(\n        command -> {\n          var channel = getLanguageServerChannel(command);\n          var deadline =\n              command.deadlineRunSeconds() > 0\n                  ? Duration.ofSeconds(command.deadlineRunSeconds())\n                  : null;\n          tsunamiPluginBinder\n              .addBinding(getRemoteVulnDetectorPluginDefinition(channel.hashCode()))\n              .toInstance(new RemoteVulnDetectorImpl(channel, BACKOFF, MAX_ATTEMPTS, deadline));\n        });\n  }\n\n  private Channel getLanguageServerChannel(LanguageServerCommand command) {\n    if (Strings.isNullOrEmpty(command.serverCommand())) {\n      return NettyChannelBuilder.forTarget(\n              String.format(\"%s:%s\", command.serverAddress(), command.port()))\n          .negotiationType(NegotiationType.PLAINTEXT)\n          .maxInboundMessageSize(MAX_MESSAGE_SIZE)\n          .build();\n    } else {\n      // TODO(b/289462738): Support IPv6 loopback (::1) interface\n      return NettyChannelBuilder.forTarget(\"127.0.0.1:\" + command.port())\n          .negotiationType(NegotiationType.PLAINTEXT)\n          .maxInboundMessageSize(MAX_MESSAGE_SIZE)\n          .build();\n    }\n  }\n\n  // TODO(b/239095108): Change channelIds to something more meaningful to identify\n  // RemoteVulnDetectors.\n  @VisibleForTesting\n  static PluginDefinition getRemoteVulnDetectorPluginDefinition(int channelId) {\n    return PluginDefinition.forRemotePlugin(\n        pluginInfoBuilder()\n            .type(PluginType.REMOTE_VULN_DETECTION)\n            .name(\"RemoteVulnDetector\" + channelId)\n            .description(\"Synthetic PluginInfo for RemoteVulnDetectors\")\n            .author(\"Tsunami\")\n            .version(\"0.0.1\")\n            .bootstrapModule(RemoteVulnDetectorBootstrapLoadingModule.class)\n            .build());\n  }\n\n  /** Builder to build a {@link PluginInfo} annotation at runtime for RemoteVulnDetectors. */\n  @AutoBuilder(callMethod = \"pluginInfo\")\n  interface PluginInfoBuilder {\n    PluginInfoBuilder type(PluginType type);\n\n    PluginInfoBuilder name(String name);\n\n    PluginInfoBuilder description(String description);\n\n    PluginInfoBuilder author(String author);\n\n    PluginInfoBuilder version(String version);\n\n    PluginInfoBuilder bootstrapModule(Class<? extends PluginBootstrapModule> bootstrapModule);\n\n    PluginInfo build();\n  }\n\n  // Used by {@link AutoBuilder}\n  @SuppressWarnings(\"unused\")\n  @AutoAnnotation\n  static PluginInfo pluginInfo(\n      PluginType type,\n      String name,\n      String description,\n      String author,\n      String version,\n      Class<? extends PluginBootstrapModule> bootstrapModule) {\n    return new AutoAnnotation_RemoteVulnDetectorLoadingModule_pluginInfo(\n        type, name, description, author, version, bootstrapModule);\n  }\n\n  static PluginInfoBuilder pluginInfoBuilder() {\n    return new AutoBuilder_RemoteVulnDetectorLoadingModule_PluginInfoBuilder();\n  }\n\n  private static class RemoteVulnDetectorBootstrapLoadingModule extends PluginBootstrapModule {\n    @Override\n    protected void configurePlugin() {}\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/ServiceFingerprinter.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.tsunami.proto.FingerprintingReport;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.TargetInfo;\n\n/**\n * A {@link TsunamiPlugin} that performs the service fingerprinting tasks.\n *\n * <p>A fingerprinter usually performs service specific fingerprinting jobs to better understand an\n * exposed service. For example, a web fingerprinter would help the scanner identify which web\n * applications and their corresponding versions are exposed under port 80 or 443.\n *\n * <p>NOTE: ServiceFingerprinters must be annotated by the {@link\n * com.google.tsunami.plugin.annotations.ForServiceName} annotation.\n */\npublic interface ServiceFingerprinter extends TsunamiPlugin {\n\n  /**\n   * Performs the service fingerprinting job on the given {@code targetInfo} and {@code\n   * networkService}.\n   *\n   * @param targetInfo information about the target to be scanned.\n   * @param networkService information about the specific network service to be fingerprinted.\n   * @return a {@link FingerprintingReport} that captures all the details about the targeted\n   *     service.\n   */\n  FingerprintingReport fingerprint(TargetInfo targetInfo, NetworkService networkService);\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/TcsClient.java",
    "content": "/*\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.tsunami.common.net.UrlUtils.removeTrailingSlashes;\n\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.common.net.HostAndPort;\nimport com.google.common.net.InetAddresses;\nimport com.google.common.net.InternetDomainName;\nimport com.google.protobuf.util.JsonFormat;\nimport com.google.tsunami.callbackserver.common.CbidGenerator;\nimport com.google.tsunami.callbackserver.common.CbidProcessor;\nimport com.google.tsunami.callbackserver.common.Sha3CbidGenerator;\nimport com.google.tsunami.callbackserver.proto.PollingResult;\nimport com.google.tsunami.common.net.http.HttpClient;\nimport com.google.tsunami.common.net.http.HttpHeaders;\nimport com.google.tsunami.common.net.http.HttpRequest;\nimport com.google.tsunami.common.net.http.HttpResponse;\nimport java.io.IOException;\nimport java.util.Optional;\n\n/** TcsClient for generating oob payload and retriving result */\npublic final class TcsClient {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n  private static final JsonFormat.Parser jsonParser = JsonFormat.parser();\n\n  private final String callbackAddress;\n  private final int callbackPort;\n  private final String pollingBaseUrl;\n\n  private final CbidGenerator cbidGenerator;\n  private final HttpClient httpClient;\n\n  public TcsClient(\n      String callbackAddress, int callbackPort, String pollingBaseUrl, HttpClient httpClient) {\n    this.callbackAddress = checkNotNull(callbackAddress);\n    this.callbackPort = callbackPort;\n    this.pollingBaseUrl = checkNotNull(pollingBaseUrl);\n    this.cbidGenerator = new Sha3CbidGenerator();\n    this.httpClient = checkNotNull(httpClient);\n  }\n\n  /**\n   * Checks whether the callback server is configured. Detectors should use this to determine if\n   * they can use the callback server for detecting vulnerabilities\n   *\n   * @return whether the callback server is enabled\n   */\n  public boolean isCallbackServerEnabled() {\n    // only return false when all config fields are empty so that improper config (e.g., missing\n    // certain fields) can be exposed. Note that {@link TcsClient} already checks that all the\n    // class variables are not null.\n    return !this.callbackAddress.isEmpty()\n        && isValidPortNumber(this.callbackPort)\n        && !this.pollingBaseUrl.isEmpty();\n  }\n\n  public String getCallbackUri(String secretString) {\n    String cbid = cbidGenerator.generate(secretString);\n\n    if (!isValidPortNumber(callbackPort)) {\n      throw new AssertionError(\"Invalid callbackPort number specified\");\n    }\n\n    HostAndPort hostAndPort =\n        callbackPort == 80\n            ? HostAndPort.fromHost(callbackAddress)\n            : HostAndPort.fromParts(callbackAddress, callbackPort);\n\n    // check if the specified address is raw IP or domain\n    if (InetAddresses.isInetAddress(callbackAddress)) {\n      return CbidProcessor.addCbidToUrl(cbid, hostAndPort);\n    } else if (InternetDomainName.isValid(callbackAddress)) {\n      return CbidProcessor.addCbidToSubdomain(cbid, hostAndPort);\n    }\n    // Should never reach here\n    throw new AssertionError(\"Unrecognized address format, should be IP address or valid domain\");\n  }\n\n  public String getCallbackAddress() {\n    if (InetAddresses.isInetAddress(callbackAddress)) {\n      return callbackAddress;\n    } else if (InternetDomainName.isValid(callbackAddress)) {\n      return callbackAddress;\n    }\n\n    // Should never reach here\n    throw new AssertionError(\"Unrecognized address format, should be IP address or valid domain\");\n  }\n\n  public int getCallbackPort() {\n    if (!isValidPortNumber(callbackPort)) {\n      throw new AssertionError(\"Invalid callbackPort number specified\");\n    }\n\n    return callbackPort;\n  }\n\n  public boolean hasOobLog(String secretString) {\n    // making a blocking call to get result\n    Optional<PollingResult> result = sendPollingRequest(secretString);\n\n    if (result.isPresent()) {\n      // In the future we may refactor hasOobLog() to return finer grained info about what kind\n      // of oob is logged\n      return result.get().getHasDnsInteraction() || result.get().getHasHttpInteraction();\n    } else {\n      // we may choose to retry sendPollingRequest() if oob interactions do arrive late.\n      return false;\n    }\n  }\n\n  private Optional<PollingResult> sendPollingRequest(String secretString) {\n    HttpRequest request =\n        HttpRequest.get(\n                String.format(\"%s/?secret=%s\", removeTrailingSlashes(pollingBaseUrl), secretString))\n            .setHeaders(HttpHeaders.builder().addHeader(\"Cache-Control\", \"no-cache\").build())\n            .build();\n    try {\n      HttpResponse response = httpClient.send(request);\n      if (response.status().isSuccess()) {\n        PollingResult.Builder result = PollingResult.newBuilder();\n        jsonParser.merge(response.bodyString().get(), result);\n        return Optional.of(result.build());\n      } else {\n        logger.atInfo().log(\"Callback server returned %s\", response.status().code());\n      }\n    } catch (IOException e) {\n      logger.atWarning().withCause(e).log(\"Polling request failed\");\n    }\n    return Optional.empty();\n  }\n\n  private static boolean isValidPortNumber(int port) {\n    return port > 0 && port < 65536;\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/TcsClientCliOptions.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.beust.jcommander.Parameter;\nimport com.beust.jcommander.Parameters;\nimport com.google.tsunami.common.cli.CliOption;\n\n/** Command line arguments for the Tsunami callbackserver. */\n@Parameters(separators = \"=\")\npublic final class TcsClientCliOptions implements CliOption {\n  @Parameter(\n      names = \"--callback-address\",\n      description = \"Address (ip or domain) of TCS http service.\")\n  public String callbackAddress;\n\n  @Parameter(names = \"--callback-port\", description = \"Port of TCS http service.\")\n  public Integer callbackPort;\n\n  @Parameter(\n      names = \"--callback-polling-uri\",\n      description = \"Uri (ip/domain + port) of TCS polling service.\")\n  public String pollingUri;\n\n  // Validations are done in {@link PayloadGeneratorModule}.\n  @Override\n  public void validate() {}\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/TcsConfigProperties.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.tsunami.common.config.annotations.ConfigProperties;\n\n/** Configuration properties for Tsunami callbackserver. */\n@ConfigProperties(\"plugin.callbackserver\")\npublic final class TcsConfigProperties {\n  /** Address (ip or domain) of TCS http service. */\n  public String callbackAddress;\n\n  /** Port of TCS http service. */\n  public Integer callbackPort;\n\n  /** Uri (ip/domain + port) of TCS polling service. */\n  public String pollingUri;\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/TsunamiPlugin.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\n/**\n * Simple plugin interface for Tsunami.\n *\n * <p>All concrete plugins for Tsunami must implement this interface. Otherwise it won't be\n * identified as a valid plugin for Tsunami.\n */\npublic interface TsunamiPlugin {}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/VulnDetector.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.Vulnerability;\n\n/**\n * A {@link TsunamiPlugin} that detects potential vulnerabilities on the target.\n *\n * <p>Usually a vulnerability detector takes the information about an exposed network service,\n * detects whether the service is vulnerable to a specific vulnerability, and reports the detection\n * results.\n */\npublic interface VulnDetector extends TsunamiPlugin {\n\n  /**\n   * Performs the detection task for specific vulnerabilities.\n   *\n   * @param targetInfo information about the scanning target itself\n   * @param matchedServices a list of network services whose vulnerabilities could be detected by\n   *     this plugin\n   * @return a {@link DetectionReportList} for all the vulnerabilities of the scanning target.\n   */\n  DetectionReportList detect(\n      TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices);\n\n  /**\n   * Provides access to the information on the vulnerabilities detected by this plugin. To avoid\n   * confusion, information about a vulnerability is referred to as an advisory.\n   *\n   * @return the advisory details.\n   */\n  abstract ImmutableList<Vulnerability> getAdvisories();\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/annotations/ForOperatingSystemClass.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * An annotation for marking the target operating system for a Tsunami {@link\n * com.google.tsunami.plugin.VulnDetector} plugin.\n *\n * <p>If annotated by this annotation, the {@link com.google.tsunami.plugin.VulnDetector} will only\n * be executed by the scanner when the target network service is running on the described Operating\n * System.\n *\n * <p>Example usage:\n *\n * <pre>{@code\n * {@literal @}ForOperatingSystemClass({\n *   vendor = \"Linux\",\n *   minAccuracy = 90\n * })\n * public class ExamplePlugin implements VulnDetector {\n *   // ...\n * }\n * }</pre>\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface ForOperatingSystemClass {\n\n  // The vendor of the target operating system, e.g. \"Microsoft\"\n  String[] vendor() default \"\";\n\n  // The family of the target operating system, e.g. \"Windows\"\n  String[] osfamily() default \"\";\n\n  // The minimum accuracy of the target operating system, e.g. 90\n  int minAccuracy() default 0;\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/annotations/ForServiceName.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * An annotation for marking the target service name(s) for a Tsunami {@link\n * com.google.tsunami.plugin.VulnDetector} plugin.\n *\n * <p>If annotated by this annotation, the {@link com.google.tsunami.plugin.VulnDetector} will only\n * be executed by the scanner when the scan target exposed a network service whose name matches the\n * listed service name in the annotation. Network service names should genuinely follow those listed\n * at RFC6335.\n *\n * Example usage:\n *\n * <pre>{@code\n * {@literal @}ForServiceName({\"http\", \"https\"})\n * public class ExamplePlugin implements VulnDetector {\n *   // ...\n * }\n * }</pre>\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface ForServiceName {\n  /**\n   * Array of target network service names for a Tsunami {@link\n   * com.google.tsunami.plugin.VulnDetector} plugin. The values for application layer protocols\n   * should genuinely follow naming conventions listed at RFC6335.\n   *\n   * @return the targeted network service names for a Tsunami plugin.\n   */\n  String[] value();\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/annotations/ForSoftware.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * An annotation for marking the target software and target version for a Tsunami {@link\n * com.google.tsunami.plugin.VulnDetector} plugin.\n *\n * <p>If annotated by this annotation, the {@link com.google.tsunami.plugin.VulnDetector} will only\n * be executed by the scanner when the scan target is running the matching software behind a network\n * service.\n *\n * Example usage:\n *\n * <pre>{@code\n * {@literal @}ForSoftware(\n *   name = \"WordPress\",\n *   versions = {\n *     \"0.8\",\n *     \"0.9\",\n *     \"[1.3,2.0)\"\n *   }\n * )\n * public class ExamplePlugin implements VulnDetector {\n *   // ...\n * }\n * }</pre>\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface ForSoftware {\n\n  /**\n   * Name of the target software, case insensitive.\n   *\n   * @return target software name.\n   */\n  // TODO(b/145315535): handle name conflicts, include other properties that uniquely identify\n  // software.\n  String name();\n\n  /**\n   * Array of versions and version ranges of the target software.\n   *\n   * <p>Some version and version range examples are:\n   *\n   * <ul>\n   *   <li><code>1.0</code> Version 1.0.\n   *   <li><code>[1.0,2.0)</code> Version 1.0 (inclusive) to 2.0 (exclusive).\n   *   <li><code>[1.0,2.0]</code> Version 1.0 to 2.0 (both inclusive).\n   *   <li><code>[1.0,)</code> Version 1.0 (inclusive) and higher.\n   *   <li><code>(,1.0]</code> Version 1.0 (inclusive) and lower.\n   * </ul>\n   *\n   * Example value for this field:\n   *\n   * <ul>\n   *   <li><code>[ 1.0 ]</code>\n   *   <li><code>[ 1.0, [1.1, 1.5) ]</code>\n   *   <li><code>[ 1.0, [1.1, 1.5), 1.7, 1.8 ]</code>\n   *   <li><code>[ (,1.0], [1.1, 1.3), [1.4,) ]</code>\n   * </ul>\n   *\n   * @return target software versions.\n   */\n  String[] versions() default {};\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/annotations/ForWebService.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * An util annotation that is shorthand of {@code {@literal @}ForServiceName({\"http\", \"https\",\n * ...})}, marking that the intended network services of a plugin are web services.\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface ForWebService {}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/annotations/PluginInfo.java",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.annotations;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\nimport com.google.tsunami.plugin.PluginType;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * An annotation for adding related information about a Tsunami plugin.\n *\n * Example usage:\n *\n * <pre>{@code\n * {@literal @}PluginInfo(\n *   type = PluginType.VULN_DETECTOR,\n *   name = \"example_plugin\",\n *   version = \"1.0\",\n *   description = \"An example plugin that demonstrates the usage of the PluginInfo annotation\",\n *   author = \"Author A (a@example.com)\",\n *   bootstrapModule = ExamplePluginBootstrapModule.class})\n * public class ExamplePlugin implements VulnDetector {\n *   // ...\n * }\n * }</pre>\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface PluginInfo {\n  /**\n   * The type of this plugin.\n   *\n   * @return Tsunami plugin type.\n   */\n  PluginType type();\n\n  /**\n   * The short name of this plugin.\n   *\n   * <p><b>Convention:</b> use {@code lower_snake_case} style (lowercase words separated by\n   * underscores) for this name.\n   *\n   * @return Tsunami plugin name.\n   */\n  // TODO(magl): add compile time style validation.\n  String name();\n\n  /**\n   * The version of this plugin.\n   *\n   * <p><b>Convention:</b> follow the {@code major.minor} version scheme.\n   *\n   * @return Tsunami plugin version.\n   */\n  // TODO(magl): add compile time style validation.\n  String version();\n\n  /**\n   * Detailed description of this plugin.\n   *\n   * <p>In general, the description of a plugin should tell the purpose of this plugin. For example,\n   * for a {@code VULN_DETECTOR}, the description should mention the affected software,\n   * vulnerability, affected version ranges, etc.\n   *\n   * @return Tsunami plugin description.\n   */\n  String description();\n\n  /**\n   * Author of this plugin.\n   *\n   * <p><b>Convention:</b> \"Name (contact email)\". For example, {@code Alice (Alice@example.com)}.\n   *\n   * @return Tsunami plugin author.\n   */\n  // TODO(magl): add compile time style validation.\n  String author();\n\n  /**\n   * Module for bootstrapping this plugin.\n   *\n   * @return Tsunami plugin's bootstrap module class.\n   */\n  Class<? extends PluginBootstrapModule> bootstrapModule();\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/annotations/RequiresCallbackServer.java",
    "content": "/*\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Annotation for marking that a plugin can only run if the callback server is enabled.\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface RequiresCallbackServer {}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/payload/NotImplementedException.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.tsunami.plugin.payload;\n\nimport com.google.errorprone.annotations.FormatMethod;\nimport com.google.errorprone.annotations.FormatString;\n\n/**\n * Thrown whenever a {@link com.google.tsunami.proto.PayloadGeneratorConfig} results in a\n * combination that does not have a payload.\n *\n * <p> To reduce the burden on callers, this is an unchecked exception. The goal is simply to\n * notify the developer that the payload generator cannot be used in the requested context. If the\n * generator <em>does</em> work in the requested context, this exception would never be thrown.\n */\npublic final class NotImplementedException extends RuntimeException {\n\n  @FormatMethod\n  public NotImplementedException(@FormatString String format, Object... args) {\n    super(String.format(format, args));\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/payload/Payload.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload;\n\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.protobuf.ByteString;\nimport com.google.tsunami.proto.PayloadAttributes;\nimport com.google.tsunami.proto.PayloadGeneratorConfig;\nimport java.util.Optional;\n\n/** Type returned by {@link PayloadGenerator} to be used in detectors. */\npublic class Payload {\n\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n  private final String payload;\n  private final Validator validator;\n  private final PayloadAttributes attributes;\n  private final PayloadGeneratorConfig config;\n\n  public Payload(\n      String payload,\n      Validator validator,\n      PayloadAttributes attributes,\n      PayloadGeneratorConfig config) {\n    this.payload = payload;\n    this.validator = validator;\n    this.attributes = attributes;\n    this.config = config;\n  }\n\n  /**\n   * Get the string representation of the payload.\n   *\n   * @return the actual payload string\n   */\n  public final String getPayload() {\n    logger.atInfo().log(\n        \"%s generated payload `%s`, %s use the callback server\",\n        this.config, this.payload, this.attributes.getUsesCallbackServer() ? \"does\" : \"does not\");\n    return this.payload;\n  }\n\n  /**\n   * Checks if the supplied payload was executed based on a given input e.g. a reflective RCE.\n   *\n   * @param input - a UTF-8 encoded string\n   * @return whether this payload is executed on the scan target.\n   */\n  public final boolean checkIfExecuted(String input) {\n    return this.validator.isExecuted(Optional.of(ByteString.copyFromUtf8(input)));\n  }\n\n  /**\n   * Checks if the supplied payload was executed based on a given input e.g. a reflective RCE.\n   *\n   * @param input - a sequence of bytes in the {@link ByteString} format.\n   * @return whether this payload is executed on the scan target.\n   */\n  public final boolean checkIfExecuted(ByteString input) {\n    return this.validator.isExecuted(Optional.of(input));\n  }\n\n  /**\n   * Checks if the supplied payload was executed based on a given input e.g. a reflective RCE.\n   *\n   * @param input - an optional sequence of bytes in the {@link ByteString} format.\n   * @return whether this payload is executed on the scan target.\n   */\n  public final boolean checkIfExecuted(Optional<ByteString> input) {\n    return this.validator.isExecuted(input);\n  }\n\n  /**\n   * Checks if the supplied payload was executed without supplying an input e.g. validation against\n   * the callback server does not require input.\n   *\n   * @return whether this payload is executed on the scan target.\n   */\n  public final boolean checkIfExecuted() {\n    return this.validator.isExecuted(Optional.empty());\n  }\n\n  /**\n   * Get additional attributes about this payload.\n   *\n   * @return the {@link PayloadAttributes} about this payload\n   */\n  public final PayloadAttributes getPayloadAttributes() {\n    return this.attributes;\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/payload/PayloadGenerator.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.protobuf.ByteString;\nimport com.google.tsunami.plugin.TcsClient;\nimport com.google.tsunami.proto.PayloadAttributes;\nimport com.google.tsunami.proto.PayloadDefinition;\nimport com.google.tsunami.proto.PayloadGeneratorConfig;\nimport java.lang.annotation.Retention;\nimport java.util.Optional;\nimport javax.inject.Inject;\nimport javax.inject.Qualifier;\n\n/** Holds the generate function to get a detection payload given config parameters */\npublic final class PayloadGenerator {\n  private static final int SECRET_LENGTH = 8;\n  private static final String TOKEN_CALLBACK_SERVER_URL = \"$TSUNAMI_PAYLOAD_TOKEN_URL\";\n  private static final String TOKEN_RANDOM_STRING = \"$TSUNAMI_PAYLOAD_TOKEN_RANDOM\";\n\n  private final TcsClient tcsClient;\n  private final PayloadSecretGenerator secretGenerator;\n  private final ImmutableList<PayloadDefinition> payloads;\n\n  @Inject\n  PayloadGenerator(\n      TcsClient tcsClient,\n      PayloadSecretGenerator secretGenerator,\n      @Payloads ImmutableList<PayloadDefinition> payloads) {\n    this.tcsClient = checkNotNull(tcsClient);\n    this.secretGenerator = checkNotNull(secretGenerator);\n    this.payloads = checkNotNull(payloads);\n  }\n\n  public boolean isCallbackServerEnabled() {\n    return tcsClient.isCallbackServerEnabled();\n  }\n\n  /**\n   * Returns a {@link Payload} for a given {@link PayloadGeneratorConfig}.\n   *\n   * <p>The framework prioritizes finding a callback server payload if callback server is enabled\n   * and falls back to any payload that matches.\n   *\n   * @param config configurations to the payload generator\n   * @return the generated {@link Payload} based on the given {@code config}\n   */\n  public Payload generate(PayloadGeneratorConfig config) {\n    return generatePayload(config, /* enforceNoCallback= */ false);\n  }\n\n  public Payload generateNoCallback(PayloadGeneratorConfig config) {\n    return generatePayload(config, /* enforceNoCallback= */ true);\n  }\n\n  private Payload generatePayload(PayloadGeneratorConfig config, boolean enforceNoCallback) {\n    PayloadDefinition selectedPayload = null;\n\n    if (tcsClient.isCallbackServerEnabled() && !enforceNoCallback) {\n      for (PayloadDefinition candidate : payloads) {\n        if (isMatchingPayload(candidate, config) && candidate.getUsesCallbackServer().getValue()) {\n          selectedPayload = candidate;\n          break;\n        }\n      }\n    }\n\n    if (selectedPayload == null) {\n      for (PayloadDefinition candidate : payloads) {\n        if (isMatchingPayload(candidate, config) && !candidate.getUsesCallbackServer().getValue()) {\n          selectedPayload = candidate;\n          break;\n        }\n      }\n    }\n\n    if (selectedPayload == null) {\n      throw new NotImplementedException(\n          \"No payload implemented for %s vulnerability type, %s interpretation environment, %s\"\n              + \" execution environment\",\n          config.getVulnerabilityType(),\n          config.getInterpretationEnvironment(),\n          config.getExecutionEnvironment());\n    }\n\n    return convertParsedPayload(selectedPayload, config);\n  }\n\n  private boolean isMatchingPayload(PayloadDefinition p, PayloadGeneratorConfig c) {\n    return p.getVulnerabilityTypeList().contains(c.getVulnerabilityType())\n        && p.getInterpretationEnvironment() == c.getInterpretationEnvironment()\n        && p.getExecutionEnvironment() == c.getExecutionEnvironment();\n  }\n\n  private Payload convertParsedPayload(PayloadDefinition p, PayloadGeneratorConfig c) {\n    String secret = secretGenerator.generate(SECRET_LENGTH);\n    if (p.getUsesCallbackServer().getValue()) {\n      return new Payload(\n          p.getPayloadString()\n              .getValue()\n              .replace(TOKEN_CALLBACK_SERVER_URL, tcsClient.getCallbackUri(secret)),\n          (Validator) (unused) -> tcsClient.hasOobLog(secret),\n          PayloadAttributes.newBuilder().setUsesCallbackServer(true).build(),\n          c);\n    } else {\n      String payloadString = p.getPayloadString().getValue().replace(TOKEN_RANDOM_STRING, secret);\n      Validator v;\n      switch (p.getValidationType()) {\n        case VALIDATION_REGEX:\n          String processedRegex =\n              p.getValidationRegex().getValue().replace(TOKEN_RANDOM_STRING, secret);\n          v =\n              (Validator)\n                  (Optional<ByteString> input) ->\n                      input.map(i -> i.toStringUtf8().matches(processedRegex)).orElse(false);\n          return new Payload(\n              payloadString,\n              v,\n              PayloadAttributes.newBuilder().setUsesCallbackServer(false).build(),\n              c);\n        default:\n          throw new NotImplementedException(\n              \"Validation type %s not implemented.\", p.getValidationType());\n      }\n    }\n  }\n\n  /** Guice interface for injecting parsed payloads from payload_definitions.yaml */\n  @Qualifier\n  @Retention(RUNTIME)\n  public @interface Payloads {}\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/payload/PayloadGeneratorModule.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.Resources;\nimport com.google.common.net.InetAddresses;\nimport com.google.common.net.InternetDomainName;\nimport com.google.gson.Gson;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Provides;\nimport com.google.protobuf.util.JsonFormat;\nimport com.google.tsunami.common.net.http.HttpClient;\nimport com.google.tsunami.plugin.TcsClient;\nimport com.google.tsunami.plugin.TcsClientCliOptions;\nimport com.google.tsunami.plugin.TcsConfigProperties;\nimport com.google.tsunami.proto.PayloadDefinition;\nimport com.google.tsunami.proto.PayloadGeneratorConfig;\nimport com.google.tsunami.proto.PayloadLibrary;\nimport com.google.tsunami.proto.PayloadValidationType;\nimport java.io.IOException;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.security.SecureRandom;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport javax.inject.Qualifier;\nimport javax.inject.Singleton;\nimport org.yaml.snakeyaml.LoaderOptions;\nimport org.yaml.snakeyaml.Yaml;\nimport org.yaml.snakeyaml.constructor.SafeConstructor;\n\n/** Guice module for installing {@link PayloadGenerator}. */\npublic final class PayloadGeneratorModule extends AbstractModule {\n  private final SecureRandom secureRng;\n\n  public PayloadGeneratorModule(SecureRandom secureRng) {\n    this.secureRng = secureRng;\n  }\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface CallbackAddress {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface CallbackPort {}\n\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})\n  @interface CallbackPollingUri {}\n\n  @Provides\n  @CallbackAddress\n  String providesCallbackAddress(TcsConfigProperties config, TcsClientCliOptions cliOptions) {\n\n    if (cliOptions.callbackAddress != null) {\n      return cliOptions.callbackAddress;\n    }\n    return config.callbackAddress;\n  }\n\n  @Provides\n  @CallbackPort\n  Integer providesCallbackPort(TcsConfigProperties config, TcsClientCliOptions cliOptions) {\n    if (cliOptions.callbackPort != null) {\n      return cliOptions.callbackPort;\n    }\n    return config.callbackPort;\n  }\n\n  @Provides\n  @CallbackPollingUri\n  String providesCallbackPollingUri(TcsConfigProperties config, TcsClientCliOptions cliOptions) {\n    if (cliOptions.pollingUri != null) {\n      return cliOptions.pollingUri;\n    }\n    return config.pollingUri;\n  }\n\n  @Provides\n  TcsClient providesTcsClient(\n      @Nullable @CallbackAddress String callbackAddress,\n      @Nullable @CallbackPort Integer callbackPort,\n      @Nullable @CallbackPollingUri String pollingUri,\n      HttpClient httpClient) {\n    // when all tcs config are not set, we provide an invalid {@link TcsClient}\n    // so that {@link TcsClient#isCallbackServerEnabled} returns false.\n    if (callbackAddress == null && callbackPort == null && pollingUri == null) {\n      return new TcsClient(\"\", 0, \"\", checkNotNull(httpClient));\n    }\n\n    checkNotNull(callbackAddress);\n    checkNotNull(callbackPort);\n    checkNotNull(pollingUri);\n    checkArgument(\n        InetAddresses.isInetAddress(callbackAddress) || InternetDomainName.isValid(callbackAddress),\n        \"Invalid callback address specified\");\n    checkArgument(callbackPort > 0 && callbackPort < 65536, \"Invalid port number specified\");\n\n    return new TcsClient(callbackAddress, callbackPort, pollingUri, checkNotNull(httpClient));\n  }\n\n  @Provides\n  PayloadSecretGenerator providesPayloadSecretGenerator() {\n    return new PayloadSecretGenerator(this.secureRng);\n  }\n\n  @Provides\n  @PayloadGenerator.Payloads\n  @Singleton\n  ImmutableList<PayloadDefinition> provideParsedPayloads() throws IOException {\n    // It is only safe to use SnakeYaml with SafeConstructor.\n    // Parse the YAML by converting it into JSON and then into the proto message\n    Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));\n    Map<String, Object> rawYamlData =\n        yaml.load(\n            Resources.toString(\n                Resources.getResource(this.getClass(), \"payload_definitions.yaml\"),\n                UTF_8));\n\n    Gson gson = new Gson();\n    String json = gson.toJson(rawYamlData);\n\n    PayloadLibrary.Builder builder = PayloadLibrary.newBuilder();\n    JsonFormat.parser().ignoringUnknownFields().merge(json, builder);\n    PayloadLibrary parsed = builder.build();\n\n    return validatePayloads(parsed.getPayloadsList());\n  }\n\n  /** Validates that the parsed payloads adhere to the defined schema. */\n  ImmutableList<PayloadDefinition> validatePayloads(List<PayloadDefinition> payloads) {\n    for (PayloadDefinition p : payloads) {\n      checkArgument(p.hasName(), \"Parsed payload does not have a name.\");\n      checkArgument(\n          p.getInterpretationEnvironment()\n              != PayloadGeneratorConfig.InterpretationEnvironment\n                  .INTERPRETATION_ENVIRONMENT_UNSPECIFIED,\n          \"Parsed payload does not have an interpretation_environment.\");\n      checkArgument(\n          p.getExecutionEnvironment()\n              != PayloadGeneratorConfig.ExecutionEnvironment.EXECUTION_ENVIRONMENT_UNSPECIFIED,\n          \"Parsed payload does not have an exeuction_environment.\");\n      checkArgument(\n          !p.getVulnerabilityTypeList().isEmpty(),\n          \"Parsed payload has no entries for vulnerability_type.\");\n      checkArgument(p.hasPayloadString(), \"Parsed payload does not have a payload_string.\");\n\n      if (p.getUsesCallbackServer().getValue()) {\n        checkArgument(\n            p.getPayloadString().getValue().contains(\"$TSUNAMI_PAYLOAD_TOKEN_URL\"),\n            \"Parsed payload uses callback server but $TSUNAMI_PAYLOAD_TOKEN_URL not found in\"\n                + \" payload_string.\");\n      } else {\n        checkArgument(\n            p.getValidationType() != PayloadValidationType.VALIDATION_TYPE_UNSPECIFIED,\n            \"Parsed payload has no validation_type and does not use the callback server.\");\n\n        if (p.getValidationType() == PayloadValidationType.VALIDATION_REGEX) {\n          checkArgument(\n              p.hasValidationRegex(),\n              \"Parsed payload has no validation_regex but uses PayloadValidationType.REGEX\");\n        }\n      }\n    }\n\n    return ImmutableList.copyOf(payloads);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/payload/PayloadSecretGenerator.java",
    "content": "/*\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload;\n\nimport com.google.common.io.BaseEncoding;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Provides;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.security.SecureRandom;\nimport javax.inject.Inject;\nimport javax.inject.Qualifier;\nimport javax.inject.Singleton;\n\n/** Generates secrets used in the Payload generation framework */\npublic final class PayloadSecretGenerator {\n\n  private final SecureRandom payloadSecretRng;\n\n  @Inject\n  PayloadSecretGenerator(@PayloadSecretRng SecureRandom payloadSecretRng) {\n    this.payloadSecretRng = payloadSecretRng;\n  }\n\n  public String generate(int secretLength) {\n    byte[] randomBytes = new byte[secretLength];\n    payloadSecretRng.nextBytes(randomBytes);\n    return BaseEncoding.base16().lowerCase().encode(randomBytes);\n  }\n\n  public static PayloadSecretGeneratorModule getModule() {\n    return new PayloadSecretGeneratorModule();\n  }\n\n  private static final class PayloadSecretGeneratorModule extends AbstractModule {\n    @Provides\n    @PayloadSecretRng\n    @Singleton\n    SecureRandom providesPayloadSecretRng() {\n      return new SecureRandom();\n    }\n  }\n\n  /**\n   * Interface for Guice binding annotation for the {@link PayloadSecretGenerator}'s SecureRandom\n   */\n  @Qualifier\n  @Retention(RetentionPolicy.RUNTIME)\n  public @interface PayloadSecretRng {}\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/payload/README.md",
    "content": "# Tsunami Payload Generation Framework\n\nThis is the code for Tsunami's payload generation framework, an optional library\nfor detectors which automatically selects the best payload for a given\nvulnerability, taking out the guesswork when writing a new detector, reducing\nfalse positives, and standardizing payloads across detectors. It is also the\ninterface for using the\n[Tsunami Callback Server](https://github.com/google/tsunami-security-scanner-callback-server).\n\nDetectors targeting remote code executions (RCE) and server-side request forgery\n(SSRF) vulnerabilities are ideal candidates for using the payload framework.\n\nFor an example of how to use the framework, see\n[the example plugin](https://github.com/google/tsunami-security-scanner-plugins/tree/master/examples/example_payload_framework_vuln_detector).\n\n## payload_definitions.yaml\n\n[payload_definitions.yaml](https://github.com/google/tsunami-security-scanner/blob/master/plugin/src/main/resources/com/google/tsunami/plugin/payload/payload_definitions.yaml)\ndefines the actual payloads used in the payload generation framework. See the\nschema definition in\n[payload_generator.proto](https://github.com/google/tsunami-security-scanner/blob/master/proto/payload_generator.proto).\nWhen adding a new payload definition, make sure to add\n[test cases](https://github.com/google/tsunami-security-scanner/blob/master/plugin/src/test/java/com/google/tsunami/plugin/payload/PayloadGeneratorTest.java).\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/payload/Validator.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload;\n\nimport com.google.protobuf.ByteString;\nimport java.util.Optional;\n\n/** Type used for functions which verify if a payload was executed */\n@FunctionalInterface\npublic interface Validator {\n  /**\n   * Checks whether the payload is executed.\n   *\n   * @param input - an optional sequence of bytes in the {@link ByteString} format.\n   * @return whether a payload is executed on the scan target.\n   */\n  boolean isExecuted(Optional<ByteString> input);\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/payload/testing/FakePayloadGeneratorModule.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.tsunami.plugin.payload.testing;\n\nimport com.google.auto.value.AutoBuilder;\nimport com.google.inject.AbstractModule;\nimport com.google.tsunami.plugin.TcsConfigProperties;\nimport com.google.tsunami.plugin.payload.PayloadGenerator;\nimport com.google.tsunami.plugin.payload.PayloadGeneratorModule;\nimport java.security.SecureRandom;\nimport java.util.Optional;\nimport okhttp3.mockwebserver.MockWebServer;\nimport org.checkerframework.checker.nullness.qual.Nullable;\n\n/**\n * Guice module for interacting with {@link PayloadGenerator} in tests. Use {@link\n * FakePayloadGeneratorModule.Builder} instead of this directly.\n */\npublic final class FakePayloadGeneratorModule extends AbstractModule {\n  private final TcsConfigProperties tcsConfig = new TcsConfigProperties();\n  private final SecureRandom secureRng;\n\n  /**\n   * @param callbackServer - if supplied, enables the payload generator to use the callback server.\n   *     If this behavior is unwanted, leave this empty.\n   * @param secureRng - if you do not need control over the output of {@link SecureRandom#nextBytes}\n   *     in tests, leave this empty.\n   */\n  FakePayloadGeneratorModule(\n      Optional<MockWebServer> callbackServer, Optional<SecureRandom> secureRng) {\n\n    this.tcsConfig.callbackAddress = callbackServer.map(c -> c.getHostName()).orElse(null);\n    this.tcsConfig.callbackPort = callbackServer.map(c -> c.getPort()).orElse(null);\n    this.tcsConfig.pollingUri = callbackServer.map(c -> c.url(\"/\").toString()).orElse(null);\n    this.secureRng = secureRng.orElse(new SecureRandom());\n  }\n\n  @Override\n  protected void configure() {\n    install(new PayloadGeneratorModule(secureRng));\n    bind(TcsConfigProperties.class).toInstance(tcsConfig);\n  }\n\n  /**\n   * Creates a builder for the {@link FakePayloadGeneratorModule}.\n   *\n   * @return a builder for configuring the module\n   */\n  public static Builder builder() {\n    return Builder.builder();\n  }\n\n  static FakePayloadGeneratorModule build(\n      @Nullable MockWebServer callbackServer, @Nullable SecureRandom secureRng) {\n    return new FakePayloadGeneratorModule(\n        Optional.ofNullable(callbackServer), Optional.ofNullable(secureRng));\n  }\n\n  /** Configures {@link FakePayloadGeneratorModule}. */\n  @AutoBuilder(callMethod = \"build\")\n  public abstract static class Builder {\n    public static Builder builder() {\n      return new AutoBuilder_FakePayloadGeneratorModule_Builder();\n    }\n\n    public abstract Builder setCallbackServer(MockWebServer callbackServer);\n\n    public abstract Builder setSecureRng(SecureRandom secureRng);\n\n    public abstract FakePayloadGeneratorModule build();\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/payload/testing/PayloadTestHelper.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload.testing;\n\nimport com.google.protobuf.util.JsonFormat;\nimport com.google.tsunami.callbackserver.proto.PollingResult;\nimport com.google.tsunami.common.net.http.HttpStatus;\nimport java.io.IOException;\nimport okhttp3.mockwebserver.MockResponse;\n\n/** Exposes some helpers when writing tests against code that use the paylaod generator. */\npublic final class PayloadTestHelper {\n\n  private PayloadTestHelper() {}\n\n  public static MockResponse generateMockSuccessfulCallbackResponse() throws IOException {\n    PollingResult log = PollingResult.newBuilder().setHasHttpInteraction(true).build();\n    String body = JsonFormat.printer().preservingProtoFieldNames().print(log);\n    return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(body);\n  }\n\n  public static MockResponse generateMockUnsuccessfulCallbackResponse() {\n    return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code());\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FailedPortScanner.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.PortScanner;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.PortScanningReport;\nimport com.google.tsunami.proto.ScanTarget;\n\n/** A fake PortScanner plugin that instantly fails for testing purpose only. */\n@PluginInfo(\n    type = PluginType.PORT_SCAN,\n    name = \"FailedPortScanner\",\n    version = \"v0.1\",\n    description = \"A fake PortScanner that instantly fails.\",\n    author = \"fake\",\n    bootstrapModule = FailedPortScannerBootstrapModule.class)\npublic final class FailedPortScanner implements PortScanner {\n\n  @Override\n  public PortScanningReport scan(ScanTarget scanTarget) {\n    throw new RuntimeException(\"PortScanner failed\");\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FailedPortScannerBootstrapModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FailedPortScanner}. */\npublic final class FailedPortScannerBootstrapModule extends PluginBootstrapModule {\n\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FailedPortScanner.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FailedRemoteVulnDetector.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Sets;\nimport com.google.tsunami.plugin.LanguageServerException;\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.RemoteVulnDetector;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.MatchedPlugin;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.PluginDefinition;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.Vulnerability;\nimport java.util.Set;\n\n/** Fake {@link RemoteVulnDetector} implementation that fails to run. */\n@PluginInfo(\n    type = PluginType.REMOTE_VULN_DETECTION,\n    name = \"FailedRemoteVulnDetector\",\n    version = \"v0.1\",\n    description = \"fake description\",\n    author = \"fake\",\n    bootstrapModule = FailedRemoteVulnDetectorBootstrapModule.class)\npublic final class FailedRemoteVulnDetector implements RemoteVulnDetector {\n\n  private final Set<MatchedPlugin> matchedPluginsToRun;\n\n  public FailedRemoteVulnDetector() {\n    this.matchedPluginsToRun = Sets.newHashSet();\n  }\n\n  @Override\n  public ImmutableList<Vulnerability> getAdvisories() {\n    return ImmutableList.of();\n  }\n\n  @Override\n  public DetectionReportList detect(TargetInfo target, ImmutableList<NetworkService> services) {\n    throw new LanguageServerException(\"RemoteVulnDetector failed.\");\n  }\n\n  @Override\n  public ImmutableList<PluginDefinition> getAllPlugins() {\n    return ImmutableList.of(PluginDefinition.getDefaultInstance());\n  }\n\n  @Override\n  public void addMatchedPluginToDetect(MatchedPlugin plugin) {\n    this.matchedPluginsToRun.add(plugin);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FailedRemoteVulnDetectorBootstrapModule.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FailedRemoteVulnDetector}. */\npublic final class FailedRemoteVulnDetectorBootstrapModule extends PluginBootstrapModule {\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FailedRemoteVulnDetector.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FailedServiceFingerprinter.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.ServiceFingerprinter;\nimport com.google.tsunami.plugin.annotations.ForServiceName;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.FingerprintingReport;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.TargetInfo;\n\n/** A fake ServiceFingerprinter plugin that instantly fails for testing purpose only. */\n@PluginInfo(\n    type = PluginType.SERVICE_FINGERPRINT,\n    name = \"FailedServiceFingerprinter\",\n    version = \"v0.1\",\n    description = \"A fake ServiceFingerprinter that instantly fails.\",\n    author = \"fake\",\n    bootstrapModule = FailedServiceFingerprinterBootstrapModule.class)\n@ForServiceName(\"http\")\npublic final class FailedServiceFingerprinter implements ServiceFingerprinter {\n\n  @Override\n  public FingerprintingReport fingerprint(TargetInfo targetInfo, NetworkService networkService) {\n    throw new RuntimeException(\"ServiceFingerprinter failed\");\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FailedServiceFingerprinterBootstrapModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FailedServiceFingerprinter}. */\npublic final class FailedServiceFingerprinterBootstrapModule extends PluginBootstrapModule {\n\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FailedServiceFingerprinter.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FailedVulnDetector.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.VulnDetector;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.Vulnerability;\n\n/** A fake VulnDetector plugin that instantly fails for testing purpose only. */\n@PluginInfo(\n    type = PluginType.VULN_DETECTION,\n    name = \"FailedVulnDetector\",\n    version = \"v0.1\",\n    description = \"A fake VulnDetector that instantly fails.\",\n    author = \"fake\",\n    bootstrapModule = FailedVulnDetectorBootstrapModule.class)\npublic class FailedVulnDetector implements VulnDetector {\n\n  @Override\n  public ImmutableList<Vulnerability> getAdvisories() {\n    return ImmutableList.of();\n  }\n\n  @Override\n  public DetectionReportList detect(\n      TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n    throw new RuntimeException(\"VulnDetector failed.\");\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FailedVulnDetectorBootstrapModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FailedVulnDetector}. */\npublic final class FailedVulnDetectorBootstrapModule extends PluginBootstrapModule {\n\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FailedVulnDetector.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakePluginExecutionModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.common.util.concurrent.ListeningScheduledExecutorService;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport com.google.inject.AbstractModule;\nimport com.google.tsunami.plugin.PluginExecutionThreadPool;\nimport com.google.tsunami.plugin.PluginExecutorModule;\nimport java.util.concurrent.Executors;\n\n/** Installs dependencies used for plugin executions in unit tests. */\npublic final class FakePluginExecutionModule extends AbstractModule {\n\n  @Override\n  protected void configure() {\n    install(new PluginExecutorModule());\n    bind(ListeningScheduledExecutorService.class)\n        .annotatedWith(PluginExecutionThreadPool.class)\n        .toInstance(MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(1)));\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakePortScanner.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.common.data.NetworkEndpointUtils;\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.PortScanner;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.NetworkEndpoint;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.PortScanningReport;\nimport com.google.tsunami.proto.ScanTarget;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.TransportProtocol;\n\n/** A fake PortScanner plugin for testing purpose only. */\n@PluginInfo(\n    type = PluginType.PORT_SCAN,\n    name = \"FakePortScanner\",\n    version = \"v0.1\",\n    description = \"A fake PortScanner.\",\n    author = \"fake\",\n    bootstrapModule = FakePortScannerBootstrapModule.class)\npublic class FakePortScanner implements PortScanner {\n\n  public static NetworkService getFakeNetworkService(NetworkEndpoint networkEndpoint) {\n    return NetworkService.newBuilder()\n        .setNetworkEndpoint(NetworkEndpointUtils.forNetworkEndpointAndPort(networkEndpoint, 80))\n        .setTransportProtocol(TransportProtocol.TCP)\n        .setServiceName(\"http\")\n        .build();\n  }\n\n  @Override\n  public PortScanningReport scan(ScanTarget scanTarget) {\n    return PortScanningReport.newBuilder()\n        .setTargetInfo(TargetInfo.newBuilder().addNetworkEndpoints(scanTarget.getNetworkEndpoint()))\n        .addNetworkServices(getFakeNetworkService(scanTarget.getNetworkEndpoint()))\n        .build();\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakePortScanner2.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.common.data.NetworkEndpointUtils;\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.PortScanner;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.NetworkEndpoint;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.PortScanningReport;\nimport com.google.tsunami.proto.ScanTarget;\nimport com.google.tsunami.proto.Software;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.TransportProtocol;\n\n/** A fake PortScanner plugin for testing purpose only. */\n@PluginInfo(\n    type = PluginType.PORT_SCAN,\n    name = \"FakePortScanner2\",\n    version = \"v0.1\",\n    description = \"Another fake PortScanner.\",\n    author = \"fake\",\n    bootstrapModule = FakePortScannerBootstrapModule2.class)\npublic class FakePortScanner2 implements PortScanner {\n\n  public static NetworkService getFakeNetworkService(NetworkEndpoint networkEndpoint) {\n    return NetworkService.newBuilder()\n        .setNetworkEndpoint(NetworkEndpointUtils.forNetworkEndpointAndPort(networkEndpoint, 22))\n        .setTransportProtocol(TransportProtocol.TCP)\n        .setServiceName(\"ssh\")\n        .setSoftware(Software.newBuilder().setName(\"FakeSoftware2\"))\n        .build();\n  }\n\n  @Override\n  public PortScanningReport scan(ScanTarget scanTarget) {\n    return PortScanningReport.newBuilder()\n        .setTargetInfo(TargetInfo.newBuilder().addNetworkEndpoints(scanTarget.getNetworkEndpoint()))\n        .addNetworkServices(getFakeNetworkService(scanTarget.getNetworkEndpoint()))\n        .build();\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakePortScannerBootstrapModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FakePortScanner}. */\npublic class FakePortScannerBootstrapModule extends PluginBootstrapModule {\n\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FakePortScanner.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakePortScannerBootstrapModule2.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FakePortScanner2}. */\npublic class FakePortScannerBootstrapModule2 extends PluginBootstrapModule {\n\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FakePortScanner2.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakePortScannerBootstrapModuleEmpty.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FakePortScannerEmpty}. */\npublic class FakePortScannerBootstrapModuleEmpty extends PluginBootstrapModule {\n\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FakePortScannerEmpty.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakePortScannerEmpty.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.PortScanner;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.PortScanningReport;\nimport com.google.tsunami.proto.ScanTarget;\nimport com.google.tsunami.proto.TargetInfo;\n\n/** A fake PortScanner plugin for testing purpose only. */\n@PluginInfo(\n    type = PluginType.PORT_SCAN,\n    name = \"FakePortScannerEmpty\",\n    version = \"v0.1\",\n    description = \"Another fake PortScanner.\",\n    author = \"fake\",\n    bootstrapModule = FakePortScannerBootstrapModuleEmpty.class)\npublic class FakePortScannerEmpty implements PortScanner {\n\n  @Override\n  public PortScanningReport scan(ScanTarget scanTarget) {\n    return PortScanningReport.newBuilder()\n        .setTargetInfo(TargetInfo.newBuilder().addNetworkEndpoints(scanTarget.getNetworkEndpoint()))\n        .build();\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakeRemoteVulnDetector.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport static com.google.common.collect.ImmutableList.toImmutableList;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Sets;\nimport com.google.protobuf.util.Timestamps;\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.RemoteVulnDetector;\nimport com.google.tsunami.proto.DetectionReport;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.DetectionStatus;\nimport com.google.tsunami.proto.MatchedPlugin;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.PluginDefinition;\nimport com.google.tsunami.proto.PluginInfo;\nimport com.google.tsunami.proto.Severity;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.Vulnerability;\nimport com.google.tsunami.proto.VulnerabilityId;\nimport java.util.Set;\n\n/**\n * Fake {@link RemoteVulnDetector} implementation that only contains one {@link PluginDefinition}\n * proto available to run.\n */\n@com.google.tsunami.plugin.annotations.PluginInfo(\n    type = PluginType.REMOTE_VULN_DETECTION,\n    name = \"FakeRemoteVulnDetector\",\n    version = \"v0.1\",\n    description = \"fake description\",\n    author = \"fake\",\n    bootstrapModule = FakeRemoteVulnDetectorBootstrapModule.class)\npublic final class FakeRemoteVulnDetector implements RemoteVulnDetector {\n\n  private final Set<MatchedPlugin> matchedPluginsToRun;\n\n  // Used when multiple instances of this {@link RemoteVulnDetector} are created.\n  private final int fakePluginId;\n\n  public FakeRemoteVulnDetector() {\n    this(0);\n  }\n\n  public FakeRemoteVulnDetector(int fakePluginId) {\n    this.fakePluginId = fakePluginId;\n    this.matchedPluginsToRun = Sets.newHashSet();\n  }\n\n  @Override\n  public ImmutableList<Vulnerability> getAdvisories() {\n    return getAdvisoriesStatic();\n  }\n\n  @Override\n  public DetectionReportList detect(TargetInfo target, ImmutableList<NetworkService> services) {\n    ImmutableList<ImmutableList<DetectionReport>> detectionReports =\n        matchedPluginsToRun.stream()\n            .map(\n                plugin ->\n                    plugin.getServicesList().stream()\n                        .map(service -> getFakeDetectionReport(target, service))\n                        .collect(toImmutableList()))\n            .collect(toImmutableList());\n    var reportListBuilder = DetectionReportList.newBuilder();\n    detectionReports.forEach(reportListBuilder::addAllDetectionReports);\n    return reportListBuilder.build();\n  }\n\n  public static DetectionReport getFakeDetectionReport(\n      TargetInfo targetInfo, NetworkService networkService) {\n    return DetectionReport.newBuilder()\n        .setTargetInfo(targetInfo)\n        .setNetworkService(networkService)\n        .setDetectionTimestamp(Timestamps.fromMillis(1234567890L))\n        .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED)\n        .setVulnerability(getAdvisoriesStatic().get(0))\n        .build();\n  }\n\n  @Override\n  public ImmutableList<PluginDefinition> getAllPlugins() {\n    return ImmutableList.of(\n        PluginDefinition.newBuilder()\n            .setInfo(\n                PluginInfo.newBuilder()\n                    .setType(PluginInfo.PluginType.VULN_DETECTION)\n                    .setName(\"FakeRemoteVuln\" + fakePluginId)\n                    .setVersion(\"v0.1\")\n                    .setDescription(\"FakeRemoteDescription\" + fakePluginId)\n                    .setAuthor(\"fake\"))\n            .build());\n  }\n\n  @Override\n  public void addMatchedPluginToDetect(MatchedPlugin plugin) {\n    this.matchedPluginsToRun.add(plugin);\n  }\n\n  private static ImmutableList<Vulnerability> getAdvisoriesStatic() {\n    return ImmutableList.of(\n        Vulnerability.newBuilder()\n            .setMainId(\n                VulnerabilityId.newBuilder().setPublisher(\"GOOGLE\").setValue(\"FakeRemoteVuln\"))\n            .setSeverity(Severity.CRITICAL)\n            .setTitle(\"FakeTitle\")\n            .setDescription(\"FakeRemoteDescription\")\n            .build());\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakeRemoteVulnDetectorBootstrapModule.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/**\n * Bootstrapping module for {@link FakeRemoteVulnDetector}. Use this if there is need for only one\n * fake RemoteVulnDetector, otherwise prefer making a fake RemoteVulnDetectorLoadingModule to load\n * multiple instances.\n */\npublic final class FakeRemoteVulnDetectorBootstrapModule extends PluginBootstrapModule {\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FakeRemoteVulnDetector.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakeServiceFingerprinter.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.ServiceFingerprinter;\nimport com.google.tsunami.plugin.annotations.ForServiceName;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.FingerprintingReport;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.ServiceContext;\nimport com.google.tsunami.proto.Software;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.WebServiceContext;\n\n/** A fake ServiceFingerprinter plugin for testing purpose only. */\n@PluginInfo(\n    type = PluginType.SERVICE_FINGERPRINT,\n    name = \"FakeServiceFingerprinter\",\n    version = \"v0.1\",\n    description = \"A fake ServiceFingerprinter.\",\n    author = \"fake\",\n    bootstrapModule = FakeServiceFingerprinterBootstrapModule.class)\n@ForServiceName(\"http\")\npublic class FakeServiceFingerprinter implements ServiceFingerprinter {\n  public static final Software IDENTIFIED_SOFTWARE =\n      Software.newBuilder().setName(\"Jenkins\").build();\n\n  public static NetworkService addWebServiceContext(NetworkService networkService) {\n    return networkService.toBuilder()\n        .setServiceContext(\n            ServiceContext.newBuilder()\n                .setWebServiceContext(\n                    WebServiceContext.newBuilder().setSoftware(IDENTIFIED_SOFTWARE)))\n        .build();\n  }\n\n  @Override\n  public FingerprintingReport fingerprint(TargetInfo targetInfo, NetworkService networkService) {\n    return FingerprintingReport.newBuilder()\n        .addNetworkServices(addWebServiceContext(networkService))\n        .build();\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakeServiceFingerprinterBootstrapModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FakeServiceFingerprinter}. */\npublic class FakeServiceFingerprinterBootstrapModule extends PluginBootstrapModule {\n\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FakeServiceFingerprinter.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakeVulnDetector.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport static com.google.common.collect.ImmutableList.toImmutableList;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.protobuf.util.Timestamps;\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.VulnDetector;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.DetectionReport;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.DetectionStatus;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.Severity;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.Vulnerability;\nimport com.google.tsunami.proto.VulnerabilityId;\n\n/** A fake VulnDetector plugin for testing purpose only. */\n@PluginInfo(\n    type = PluginType.VULN_DETECTION,\n    name = \"FakeVulnDetector\",\n    version = \"v0.1\",\n    description = \"A fake VulnDetector.\",\n    author = \"fake\",\n    bootstrapModule = FakeVulnDetectorBootstrapModule.class)\npublic class FakeVulnDetector implements VulnDetector {\n  public static DetectionReport getFakeDetectionReport(\n      TargetInfo targetInfo, NetworkService networkService) {\n    return DetectionReport.newBuilder()\n        .setTargetInfo(targetInfo)\n        .setNetworkService(networkService)\n        .setDetectionTimestamp(Timestamps.fromMillis(1234567890L))\n        .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED)\n        .setVulnerability(getAdvisoriesStatic().get(0))\n        .build();\n  }\n\n  @Override\n  public ImmutableList<Vulnerability> getAdvisories() {\n    return getAdvisoriesStatic();\n  }\n\n  @Override\n  public DetectionReportList detect(\n      TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n    return DetectionReportList.newBuilder()\n        .addAllDetectionReports(\n            matchedServices.stream()\n                .map(networkService -> getFakeDetectionReport(targetInfo, networkService))\n                .collect(toImmutableList()))\n        .build();\n  }\n\n  private static ImmutableList<Vulnerability> getAdvisoriesStatic() {\n    return ImmutableList.of(\n        Vulnerability.newBuilder()\n            .setMainId(VulnerabilityId.newBuilder().setPublisher(\"GOOGLE\").setValue(\"FakeVuln1\"))\n            .setSeverity(Severity.CRITICAL)\n            .setTitle(\"FakeTitle1\")\n            .setDescription(\"FakeDescription1\")\n            .build());\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakeVulnDetector2.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport static com.google.common.collect.ImmutableList.toImmutableList;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.protobuf.util.Timestamps;\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.VulnDetector;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.DetectionReport;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.DetectionStatus;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.Severity;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.Vulnerability;\nimport com.google.tsunami.proto.VulnerabilityId;\n\n/** A fake VulnDetector plugin for testing purpose only. */\n@PluginInfo(\n    type = PluginType.VULN_DETECTION,\n    name = \"FakeVulnDetector2\",\n    version = \"v0.1\",\n    description = \"Another fake VulnDetector.\",\n    author = \"fake\",\n    bootstrapModule = FakeVulnDetectorBootstrapModule2.class)\npublic class FakeVulnDetector2 implements VulnDetector {\n  public static DetectionReport getFakeDetectionReport(\n      TargetInfo targetInfo, NetworkService networkService) {\n    return DetectionReport.newBuilder()\n        .setTargetInfo(targetInfo)\n        .setNetworkService(networkService)\n        .setDetectionTimestamp(Timestamps.fromMillis(9876543210L))\n        .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED)\n        .setVulnerability(getAdvisoriesStatic().get(0))\n        .build();\n  }\n\n  @Override\n  public ImmutableList<Vulnerability> getAdvisories() {\n    return getAdvisoriesStatic();\n  }\n\n  @Override\n  public DetectionReportList detect(\n      TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n    return DetectionReportList.newBuilder()\n        .addAllDetectionReports(\n            matchedServices.stream()\n                .map(networkService -> getFakeDetectionReport(targetInfo, networkService))\n                .collect(toImmutableList()))\n        .build();\n  }\n\n  private static ImmutableList<Vulnerability> getAdvisoriesStatic() {\n    return ImmutableList.of(\n        Vulnerability.newBuilder()\n            .setMainId(VulnerabilityId.newBuilder().setPublisher(\"GOOGLE\").setValue(\"FakeVuln2\"))\n            .setSeverity(Severity.MEDIUM)\n            .setTitle(\"FakeTitle2\")\n            .setDescription(\"FakeDescription2\")\n            .build());\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakeVulnDetectorBootstrapModule.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FakeVulnDetector}. */\npublic class FakeVulnDetectorBootstrapModule extends PluginBootstrapModule {\n\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FakeVulnDetector.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakeVulnDetectorBootstrapModule2.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FakeVulnDetector2}. */\npublic class FakeVulnDetectorBootstrapModule2 extends PluginBootstrapModule {\n\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FakeVulnDetector2.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakeVulnDetectorBootstrapModuleEmpty.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.tsunami.plugin.PluginBootstrapModule;\n\n/** Bootstrapping module for {@link FakeVulnDetectorEmpty}. */\npublic class FakeVulnDetectorBootstrapModuleEmpty extends PluginBootstrapModule {\n\n  @Override\n  protected void configurePlugin() {\n    registerPlugin(FakeVulnDetectorEmpty.class);\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/google/tsunami/plugin/testing/FakeVulnDetectorEmpty.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.testing;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.tsunami.plugin.PluginType;\nimport com.google.tsunami.plugin.VulnDetector;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.Vulnerability;\n\n/** A fake VulnDetector plugin for testing purpose only. */\n@PluginInfo(\n    type = PluginType.VULN_DETECTION,\n    name = \"FakeVulnDetectorEmpty\",\n    version = \"v0.1\",\n    description = \"Another fake VulnDetector.\",\n    author = \"fake\",\n    bootstrapModule = FakeVulnDetectorBootstrapModuleEmpty.class)\npublic class FakeVulnDetectorEmpty implements VulnDetector {\n\n  @Override\n  public DetectionReportList detect(\n      TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n    return DetectionReportList.newBuilder().build();\n  }\n\n  @Override\n  public ImmutableList<Vulnerability> getAdvisories() {\n    return ImmutableList.of();\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/resources/com/google/tsunami/plugin/payload/payload_definitions.yaml",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# See payload_generator.proto for how this file is defined\n\npayloads:\n  - name: general_ssrf_callback\n    interpretation_environment: INTERPRETATION_ANY\n    execution_environment: EXEC_ANY\n    uses_callback_server: true\n    payload_string: $TSUNAMI_PAYLOAD_TOKEN_URL\n    vulnerability_type:\n      - SSRF\n  - name: general_ssrf_no_callback\n    # Detectors could break if this page changes or goes down.\n    interpretation_environment: INTERPRETATION_ANY\n    execution_environment: EXEC_ANY\n    uses_callback_server: false\n    payload_string: http://public-firing-range.appspot.com/\n    validation_type: VALIDATION_REGEX\n    validation_regex: (?s).*What is the Firing Range?.*\n    vulnerability_type:\n      - SSRF\n  - name: linux_callback\n    interpretation_environment: LINUX_SHELL\n    execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n    uses_callback_server: true\n    payload_string: curl $TSUNAMI_PAYLOAD_TOKEN_URL\n    vulnerability_type:\n      - REFLECTIVE_RCE\n      - BLIND_RCE\n  - name: linux_root_crontab\n    # Write the crontab payload to /etc/cron.d/tsunami_rce_cron\n    interpretation_environment: LINUX_ROOT_CRONTAB\n    execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n    uses_callback_server: true\n    payload_string: \"* * * * * root curl $TSUNAMI_PAYLOAD_TOKEN_URL \\n\"\n    vulnerability_type:\n      - ARBITRARY_FILE_WRITE\n  - name: linux_curl_trace_read\n    interpretation_environment: LINUX_SHELL\n    execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n    uses_callback_server: false\n    payload_string: curl --trace /tmp/tsunami-rce -- tsunami-rce-$TSUNAMI_PAYLOAD_TOKEN_RANDOM\n    validation_type: VALIDATION_REGEX\n    validation_regex: (?s).*tsunami-rce-$TSUNAMI_PAYLOAD_TOKEN_RANDOM.*\n    vulnerability_type:\n      - BLIND_RCE_FILE_READ\n  - name: windows_callback\n    interpretation_environment: WINDOWS_SHELL\n    execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n    uses_callback_server: true\n    payload_string: powershell -Command \"Invoke-WebRequest -URI $TSUNAMI_PAYLOAD_TOKEN_URL\"\n    vulnerability_type:\n      - REFLECTIVE_RCE\n      - BLIND_RCE\n  - name: windows_echo\n    interpretation_environment: WINDOWS_SHELL\n    execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n    uses_callback_server: false\n    payload_string: powershell -Command \"echo TSUNAMI_PAYLOAD_START$(echo $TSUNAMI_PAYLOAD_TOKEN_RANDOM)TSUNAMI_PAYLOAD_END\"\n    validation_type: VALIDATION_REGEX\n    validation_regex: (?s).*TSUNAMI_PAYLOAD_START$TSUNAMI_PAYLOAD_TOKEN_RANDOMTSUNAMI_PAYLOAD_END.*\n    vulnerability_type:\n      - REFLECTIVE_RCE\n  - name: linux_printf\n    interpretation_environment: LINUX_SHELL\n    execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n    uses_callback_server: false\n    payload_string: printf %s%s%s TSUNAMI_PAYLOAD_START $TSUNAMI_PAYLOAD_TOKEN_RANDOM TSUNAMI_PAYLOAD_END\n    validation_type: VALIDATION_REGEX\n    validation_regex: (?s).*TSUNAMI_PAYLOAD_START$TSUNAMI_PAYLOAD_TOKEN_RANDOMTSUNAMI_PAYLOAD_END.*\n    vulnerability_type:\n      - REFLECTIVE_RCE\n  - name: java_string_format\n    interpretationEnvironment: JAVA\n    executionEnvironment: EXEC_INTERPRETATION_ENVIRONMENT\n    usesCallbackServer: false\n    payloadString: String.format(\"%s%s%s\", \"TSUNAMI_PAYLOAD_START\", \"$TSUNAMI_PAYLOAD_TOKEN_RANDOM\", \"TSUNAMI_PAYLOAD_END\")\n    validation_type: VALIDATION_REGEX\n    validation_regex: (?s).*TSUNAMI_PAYLOAD_START$TSUNAMI_PAYLOAD_TOKEN_RANDOMTSUNAMI_PAYLOAD_END.*\n    vulnerabilityType:\n      - REFLECTIVE_RCE\n  - name: jsp_print\n    interpretation_environment: JSP\n    execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n    uses_callback_server: false\n    payload_string: <% out.print(String.format(\"%s%s%s\",\"TSUNAMI_PAYLOAD_START\", \"$TSUNAMI_PAYLOAD_TOKEN_RANDOM\", \"TSUNAMI_PAYLOAD_END\")); %>\n    validation_type: VALIDATION_REGEX\n    validation_regex: (?s).*TSUNAMI_PAYLOAD_START$TSUNAMI_PAYLOAD_TOKEN_RANDOMTSUNAMI_PAYLOAD_END.*\n    vulnerability_type:\n      - REFLECTIVE_RCE\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/PluginDefinitionTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.plugin.testing.FakeRemoteVulnDetector;\nimport com.google.tsunami.plugin.testing.FakeVulnDetector;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link PluginDefinition}. */\n@RunWith(JUnit4.class)\npublic class PluginDefinitionTest {\n\n  @Test\n  public void id_always_generatesCorrectPluginId() {\n    PluginDefinition pluginDefinition = PluginDefinition.forPlugin(FakeVulnDetector.class);\n\n    assertThat(pluginDefinition.id()).isEqualTo(\"/fake/VULN_DETECTION/FakeVulnDetector/v0.1\");\n  }\n\n  @Test\n  public void forPlugin_whenPluginHasNoAnnotation_throwsException() {\n    assertThrows(\n        IllegalStateException.class, () -> PluginDefinition.forPlugin(NoAnnotationPlugin.class));\n  }\n\n  @Test\n  public void forRemotePlugin_always_generatesCorrectDefinition() {\n    PluginInfo pluginInfo = FakeRemoteVulnDetector.class.getAnnotation(PluginInfo.class);\n    PluginDefinition pluginDefinition = PluginDefinition.forRemotePlugin(pluginInfo);\n\n    assertThat(pluginDefinition.type()).isEqualTo(pluginInfo.type());\n    assertThat(pluginDefinition.name()).isEqualTo(pluginInfo.name());\n    assertThat(pluginDefinition.author()).isEqualTo(pluginInfo.author());\n    assertThat(pluginDefinition.version()).isEqualTo(pluginInfo.version());\n    assertThat(pluginDefinition.id())\n        .isEqualTo(\"/fake/REMOTE_VULN_DETECTION/FakeRemoteVulnDetector/v0.1\");\n  }\n\n  @Test\n  public void forRemotePlugin_whenPassedNull_throwsException() {\n    assertThrows(NullPointerException.class, () -> PluginDefinition.forRemotePlugin(null));\n  }\n\n  private static final class NoAnnotationPlugin implements TsunamiPlugin {}\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/PluginExecutorImplTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.common.base.Stopwatch;\nimport com.google.common.testing.FakeTicker;\nimport com.google.common.util.concurrent.ListeningScheduledExecutorService;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport com.google.tsunami.plugin.PluginExecutor.PluginExecutorConfig;\nimport com.google.tsunami.plugin.PluginManager.PluginMatchingResult;\nimport com.google.tsunami.plugin.testing.FakePortScanner;\nimport java.time.Duration;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Executors;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link PluginExecutorImpl}. */\n@RunWith(JUnit4.class)\npublic final class PluginExecutorImplTest {\n  private static final PluginMatchingResult<FakePortScanner> FAKE_MATCHING_RESULT =\n      PluginMatchingResult.<FakePortScanner>builder()\n          .setTsunamiPlugin(new FakePortScanner())\n          .setPluginDefinition(PluginDefinition.forPlugin(FakePortScanner.class))\n          .build();\n  private static final Duration TICK_DURATION = Duration.ofSeconds(1);\n  private static final ListeningScheduledExecutorService PLUGIN_EXECUTION_THREAD_POOL =\n      MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(1));\n\n  private final FakeTicker ticker = new FakeTicker().setAutoIncrementStep(TICK_DURATION);\n  private final Stopwatch executionStopWatch = Stopwatch.createUnstarted(ticker);\n\n  @Test\n  public void executeAsync_whenSucceeded_returnsSucceededResult()\n      throws ExecutionException, InterruptedException {\n    PluginExecutorConfig<String> executorConfig =\n        PluginExecutorConfig.<String>builder()\n            .setMatchedPlugin(FAKE_MATCHING_RESULT)\n            .setPluginExecutionLogic(() -> \"result data\")\n            .build();\n\n    PluginExecutionResult<String> executionResult =\n        new PluginExecutorImpl(PLUGIN_EXECUTION_THREAD_POOL, executionStopWatch)\n            .executeAsync(executorConfig)\n            .get();\n\n    assertThat(executionResult.exception()).isEmpty();\n    assertThat(executionResult.isSucceeded()).isTrue();\n    assertThat(executionResult.executionStopwatch().elapsed()).isEqualTo(TICK_DURATION);\n    assertThat(executionResult.resultData()).hasValue(\"result data\");\n  }\n\n  @Test\n  public void executeAsync_whenFailedWithPluginExecutionException_returnsFailedResult()\n      throws ExecutionException, InterruptedException {\n    PluginExecutorConfig<String> executorConfig =\n        PluginExecutorConfig.<String>builder()\n            .setMatchedPlugin(FAKE_MATCHING_RESULT)\n            .setPluginExecutionLogic(\n                () -> {\n                  throw new PluginExecutionException(\"test exception\");\n                })\n            .build();\n\n    PluginExecutionResult<String> executionResult =\n        new PluginExecutorImpl(PLUGIN_EXECUTION_THREAD_POOL, executionStopWatch)\n            .executeAsync(executorConfig)\n            .get();\n\n    assertThat(executionResult.exception()).isPresent();\n    assertThat(executionResult.exception().get()).hasCauseThat().isNull();\n    assertThat(executionResult.exception().get()).hasMessageThat().contains(\"test exception\");\n    assertThat(executionResult.isSucceeded()).isFalse();\n    assertThat(executionResult.executionStopwatch().elapsed()).isEqualTo(TICK_DURATION);\n    assertThat(executionResult.resultData()).isEmpty();\n  }\n\n  @Test\n  public void executeAsync_whenFailedWithUnknownException_returnsFailedResult()\n      throws ExecutionException, InterruptedException {\n    PluginExecutorConfig<String> executorConfig =\n        PluginExecutorConfig.<String>builder()\n            .setMatchedPlugin(FAKE_MATCHING_RESULT)\n            .setPluginExecutionLogic(\n                () -> {\n                  throw new RuntimeException(\"test unknown exception\");\n                })\n            .build();\n\n    PluginExecutionResult<String> executionResult =\n        new PluginExecutorImpl(PLUGIN_EXECUTION_THREAD_POOL, executionStopWatch)\n            .executeAsync(executorConfig)\n            .get();\n\n    assertThat(executionResult.exception()).isPresent();\n    assertThat(executionResult.exception().get())\n        .hasCauseThat()\n        .isInstanceOf(RuntimeException.class);\n    assertThat(executionResult.exception().get())\n        .hasMessageThat()\n        .contains(\n            String.format(\"Plugin execution error on '%s'.\", FAKE_MATCHING_RESULT.pluginId()));\n    assertThat(executionResult.isSucceeded()).isFalse();\n    assertThat(executionResult.executionStopwatch().elapsed()).isEqualTo(TICK_DURATION);\n    assertThat(executionResult.resultData()).isEmpty();\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/PluginLoadingModuleTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.collect.ImmutableList.toImmutableList;\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.inject.CreationException;\nimport com.google.inject.Guice;\nimport com.google.inject.Key;\nimport com.google.inject.util.Types;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.PortScanningReport;\nimport com.google.tsunami.proto.ScanTarget;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.Vulnerability;\nimport io.github.classgraph.ClassGraph;\nimport io.github.classgraph.ScanResult;\nimport java.util.Map;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link PluginLoadingModule}. */\n@RunWith(JUnit4.class)\npublic final class PluginLoadingModuleTest {\n  @SuppressWarnings(\"unchecked\")\n  private static final Key<Map<PluginDefinition, TsunamiPlugin>> PLUGIN_BINDING_KEY =\n      (Key<Map<PluginDefinition, TsunamiPlugin>>)\n          Key.get(Types.mapOf(PluginDefinition.class, TsunamiPlugin.class));\n\n  private static final ImmutableList<String> COMMON_CLASSES_TO_LOAD =\n      ImmutableList.of(\n          TsunamiPlugin.class.getTypeName(),\n          PortScanner.class.getTypeName(),\n          VulnDetector.class.getTypeName(),\n          PluginBootstrapModule.class.getTypeName());\n\n  @Test\n  public void configure_always_loadsAllTsunamiPlugins() {\n    ImmutableList<String> whitelistedClasses =\n        ImmutableList.<String>builder()\n            .addAll(COMMON_CLASSES_TO_LOAD)\n            .add(\n                FakePortScanner.class.getTypeName(),\n                FakePortScannerBootstrapModule.class.getTypeName(),\n                FakeVulnDetector.class.getTypeName(),\n                FakeVulnDetectorBootstrapModule.class.getTypeName(),\n                FakeVulnDetector2.class.getTypeName(),\n                FakeVulnDetector2.FakeVulnDetector2BootstrapModule.class.getTypeName())\n            .build();\n    try (ScanResult classScanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(whitelistedClasses.toArray(new String[0]))\n            .scan()) {\n      Iterable<Class<? extends TsunamiPlugin>> installedPluginTypes =\n          Guice.createInjector(new PluginLoadingModule(true, classScanResult))\n              .getInstance(PLUGIN_BINDING_KEY)\n              .values()\n              .stream()\n              .map(TsunamiPlugin::getClass)\n              .collect(toImmutableList());\n\n      assertThat(installedPluginTypes)\n          .containsExactly(FakePortScanner.class, FakeVulnDetector.class, FakeVulnDetector2.class);\n    }\n  }\n\n  @Test\n  public void configure_whenPluginMissingRequiredAnnotation_throwsException() {\n    ImmutableList<String> whitelistedClasses =\n        ImmutableList.<String>builder()\n            .addAll(COMMON_CLASSES_TO_LOAD)\n            .add(NoAnnotationDetector.class.getTypeName())\n            .build();\n    try (ScanResult classScanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(whitelistedClasses.toArray(new String[0]))\n            .scan()) {\n      CreationException ex =\n          assertThrows(\n              CreationException.class,\n              () ->\n                  Guice.createInjector(new PluginLoadingModule(true, classScanResult))\n                      .getAllBindings());\n      assertThat(ex).hasCauseThat().isInstanceOf(IllegalStateException.class);\n    }\n  }\n\n  @Test\n  public void configure_whenPluginBootstrapModuleCannotBeInitialized_throwsException() {\n    ImmutableList<String> whitelistedClasses =\n        ImmutableList.<String>builder()\n            .addAll(COMMON_CLASSES_TO_LOAD)\n            .add(\n                FakePortScanner.class.getTypeName(),\n                FakePortScannerBootstrapModule.class.getTypeName())\n            .build();\n    try (ScanResult classScanResult =\n        new ClassGraph()\n            .enableAllInfo()\n            .whitelistClasses(whitelistedClasses.toArray(new String[0]))\n            .scan()) {\n      assertThrows(\n          AssertionError.class,\n          () ->\n              Guice.createInjector(new PluginLoadingModule(false, classScanResult))\n                  .getAllBindings());\n    }\n  }\n\n  @PluginInfo(\n      type = PluginType.PORT_SCAN,\n      name = \"FakePortScanner\",\n      version = \"0.1\",\n      description = \"FakePortScanner\",\n      author = \"TestAuthor\",\n      bootstrapModule = FakePortScannerBootstrapModule.class)\n  private static final class FakePortScanner implements PortScanner {\n    @Override\n    public PortScanningReport scan(ScanTarget scanTarget) {\n      return null;\n    }\n  }\n\n  private static final class FakePortScannerBootstrapModule extends PluginBootstrapModule {\n    @Override\n    protected void configurePlugin() {\n      registerPlugin(FakePortScanner.class);\n    }\n  }\n\n  @PluginInfo(\n      type = PluginType.VULN_DETECTION,\n      name = \"FakeVulnDetector\",\n      version = \"0.1\",\n      description = \"FakeVulnDetector\",\n      author = \"TestAuthor\",\n      bootstrapModule = FakeVulnDetectorBootstrapModule.class)\n  private static final class FakeVulnDetector implements VulnDetector {\n    @Override\n    public DetectionReportList detect(\n        TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n      return null;\n    }\n\n    @Override\n    public ImmutableList<Vulnerability> getAdvisories() {\n      return ImmutableList.of();\n    }\n  }\n\n  private static final class FakeVulnDetectorBootstrapModule extends PluginBootstrapModule {\n    @Override\n    protected void configurePlugin() {\n      registerPlugin(FakeVulnDetector.class);\n    }\n  }\n\n  @PluginInfo(\n      type = PluginType.VULN_DETECTION,\n      name = \"FakeVulnDetector2\",\n      version = \"0.1\",\n      description = \"FakeVulnDetector2\",\n      author = \"TestAuthor\",\n      bootstrapModule = FakeVulnDetector2.FakeVulnDetector2BootstrapModule.class)\n  private static final class FakeVulnDetector2 implements VulnDetector {\n    @Override\n    public DetectionReportList detect(\n        TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n      return null;\n    }\n\n    @Override\n    public ImmutableList<Vulnerability> getAdvisories() {\n      return ImmutableList.of();\n    }\n\n    static final class FakeVulnDetector2BootstrapModule extends PluginBootstrapModule {\n      @Override\n      protected void configurePlugin() {\n        registerPlugin(FakeVulnDetector2.class);\n      }\n    }\n  }\n\n  private static final class NoAnnotationDetector implements VulnDetector {\n    @Override\n    public DetectionReportList detect(\n        TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n      return null;\n    }\n\n    @Override\n    public ImmutableList<Vulnerability> getAdvisories() {\n      return ImmutableList.of();\n    }\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/PluginManagerTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Lists;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Guice;\nimport com.google.inject.multibindings.MapBinder;\nimport com.google.tsunami.common.cli.CliOptionsModule;\nimport com.google.tsunami.common.data.NetworkEndpointUtils;\nimport com.google.tsunami.common.net.http.HttpClientModule;\nimport com.google.tsunami.plugin.PluginManager.PluginMatchingResult;\nimport com.google.tsunami.plugin.annotations.ForOperatingSystemClass;\nimport com.google.tsunami.plugin.annotations.ForServiceName;\nimport com.google.tsunami.plugin.annotations.ForSoftware;\nimport com.google.tsunami.plugin.annotations.ForWebService;\nimport com.google.tsunami.plugin.annotations.PluginInfo;\nimport com.google.tsunami.plugin.payload.PayloadGeneratorModule;\nimport com.google.tsunami.plugin.testing.FakePortScanner;\nimport com.google.tsunami.plugin.testing.FakePortScanner2;\nimport com.google.tsunami.plugin.testing.FakePortScannerBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakePortScannerBootstrapModule2;\nimport com.google.tsunami.plugin.testing.FakeRemoteVulnDetector;\nimport com.google.tsunami.plugin.testing.FakeServiceFingerprinterBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakeVulnDetector;\nimport com.google.tsunami.plugin.testing.FakeVulnDetector2;\nimport com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModule2;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.FingerprintingReport;\nimport com.google.tsunami.proto.MatchedPlugin;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.OperatingSystemClass;\nimport com.google.tsunami.proto.ReconnaissanceReport;\nimport com.google.tsunami.proto.Software;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.TargetOperatingSystemClass;\nimport com.google.tsunami.proto.TargetServiceName;\nimport com.google.tsunami.proto.TargetSoftware;\nimport com.google.tsunami.proto.TransportProtocol;\nimport com.google.tsunami.proto.Vulnerability;\nimport io.github.classgraph.ClassGraph;\nimport io.github.classgraph.ScanResult;\nimport java.security.SecureRandom;\nimport java.util.List;\nimport java.util.Optional;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link PluginManager}. */\n@RunWith(JUnit4.class)\npublic class PluginManagerTest {\n\n  @Test\n  public void getPortScanners_whenMultiplePortScannersInstalled_returnsAllPortScanners() {\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakePortScannerBootstrapModule2(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                new FakeVulnDetectorBootstrapModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<PortScanner>> portScanners = pluginManager.getPortScanners();\n\n    assertThat(\n            portScanners.stream()\n                .map(pluginMatchingResult -> pluginMatchingResult.tsunamiPlugin().getClass()))\n        .containsExactly(FakePortScanner.class, FakePortScanner2.class);\n  }\n\n  @Test\n  public void getPortScanners_whenNoPortScannersInstalled_returnsEmptyList() {\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakeServiceFingerprinterBootstrapModule(),\n                new FakeVulnDetectorBootstrapModule())\n            .getInstance(PluginManager.class);\n\n    assertThat(pluginManager.getPortScanners()).isEmpty();\n  }\n\n  @Test\n  public void getPortScanner_whenMultiplePortScannersInstalled_returnsTheFirstMatchedPortScanner() {\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakePortScannerBootstrapModule2(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                new FakeVulnDetectorBootstrapModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<PortScanner>> allPortScanners =\n        pluginManager.getPortScanners();\n    Optional<PluginMatchingResult<PortScanner>> firstMatchedPortScanner =\n        pluginManager.getPortScanner();\n\n    assertThat(firstMatchedPortScanner).isPresent();\n    assertThat(firstMatchedPortScanner.get().pluginDefinition())\n        .isEqualTo(allPortScanners.get(0).pluginDefinition());\n    assertThat(firstMatchedPortScanner.get().tsunamiPlugin().getClass())\n        .isEqualTo(allPortScanners.get(0).tsunamiPlugin().getClass());\n  }\n\n  @Test\n  public void getPortScanner_whenNoPortScannersInstalled_returnsEmptyOptional() {\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakeServiceFingerprinterBootstrapModule(),\n                new FakeVulnDetectorBootstrapModule())\n            .getInstance(PluginManager.class);\n\n    assertThat(pluginManager.getPortScanner()).isEmpty();\n  }\n\n  @Test\n  public void getServiceFingerprinter_whenFingerprinterNotAnnotated_returnsEmpty() {\n    NetworkService httpService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                NoAnnotationFingerprinter.getModule())\n            .getInstance(PluginManager.class);\n\n    Optional<PluginMatchingResult<ServiceFingerprinter>> fingerprinter =\n        pluginManager.getServiceFingerprinter(httpService);\n\n    assertThat(fingerprinter).isEmpty();\n  }\n\n  @Test\n  public void getServiceFingerprinter_whenFingerprinterHasMatch_returnsMatch() {\n    NetworkService httpService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule())\n            .getInstance(PluginManager.class);\n\n    Optional<PluginMatchingResult<ServiceFingerprinter>> fingerprinter =\n        pluginManager.getServiceFingerprinter(httpService);\n\n    assertThat(fingerprinter).isPresent();\n    assertThat(fingerprinter.get().matchedServices()).containsExactly(httpService);\n  }\n\n  @Test\n  public void getServiceFingerprinter_whenNoFingerprinterMatches_returnsEmpty() {\n    NetworkService httpsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule())\n            .getInstance(PluginManager.class);\n\n    Optional<PluginMatchingResult<ServiceFingerprinter>> fingerprinter =\n        pluginManager.getServiceFingerprinter(httpsService);\n\n    assertThat(fingerprinter).isEmpty();\n  }\n\n  @Test\n  public void getServiceFingerprinter_whenForWebServiceAnnotationAndWebService_returnsMatch() {\n    NetworkService httpProxyService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http-proxy\")\n            .build();\n    NetworkService httpsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                FakeWebFingerprinter.getModule())\n            .getInstance(PluginManager.class);\n\n    Optional<PluginMatchingResult<ServiceFingerprinter>> fingerprinter =\n        pluginManager.getServiceFingerprinter(httpsService);\n    assertThat(fingerprinter).isPresent();\n    assertThat(fingerprinter.get().matchedServices()).containsExactly(httpsService);\n\n    fingerprinter = pluginManager.getServiceFingerprinter(httpProxyService);\n    assertThat(fingerprinter).isPresent();\n    assertThat(fingerprinter.get().matchedServices()).containsExactly(httpProxyService);\n  }\n\n  @Test\n  public void getServiceFingerprinter_whenForWebServiceAnnotationAndNonWebService_returnsEmpty() {\n    NetworkService sshService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"ssh\")\n            .build();\n    NetworkService rdpService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"rdp\")\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                FakeWebFingerprinter.getModule())\n            .getInstance(PluginManager.class);\n\n    assertThat(pluginManager.getServiceFingerprinter(sshService)).isEmpty();\n    assertThat(pluginManager.getServiceFingerprinter(rdpService)).isEmpty();\n  }\n\n  @Test\n  public void\n      getVulnDetectors_whenMultipleVulnDetectorsInstalledNoFiltering_returnsAllVulnDetector() {\n    NetworkService fakeNetworkService1 =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n    NetworkService fakeNetworkService2 =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(fakeNetworkService1)\n            .addNetworkServices(fakeNetworkService2)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                new FakeVulnDetectorBootstrapModule(),\n                new FakeVulnDetectorBootstrapModule2())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(\n            vulnDetectors.stream()\n                .map(pluginMatchingResult -> pluginMatchingResult.tsunamiPlugin().getClass()))\n        .containsExactly(FakeVulnDetector.class, FakeVulnDetector2.class);\n    assertThat(vulnDetectors.stream().map(PluginMatchingResult::matchedServices))\n        .containsExactly(\n            fakeReconnaissanceReport.getNetworkServicesList(),\n            fakeReconnaissanceReport.getNetworkServicesList());\n  }\n\n  @Test\n  public void getVulnDetectors_whenServiceNameFilterHasMatchingService_returnsMatchedService() {\n    NetworkService httpService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n    NetworkService httpsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    NetworkService noNameService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 12345))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(httpService)\n            .addNetworkServices(httpsService)\n            .addNetworkServices(noNameService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeServiceNameFilteringDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).hasSize(1);\n    assertThat(vulnDetectors.get(0).tsunamiPlugin().getClass())\n        .isEqualTo(FakeServiceNameFilteringDetector.class);\n    assertThat(vulnDetectors.get(0).matchedServices()).containsExactly(httpService, noNameService);\n  }\n\n  @Test\n  public void getVulnDetectors_whenServiceNameFilterHasNoMatchingService_returnsEmpty() {\n    NetworkService httpsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(httpsService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeServiceNameFilteringDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).isEmpty();\n  }\n\n  @Test\n  public void getVulnDetectors_whenSoftwareFilterHasMatchingService_returnsMatchedService() {\n    NetworkService wordPressService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .setSoftware(Software.newBuilder().setName(\"WordPress\"))\n            .build();\n    NetworkService jenkinsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .setSoftware(Software.newBuilder().setName(\"Jenkins\"))\n            .build();\n    NetworkService noNameService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 12345))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(wordPressService)\n            .addNetworkServices(jenkinsService)\n            .addNetworkServices(noNameService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeSoftwareFilteringDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).hasSize(1);\n    assertThat(vulnDetectors.get(0).tsunamiPlugin().getClass())\n        .isEqualTo(FakeSoftwareFilteringDetector.class);\n    assertThat(vulnDetectors.get(0).matchedServices())\n        .containsExactly(jenkinsService, noNameService);\n  }\n\n  @Test\n  public void getVulnDetectors_whenOsFilterHasNoMatchingClass_returnsEmpty() {\n    NetworkService wordPressService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .setSoftware(Software.newBuilder().setName(\"WordPress\"))\n            .build();\n    NetworkService jenkinsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .setSoftware(Software.newBuilder().setName(\"Jenkins\"))\n            .build();\n    NetworkService noNameService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 12345))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(wordPressService)\n            .addNetworkServices(jenkinsService)\n            .addNetworkServices(noNameService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeOsFilteringDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    // matchVulnDetectors returns Optional.empty() when there is no match.\n    assertThat(vulnDetectors).isEmpty();\n  }\n\n  @Test\n  public void getVulnDetectors_whenOsFilterHasMatchingClass_returnsMatches() {\n    NetworkService wordPressService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .setSoftware(Software.newBuilder().setName(\"WordPress\"))\n            .build();\n    NetworkService jenkinsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .setSoftware(Software.newBuilder().setName(\"Jenkins\"))\n            .build();\n    NetworkService noNameService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 12345))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(\n                TargetInfo.newBuilder()\n                    .addOperatingSystemClasses(\n                        OperatingSystemClass.newBuilder()\n                            .setVendor(\"Vendor\")\n                            .setOsFamily(\"FakeOS\")\n                            .setAccuracy(99)))\n            .addNetworkServices(wordPressService)\n            .addNetworkServices(jenkinsService)\n            .addNetworkServices(noNameService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeOsFilteringDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).hasSize(1);\n    assertThat(vulnDetectors.get(0).tsunamiPlugin().getClass())\n        .isEqualTo(FakeOsFilteringDetector.class);\n    // And matches everything (as all these services are running on the same target)\n    assertThat(vulnDetectors.get(0).matchedServices())\n        .containsExactly(wordPressService, jenkinsService, noNameService);\n  }\n\n  @Test\n  public void getVulnDetectors_whenOsServiceFilterHasMatchingClass_returnsMatches() {\n    NetworkService wordPressService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .setSoftware(Software.newBuilder().setName(\"WordPress\"))\n            .build();\n    NetworkService jenkinsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .setSoftware(Software.newBuilder().setName(\"Jenkins\"))\n            .build();\n    NetworkService noNameService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 12345))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(\n                TargetInfo.newBuilder()\n                    .addOperatingSystemClasses(\n                        OperatingSystemClass.newBuilder()\n                            .setVendor(\"Vendor\")\n                            .setOsFamily(\"FakeOS\")\n                            .setAccuracy(99)))\n            .addNetworkServices(wordPressService)\n            .addNetworkServices(jenkinsService)\n            .addNetworkServices(noNameService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeOsServiceFilteringDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).hasSize(1);\n    assertThat(vulnDetectors.get(0).tsunamiPlugin().getClass())\n        .isEqualTo(FakeOsServiceFilteringDetector.class);\n    // And matches the ones with the Jenkins software (and noname as well, as no software info is\n    // present there; see hasMatchingServiceName)\n    assertThat(vulnDetectors.get(0).matchedServices())\n        .containsExactly(jenkinsService, noNameService);\n  }\n\n  @Test\n  public void getVulnDetectors_whenSoftwareFilterHasNoMatchingService_returnsEmpty() {\n    NetworkService wordPressService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .setSoftware(Software.newBuilder().setName(\"WordPress\"))\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(wordPressService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeSoftwareFilteringDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).isEmpty();\n  }\n\n  @Test\n  public void getVulnDetectors_whenNoVulnDetectorsInstalled_returnsEmptyList() {\n    NetworkService fakeNetworkService1 =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n    NetworkService fakeNetworkService2 =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(fakeNetworkService1)\n            .addNetworkServices(fakeNetworkService2)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule())\n            .getInstance(PluginManager.class);\n\n    assertThat(pluginManager.getVulnDetectors(fakeReconnaissanceReport)).isEmpty();\n  }\n\n  @Test\n  public void\n      getVulnDetectors_whenRemotePluginsInstalledNoFiltering_returnsAllRemoteTsunamiPlugins()\n          throws Exception {\n    NetworkService fakeNetworkService1 =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n    NetworkService fakeNetworkService2 =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(fakeNetworkService1)\n            .addNetworkServices(fakeNetworkService2)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakeServiceFingerprinterBootstrapModule(),\n                new FakeRemoteVulnDetectorLoadingModule(2))\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> remotePlugins =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(\n            remotePlugins.stream()\n                .map(pluginMatchingResult -> pluginMatchingResult.tsunamiPlugin().getClass()))\n        .containsExactly(FakeRemoteVulnDetector.class, FakeRemoteVulnDetector.class);\n  }\n\n  @Test\n  public void\n      getVulnDetectors_whenRemoteDetectorServiceNameFilterHasMatchingService_returnsMatchedService() {\n    NetworkService httpService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n    NetworkService httpsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    NetworkService noNameService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 12345))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(httpService)\n            .addNetworkServices(httpsService)\n            .addNetworkServices(noNameService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeFilteringRemoteDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).hasSize(1);\n    ImmutableList<MatchedPlugin> matchedResult =\n        ((FakeFilteringRemoteDetector) vulnDetectors.get(0).tsunamiPlugin()).getMatchedPlugins();\n    assertThat(matchedResult).isNotEmpty();\n    assertThat(matchedResult.get(0).getPlugin())\n        .isEqualTo(FakeFilteringRemoteDetector.getHttpServiceDefinition());\n    assertThat(matchedResult.get(0).getServicesList()).containsExactly(httpService, noNameService);\n  }\n\n  @Test\n  public void getVulnDetectors_whenRemoteDetectorWithServiceNameHasNoMatch_returnsNoServices() {\n    NetworkService httpsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(httpsService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeFilteringRemoteDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).hasSize(1);\n    ImmutableList<MatchedPlugin> matchedResult =\n        ((FakeFilteringRemoteDetector) vulnDetectors.get(0).tsunamiPlugin()).getMatchedPlugins();\n    assertThat(matchedResult.get(0).getServicesList()).isEmpty();\n  }\n\n  @Test\n  public void\n      getVulnDetectors_whenRemoteDetectorSoftwareFilterHasMatchingService_returnsMatchedService() {\n    NetworkService wordPressService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .setSoftware(Software.newBuilder().setName(\"WordPress\"))\n            .build();\n    NetworkService jenkinsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .setSoftware(Software.newBuilder().setName(\"Jenkins\"))\n            .build();\n    NetworkService noNameService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 12345))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(wordPressService)\n            .addNetworkServices(jenkinsService)\n            .addNetworkServices(noNameService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeFilteringRemoteDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).hasSize(1);\n    ImmutableList<MatchedPlugin> matchedResult =\n        ((FakeFilteringRemoteDetector) vulnDetectors.get(0).tsunamiPlugin()).getMatchedPlugins();\n    assertThat(matchedResult).hasSize(4);\n    assertThat(matchedResult.get(1).getPlugin())\n        .isEqualTo(FakeFilteringRemoteDetector.getJenkinsServiceDefinition());\n    assertThat(matchedResult.get(1).getServicesList())\n        .containsExactly(jenkinsService, noNameService);\n\n    // The other non-OS detector should match, too:\n    assertThat(matchedResult.get(0).getPlugin())\n        .isEqualTo(FakeFilteringRemoteDetector.getHttpServiceDefinition());\n    // wordpress: matching service_name (http)\n    // jenkins: mismatching service_name (http*s*)\n    // nonameservice: no service_name present in the NetworkService, hasMatchingServiceName accepts\n    // that\n    assertThat(matchedResult.get(0).getServicesList())\n        .containsExactly(wordPressService, noNameService);\n    // The OS related ones should be empty:\n    assertThat(matchedResult.get(2).getServicesCount()).isEqualTo(0);\n    assertThat(matchedResult.get(3).getServicesCount()).isEqualTo(0);\n  }\n\n  @Test\n  public void\n      getVulnDetectors_whenRemoteDetectorOsFilterHasMatchingService_returnsMatchedService() {\n    NetworkService wordPressService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .setSoftware(Software.newBuilder().setName(\"WordPress\"))\n            .build();\n    NetworkService jenkinsService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .setSoftware(Software.newBuilder().setName(\"Jenkins\"))\n            .build();\n    NetworkService noNameService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 12345))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(\n                TargetInfo.newBuilder()\n                    .addOperatingSystemClasses(\n                        OperatingSystemClass.newBuilder()\n                            .setType(\"general purpose\")\n                            .setVendor(\"Vendor\")\n                            .setOsFamily(\"FakeOS\")\n                            .setAccuracy(96)))\n            .addNetworkServices(wordPressService)\n            .addNetworkServices(jenkinsService)\n            .addNetworkServices(noNameService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeFilteringRemoteDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).hasSize(1);\n    ImmutableList<MatchedPlugin> matchedResult =\n        ((FakeFilteringRemoteDetector) vulnDetectors.get(0).tsunamiPlugin()).getMatchedPlugins();\n    assertThat(matchedResult).hasSize(4);\n    assertThat(matchedResult.get(1).getPlugin())\n        .isEqualTo(FakeFilteringRemoteDetector.getJenkinsServiceDefinition());\n    assertThat(matchedResult.get(1).getServicesList())\n        .containsExactly(jenkinsService, noNameService);\n\n    // The other non-OS detector should match, too:\n    assertThat(matchedResult.get(0).getPlugin())\n        .isEqualTo(FakeFilteringRemoteDetector.getHttpServiceDefinition());\n    // wordpress: matching service_name (http)\n    // jenkins: mismatching service_name (http*s*)\n    // nonameservice: no service_name present in the NetworkService, hasMatchingServiceName accepts\n    // that\n    assertThat(matchedResult.get(0).getServicesList())\n        .containsExactly(wordPressService, noNameService);\n\n    // The one that matches the OS only, should match everything:\n    assertThat(matchedResult.get(2).getPlugin())\n        .isEqualTo(FakeFilteringRemoteDetector.getOperatingSystemServiceDefinition());\n    assertThat(matchedResult.get(2).getServicesList())\n        .containsExactly(wordPressService, jenkinsService, noNameService);\n\n    // The one that matches the OS and \"http\", should return these:\n    assertThat(matchedResult.get(3).getPlugin())\n        .isEqualTo(FakeFilteringRemoteDetector.getOperatingSystemAndHttpServiceDefinition());\n    assertThat(matchedResult.get(3).getServicesList())\n        .containsExactly(wordPressService, noNameService);\n  }\n\n  @Test\n  public void\n      getVulnDetectors_whenRemoteDetectorWithSoftwareFilterHasNoMatchingService_returnsNoServices() {\n    NetworkService wordPressService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .setSoftware(Software.newBuilder().setName(\"WordPress\"))\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(wordPressService)\n            .build();\n    PluginManager pluginManager =\n        Guice.createInjector(\n                new HttpClientModule.Builder().build(),\n                new PayloadGeneratorModule(new SecureRandom()),\n                new FakePortScannerBootstrapModule(),\n                new FakeServiceFingerprinterBootstrapModule(),\n                FakeFilteringRemoteDetector.getModule())\n            .getInstance(PluginManager.class);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n        pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n    assertThat(vulnDetectors).hasSize(1);\n    ImmutableList<MatchedPlugin> matchedResult =\n        ((FakeFilteringRemoteDetector) vulnDetectors.get(0).tsunamiPlugin()).getMatchedPlugins();\n    assertThat(matchedResult).hasSize(4);\n    for (var mr : matchedResult) {\n      assertThat(mr.getServicesCount()).isEqualTo(0);\n    }\n  }\n\n  @Test\n  public void getVulnDetectors_whenDetectorsIncludeIsOverridden_returnsMatchingVulnDetector() {\n    NetworkService fakeNetworkService1 =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n    NetworkService fakeNetworkService2 =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(fakeNetworkService1)\n            .addNetworkServices(fakeNetworkService2)\n            .build();\n    try (ScanResult scanResult = new ClassGraph().enableAllInfo().scan()) {\n      PluginManager pluginManager =\n          Guice.createInjector(\n                  new CliOptionsModule(\n                      scanResult,\n                      \"TsunamiCliTest\",\n                      new String[] {\"--detectors-include=Blabla1, FakeVulnDetector, Blabla2\"}),\n                  new HttpClientModule.Builder().build(),\n                  new PayloadGeneratorModule(new SecureRandom()),\n                  new FakePortScannerBootstrapModule(),\n                  new FakeServiceFingerprinterBootstrapModule(),\n                  new FakeVulnDetectorBootstrapModule(),\n                  new FakeVulnDetectorBootstrapModule2())\n              .getInstance(PluginManager.class);\n\n      ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n          pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n      assertThat(\n              vulnDetectors.stream()\n                  .map(pluginMatchingResult -> pluginMatchingResult.tsunamiPlugin().getClass()))\n          .containsExactly(FakeVulnDetector.class);\n      assertThat(vulnDetectors.stream().map(PluginMatchingResult::matchedServices))\n          .containsExactly(fakeReconnaissanceReport.getNetworkServicesList());\n    }\n  }\n\n  @Test\n  public void getVulnDetectors_whenDetectorsExcludeIsOverridden_returnsMatchingVulnDetector() {\n    NetworkService fakeNetworkService1 =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n    NetworkService fakeNetworkService2 =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"https\")\n            .build();\n    ReconnaissanceReport fakeReconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .setTargetInfo(TargetInfo.getDefaultInstance())\n            .addNetworkServices(fakeNetworkService1)\n            .addNetworkServices(fakeNetworkService2)\n            .build();\n    try (ScanResult scanResult = new ClassGraph().enableAllInfo().scan()) {\n      PluginManager pluginManager =\n          Guice.createInjector(\n                  new CliOptionsModule(\n                      scanResult,\n                      \"TsunamiCliTest\",\n                      new String[] {\"--detectors-exclude=FakeVulnDetector\"}),\n                  new HttpClientModule.Builder().build(),\n                  new PayloadGeneratorModule(new SecureRandom()),\n                  new FakePortScannerBootstrapModule(),\n                  new FakeServiceFingerprinterBootstrapModule(),\n                  new FakeVulnDetectorBootstrapModule(),\n                  new FakeVulnDetectorBootstrapModule2())\n              .getInstance(PluginManager.class);\n\n      ImmutableList<PluginMatchingResult<VulnDetector>> vulnDetectors =\n          pluginManager.getVulnDetectors(fakeReconnaissanceReport);\n\n      assertThat(\n              vulnDetectors.stream()\n                  .map(pluginMatchingResult -> pluginMatchingResult.tsunamiPlugin().getClass()))\n          .containsExactly(FakeVulnDetector2.class);\n      assertThat(vulnDetectors.stream().map(PluginMatchingResult::matchedServices))\n          .containsExactly(fakeReconnaissanceReport.getNetworkServicesList());\n    }\n  }\n\n  @PluginInfo(\n      type = PluginType.SERVICE_FINGERPRINT,\n      name = \"NoAnnotationFingerprinter\",\n      version = \"v0.1\",\n      description = \"A fake ServiceFingerprinter.\",\n      author = \"fake\",\n      bootstrapModule = NoAnnotationFingerprinter.NoAnnotationFingerprinterBootstrapModule.class)\n  private static final class NoAnnotationFingerprinter implements ServiceFingerprinter {\n    @Override\n    public FingerprintingReport fingerprint(TargetInfo targetInfo, NetworkService networkService) {\n      return null;\n    }\n\n    static NoAnnotationFingerprinterBootstrapModule getModule() {\n      return new NoAnnotationFingerprinterBootstrapModule();\n    }\n\n    private static final class NoAnnotationFingerprinterBootstrapModule\n        extends PluginBootstrapModule {\n      @Override\n      protected void configurePlugin() {\n        registerPlugin(NoAnnotationFingerprinter.class);\n      }\n    }\n  }\n\n  @PluginInfo(\n      type = PluginType.SERVICE_FINGERPRINT,\n      name = \"FakeWebFingerprinter\",\n      version = \"v0.1\",\n      description = \"A fake ServiceFingerprinter for web services.\",\n      author = \"fake\",\n      bootstrapModule = FakeWebFingerprinter.FakeWebFingerprinterBootstrapModule.class)\n  @ForWebService\n  private static final class FakeWebFingerprinter implements ServiceFingerprinter {\n    @Override\n    public FingerprintingReport fingerprint(TargetInfo targetInfo, NetworkService networkService) {\n      return null;\n    }\n\n    static FakeWebFingerprinterBootstrapModule getModule() {\n      return new FakeWebFingerprinterBootstrapModule();\n    }\n\n    private static final class FakeWebFingerprinterBootstrapModule extends PluginBootstrapModule {\n      @Override\n      protected void configurePlugin() {\n        registerPlugin(FakeWebFingerprinter.class);\n      }\n    }\n  }\n\n  @PluginInfo(\n      type = PluginType.VULN_DETECTION,\n      name = \"FakeServiceNameFilteringDetector\",\n      version = \"v0.1\",\n      description = \"A fake VulnDetector.\",\n      author = \"fake\",\n      bootstrapModule =\n          FakeServiceNameFilteringDetector.FakeServiceNameFilteringDetectorBootstrapModule.class)\n  @ForServiceName(\"http\")\n  private static final class FakeServiceNameFilteringDetector implements VulnDetector {\n    @Override\n    public DetectionReportList detect(\n        TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n      return null;\n    }\n\n    @Override\n    public ImmutableList<Vulnerability> getAdvisories() {\n      return ImmutableList.of();\n    }\n\n    static FakeServiceNameFilteringDetectorBootstrapModule getModule() {\n      return new FakeServiceNameFilteringDetectorBootstrapModule();\n    }\n\n    private static final class FakeServiceNameFilteringDetectorBootstrapModule\n        extends PluginBootstrapModule {\n      @Override\n      protected void configurePlugin() {\n        registerPlugin(FakeServiceNameFilteringDetector.class);\n      }\n    }\n  }\n\n  @PluginInfo(\n      type = PluginType.VULN_DETECTION,\n      name = \"FakeSoftwareFilteringDetector\",\n      version = \"v0.1\",\n      description = \"A fake VulnDetector.\",\n      author = \"fake\",\n      bootstrapModule =\n          FakeSoftwareFilteringDetector.FakeSofwareFilteringDetectorBootstrapModule.class)\n  @ForSoftware(name = \"Jenkins\")\n  private static final class FakeSoftwareFilteringDetector implements VulnDetector {\n    @Override\n    public DetectionReportList detect(\n        TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n      return null;\n    }\n\n    @Override\n    public ImmutableList<Vulnerability> getAdvisories() {\n      return ImmutableList.of();\n    }\n\n    static FakeSofwareFilteringDetectorBootstrapModule getModule() {\n      return new FakeSofwareFilteringDetectorBootstrapModule();\n    }\n\n    private static final class FakeSofwareFilteringDetectorBootstrapModule\n        extends PluginBootstrapModule {\n      @Override\n      protected void configurePlugin() {\n        registerPlugin(FakeSoftwareFilteringDetector.class);\n      }\n    }\n  }\n\n  @PluginInfo(\n      type = PluginType.VULN_DETECTION,\n      name = \"FakeOsFilteringDetector\",\n      version = \"v0.1\",\n      description = \"A fake VulnDetector.\",\n      author = \"fake\",\n      bootstrapModule = FakeOsFilteringDetector.FakeOsFilteringDetectorBootstrapModule.class)\n  @ForOperatingSystemClass(osfamily = \"FakeOS\")\n  private static final class FakeOsFilteringDetector implements VulnDetector {\n    @Override\n    public DetectionReportList detect(\n        TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n      return null;\n    }\n\n    @Override\n    public ImmutableList<Vulnerability> getAdvisories() {\n      return ImmutableList.of();\n    }\n\n    static FakeOsFilteringDetectorBootstrapModule getModule() {\n      return new FakeOsFilteringDetectorBootstrapModule();\n    }\n\n    private static final class FakeOsFilteringDetectorBootstrapModule\n        extends PluginBootstrapModule {\n      @Override\n      protected void configurePlugin() {\n        registerPlugin(FakeOsFilteringDetector.class);\n      }\n    }\n  }\n\n  @PluginInfo(\n      type = PluginType.VULN_DETECTION,\n      name = \"FakeOsServiceFilteringDetector\",\n      version = \"v0.1\",\n      description = \"A fake VulnDetector.\",\n      author = \"fake\",\n      bootstrapModule =\n          FakeOsServiceFilteringDetector.FakeOsServiceFilteringDetectorBootstrapModule.class)\n  @ForOperatingSystemClass(osfamily = \"FakeOS\")\n  @ForSoftware(name = \"Jenkins\")\n  private static final class FakeOsServiceFilteringDetector implements VulnDetector {\n    @Override\n    public DetectionReportList detect(\n        TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n      return null;\n    }\n\n    @Override\n    public ImmutableList<Vulnerability> getAdvisories() {\n      return ImmutableList.of();\n    }\n\n    static FakeOsServiceFilteringDetectorBootstrapModule getModule() {\n      return new FakeOsServiceFilteringDetectorBootstrapModule();\n    }\n\n    private static final class FakeOsServiceFilteringDetectorBootstrapModule\n        extends PluginBootstrapModule {\n      @Override\n      protected void configurePlugin() {\n        registerPlugin(FakeOsServiceFilteringDetector.class);\n      }\n    }\n  }\n\n  @PluginInfo(\n      type = PluginType.REMOTE_VULN_DETECTION,\n      name = \"FakeFilteringRemoteDetector\",\n      version = \"v0.1\",\n      description = \"A fake RemoteVulnDetector.\",\n      author = \"fake\",\n      bootstrapModule =\n          FakeFilteringRemoteDetector.FakeFilteringRemoteDetectorBootstrapModule.class)\n  private static final class FakeFilteringRemoteDetector implements RemoteVulnDetector {\n\n    private final List<MatchedPlugin> matchedPlugins;\n\n    FakeFilteringRemoteDetector() {\n      matchedPlugins = Lists.newArrayList();\n    }\n\n    public ImmutableList<MatchedPlugin> getMatchedPlugins() {\n      return ImmutableList.copyOf(matchedPlugins);\n    }\n\n    @Override\n    public DetectionReportList detect(\n        TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {\n      return null;\n    }\n\n    @Override\n    public ImmutableList<Vulnerability> getAdvisories() {\n      return ImmutableList.of();\n    }\n\n    @Override\n    public ImmutableList<com.google.tsunami.proto.PluginDefinition> getAllPlugins() {\n      return ImmutableList.of(\n          getHttpServiceDefinition(),\n          getJenkinsServiceDefinition(),\n          getOperatingSystemServiceDefinition(),\n          getOperatingSystemAndHttpServiceDefinition());\n    }\n\n    @Override\n    public void addMatchedPluginToDetect(MatchedPlugin plugin) {\n      matchedPlugins.add(plugin);\n    }\n\n    static com.google.tsunami.proto.PluginDefinition getHttpServiceDefinition() {\n      return com.google.tsunami.proto.PluginDefinition.newBuilder()\n          .setInfo(\n              com.google.tsunami.proto.PluginInfo.newBuilder()\n                  .setType(com.google.tsunami.proto.PluginInfo.PluginType.VULN_DETECTION)\n                  .setName(\"FakeHttpServiceVuln\")\n                  .setVersion(\"v0.1\")\n                  .setDescription(\"A fake VulnDetector.\")\n                  .setAuthor(\"fake\"))\n          .setTargetServiceName(TargetServiceName.newBuilder().addValue(\"http\"))\n          .build();\n    }\n\n    static com.google.tsunami.proto.PluginDefinition getJenkinsServiceDefinition() {\n      return com.google.tsunami.proto.PluginDefinition.newBuilder()\n          .setInfo(\n              com.google.tsunami.proto.PluginInfo.newBuilder()\n                  .setType(com.google.tsunami.proto.PluginInfo.PluginType.VULN_DETECTION)\n                  .setName(\"FakeJenkinsVuln\")\n                  .setVersion(\"v0.1\")\n                  .setDescription(\"A fake VulnDetector\")\n                  .setAuthor(\"fake\"))\n          .setTargetSoftware(TargetSoftware.newBuilder().setName(\"Jenkins\"))\n          .build();\n    }\n\n    static com.google.tsunami.proto.PluginDefinition getOperatingSystemServiceDefinition() {\n      return com.google.tsunami.proto.PluginDefinition.newBuilder()\n          .setInfo(\n              com.google.tsunami.proto.PluginInfo.newBuilder()\n                  .setType(com.google.tsunami.proto.PluginInfo.PluginType.VULN_DETECTION)\n                  .setName(\"FakeOsVuln\")\n                  .setVersion(\"v0.1\")\n                  .setDescription(\"A fake VulnDetector that targets services running on FakeOS\")\n                  .setAuthor(\"fake\"))\n          .setTargetOperatingSystemClass(\n              TargetOperatingSystemClass.newBuilder()\n                  .addOsFamily(\"ThisWontMatch\")\n                  .addOsFamily(\"FakeOS\"))\n          .build();\n    }\n\n    static com.google.tsunami.proto.PluginDefinition getOperatingSystemAndHttpServiceDefinition() {\n      return com.google.tsunami.proto.PluginDefinition.newBuilder()\n          .setInfo(\n              com.google.tsunami.proto.PluginInfo.newBuilder()\n                  .setType(com.google.tsunami.proto.PluginInfo.PluginType.VULN_DETECTION)\n                  .setName(\"FakeOsHttpVuln\")\n                  .setVersion(\"v0.1\")\n                  .setDescription(\n                      \"A fake VulnDetector that targets http services running on FakeOS\")\n                  .setAuthor(\"fake\"))\n          .setTargetServiceName(TargetServiceName.newBuilder().addValue(\"http\"))\n          .setTargetOperatingSystemClass(\n              TargetOperatingSystemClass.newBuilder()\n                  .addVendor(\"ThisWontMatch\")\n                  .addOsFamily(\"FakeOS\")\n                  .setMinAccuracy(90))\n          .build();\n    }\n\n    static FakeFilteringRemoteDetectorBootstrapModule getModule() {\n      return new FakeFilteringRemoteDetectorBootstrapModule();\n    }\n\n    private static final class FakeFilteringRemoteDetectorBootstrapModule\n        extends PluginBootstrapModule {\n      @Override\n      protected void configurePlugin() {\n        registerPlugin(FakeFilteringRemoteDetector.class);\n      }\n    }\n  }\n\n  private static final class FakeRemoteVulnDetectorLoadingModule extends AbstractModule {\n    private final int numRemotePlugins;\n\n    public FakeRemoteVulnDetectorLoadingModule() {\n      this(0);\n    }\n\n    public FakeRemoteVulnDetectorLoadingModule(int numRemotePlugins) {\n      this.numRemotePlugins = numRemotePlugins;\n    }\n\n    @Override\n    protected void configure() {\n      MapBinder<PluginDefinition, TsunamiPlugin> tsunamiPluginBinder =\n          MapBinder.newMapBinder(binder(), PluginDefinition.class, TsunamiPlugin.class);\n      for (int i = 0; i < numRemotePlugins; i++) {\n        tsunamiPluginBinder\n            .addBinding(RemoteVulnDetectorLoadingModule.getRemoteVulnDetectorPluginDefinition(i))\n            .toInstance(new FakeRemoteVulnDetector(i));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/PluginServiceClientTest.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static java.util.concurrent.TimeUnit.SECONDS;\n\nimport com.google.common.collect.Lists;\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.tsunami.common.data.NetworkEndpointUtils;\nimport com.google.tsunami.proto.DetectionReport;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.ListPluginsRequest;\nimport com.google.tsunami.proto.ListPluginsResponse;\nimport com.google.tsunami.proto.MatchedPlugin;\nimport com.google.tsunami.proto.NetworkEndpoint;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.PluginDefinition;\nimport com.google.tsunami.proto.PluginInfo;\nimport com.google.tsunami.proto.PluginServiceGrpc.PluginServiceImplBase;\nimport com.google.tsunami.proto.RunRequest;\nimport com.google.tsunami.proto.RunResponse;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.TransportProtocol;\nimport io.grpc.Deadline;\nimport io.grpc.health.v1.HealthCheckRequest;\nimport io.grpc.health.v1.HealthCheckResponse;\nimport io.grpc.health.v1.HealthCheckResponse.ServingStatus;\nimport io.grpc.health.v1.HealthGrpc.HealthImplBase;\nimport io.grpc.inprocess.InProcessChannelBuilder;\nimport io.grpc.inprocess.InProcessServerBuilder;\nimport io.grpc.stub.StreamObserver;\nimport io.grpc.testing.GrpcCleanupRule;\nimport io.grpc.util.MutableHandlerRegistry;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link PluginServiceClient}. */\n@RunWith(JUnit4.class)\npublic final class PluginServiceClientTest {\n\n  @Rule public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();\n\n  private static final String PLUGIN_NAME = \"test plugin\";\n  private static final String PLUGIN_VERSION = \"0.0.1\";\n  private static final String PLUGIN_DESCRIPTION = \"test description\";\n  private static final String PLUGIN_AUTHOR = \"tester\";\n\n  private static final Deadline DEADLINE_DEFAULT = Deadline.after(5, SECONDS);\n\n  private PluginServiceClient pluginService;\n  private final MutableHandlerRegistry serviceRegistry = new MutableHandlerRegistry();\n\n  @Before\n  public void setUp() throws Exception {\n    String serverName = InProcessServerBuilder.generateName();\n    grpcCleanup.register(\n        InProcessServerBuilder.forName(serverName)\n            .fallbackHandlerRegistry(serviceRegistry)\n            .directExecutor()\n            .build()\n            .start());\n\n    pluginService =\n        new PluginServiceClient(\n            InProcessChannelBuilder.forName(serverName).directExecutor().build());\n  }\n\n  @Test\n  public void pluginService_isNotNull() {\n    assertThat(pluginService).isNotNull();\n  }\n\n  @Test\n  public void run_invalidRequest_returnNoDetectionReports() throws Exception {\n    RunRequest runRequest = RunRequest.getDefaultInstance();\n    PluginServiceImplBase runImpl =\n        new PluginServiceImplBase() {\n          @Override\n          public void run(RunRequest request, StreamObserver<RunResponse> responseObserver) {\n            responseObserver.onNext(RunResponse.getDefaultInstance());\n            responseObserver.onCompleted();\n          }\n        };\n    serviceRegistry.addService(runImpl);\n\n    ListenableFuture<RunResponse> run = pluginService.runWithDeadline(runRequest, DEADLINE_DEFAULT);\n    RunResponse runResponse = run.get();\n\n    assertThat(run.isDone()).isTrue();\n    assertThat(runResponse.hasReports()).isFalse();\n  }\n\n  @Test\n  public void run_singlePluginValidRequest_returnSingleDetectionReport() throws Exception {\n    RunRequest runRequest = createSinglePluginRunRequest();\n    PluginServiceImplBase runImpl =\n        new PluginServiceImplBase() {\n          @Override\n          public void run(RunRequest request, StreamObserver<RunResponse> responseObserver) {\n            DetectionReportList reportList =\n                DetectionReportList.newBuilder()\n                    .addDetectionReports(\n                        DetectionReport.newBuilder()\n                            .setTargetInfo(request.getTarget())\n                            .setNetworkService(request.getPlugins(0).getServices(0)))\n                    .build();\n            responseObserver.onNext(RunResponse.newBuilder().setReports(reportList).build());\n            responseObserver.onCompleted();\n          }\n        };\n    serviceRegistry.addService(runImpl);\n\n    ListenableFuture<RunResponse> run = pluginService.runWithDeadline(runRequest, DEADLINE_DEFAULT);\n    RunResponse runResponse = run.get();\n\n    assertThat(run.isDone()).isTrue();\n    assertRunResponseContainsAllRunRequestParameters(runResponse, runRequest);\n  }\n\n  @Test\n  public void run_multiplePluginValidRequest_returnMultipleDetectionReports() throws Exception {\n    int numPluginsToTest = 5;\n\n    List<NetworkEndpoint> endpoints = new ArrayList<>(numPluginsToTest);\n    endpoints.add(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80));\n    endpoints.add(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 443));\n    endpoints.add(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 123));\n    endpoints.add(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 456));\n    endpoints.add(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 789));\n\n    PluginInfo.Builder pluginInfoBuilder =\n        PluginInfo.newBuilder()\n            .setType(PluginInfo.PluginType.VULN_DETECTION)\n            .setVersion(PLUGIN_VERSION)\n            .setDescription(PLUGIN_DESCRIPTION)\n            .setAuthor(PLUGIN_AUTHOR);\n\n    TargetInfo target = TargetInfo.newBuilder().addAllNetworkEndpoints(endpoints).build();\n\n    RunRequest.Builder runRequestBuilder = RunRequest.newBuilder().setTarget(target);\n\n    for (int i = 0; i < numPluginsToTest; i++) {\n      PluginInfo pluginInfo =\n          pluginInfoBuilder.setName(String.format(PLUGIN_NAME + \" %d\", i)).build();\n      NetworkService httpService =\n          NetworkService.newBuilder()\n              .setNetworkEndpoint(endpoints.get(i))\n              .setTransportProtocol(TransportProtocol.TCP)\n              .setServiceName(\"http\")\n              .build();\n      runRequestBuilder.addPlugins(\n          MatchedPlugin.newBuilder()\n              .addServices(httpService)\n              .setPlugin(PluginDefinition.newBuilder().setInfo(pluginInfo).build()));\n    }\n    RunRequest runRequest = runRequestBuilder.build();\n\n    PluginServiceImplBase runImpl =\n        new PluginServiceImplBase() {\n          @Override\n          public void run(RunRequest request, StreamObserver<RunResponse> responseObserver) {\n            DetectionReportList.Builder reportListBuilder = DetectionReportList.newBuilder();\n            for (MatchedPlugin plugin : request.getPluginsList()) {\n              reportListBuilder.addDetectionReports(\n                  DetectionReport.newBuilder()\n                      .setTargetInfo(request.getTarget())\n                      .setNetworkService(plugin.getServices(0)));\n            }\n            responseObserver.onNext(RunResponse.newBuilder().setReports(reportListBuilder).build());\n            responseObserver.onCompleted();\n          }\n        };\n    serviceRegistry.addService(runImpl);\n\n    ListenableFuture<RunResponse> run = pluginService.runWithDeadline(runRequest, DEADLINE_DEFAULT);\n    RunResponse runResponse = run.get();\n\n    assertThat(run.isDone()).isTrue();\n    assertThat(runResponse.getReports().getDetectionReportsCount()).isEqualTo(numPluginsToTest);\n    assertRunResponseContainsAllRunRequestParameters(runResponse, runRequest);\n  }\n\n  @Test\n  public void listPlugins_returnsMultiplePlugins() throws Exception {\n    ListPluginsRequest request = ListPluginsRequest.getDefaultInstance();\n\n    List<PluginDefinition> plugins = Lists.newArrayList();\n    for (int i = 0; i < 5; i++) {\n      plugins.add(createSinglePluginDefinitionWithName(String.format(PLUGIN_NAME + \"%d\", i)));\n    }\n\n    PluginServiceImplBase listPluginsImpl =\n        new PluginServiceImplBase() {\n          @Override\n          public void listPlugins(\n              ListPluginsRequest request, StreamObserver<ListPluginsResponse> responseObserver) {\n            responseObserver.onNext(\n                ListPluginsResponse.newBuilder().addAllPlugins(plugins).build());\n            responseObserver.onCompleted();\n          }\n        };\n    serviceRegistry.addService(listPluginsImpl);\n\n    ListenableFuture<ListPluginsResponse> listPlugins =\n        pluginService.listPluginsWithDeadline(request, DEADLINE_DEFAULT);\n\n    assertThat(listPlugins.isDone()).isTrue();\n    assertThat(listPlugins.get().getPluginsList()).containsExactlyElementsIn(plugins);\n  }\n\n  @Test\n  public void checkHealth_returnServingHealthResponse() throws Exception {\n    HealthCheckRequest request = HealthCheckRequest.getDefaultInstance();\n\n    HealthImplBase healthImpl =\n        new HealthImplBase() {\n          @Override\n          public void check(\n              HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) {\n            responseObserver.onNext(\n                HealthCheckResponse.newBuilder().setStatus(ServingStatus.SERVING).build());\n            responseObserver.onCompleted();\n          }\n        };\n    serviceRegistry.addService(healthImpl);\n\n    ListenableFuture<HealthCheckResponse> health =\n        pluginService.checkHealthWithDeadline(request, DEADLINE_DEFAULT);\n\n    assertThat(health.isDone()).isTrue();\n    assertThat(health.get().getStatus()).isEqualTo(ServingStatus.SERVING);\n  }\n\n  @Test\n  public void checkHealth_returnNotServingHealthResponse() throws Exception {\n    HealthCheckRequest request = HealthCheckRequest.getDefaultInstance();\n\n    HealthImplBase healthImpl =\n        new HealthImplBase() {\n          @Override\n          public void check(\n              HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) {\n            responseObserver.onNext(\n                HealthCheckResponse.newBuilder().setStatus(ServingStatus.NOT_SERVING).build());\n            responseObserver.onCompleted();\n          }\n        };\n    serviceRegistry.addService(healthImpl);\n\n    ListenableFuture<HealthCheckResponse> health =\n        pluginService.checkHealthWithDeadline(request, DEADLINE_DEFAULT);\n\n    assertThat(health.isDone()).isTrue();\n    assertThat(health.get().getStatus()).isEqualTo(ServingStatus.NOT_SERVING);\n  }\n\n  private void assertRunResponseContainsAllRunRequestParameters(\n      RunResponse response, RunRequest request) throws Exception {\n    for (MatchedPlugin plugin : request.getPluginsList()) {\n      DetectionReport expectedReport =\n          DetectionReport.newBuilder()\n              .setTargetInfo(request.getTarget())\n              .setNetworkService(plugin.getServices(0))\n              .build();\n      assertThat(response.getReports().getDetectionReportsList()).contains(expectedReport);\n    }\n  }\n\n  private PluginDefinition createSinglePluginDefinitionWithName(String name) {\n    PluginInfo pluginInfo =\n        PluginInfo.newBuilder()\n            .setType(PluginInfo.PluginType.VULN_DETECTION)\n            .setName(name)\n            .setVersion(PLUGIN_VERSION)\n            .setDescription(PLUGIN_DESCRIPTION)\n            .setAuthor(PLUGIN_AUTHOR)\n            .build();\n    return PluginDefinition.newBuilder().setInfo(pluginInfo).build();\n  }\n\n  private RunRequest createSinglePluginRunRequest() {\n    PluginDefinition singlePlugin = createSinglePluginDefinitionWithName(PLUGIN_NAME);\n    NetworkService httpService =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n    TargetInfo target =\n        TargetInfo.newBuilder().addNetworkEndpoints(httpService.getNetworkEndpoint()).build();\n\n    return RunRequest.newBuilder()\n        .setTarget(target)\n        .addPlugins(MatchedPlugin.newBuilder().addServices(httpService).setPlugin(singlePlugin))\n        .build();\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/RemoteVulnDetectorImplTest.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.api.client.util.ExponentialBackOff;\nimport com.google.common.collect.ImmutableList;\nimport com.google.inject.AbstractModule;\nimport com.google.inject.Guice;\nimport com.google.tsunami.common.data.NetworkEndpointUtils;\nimport com.google.tsunami.proto.DetectionReport;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.ListPluginsRequest;\nimport com.google.tsunami.proto.ListPluginsResponse;\nimport com.google.tsunami.proto.MatchedPlugin;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.PluginDefinition;\nimport com.google.tsunami.proto.PluginInfo;\nimport com.google.tsunami.proto.PluginServiceGrpc.PluginServiceImplBase;\nimport com.google.tsunami.proto.RunCompactRequest;\nimport com.google.tsunami.proto.RunRequest;\nimport com.google.tsunami.proto.RunResponse;\nimport com.google.tsunami.proto.TargetInfo;\nimport com.google.tsunami.proto.TransportProtocol;\nimport io.grpc.health.v1.HealthCheckRequest;\nimport io.grpc.health.v1.HealthCheckResponse;\nimport io.grpc.health.v1.HealthCheckResponse.ServingStatus;\nimport io.grpc.health.v1.HealthGrpc.HealthImplBase;\nimport io.grpc.inprocess.InProcessChannelBuilder;\nimport io.grpc.inprocess.InProcessServerBuilder;\nimport io.grpc.stub.StreamObserver;\nimport io.grpc.testing.GrpcCleanupRule;\nimport io.grpc.util.MutableHandlerRegistry;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic final class RemoteVulnDetectorImplTest {\n\n  private static final String PLUGIN_VERSION = \"0.0.1\";\n  private static final String PLUGIN_DESCRIPTION = \"test description\";\n  private static final String PLUGIN_AUTHOR = \"tester\";\n  private static final int INITIAL_WAIT_TIME_MS = 20;\n  private static final int MAX_WAIT_TIME_MS = 30000;\n  private static final int WAIT_TIME_MULTIPLIER = 3;\n  private static final int MAX_ATTEMPTS = 3;\n  private static final ExponentialBackOff BACKOFF =\n      new ExponentialBackOff.Builder()\n          .setInitialIntervalMillis(INITIAL_WAIT_TIME_MS)\n          .setRandomizationFactor(0.1)\n          .setMultiplier(WAIT_TIME_MULTIPLIER)\n          .setMaxElapsedTimeMillis(MAX_WAIT_TIME_MS)\n          .build();\n\n  private final MutableHandlerRegistry serviceRegistry = new MutableHandlerRegistry();\n\n  @Rule public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();\n\n  @Before\n  public void setUp() throws Exception {\n    serviceRegistry.addService(\n        new PluginServiceImplBase() {\n          @Override\n          public void run(RunRequest request, StreamObserver<RunResponse> responseObserver) {\n            DetectionReportList.Builder reportListBuilder = DetectionReportList.newBuilder();\n            for (MatchedPlugin plugin : request.getPluginsList()) {\n              reportListBuilder.addDetectionReports(\n                  DetectionReport.newBuilder()\n                      .setTargetInfo(request.getTarget())\n                      .setNetworkService(plugin.getServices(0)));\n            }\n            responseObserver.onNext(RunResponse.newBuilder().setReports(reportListBuilder).build());\n            responseObserver.onCompleted();\n          }\n        });\n  }\n\n  @Test\n  public void detect_withServingServer_returnsSuccessfulDetectionReportList() throws Exception {\n    registerHealthCheckWithStatus(ServingStatus.SERVING);\n    registerSuccessfulRunService();\n  \n    RemoteVulnDetector pluginToTest = getNewRemoteVulnDetectorInstance();\n    var endpointToTest = NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80);\n    var serviceToTest =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(endpointToTest)\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n  \n    TargetInfo testTarget = TargetInfo.newBuilder().addNetworkEndpoints(endpointToTest).build();\n    pluginToTest.addMatchedPluginToDetect(\n        MatchedPlugin.newBuilder()\n            .addServices(serviceToTest)\n            .setPlugin(createSinglePluginDefinitionWithName(\"test\"))\n            .build());\n    assertThat(pluginToTest.detect(testTarget, ImmutableList.of()).getDetectionReportsList())\n        .comparingExpectedFieldsOnly()\n        .containsExactly(\n            DetectionReport.newBuilder()\n                .setTargetInfo(testTarget)\n                .setNetworkService(serviceToTest)\n                .build());\n  }\n\n  @Test\n  public void detect_withNonServingServer_returnsEmptyDetectionReportList() throws Exception {\n    registerHealthCheckWithStatus(ServingStatus.NOT_SERVING);\n  \n    RemoteVulnDetector pluginToTest = getNewRemoteVulnDetectorInstance();\n    var endpointToTest = NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80);\n    var serviceToTest =\n        NetworkService.newBuilder()\n            .setNetworkEndpoint(endpointToTest)\n            .setTransportProtocol(TransportProtocol.TCP)\n            .setServiceName(\"http\")\n            .build();\n  \n    TargetInfo testTarget = TargetInfo.newBuilder().addNetworkEndpoints(endpointToTest).build();\n    pluginToTest.addMatchedPluginToDetect(\n        MatchedPlugin.newBuilder()\n            .addServices(serviceToTest)\n            .setPlugin(createSinglePluginDefinitionWithName(\"test\"))\n            .build());\n    assertThat(pluginToTest.detect(testTarget, ImmutableList.of()).getDetectionReportsList())\n        .isEmpty();\n  }\n\n  @Test(timeout = 30000L)\n  public void detect_withRpcError_throwsLanguageServerException() throws Exception {\n    registerHealthCheckWithError();\n\n    assertThrows(\n        LanguageServerException.class,\n        () ->\n            getNewRemoteVulnDetectorInstance()\n                .detect(TargetInfo.getDefaultInstance(), ImmutableList.of()));\n  }\n\n  @Test\n  public void getAllPlugins_withServingServer_returnsSuccessfulList() throws Exception {\n    registerHealthCheckWithStatus(ServingStatus.SERVING);\n  \n    var plugin = createSinglePluginDefinitionWithName(\"test\");\n    RemoteVulnDetector pluginToTest = getNewRemoteVulnDetectorInstance();\n    serviceRegistry.addService(\n        new PluginServiceImplBase() {\n          @Override\n          public void listPlugins(\n              ListPluginsRequest request, StreamObserver<ListPluginsResponse> responseObserver) {\n            responseObserver.onNext(ListPluginsResponse.newBuilder().addPlugins(plugin).build());\n            responseObserver.onCompleted();\n          }\n        });\n\n    assertThat(pluginToTest.getAllPlugins()).containsExactly(plugin);\n  }\n\n  @Test\n  public void getAllPlugins_withCompactRunRequest_callsRunCompact() throws Exception {\n    registerHealthCheckWithStatus(ServingStatus.SERVING);\n\n    var targetInfo =\n        TargetInfo.newBuilder()\n            .addNetworkEndpoints(NetworkEndpointUtils.forIpAndPort(\"1.1.1.1\", 80))\n            .build();\n    var someNetworkService = NetworkService.getDefaultInstance();\n    var expectedDetectionReport =\n        DetectionReport.newBuilder()\n            .setTargetInfo(targetInfo)\n            .setNetworkService(someNetworkService)\n            .build();\n\n    var plugin = createSinglePluginDefinitionWithName(\"test\");\n    RemoteVulnDetector pluginToTest = getNewRemoteVulnDetectorInstance();\n    serviceRegistry.addService(\n        new PluginServiceImplBase() {\n          @Override\n          public void listPlugins(\n              ListPluginsRequest request, StreamObserver<ListPluginsResponse> responseObserver) {\n            responseObserver.onNext(\n                ListPluginsResponse.newBuilder()\n                    .setWantCompactRunRequest(true)\n                    .addPlugins(plugin)\n                    .build());\n            responseObserver.onCompleted();\n          }\n\n          @Override\n          public void run(RunRequest request, StreamObserver<RunResponse> responseObserver) {\n            responseObserver.onError(new Exception(\"run should not be called\"));\n          }\n\n          @Override\n          public void runCompact(\n              RunCompactRequest request, StreamObserver<RunResponse> responseObserver) {\n            responseObserver.onNext(\n                RunResponse.newBuilder()\n                    .setReports(\n                        DetectionReportList.newBuilder()\n                            .addDetectionReports(expectedDetectionReport))\n                    .build());\n            responseObserver.onCompleted();\n          }\n        });\n\n    assertThat(pluginToTest.getAllPlugins()).containsExactly(plugin);\n    assertThat(\n            pluginToTest\n                .detect(targetInfo, ImmutableList.of(someNetworkService))\n                .getDetectionReportsList())\n        .containsExactly(expectedDetectionReport);\n  }\n\n  @Test\n  public void getAllPlugins_withNonServingServer_returnsEmptyList() throws Exception {\n    registerHealthCheckWithStatus(ServingStatus.NOT_SERVING);\n    assertThat(getNewRemoteVulnDetectorInstance().getAllPlugins()).isEmpty();\n  }\n\n  @Test(timeout = 30000L)\n  public void getAllPlugins_withRpcError_throwsLanguageServerException() throws Exception {\n    registerHealthCheckWithError();\n    assertThrows(LanguageServerException.class, getNewRemoteVulnDetectorInstance()::getAllPlugins);\n  }\n\n  @Test(timeout = 30000L)\n  public void getAllPlugins_withUnregisteredHealthService_throwsLanguageServerException()\n      throws Exception {\n    assertThrows(LanguageServerException.class, getNewRemoteVulnDetectorInstance()::getAllPlugins);\n  }\n\n  private RemoteVulnDetector getNewRemoteVulnDetectorInstance() throws Exception {\n    String serverName = InProcessServerBuilder.generateName();\n    grpcCleanup.register(\n        InProcessServerBuilder.forName(serverName)\n            .fallbackHandlerRegistry(serviceRegistry)\n            .directExecutor()\n            .build()\n            .start());\n\n    return Guice.createInjector(\n            new AbstractModule() {\n              @Override\n              protected void configure() {\n                bind(RemoteVulnDetector.class)\n                    .toInstance(\n                        new RemoteVulnDetectorImpl(\n                            InProcessChannelBuilder.forName(serverName).directExecutor().build(),\n                            BACKOFF,\n                            MAX_ATTEMPTS,\n                            null));\n              }\n            })\n        .getInstance(RemoteVulnDetector.class);\n  }\n\n  private void registerHealthCheckWithError() {\n    serviceRegistry.addService(\n        new HealthImplBase() {\n          @Override\n          public void check(\n              HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) {\n            responseObserver.onError(new RuntimeException(\"Test failure.\"));\n            responseObserver.onCompleted();\n          }\n        });\n  }\n\n  private void registerHealthCheckWithStatus(ServingStatus status) {\n    serviceRegistry.addService(\n        new HealthImplBase() {\n          @Override\n          public void check(\n              HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) {\n            responseObserver.onNext(HealthCheckResponse.newBuilder().setStatus(status).build());\n            responseObserver.onCompleted();\n          }\n        });\n  }\n\n  private void registerSuccessfulRunService() {\n    serviceRegistry.addService(\n        new PluginServiceImplBase() {\n          @Override\n          public void run(RunRequest request, StreamObserver<RunResponse> responseObserver) {\n            DetectionReportList.Builder reportListBuilder = DetectionReportList.newBuilder();\n            for (MatchedPlugin plugin : request.getPluginsList()) {\n              reportListBuilder.addDetectionReports(\n                  DetectionReport.newBuilder()\n                      .setTargetInfo(request.getTarget())\n                      .setNetworkService(plugin.getServices(0)));\n            }\n            responseObserver.onNext(RunResponse.newBuilder().setReports(reportListBuilder).build());\n            responseObserver.onCompleted();\n          }\n        });\n  }\n\n  private PluginDefinition createSinglePluginDefinitionWithName(String name) {\n    return PluginDefinition.newBuilder()\n        .setInfo(\n            PluginInfo.newBuilder()\n                .setType(PluginInfo.PluginType.VULN_DETECTION)\n                .setName(name)\n                .setVersion(PLUGIN_VERSION)\n                .setDescription(PLUGIN_DESCRIPTION)\n                .setAuthor(PLUGIN_AUTHOR))\n        .build();\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/RemoteVulnDetectorLoadingModuleTest.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.inject.Guice;\nimport com.google.inject.Key;\nimport com.google.inject.util.Types;\nimport com.google.tsunami.common.server.LanguageServerCommand;\nimport io.grpc.inprocess.InProcessServerBuilder;\nimport java.time.Duration;\nimport java.util.Map;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n@RunWith(JUnit4.class)\npublic final class RemoteVulnDetectorLoadingModuleTest {\n\n  @SuppressWarnings(\"unchecked\")\n  private static final Key<Map<PluginDefinition, TsunamiPlugin>> PLUGIN_BINDING_KEY =\n      (Key<Map<PluginDefinition, TsunamiPlugin>>)\n          Key.get(Types.mapOf(PluginDefinition.class, TsunamiPlugin.class));\n\n  private static String generateServerName() {\n    return InProcessServerBuilder.generateName();\n  }\n\n  @Test\n  public void configure_whenNoChannelsRegistered_loadsNoRemotePlugins() {\n    Map<PluginDefinition, TsunamiPlugin> remotePlugins =\n        Guice.createInjector(new RemoteVulnDetectorLoadingModule(ImmutableList.of()))\n            .getInstance(PLUGIN_BINDING_KEY);\n\n    assertThat(remotePlugins).isEmpty();\n  }\n\n  @Test\n  public void configure_always_loadsAllRemotePlugins() {\n    var path0 =\n        LanguageServerCommand.create(\n            generateServerName(),\n            \"\",\n            \"34567\",\n            \"193\",\n            \"/output/here\",\n            false,\n            Duration.ofSeconds(10),\n            \"157.34.0.2\",\n            8080,\n            \"157.34.0.2:8881\",\n            0);\n    var path1 =\n        LanguageServerCommand.create(\n            generateServerName(),\n            \"\",\n            \"34566\",\n            \"193\",\n            \"/output/now\",\n            false,\n            Duration.ofSeconds(10),\n            \"157.34.0.2\",\n            8080,\n            \"157.34.0.2:8881\",\n            0);\n    var server0 =\n        LanguageServerCommand.create(\n            \"\",\n            \"127.0.0.1\",\n            \"34567\",\n            \"193\",\n            \"/output/here\",\n            false,\n            Duration.ofSeconds(10),\n            \"157.34.0.2\",\n            8080,\n            \"157.34.0.2:8881\",\n            0);\n    Map<PluginDefinition, TsunamiPlugin> remotePlugins =\n        Guice.createInjector(\n                new RemoteVulnDetectorLoadingModule(ImmutableList.of(path0, path1, server0)))\n            .getInstance(PLUGIN_BINDING_KEY);\n\n    assertThat(remotePlugins).hasSize(3);\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/TcsClientTest.java",
    "content": "/*\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.google.tsunami.plugin;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.inject.Guice;\nimport com.google.protobuf.util.JsonFormat;\nimport com.google.tsunami.callbackserver.proto.PollingResult;\nimport com.google.tsunami.common.net.http.HttpClient;\nimport com.google.tsunami.common.net.http.HttpClientModule;\nimport com.google.tsunami.common.net.http.HttpStatus;\nimport java.io.IOException;\nimport javax.inject.Inject;\nimport okhttp3.mockwebserver.MockResponse;\nimport okhttp3.mockwebserver.MockWebServer;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link TcsClient}. */\n@RunWith(JUnit4.class)\npublic final class TcsClientTest {\n  private static final String SECRET = \"a3d9ed89deadbeef\";\n  private static final String CBID = \"04041e8898e739ca33a250923e24f59ca41a8373f8cf6a45a1275f3b\";\n  private static final String VALID_IPV4_ADDRESS = \"127.0.0.1\";\n  private static final String VALID_IPV6_ADDRESS = \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\";\n  private static final int VALID_PORT = 8000;\n  private static final String VALID_DOMAIN = \"valid.co\";\n  private static final String VALID_URL = \"http://valid.co\";\n  private static final String INVALID_ADDRESS = \"http://invalid-address.com\";\n\n  @Inject private HttpClient httpClient;\n\n  private TcsClient client;\n\n  @Before\n  public void setup() {\n    Guice.createInjector(new HttpClientModule.Builder().build()).injectMembers(this);\n  }\n\n  @Test\n  public void getCallbackUri_validIpv4Address_returnsUriWithCbidInPath() {\n    client = new TcsClient(VALID_IPV4_ADDRESS, VALID_PORT, VALID_URL, httpClient);\n\n    String url = client.getCallbackUri(SECRET);\n\n    String expectedUriString =\n        String.format(\"http://%s:%d/%s\", VALID_IPV4_ADDRESS, VALID_PORT, CBID);\n    assertThat(url).isEqualTo(expectedUriString);\n  }\n\n  @Test\n  public void getCallbackUri_validIpv6Address_returnsUriWithCbidInPath() {\n    client = new TcsClient(VALID_IPV6_ADDRESS, VALID_PORT, VALID_URL, httpClient);\n\n    String url = client.getCallbackUri(SECRET);\n\n    String expectedUriString =\n        String.format(\"http://[%s]:%d/%s\", VALID_IPV6_ADDRESS, VALID_PORT, CBID);\n    assertThat(url).isEqualTo(expectedUriString);\n  }\n\n  @Test\n  public void getCallbackUri_validDomainAddress_returnsUriWithCbidInSubdomain() {\n    client = new TcsClient(VALID_DOMAIN, VALID_PORT, VALID_URL, httpClient);\n\n    String url = client.getCallbackUri(SECRET);\n\n    String expectedUriString = String.format(\"%s.%s:%d\", CBID, VALID_DOMAIN, VALID_PORT);\n    assertThat(url).isEqualTo(expectedUriString);\n  }\n\n  @Test\n  public void getCallbackUri_callbackPortIs80_returnsUriWithoutPortNum() {\n    client = new TcsClient(VALID_IPV4_ADDRESS, 80, VALID_URL, httpClient);\n\n    String url = client.getCallbackUri(SECRET);\n\n    String expectedUriString = String.format(\"http://%s/%s\", VALID_IPV4_ADDRESS, CBID);\n    assertThat(url).isEqualTo(expectedUriString);\n  }\n\n  @Test\n  public void getCallbackUri_invalidAddress_throwsError() {\n    client = new TcsClient(INVALID_ADDRESS, VALID_PORT, VALID_URL, httpClient);\n\n    assertThrows(IllegalArgumentException.class, () -> client.getCallbackUri(SECRET));\n  }\n\n  @Test\n  public void getCallbackUri_invalidCallbackPort_throwsError() {\n    client = new TcsClient(VALID_DOMAIN, 100000, VALID_URL, httpClient);\n    assertThrows(AssertionError.class, () -> client.getCallbackUri(SECRET));\n  }\n\n  @Test\n  public void getCallbackAddress_validIpv4Address_returnsAddress() {\n    client = new TcsClient(VALID_IPV4_ADDRESS, VALID_PORT, VALID_URL, httpClient);\n    assertThat(client.getCallbackAddress()).isEqualTo(VALID_IPV4_ADDRESS);\n  }\n\n  @Test\n  public void getCallbackAddress_validIpv6Address_returnsAddress() {\n    client = new TcsClient(VALID_IPV6_ADDRESS, VALID_PORT, VALID_URL, httpClient);\n    assertThat(client.getCallbackAddress()).isEqualTo(VALID_IPV6_ADDRESS);\n  }\n\n  @Test\n  public void getCallbackAddress_validDomainAddress_returnsAddress() {\n    client = new TcsClient(VALID_DOMAIN, VALID_PORT, VALID_URL, httpClient);\n    assertThat(client.getCallbackAddress()).isEqualTo(VALID_DOMAIN);\n  }\n\n  @Test\n  public void getCallbackAddress_invalidAddress_throwsError() {\n    client = new TcsClient(INVALID_ADDRESS, 100000, VALID_URL, httpClient);\n    assertThrows(AssertionError.class, () -> client.getCallbackAddress());\n  }\n\n  @Test\n  public void getCallbackPort_validPort_returnsPort() {\n    client = new TcsClient(VALID_DOMAIN, VALID_PORT, VALID_URL, httpClient);\n    assertThat(client.getCallbackPort()).isEqualTo(VALID_PORT);\n  }\n\n  @Test\n  public void getCallbackPort_invalidPort_throwsError() {\n    client = new TcsClient(VALID_DOMAIN, 100000, VALID_URL, httpClient);\n    assertThrows(AssertionError.class, () -> client.getCallbackPort());\n  }\n\n  @Test\n  public void isVulnerable_sendsValidPollingRequest() throws IOException, InterruptedException {\n    MockWebServer mockWebServer = new MockWebServer();\n    mockWebServer.start();\n    client = new TcsClient(VALID_DOMAIN, VALID_PORT, mockWebServer.url(\"/\").toString(), httpClient);\n\n    client.hasOobLog(SECRET);\n\n    assertThat(mockWebServer.takeRequest().getPath())\n        .isEqualTo(String.format(\"/?secret=%s\", SECRET));\n    mockWebServer.shutdown();\n  }\n\n  @Test\n  public void isVulnerable_validLogRecordWithHttpLogged_returnsTrue() throws IOException {\n    PollingResult log = PollingResult.newBuilder().setHasHttpInteraction(true).build();\n    String body = JsonFormat.printer().preservingProtoFieldNames().print(log);\n    MockWebServer mockWebServer = new MockWebServer();\n    mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(body));\n    client = new TcsClient(VALID_DOMAIN, VALID_PORT, mockWebServer.url(\"/\").toString(), httpClient);\n\n    boolean detectionResult = client.hasOobLog(SECRET);\n\n    assertThat(detectionResult).isTrue();\n    mockWebServer.shutdown();\n  }\n\n  @Test\n  public void isVulnerable_validLogRecordWithNothingLogged_returnsFalse() throws IOException {\n    PollingResult log = PollingResult.getDefaultInstance();\n    String body = JsonFormat.printer().preservingProtoFieldNames().print(log);\n    MockWebServer mockWebServer = new MockWebServer();\n    mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(body));\n    client = new TcsClient(VALID_DOMAIN, VALID_PORT, mockWebServer.url(\"/\").toString(), httpClient);\n\n    boolean detectionResult = client.hasOobLog(SECRET);\n\n    assertThat(detectionResult).isFalse();\n    mockWebServer.shutdown();\n  }\n\n  @Test\n  public void isVulnerable_noLogRecordFetched_returnsFalse() throws IOException {\n    MockWebServer mockWebServer = new MockWebServer();\n    mockWebServer.enqueue(\n        new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code()).setBody(\"\"));\n    client = new TcsClient(VALID_DOMAIN, VALID_PORT, mockWebServer.url(\"/\").toString(), httpClient);\n\n    boolean detectionResult = client.hasOobLog(SECRET);\n\n    assertThat(detectionResult).isFalse();\n    mockWebServer.shutdown();\n  }\n\n  @Test\n  public void isVulnerable_requestFailed_returnsFalse() {\n    client = new TcsClient(VALID_DOMAIN, VALID_PORT, \"http://unknownhost/path\", httpClient);\n\n    boolean detectionResult = client.hasOobLog(SECRET);\n\n    assertThat(detectionResult).isFalse();\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/payload/PayloadGeneratorModuleTest.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.assertTrue;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.inject.Guice;\nimport com.google.protobuf.BoolValue;\nimport com.google.protobuf.StringValue;\nimport com.google.tsunami.common.net.http.HttpClient;\nimport com.google.tsunami.common.net.http.HttpClientModule;\nimport com.google.tsunami.plugin.TcsClient;\nimport com.google.tsunami.plugin.TcsClientCliOptions;\nimport com.google.tsunami.plugin.TcsConfigProperties;\nimport com.google.tsunami.proto.PayloadDefinition;\nimport com.google.tsunami.proto.PayloadGeneratorConfig.ExecutionEnvironment;\nimport com.google.tsunami.proto.PayloadGeneratorConfig.InterpretationEnvironment;\nimport com.google.tsunami.proto.PayloadGeneratorConfig.VulnerabilityType;\nimport com.google.tsunami.proto.PayloadValidationType;\nimport java.io.IOException;\nimport java.security.SecureRandom;\nimport java.util.Arrays;\nimport java.util.List;\nimport javax.inject.Inject;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.Parameterized;\nimport org.junit.runners.Parameterized.Parameter;\nimport org.junit.runners.Parameterized.Parameters;\n\n/** Tests for {@link PayloadGeneratorModule} */\n@RunWith(Parameterized.class)\npublic final class PayloadGeneratorModuleTest {\n  private static final String DOMAIN_1 = \"mydomain1.com\";\n  private static final Integer PORT_1 = 1111;\n  private static final String POLLING_URI_1 = String.format(\"http://%s:%d\", DOMAIN_1, PORT_1);\n\n  private static final String DOMAIN_2 = \"http://mydomain2.com:2222\";\n  private static final Integer PORT_2 = 2222;\n  private static final String POLLING_URI_2 = String.format(\"http://%s:%d\", DOMAIN_2, PORT_2);\n\n  @Inject private HttpClient httpClient;\n\n  private TcsClientCliOptions cliOptions;\n  private TcsConfigProperties configProperties;\n\n  private PayloadGeneratorModule module;\n\n  private final PayloadDefinition.Builder goodCallbackDefinition =\n      PayloadDefinition.newBuilder()\n          .setName(StringValue.of(\"Test 1\"))\n          .setInterpretationEnvironment(InterpretationEnvironment.LINUX_SHELL)\n          .setExecutionEnvironment(ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .addVulnerabilityType(VulnerabilityType.REFLECTIVE_RCE)\n          .setUsesCallbackServer(BoolValue.of(true))\n          .setPayloadString(StringValue.of(\"curl $TSUNAMI_PAYLOAD_TOKEN_URL\"));\n\n  private final PayloadDefinition.Builder goodNoCallbackDefinition =\n      PayloadDefinition.newBuilder()\n          .setName(StringValue.of(\"Test 2\"))\n          .setInterpretationEnvironment(InterpretationEnvironment.LINUX_SHELL)\n          .setExecutionEnvironment(ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .addVulnerabilityType(VulnerabilityType.REFLECTIVE_RCE)\n          .setUsesCallbackServer(BoolValue.of(false))\n          .setPayloadString(StringValue.of(\"my payload\"))\n          .setValidationType(PayloadValidationType.VALIDATION_REGEX)\n          .setValidationRegex(StringValue.of(\"myregex\"));\n\n  @Before\n  public void setUp() {\n    Guice.createInjector(new HttpClientModule.Builder().build()).injectMembers(this);\n    this.module = new PayloadGeneratorModule(new SecureRandom());\n    cliOptions = new TcsClientCliOptions();\n    configProperties = new TcsConfigProperties();\n  }\n\n  @Test\n  public void providesTcsClient_withNoConfig_returnsInvalidTcsClient() {\n    TcsClient client = module.providesTcsClient(null, null, null, httpClient);\n\n    assertFalse(client.isCallbackServerEnabled());\n  }\n\n  @Test\n  public void providesTcsClient_withGoodConfig_returnsValidTcsClient() {\n    TcsClient client = module.providesTcsClient(DOMAIN_1, PORT_1, POLLING_URI_1, httpClient);\n\n    assertTrue(client.isCallbackServerEnabled());\n  }\n\n  @Test\n  public void providesTcsClient_withConfigPropertiesAndCliOptions_prioritizesCliOptions() {\n    configProperties.callbackAddress = DOMAIN_2;\n    configProperties.callbackPort = PORT_2;\n    configProperties.pollingUri = POLLING_URI_2;\n    cliOptions.callbackAddress = DOMAIN_1;\n    cliOptions.callbackPort = PORT_1;\n    cliOptions.pollingUri = POLLING_URI_1;\n\n    String callbackAddress = module.providesCallbackAddress(configProperties, cliOptions);\n    Integer callbackPort = module.providesCallbackPort(configProperties, cliOptions);\n    String pollingUri = module.providesCallbackPollingUri(configProperties, cliOptions);\n\n    assertThat(callbackAddress).isEqualTo(DOMAIN_1);\n    assertThat(callbackPort).isEqualTo(PORT_1);\n    assertThat(pollingUri).isEqualTo(POLLING_URI_1);\n  }\n\n  @Parameter(0)\n  public String callbackAddress;\n\n  @Parameter(1)\n  public Integer callbackPort;\n\n  @Parameter(2)\n  public String pollingUri;\n\n  @Parameter(3)\n  public Class<Throwable> exceptionClass;\n\n  @Parameters\n  public static List<Object[]> data() {\n    return Arrays.asList(\n        new Object[][] {\n          {null, 1, \"mydomain.com\", NullPointerException.class},\n          {\"mydomain.com\", null, \"mydomain.com\", NullPointerException.class},\n          {\"mydomain.com\", 1, null, NullPointerException.class},\n          {\"mydomain.com\", 0, \"mydomain.com\", IllegalArgumentException.class},\n          {\"a bad address\", 1, \"mydomain.com\", IllegalArgumentException.class},\n        });\n  }\n\n  @Test\n  public void providesTcsClient_withBadConfig_throwsException() {\n    assertThrows(\n        this.exceptionClass,\n        () ->\n            module.providesTcsClient(\n                this.callbackAddress, this.callbackPort, this.pollingUri, httpClient));\n  }\n\n  @Test\n  public void provideParsedPayloads_returnsSomePayloads() throws IOException {\n    ImmutableList<PayloadDefinition> payloads = module.provideParsedPayloads();\n\n    assertThat(payloads).isNotEmpty();\n  }\n\n  @Test\n  public void validatePayloads_withGoodPayloads_returnsPayloads() throws IOException {\n    PayloadDefinition p0 = goodCallbackDefinition.build();\n    PayloadDefinition p1 = goodNoCallbackDefinition.build();\n\n    ImmutableList<PayloadDefinition> payloads = module.validatePayloads(ImmutableList.of(p0, p1));\n\n    assertThat(payloads).containsExactly(p0, p1).inOrder();\n  }\n\n  @Test\n  public void validatePayloads_withoutInterpretationEnvironment_throwsException()\n      throws IOException {\n    PayloadDefinition p = goodCallbackDefinition.clearInterpretationEnvironment().build();\n\n    Throwable thrown =\n        assertThrows(\n            IllegalArgumentException.class, () -> module.validatePayloads(ImmutableList.of(p)));\n    assertThat(thrown).hasMessageThat().contains(\"interpretation_environment\");\n  }\n\n  @Test\n  public void validatePayloads_withoutExecutionEnvironment_throwsException() throws IOException {\n    PayloadDefinition p = goodCallbackDefinition.clearExecutionEnvironment().build();\n\n    Throwable thrown =\n        assertThrows(\n            IllegalArgumentException.class, () -> module.validatePayloads(ImmutableList.of(p)));\n    assertThat(thrown).hasMessageThat().contains(\"exeuction_environment\");\n  }\n\n  @Test\n  public void validatePayloads_withoutVulnerabilityType_throwsException() throws IOException {\n    PayloadDefinition p = goodCallbackDefinition.clearVulnerabilityType().build();\n\n    Throwable thrown =\n        assertThrows(\n            IllegalArgumentException.class, () -> module.validatePayloads(ImmutableList.of(p)));\n    assertThat(thrown).hasMessageThat().contains(\"vulnerability_type\");\n  }\n\n  @Test\n  public void validatePayloads_withoutPayloadString_throwsException() throws IOException {\n    PayloadDefinition p = goodCallbackDefinition.clearPayloadString().build();\n\n    Throwable thrown =\n        assertThrows(\n            IllegalArgumentException.class, () -> module.validatePayloads(ImmutableList.of(p)));\n    assertThat(thrown).hasMessageThat().contains(\"payload_string\");\n  }\n\n  @Test\n  public void validatePayloads_withCallbackPayloadWithoutUrlToken_throwsException()\n      throws IOException {\n    PayloadDefinition p =\n        goodCallbackDefinition.setPayloadString(StringValue.of(\"my payload\")).build();\n\n    Throwable thrown =\n        assertThrows(\n            IllegalArgumentException.class, () -> module.validatePayloads(ImmutableList.of(p)));\n    assertThat(thrown).hasMessageThat().contains(\"$TSUNAMI_PAYLOAD_TOKEN_URL\");\n  }\n\n  @Test\n  public void validatePayloads_withNoCallbackPayloadWithoutValidationType_throwsException()\n      throws IOException {\n    PayloadDefinition p = goodNoCallbackDefinition.clearValidationType().build();\n\n    Throwable thrown =\n        assertThrows(\n            IllegalArgumentException.class, () -> module.validatePayloads(ImmutableList.of(p)));\n    assertThat(thrown).hasMessageThat().contains(\"validation_type\");\n  }\n\n  @Test\n  public void validatePayloads_withRegexValidationWithoutValidationRegex_throwsException()\n      throws IOException {\n    PayloadDefinition p = goodNoCallbackDefinition.clearValidationRegex().build();\n\n    Throwable thrown =\n        assertThrows(\n            IllegalArgumentException.class, () -> module.validatePayloads(ImmutableList.of(p)));\n    assertThat(thrown).hasMessageThat().contains(\"validation_regex\");\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/payload/PayloadGeneratorWithCallbackServerTest.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.assertTrue;\n\nimport com.google.inject.Guice;\nimport com.google.tsunami.common.net.http.HttpClientModule;\nimport com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule;\nimport com.google.tsunami.plugin.payload.testing.PayloadTestHelper;\nimport com.google.tsunami.proto.PayloadGeneratorConfig;\nimport java.io.IOException;\nimport java.security.SecureRandom;\nimport java.util.Arrays;\nimport javax.inject.Inject;\nimport okhttp3.mockwebserver.MockWebServer;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link PayloadGenerator} for cases which utilize the callback server. */\n@RunWith(JUnit4.class)\npublic final class PayloadGeneratorWithCallbackServerTest {\n\n  @Inject private PayloadGenerator payloadGenerator;\n\n  private MockWebServer mockCallbackServer;\n  private final SecureRandom testSecureRandom =\n      new SecureRandom() {\n        @Override\n        public void nextBytes(byte[] bytes) {\n          Arrays.fill(bytes, (byte) 0xFF);\n        }\n      };\n  private static final PayloadGeneratorConfig LINUX_REFLECTIVE_RCE_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE)\n          .setInterpretationEnvironment(\n              PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL)\n          .setExecutionEnvironment(\n              PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .build();\n  private static final PayloadGeneratorConfig LINUX_ARBITRARY_FILE_WRITE_CRON_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.ARBITRARY_FILE_WRITE)\n          .setInterpretationEnvironment(\n              PayloadGeneratorConfig.InterpretationEnvironment.LINUX_ROOT_CRONTAB)\n          .setExecutionEnvironment(\n              PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .build();\n  private static final PayloadGeneratorConfig LINUX_BLIND_RCE_FILE_READ_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.BLIND_RCE_FILE_READ)\n          .setInterpretationEnvironment(\n              PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL)\n          .setExecutionEnvironment(\n              PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .build();\n  private static final PayloadGeneratorConfig WINDOWS_REFLECTIVE_RCE_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE)\n          .setInterpretationEnvironment(\n              PayloadGeneratorConfig.InterpretationEnvironment.WINDOWS_SHELL)\n          .setExecutionEnvironment(\n              PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .build();\n  private static final PayloadGeneratorConfig ANY_SSRF_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.SSRF)\n          .setInterpretationEnvironment(\n              PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY)\n          .setExecutionEnvironment(PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY)\n          .build();\n  private static final String CORRECT_PRINTF =\n      \"printf %s%s%s TSUNAMI_PAYLOAD_START ffffffffffffffff TSUNAMI_PAYLOAD_END\";\n  private static final String CORRECT_CURL_TRACE =\n      \"curl --trace /tmp/tsunami-rce -- tsunami-rce-ffffffffffffffff\";\n  private static final String CORRECT_WINDOWS_ECHO =\n      \"powershell -Command \\\"echo TSUNAMI_PAYLOAD_START$(echo\"\n          + \" ffffffffffffffff)TSUNAMI_PAYLOAD_END\\\"\";\n\n  @Before\n  public void setUp() throws IOException {\n    mockCallbackServer = new MockWebServer();\n    mockCallbackServer.start();\n    Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            FakePayloadGeneratorModule.builder()\n                .setCallbackServer(mockCallbackServer)\n                .setSecureRng(testSecureRandom)\n                .build())\n        .injectMembers(this);\n  }\n\n  @Test\n  public void isCallbackServerEnabled_returnsTrue() {\n    assertTrue(payloadGenerator.isCallbackServerEnabled());\n  }\n\n  @Test\n  public void generate_withLinuxConfiguration_returnsCurlPayload() {\n    Payload payload = payloadGenerator.generate(LINUX_REFLECTIVE_RCE_CONFIG);\n\n    assertThat(payload.getPayload()).contains(\"curl\");\n    assertThat(payload.getPayload()).contains(mockCallbackServer.getHostName());\n    assertThat(payload.getPayload()).contains(Integer.toString(mockCallbackServer.getPort(), 10));\n    assertTrue(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void generate_withLinuxConfiguration_returnsPrintfPayload() {\n    Payload payload = payloadGenerator.generateNoCallback(LINUX_REFLECTIVE_RCE_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(CORRECT_PRINTF);\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void checkIfExecuted_withLinuxConfiguration_andExecutedCallbackUrl_returnsTrue()\n      throws IOException {\n\n    mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse());\n    Payload payload = payloadGenerator.generate(LINUX_REFLECTIVE_RCE_CONFIG);\n\n    assertTrue(payload.checkIfExecuted());\n  }\n\n  @Test\n  public void checkIfExecuted_withLinuxConfiguration_andNotExecutedCallbackUrl_returnsFalse() {\n\n    mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse());\n    Payload payload = payloadGenerator.generate(LINUX_REFLECTIVE_RCE_CONFIG);\n\n    assertFalse(payload.checkIfExecuted());\n  }\n\n  @Test\n  public void generate_withCrontabConfiguration_returnsCronCurlPayload() {\n    Payload payload = payloadGenerator.generate(LINUX_ARBITRARY_FILE_WRITE_CRON_CONFIG);\n\n    assertThat(payload.getPayload()).contains(\"* * * * * root curl\");\n    assertThat(payload.getPayload()).contains(mockCallbackServer.getHostName());\n    assertThat(payload.getPayload()).contains(Integer.toString(mockCallbackServer.getPort(), 10));\n    assertTrue(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void checkIfExecuted_withCrontabConfiguration_andExecutedCallbackUrl_returnsTrue()\n      throws IOException {\n\n    mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse());\n    Payload payload = payloadGenerator.generate(LINUX_ARBITRARY_FILE_WRITE_CRON_CONFIG);\n\n    assertTrue(payload.checkIfExecuted());\n  }\n\n  @Test\n  public void checkIfExecuted_withCrontabConfiguration_andNotExecutedCallbackUrl_returnsFalse() {\n\n    mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse());\n    Payload payload = payloadGenerator.generate(LINUX_ARBITRARY_FILE_WRITE_CRON_CONFIG);\n\n    assertFalse(payload.checkIfExecuted());\n  }\n\n  @Test\n  public void generate_withCurlTraceConfiguration_returnsCurlTracePayload() {\n    Payload payload = payloadGenerator.generateNoCallback(LINUX_BLIND_RCE_FILE_READ_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(CORRECT_CURL_TRACE);\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void generate_withWindowsConfiguration_returnsEchoPayload() {\n    Payload payload = payloadGenerator.generateNoCallback(WINDOWS_REFLECTIVE_RCE_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(CORRECT_WINDOWS_ECHO);\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void checkIfExecuted_withWindowsConfiguration_andExecutedCallbackUrl_returnsTrue()\n      throws IOException {\n\n    mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse());\n    Payload payload = payloadGenerator.generate(WINDOWS_REFLECTIVE_RCE_CONFIG);\n\n    assertTrue(payload.checkIfExecuted());\n  }\n\n  @Test\n  public void checkIfExecuted_withWindowsConfiguration_andNotExecutedCallbackUrl_returnsFalse() {\n\n    mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse());\n    Payload payload = payloadGenerator.generate(WINDOWS_REFLECTIVE_RCE_CONFIG);\n\n    assertFalse(payload.checkIfExecuted());\n  }\n\n  @Test\n  public void getPayload_withSsrfConfiguration_returnsCallbackUrl() {\n    Payload payload = payloadGenerator.generate(ANY_SSRF_CONFIG);\n\n    assertTrue(payload.getPayloadAttributes().getUsesCallbackServer());\n    assertThat(payload.getPayload()).contains(mockCallbackServer.getHostName());\n    assertThat(payload.getPayload()).contains(Integer.toString(mockCallbackServer.getPort(), 10));\n  }\n\n  @Test\n  public void checkIfExecuted_withSsrfConfiguration_andExecutedUrl_returnsTrue()\n      throws IOException {\n    mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse());\n    Payload payload = payloadGenerator.generate(ANY_SSRF_CONFIG);\n\n    assertTrue(payload.checkIfExecuted());\n  }\n\n  @Test\n  public void getPayload_withSsrfConfiguration_andNotExecutedUrl_returnsFalse() {\n    mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse());\n    Payload payload = payloadGenerator.generate(ANY_SSRF_CONFIG);\n\n    assertFalse(payload.checkIfExecuted());\n  }\n\n  @Test\n  public void generate_withoutVulnerabilityType_throwsNotImplementedException() {\n    assertThrows(\n        NotImplementedException.class,\n        () ->\n            payloadGenerator.generate(\n                PayloadGeneratorConfig.newBuilder()\n                    .setInterpretationEnvironment(\n                        PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL)\n                    .setExecutionEnvironment(\n                        PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n                    .build()));\n  }\n\n  @Test\n  public void generate_withoutInterpretationEnvironment_throwsNotImplementedException() {\n    assertThrows(\n        NotImplementedException.class,\n        () ->\n            payloadGenerator.generate(\n                PayloadGeneratorConfig.newBuilder()\n                    .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE)\n                    .setExecutionEnvironment(\n                        PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n                    .build()));\n  }\n\n  @Test\n  public void generate_withoutExecutionEnvironment_throwsNotImplementedException() {\n    assertThrows(\n        NotImplementedException.class,\n        () ->\n            payloadGenerator.generate(\n                PayloadGeneratorConfig.newBuilder()\n                    .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE)\n                    .setInterpretationEnvironment(\n                        PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL)\n                    .build()));\n  }\n\n  @Test\n  public void generate_withoutConfig_throwsNotImplementedException() {\n    assertThrows(\n        NotImplementedException.class,\n        () -> payloadGenerator.generate(PayloadGeneratorConfig.getDefaultInstance()));\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/payload/PayloadGeneratorWithoutCallbackServerTest.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.assertTrue;\n\nimport com.google.inject.Guice;\nimport com.google.protobuf.ByteString;\nimport com.google.tsunami.common.net.http.HttpClientModule;\nimport com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule;\nimport com.google.tsunami.proto.PayloadGeneratorConfig;\nimport java.security.SecureRandom;\nimport java.util.Arrays;\nimport javax.inject.Inject;\nimport okhttp3.mockwebserver.MockWebServer;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link PayloadGenerator} for cases which do not utilize the callback server. */\n@RunWith(JUnit4.class)\npublic final class PayloadGeneratorWithoutCallbackServerTest {\n\n  @Inject private PayloadGenerator payloadGenerator;\n\n  private MockWebServer mockCallbackServer;\n  private final SecureRandom testSecureRandom =\n      new SecureRandom() {\n        @Override\n        public void nextBytes(byte[] bytes) {\n          Arrays.fill(bytes, (byte) 0xFF);\n        }\n      };\n  private static final PayloadGeneratorConfig LINUX_REFLECTIVE_RCE_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE)\n          .setInterpretationEnvironment(\n              PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL)\n          .setExecutionEnvironment(\n              PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .build();\n  private static final PayloadGeneratorConfig LINUX_ARBITRARY_FILE_WRITE_CRON_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.ARBITRARY_FILE_WRITE)\n          .setInterpretationEnvironment(\n              PayloadGeneratorConfig.InterpretationEnvironment.LINUX_ROOT_CRONTAB)\n          .setExecutionEnvironment(\n              PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .build();\n  private static final PayloadGeneratorConfig LINUX_BLIND_RCE_FILE_READ_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.BLIND_RCE_FILE_READ)\n          .setInterpretationEnvironment(\n              PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL)\n          .setExecutionEnvironment(\n              PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .build();\n  private static final PayloadGeneratorConfig JAVA_REFLECTIVE_RCE_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE)\n          .setInterpretationEnvironment(PayloadGeneratorConfig.InterpretationEnvironment.JAVA)\n          .setExecutionEnvironment(\n              PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .build();\n  private static final PayloadGeneratorConfig JSP_REFLECTIVE_RCE_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE)\n          .setInterpretationEnvironment(PayloadGeneratorConfig.InterpretationEnvironment.JSP)\n          .setExecutionEnvironment(\n              PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .build();\n  private static final PayloadGeneratorConfig WINDOWS_REFLECTIVE_RCE_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE)\n          .setInterpretationEnvironment(\n              PayloadGeneratorConfig.InterpretationEnvironment.WINDOWS_SHELL)\n          .setExecutionEnvironment(\n              PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n          .build();\n  private static final PayloadGeneratorConfig ANY_SSRF_CONFIG =\n      PayloadGeneratorConfig.newBuilder()\n          .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.SSRF)\n          .setInterpretationEnvironment(\n              PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY)\n          .setExecutionEnvironment(PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY)\n          .build();\n  private static final String CORRECT_PRINTF =\n      \"printf %s%s%s TSUNAMI_PAYLOAD_START ffffffffffffffff TSUNAMI_PAYLOAD_END\";\n  private static final String CORRECT_CURL_TRACE =\n      \"curl --trace /tmp/tsunami-rce -- tsunami-rce-ffffffffffffffff\";\n  private static final String CORRECT_WINDOWS_ECHO =\n      \"powershell -Command \\\"echo TSUNAMI_PAYLOAD_START$(echo\"\n          + \" ffffffffffffffff)TSUNAMI_PAYLOAD_END\\\"\";\n\n  @Before\n  public void setUp() {\n    Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom).build())\n        .injectMembers(this);\n  }\n\n  @Test\n  public void isCallbackServerEnabled_returnsFalse() {\n    assertFalse(payloadGenerator.isCallbackServerEnabled());\n  }\n\n  @Test\n  public void getNonCallbackPayload_withLinuxConfiguration_returnsPrintfPayload() {\n    Payload payload = payloadGenerator.generateNoCallback(LINUX_REFLECTIVE_RCE_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(CORRECT_PRINTF);\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void getPayload_withLinuxConfiguration_returnsPrintfPayload() {\n    Payload payload = payloadGenerator.generate(LINUX_REFLECTIVE_RCE_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(CORRECT_PRINTF);\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void checkIfExecuted_withLinuxConfiguration_andCorrectInput_returnsTrue() {\n    Payload payload = payloadGenerator.generate(LINUX_REFLECTIVE_RCE_CONFIG);\n\n    assertTrue(\n        payload.checkIfExecuted(\n            ByteString.copyFromUtf8(\n                \"RANDOMOUTPUTTSUNAMI_PAYLOAD_STARTffffffffffffffffTSUNAMI_PAYLOAD_END\")));\n  }\n\n  @Test\n  public void checkIfExecuted_withLinuxConfiguration_andIncorectInput_returnsFalse() {\n    Payload payload = payloadGenerator.generate(LINUX_REFLECTIVE_RCE_CONFIG);\n\n    assertFalse(payload.checkIfExecuted(ByteString.copyFromUtf8(CORRECT_PRINTF)));\n  }\n\n  @Test\n  public void generateNonCallbackPayload_withCrontabConfiguration_throwsNotImplementedException() {\n\n    assertThrows(\n        NotImplementedException.class,\n        () -> payloadGenerator.generateNoCallback(LINUX_ARBITRARY_FILE_WRITE_CRON_CONFIG));\n  }\n\n  @Test\n  public void getNonCallbackPayload_withBlindRceReadConfiguration_returnsCurlTracePayload() {\n    Payload payload = payloadGenerator.generateNoCallback(LINUX_BLIND_RCE_FILE_READ_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(CORRECT_CURL_TRACE);\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void getPayload_withBlindRceReadConfiguration_returnsCurlTracePayload() {\n    Payload payload = payloadGenerator.generate(LINUX_BLIND_RCE_FILE_READ_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(CORRECT_CURL_TRACE);\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void checkIfExecuted_withBlindRceReadConfiguration_andCorrectInput_returnsTrue() {\n    Payload payload = payloadGenerator.generate(LINUX_BLIND_RCE_FILE_READ_CONFIG);\n\n    assertTrue(\n        payload.checkIfExecuted(\n            ByteString.copyFromUtf8(\"RANDOMOUTPUTtsunami-rce-ffffffffffffffff\")));\n  }\n\n  @Test\n  public void checkIfExecuted_withBlindRceReadConfiguration_andIncorectInput_returnsFalse() {\n    Payload payload = payloadGenerator.generate(LINUX_BLIND_RCE_FILE_READ_CONFIG);\n\n    assertFalse(payload.checkIfExecuted(ByteString.copyFromUtf8(\"RANDOMINPUT\")));\n  }\n\n  @Test\n  public void getNonCallbackPayload_withWindowsConfiguration_returnsPrintfPayload() {\n    Payload payload = payloadGenerator.generateNoCallback(WINDOWS_REFLECTIVE_RCE_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(CORRECT_WINDOWS_ECHO);\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void getPayload_withWindowsConfiguration_returnsEchoPayload() {\n    Payload payload = payloadGenerator.generate(WINDOWS_REFLECTIVE_RCE_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(CORRECT_WINDOWS_ECHO);\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void checkIfExecuted_withWindowsConfiguration_andCorrectInput_returnsTrue() {\n    Payload payload = payloadGenerator.generate(WINDOWS_REFLECTIVE_RCE_CONFIG);\n\n    assertTrue(\n        payload.checkIfExecuted(\n            ByteString.copyFromUtf8(\n                \"RANDOMOUTPUTTSUNAMI_PAYLOAD_STARTffffffffffffffffTSUNAMI_PAYLOAD_END\")));\n  }\n\n  @Test\n  public void checkIfExecuted_withWindowsConfiguration_andIncorectInput_returnsFalse() {\n    Payload payload = payloadGenerator.generate(WINDOWS_REFLECTIVE_RCE_CONFIG);\n\n    assertFalse(payload.checkIfExecuted(ByteString.copyFromUtf8(CORRECT_PRINTF)));\n  }\n\n  @Test\n  public void getPayload_withJavaConfiguration_returnsPrintfPayload() {\n    Payload payload = payloadGenerator.generate(JAVA_REFLECTIVE_RCE_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(\n            \"String.format(\\\"%s%s%s\\\", \\\"TSUNAMI_PAYLOAD_START\\\", \\\"ffffffffffffffff\\\",\"\n                + \" \\\"TSUNAMI_PAYLOAD_END\\\")\");\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void checkIfExecuted_withJavaConfiguration_andCorrectInput_returnsTrue() {\n    Payload payload = payloadGenerator.generate(JAVA_REFLECTIVE_RCE_CONFIG);\n\n    assertTrue(\n        payload.checkIfExecuted(\n            ByteString.copyFromUtf8(\n                \"RANDOMOUTPUTTSUNAMI_PAYLOAD_STARTffffffffffffffffTSUNAMI_PAYLOAD_END\")));\n  }\n\n  @Test\n  public void checkIfExecuted_withJavaConfiguration_andIncorrectInput_returnsFalse() {\n    Payload payload = payloadGenerator.generate(JAVA_REFLECTIVE_RCE_CONFIG);\n\n    assertFalse(\n        payload.checkIfExecuted(\n            ByteString.copyFromUtf8(\"TSUNAMI_PAYLOAD_START ffffffffffffffff TSUNAMI_PAYLOAD_END\")));\n  }\n\n  @Test\n  public void getPayload_withJspConfiguration_returnsPrintfPayload() {\n    Payload payload = payloadGenerator.generate(JSP_REFLECTIVE_RCE_CONFIG);\n\n    assertThat(payload.getPayload())\n        .isEqualTo(\n            \"<% out.print(String.format(\\\"%s%s%s\\\",\\\"TSUNAMI_PAYLOAD_START\\\", \\\"ffffffffffffffff\\\",\"\n                + \" \\\"TSUNAMI_PAYLOAD_END\\\")); %>\");\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void checkIfExecuted_withJspConfiguration_andCorrectInput_returnsTrue() {\n    Payload payload = payloadGenerator.generate(JSP_REFLECTIVE_RCE_CONFIG);\n\n    assertTrue(\n        payload.checkIfExecuted(\n            ByteString.copyFromUtf8(\n                \"RANDOMOUTPUTTSUNAMI_PAYLOAD_STARTffffffffffffffffTSUNAMI_PAYLOAD_END\")));\n  }\n\n  @Test\n  public void checkIfExecuted_withJspConfiguration_andIncorrectInput_returnsFalse() {\n    Payload payload = payloadGenerator.generate(JSP_REFLECTIVE_RCE_CONFIG);\n\n    assertFalse(\n        payload.checkIfExecuted(\n            ByteString.copyFromUtf8(\"TSUNAMI_PAYLOAD_START ffffffffffffffff TSUNAMI_PAYLOAD_END\")));\n  }\n\n  @Test\n  public void getPayload_withSsrfConfiguration_returnsGooglePayload() {\n    Payload payload = payloadGenerator.generate(ANY_SSRF_CONFIG);\n\n    assertThat(payload.getPayload()).isEqualTo(\"http://public-firing-range.appspot.com/\");\n    assertFalse(payload.getPayloadAttributes().getUsesCallbackServer());\n  }\n\n  @Test\n  public void checkIfExecuted_withSsrfConfiguration_andCorrectInput_returnsTrue() {\n    Payload payload = payloadGenerator.generate(ANY_SSRF_CONFIG);\n\n    assertTrue(payload.checkIfExecuted(\"<h1>What is the Firing Range?</h1>\"));\n  }\n\n  @Test\n  public void checkIfExecuted_withSsrfConfiguration_andIncorrectInput_returnsFalse() {\n    Payload payload = payloadGenerator.generate(ANY_SSRF_CONFIG);\n\n    assertFalse(payload.checkIfExecuted(\"404 not found\"));\n  }\n\n  @Test\n  public void generate_withoutVulnerabilityType_throwsNotImplementedException() {\n    assertThrows(\n        NotImplementedException.class,\n        () ->\n            payloadGenerator.generate(\n                PayloadGeneratorConfig.newBuilder()\n                    .setInterpretationEnvironment(\n                        PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL)\n                    .setExecutionEnvironment(\n                        PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n                    .build()));\n  }\n\n  @Test\n  public void generate_withoutInterpretationEnvironment_throwsNotImplementedException() {\n    assertThrows(\n        NotImplementedException.class,\n        () ->\n            payloadGenerator.generate(\n                PayloadGeneratorConfig.newBuilder()\n                    .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE)\n                    .setExecutionEnvironment(\n                        PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)\n                    .build()));\n  }\n\n  @Test\n  public void generate_withoutExecutionEnvironment_throwsNotImplementedException() {\n    assertThrows(\n        NotImplementedException.class,\n        () ->\n            payloadGenerator.generate(\n                PayloadGeneratorConfig.newBuilder()\n                    .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE)\n                    .setInterpretationEnvironment(\n                        PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL)\n                    .build()));\n  }\n\n  @Test\n  public void generate_withoutConfig_throwsNotImplementedException() {\n    assertThrows(\n        NotImplementedException.class,\n        () -> payloadGenerator.generate(PayloadGeneratorConfig.getDefaultInstance()));\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/payload/PayloadSecretGeneratorTest.java",
    "content": "/*\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport java.security.SecureRandom;\nimport java.util.Arrays;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link PayloadSecretGenerator}. */\n@RunWith(JUnit4.class)\npublic final class PayloadSecretGeneratorTest {\n  private static final SecureRandom TEST_RNG =\n      new SecureRandom() {\n        @Override\n        public void nextBytes(byte[] bytes) {\n          Arrays.fill(bytes, (byte) 0xFF);\n        }\n      };\n\n  @Test\n  public void generate_always_generatesExpectedSecretString() {\n    PayloadSecretGenerator secretGenerator = new PayloadSecretGenerator(TEST_RNG);\n\n    assertThat(secretGenerator.generate(4)).isEqualTo(\"ffffffff\");\n  }\n}\n"
  },
  {
    "path": "plugin/src/test/java/com/google/tsunami/plugin/payload/PayloadTest.java",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.plugin.payload;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\nimport com.google.protobuf.ByteString;\nimport com.google.tsunami.proto.PayloadAttributes;\nimport com.google.tsunami.proto.PayloadGeneratorConfig;\nimport java.util.Optional;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link Payload} class */\n@RunWith(JUnit4.class)\npublic final class PayloadTest {\n\n  private static final PayloadGeneratorConfig CONFIG = PayloadGeneratorConfig.getDefaultInstance();\n  private static final PayloadAttributes PAYLOAD_ATTRIBUTES =\n      PayloadAttributes.getDefaultInstance();\n\n  @Test\n  public void getPayload_returnsPayloadString() {\n    Validator validator = (unused) -> false;\n    Payload payload = new Payload(\"my-payload\", validator, PAYLOAD_ATTRIBUTES, CONFIG);\n\n    assertEquals(\"my-payload\", payload.getPayload());\n  }\n\n  @Test\n  public void checkIfExecuted_withNoParameter_executesValidator() {\n    TestValidatorIsCalledValidator testValidator = new TestValidatorIsCalledValidator();\n    Payload payload = new Payload(\"my-payload\", testValidator, PAYLOAD_ATTRIBUTES, CONFIG);\n\n    payload.checkIfExecuted();\n    assertTrue(testValidator.wasCalled);\n  }\n\n  @Test\n  public void checkIfExecuted_withString_executesValidator() {\n    TestValidatorIsCalledValidator testValidator = new TestValidatorIsCalledValidator();\n    Payload payload = new Payload(\"my-payload\", testValidator, PAYLOAD_ATTRIBUTES, CONFIG);\n\n    payload.checkIfExecuted(\"my-input\");\n    assertTrue(testValidator.wasCalled);\n  }\n\n  @Test\n  public void checkIfExecuted_withByteString_executesValidator() {\n    TestValidatorIsCalledValidator testValidator = new TestValidatorIsCalledValidator();\n    Payload payload = new Payload(\"my-payload\", testValidator, PAYLOAD_ATTRIBUTES, CONFIG);\n\n    payload.checkIfExecuted(ByteString.copyFromUtf8(\"my-input\"));\n    assertTrue(testValidator.wasCalled);\n  }\n\n  @Test\n  public void checkIfExecuted_withOptional_executesValidator() {\n    TestValidatorIsCalledValidator testValidator = new TestValidatorIsCalledValidator();\n    Payload payload = new Payload(\"my-payload\", testValidator, PAYLOAD_ATTRIBUTES, CONFIG);\n\n    payload.checkIfExecuted(Optional.empty());\n    assertTrue(testValidator.wasCalled);\n  }\n\n  @Test\n  public void getPayloadAttributes_returnsPayloadAttributes() {\n    Validator validator = (unused) -> false;\n    Payload payload = new Payload(\"my-payload\", validator, PAYLOAD_ATTRIBUTES, CONFIG);\n\n    assertEquals(payload.getPayloadAttributes(), PAYLOAD_ATTRIBUTES);\n  }\n\n  private static final class TestValidatorIsCalledValidator implements Validator {\n    public boolean wasCalled = false;\n\n    @Override\n    public boolean isExecuted(Optional<ByteString> input) {\n      wasCalled = true;\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "plugin_server/py/common/data/network_endpoint_utils.py",
    "content": "\"\"\"Static utility methods pertaining to network endpoint protocol buffer.\n\nFor any utility update, please consider if Java's network endpoint utils\n(common/src/main/java/com/google/tsunami/common/data/NetworkEndpointUtils.java)\nalso needs the modification.\n\"\"\"\n\nimport ipaddress\nfrom typing import Optional\nimport network_pb2\n\nAddressFamily = network_pb2.AddressFamily\nHostname = network_pb2.Hostname\nIpAddress = network_pb2.IpAddress\nNetworkEndpoint = network_pb2.NetworkEndpoint\nType = NetworkEndpoint.Type\nDEFINITELY_IP_TYPES = [\n    NetworkEndpoint.Type.IP,\n    NetworkEndpoint.Type.IP_PORT,\n    NetworkEndpoint.Type.IP_HOSTNAME,\n    NetworkEndpoint.Type.IP_HOSTNAME_PORT\n]\nDEFINITELY_HOSTNAME_TYPES = [\n    NetworkEndpoint.Type.HOSTNAME,\n    NetworkEndpoint.Type.HOSTNAME_PORT,\n    NetworkEndpoint.Type.IP_HOSTNAME,\n    NetworkEndpoint.Type.IP_HOSTNAME_PORT\n]\nDEFINITELY_PORT_TYPES = [\n    NetworkEndpoint.Type.IP_PORT,\n    NetworkEndpoint.Type.IP_HOSTNAME_PORT,\n    NetworkEndpoint.Type.HOSTNAME_PORT\n]\n\nMAX_PORT_NUMBER = 65535\n\n\ndef has_ip_address(network_endpoint: NetworkEndpoint) -> bool:\n  return network_endpoint.type in DEFINITELY_IP_TYPES\n\n\ndef has_hostname(network_endpoint: NetworkEndpoint) -> bool:\n  return network_endpoint.type in DEFINITELY_HOSTNAME_TYPES\n\n\ndef has_port(network_endpoint: NetworkEndpoint) -> bool:\n  return network_endpoint.type in DEFINITELY_PORT_TYPES\n\n\ndef is_ipv6_endpoint(network_endpoint: NetworkEndpoint) -> bool:\n  return has_ip_address(\n      network_endpoint\n  ) and network_endpoint.ip_address.address_family == AddressFamily.IPV6\n\n\ndef to_uri_authority(network_endpoint: NetworkEndpoint) -> str:\n  \"\"\"Converts network endpoint to URI string.\n\n  Composes URI from the given endpoint information.\n  Hostname takes precedence over IP address as hostname is a stable identifier\n  for a physical thing whereas IP addresses are ephemeral and unstable.\n\n  Args:\n    network_endpoint: instance of a network endpoint protobuf.\n\n  Returns:\n    Return None with an unspecified endpoint type. Return URI string with\n    valid endpoint type. For example, various combination of ip address, port,\n    and hostname would generate the below uri:\n      *  ip_v4 = \"1.2.3.4\" -> uri = \"1.2.3.4\"\n      *  ip_v6 = \"3ffe::1\" -> uri = \"[3ffe::1]\"\n      *   host = \"localhost\" -> uri = \"localhost\"\n      *  ip_v4 = \"1.2.3.4\"   port = 8888 -> uri = \"1.2.3.4:8888\"\n      *  ip_v6 = \"3ffe::1\"   port = 8888 -> uri = \"[3ffe::1]:8888\"\n      *   host = \"localhost\" port = 8888 -> uri = \"localhost:8888\"\n\n  Raises:\n    ValueError: an error occurred while looking up network endpoint type.\n  \"\"\"\n\n  ip_address = network_endpoint.ip_address.address\n  port = network_endpoint.port.port_number\n  hostname = network_endpoint.hostname.name\n  uri = ''\n\n  if network_endpoint.type == NetworkEndpoint.Type.TYPE_UNSPECIFIED:\n    raise_invalid_network_endpoint_type(network_endpoint.type)\n  if network_endpoint.type == NetworkEndpoint.Type.IP:\n    uri = ip_to_uri(ip_address)\n  elif network_endpoint.type == NetworkEndpoint.Type.IP_PORT:\n    uri = ip_to_uri(ip_address) + ':' + str(port)\n  elif network_endpoint.type == NetworkEndpoint.Type.IP_HOSTNAME or network_endpoint.type == NetworkEndpoint.Type.HOSTNAME:\n    uri = hostname\n  elif network_endpoint.type == NetworkEndpoint.Type.IP_HOSTNAME_PORT or network_endpoint.type == NetworkEndpoint.Type.HOSTNAME_PORT:\n    uri = hostname + ':' + str(port)\n  return uri\n\n\ndef for_ip(ip_address: str) -> NetworkEndpoint:\n  \"\"\"Convert ip address to network endpoint protobuf.\"\"\"\n  network_endpoint = create_with_ip(ip_address)\n  network_endpoint.type = NetworkEndpoint.Type.IP\n  return network_endpoint\n\n\ndef for_ip_and_port(ip_address: str, port: int) -> NetworkEndpoint:\n  \"\"\"Convert ip address and port to network endpoint protobuf.\"\"\"\n  validate_port(port)\n  network_endpoint = create_with_ip(ip_address)\n  network_endpoint.type = NetworkEndpoint.Type.IP_PORT\n  network_endpoint.port.port_number = port\n  return network_endpoint\n\n\ndef for_hostname(hostname: str) -> NetworkEndpoint:\n  \"\"\"Convert hostname to network endpoint protobuf.\"\"\"\n  validate_hostname(hostname)\n  network_endpoint = NetworkEndpoint()\n  network_endpoint.hostname.name = hostname\n  network_endpoint.type = NetworkEndpoint.Type.HOSTNAME\n  return network_endpoint\n\n\ndef for_ip_and_hostname(ip_address: str, hostname: str) -> NetworkEndpoint:\n  \"\"\"Convert ip address and hostname to network endpoint protobuf.\"\"\"\n  network_endpoint = create_with_ip(ip_address)\n  network_endpoint.type = NetworkEndpoint.Type.IP_HOSTNAME\n  network_endpoint.hostname.name = hostname\n  return network_endpoint\n\n\ndef for_hostname_and_port(hostname: str, port: int) -> NetworkEndpoint:\n  \"\"\"Convert hostname and port to network endpoint protobuf.\"\"\"\n  validate_port(port)\n  validate_hostname(hostname)\n  network_endpoint = NetworkEndpoint()\n  network_endpoint.hostname.name = hostname\n  network_endpoint.type = NetworkEndpoint.Type.HOSTNAME_PORT\n  network_endpoint.port.port_number = port\n  return network_endpoint\n\n\ndef for_ip_hostname_and_port(ip_address: str, hostname: str,\n                             port: int) -> NetworkEndpoint:\n  \"\"\"Convert ip address, hostname and port to network endpoint protobuf.\"\"\"\n  validate_port(port)\n  network_endpoint = create_with_ip(ip_address)\n  network_endpoint.type = NetworkEndpoint.Type.IP_HOSTNAME_PORT\n  network_endpoint.hostname.name = hostname\n  network_endpoint.port.port_number = port\n  return network_endpoint\n\n\ndef for_network_endpoint_and_port(network_endpoint: NetworkEndpoint,\n                                  port: int) -> Optional[NetworkEndpoint]:\n  \"\"\"Create protobuf from endpoint type lookup.\"\"\"\n  validate_port(port)\n\n  if network_endpoint.type == NetworkEndpoint.Type.IP:\n    return for_ip_and_port(network_endpoint.ip_address.address, port)\n  elif network_endpoint.type == NetworkEndpoint.Type.HOSTNAME:\n    return for_hostname_and_port(network_endpoint.hostname.name, port)\n  elif network_endpoint.type == NetworkEndpoint.Type.IP_HOSTNAME:\n    return for_ip_hostname_and_port(network_endpoint.ip_address.address,\n                                    network_endpoint.hostname.name, port)\n  else:\n    raise_invalid_network_endpoint_type(network_endpoint.type)\n\n\ndef create_with_ip(ip_address: str) -> NetworkEndpoint:\n  \"\"\"Converts ip address to protobuf network endpoint.\"\"\"\n  try:\n    ipaddress.ip_address(ip_address)\n  except ValueError as exc:\n    raise ValueError('%s is not an IP address.' % ip_address) from exc\n  return NetworkEndpoint(\n      ip_address=IpAddress(\n          address=ip_address,\n          address_family=address_family(ip_address)))\n\n\ndef validate_port(port: int) -> None:\n  if (port < 0 or port > MAX_PORT_NUMBER):\n    raise ValueError('Port out of range. Expected [0, %s].' % MAX_PORT_NUMBER)\n\n\ndef validate_hostname(hostname: str) -> None:\n  try:\n    ipaddress.ip_address(hostname)\n    raise Exception(\"Expected hostname, got IP address '%s'.\" % hostname)\n  except ValueError:\n    pass\n\n\ndef raise_invalid_network_endpoint_type(endpoint_type: Type) -> None:\n  raise ValueError('Invalid network endpoint type: %s.' %\n                   NetworkEndpoint.Type.Name(endpoint_type))\n\n\ndef address_family(ip_address: str) -> AddressFamily:\n  try:\n    ipaddress.IPv4Address(ip_address)\n    return AddressFamily.IPV4\n  except ipaddress.AddressValueError:\n    return AddressFamily.IPV6\n\n\ndef ip_to_uri(ip_address: str) -> str:\n  return ip_address if address_family(\n      ip_address) == AddressFamily.IPV4 else '[%s]' % ip_address\n"
  },
  {
    "path": "plugin_server/py/common/data/network_endpoint_utils_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.common.data.network_endpoint_utils.\"\"\"\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom common.data import network_endpoint_utils\nimport network_pb2\n\nAddressFamily = network_pb2.AddressFamily\nHostname = network_pb2.Hostname\nIpAddress = network_pb2.IpAddress\nNetworkEndpoint = network_pb2.NetworkEndpoint\nPort = network_pb2.Port\n_IPV4 = '8.8.8.8'\n_IPV6 = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'\n_PORT = 80\n_HOSTNAME = 'localhost'\n\n\ndef _make_ipv4_endpoint(port=False):\n  network_endpoint = NetworkEndpoint(\n      ip_address=IpAddress(address=_IPV4, address_family=AddressFamily.IPV4),\n  )\n  return _add_network_type_and_port(network_endpoint, port)\n\n\ndef _make_ipv6_endpoint(port=False):\n  network_endpoint = NetworkEndpoint(\n      ip_address=IpAddress(address=_IPV6, address_family=AddressFamily.IPV6),\n  )\n  return _add_network_type_and_port(network_endpoint, port)\n\n\ndef _make_hostname_endpoint(port=False):\n  network_endpoint = NetworkEndpoint(\n      hostname=Hostname(name=_HOSTNAME),\n  )\n  if port:\n    network_endpoint.port.port_number = _PORT\n    network_endpoint.type = NetworkEndpoint.Type.HOSTNAME_PORT\n  else:\n    network_endpoint.type = NetworkEndpoint.Type.HOSTNAME\n  return network_endpoint\n\n\ndef _add_network_type_and_port(network_endpoint, port=False):\n  if port:\n    network_endpoint.port.port_number = _PORT\n    network_endpoint.type = NetworkEndpoint.Type.IP_PORT\n  else:\n    network_endpoint.type = NetworkEndpoint.Type.IP\n  return network_endpoint\n\n\nclass NetworkEndpointUtilsTest(parameterized.TestCase):\n  def test_has_hostname(self):\n    self.assertTrue(network_endpoint_utils.has_hostname(\n        _make_hostname_endpoint()))\n    self.assertTrue(network_endpoint_utils.has_hostname(\n        _make_hostname_endpoint(True)))\n\n    network_endpoint = _make_ipv4_endpoint(True)\n    network_endpoint.hostname.name = _HOSTNAME\n    network_endpoint.type = NetworkEndpoint.Type.IP_HOSTNAME_PORT\n    self.assertTrue(network_endpoint_utils.has_hostname(network_endpoint))\n\n  def test_has_port(self):\n    self.assertTrue(network_endpoint_utils.has_port(\n        _make_hostname_endpoint(True)))\n\n    network_endpoint = _make_ipv4_endpoint(True)\n    network_endpoint.hostname.name = _HOSTNAME\n    network_endpoint.type = NetworkEndpoint.Type.IP_HOSTNAME_PORT\n    self.assertTrue(network_endpoint_utils.has_port(network_endpoint))\n\n  @parameterized.named_parameters(\n      ('ipv6', _make_ipv6_endpoint()),\n      ('ipv6_and_port', _make_ipv6_endpoint(True)))\n  def test_is_ipv6_endpoint(self, network_endpoint: NetworkEndpoint):\n    self.assertTrue(network_endpoint_utils.is_ipv6_endpoint(network_endpoint))\n\n  @parameterized.named_parameters(\n      ('ipv4', _make_ipv4_endpoint()),\n      ('ipv4_and_port', _make_ipv4_endpoint(True)),\n      ('hostname', _make_hostname_endpoint()),\n      ('hostname_and_port', _make_hostname_endpoint(True)))\n  def test_is_ipv6_endpoint_returns_false(self,\n                                          network_endpoint: NetworkEndpoint):\n    self.assertFalse(network_endpoint_utils.is_ipv6_endpoint(network_endpoint))\n\n  @parameterized.named_parameters(\n      ('ipv4', _make_ipv4_endpoint(), _IPV4),\n      ('ipv4_and_port', _make_ipv4_endpoint(True), '8.8.8.8:80'),\n      ('ipv6', _make_ipv6_endpoint(),\n       '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]'),\n      ('ipv6_and_port', _make_ipv6_endpoint(True),\n       '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80'),\n      ('hostname', _make_hostname_endpoint(), _HOSTNAME),\n      ('hostname_and_port', _make_hostname_endpoint(True),\n       'localhost:80'))\n  def test_to_uri_authority(self, network_endpoint: NetworkEndpoint,\n                            uri_authority: str):\n    self.assertEqual(uri_authority,\n                     network_endpoint_utils.to_uri_authority(network_endpoint))\n\n  @parameterized.named_parameters(\n      ('ipv4', _make_ipv4_endpoint(), NetworkEndpoint.Type.IP_HOSTNAME,\n       _HOSTNAME), ('ipv6_endpoint', _make_ipv6_endpoint(),\n                    NetworkEndpoint.Type.IP_HOSTNAME, _HOSTNAME),\n      ('ipv4_and_port', _make_ipv4_endpoint(True),\n       NetworkEndpoint.Type.IP_HOSTNAME_PORT, 'localhost:80'),\n      ('ipv6_and_port', _make_ipv6_endpoint(True),\n       NetworkEndpoint.Type.IP_HOSTNAME_PORT, 'localhost:80'))\n  def test_to_uri_authority_ip_and_host(self, network_endpoint: NetworkEndpoint,\n                                        endpoint_type: NetworkEndpoint.Type,\n                                        uri_authority: str):\n    network_endpoint.hostname.name = _HOSTNAME\n    network_endpoint.type = endpoint_type\n    self.assertEqual(uri_authority,\n                     network_endpoint_utils.to_uri_authority(network_endpoint))\n\n  def test_to_uri_authority_ip_and_host_with_unspecified_type(self):\n    network_endpoint = NetworkEndpoint(\n        type=NetworkEndpoint.Type.TYPE_UNSPECIFIED,\n    )\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.to_uri_authority(network_endpoint)\n    self.assertEqual('Invalid network endpoint type: TYPE_UNSPECIFIED.',\n                     str(exc.exception))\n\n  def test_for_ip_with_ipv4(self):\n    self.assertEqual(_make_ipv4_endpoint(),\n                     network_endpoint_utils.for_ip(_IPV4))\n\n  def test_for_ip_with_ipv6(self):\n    self.assertEqual(_make_ipv6_endpoint(),\n                     network_endpoint_utils.for_ip(_IPV6))\n\n  def test_for_ip_and_port_with_ipv4_and_port(self):\n    self.assertEqual(_make_ipv4_endpoint(True),\n                     network_endpoint_utils.for_ip_and_port(_IPV4, _PORT))\n\n  def test_for_ip_and_port_with_ipv6_and_port(self):\n    self.assertEqual(_make_ipv6_endpoint(True),\n                     network_endpoint_utils.for_ip_and_port(_IPV6, _PORT))\n\n  def test_for_hostname_with_hostname(self):\n    network_endpoint = _make_hostname_endpoint()\n    self.assertEqual(network_endpoint,\n                     network_endpoint_utils.for_hostname(_HOSTNAME))\n\n  def test_for_hostname_and_port_with_hostname_and_port(self):\n    network_endpoint = _make_hostname_endpoint(True)\n    self.assertEqual(\n        network_endpoint,\n        network_endpoint_utils.for_hostname_and_port(_HOSTNAME, _PORT))\n\n  def test_for_ip_and_hostname_with_ipv4_and_hostname(self):\n    network_endpoint = _make_ipv4_endpoint()\n    network_endpoint.hostname.name = _HOSTNAME\n    network_endpoint.type = NetworkEndpoint.Type.IP_HOSTNAME\n    self.assertEqual(\n        network_endpoint,\n        network_endpoint_utils.for_ip_and_hostname(_IPV4, _HOSTNAME))\n\n  def test_for_ip_and_hostname_with_ipv6_and_hostname(self):\n    network_endpoint = _make_ipv6_endpoint()\n    network_endpoint.hostname.name = _HOSTNAME\n    network_endpoint.type = NetworkEndpoint.Type.IP_HOSTNAME\n    self.assertEqual(\n        network_endpoint,\n        network_endpoint_utils.for_ip_and_hostname(_IPV6, _HOSTNAME))\n\n  def test_for_ip_hostname_and_port_with_ipv4_hostname_and_port(self):\n    network_endpoint = _make_ipv4_endpoint(True)\n    network_endpoint.hostname.name = _HOSTNAME\n    network_endpoint.type = NetworkEndpoint.Type.IP_HOSTNAME_PORT\n    self.assertEqual(\n        network_endpoint,\n        network_endpoint_utils.for_ip_hostname_and_port(_IPV4, _HOSTNAME,\n                                                        _PORT))\n\n  def test_for_ip_hostname_and_port_with_ipv6_hostname_and_port(self):\n    network_endpoint = _make_ipv6_endpoint(True)\n    network_endpoint.hostname.name = _HOSTNAME\n    network_endpoint.type = NetworkEndpoint.Type.IP_HOSTNAME_PORT\n    self.assertEqual(\n        network_endpoint,\n        network_endpoint_utils.for_ip_hostname_and_port(_IPV6, _HOSTNAME,\n                                                        _PORT))\n\n  def test_for_ip_hostname_and_port_with_invalid_port(self):\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_ip_hostname_and_port(_IPV6, _HOSTNAME, 65536)\n    self.assertEqual('Port out of range. Expected [0, 65535].',\n                     str(exc.exception))\n\n  def test_for_network_endpoint_and_port_with_ipv4_and_port(self):\n    network_endpoint = network_endpoint_utils.for_ip(_IPV4)\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_network_endpoint_and_port(network_endpoint, -1)\n    self.assertEqual('Port out of range. Expected [0, 65535].',\n                     str(exc.exception))\n\n  def test_for_network_endpoint_and_port_with_ipv6_and_port(self):\n    network_endpoint = network_endpoint_utils.for_ip(_IPV6)\n    self.assertEqual(\n        _make_ipv6_endpoint(True),\n        network_endpoint_utils.for_network_endpoint_and_port(\n            network_endpoint, _PORT))\n\n  def test_for_network_endpoint_and_port_with_hostname_and_port(self):\n    network_endpoint = network_endpoint_utils.for_hostname(_HOSTNAME)\n    self.assertEqual(\n        _make_hostname_endpoint(True),\n        network_endpoint_utils.for_network_endpoint_and_port(\n            network_endpoint, _PORT))\n\n  def test_for_network_endpoint_and_port_with_ip_and_port(self):\n    network_endpoint = _make_ipv4_endpoint()\n    network_endpoint.hostname.name = _HOSTNAME\n    network_endpoint.type = NetworkEndpoint.Type.IP_HOSTNAME\n    ip_hostname_port_endpoint = network_endpoint_utils.for_ip_and_port(\n        _IPV4, _PORT)\n    ip_hostname_port_endpoint.hostname.name = _HOSTNAME\n    ip_hostname_port_endpoint.type = NetworkEndpoint.Type.IP_HOSTNAME_PORT\n    self.assertEqual(\n        ip_hostname_port_endpoint,\n        network_endpoint_utils.for_network_endpoint_and_port(\n            network_endpoint, _PORT))\n\n  def test_for_ip_with_invalid_ip(self):\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_ip('123')\n    self.assertEqual('123 is not an IP address.', str(exc.exception))\n\n  def test_for_ip_and_port_with_invalid_ip(self):\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_ip_and_port('123', _PORT)\n    self.assertEqual('123 is not an IP address.', str(exc.exception))\n\n  def test_for_ip_and_port_with_invalid_port(self):\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_ip_and_port(_IPV4, -1)\n    self.assertEqual('Port out of range. Expected [0, 65535].',\n                     str(exc.exception))\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_ip_and_port(_IPV4, 65536)\n    self.assertEqual('Port out of range. Expected [0, 65535].',\n                     str(exc.exception))\n\n  def test_for_hostname_with_ip(self):\n    with self.assertRaises(Exception) as exc:\n      network_endpoint_utils.for_hostname(_IPV4)\n    self.assertEqual(\"Expected hostname, got IP address '%s'.\" % _IPV4,\n                     str(exc.exception))\n    with self.assertRaises(Exception) as exc:\n      network_endpoint_utils.for_hostname(_IPV6)\n    self.assertEqual(\"Expected hostname, got IP address '%s'.\" % _IPV6,\n                     str(exc.exception))\n\n  def test_for_hostname_and_port_with_invalid_ip(self):\n    with self.assertRaises(Exception) as exc:\n      network_endpoint_utils.for_hostname_and_port(_IPV4, _PORT)\n    self.assertEqual(\"Expected hostname, got IP address '%s'.\" % _IPV4,\n                     str(exc.exception))\n    with self.assertRaises(Exception) as exc:\n      network_endpoint_utils.for_hostname_and_port(_IPV6, _PORT)\n    self.assertEqual(\"Expected hostname, got IP address '%s'.\" % _IPV6,\n                     str(exc.exception))\n\n  def test_for_hostname_and_port_with_invalid_port(self):\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_hostname_and_port(_HOSTNAME, -1)\n    self.assertEqual('Port out of range. Expected [0, 65535].',\n                     str(exc.exception))\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_hostname_and_port(_HOSTNAME, 65536)\n    self.assertEqual('Port out of range. Expected [0, 65535].',\n                     str(exc.exception))\n\n  def test_for_network_endpoint_and_port_with_invalid_endpoint_type(self):\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_network_endpoint_and_port(\n          _make_ipv4_endpoint(True), _PORT)\n    self.assertEqual('Invalid network endpoint type: IP_PORT.',\n                     str(exc.exception))\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_network_endpoint_and_port(\n          _make_hostname_endpoint(True), _PORT)\n    self.assertEqual('Invalid network endpoint type: HOSTNAME_PORT.',\n                     str(exc.exception))\n\n  def testfor_network_endpoint_and_port_with_invalid_port(self):\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_network_endpoint_and_port(\n          _make_ipv4_endpoint(), -1)\n    self.assertEqual('Port out of range. Expected [0, 65535].',\n                     str(exc.exception))\n    with self.assertRaises(ValueError) as exc:\n      network_endpoint_utils.for_network_endpoint_and_port(\n          _make_hostname_endpoint(), 65536)\n    self.assertEqual('Port out of range. Expected [0, 65535].',\n                     str(exc.exception))\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/common/data/network_service_utils.py",
    "content": "\"\"\"Utilities for handling network_service_pb2 protos.\n\nFor any utility update, please consider if Java's network service utils\n(common/src/main/java/com/google/tsunami/common/data/NetworkServiceUtils.java)\nalso needs the modification.\n\"\"\"\n\n\nimport socket\nfrom typing import Optional\nimport urllib.parse\nfrom common.data import network_endpoint_utils\nimport network_pb2\nimport network_service_pb2\n\nurlparse = urllib.parse.urlparse\nNetworkService = network_service_pb2.NetworkService\nServiceContext = network_service_pb2.ServiceContext\nWebServiceContext = network_service_pb2.WebServiceContext\nAddressFamily = network_pb2.AddressFamily\nHostname = network_pb2.Hostname\nIpAddress = network_pb2.IpAddress\nNetworkEndpoint = network_pb2.NetworkEndpoint\nPort = network_pb2.Port\nTransportProtocol = network_pb2.TransportProtocol\n\nis_plain_http_by_known_web_service_name = {\n    \"http\": True,\n    \"http-alt\": True,\n    \"http-proxy\": True,\n    \"https\": False,\n    \"radan-http\": True,\n    \"ssl/http\": False,\n    \"ssl/https\": False,\n}\n\n\ndef is_web_service(network_service: NetworkService) -> bool:\n  return network_service.service_name.lower(\n  ) in is_plain_http_by_known_web_service_name\n\n\ndef is_plain_http_service(network_service: NetworkService) -> bool:\n  return is_web_service(\n      network_service) and is_plain_http_by_known_web_service_name.get(\n          network_service.service_name.lower(), False)\n\n\ndef get_service_name(network_service: NetworkService) -> str:\n  return network_service.software.name.lower(\n  ) or network_service.service_name.lower()\n\n\ndef build_uri_network_service(uri_string: str) -> NetworkService:\n  \"\"\"Compose network service protobuf from URI.\n\n  Parses the URI string into host, scheme, port, ip address, and ip address\n  family. Then uses the above information to compose the network endpoint.\n\n  Args:\n    uri_string: the uri of the endpoint\n\n  Returns:\n    Network endpoint protobuf\n\n  Raises:\n    ValueError: if uri is not http or https or if ip address is invalid\n  \"\"\"\n  uri = urlparse(uri_string)\n  hostname = uri.hostname\n  scheme = uri.scheme\n  validate_scheme(scheme)\n\n  port = sanitize_port(uri.port, scheme)\n  address_info = socket.getaddrinfo(hostname, port)[0]\n  ip_address = address_info[4][0]\n  address_family = get_address_family(address_info[0])\n\n  network_endpoint = NetworkEndpoint(\n      ip_address=IpAddress(\n          address_family=address_family,\n          address=ip_address,\n      ),\n      type=NetworkEndpoint.Type.IP_HOSTNAME_PORT,\n      hostname=Hostname(name=hostname),\n      port=Port(port_number=port),\n  )\n  return NetworkService(\n      network_endpoint=network_endpoint,\n      transport_protocol=TransportProtocol.TCP,\n      service_name=scheme,\n      service_context=ServiceContext(\n          web_service_context=WebServiceContext(application_root=uri.path)\n      )\n  )\n\n\ndef build_web_application_root_url(network_service: NetworkService) -> str:\n  \"\"\"Build the root url for web application service.\n\n  Args:\n    network_service: network service protobuf with a valid service defined\n    in the is_plain_http_by_known_web_service_name dict.\n\n  Returns:\n    The root URL for the web service which always ends with a \"/\"\n    (i.e., http://localhost:8080/, https://127.1.23.1/pathway/)\n  \"\"\"\n  if not is_web_service(network_service):\n    raise ValueError(\"Invalid network service: %s\" % network_service)\n  return build_web_protocol(\n      network_service) + build_web_uri_authority(\n          network_service) + build_web_app_root_path(network_service)\n\n\ndef build_web_protocol(network_service: NetworkService) -> str:\n  if is_plain_http_service(network_service):\n    return \"http://\"\n  else:\n    return \"https://\"\n\n\ndef build_web_uri_authority(network_service: NetworkService) -> str:\n  \"\"\"Creates URI authority using the network service.\n\n  The URI authority has 2 components: domain name and port number. Removes the\n  port number from URI authority if the web service uses the standard default\n  port. Port 80 for http/unsecure network and port 443 for https/secure\n  network.\n\n  Args:\n    network_service: contains the service name and network endpoint needed to\n    construct the URI authority.\n\n  Returns:\n    The URI authority. For example, various combination of ip\n    address, port, hostname and service would generate the below uri:\n\n      With services 'http', 'http-alt', 'http-proxy', and 'radan-http:\n        *  ip_v4 = \"1.2.3.4\" port = 80 -> uri_athority= \"1.2.3.4\"\n        *  ip_v6 = \"3ffe::1\" port = 80 -> uri_athority = \"[3ffe::1]\"\n        *   host = \"localhost\" port = 80 -> uri_athority = \"localhost\"\n        *  ip_v4 = \"1.2.3.4\"   port = 443 -> uri_athority = \"1.2.3.4:443\"\n        *  ip_v6 = \"3ffe::1\"   port = 8000 -> uri_athority = \"[3ffe::1]:8000\"\n        *   host = \"localhost\" port = 8888 -> uri_athority = \"localhost:8888\"\n\n      With services 'https', 'ssl/http', and 'ssl/https':\n        *  ip_v4 = \"1.2.3.4\" port = 443 -> uri_athority = \"1.2.3.4\"\n        *  ip_v6 = \"3ffe::1\" port = 443 -> uri_athority = \"[3ffe::1]\"\n        *   host = \"localhost\" port = 443 -> uri_athority = \"localhost\"\n        *  ip_v4 = \"1.2.3.4\"   port = 8000 -> uri_athority = \"1.2.3.4:8000\"\n        *  ip_v6 = \"3ffe::1\"   port = 80 -> uri_athority = \"[3ffe::1]:80\"\n        *   host = \"localhost\" port = 8888 -> uri_athority = \"localhost:8888\"\n  \"\"\"\n  uri_authority = network_endpoint_utils.to_uri_authority(\n      network_service.network_endpoint)\n  if is_plain_http_service(network_service) and uri_authority.endswith(\n      \":80\"):\n    return uri_authority.replace(\":80\", \"\")\n  if not is_plain_http_service(\n      network_service) and uri_authority.endswith(\":443\"):\n    return uri_authority.replace(\":443\", \"\")\n  return uri_authority\n\n\ndef build_web_app_root_path(network_service: NetworkService) -> str:\n  if network_service.service_context:\n    root_path = network_service.service_context.web_service_context.application_root\n  else:\n    root_path = \"/\"\n  if not root_path.startswith(\"/\"):\n    root_path = \"/\" + root_path\n  if not root_path.endswith(\"/\"):\n    root_path = root_path + \"/\"\n  return root_path\n\n\ndef get_address_family(address_family: socket.AddressFamily) -> AddressFamily:\n  if address_family == socket.AF_INET:\n    return AddressFamily.IPV4\n  elif address_family == socket.AF_INET6:\n    return AddressFamily.IPV6\n  else:\n    raise ValueError(\"Invalid address family: %s\" % address_family)\n\n\ndef sanitize_port(port: Optional[int], scheme: str) -> int:\n  if isinstance(port, type(None)):\n    return get_port(-1, scheme)\n  return get_port(port, scheme)\n\n\ndef get_port(port: int, scheme: str) -> int:\n  if port >= 0:\n    return port\n  return 80 if scheme == \"http\" else 443\n\n\ndef validate_scheme(scheme: str) -> None:\n  if scheme == \"http\" or scheme == \"https\":\n    pass\n  else:\n    raise ValueError(\n        \"URI scheme should be one of the following: 'http', 'https'\")\n"
  },
  {
    "path": "plugin_server/py/common/data/network_service_utils_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.common.data.network_service_utils.\"\"\"\n\nimport socket\nfrom unittest import mock\nimport urllib.parse\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom common.data import network_service_utils\nimport network_pb2\nimport network_service_pb2\n\nAddressFamily = network_pb2.AddressFamily\nNetworkEndpoint = network_pb2.NetworkEndpoint\nNetworkService = network_service_pb2.NetworkService\nHostname = network_pb2.Hostname\nIpAddress = network_pb2.IpAddress\nPort = network_pb2.Port\nTransportProtocol = network_pb2.TransportProtocol\nWebServiceContext = network_service_pb2.WebServiceContext\n_ROOT = 'i_am_root'\n_PORT = 8888\n\n\nclass NetworkServiceUtilsTest(parameterized.TestCase):\n  def make_web_service(self, service_name: str):\n    network_service = NetworkService(service_name=service_name,)\n    return network_service\n\n  def make_ip_port_endpoint(self, port: int = _PORT):\n    network_endpoint = NetworkEndpoint(\n        port=Port(port_number=port),\n        ip_address=IpAddress(\n            address='0.0.0.0', address_family=AddressFamily.IPV4),\n        type=NetworkEndpoint.Type.IP_PORT,\n    )\n    return network_endpoint\n\n  def make_web_service_context(self, root: str = _ROOT):\n    web_service_context = WebServiceContext(application_root=root)\n    return web_service_context\n\n  @parameterized.named_parameters(\n      ('http', 'http'),\n      ('http-alt', 'http-alt'),\n      ('http-proxy', 'http-proxy'),\n      ('https', 'https'),\n      ('ssl/http', 'ssl/http'),\n      ('ssl/https', 'ssl/https'),\n      ('radan-http', 'radan-http'),\n      ('is_case_insensitive', 'HTTPS'))\n  def test_is_web_service(self, service_name: str):\n    network_service = self.make_web_service(service_name)\n    self.assertTrue(network_service_utils.is_web_service(network_service))\n\n  def test_is_web_service_with_invalid_web_service(self):\n    network_service = self.make_web_service('ssh')\n    self.assertFalse(network_service_utils.is_web_service(network_service))\n\n  @parameterized.named_parameters(\n      ('http', 'http'),\n      ('http-alt', 'http-alt'),\n      ('http-proxy', 'http-proxy'),\n      ('radan-http', 'radan-http'))\n  def test_is_plain_http_service(self, service_name: str):\n    network_service = self.make_web_service(service_name)\n    self.assertTrue(\n        network_service_utils.is_plain_http_service(network_service))\n\n  @parameterized.named_parameters(\n      ('https', 'https'),\n      ('ssh', 'ssh'),\n      ('ssl/http', 'ssl/http'),\n      ('ssl/https', 'ssl/https'))\n  def test_is_plain_http_service_with_invalid_service(self, service_name: str):\n    network_service = self.make_web_service(service_name)\n    self.assertFalse(\n        network_service_utils.is_plain_http_service(network_service))\n\n  def test_get_service_name_with_software(self):\n    network_service = self.make_web_service('ssh')\n    network_service.network_endpoint.CopyFrom(self.make_ip_port_endpoint())\n    network_service.software.name = 'Oracle'\n    self.assertEqual('oracle',\n                     network_service_utils.get_service_name(network_service))\n\n  def test_get_service_name_with_web_service(self):\n    network_service = self.make_web_service('ssh')\n    network_service.network_endpoint.CopyFrom(self.make_ip_port_endpoint())\n    self.assertEqual('ssh',\n                     network_service_utils.get_service_name(network_service))\n\n  @parameterized.named_parameters(\n      ('http_without_root', 'http', _PORT, '', 'http://0.0.0.0:8888/'),\n      ('http_with_root_path', 'http', _PORT, _ROOT,\n       'http://0.0.0.0:8888/i_am_root/'),\n      ('root_path_no_leading_slash', 'http', _PORT, '/i_am_root',\n       'http://0.0.0.0:8888/i_am_root/'),\n      ('http_on_port_80', 'http', 80, _ROOT, 'http://0.0.0.0/i_am_root/'),\n      ('https_on_port_443', 'https', 443, _ROOT, 'https://0.0.0.0/i_am_root/'),\n      ('https_without_root', 'https', 8000, '', 'https://0.0.0.0:8000/'))\n  def test_build_web_application_root_url(self, service_name: str, port: int,\n                                          root: str, url: str):\n    network_service = self.make_web_service(service_name)\n    network_service.network_endpoint.CopyFrom(self.make_ip_port_endpoint(port))\n    network_service.service_context.web_service_context.CopyFrom(\n        self.make_web_service_context(root))\n    self.assertEqual(\n        url,\n        network_service_utils.build_web_application_root_url(network_service))\n\n  def test_build_uri_network_service_with_ipv4(self):\n    network_service = self.make_web_service('https')\n    network_service.transport_protocol = TransportProtocol.TCP\n    network_service.service_context.web_service_context.CopyFrom(\n        self.make_web_service_context('/i_am_root'))\n    address_info = socket.getaddrinfo('localhost', 443)[0]\n    ip_address = address_info[4][0]\n    address_family = network_service_utils.get_address_family(address_info[0])\n\n    network_endpoint = NetworkEndpoint(\n        port=Port(port_number=443),\n        ip_address=IpAddress(\n            address=ip_address, address_family=address_family),\n        type=NetworkEndpoint.Type.IP_HOSTNAME_PORT,\n        hostname=Hostname(name='localhost')\n    )\n    network_service.network_endpoint.CopyFrom(network_endpoint)\n\n    self.assertEqual(\n        network_service,\n        network_service_utils.build_uri_network_service(\n            'https://localhost/i_am_root'))\n\n  @mock.patch.object(\n      socket,\n      'getaddrinfo',\n      new=mock.MagicMock(\n          return_value=[[socket.AF_INET6, None, None, None, ['::1']]]))\n  def test_build_uri_network_service_with_ipv6(self):\n    network_service = self.make_web_service('http')\n    network_service.transport_protocol = TransportProtocol.TCP\n    network_service.service_context.web_service_context.CopyFrom(\n        self.make_web_service_context('/i_am_root'))\n\n    network_endpoint = NetworkEndpoint(\n        port=Port(port_number=80),\n        ip_address=IpAddress(\n            address='::1', address_family=AddressFamily.IPV6),\n        type=NetworkEndpoint.Type.IP_HOSTNAME_PORT,\n        hostname=Hostname(name='some_hostname_with_ipv6')\n    )\n    network_service.network_endpoint.CopyFrom(network_endpoint)\n\n    self.assertEqual(\n        network_service,\n        network_service_utils.build_uri_network_service(\n            'http://some_hostname_with_ipv6/i_am_root'))\n\n  @mock.patch.object(\n      urllib.parse,\n      'urlparse',\n      new=mock.MagicMock(\n          return_value={\n              'hostname': 'some_hostname_with_ipv6',\n              'scheme': 'http',\n              'port': 0,\n              'path': 'i_am_root'\n          }))\n  @mock.patch.object(\n      socket,\n      'getaddrinfo',\n      new=mock.MagicMock(\n          return_value=[[socket.AF_INET6, None, None, None, ['::1']]]))\n  def test_build_uri_network_service_with_port_number_zero(self):\n    network_service = self.make_web_service('http')\n    network_service.transport_protocol = TransportProtocol.TCP\n    network_service.service_context.web_service_context.CopyFrom(\n        self.make_web_service_context('/i_am_root'))\n\n    network_endpoint = NetworkEndpoint(\n        port=Port(port_number=0),\n        ip_address=IpAddress(\n            address='::1', address_family=AddressFamily.IPV6),\n        type=NetworkEndpoint.Type.IP_HOSTNAME_PORT,\n        hostname=Hostname(name='some_hostname_with_ipv6')\n    )\n    network_service.network_endpoint.CopyFrom(network_endpoint)\n\n    self.assertEqual(\n        network_service,\n        network_service_utils.build_uri_network_service(\n            'http://some_hostname_with_ipv6:0/i_am_root'))\n\n  def test_build_uri_network_service_with_invalid_scheme(self):\n    with self.assertRaises(ValueError) as err:\n      network_service_utils.build_uri_network_service(\n          'http-alt://localhost/i_am_root')\n    self.assertEqual(\n        \"URI scheme should be one of the following: 'http', 'https'\",\n        str(err.exception))\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/common/net/http/host_resolver_http_adapter.py",
    "content": "\"\"\"Custom HTTP Adapter to handle host-based routing support for load balancers.\"\"\"\n\nimport socket\nfrom typing import Optional\nfrom urllib import parse\n\nimport requests\n\nfrom common.net.http.http_header_fields import HttpHeaderFields\n\n\nclass HostResolverHttpAdapter(requests.adapters.HTTPAdapter):\n  \"\"\"Custom HTTP adapter for proper hostname resolution.\n\n  When load balancers are used, there is a chance that the hostname does not\n  resolve to the IP address of the vulnerable application. When the hostname\n  does not resolve to the given IP address, the IP address returned by NMAP is\n  prioritized and used in the \"netloc\" portion of the URL (see\n  parse.urlsplit()). This Adapter also adds the host header of the request\n  package that would have been otherwise omitted by default.\n\n  Attributes:\n    pool_connections: Number of connection pools to cache.\n    pool_max: Maximum number of connections to save in the pool.\n  \"\"\"\n\n  def __init__(self, pool_connections: int, pool_maxsize: int):\n    super().__init__(\n        pool_connections=pool_connections, pool_maxsize=pool_maxsize\n    )\n\n  def _add_host_header(\n      self, request: requests.PreparedRequest, hostname: str\n  ) -> None:\n    \"\"\"Adds host:port as the host header.\"\"\"\n    request.headers[HttpHeaderFields.HOST.value] = hostname\n\n  def _require_ipv6_brackets(self, ip: str) -> str:\n    \"\"\"Adds enclosing brackets if IPV6.\"\"\"\n    try:\n      socket.inet_pton(socket.AF_INET6, ip)\n      return \"[%s]\" % ip\n    except OSError:\n      return ip\n\n  def _resolve(self,\n               hostname: str,\n               ip: Optional[str] = None,\n               port: Optional[int] = None) -> Optional[str]:\n    \"\"\"Use the hostname if it resolves to the ip, else use the ip address.\n\n    Args:\n      hostname: Hostname of the target network. This could be the domain name or\n        the IP address.\n      ip: Optional IP address of target network.\n      port: Optional port of target network.\n\n    Returns:\n      String of the resolved hostname.\n    \"\"\"\n    if hostname == ip or not ip or ip in socket.getaddrinfo(hostname, port):\n      return hostname\n    return ip\n\n  def send(\n      self,\n      request: requests.PreparedRequest,\n      ip: Optional[str] = None,\n      **kwargs\n  ) -> requests.Response:\n    result = parse.urlparse(request.url)\n    self._add_host_header(request, result.netloc)\n    # use local dns\n    resolved_host = self._resolve(result.hostname, ip=ip, port=result.port)\n    if resolved_host != result.hostname:\n      resolved_host = self._require_ipv6_brackets(resolved_host)\n      netloc = result.netloc.lower().replace(result.hostname, resolved_host)\n      request.url = parse.urlunparse((\n          result.scheme,\n          netloc,\n          result.path,\n          result.params,\n          result.query,\n          result.fragment,\n      ))\n    return super().send(request, **kwargs)\n"
  },
  {
    "path": "plugin_server/py/common/net/http/host_resolver_http_adapter_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.common.net.requests_http_client.\"\"\"\n\nfrom unittest import mock\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport requests\n\nfrom common.net.http.host_resolver_http_adapter import HostResolverHttpAdapter\nfrom common.net.http.http_header_fields import HttpHeaderFields\nfrom common.net.http.http_method import HttpMethod\n\n\nclass HostResolverHttpAdapterTest(parameterized.TestCase):\n\n  @classmethod\n  def setUpClass(cls):\n    super().setUpClass()\n    cls.custom_adapter = HostResolverHttpAdapter(5, 10)\n\n  def setUp(self):\n    super().setUp()\n    self.addCleanup(mock.patch.stopall)\n    # Mock of requests's HTTPAdapter\n    response = requests.Response()\n    response.status_code = 200\n    mock.patch.object(\n        requests.adapters.HTTPAdapter,\n        'send',\n        return_value=response,\n    ).start()\n    # Mock hostname lookup\n    self.mock_getaddrinfo = mock.patch('socket.getaddrinfo').start()\n\n  @parameterized.named_parameters(\n      ('with_hostname', 'vuln-app.com'),\n      ('with_ipv4', '199.21.82.88'),\n      (\n          'with_ipv6',\n          '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]',\n      ),\n  )\n  def test_send_dispatches_with_host_header(self, host):\n    url = 'http://{}:8080/send'.format(host)\n    request = self._prepare_request(url)\n\n    self.custom_adapter.send(request)\n\n    requests.adapters.HTTPAdapter.send.assert_called_with(request)\n    self.assertEqual(\n        request.headers.get(HttpHeaderFields.HOST.value), '{}:8080'.format(host)\n    )\n\n  def test_send_without_target_ip_dispatches_default_hostname(self):\n    url = 'http://vuln-app.com:8080/send'\n    request = self._prepare_request(url)\n\n    self.custom_adapter.send(request)\n\n    requests.adapters.HTTPAdapter.send.assert_called_with(request)\n    self.assertEqual(request.url, url)\n\n  def test_send_when_hostname_resolves_to_ip_uses_default_hostname(self):\n    url = 'http://vuln-app.com:8080/send'\n    ip = '199.21.82.88'\n    request = self._prepare_request(url)\n\n    self.mock_getaddrinfo.return_value = [ip]\n    self.custom_adapter.send(request, ip=ip)\n\n    requests.adapters.HTTPAdapter.send.assert_called_once_with(request)\n    self.assertEqual(\n        request.headers.get(HttpHeaderFields.HOST.value), 'vuln-app.com:8080'\n    )\n    self.assertEqual(request.url, url)\n\n  def test_send_when_hostname_is_the_ip_uses_default_hostname(self):\n    ip = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'\n    url = 'http://[{}]:8080/send'.format(ip)\n    request = self._prepare_request(url)\n\n    self.custom_adapter.send(request, ip=ip)\n\n    requests.adapters.HTTPAdapter.send.assert_called_once_with(request)\n    self.assertEqual(\n        request.headers.get(HttpHeaderFields.HOST.value),\n        '[{}]:8080'.format(ip))\n    self.assertEqual(request.url, url)\n\n  def test_send_when_hostname_is_case_insensitive(self):\n    url = 'http://vuln-APP.com:8080/send'\n    ip = '199.21.82.88'\n    request = self._prepare_request(url)\n\n    self.custom_adapter.send(request, ip=ip)\n\n    requests.adapters.HTTPAdapter.send.assert_called_once_with(request)\n    self.assertEqual(\n        request.headers.get(HttpHeaderFields.HOST.value), 'vuln-APP.com:8080'\n    )\n    self.assertEqual(request.url, 'http://199.21.82.88:8080/send')\n\n  def test_send_when_hostname_does_not_resolve_to_ipv4_uses_ipv4(self):\n    url = 'http://vuln-app.com:8080/send'\n    ip = '199.21.82.88'\n    request = self._prepare_request(url)\n\n    self.mock_getaddrinfo.return_value = ['1.1.1.1']\n    self.custom_adapter.send(request, ip=ip)\n\n    requests.adapters.HTTPAdapter.send.assert_called_once_with(request)\n    self.assertEqual(\n        request.headers.get(HttpHeaderFields.HOST.value), 'vuln-app.com:8080'\n    )\n    self.assertEqual(request.url, 'http://199.21.82.88:8080/send')\n\n  def test_send_when_hostname_does_not_resolve_to_ipv6_uses_ipv6(self):\n    url = 'http://vuln-app.com:8080/send'\n    ip = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'\n    request = self._prepare_request(url)\n\n    self.mock_getaddrinfo.return_value = ['1.1.1.1']\n    self.custom_adapter.send(request, ip=ip)\n\n    requests.adapters.HTTPAdapter.send.assert_called_once_with(request)\n    self.assertEqual(\n        request.headers.get(HttpHeaderFields.HOST.value), 'vuln-app.com:8080'\n    )\n    self.assertEqual(\n        request.url,\n        'http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080/send',\n    )\n\n  def _prepare_request(self, url):\n    request = requests.Request(\n        method=HttpMethod.GET, url=url, data=b'HTML content'\n    )\n    request = request.prepare()\n    request.url = url\n    return request\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_client.py",
    "content": "\"\"\"Base class for HTTP client.\"\"\"\nimport abc\n\nfrom typing import Optional\nfrom common.data.network_service_utils import NetworkService\nfrom common.net.http.http_request import HttpRequest\nfrom common.net.http.http_response import HttpResponse\n\n\nclass HttpClient(metaclass=abc.ABCMeta):\n  \"\"\"HTTP client library used for communicating with remote servers.\n\n  Attributes:\n    allow_redirects: Optional boolean to determine whether requests may be\n      redirected. True by default.\n    log_id: the log id.\n    timeout_sec: Optional float. How long to wait for the server to send\n      data before giving up.\n    verify_ssl: Optional boolean to verify SSL certification. True by default.\n  \"\"\"\n  TSUNAMI_USER_AGENT = 'TsunamiSecurityScanner'\n\n  def __init__(self,\n               log_id: Optional[str] = None,\n               timeout_sec: Optional[float] = None,\n               allow_redirects: Optional[bool] = True,\n               verify_ssl: Optional[bool] = True):\n    self.allow_redirects = allow_redirects\n    self.log_id = log_id\n    self.timeout_sec = timeout_sec\n    self.verify_ssl = verify_ssl\n\n  @abc.abstractmethod\n  def get_log_id(self) -> str:\n    pass\n\n  @abc.abstractmethod\n  def send(self,\n           http_request: HttpRequest,\n           network_service: Optional[NetworkService] = None) -> HttpResponse:\n    \"\"\"Send the HTTP request using this client.\"\"\"\n    pass\n\n  @abc.abstractmethod\n  def send_async(\n      self,\n      http_request: HttpRequest,\n      network_service: Optional[NetworkService] = None) -> HttpResponse:\n    \"\"\"Send the HTTP request asynchronously.\"\"\"\n    pass\n\n  @abc.abstractmethod\n  def modify(self):\n    \"\"\"Allows client code to modify the configurations of the HTTP client.\"\"\"\n    pass\n\n\nclass Builder(metaclass=abc.ABCMeta):\n  \"\"\"Base builder for implementations of HttpClient.\"\"\"\n\n  @abc.abstractmethod\n  def set_allow_redirects(self, allow_redirects: bool):\n    pass\n\n  @abc.abstractmethod\n  def set_verify_ssl(self, verify_ssl: bool):\n    pass\n\n  @abc.abstractmethod\n  def set_log_id(self, log_id: str):\n    pass\n\n  @abc.abstractmethod\n  def set_timeout_sec(self, timeout_sec: float):\n    pass\n\n  @abc.abstractmethod\n  def build(self):\n    pass\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_header_fields.py",
    "content": "\"\"\"HTTP header field names.\"\"\"\n\nimport enum\n\n\nclass HttpHeaderFields(enum.Enum):\n  \"\"\"HTTP header field definition from https://guava.dev/releases/snapshot/api/docs/com/google/common/net/HttpHeaders.html.\n\n  Attributes:\n    list: Get all header fields.\n    get_from_lower: Get header field name from a case insensitive name.\n  \"\"\"\n\n  # HTTP Request and Response header fields\n\n  CACHE_CONTROL = \"Cache-Control\"\n  CONTENT_LENGTH = \"Content-Length\"\n  CONTENT_TYPE = \"Content-Type\"\n  DATE = \"Date\"\n  PRAGMA = \"Pragma\"\n  VIA = \"Via\"\n  WARNING = \"Warning\"\n\n  # HTTP Request header fields\n\n  ACCEPT = \"Accept\"\n  ACCEPT_CHARSET = \"Accept-Charset\"\n  ACCEPT_ENCODING = \"Accept-Encoding\"\n  ACCEPT_LANGUAGE = \"Accept-Language\"\n  ACCESS_CONTROL_REQUEST_HEADERS = \"Access-Control-Request-Headers\"\n  ACCESS_CONTROL_REQUEST_METHOD = \"Access-Control-Request-Method\"\n  AUTHORIZATION = \"Authorization\"\n  CONNECTION = \"Connection\"\n  COOKIE = \"Cookie\"\n  CROSS_ORIGIN_RESOURCE_POLICY = \"Cross-Origin-Resource-Policy\"\n  EARLY_DATA = \"Early-Data\"\n  EXPECT = \"Expect\"\n  FROM = \"From\"\n  FORWARDED = \"Forwarded\"\n  FOLLOW_ONLY_WHEN_PRERENDER_SHOWN = \"Follow-Only-When-Prerender-Shown\"\n  HOST = \"Host\"\n  HTTP2_SETTINGS = \"HTTP2-Settings\"\n  IF_MATCH = \"If-Match\"\n  IF_MODIFIED_SINCE = \"If-Modified-Since\"\n  IF_NONE_MATCH = \"If-None-Match\"\n  IF_RANGE = \"If-Range\"\n  IF_UNMODIFIED_SINCE = \"If-Unmodified-Since\"\n  LAST_EVENT_ID = \"Last-Event-ID\"\n  MAX_FORWARDS = \"Max-Forwards\"\n  ORIGIN = \"Origin\"\n  ORIGIN_ISOLATION = \"Origin-Isolation\"\n  PROXY_AUTHORIZATION = \"Proxy-Authorization\"\n  RANGE = \"Range\"\n  REFERER = \"Referer\"\n  REFERRER_POLICY = \"Referrer-Policy\"\n  NO_REFERRER = \"no-referrer\"\n  NO_REFFERER_WHEN_DOWNGRADE = \"no-referrer-when-downgrade\"\n  SAME_ORIGIN = \"same-origin\"\n  STRICT_ORIGIN = \"strict-origin\"\n  ORIGIN_WHEN_CROSS_ORIGIN = \"origin-when-cross-origin\"\n  STRICT_ORIGIN_WHEN_CROSS_ORIGIN = \"strict-origin-when-cross-origin\"\n  UNSAFE_URL = \"unsafe-url\"\n  SERVICE_WORKER = \"Service-Worker\"\n  TE = \"TE\"\n  UPGRADE = \"Upgrade\"\n  UPGRADE_INSECURE_REQUESTS = \"Upgrade-Insecure-Requests\"\n  USER_AGENT = \"User-Agent\"\n\n  # HTTP Response header fields\n\n  ACCEPT_RANGES = \"Accept-Ranges\"\n  ACCESS_CONTROL_ALLOW_HEADERS = \"Access-Control-Allow-Headers\"\n  ACCESS_CONTROL_ALLOW_METHODS = \"Access-Control-Allow-Methods\"\n  ACCESS_CONTROL_ALLOW_ORIGIN = \"Access-Control-Allow-Origin\"\n  ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = \"Access-Control-Allow-Private-Network\"\n  ACCESS_CONTROL_ALLOW_CREDENTIALS = \"Access-Control-Allow-Credentials\"\n  ACCESS_CONTROL_EXPOSE_HEADERS = \"Access-Control-Expose-Headers\"\n  ACCESS_CONTROL_MAX_AGE = \"Access-Control-Max-Age\"\n  AGE = \"Age\"\n  ALLOW = \"Allow\"\n  CONTENT_DISPOSITION = \"Content-Disposition\"\n  CONTENT_ENCODING = \"Content-Encoding\"\n  CONTENT_LANGUAGE = \"Content-Language\"\n  CONTENT_LOCATION = \"Content-Location\"\n  CONTENT_MD5 = \"Content-MD5\"\n  CONTENT_RANGE = \"Content-Range\"\n  CONTENT_SECURITY_POLICY = \"Content-Security-Policy\"\n  CONTENT_SECURITY_POLICY_REPORT_ONLY = \"Content-Security-Policy-Report-Only\"\n  X_CONTENT_SECURITY_POLICY = \"X-Content-Security-Policy\"\n  X_CONTENT_SECURITY_POLICY_REPORT_ONLY = \"X-Content-Security-Policy-Report-Only\"\n  X_WEBKIT_CSP = \"X-WebKit-CSP\"\n  X_WEBKIT_CSP_REPORT_ONLY = \"X-WebKit-CSP-Report-Only\"\n  CROSS_ORIGIN_EMBEDDER_POLICY = \"Cross-Origin-Embedder-Policy\"\n  CROSS_ORIGIN_EMBEDDER_POLICY_REPORT_ONLY = \"Cross-Origin-Embedder-Policy-Report-Only\"\n  CROSS_ORIGIN_OPENER_POLICY = \"Cross-Origin-Opener-Policy\"\n  ETAG = \"ETag\"\n  EXPIRES = \"Expires\"\n  LAST_MODIFIED = \"Last-Modified\"\n  LINK = \"Link\"\n  LOCATION = \"Location\"\n  KEEP_ALIVE = \"Keep-Alive\"\n  NO_VARY_SEARCH = \"No-Vary-Search\"\n  ORIGIN_TRIAL = \"Origin-Trial\"\n  P3P = \"P3P\"\n  PROXY_AUTHENTICATE = \"Proxy-Authenticate\"\n  REFRESH = \"Refresh\"\n  REPORT_TO = \"Report-To\"\n  RETRY_AFTER = \"Retry-After\"\n  SERVER = \"Server\"\n  SERVER_TIMING = \"Server-Timing\"\n  SERVICE_WORKER_ALLOWED = \"Service-Worker-Allowed\"\n  SET_COOKIE = \"Set-Cookie\"\n  SET_COOKIE2 = \"Set-Cookie2\"\n  SOURCE_MAP = \"SourceMap\"\n  SUPPORTS_LOADING_MODE = \"Supports-Loading-Mode\"\n  STRICT_TRANSPORT_SECURITY = \"Strict-Transport-Security\"\n  TIMING_ALLOW_ORIGIN = \"Timing-Allow-Origin\"\n  TRAILER = \"Trailer\"\n  TRANSFER_ENCODING = \"Transfer-Encoding\"\n  VARY = \"Vary\"\n  WWW_AUTHENTICATE = \"WWW-Authenticate\"\n  DNT = \"DNT\"\n  X_CONTENT_TYPE_OPTIONS = \"X-Content-Type-Options\"\n  X_DEVICE_IP = \"X-Device-IP\"\n  X_DEVICE_REFERER = \"X-Device-Referer\"\n  X_DEVICE_ACCEPT_LANGUAGE = \"X-Device-Accept-Language\"\n  X_DEVICE_REQUESTED_WITH = \"X-Device-Requested-With\"\n  X_DO_NOT_TRACK = \"X-Do-Not-Track\"\n  X_FORWARDED_FOR = \"X-Forwarded-For\"\n  X_FORWARDED_PROTO = \"X-Forwarded-Proto\"\n  X_FORWARDED_HOST = \"X-Forwarded-Host\"\n  X_FORWARDED_PORT = \"X-Forwarded-Port\"\n  X_FRAME_OPTIONS = \"X-Frame-Options\"\n  X_POWERED_BY = \"X-Powered-By\"\n  PUBLIC_KEY_PINS = \"Public-Key-Pins\"\n  PUBLIC_KEY_PINS_REPORT_ONLY = \"Public-Key-Pins-Report-Only\"\n  X_REQUEST_ID = \"X-Request-ID\"\n  X_REQUESTED_WITH = \"X-Requested-With\"\n  X_USER_IP = \"X-User-IP\"\n  X_DOWNLOAD_OPTIONS = \"X-Download-Options\"\n  X_XSS_PROTECTION = \"X-XSS-Protection\"\n  X_DNS_PREFETCH_CONTROL = \"X-DNS-Prefetch-Control\"\n  PING_FROM = \"Ping-From\"\n  PING_TO = \"Ping-To\"\n  PURPOSE = \"Purpose\"\n  X_PURPOSE = \"X-Purpose\"\n  X_MOZ = \"X-Moz\"\n  DEVICE_MEMORY = \"Device-Memory\"\n  DOWNLINK = \"Downlink\"\n  ECT = \"ECT\"\n  RTT = \"RTT\"\n  SAVE_DATA = \"Save-Data\"\n  VIEWPORT_WIDTH = \"Viewport-Width\"\n  WIDTH = \"Width\"\n  PERMISSIONS_POLICY = \"Permissions-Policy\"\n  SEC_CH_PREFERS_COLOR_SCHEME = \"Sec-CH-Prefers-Color-Scheme\"\n  ACCEPT_CH = \"Accept-CH\"\n  CRITICAL_CH = \"Critical-CH\"\n  SEC_CH_UA = \"Sec-CH-UA\"\n  SEC_CH_UA_ARCH = \"Sec-CH-UA-Arch\"\n  SEC_CH_UA_MODEL = \"Sec-CH-UA-Model\"\n  SEC_CH_UA_PLATFORM = \"Sec-CH-UA-Platform\"\n  SEC_CH_UA_PLATFORM_VERSION = \"Sec-CH-UA-Platform-Version\"\n  SEC_CH_UA_FULL_VERSION_LIST = \"Sec-CH-UA-Full-Version-List\"\n  SEC_CH_UA_MOBILE = \"Sec-CH-UA-Mobile\"\n  SEC_CH_UA_WOW64 = \"Sec-CH-UA-WoW64\"\n  SEC_CH_UA_BITNESS = \"Sec-CH-UA-Bitness\"\n  SEC_CH_VIEWPORT_WIDTH = \"Sec-CH-Viewport-Width\"\n  SEC_CH_VIEWPORT_HEIGHT = \"Sec-CH-Viewport-Height\"\n  SEC_CH_DPR = \"Sec-CH-DPR\"\n  SEC_FETCH_DEST = \"Sec-Fetch-Dest\"\n  SEC_FETCH_MODE = \"Sec-Fetch-Mode\"\n  SEC_FETCH_SITE = \"Sec-Fetch-Site\"\n  SEC_FETCH_USER = \"Sec-Fetch-User\"\n  SEC_METADATA = \"Sec-Metadata\"\n  SEC_TOKEN_BINDING = \"Sec-Token-Binding\"\n  SEC_PROVIDED_TOKEN_BINDING_ID = \"Sec-Provided-Token-Binding-ID\"\n  SEC_REFERRED_TOKEN_BINDING_ID = \"Sec-Referred-Token-Binding-ID\"\n  SEC_WEBSOCKET_ACCEPT = \"Sec-WebSocket-Accept\"\n  SEC_WEBSOCKET_EXTENSIONS = \"Sec-WebSocket-Extensions\"\n  SEC_WEBSOCKET_KEY = \"Sec-WebSocket-Key\"\n  SEC_WEBSOCKET_PROTOCOL = \"Sec-WebSocket-Protocol\"\n  SEC_WEBSOCKET_VERSION = \"Sec-WebSocket-Version\"\n  CDN_LOOP = \"CDN-Loop\"\n\n  @classmethod\n  def list(cls):\n    return list(map(lambda field: field.value, HttpHeaderFields))\n\n  @classmethod\n  def get_from_lower(cls, name):\n    for field in cls:\n      if field.value.lower() == name.lower():\n        return field.value\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_header_fields_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.common.net.http_headers.\"\"\"\n\nfrom absl.testing import absltest\nfrom common.net.http import http_header_fields\n\n\nHttpHeaderFields = http_header_fields.HttpHeaderFields\n\n\nclass HttpHeaderFieldsTest(absltest.TestCase):\n  def test_list_returns_all_header_fields(self):\n    self.assertEqual(HttpHeaderFields.list().__len__(), 165)\n\n  def test_get_from_lower_with_existing_field(self):\n    self.assertEqual(\n        HttpHeaderFields.get_from_lower('CDN-LOOP'),\n        HttpHeaderFields.CDN_LOOP.value)\n\n  def test_get_from_lower_with_unknown_field(self):\n    self.assertIsNone(HttpHeaderFields.get_from_lower('CDN-LAP'))\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_headers.py",
    "content": "\"\"\"HTTP headers utility.\"\"\"\n\nimport collections\nimport re\nfrom typing import Optional\nfrom common.net.http.http_header_fields import HttpHeaderFields\n\n\nclass HttpHeaders:\n  \"\"\"HTTP headers utility class.\n\n  Please use Builder() to create instances of this class.\n\n  Attributes:\n    raw_headers: Collection of header fields and values. Each field could have\n      multiple values.\n  \"\"\"\n\n  def __init__(self):\n    self.raw_headers = collections.defaultdict(list)\n\n  def names(self) -> set[str]:\n    \"\"\"Get the list of unique header field names.\"\"\"\n    return set(self.raw_headers.keys())\n\n  def get(self, name: str) -> Optional[str]:\n    \"\"\"Get the first value for a specified HTTP field name.\"\"\"\n    values = self.get_all(name)\n    if not values:\n      return None\n    return values[0]\n\n  def get_all(self, name: str) -> list[str]:\n    \"\"\"Get all values for a specified HTTP field name.\n\n    Values are in the order they were added to the builder.\n\n    Args:\n      name: header name\n\n    Returns:\n      List of matched header values.\n    \"\"\"\n    if name is None:\n      raise ValueError('Name cannot be None.')\n    values = self.raw_headers.get(name, [])\n    if values:\n      return values\n    canonicalized_name = _canonicalize(name)\n    return self.raw_headers.get(canonicalized_name, [])\n\n  @classmethod\n  def builder(cls) -> 'Builder':\n    return Builder()\n\n\nclass Builder:\n  \"\"\"Builder class to create HTTP headers object.\n\n  Attributes:\n    http_headers: Collection of header field names and corresponding values.\n  \"\"\"\n  # RFC 2616 section 4.2.\n  # Allow printable graphic characters except for colon\n  HEADER_NAME_MATCHER = '^([!-~])[^:]*$'\n  # No control characters except for horizontal tab and space\n  HEADER_VALUE_MATCHER = '^[^\\x00-\\x08\\x0A-\\x1f\\x7f]*$'\n\n  def __init__(self):\n    self.http_headers = HttpHeaders()\n\n  def build(self) -> HttpHeaders:\n    return self.http_headers\n\n  def add_header(self, name: str, value: str, canonicalize: bool = True):\n    \"\"\"Add HTTP header to headers object.\n\n    Args:\n      name: HTTP header field name\n      value: HTTP header value\n      canonicalize: Optional boolean to normalize header or not. Default is\n        True.\n\n    Returns:\n      The builder object.\n\n    Raises:\n      ValueError: If name or value is None. If header name or value pair does\n      not comply with standards.\n    \"\"\"\n    if name is None:\n      raise ValueError('Name cannot be None.')\n    if value is None:\n      raise ValueError('Value cannot be None.')\n    if canonicalize:\n      name = self._canonicalize_header_name(name, value)\n    self.http_headers.raw_headers[name].append(value)\n    return self\n\n  def _canonicalize_header_name(self, name, value) -> str:\n    if not self._is_legal_header_name(name):\n      raise ValueError('Illegal header name %s.' % name)\n    if not self._is_legal_header_value(value):\n      raise ValueError('Illegal header value %s.' % value)\n    return _canonicalize(name)\n\n  def _is_legal_header_name(self, name: str) -> bool:\n    return bool(re.fullmatch(self.HEADER_NAME_MATCHER, name))\n\n  def _is_legal_header_value(self, value: str) -> bool:\n    return bool(re.fullmatch(self.HEADER_VALUE_MATCHER, value))\n\n\ndef _canonicalize(header_name: str) -> str:\n  \"\"\"Normalize header field name.\n\n  Args:\n    header_name: An HTTP header field name.\n\n  Returns:\n    An HttpHeaderField value or the header_name in lowercase.\n  \"\"\"\n  try:\n    return HttpHeaderFields(header_name).value\n  except ValueError:\n    return HttpHeaderFields.get_from_lower(header_name) or header_name.lower()\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_headers_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.common.net.http_headers.\"\"\"\n\nimport collections\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom common.net.http import http_header_fields\nfrom common.net.http import http_headers\n\n\nHttpHeaderFields = http_header_fields.HttpHeaderFields\n\n\nclass HttpHeadersTest(parameterized.TestCase):\n  def test_builder_add_header_always_puts_in_headers_map(self):\n    headers = http_headers.Builder().add_header('test_h', 'test_v').build()\n    expected = collections.defaultdict(list, {'test_h': ['test_v']})\n    self.assertEqual(headers.raw_headers, expected)\n\n  def test_builder_add_header_with_known_header_canonicalizes_header_name(self):\n    field = HttpHeaderFields.ACCEPT_LANGUAGE.value\n    headers = http_headers.Builder().add_header(field.upper(), 'en').build()\n    expected = collections.defaultdict(list, {field: ['en']})\n    self.assertEqual(headers.raw_headers, expected)\n\n  def test_builder_add_header_with_enabled_canonicalization(self):\n    field = HttpHeaderFields.ACCEPT_LANGUAGE.value\n    headers = http_headers.Builder().add_header(field, 'en', True).build()\n    expected = collections.defaultdict(list, {field: ['en']})\n    self.assertEqual(headers.raw_headers, expected)\n\n  def test_builder_add_header_with_disabled_canonicalization_adds_raw_header(\n      self):\n    headers = http_headers.Builder().add_header('ACCEPT_Language', 'en',\n                                                False).build()\n    expected = collections.defaultdict(list, {'ACCEPT_Language': ['en']})\n    self.assertEqual(headers.raw_headers, expected)\n\n  @parameterized.named_parameters(\n      ('with_no_name', None, 'en', 'Name cannot be None.'),\n      ('with_no_value', HttpHeaderFields.ACCEPT_LANGUAGE.value, None,\n       'Value cannot be None.'),\n      ('with_illegal_header_name', ':::', 'en', 'Illegal header name :::.'),\n      ('with_illegal_header_value', HttpHeaderFields.ACCEPT_LANGUAGE.value,\n       chr(1), 'Illegal header value %s.' % chr(1)),\n  )\n  def test_builder_add_header_raises_error(self, field, value, message):\n    with self.assertRaises(ValueError) as exc:\n      http_headers.Builder().add_header(field, value).build()\n    self.assertEqual(message, str(exc.exception))\n\n  def test_names_always_returns_all_header_names(self):\n    headers = http_headers.Builder().add_header(\n        HttpHeaderFields.ACCEPT.value, 'application/xml').add_header(\n            HttpHeaderFields.CONTENT_TYPE.value,\n            'text/html').add_header(HttpHeaderFields.ACCEPT.value,\n                                    'image/webp').build()\n    expected = set(\n        [HttpHeaderFields.ACCEPT.value, HttpHeaderFields.CONTENT_TYPE.value])\n    self.assertEqual(headers.names(), expected)\n\n  def test_get_when_requested_header_exists_returns_requested_header(self):\n    headers = http_headers.Builder().add_header(\n        HttpHeaderFields.ACCEPT.value,\n        'application/xml').add_header(HttpHeaderFields.CONTENT_TYPE.value,\n                                      'text/html').build()\n    self.assertEqual(\n        headers.get(HttpHeaderFields.ACCEPT.value), 'application/xml')\n\n  def test_get_when_multiple_values_exist_returns_first_value(self):\n    headers = http_headers.Builder().add_header(\n        HttpHeaderFields.ACCEPT.value, 'application/xml').add_header(\n            HttpHeaderFields.CONTENT_TYPE.value,\n            'text/html').add_header(HttpHeaderFields.ACCEPT.value,\n                                    'image/webp').build()\n    self.assertEqual(\n        headers.get(HttpHeaderFields.ACCEPT.value), 'application/xml')\n\n  def test_get_when_requested_header_does_not_exist_returns_none(self):\n    headers = http_headers.Builder().add_header(HttpHeaderFields.ACCEPT.value,\n                                                'app/xml').build()\n    self.assertIsNone(headers.get('cookie'))\n\n  def test_get_with_none_header_name_raise_error(self):\n    headers = http_headers.Builder().add_header(HttpHeaderFields.ACCEPT.value,\n                                                'app/xml').build()\n    with self.assertRaises(ValueError) as exc:\n      headers.get(None)\n    self.assertEqual('Name cannot be None.', str(exc.exception))\n\n  def test_get_all_always_returns_requested_values(self):\n    headers = http_headers.Builder().add_header(\n        HttpHeaderFields.ACCEPT.value, 'application/xml').add_header(\n            HttpHeaderFields.CONTENT_TYPE.value,\n            'text/html').add_header(HttpHeaderFields.ACCEPT.value,\n                                    'image/webp').build()\n    expected = ['application/xml', 'image/webp']\n    self.assertEqual(headers.get_all('accept'), expected)\n\n  def test_get_all_with_known_header_value_canonicalizes_requested_header(self):\n    headers = http_headers.Builder().add_header(\n        HttpHeaderFields.ACCEPT.value, 'application/xml').add_header(\n            HttpHeaderFields.CONTENT_TYPE.value,\n            'text/html').add_header(HttpHeaderFields.ACCEPT.value,\n                                    'image/webp').build()\n    expected = ['application/xml', 'image/webp']\n    self.assertEqual(headers.get_all('ACCEPT'), expected)\n\n  def test_get_all_when_request_value_does_not_exist_returns_empty_list(self):\n    headers = http_headers.Builder().add_header(\n        HttpHeaderFields.ACCEPT.value, 'application/xml').add_header(\n            HttpHeaderFields.CONTENT_TYPE.value,\n            'text/html').add_header(HttpHeaderFields.ACCEPT.value,\n                                    'image/webp').build()\n    self.assertEmpty(headers.get_all('cookie'))\n\n  def test_get_all_with_none_header_name_raise_error(self):\n    headers = http_headers.Builder().add_header(HttpHeaderFields.ACCEPT.value,\n                                                'app/xml').build()\n    with self.assertRaises(ValueError) as exc:\n      headers.get_all(None)\n    self.assertEqual('Name cannot be None.', str(exc.exception))\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_method.py",
    "content": "\"\"\"HTTP method for CRUD operations.\"\"\"\n\nimport enum\n\n\nclass HttpMethod(str, enum.Enum):\n  \"\"\"HTTP request type.\n\n  Attributes:\n    string: HTTP method.\n  \"\"\"\n  GET = \"GET\"\n  HEAD = \"HEAD\"\n  POST = \"POST\"\n  PUT = \"PUT\"\n  DELETE = \"DELETE\"\n\n  def __init__(self, string):\n    self.string = string\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_request.py",
    "content": "\"\"\"HTTP request utility.\"\"\"\n\nfrom typing import Optional\n\nfrom common.net.http.http_headers import Builder as HttpHeadersBuilder\nfrom common.net.http.http_headers import HttpHeaders\nfrom common.net.http.http_method import HttpMethod\n\n\ndef check_url_argument(func):\n  def wrapper(cls, url):\n    if not url:\n      raise ValueError('Url cannot be None.')\n    return func(cls, url)\n  return wrapper\n\n\nclass HttpRequest:\n  \"\"\"HTTP request utility class.\n\n  Please use Builder() to create instances of this class.\n\n  Attributes:\n    method: The HTTP request type.\n    url: String address of the request.\n    headers: The HTTP request headers in key/value pairs.\n    body: The HTTP body could be empty per the request type. GET and\n      HEAD request types must have empty request_body.\n  \"\"\"\n\n  def __init__(self):\n    self.method: HttpMethod = None\n    self.url: Optional[str] = None\n    self.headers: HttpHeaders = None\n    self.body: Optional[bytes] = None\n\n  @classmethod\n  @check_url_argument\n  def get(cls, url: str):\n    return Builder().set_method(HttpMethod.GET).set_url(url)\n\n  @classmethod\n  @check_url_argument\n  def head(cls, url: str):\n    return Builder().set_method(HttpMethod.HEAD).set_url(url)\n\n  @classmethod\n  @check_url_argument\n  def post(cls, url: str):\n    return Builder().set_method(HttpMethod.POST).set_url(url)\n\n  @classmethod\n  @check_url_argument\n  def put(cls, url: str):\n    return Builder().set_method(HttpMethod.PUT).set_url(url)\n\n  @classmethod\n  @check_url_argument\n  def delete(cls, url: str):\n    return Builder().set_method(HttpMethod.DELETE).set_url(url)\n\n  @classmethod\n  def builder(cls):\n    return Builder()\n\n\nclass Builder:\n  \"\"\"Builder class to create HTTP request object.\n\n  Attributes:\n    http_request: HTTP request object built.\n  \"\"\"\n\n  def __init__(self):\n    self.http_request = HttpRequest()\n\n  def set_method(self, method: HttpMethod) -> 'Builder':\n    \"\"\"Set the HttpMethod type.\"\"\"\n    self.http_request.method = method\n    return self\n\n  def set_url(self, url: str) -> 'Builder':\n    \"\"\"Set the string address for the request.\"\"\"\n    self.http_request.url = url\n    return self\n\n  def set_headers(self, headers: HttpHeaders) -> 'Builder':\n    \"\"\"Set the HttpHeaders for the request.\"\"\"\n    self.http_request.headers = headers\n    return self\n\n  def set_request_body(self, request_body: Optional[bytes] = None) -> 'Builder':\n    \"\"\"Set the request body.\"\"\"\n    self.http_request.body = request_body\n    return self\n\n  def with_empty_headers(self) -> 'Builder':\n    \"\"\"Set an empty Http_headers for the request.\"\"\"\n    self.set_headers(HttpHeadersBuilder().build())\n    return self\n\n  def build(self) -> 'HttpRequest':\n    if (\n        self.http_request.method == HttpMethod.GET\n        or self.http_request.method == HttpMethod.HEAD\n    ):\n      if self.http_request.body:\n        raise ValueError(\n            'A request body is not allowed for HTTP GET/HEAD request.')\n    return self.http_request\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_request_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.common.net.http_request.\"\"\"\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\n\nfrom common.net.http.http_headers import HttpHeaders\nfrom common.net.http.http_method import HttpMethod\nfrom common.net.http.http_request import HttpRequest\n\n\nclass HttpRequestTest(parameterized.TestCase):\n\n  @parameterized.named_parameters(\n      ('get_method', HttpRequest.get), ('head_method', HttpRequest.head),\n      ('post_method', HttpRequest.post), ('delete_method', HttpRequest.delete))\n  def test_http_request_methods_with_empty_url_raise_error(\n      self, request_method):\n    with self.assertRaises(ValueError) as exc:\n      request_method('')\n    self.assertEqual(str(exc.exception), 'Url cannot be None.')\n\n  def test_get_builds_http_get_request(self):\n    request = HttpRequest.get(\n        'http://localhost/url').with_empty_headers().build()\n    self.assertEqual(request.method, HttpMethod.GET)\n    self.assertEqual(request.url, 'http://localhost/url')\n\n  def test_head_builds_http_head_request(self):\n    request = HttpRequest.head(\n        'http://localhost/url').with_empty_headers().build()\n    self.assertEqual(request.method, HttpMethod.HEAD)\n    self.assertEqual(request.url, 'http://localhost/url')\n\n  def test_head_builds_http_post_request(self):\n    request = HttpRequest.post(\n        'http://localhost/url').with_empty_headers().build()\n    self.assertEqual(request.method, HttpMethod.POST)\n    self.assertEqual(request.url, 'http://localhost/url')\n\n  def test_head_builds_http_delete_request(self):\n    request = HttpRequest.delete(\n        'http://localhost/url').with_empty_headers().build()\n    self.assertEqual(request.method, HttpMethod.DELETE)\n    self.assertEqual(request.url, 'http://localhost/url')\n\n  def test_put_builds_http_put_request(self):\n    request = HttpRequest.put(\n        'http://localhost/url').with_empty_headers().build()\n    self.assertEqual(request.method, HttpMethod.PUT)\n    self.assertEqual(request.url, 'http://localhost/url')\n\n  def test_get_with_request_body_raise_error(self):\n    with self.assertRaises(ValueError) as exc:\n      HttpRequest.builder().set_method(\n          HttpMethod.GET).set_url('http://localhost/url').set_headers(\n              HttpHeaders.builder()).set_request_body(bytes(\n                  'abc', 'utf-8')).build()\n    self.assertEqual('A request body is not allowed for HTTP GET/HEAD request.',\n                     str(exc.exception))\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_response.py",
    "content": "\"\"\"HTTP response utility.\"\"\"\n\nimport abc\nfrom collections.abc import Mapping\nimport json\nfrom typing import Any\nfrom common.net.http.http_headers import HttpHeaders\nfrom common.net.http.http_status import HttpStatus\n\n\nclass HttpResponse(metaclass=abc.ABCMeta):\n  \"\"\"HTTP request utility class.\n\n  Please use Builder() to create instances of this class.\n\n  Attributes:\n    status: The HTTP response status.\n    url: String address that produced this HTTP response.\n    headers: The HTTP response headers in key/value pairs.\n    body: The HTTP body data.\n  \"\"\"\n\n  def __init__(self):\n    self.status: HttpStatus | None = None\n    self.url: str | None = None\n    self.headers: HttpHeaders | None = None\n    self.body: bytes | None = None\n\n  def body_string(self) -> str:\n    \"\"\"Get the body data as a UTF-8 encoded string.\"\"\"\n    if self.body is None:\n      return ''\n    return self.body.decode('utf-8')\n\n  def body_json(self) -> Mapping[str, Any] | None:\n    \"\"\"Parse the response body as JSON. Returns null if parsing failed.\"\"\"\n    try:\n      return json.loads(self.body_string())\n    except ValueError:\n      return None\n\n  def json_field_has_value(self, field: str, value: str):\n    \"\"\"Check if JSON body of the response has field and value pair.\"\"\"\n    body = self.body_json()\n    if body is not None:\n      return body.get(field) == value\n    return False\n\n  @classmethod\n  def builder(cls):\n    return Builder()\n\n\nclass Builder:\n  \"\"\"Builder class to create HTTP request object.\n\n  Attributes:\n    http_response: HTTP request object built.\n  \"\"\"\n\n  def __init__(self):\n    self.http_response = HttpResponse()\n\n  def set_status(self, status: HttpStatus):\n    \"\"\"Set the HttpStatus type.\"\"\"\n    self.http_response.status = status\n    return self\n\n  def set_headers(self, headers: HttpHeaders):\n    \"\"\"Set the HttpHeaders for the response.\"\"\"\n    self.http_response.headers = headers\n    return self\n\n  def set_response_body(self, response_body: bytes | None):\n    \"\"\"Set the body for the response.\"\"\"\n    self.http_response.body = response_body\n    return self\n\n  def set_url(self, url: str):\n    \"\"\"Set URL response.\"\"\"\n    self.http_response.url = url\n    return self\n\n  def build(self):\n    return self.http_response\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_response_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.common.net.http_response.\"\"\"\n\nfrom absl.testing import absltest\n\nfrom common.net.http.http_headers import HttpHeaders\nfrom common.net.http.http_response import HttpResponse\nfrom common.net.http.http_status import HttpStatus\n\n\nclass HttpResponseTest(absltest.TestCase):\n  def test_body_json_with_valid_response_body_returns_parsed_json(self):\n    response = HttpResponse.builder().set_status(\n        HttpStatus.OK).set_response_body(\n            bytes('{ \\\"test_value\\\": 1 }',\n                  'utf-8')).set_url('http://localhost/url').build()\n    self.assertNotEmpty(response.body_json())\n    self.assertTrue(type(response.body_json()), 'json')\n    self.assertEqual(response.body_json()['test_value'], 1)\n\n  def test_body_json_with_no_response_body_returns_null(self):\n    response = HttpResponse.builder().set_status(HttpStatus.OK).set_headers(\n        HttpHeaders.builder().build()).set_url('http://localhost/url').build()\n    self.assertIsNone(response.body_json())\n\n  def test_body_json_with_non_json_response_body_returns_null(self):\n    response = HttpResponse.builder().set_status(HttpStatus.OK).set_headers(\n        HttpHeaders.builder().build()).set_response_body(bytes(\n            'abc', 'utf-8')).set_url('http://localhost/url').build()\n    self.assertIsNone(response.body_json())\n\n  def test_json_field_has_value_with_empty_json_body_returns_false(self):\n    response = HttpResponse.builder().set_status(HttpStatus.OK).set_headers(\n        HttpHeaders.builder().build()).set_response_body(bytes(\n            '{ }', 'utf-8')).set_url('http://localhost/url').build()\n    self.assertFalse(response.json_field_has_value('field', 'value'))\n\n  def test_json_field_has_value_with_no_body_returns_false(self):\n    response = HttpResponse.builder().set_status(HttpStatus.OK).set_headers(\n        HttpHeaders.builder().build()).set_url('http://localhost/url').build()\n    self.assertFalse(response.json_field_has_value('field', 'value'))\n\n  def test_json_field_has_value_with_non_empty_json_body_returns_true(self):\n    response = HttpResponse.builder().set_status(HttpStatus.OK).set_headers(\n        HttpHeaders.builder().build()).set_response_body(\n            bytes('{ \\\"field\\\": \\\"value\\\"}',\n                  'utf-8')).set_url('http://localhost/url').build()\n    self.assertTrue(response.json_field_has_value('field', 'value'))\n\n  def test_is_success_with_unspecified_status_returns_false(self):\n    response = HttpResponse.builder().set_status(\n        HttpStatus.HTTP_STATUS_UNSPECIFIED).set_headers(\n            HttpHeaders.builder().build()).set_url(\n                'http://localhost/url').build()\n    self.assertFalse(response.status.is_success())\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_status.py",
    "content": "\"\"\"Http Status Codes utility class.\"\"\"\n\nimport aenum as enum\n\n\nclass HttpStatus(enum.MultiValueEnum):\n  \"\"\"HTTP Status Codes defined in RFC 2616, RFC 6585, RFC 4918 and RFC 7538.\n\n  @see <a href=\"http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html\"\n      target=\"_top\">http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html</a>\n  @see <a href=\"http://tools.ietf.org/html/rfc6585\"\n      target=\"_top\">http://tools.ietf.org/html/rfc6585</a>\n  @see <a href=\"https://tools.ietf.org/html/rfc4918\"\n      target=\"_top\">https://tools.ietf.org/html/rfc4918</a>\n  @see <a href=\"https://tools.ietf.org/html/rfc7538\"\n      target=\"_top\">https://tools.ietf.org/html/rfc7538</a>\n\n  Attributes:\n    from_code: Get the HttpStatus from status code.\n    is_redirect: Check if it is a redirected status response.\n    is_success: Check if it is a success status response.\n    to_string: Get the status name.\n  \"\"\"\n  # Default\n  HTTP_STATUS_UNSPECIFIED = 0, \"Status Unspecified\"\n\n  # Informational 1xx\n  CONTINUE = 100, \"Continue\"\n  SWITCHING_PROTOCOLS = 101, \"Switching Protocols\"\n\n  # Successful 2xx\n  OK = 200, \"OK\"\n  CREATED = 201, \"Created\"\n  ACCEPTED = 202, \"Accepted\"\n  NON_AUTHORITATIVE_INFORMATION = 203, \"Non-Authoritative Information\"\n  NO_CONTENT = 204, \"No Content\"\n  RESET_CONTENT = 205, \"Reset Content\"\n  PARTIAL_CONTENT = 206, \"Partial Content\"\n  MULTI_STATUS = 207, \"Multi-Status\"\n\n  # Redirection 3xx\n  MULTIPLE_CHOICES = 300, \"Multiple Choices\"\n  MOVED_PERMANENTLY = 301, \"Moved Permanently\"\n  FOUND = 302, \"Found\"\n  SEE_OTHER = 303, \"See Other\"\n  NOT_MODIFIED = 304, \"Not Modified\"\n  USE_PROXY = 305, \"Use Proxy\"\n  TEMPORARY_REDIRECT = 307, \"Temporary Redirect\"\n  PERMANENT_REDIRECT = 308, \"Permanent Redirect\"\n\n# Client Error 4xx\n  BAD_REQUEST = 400, \"Bad Request\"\n  UNAUTHORIZED = 401, \"Unauthorized\"\n  PAYMENT_REQUIRED = 402, \"Payment Required\"\n  FORBIDDEN = 403, \"Forbidden\"\n  NOT_FOUND = 404, \"Not Found\"\n  METHOD_NOT_ALLOWED = 405, \"Method Not Allowed\"\n  NOT_ACCEPTABLE = 406, \"Not Acceptable\"\n  PROXY_AUTHENTICATION_REQUIRED = 407, \"Proxy Authentication Required\"\n  REQUEST_TIMEOUT = 408, \"Request Timeout\"\n  CONFLICT = 409, \"Conflict\"\n  GONE = 410, \"Gone\"\n  LENGTH_REQUIRED = 411, \"Length Required\"\n  PRECONDITION_FAILED = 412, \"Precondition Failed\"\n  REQUEST_ENTITY_TOO_LARGE = 413, \"Request Entity Too Large\"\n  REQUEST_URI_TOO_LONG = 414, \"Request URI Too Long\"\n  UNSUPPORTED_MEDIA_TYPE = 415, \"Unsupported Media Type\"\n  REQUEST_RANGE_NOT_SATISFIABLE = 416, \"Request Range Not Satisfiable\"\n  EXPECTATION_FAILED = 417, \"Expectation Failed\"\n  UNPROCESSABLE_ENTITY = 422, \"Unprocessable Entity\"\n  LOCKED = 423, \"Locked\"\n  FAILED_DEPENDENCY = 424, \"Failed Dependency\"\n  PRECONDITION_REQUIRED = 428, \"Precondition Required\"\n  TOO_MANY_REQUESTS = 429, \"Too Many Requests\"\n  REQUEST_HEADER_FIELDS_TOO_LARGE = 431, \"Request Header Fields Too Large\"\n\n  # Server Error 5xx\n  INTERNAL_SERVER_ERROR = 500, \"Internal Server Error\"\n  NOT_IMPLEMENTED = 501, \"Not Implemented\"\n  BAD_GATEWAY = 502, \"Bad Gateway\"\n  SERVICE_UNAVAILABLE = 503, \"Service Unavailable\"\n  GATEWAY_TIMEOUT = 504, \"Gateway Timeout\"\n  HTTP_VERSION_NOT_SUPPORTED = 505, \"HTTP Version Not Supported\"\n  INSUFFICIENT_STORAGE = 507, \"Insufficient Storage\"\n  NETWORK_AUTHENTICATION_REQUIRED = 511, \"Network Authentication Required\"\n\n  # IE returns 1223 for 'Operation Aborted' instead of 204 with the status text\n  # 'Unknown' and an empty response headers. Known to occur on IE 6 on XP\n  # through IE9 on Window 7.\n  QUIRK_IE_NO_CONTENT = 1223, \"Quirk IE No Content\"\n\n  @classmethod\n  def from_code(cls, code: int):\n    \"\"\"Gets the HTTP status from the status code.\n\n    Args:\n      code: The HTTP status code.\n\n    Returns:\n      The matching HTTP status or HTTP_STATUS_UNSPECIFIED if no known status is\n      found.\n    \"\"\"\n    try:\n      status = HttpStatus(code)\n      return status\n    except ValueError:\n      return HttpStatus.HTTP_STATUS_UNSPECIFIED\n\n  def __init__(self, code, message=\"\"):\n    self.code = code\n    self.message = message\n\n  def __str__(self) -> str:\n    return self.message\n\n  def is_redirect(self) -> bool:\n    return bool(self in _REDIRECTED_HTTP_STATUS)\n\n  def is_success(self) -> bool:\n    return self.code >= 200 and self.code < 300\n\n\n_REDIRECTED_HTTP_STATUS = [\n    HttpStatus.MULTIPLE_CHOICES,\n    HttpStatus.MOVED_PERMANENTLY,\n    HttpStatus.FOUND,\n    HttpStatus.SEE_OTHER,\n    HttpStatus.TEMPORARY_REDIRECT,\n    HttpStatus.PERMANENT_REDIRECT,\n]\n"
  },
  {
    "path": "plugin_server/py/common/net/http/http_status_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.common.net.http_status.\"\"\"\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom common.net.http import http_status\n\nHttpStatus = http_status.HttpStatus\n\n\nclass HttpStatusTest(parameterized.TestCase):\n  def test_from_code_with_status_code_returns_http_status(self):\n    status = HttpStatus.from_code(200)\n    self.assertEqual(status, HttpStatus.OK)\n\n  def test_from_code_with_invalid_status_code_returns_http_status_unspecified(\n      self):\n    status = HttpStatus.from_code(1)\n    self.assertEqual(status, HttpStatus.HTTP_STATUS_UNSPECIFIED)\n\n  @parameterized.named_parameters(\n      ('with_multiple_choices', HttpStatus.MULTIPLE_CHOICES),\n      ('with_moved_permanently', HttpStatus.MOVED_PERMANENTLY),\n      ('with_found', HttpStatus.FOUND),\n      ('with_see_other', HttpStatus.SEE_OTHER),\n      ('with_temporary_redirect', HttpStatus.TEMPORARY_REDIRECT),\n      ('with_permanent_redirect', HttpStatus.PERMANENT_REDIRECT))\n  def test_is_redirect_returns_true(self, status):\n    self.assertTrue(status.is_redirect())\n\n  def test_is_redirect_with_non_redirected_status_returns_false(self):\n    self.assertFalse(HttpStatus.LOCKED.is_redirect())\n\n  def test_is_success_with_code_between_199_and_300(self):\n    self.assertTrue(HttpStatus.from_code(200).is_success())\n    self.assertTrue(HttpStatus.from_code(207).is_success())\n    self.assertFalse(HttpStatus.from_code(300).is_success())\n\n  def test___str__returns_status_message(self):\n    self.assertEqual(HttpStatus.CONTINUE.__str__(), 'Continue')\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/common/net/http/requests_http_client.py",
    "content": "\"\"\"HTTP client using requests.\"\"\"\n\nimport asyncio\nimport concurrent.futures\nimport functools\nfrom typing import Optional\n\nfrom absl import logging\nimport requests\n\nfrom common.data.network_service_utils import NetworkService\nfrom common.net.http.host_resolver_http_adapter import HostResolverHttpAdapter\nfrom common.net.http.http_client import Builder\nfrom common.net.http.http_client import HttpClient\nfrom common.net.http.http_header_fields import HttpHeaderFields\nfrom common.net.http.http_headers import HttpHeaders\nfrom common.net.http.http_request import HttpRequest\nfrom common.net.http.http_response import HttpResponse\nfrom common.net.http.http_status import HttpStatus\n\n\n_DEFAULT_ALLOW_REDIRECT = True\n_DEFAULT_POOL_CONNECTIONS = 5\n_DEFAULT_POOL_MAXSIZE = 10\n_DEFAULT_MAX_WORKERS = 64\n_TIMEOUT_SEC = 10\n_VERIFY_SSL = True\n\n\nclass RequestsHttpClient(HttpClient):\n  \"\"\"Requests HTTP client library used for communicating with remote servers.\n\n  Attributes:\n    allow_redirects: Optional boolean to determine whether requests may be\n      redirected. True by default.\n    log_id: the log id.\n    max_workers: Maximum number of threads to execute concurrently.\n    session: Requests session object to manage and persist settings across\n      requests.\n    timeout_sec: Optional float. How long to wait for the server to send\n      data before giving up.\n    verify_ssl: Optional boolean to verify SSL certification. True by default.\n  \"\"\"\n  TSUNAMI_USER_AGENT = 'TsunamiSecurityScanner'\n\n  def __init__(\n      self,\n      session: requests.Session,\n      allow_redirects: Optional[bool],\n      log_id: Optional[str],\n      max_workers: Optional[int],\n      timeout_sec: Optional[float],\n      verify_ssl: Optional[bool],\n  ):\n    self.session = session\n    self.allow_redirects = allow_redirects\n    self.log_id = log_id\n    self.max_workers = max_workers\n    self.timeout_sec = timeout_sec\n    self.verify_ssl = verify_ssl\n\n  def get_log_id(self) -> str:\n    return self.log_id\n\n  def send(self,\n           http_request: HttpRequest,\n           network_service: Optional[NetworkService] = None) -> HttpResponse:\n    \"\"\"Send the HTTP request using this client.\"\"\"\n    logging.info(\"%sSending HTTP '%s' request to '%s'.\", self.log_id,\n                 http_request.method, http_request.url)\n    req = self._prepare_request(http_request)\n    resp = self.session.send(\n        request=req,\n        ip=self._get_ip(network_service),\n        verify=self.verify_ssl,\n        timeout=self.timeout_sec,\n        allow_redirects=self.allow_redirects,\n    )\n    return self._parse_response(resp)\n\n  def send_async(\n      self,\n      http_request: HttpRequest,\n      network_service: Optional[NetworkService] = None) -> HttpResponse:\n    \"\"\"Send the HTTP request asynchronously.\"\"\"\n    logging.info(\"%sSending HTTP '%s' request to '%s'.\", self.log_id,\n                 http_request.method, http_request.url)\n    req = self._prepare_request(http_request)\n    loop = asyncio.get_event_loop()\n    future = asyncio.ensure_future(self._prepare_future(req, network_service))\n    loop.run_until_complete(future)\n    res = future.result()\n    return self._parse_response(res)\n\n  @classmethod\n  def modify(cls):\n    \"\"\"Allows client code to modify the configurations of the HTTP client.\"\"\"\n    return RequestsHttpClientBuilder()\n\n  def _build_response_headers(self, headers: dict[str, str]) -> HttpHeaders:\n    headers_builder = HttpHeaders.builder()\n    for field in headers:\n      headers_builder.add_header(field, headers[field])\n    return headers_builder.build()\n\n  def _parse_response(self, res: requests.Response) -> HttpResponse:\n    response_header = self._build_response_headers(res.headers)\n    status = HttpStatus.from_code(res.status_code)\n    return (\n        HttpResponse.builder()\n        .set_url(res.url)\n        .set_status(status)\n        .set_headers(response_header)\n        .set_response_body(res.content)\n        .build()\n    )\n\n  async def _prepare_future(\n      self,\n      req: requests.PreparedRequest,\n      network_service: Optional[NetworkService],\n  ):\n    \"\"\"Prepare async request to include configuration.\"\"\"\n    loop = asyncio.get_event_loop()\n    future = loop.run_in_executor(\n        concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers),\n        functools.partial(\n            self.session.send,\n            request=req,\n            ip=self._get_ip(network_service),\n            verify=self.verify_ssl,\n            timeout=self.timeout_sec,\n            allow_redirects=self.allow_redirects,\n        ),\n    )\n    return await future\n\n  def _prepare_request(\n      self, http_request: HttpRequest\n  ) -> requests.PreparedRequest:\n    \"\"\"Prepare request to bypass Requests library's canonicalization.\n\n    Client can accept requests with any URL paths such as paths with \"../..\" and\n    special characters.\n\n    Args:\n      http_request: HTTP request to prep for.\n\n    Returns:\n      PreparedRequest: Request containing the exact bytes that is ready to be\n        sent.\n    \"\"\"\n    req = requests.Request(\n        method=http_request.method,\n        url=http_request.url,\n        data=http_request.body,\n        headers=self._serialize_request_headers(http_request.headers)\n        )\n    prepped = req.prepare()\n    # URL reassignment to bypass URL canonicalization\n    prepped.url = http_request.url\n    return prepped\n\n  def _serialize_request_headers(self, headers: HttpHeaders) -> dict[str, str]:\n    \"\"\"Put headers in a dictionary and add Tsunami user agent.\"\"\"\n    serialized_headers = {}\n    for field, values in headers.raw_headers.items():\n      serialized_headers[field] = ', '.join(values)\n    serialized_headers[\n        HttpHeaderFields.USER_AGENT.value] = self.TSUNAMI_USER_AGENT\n    return serialized_headers\n\n  def _get_ip(self, network_service: Optional[NetworkService]) -> Optional[str]:\n    if not network_service:\n      return None\n    return network_service.network_endpoint.ip_address.address\n\n\nclass RequestsHttpClientBuilder(Builder):\n  \"\"\"Base builder for implementations of RequestsHttpClient.\"\"\"\n\n  def __init__(self):\n    self.log_id = None\n    # SSL certification verification.\n    self.verify_ssl = _VERIFY_SSL\n    # How long to wait for the server to send data before giving up.\n    self.timeout_sec = _TIMEOUT_SEC\n    # Whether requests may be redirected.\n    self.allow_redirects = _DEFAULT_ALLOW_REDIRECT\n    # Maximum number of threads to execute concurrently.\n    self.max_workers = _DEFAULT_MAX_WORKERS\n    # Number of urllib3 connection pools to cache.\n    self.pool_connections = _DEFAULT_POOL_CONNECTIONS\n    # Maximum number of connections to save in the pool.\n    self.pool_maxsize = _DEFAULT_POOL_MAXSIZE\n\n  def set_allow_redirects(\n      self, allow_redirects: bool\n  ) -> 'RequestsHttpClientBuilder':\n    self.allow_redirects = allow_redirects\n    return self\n\n  def set_log_id(self, log_id: str) -> 'RequestsHttpClientBuilder':\n    self.log_id = log_id\n    return self\n\n  def set_max_workers(self, max_workers: int) -> 'RequestsHttpClientBuilder':\n    self.max_workers = max_workers\n    return self\n\n  def set_pool_connections(\n      self, pool_connections: int\n  ) -> 'RequestsHttpClientBuilder':\n    self.pool_connections = pool_connections\n    return self\n\n  def set_pool_maxsize(self, pool_maxsize: int) -> 'RequestsHttpClientBuilder':\n    self.pool_maxsize = pool_maxsize\n    return self\n\n  def set_timeout_sec(self, timeout_sec: float) -> 'RequestsHttpClientBuilder':\n    self.timeout_sec = timeout_sec\n    return self\n\n  def set_verify_ssl(self, verify_ssl: bool) -> 'RequestsHttpClientBuilder':\n    self.verify_ssl = verify_ssl\n    return self\n\n  def build(self) -> RequestsHttpClient:\n    session = requests.Session()\n    adapter = HostResolverHttpAdapter(\n        pool_maxsize=self.pool_maxsize,\n        pool_connections=self.pool_connections,\n    )\n    session.mount('http://', adapter)\n    session.mount('https://', adapter)\n    return RequestsHttpClient(\n        session=session,\n        allow_redirects=self.allow_redirects,\n        log_id=self.log_id,\n        max_workers=self.max_workers,\n        timeout_sec=self.timeout_sec,\n        verify_ssl=self.verify_ssl,\n    )\n"
  },
  {
    "path": "plugin_server/py/common/net/http/requests_http_client_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.common.net.requests_http_client.\"\"\"\n\nimport unittest\n\nfrom absl.testing import absltest\nimport requests\nimport requests_mock\n\nfrom common.data import network_endpoint_utils\nfrom common.net.http.host_resolver_http_adapter import HostResolverHttpAdapter\nfrom common.net.http.http_header_fields import HttpHeaderFields\nfrom common.net.http.http_headers import HttpHeaders\nfrom common.net.http.http_method import HttpMethod\nfrom common.net.http.http_request import HttpRequest\nfrom common.net.http.http_response import HttpResponse\nfrom common.net.http.http_status import HttpStatus\nfrom common.net.http.requests_http_client import RequestsHttpClient\nfrom common.net.http.requests_http_client import RequestsHttpClientBuilder\nimport network_pb2\nimport network_service_pb2\n\n\nclass RequestsHttpClientTest(absltest.TestCase):\n  @classmethod\n  def setUpClass(cls):\n    super().setUpClass()\n    cls.client = RequestsHttpClientBuilder().build()\n\n  def test_get_log_id(self):\n    self.assertEqual(self.client.modify().set_log_id(1).build().get_log_id(), 1)\n\n  @requests_mock.mock()\n  def test_send_returns_expected_http_response(self, mock):\n    url = 'http://example.com/send/%2e%2e/%2e%2e/etc/path'\n    mock.register_uri(HttpMethod.GET, url)\n    response = self.client.send(\n        HttpRequest().get(url).with_empty_headers().build()\n    )\n    expected = self._create_expected_response(\n        url, headers=HttpHeaders().builder().build()\n    )\n    self._assert_response_is_expected(response, expected)\n\n  @requests_mock.mock()\n  def test_send_with_get_request_returns_expected_http_response(self, mock):\n    body = 'GET BODY'.encode('utf-8')\n    url = 'http://example.com/get/[]%$/test-path'\n    header_field = HttpHeaderFields.CONTENT_TYPE.value\n    header_value = 'text/html; charset=utf-8'\n    mock.register_uri(\n        HttpMethod.GET, url, content=body, headers={header_field: header_value}\n    )\n    response = self.client.send(\n        HttpRequest()\n        .get(url)\n        .with_empty_headers()\n        .build()\n    )\n    expected = self._create_expected_response(url, body=body)\n    self._assert_response_is_expected(response, expected)\n\n  @requests_mock.mock()\n  def test_send_async_with_get_request_returns_expected_http_response(\n      self, mock\n  ):\n    body = 'GET BODY'.encode('utf-8')\n    url = 'http://example.com/get/[]%$/test-path'\n    header_field = HttpHeaderFields.CONTENT_TYPE.value\n    header_value = 'text/html; charset=utf-8'\n    mock.register_uri(\n        HttpMethod.GET, url, content=body, headers={header_field: header_value}\n    )\n    response = self.client.send_async(\n        HttpRequest()\n        .get(url)\n        .with_empty_headers()\n        .build()\n    )\n    expected = self._create_expected_response(url, body=body)\n    self._assert_response_is_expected(response, expected)\n\n  @requests_mock.mock()\n  def test_send_with_head_request_returns_http_response_without_body(\n      self, mock\n  ):\n    body = 'Body should not exist.'.encode('utf-8')\n    url = 'http://example.com/send/[]%$/test-path'\n    header_field = HttpHeaderFields.CONTENT_TYPE.value\n    header_value = 'text/html; charset=utf-8'\n    mock.register_uri(\n        HttpMethod.HEAD, url, content=body, headers={header_field: header_value}\n    )\n    response = self.client.send(\n        HttpRequest()\n        .head(url)\n        .with_empty_headers()\n        .build()\n    )\n    expected = self._create_expected_response(url, body=body)\n    self._assert_response_is_expected(response, expected)\n\n  @requests_mock.mock()\n  def test_send_async_with_head_request_returns_expected_http_response_without_body(\n      self, mock\n  ):\n    body = 'HEAD BODY'.encode('utf-8')\n    url = 'http://example.com/post/[]%$/test-path'\n    header_field = HttpHeaderFields.CONTENT_TYPE.value\n    header_value = 'text/html; charset=utf-8'\n    mock.register_uri(\n        HttpMethod.HEAD, url, content=body, headers={header_field: header_value}\n    )\n    response = self.client.send_async(\n        HttpRequest()\n        .head(url)\n        .with_empty_headers()\n        .build()\n    )\n    expected = self._create_expected_response(url, body=body)\n    self._assert_response_is_expected(response, expected)\n\n  @requests_mock.mock()\n  def test_send_with_post_request_returns_expected_http_response(\n      self, mock\n  ):\n    body = 'POST BODY'.encode('utf-8')\n    url = 'http://example.com/post/[]%$/test-path'\n    header_field = HttpHeaderFields.CONTENT_TYPE.value\n    header_value = 'text/html; charset=utf-8'\n    mock.register_uri(\n        HttpMethod.POST, url, content=body, headers={header_field: header_value}\n    )\n    response = self.client.send(\n        HttpRequest()\n        .post(url)\n        .set_headers(\n            HttpHeaders()\n            .builder()\n            .add_header(header_field, header_value)\n            .build()\n        )\n        .build()\n    )\n    expected = self._create_expected_response(url, body=body)\n    self._assert_response_is_expected(response, expected)\n\n  @requests_mock.mock()\n  def test_send_async_with_post_request_returns_expected_http_response(\n      self, mock\n  ):\n    body = 'POST BODY'.encode('utf-8')\n    url = 'http://example.com/post/[]%$/test-path'\n    header_field = HttpHeaderFields.CONTENT_TYPE.value\n    header_value = 'text/html; charset=utf-8'\n    mock.register_uri(\n        HttpMethod.POST, url, content=body, headers={header_field: header_value}\n    )\n    response = self.client.send_async(\n        HttpRequest()\n        .post(url)\n        .set_headers(\n            HttpHeaders()\n            .builder()\n            .add_header(header_field, header_value)\n            .build()\n        )\n        .build()\n    )\n    expected = self._create_expected_response(url, body=body)\n    self._assert_response_is_expected(response, expected)\n\n  @requests_mock.mock()\n  def test_send_with_post_request_with_empty_headers_returns_expected_http_response(\n      self, mock\n  ):\n    body = '{ \"test\": \"json\" }'.encode('utf-8')\n    url = 'http://example.com/post/test-path'\n    header_field = HttpHeaderFields.CONTENT_TYPE.value\n    header_value = 'text/html; charset=utf-8'\n    mock.register_uri(\n        HttpMethod.POST, url, content=body, headers={header_field: header_value}\n    )\n    response = self.client.send(\n        HttpRequest()\n        .post(url)\n        .with_empty_headers()\n        .build()\n    )\n    expected = self._create_expected_response(url, body=body)\n    self._assert_response_is_expected(response, expected)\n\n  @requests_mock.mock()\n  def test_send_async_with_post_request_with_empty_headers_returns_expected_http_response(\n      self, mock\n  ):\n    body = '{ \\\"test\\\": \\\"json\\\" }'.encode('utf-8')\n    url = 'http://example.com/post/test-path'\n    header_field = HttpHeaderFields.CONTENT_TYPE.value\n    header_value = 'text/html; charset=utf-8'\n    mock.register_uri(\n        HttpMethod.POST, url, content=body, headers={header_field: header_value}\n    )\n    response = self.client.send_async(\n        HttpRequest().post(url).with_empty_headers().build()\n    )\n    expected = self._create_expected_response(\n        url, body='{ \"test\": \"json\" }'.encode('utf-8')\n    )\n    self._assert_response_is_expected(response, expected)\n\n  @requests_mock.mock()\n  def test_send_with_hostname_and_ip_use_hostname_as_proxy(self, mock):\n    url = 'http://example.com/post/test-path'\n    network_endpoint = network_endpoint_utils.for_ip_and_hostname(\n        '127.0.0.1', 'proxy.com'\n    )\n    network_service = network_service_pb2.NetworkService(\n        network_endpoint=network_endpoint,\n        transport_protocol=network_pb2.TransportProtocol.TCP,\n    )\n    adapter = HostResolverHttpAdapter(5, 10)\n    requests.Session.get_adapter = unittest.mock.MagicMock(return_value=adapter)\n    mock.register_uri(HttpMethod.GET, url)\n    response = self.client.send(\n        HttpRequest()\n        .get(url)\n        .with_empty_headers()\n        .build(),\n        network_service=network_service,\n    )\n    expected = self._create_expected_response(\n        url, headers=HttpHeaders.builder().build()\n    )\n    self._assert_response_is_expected(response, expected)\n\n  @requests_mock.mock()\n  def test_send_async_with_hostname_and_ip_use_hostname_as_proxy(self, mock):\n    url = 'http://example.com/post/test-path'\n    network_endpoint = network_endpoint_utils.for_ip_and_hostname(\n        '127.0.0.1', 'proxy.com'\n    )\n    network_service = network_service_pb2.NetworkService(\n        network_endpoint=network_endpoint,\n        transport_protocol=network_pb2.TransportProtocol.TCP,\n    )\n    adapter = HostResolverHttpAdapter(5, 10)\n    requests.Session.get_adapter = unittest.mock.MagicMock(return_value=adapter)\n    mock.register_uri(HttpMethod.GET, url)\n    response = self.client.send_async(\n        HttpRequest()\n        .get(url)\n        .with_empty_headers()\n        .build(),\n        network_service=network_service,\n    )\n    expected = self._create_expected_response(\n        url, headers=HttpHeaders.builder().build()\n    )\n    self._assert_response_is_expected(response, expected)\n\n  def test_requests_http_client_default_configs(self):\n    self.assertTrue(self.client.allow_redirects)\n    self.assertTrue(self.client.verify_ssl)\n\n  @requests_mock.mock()\n  def test_send_with_modified_configuration(self, mock):\n    url = 'http://example.com/post/test-path'\n    network_endpoint = network_endpoint_utils.for_ip_and_hostname(\n        '127.0.0.1', 'proxy.com'\n    )\n    network_service = network_service_pb2.NetworkService(\n        network_endpoint=network_endpoint,\n        transport_protocol=network_pb2.TransportProtocol.TCP,\n    )\n    mock.register_uri(HttpMethod.GET, url)\n    client = (\n        RequestsHttpClient.modify()\n        .set_timeout_sec(1.1)\n        .set_verify_ssl(False)\n        .set_allow_redirects(False)\n        .set_max_workers(2)\n        .set_pool_connections(1)\n        .set_pool_maxsize(1)\n    )\n    self.assertFalse(client.allow_redirects)\n    self.assertFalse(client.verify_ssl)\n    self.assertEqual(client.timeout_sec, 1.1)\n    self.assertEqual(client.max_workers, 2)\n    response = client.build().send(\n        HttpRequest().get(url).with_empty_headers().build(),\n        network_service=network_service,\n    )\n    expected = self._create_expected_response(\n        url, headers=HttpHeaders.builder().build()\n    )\n    self._assert_response_is_expected(response, expected)\n\n  def test_send_when_request_failed_raise_error(self):\n    url = 'http://example.com/post/test-path'\n    with self.assertRaises(requests.exceptions.RequestException):\n      self.client.send(\n          HttpRequest().post(url).with_empty_headers().build()\n      )\n\n  def test_send_async_when_request_failed_raise_error(self):\n    url = 'http://example.com/delete/test-path'\n    with self.assertRaises(requests.exceptions.RequestException):\n      self.client.send_async(\n          HttpRequest().delete(url).with_empty_headers().build()\n      )\n\n  def test__serialize_request_headers_include_custom_user_agent(self):\n    field = HttpHeaderFields.CONTENT_TYPE.value\n    value = 'text/html; charset=utf-8'\n    headers = HttpHeaders.builder().add_header(field, value).build()\n    self.assertEqual(\n        self.client._serialize_request_headers(headers),\n        {\n            field: value,\n            HttpHeaderFields.USER_AGENT.value: 'TsunamiSecurityScanner',\n        },\n    )\n\n  def _assert_response_is_expected(self, response, expected):\n    self.assertEqual(response.body_json(), expected.body_json())\n    self.assertEqual(response.status, expected.status)\n    self.assertEqual(response.url, expected.url)\n    self.assertEqual(response.headers.raw_headers, expected.headers.raw_headers)\n\n  def _create_expected_response(\n      self,\n      url,\n      status=HttpStatus.OK,\n      headers=HttpHeaders.builder()\n      .add_header(\n          HttpHeaderFields.CONTENT_TYPE.value, 'text/html; charset=utf-8'\n      )\n      .build(),\n      body=None,\n  ):\n    return (\n        HttpResponse()\n        .builder()\n        .set_url(url)\n        .set_status(status)\n        .set_headers(headers)\n        .set_response_body(body)\n        .build()\n    )\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/plugin/payload/payload.py",
    "content": "\"\"\"Payload is the type returned by the Payload Generator.\"\"\"\n\nfrom typing import Optional\n\nfrom absl import logging\n\nfrom plugin.payload.validator import Validator\nimport payload_generator_pb2\n\n\nclass Payload:\n  \"\"\"Out-of-bound payload to be sent to the scan target.\"\"\"\n\n  def __init__(\n      self,\n      payload: str,\n      validator: Validator,\n      attributes: payload_generator_pb2.PayloadAttributes,\n      config: payload_generator_pb2.PayloadGeneratorConfig,\n  ):\n    self.payload = payload\n    self.validator = validator\n    self.attributes = attributes\n    self.config = config\n\n  def get_payload(self) -> str:\n    \"\"\"Get the string representation of the payload.\n\n    Returns:\n      The payload string\n    \"\"\"\n    logging.info(\n        '%s generated payload `%s`, %s use the callback server',\n        self.config,\n        self.payload,\n        'does' if self.attributes.uses_callback_server else 'does not',\n    )\n    return self.payload\n\n  def check_if_executed(\n      self, payload_data: Optional[str | bytes] = None\n  ) -> bool:\n    \"\"\"Check if the payload was executed on the scan target.\n\n    Args:\n      payload_data: Optional string or bytees representation of the payload to\n        be verified.\n\n    Returns:\n      Whether the payload was executed.\n    \"\"\"\n    payload = (\n        payload_data.encode('utf-8')\n        if isinstance(payload_data, str)\n        else payload_data\n    )\n    return self.validator.is_executed(payload)\n\n  def get_payload_attributes(self) -> payload_generator_pb2.PayloadAttributes:\n    \"\"\"Get the payload attributes.\n\n    Returns:\n      Details of the payload.\n    \"\"\"\n    return self.attributes\n"
  },
  {
    "path": "plugin_server/py/plugin/payload/payload_generator.py",
    "content": "\"\"\"Payload generator create custom payload for Tsunami detectors.\"\"\"\n\nimport re\nfrom typing import Any, Callable, Optional\n\nfrom plugin.payload.payload import Payload\nfrom plugin.payload.payload_secret_generator import PayloadSecretGenerator\nfrom plugin.payload.validator import Validator\nfrom plugin.tcs_client import TcsClient\nimport payload_generator_pb2 as pg\n\n\nclass PayloadGenerator:\n  \"\"\"Select a payload with the given payload generator config.\"\"\"\n\n  SECRET_LENGTH = 8\n  TOKEN_CALLBACK_SERVER_URL = '$TSUNAMI_PAYLOAD_TOKEN_URL'\n  TOKEN_RANDOM_STRING = '$TSUNAMI_PAYLOAD_TOKEN_RANDOM'\n\n  def __init__(\n      self,\n      payload_secret_generator: PayloadSecretGenerator,\n      payloads: list[pg.PayloadDefinition],\n      tcs_client: TcsClient,\n  ):\n    \"\"\"Initialize Payload Generator.\n\n    Args:\n      payload_secret_generator: A secret generator used to create a one-time\n        string for payload use.\n      payloads: A list of pre-generated payload definitions for selection.\n      tcs_client: UI for interacting with the Tsunami Callback Server.\n    \"\"\"\n    self.payload_secret_generator = payload_secret_generator\n    self.payloads = payloads\n    self.tcs_client = tcs_client\n\n  def is_callback_server_enabled(self):\n    \"\"\"Check if callback server is enabled.\n\n    Returns:\n      Whether the callback server is enabled.\n    \"\"\"\n    return self.tcs_client.is_callback_server_enabled()\n\n  def generate(self, config: pg.PayloadGeneratorConfig) -> Payload:\n    \"\"\"Find a matching payload that uses callback server.\n\n    The algorithm prioritizes the matching payload with callback server enabled\n    and falls back to any other matching payload.\n\n    Args:\n      config: Payload generator config detailing the attributes required for the\n        selected payload.\n\n    Returns:\n      A matching payload per the payload generator config.\n    \"\"\"\n    return self._generate_payload(config, True)\n\n  def generate_no_callback(self, config: pg.PayloadGeneratorConfig) -> Payload:\n    \"\"\"Find a matching payload that does not uses callback server.\n\n    Args:\n      config: Payload generator config detailing the attributes required for the\n        selected payload.\n\n    Returns:\n      A matching payload per the payload generator config.\n    \"\"\"\n    return self._generate_payload(config, False)\n\n  def _generate_payload(\n      self, config: pg.PayloadGeneratorConfig, use_callback: bool\n  ) -> Payload:\n    \"\"\"Find matching payload per the provided attributes.\"\"\"\n    payload = None\n    if self.tcs_client.is_callback_server_enabled() and use_callback:\n      payload = self._find_matching_payload(config, use_callback)\n    if not payload:\n      payload = self._find_matching_payload(config, False)\n    if not payload:\n      raise LookupError(\n          'No payload implemented for %s vulnerability type, %s interpretation'\n          ' environment, and %s execution environment.'\n          % (\n              pg.PayloadGeneratorConfig.VulnerabilityType.Name(\n                  config.vulnerability_type\n              ),\n              pg.PayloadGeneratorConfig.InterpretationEnvironment.Name(\n                  config.interpretation_environment\n              ),\n              pg.PayloadGeneratorConfig.ExecutionEnvironment.Name(\n                  config.execution_environment\n              ),\n          )\n      )\n    return payload\n\n  def _find_matching_payload(\n      self, config: pg.PayloadGeneratorConfig, use_callback: bool\n  ) -> Optional[Payload]:\n    for payload in self.payloads:\n      if (self._payload_matches_config(payload, config, use_callback)):\n        return self._parse_payload(payload, config)\n\n  def _parse_payload(\n      self, payload: pg.PayloadDefinition, config: pg.PayloadGeneratorConfig\n  ) -> Payload:\n    \"\"\"Create payload from the selected payload definition.\"\"\"\n    secret = self.payload_secret_generator.generate(self.SECRET_LENGTH)\n    if bool(payload.uses_callback_server.ByteSize()):\n      payload_string = payload.payload_string.value.replace(\n          self.TOKEN_CALLBACK_SERVER_URL,\n          self.tcs_client.get_callback_uri(secret),\n      )\n      validator = type(\n          'PayloadValidator',\n          (Validator,),\n          {'is_executed': lambda s, _: self.tcs_client.has_oob_log(secret)},\n      )()\n      return Payload(\n          payload_string,\n          validator,\n          pg.PayloadAttributes(uses_callback_server=True),\n          config,\n      )\n    else:\n      payload_string = payload.payload_string.value.replace(\n          self.TOKEN_RANDOM_STRING, secret\n      )\n      if payload.validation_type != pg.PayloadValidationType.Value(\n          'VALIDATION_REGEX'\n      ):\n        raise NotImplementedError(\n            'Validation type %s not supported.'\n            % pg.PayloadGeneratorConfig.VulnerabilityType.Name(\n                config.vulnerability_type)\n            )\n      regex = payload.validation_regex.value.replace(\n          self.TOKEN_RANDOM_STRING, secret\n      )\n      validator = type(\n          'PayloadValidator',\n          (Validator,),\n          {'is_executed': _is_executed(regex)},\n      )()\n      return Payload(\n          payload_string,\n          validator,\n          pg.PayloadAttributes(uses_callback_server=False),\n          config,\n      )\n\n  def _payload_matches_config(\n      self,\n      payload: pg.PayloadDefinition,\n      config: pg.PayloadGeneratorConfig,\n      use_callback: bool,\n  ) -> bool:\n    return (\n        config.vulnerability_type in payload.vulnerability_type\n        and config.interpretation_environment\n        == payload.interpretation_environment\n        and config.execution_environment == payload.execution_environment\n        and bool(payload.uses_callback_server.ByteSize()) == use_callback\n    )\n\n\ndef _is_executed(regex: str) -> Callable[[Any, Optional[bytes]], bool]:\n  \"\"\"Check if the returned payload is executed by validating against the regex.\"\"\"\n\n  def check_payload_execution(_, data: Optional[bytes]) -> bool:\n    if data is None:\n      raise ValueError('No valid payload input is entered.')\n    string = data.decode('utf-8')\n    return bool(re.compile(regex).search(string)) or False\n\n  return check_payload_execution\n"
  },
  {
    "path": "plugin_server/py/plugin/payload/payload_generator_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.plugin.payload.payload_generator.\"\"\"\n\nimport hashlib\nimport unittest\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport requests_mock\n\nfrom google.protobuf import wrappers_pb2\nfrom common.net.http.requests_http_client import RequestsHttpClientBuilder\nfrom plugin.payload.payload_generator import PayloadGenerator\nfrom plugin.payload.payload_generator_test_helper import ANY_SSRF_CONFIG\nfrom plugin.payload.payload_generator_test_helper import JAVA_REFLECTIVE_RCE_CONFIG\nfrom plugin.payload.payload_generator_test_helper import LINUX_REFLECTIVE_RCE_CONFIG\nfrom plugin.payload.payload_generator_test_helper import LINUX_UNSPECIFIED_CONFIG\nfrom plugin.payload.payload_secret_generator import PayloadSecretGenerator\nfrom plugin.payload.payload_utility import get_parsed_payload\nfrom plugin.tcs_client import TcsClient\nimport payload_generator_pb2 as pg\n\n\n_IP_ADDRESS = '127.0.0.1'\n_PORT = 8000\n_URL = 'http://valid.com'\n_SECRET = '_1234_HERE_IT_IS_'\n\n\nclass PayloadGeneratorWithCallbackTest(parameterized.TestCase):\n\n  @classmethod\n  def setUpClass(cls):\n    super().setUpClass()\n    psg = PayloadSecretGenerator()\n    psg.generate = unittest.mock.MagicMock(return_value=_SECRET)\n    client = TcsClient(\n        _IP_ADDRESS, _PORT, _URL, RequestsHttpClientBuilder().build()\n    )\n    payloads = get_parsed_payload()\n    cls.payload_generator = PayloadGenerator(psg, payloads, client)\n\n  def test_is_callback_server_enabled_returns_true(self):\n    self.assertTrue(self.payload_generator.is_callback_server_enabled())\n\n  @parameterized.named_parameters(\n      ('linux_config', LINUX_REFLECTIVE_RCE_CONFIG, 'curl'),\n      (\n          'ssrf_config',\n          ANY_SSRF_CONFIG,\n          hashlib.sha3_224(_SECRET.encode('utf-8')).hexdigest(),\n      ),\n  )\n  def test_generate_with_callback_returns_payload(\n      self, config, expected_payload\n  ):\n    payload = self.payload_generator.generate(config)\n    self.assertIn(expected_payload, payload.payload)\n    self.assertIn(_IP_ADDRESS, payload.payload)\n    self.assertIn(str(_PORT), payload.payload)\n    self.assertTrue(payload.get_payload_attributes().uses_callback_server)\n\n  @parameterized.named_parameters(\n      (\n          'linux_config',\n          LINUX_REFLECTIVE_RCE_CONFIG,\n          (\n              'printf %s%s%s'\n              ' TSUNAMI_PAYLOAD_START {secret} TSUNAMI_PAYLOAD_END'\n          ).format(secret=_SECRET),\n      ),\n      (\n          'ssrf_config',\n          ANY_SSRF_CONFIG,\n          'http://public-firing-range.appspot.com/',\n      ),\n      (\n          'java_config',\n          JAVA_REFLECTIVE_RCE_CONFIG,\n          (\n              'String.format(\"%s%s%s\", \"TSUNAMI_PAYLOAD_START\",'\n              ' \"{secret}\", \"TSUNAMI_PAYLOAD_END\")'\n          ).format(secret=_SECRET),\n      ),\n  )\n  def test_generate_no_callback_returns_payload(\n      self, config, expected_payload\n  ):\n    payload = self.payload_generator.generate_no_callback(config)\n    self.assertIn(expected_payload, payload.payload)\n    self.assertFalse(payload.get_payload_attributes().uses_callback_server)\n\n  @requests_mock.mock()\n  def test_check_if_executed_linux_and_payload_exec_returns_true(self, mock):\n    body = '{ \"has_dns_interaction\":false, \"has_http_interaction\":true}'\n    mock.register_uri('GET', _URL, content=body.encode('utf-8'))\n    payload = self.payload_generator.generate(LINUX_REFLECTIVE_RCE_CONFIG)\n    self.assertTrue(payload.check_if_executed())\n\n  @requests_mock.mock()\n  def test_check_if_executed_linux_and_payload_exec_with_dns_returns_true(\n      self, mock):\n    body = '{ \"has_dns_interaction\":true, \"has_http_interaction\":true}'\n    mock.register_uri('GET', _URL, content=body.encode('utf-8'))\n    payload = self.payload_generator.generate(LINUX_REFLECTIVE_RCE_CONFIG)\n    self.assertTrue(payload.check_if_executed())\n\n  @requests_mock.mock()\n  def test_check_if_executed_linux_no_payload_exec_returns_false(self, mock):\n    body = '{ \"has_dns_interaction\":false, \"has_http_interaction\":false}'\n    mock.register_uri('GET', _URL, content=body.encode('utf-8'))\n    payload = self.payload_generator.generate(LINUX_REFLECTIVE_RCE_CONFIG)\n    self.assertFalse(payload.check_if_executed())\n\n  @requests_mock.mock()\n  def test_check_if_executed_ssrf_and_payload_exec_returns_true(self, mock):\n    body = '{ \"has_dns_interaction\":true, \"has_http_interaction\":false}'\n    mock.register_uri('GET', _URL, content=body.encode('utf-8'))\n    payload = self.payload_generator.generate(ANY_SSRF_CONFIG)\n    self.assertTrue(payload.check_if_executed())\n\n  @requests_mock.mock()\n  def test_check_if_executed_ssrf_and_no_payload_exec_returns_false(self, mock):\n    body = '{ \"has_dns_interaction\":false, \"has_http_interaction\":false}'\n    mock.register_uri('GET', _URL, content=body.encode('utf-8'))\n    payload = self.payload_generator.generate(ANY_SSRF_CONFIG)\n    self.assertFalse(payload.check_if_executed())\n\n  def test_generate_with_no_vulnerability_type_raises_lookup_error(self):\n    with self.assertRaises(LookupError) as exc:\n      self.payload_generator.generate(\n          pg.PayloadGeneratorConfig(\n              interpretation_environment=pg.PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY,\n              execution_environment=pg.PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT,\n          )\n      )\n    self.assertEqual(\n        (\n            'No payload implemented for VULNERABILITY_TYPE_UNSPECIFIED'\n            ' vulnerability type, INTERPRETATION_ANY interpretation'\n            ' environment, and EXEC_INTERPRETATION_ENVIRONMENT execution'\n            ' environment.'\n        ),\n        str(exc.exception),\n    )\n\n  def test_generate_with_no_interpretation_environment_raises_error(self):\n    with self.assertRaises(LookupError) as exc:\n      self.payload_generator.generate(\n          pg.PayloadGeneratorConfig(\n              vulnerability_type=pg.PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE,\n              execution_environment=pg.PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT,\n          )\n      )\n    self.assertEqual(\n        (\n            'No payload implemented for REFLECTIVE_RCE vulnerability type,'\n            ' INTERPRETATION_ENVIRONMENT_UNSPECIFIED interpretation'\n            ' environment, and EXEC_INTERPRETATION_ENVIRONMENT execution'\n            ' environment.'\n        ),\n        str(exc.exception),\n    )\n\n  def test_generate_with_no_execution_environment_raises_lookup_error(self):\n    with self.assertRaises(LookupError) as exc:\n      self.payload_generator.generate(\n          pg.PayloadGeneratorConfig(\n              interpretation_environment=pg.PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY,\n              vulnerability_type=pg.PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE,\n          )\n      )\n    self.assertEqual(\n        (\n            'No payload implemented for REFLECTIVE_RCE vulnerability type,'\n            ' INTERPRETATION_ANY interpretation environment, and'\n            ' EXECUTION_ENVIRONMENT_UNSPECIFIED execution environment.'\n        ),\n        str(exc.exception),\n    )\n\n  def test_generate_with_no_config_raises_lookup_error(self):\n    with self.assertRaises(LookupError) as exc:\n      self.payload_generator.generate(pg.PayloadGeneratorConfig())\n    self.assertEqual(\n        (\n            'No payload implemented for VULNERABILITY_TYPE_UNSPECIFIED'\n            ' vulnerability type, INTERPRETATION_ENVIRONMENT_UNSPECIFIED'\n            ' interpretation environment, and EXECUTION_ENVIRONMENT_UNSPECIFIED'\n            ' execution environment.'\n        ),\n        str(exc.exception),\n    )\n\n\nclass PayloadGeneratorWithoutCallbackTest(parameterized.TestCase):\n\n  @classmethod\n  def setUpClass(cls):\n    super().setUpClass()\n    psg = PayloadSecretGenerator()\n    psg.generate = unittest.mock.MagicMock(return_value=_SECRET)\n    payloads = get_parsed_payload()\n    disabled_client = TcsClient('', 0, '', RequestsHttpClientBuilder().build())\n    cls.payload_generator_without_callback_server = PayloadGenerator(\n        psg, payloads, disabled_client\n    )\n\n  def test_is_callback_server_enabled_with_no_callback_returns_false(self):\n    self.assertFalse(\n        self.payload_generator_without_callback_server.is_callback_server_enabled()\n    )\n\n  @parameterized.named_parameters(\n      (\n          'linux_config',\n          LINUX_REFLECTIVE_RCE_CONFIG,\n          (\n              'printf %s%s%s TSUNAMI_'\n              'PAYLOAD_START {secret} TSUNAMI_PAYLOAD_END'\n          ).format(secret=_SECRET),\n      ),\n      (\n          'ssrf_config',\n          ANY_SSRF_CONFIG,\n          'http://public-firing-range.appspot.com/',\n      ),\n      (\n          'java_config',\n          JAVA_REFLECTIVE_RCE_CONFIG,\n          (\n              'String.format(\"%s%s%s\", \"TSUNAMI_PAYLOAD_START\",'\n              ' \"{secret}\", \"TSUNAMI_PAYLOAD_END\")'\n          ).format(secret=_SECRET),\n      ),\n  )\n  def test_generate_without_callback_returns_payload(\n      self, config, expected_payload\n  ):\n    payload = self.payload_generator_without_callback_server.generate(config)\n    self.assertEqual(expected_payload, payload.payload)\n    self.assertFalse(payload.get_payload_attributes().uses_callback_server)\n\n  @parameterized.named_parameters(\n      (\n          'linux_config',\n          LINUX_REFLECTIVE_RCE_CONFIG,\n          ('TSUNAMI_PAYLOAD_START{secret}TSUNAMI_PAYLOAD_END').format(\n              secret=_SECRET\n          ),\n      ),\n      ('ssrf_config', ANY_SSRF_CONFIG, '<h1>What is the Firing Range?</h1>'),\n      (\n          'java_config',\n          JAVA_REFLECTIVE_RCE_CONFIG,\n          ('TSUNAMI_PAYLOAD_START{secret}TSUNAMI_PAYLOAD_END').format(\n              secret=_SECRET\n          ),\n      ),\n  )\n  def test_check_if_executed_with_correct_payload_input_returns_true(\n      self, config, expected_payload\n  ):\n    payload = self.payload_generator_without_callback_server.generate(config)\n    payload_byte = bytes(expected_payload, 'utf-8')\n    self.assertTrue(payload.check_if_executed(payload_byte))\n\n  @parameterized.named_parameters(\n      ('linux_config', LINUX_REFLECTIVE_RCE_CONFIG, 'Random input'),\n      ('ssrf_config', ANY_SSRF_CONFIG, '<h1>Not Firing Range</h1>'),\n      (\n          'java_config',\n          JAVA_REFLECTIVE_RCE_CONFIG,\n          'TSUNAMI_PAYLOAD_START_Nothing_here_TSUNAMI_PAYLOAD_END',\n      ),\n  )\n  def test_check_if_executed_with_bad_payload_input_returns_false(\n      self, config, expected_payload\n  ):\n    payload = self.payload_generator_without_callback_server.generate(config)\n    payload_byte = bytes(expected_payload, 'utf-8')\n    self.assertFalse(payload.check_if_executed(payload_byte))\n\n  def test_generate_with_no_callback_no_vulnerability_type_raises_error(self):\n    with self.assertRaises(LookupError) as exc:\n      self.payload_generator_without_callback_server.generate(\n          pg.PayloadGeneratorConfig(\n              interpretation_environment=pg.PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY,\n              execution_environment=pg.PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT,\n          )\n      )\n    self.assertEqual(\n        (\n            'No payload implemented for VULNERABILITY_TYPE_UNSPECIFIED'\n            ' vulnerability type, INTERPRETATION_ANY interpretation'\n            ' environment, and EXEC_INTERPRETATION_ENVIRONMENT execution'\n            ' environment.'\n        ),\n        str(exc.exception),\n    )\n\n  def test_generate_with_no_callback_no_interpretation_env_raises_error(self):\n    with self.assertRaises(LookupError) as exc:\n      self.payload_generator_without_callback_server.generate(\n          pg.PayloadGeneratorConfig(\n              vulnerability_type=pg.PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE,\n              execution_environment=pg.PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT,\n          )\n      )\n    self.assertEqual(\n        (\n            'No payload implemented for REFLECTIVE_RCE vulnerability type,'\n            ' INTERPRETATION_ENVIRONMENT_UNSPECIFIED interpretation'\n            ' environment, and EXEC_INTERPRETATION_ENVIRONMENT execution'\n            ' environment.'\n        ),\n        str(exc.exception),\n    )\n\n  def test_generate_with_no_callback_and_no_execution_env_raises_error(self):\n    with self.assertRaises(LookupError) as exc:\n      self.payload_generator_without_callback_server.generate(\n          pg.PayloadGeneratorConfig(\n              interpretation_environment=pg.PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY,\n              vulnerability_type=pg.PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE,\n          )\n      )\n    self.assertEqual(\n        (\n            'No payload implemented for REFLECTIVE_RCE vulnerability type,'\n            ' INTERPRETATION_ANY interpretation environment, and'\n            ' EXECUTION_ENVIRONMENT_UNSPECIFIED execution environment.'\n        ),\n        str(exc.exception),\n    )\n\n  def test_generate_with_no_callback_and_no_config_raises_lookup_error(self):\n    with self.assertRaises(LookupError) as exc:\n      self.payload_generator_without_callback_server.generate(\n          pg.PayloadGeneratorConfig()\n      )\n    self.assertEqual(\n        (\n            'No payload implemented for VULNERABILITY_TYPE_UNSPECIFIED'\n            ' vulnerability type, INTERPRETATION_ENVIRONMENT_UNSPECIFIED'\n            ' interpretation environment, and EXECUTION_ENVIRONMENT_UNSPECIFIED'\n            ' execution environment.'\n        ),\n        str(exc.exception),\n    )\n\n  def test_generate_with_no_callback_and_no_validation_type_raises_error(self):\n    payload_list = [pg.PayloadDefinition(\n        interpretation_environment=pg.PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL,\n        name=wrappers_pb2.StringValue(value='linux_printf'),\n        execution_environment=pg.PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT,\n        vulnerability_type=[pg.PayloadGeneratorConfig.VulnerabilityType.VULNERABILITY_TYPE_UNSPECIFIED],\n        uses_callback_server=wrappers_pb2.BoolValue(value=False),\n        payload_string=wrappers_pb2.StringValue(value='printf %s%s%s TSUNAMI_'\n                                                'PAYLOAD_START $TSUNAMI_PAYLOAD'\n                                                '_TOKEN_RANDOM TSUNAMI_PAYLOAD_'\n                                                'END'),\n        )]\n    psg = PayloadSecretGenerator()\n    psg.generate = unittest.mock.MagicMock(return_value=_SECRET)\n    payload_generator_no_callback = PayloadGenerator(\n        psg,\n        payload_list,\n        TcsClient('', 0, '', RequestsHttpClientBuilder().build()),\n    )\n    with self.assertRaises(NotImplementedError) as exc:\n      payload_generator_no_callback.generate(LINUX_UNSPECIFIED_CONFIG)\n    self.assertEqual(\n        'Validation type VULNERABILITY_TYPE_UNSPECIFIED not supported.',\n        str(exc.exception),\n    )\n\n  def test_check_if_executed_with_no_callback_and_no_payload_raises_error(self):\n    payload = self.payload_generator_without_callback_server.generate(\n        LINUX_REFLECTIVE_RCE_CONFIG\n    )\n    with self.assertRaises(ValueError) as exc:\n      payload.check_if_executed()\n    self.assertEqual('No valid payload input is entered.', str(exc.exception))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/plugin/payload/payload_generator_test_helper.py",
    "content": "\"\"\"Payload Generator Test Helper.\"\"\"\nimport payload_generator_pb2 as pg\n\n\nLINUX_REFLECTIVE_RCE_CONFIG = pg.PayloadGeneratorConfig(\n    vulnerability_type=pg.PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE,\n    interpretation_environment=pg.PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL,\n    execution_environment=pg.PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT,\n)\n\nLINUX_UNSPECIFIED_CONFIG = pg.PayloadGeneratorConfig(\n    vulnerability_type=pg.PayloadGeneratorConfig.VulnerabilityType.VULNERABILITY_TYPE_UNSPECIFIED,\n    interpretation_environment=pg.PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL,\n    execution_environment=pg.PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT,\n)\n\nANY_SSRF_CONFIG = pg.PayloadGeneratorConfig(\n    vulnerability_type=pg.PayloadGeneratorConfig.VulnerabilityType.SSRF,\n    interpretation_environment=pg.PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY,\n    execution_environment=pg.PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY,\n)\n\nJAVA_REFLECTIVE_RCE_CONFIG = pg.PayloadGeneratorConfig(\n    vulnerability_type=pg.PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE,\n    interpretation_environment=pg.PayloadGeneratorConfig.InterpretationEnvironment.JAVA,\n    execution_environment=pg.PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT,\n)\n"
  },
  {
    "path": "plugin_server/py/plugin/payload/payload_secret_generator.py",
    "content": "\"\"\"Payload Secret Generator class.\"\"\"\n\nimport base64\nimport os\n\n\nclass PayloadSecretGenerator:\n  \"\"\"Payload Secret Generator creates a one-time secret.\"\"\"\n\n  def generate(self, size: int) -> str:\n    \"\"\"Generate a random secret string with n bytes.\n\n    Args:\n      size: number of bytes.\n\n    Returns:\n      The decoded string.\n    \"\"\"\n    random_bytes = os.urandom(size)\n    return base64.b16encode(random_bytes).decode()\n"
  },
  {
    "path": "plugin_server/py/plugin/payload/payload_secret_generator_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.plugin.payload.payload_secret_generator.\"\"\"\n\nimport base64\n\nfrom google3.testing.pybase import googletest\nfrom plugin.payload.payload_secret_generator import PayloadSecretGenerator\n\n\n_SECRET_BYTE_SIZE = 8\n\n\nclass PayloadSecretGeneratorTest(googletest.TestCase):\n\n  def test_generate_returns_secret_with_specified_size(self):\n    secret = PayloadSecretGenerator().generate(_SECRET_BYTE_SIZE)\n    self.assertLen(base64.b16decode(secret), _SECRET_BYTE_SIZE)\n\n\nif __name__ == \"__main__\":\n  googletest.main()\n"
  },
  {
    "path": "plugin_server/py/plugin/payload/payload_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.plugin.payload.payload.\"\"\"\nfrom typing import Optional\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\n\nfrom plugin.payload.payload import Payload\nfrom plugin.payload.validator import Validator\nimport payload_generator_pb2 as pg\n\n_CONFIG = pg.PayloadGeneratorConfig()\n_PAYLOAD_ATTRIBUTES = pg.PayloadAttributes()\n\n\nclass PayloadTest(parameterized.TestCase):\n\n  class MockValidator(Validator):\n    was_called = False\n\n    def is_executed(self, payload_data: Optional[bytes]):\n      self.was_called = True\n      return False\n\n  def test_get_payload_returns_payload_string(self):\n    payload = Payload(\n        'payload string', self.MockValidator(), _PAYLOAD_ATTRIBUTES, _CONFIG\n    )\n    self.assertEqual('payload string', payload.get_payload())\n\n  @parameterized.named_parameters(\n      ('with_no_payload_runs_validator', None),\n      ('with_payload_string_runs_validator', 'payload string'),\n      ('with_bytes_runs_validator', b'payload string'),\n  )\n  def test_check_if_executed(self, param):\n    validator = self.MockValidator()\n    payload = Payload('payload string', validator, _PAYLOAD_ATTRIBUTES, _CONFIG)\n    payload.check_if_executed(param)\n    self.assertTrue(validator.was_called)\n\n  def test_get_payload_attributes_returns_payload_attributes(self):\n    validator = self.MockValidator()\n    payload = Payload('payload string', validator, _PAYLOAD_ATTRIBUTES, _CONFIG)\n    self.assertEqual(_PAYLOAD_ATTRIBUTES, payload.get_payload_attributes())\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/plugin/payload/payload_utility.py",
    "content": "\"\"\"Utility service for retrieving and parsing payload.\"\"\"\n\nfrom ruamel import yaml\nfrom google.protobuf import json_format\nfrom pathlib import Path\nimport payload_generator_pb2 as pg\n\n_PATH = '../../plugin/src/main/resources/com/google/tsunami/plugin/payload/payload_definitions.yaml'\n\n\ndef set_payload_file_path(path: str):\n  \"\"\"Set the path to the payload file.\n\n  Args:\n    path: The path to the payload file.\n  \"\"\"\n  if not path:\n    return\n\n  global _PATH\n  _PATH = path\n\n\ndef get_parsed_payload() -> list[pg.PayloadDefinition]:\n  \"\"\"Get payload from payload_definitions.yaml.\n\n  Returns:\n   PayloadLibrary: List of payload definitions after validation.\n\n  Raises:\n    ValueError: If payload is missing a name, interpretation_environment,\n      uses_callback_server, payload_string, or vulnerability_type. If the\n      payload definition is invalid. Examples of invalidity include:\n        - Payload that uses callback but the tsunami payload string is missing.\n        - Payload that does not uses callback but has no specified validation\n          type.\n        - Payload that uses validation regex but does not specify the regex to\n          be used.\n  \"\"\"\n  payload_str = Path(_PATH).read_text()\n  yaml_parser = yaml.YAML(typ='safe', pure=True)\n  payload_dict = yaml_parser.load(payload_str)\n  payload_library = json_format.ParseDict(payload_dict, pg.PayloadLibrary())\n  return _validate_payloads([p for p in payload_library.payloads])\n\n\ndef _validate_payloads(\n    payloads: list[pg.PayloadDefinition],\n) -> list[pg.PayloadDefinition]:\n  \"\"\"Validate the pre-loaded payloads.\"\"\"\n  for payload in payloads:\n    if not payload.HasField('name'):\n      raise ValueError('Parse payload does not have a name.')\n    if (\n        payload.interpretation_environment\n        is pg.PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ENVIRONMENT_UNSPECIFIED\n    ):\n      raise ValueError(\n          'Parse payload does not have an interpretation environment.'\n      )\n    if (\n        payload.execution_environment\n        is pg.PayloadGeneratorConfig.ExecutionEnvironment.EXECUTION_ENVIRONMENT_UNSPECIFIED\n    ):\n      raise ValueError('Parse payload does not have an execution environment.')\n    if (\n        pg.PayloadGeneratorConfig.VulnerabilityType.VULNERABILITY_TYPE_UNSPECIFIED\n        in payload.vulnerability_type\n    ):\n      raise ValueError('Parse payload does not have a vulnerability type.')\n    if not payload.HasField('payload_string'):\n      raise ValueError('Parse payload does not have a payload string.')\n    if bool(\n        payload.uses_callback_server.ByteSize()\n    ) and '$TSUNAMI_PAYLOAD_TOKEN_URL' not in str(payload.payload_string):\n      raise ValueError(\n          'Parse payload uses callback server but $TSUNAMI_PAYLOAD_TOKEN_URL'\n          ' not found in payload string.'\n      )\n    if not bool(payload.uses_callback_server.ByteSize()):\n      if (\n          payload.validation_type\n          is pg.PayloadValidationType.VALIDATION_TYPE_UNSPECIFIED\n      ):\n        raise ValueError(\n            'Parse payload does not have a validation type and'\n            ' does not use the callback server.'\n        )\n      if (\n          payload.validation_type is pg.PayloadValidationType.VALIDATION_REGEX\n          and not payload.HasField('validation_regex')\n      ):\n        raise ValueError(\n            'Parse payload has no validation regex but uses'\n            ' PayloadValidationType.REGEX.'\n        )\n  return payloads\n"
  },
  {
    "path": "plugin_server/py/plugin/payload/payload_utility_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.plugin.payload.payload_utility.\"\"\"\n\nimport unittest\n\nfrom google3.pyglib import resources\nfrom google3.testing.pybase import googletest\nfrom plugin.payload import payload_utility\n\n\nclass PayloadUtilityTest(googletest.TestCase):\n\n  def test_get_parsed_payload_with_good_payloads_returns_payloads(self):\n    pd_string = \"\"\"payloads:\n      - name: The Good PD\n        interpretation_environment: LINUX_SHELL\n        execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n        uses_callback_server: true\n        payload_string: curl $TSUNAMI_PAYLOAD_TOKEN_URL\n        vulnerability_type:\n          - REFLECTIVE_RCE\n    \"\"\"\n    resources.GetResource = unittest.mock.Mock(return_value=pd_string)\n    self.assertLen(payload_utility.get_parsed_payload(), 1)\n\n  def test_get_parsed_payload_without_name_raise_valueerror(self):\n    pd_string = \"\"\"payloads:\n      - interpretation_environment: LINUX_SHELL\n        execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n        uses_callback_server: true\n        payload_string: curl $TSUNAMI_PAYLOAD_TOKEN_URL\n        vulnerability_type:\n          - REFLECTIVE_RCE\n    \"\"\"\n    with self.assertRaises(ValueError) as exc:\n      resources.GetResource = unittest.mock.Mock(return_value=pd_string)\n      payload_utility.get_parsed_payload()\n    self.assertEqual('Parse payload does not have a name.', str(exc.exception))\n\n  def test_get_parsed_payload_no_interpretation_environment_raise_error(self):\n    pd_string = \"\"\"payloads:\n      - name: The Good PD\n        interpretation_environment: INTERPRETATION_ENVIRONMENT_UNSPECIFIED\n        execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n        uses_callback_server: true\n        payload_string: curl $TSUNAMI_PAYLOAD_TOKEN_URL\n        vulnerability_type:\n          - REFLECTIVE_RCE\n    \"\"\"\n    with self.assertRaises(ValueError) as exc:\n      resources.GetResource = unittest.mock.Mock(return_value=pd_string)\n      payload_utility.get_parsed_payload()\n    self.assertEqual(\n        'Parse payload does not have an interpretation environment.',\n        str(exc.exception),\n    )\n\n  def test_get_parsed_payload_no_execution_environment_raise_error(self):\n    pd_string = \"\"\"payloads:\n      - name: The Good PD\n        interpretation_environment: LINUX_SHELL\n        execution_environment: EXECUTION_ENVIRONMENT_UNSPECIFIED\n        uses_callback_server: true\n        payload_string: curl $TSUNAMI_PAYLOAD_TOKEN_URL\n        vulnerability_type:\n          - REFLECTIVE_RCE\n    \"\"\"\n    with self.assertRaises(ValueError) as exc:\n      resources.GetResource = unittest.mock.Mock(return_value=pd_string)\n      payload_utility.get_parsed_payload()\n    self.assertEqual(\n        'Parse payload does not have an execution environment.',\n        str(exc.exception),\n    )\n\n  def test_get_parsed_payload_no_vulnerability_type_raise_error(self):\n    pd_string = \"\"\"payloads:\n      - name: The Good PD\n        interpretation_environment: LINUX_SHELL\n        execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n        payload_string: curl $TSUNAMI_PAYLOAD_TOKEN_URL\n        uses_callback_server: true\n        vulnerability_type:\n          - VULNERABILITY_TYPE_UNSPECIFIED\n    \"\"\"\n    with self.assertRaises(ValueError) as exc:\n      resources.GetResource = unittest.mock.Mock(return_value=pd_string)\n      payload_utility.get_parsed_payload()\n    self.assertEqual(\n        'Parse payload does not have a vulnerability type.',\n        str(exc.exception),\n    )\n\n  def test_get_parsed_payload_no_payload_string_raise_error(self):\n    pd_string = \"\"\"payloads:\n      - name: The Good PD\n        interpretation_environment: LINUX_SHELL\n        execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n        uses_callback_server: true\n        vulnerability_type:\n          - REFLECTIVE_RCE\n    \"\"\"\n    with self.assertRaises(ValueError) as exc:\n      resources.GetResource = unittest.mock.Mock(return_value=pd_string)\n      payload_utility.get_parsed_payload()\n    self.assertEqual(\n        'Parse payload does not have a payload string.',\n        str(exc.exception),\n    )\n\n  def test_get_parsed_payload_no_callback_constant_in_payload_raise_error(self):\n    pd_string = \"\"\"payloads:\n      - name: The Good PD\n        interpretation_environment: LINUX_SHELL\n        execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n        uses_callback_server: true\n        payload_string: curl soemthing here\n        vulnerability_type:\n          - REFLECTIVE_RCE\n    \"\"\"\n    with self.assertRaises(ValueError) as exc:\n      resources.GetResource = unittest.mock.Mock(return_value=pd_string)\n      payload_utility.get_parsed_payload()\n    self.assertEqual(\n        (\n            'Parse payload uses callback server but $TSUNAMI_PAYLOAD_TOKEN_URL'\n            ' not found in payload string.'\n        ),\n        str(exc.exception),\n    )\n\n  def test_get_parsed_payload_no_callback_server_no_validation_type_raise_error(\n      self):\n    pd_string = \"\"\"payloads:\n      - name: The Good PD\n        interpretation_environment: LINUX_SHELL\n        execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n        uses_callback_server: false\n        payload_string: curl $TSUNAMI_PAYLOAD_TOKEN_URL\n        validation_type: VALIDATION_TYPE_UNSPECIFIED\n        vulnerability_type:\n          - REFLECTIVE_RCE\n    \"\"\"\n    with self.assertRaises(ValueError) as exc:\n      resources.GetResource = unittest.mock.Mock(return_value=pd_string)\n      payload_utility.get_parsed_payload()\n    self.assertEqual(\n        (\n            'Parse payload does not have a validation type and does not use the'\n            ' callback server.'\n        ),\n        str(exc.exception),\n    )\n\n  def test_get_parsed_payload_no_callback_server_no_val_regex_raise_error(\n      self):\n    pd_string = \"\"\"payloads:\n      - name: The Good PD\n        interpretation_environment: LINUX_SHELL\n        execution_environment: EXEC_INTERPRETATION_ENVIRONMENT\n        uses_callback_server: false\n        payload_string: curl $TSUNAMI_PAYLOAD_TOKEN_URL\n        validation_type: VALIDATION_REGEX\n        vulnerability_type:\n          - REFLECTIVE_RCE\n    \"\"\"\n    with self.assertRaises(ValueError) as exc:\n      resources.GetResource = unittest.mock.Mock(return_value=pd_string)\n      payload_utility.get_parsed_payload()\n    self.assertEqual(\n        (\n            'Parse payload has no validation regex but uses'\n            ' PayloadValidationType.REGEX.'\n        ),\n        str(exc.exception),\n    )\n\n\nif __name__ == '__main__':\n  googletest.main()\n"
  },
  {
    "path": "plugin_server/py/plugin/payload/validator.py",
    "content": "\"\"\"Interface type for the function to verify payload execution.\"\"\"\n\nimport abc\nfrom typing import Optional\n\n\nclass Validator(metaclass=abc.ABCMeta):\n  \"\"\"Type used to verify payload execution in the out-of-bound Payload.\"\"\"\n\n  @abc.abstractmethod\n  def is_executed(self, data: Optional[bytes]) -> bool:\n    \"\"\"Checks whether the payload is executed.\n\n    Args:\n      data: Optional data in bytes representation.\n\n    Returns:\n      Whether the payload is executed.\n    \"\"\"\n"
  },
  {
    "path": "plugin_server/py/plugin/tcs_client.py",
    "content": "\"\"\"Tsunami Callback Server client.\"\"\"\nimport hashlib\nfrom typing import Optional\nfrom absl import logging\nfrom google.protobuf import json_format\nfrom common.data import network_endpoint_utils\nfrom common.net.http.http_client import HttpClient\nfrom common.net.http.http_headers import HttpHeaders\nfrom common.net.http.http_request import HttpRequest\nimport network_pb2\nimport polling_pb2\n\n\nclass TcsClient:\n  \"\"\"Client use for communicating with Tsunami Callback Server.\"\"\"\n\n  def __init__(\n      self,\n      callback_address: str,\n      callback_port: int,\n      polling_base_url: str,\n      http_client: HttpClient,\n  ):\n    \"\"\"Initialize Tsunami Callback Server client.\n\n    Args:\n      callback_address: IP address or the hostname of the callback server.\n      callback_port: The port that the callback server is running on.\n      polling_base_url: Base url of the callback server.\n      http_client: Client used to send HTTP requests and process responses.\n    \"\"\"\n    self.callback_endpoint = self._create_callback_address(\n        callback_address, callback_port\n    )\n    self.polling_base_url = self._remove_trailing_slashes(polling_base_url)\n    self.http_client = http_client\n\n  def is_callback_server_enabled(self) -> bool:\n    \"\"\"Check if callback server is enabled.\n\n    Returns:\n      Whether callback server is reachable.\n    \"\"\"\n    return bool(\n        self.callback_endpoint.ip_address.address\n        or self.callback_endpoint.hostname.name\n    ) and bool(self.polling_base_url)\n\n  def get_callback_uri(self, secret_string: str) -> str:\n    \"\"\"Assemble the URI to reach the callback server.\n\n    Args:\n      secret_string: Callback unique ID that is bonded to the scan target. Used\n        to include in the request URI.\n\n    Returns:\n      The server URI.\n\n      Examples with provided hostname:\n        04041e8898e739ca33.google.com\n        04041e8898e739ca33.google.com:8080\n\n      Examples with provided IP address:\n        http://127.0.0.1:8080/04041e8898e739ca33\n        http://[2001:db8:3333:4444:5555:6666:7777:8888]/04041e8898e739ca33\n    \"\"\"\n    # Generate hash from 8 bytes secret string using SHA-3 hashing\n    cbid = hashlib.sha3_224(secret_string.encode('utf-8')).hexdigest()\n    uri = network_endpoint_utils.to_uri_authority(self.callback_endpoint)\n    # return uri with provided hostname\n    if network_endpoint_utils.has_hostname(self.callback_endpoint):\n      return '%s.%s' % (cbid, uri)\n    # return uri with provided ip address\n    return 'http://%s/%s' % (uri, cbid)\n\n  def has_oob_log(self, secret_string: str) -> bool:\n    \"\"\"Check if callback server has received OOB log.\n\n    Args:\n      secret_string: Callback unique ID that is bonded to the scan target. Used\n        to include in the request URI.\n\n    Returns:\n      If callback server has out-of-bounds log.\n    \"\"\"\n    result = self._send_polling_request(secret_string)\n    if result:\n      return result.has_dns_interaction or result.has_http_interaction\n    return False\n\n  def _send_polling_request(\n      self, secret_string: str\n  ) -> Optional[polling_pb2.PollingResult]:\n    \"\"\"Send HTTP requests to the callback server.\n\n    Args:\n      secret_string: Callback unique ID that is bonded to the scan target. Used\n        to include in the request URI.\n\n    Returns:\n      The polling results of whether or not the scan target has DNS or HTTP\n      interaction. Returns None if polling request failed.\n    \"\"\"\n    request = self._build_polling_request(secret_string)\n    try:\n      response = self.http_client.send(request)\n      if response.status and response.status.is_success():\n        return json_format.Parse(\n            response.body_string(), polling_pb2.PollingResult()\n        )\n      else:\n        code = response.status.code if response.status else 'UNKNOWN'\n        logging.info('OOB server returned %s.', code)\n    except (json_format.ParseError, ValueError):\n      logging.exception('Polling request failed.')\n    return None\n\n  def _build_polling_request(self, secret_string: str) -> HttpRequest:\n    url = '%s/?secret=%s' % (self.polling_base_url, secret_string)\n    return (\n        HttpRequest.get(url)\n        .set_headers(\n            HttpHeaders.builder()\n            .add_header('Cache-Control', 'no-cache')\n            .build()\n        )\n        .build()\n    )\n\n  def _create_callback_address(\n      self, address: str, port: int\n  ) -> network_pb2.NetworkEndpoint:\n    try:\n      return (\n          network_endpoint_utils.for_ip(address)\n          if port == 80\n          else network_endpoint_utils.for_ip_and_port(address, port)\n      )\n    except ValueError:\n      return (\n          network_endpoint_utils.for_hostname(address)\n          if port == 80\n          else network_endpoint_utils.for_hostname_and_port(address, port)\n      )\n\n  def _remove_trailing_slashes(self, url: str) -> str:\n    return url.strip('/')\n"
  },
  {
    "path": "plugin_server/py/plugin/tcs_client_test.py",
    "content": "\"\"\"Tests for google3.third_party.java_src.tsunami.plugin_server.py.plugin.tcs_client.\"\"\"\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport requests_mock\nfrom common.net.http.requests_http_client import RequestsHttpClientBuilder\nfrom plugin.tcs_client import TcsClient\n\n\nSECRET = 'a3d9ed89deadbeef'\nCBID = '04041e8898e739ca33a250923e24f59ca41a8373f8cf6a45a1275f3b'\nIPV4 = '127.0.0.1'\nIPV6 = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'\nPORT = 8000\nDOMAIN = 'valid.com'\nURL = 'http://valid.com'\nINVALID_ADDRESS = 'http://invalid.com'\n\n\nclass TcsClientTest(parameterized.TestCase):\n\n  @classmethod\n  def setUpClass(cls):\n    super().setUpClass()\n    cls.http_client = RequestsHttpClientBuilder().build()\n\n  @parameterized.named_parameters(\n      (\n          'with_valid_ipv4_returns_uri',\n          IPV4,\n          PORT,\n          'http://%s:%s/%s' % (IPV4, PORT, CBID),\n      ),\n      (\n          'with_valid_ipv6_returns_uri',\n          IPV6,\n          PORT,\n          'http://[%s]:%s/%s' % (IPV6, PORT, CBID),\n      ),\n      (\n          'with_valid_domain_returns_uri',\n          DOMAIN,\n          PORT,\n          '%s.%s:%s' % (CBID, DOMAIN, PORT),\n      ),\n      (\n          'with_port_80_and_ip_returns_uri_without_port',\n          IPV4,\n          80,\n          'http://%s/%s' % (IPV4, CBID),\n      ),\n      (\n          'with_port_80_and_domain_returns_uri_without_port',\n          DOMAIN,\n          80,\n          '%s.%s' % (CBID, DOMAIN),\n      ),\n  )\n\n  def test_get_callback_uri(self, address, port, exepected_uri):\n    client = TcsClient(address, port, URL, self.http_client)\n    resulted_uri = client.get_callback_uri(SECRET)\n    self.assertEqual(resulted_uri, exepected_uri)\n\n  @parameterized.named_parameters(\n      ('with_valid_ipv4_returns_true', IPV4, URL, True),\n      ('with_valid_ipv6_returns_true', IPV6, URL, True),\n      ('with_valid_hostname_and_base_url_returns_true', DOMAIN, URL, True),\n      ('with_invalid_hostname_returns_false', '', URL, False),\n      ('with_invalid_base_url_returns_false', DOMAIN, '', False),\n  )\n  def test_is_callback_server_enabled(\n      self, address, url, expected\n  ):\n    client = TcsClient(address, PORT, url, self.http_client)\n    self.assertEqual(client.is_callback_server_enabled(), expected)\n\n  def test_tcs_client_with_invalid_port_raises_error(self):\n    with self.assertRaises(ValueError):\n      TcsClient(DOMAIN, 100000, URL, self.http_client)\n\n  @requests_mock.mock()\n  def test_has_oob_log_sends_polling_request(self, mock):\n    body = '{ \"has_dns_interaction\":false, \"has_http_interaction\":true}'\n    mock.register_uri(\n        'GET', '%s/?secret=%s' % (URL, SECRET), content=body.encode('utf-8')\n    )\n    client = TcsClient(DOMAIN, PORT, URL, self.http_client)\n    self.assertTrue(client.has_oob_log(SECRET))\n\n  @requests_mock.mock()\n  def test_has_oob_log_with_no_logs_returns_false(self, mock):\n    body = '{ \"has_dns_interaction\":false, \"has_http_interaction\":false}'\n    mock.register_uri(\n        'GET', '%s/?secret=%s' % (URL, SECRET), content=body.encode('utf-8')\n    )\n    client = TcsClient(DOMAIN, PORT, URL, self.http_client)\n    self.assertFalse(client.has_oob_log(SECRET))\n\n  @requests_mock.mock()\n  def test_has_oob_log_with_no_response_returns_false(self, mock):\n    mock.register_uri('GET', '%s/?secret=%s' % (URL, SECRET))\n    client = TcsClient(DOMAIN, PORT, URL, self.http_client)\n    self.assertFalse(client.has_oob_log(SECRET))\n\n  @requests_mock.mock()\n  def test_has_oob_log_with_unsuccessful_response_returns_false(self, mock):\n    mock.register_uri('GET', '%s/?secret=%s' % (URL, SECRET), status_code=500)\n    client = TcsClient(DOMAIN, PORT, URL, self.http_client)\n    self.assertFalse(client.has_oob_log(SECRET))\n\n  def test_create_tscclient_removes_trailing_slashes(self):\n    client = TcsClient(DOMAIN, PORT, 'http://my-url.com//', self.http_client)\n    self.assertEqual('http://my-url.com', client.polling_base_url)\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/plugin_server.py",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Main gRPC server to execute Python Tsunami plugins.\"\"\"\n\nfrom concurrent import futures\nimport importlib\nimport pkgutil\nimport signal\nimport threading\nimport types\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nimport grpc\nfrom grpc_health.v1 import health\nfrom grpc_health.v1 import health_pb2\nfrom grpc_health.v1 import health_pb2_grpc\nfrom grpc_reflection.v1alpha import reflection\n\nimport plugin_service\nimport tsunami_plugin\nfrom common.net.http.requests_http_client import RequestsHttpClientBuilder\nfrom plugin.payload import payload_utility\nfrom plugin.payload.payload_generator import PayloadGenerator\nfrom plugin.payload.payload_secret_generator import PayloadSecretGenerator\nfrom plugin.payload.payload_utility import get_parsed_payload\nfrom plugin.tcs_client import TcsClient\nimport plugin_service_pb2\nimport plugin_service_pb2_grpc\n\n\n_HOST = '127.0.0.1'\n_PORT = flags.DEFINE_integer('port', 34567, 'port to listen on.')\n_THREADS = flags.DEFINE_integer('threads', 10,\n                                'number of worker threads in thread pool.')\n_OUTPUT = flags.DEFINE_string('log_output', '/tmp',\n                              'server execution log directory.')\n_TIMEOUT_SEC = flags.DEFINE_float(\n    'timeout_seconds', 10, 'Timeout in seconds for complete HTTP calls.'\n)\n_LOG_ID = flags.DEFINE_string('log_id', '',\n                              'id to track logs for all outgoing HTTP calls.')\n_TRUST_ALL_SSL_CERT = flags.DEFINE_boolean(\n    'trust_all_ssl_cert', True, 'Trust all SSL certificates on HTTPS traffic.'\n)\n_CALLBACK_ADDRESS = flags.DEFINE_string(\n    'callback_address',\n    '127.0.0.1',\n    'Hostname or IP address of the callback server.',\n)\n_CALLBACK_PORT = flags.DEFINE_integer(\n    'callback_port', 8881, 'Callback server port for HTTP logging service.'\n)\n_CALLBACK_POLLING_URI = flags.DEFINE_string(\n    'polling_uri',\n    'http://127.0.0.1:8880',\n    'Callback server URI for log polling service.',\n)\n_PAYLOAD_FILE_PATH = flags.DEFINE_string(\n    'payload_file_path',\n    '',\n    'Path to the payload_definitions.yaml file.',\n)\n\n\ndef main(unused_argv):\n  _configure_log()\n  payload_utility.set_payload_file_path(_PAYLOAD_FILE_PATH.value)\n\n  # Load plugins from tsunami_plugins repository.\n  plugin_pkg = importlib.import_module(\n      'py_plugins'\n  )\n  _import_py_plugins(plugin_pkg)\n\n  server_addr = f'{_HOST}:{_PORT.value}'\n  server = grpc.server(futures.ThreadPoolExecutor(max_workers=_THREADS.value))\n\n  _configure_plugin_service(server)\n  health_servicer = _register_health_service(server)\n  server.add_insecure_port(server_addr)\n\n  server.start()\n  logging.info('Server started at %s.', server_addr)\n  # Set to SERVING after server proves its availability.\n  _set_health_service_to_serving(server, health_servicer)\n\n  # Java Process.destroy() sends SIGTERM\n  sig_term_received = threading.Event()\n\n  def on_sigterm(signum, frame):\n    logging.info('Got signal %s, %s', signum, frame)\n    sig_term_received.set()\n\n  signal.signal(signal.SIGTERM, on_sigterm)\n  sig_term_received.wait()\n  logging.info('Stopped RPC server, Waiting for RPCs to complete...')\n  server.stop(3).wait()\n  logging.info('Done stopping server')\n\n\ndef _import_py_plugins(plugin_pkg: types.ModuleType):\n  \"\"\"Imports all Python Tsunami plugin modules.\"\"\"\n  for _, name, is_pkg in pkgutil.walk_packages(plugin_pkg.__path__):\n    full_name = plugin_pkg.__name__ + '.' + name\n    pkg = importlib.import_module(full_name)\n    if is_pkg:\n      _import_py_plugins(pkg)\n    else:\n      logging.info('Loaded plugin module %s', full_name)\n\n\ndef _configure_log():\n  \"\"\"Store and print out log stream.\"\"\"\n  logging.use_absl_handler()\n  logger = logging.get_absl_handler()\n  logger.use_absl_log_file(\n      'py_plugin_server', _OUTPUT.value\n  )\n  # Label the log record that comes from the python server for debugging.\n  logger.setFormatter(\n      logging.PythonFormatter('[Python Server] %(asctime)s %(message)s')\n  )\n  logging.set_verbosity(logging.INFO)\n\n\ndef _configure_plugin_service(server):\n  \"\"\"Configures the main plugin service for handling plugin related gRPC requests.\"\"\"\n  http_client = (\n      RequestsHttpClientBuilder()\n      .set_timeout_sec(_TIMEOUT_SEC.value)\n      .set_verify_ssl(not _TRUST_ALL_SSL_CERT.value)\n      .set_log_id(_LOG_ID.value)\n      .build()\n  )\n  callback_client = TcsClient(\n      _CALLBACK_ADDRESS.value,\n      _CALLBACK_PORT.value,\n      _CALLBACK_POLLING_URI.value,\n      http_client,\n  )\n  payload_generator = PayloadGenerator(\n      PayloadSecretGenerator(), get_parsed_payload(), callback_client\n  )\n  # Get all VulnDetector class implementations.\n  plugins = [\n      cls(http_client, payload_generator)\n      for cls in tsunami_plugin.VulnDetector.__subclasses__()\n  ]\n  logging.info('Configured %d python plugin:', len(plugins))\n  for plugin in plugins:\n    logging.info('\\t%s', plugin.GetPluginDefinition().info.name)\n  servicer = plugin_service.PluginServiceServicer(\n      py_plugins=plugins, max_workers=_THREADS.value\n  )\n  plugin_service_pb2_grpc.add_PluginServiceServicer_to_server(servicer, server)\n\n\ndef _register_health_service(server):\n  \"\"\"Add gRPC health checking service to server.\"\"\"\n  health_servicer = health.HealthServicer(\n      experimental_non_blocking=True,\n      experimental_thread_pool=futures.ThreadPoolExecutor(\n          max_workers=_THREADS.value))\n  health_pb2_grpc.add_HealthServicer_to_server(health_servicer, server)\n  return health_servicer\n\n\ndef _set_health_service_to_serving(server, health_servicer):\n  \"\"\"Set gRPC health checking service to SERVING.\"\"\"\n  # Set all services to SERVING.\n  services = tuple(service.full_name\n                   for service in plugin_service_pb2.DESCRIPTOR.services_by_name\n                   .values()) + (reflection.SERVICE_NAME, health.SERVICE_NAME)\n  for service in services:\n    health_servicer.set(service, health_pb2.HealthCheckResponse.SERVING)\n    logging.info('Service %s is now SERVING', service)\n  reflection.enable_server_reflection(services, server)\n\n\nif __name__ == '__main__':\n  flags.set_default(logging.ALSOLOGTOSTDERR, True)\n  app.run(main)\n"
  },
  {
    "path": "plugin_server/py/plugin_service.py",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Python gRPC PluginService server adapter to provide communication with the Java client.\"\"\"\n\nfrom concurrent import futures\nfrom typing import cast\n\nfrom absl import logging\n\nimport tsunami_plugin\nimport detection_pb2\nimport plugin_representation_pb2\nimport plugin_service_pb2\nimport plugin_service_pb2_grpc\n\nRunResponse = plugin_service_pb2.RunResponse\nListPluginsRequest = plugin_service_pb2.ListPluginsRequest\nListPluginsResponse = plugin_service_pb2.ListPluginsResponse\n_PluginServiceServicer = plugin_service_pb2_grpc.PluginServiceServicer\n_PluginType = plugin_representation_pb2.PluginInfo.PluginType\n\n_DETECTION_TIMEOUT = 60\n\n\nclass PluginServiceServicer(plugin_service_pb2_grpc.PluginServiceServicer):\n  \"\"\"PluginService server implementation for communication with the Java client.\n\n  This class executes requests called by the Java client. All request types are\n  given by the plugin_service proto definition.\n  \"\"\"\n\n  def __init__(self, py_plugins: list[tsunami_plugin.TsunamiPlugin],\n               max_workers: int):\n    self.py_plugins = py_plugins\n    self.max_workers = max_workers\n\n  def Run(\n      self,\n      request: plugin_service_pb2.RunRequest,\n      servicer_context: plugin_service_pb2_grpc.PluginServiceServicer,\n  ) -> RunResponse:\n    logging.info('Received Run request: %s', request)\n    report_list = detection_pb2.DetectionReportList()\n\n    detection_futures = []\n\n    with futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:\n      for matched_plugin in request.plugins:\n        plugin_def = matched_plugin.plugin\n        for plugin in self.py_plugins:\n          if plugin.GetPluginDefinition() == plugin_def:\n            logging.info('Running python plugin %s.', type(plugin).__name__)\n\n            if plugin_def.info.type is _PluginType.VULN_DETECTION:\n              plugin = cast(tsunami_plugin.VulnDetector, plugin)\n              detection_futures.append(\n                  executor.submit(plugin.Detect, request.target,\n                                  matched_plugin.services))\n\n    for detection in detection_futures:\n      report_list.detection_reports.extend(\n          detection.result(timeout=_DETECTION_TIMEOUT).detection_reports)\n\n    response = RunResponse()\n    response.reports.CopyFrom(report_list)\n    return response\n\n  def ListPlugins(\n      self,\n      request: ListPluginsRequest,\n      servicer_context: _PluginServiceServicer,\n  ) -> ListPluginsResponse:\n    logging.info('Received ListPlugins request: %s', request)\n    response = ListPluginsResponse()\n    response.plugins.MergeFrom(\n        [plugin.GetPluginDefinition() for plugin in self.py_plugins])\n    return response\n"
  },
  {
    "path": "plugin_server/py/plugin_service_test.py",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Tests for plugin_service.\"\"\"\n\nimport ipaddress\nimport time\n\nfrom absl.testing import absltest\nimport grpc_testing\n\nfrom google.protobuf import timestamp_pb2\nimport plugin_service\nimport tsunami_plugin\nfrom common.net.http.http_client import HttpClient\nfrom common.net.http.requests_http_client import RequestsHttpClientBuilder\nfrom plugin.payload.payload_generator import PayloadGenerator\nfrom plugin.payload.payload_secret_generator import PayloadSecretGenerator\nfrom plugin.payload.payload_utility import get_parsed_payload\nfrom plugin.tcs_client import TcsClient\nimport detection_pb2\nimport network_pb2\nimport network_service_pb2\nimport plugin_representation_pb2\nimport plugin_service_pb2\nimport reconnaissance_pb2\nimport vulnerability_pb2\n\n\n_NetworkEndpoint = network_pb2.NetworkEndpoint\n_NetworkService = network_service_pb2.NetworkService\n_PluginInfo = plugin_representation_pb2.PluginInfo\n_TargetInfo = reconnaissance_pb2.TargetInfo\n_AddressFamily = network_pb2.AddressFamily\n_ServiceDescriptor = plugin_service_pb2.DESCRIPTOR.services_by_name[\n    'PluginService'\n]\n_RunMethod = _ServiceDescriptor.methods_by_name['Run']\n_ListPluginsMethod = _ServiceDescriptor.methods_by_name['ListPlugins']\nMAX_WORKERS = 1\n\n\nclass PluginServiceTest(absltest.TestCase):\n\n  def setUp(self):\n    super().setUp()\n    # payload generator and client setup\n    self.request_client = RequestsHttpClientBuilder().build()\n    psg = PayloadSecretGenerator()\n    callback_client = TcsClient(\n        '127.0.0.1', 8000, 'http://127.0.0.1:8000/test', self.request_client\n    )\n    self.payload_generator = PayloadGenerator(\n        psg, get_parsed_payload(), callback_client\n    )\n    self.test_plugin = FakeVulnDetector(\n        self.request_client, self.payload_generator\n    )\n    self._time = grpc_testing.strict_fake_time(time.time())\n    self._server = grpc_testing.server_from_dictionary(\n        {\n            _ServiceDescriptor: plugin_service.PluginServiceServicer(\n                py_plugins=[self.test_plugin], max_workers=MAX_WORKERS\n            ),\n        },\n        self._time,\n    )\n\n    self._channel = grpc_testing.channel(\n        plugin_service_pb2.DESCRIPTOR.services_by_name.values(), self._time\n    )\n\n  def tearDown(self):\n    self._channel.close()\n    super().tearDown()\n\n  def test_run_plugins_registered_returns_valid_response(self):\n    plugin_to_test = FakeVulnDetector(\n        self.request_client, self.payload_generator\n    )\n    endpoint = _build_network_endpoint('1.1.1.1', 80)\n    service = _NetworkService(\n        network_endpoint=endpoint,\n        transport_protocol=network_pb2.TCP,\n        service_name='http',\n    )\n    target = _TargetInfo(network_endpoints=[endpoint])\n    services = [service]\n    request = plugin_service_pb2.RunRequest(\n        target=target,\n        plugins=[\n            plugin_service_pb2.MatchedPlugin(\n                services=services, plugin=plugin_to_test.GetPluginDefinition()\n            )\n        ],\n    )\n\n    rpc = self._server.invoke_unary_unary(_RunMethod, (), request, None)\n    response, _, _, _ = rpc.termination()\n\n    self.assertLen(response.reports.detection_reports, 1)\n    self.assertEqual(\n        plugin_to_test._BuildFakeDetectionReport(\n            target=target, network_service=services[0]\n        ),\n        response.reports.detection_reports[0],\n    )\n\n  def test_run_no_plugins_registered_returns_empty_response(self):\n    endpoint = _build_network_endpoint('1.1.1.1', 80)\n    target = _TargetInfo(network_endpoints=[endpoint])\n    request = plugin_service_pb2.RunRequest(target=target, plugins=[])\n\n    rpc = self._server.invoke_unary_unary(_RunMethod, (), request, None)\n    response, _, _, _ = rpc.termination()\n\n    self.assertEmpty(response.reports.detection_reports)\n\n  def test_list_plugins_plugins_registered_returns_valid_response(self):\n    request = plugin_service.ListPluginsRequest()\n    rpc = self._server.invoke_unary_unary(_ListPluginsMethod, (), request, None)\n    response, _, _, _ = rpc.termination()\n    self.assertEqual(\n        plugin_service.ListPluginsResponse(\n            plugins=[self.test_plugin.GetPluginDefinition()]\n        ),\n        response,\n    )\n\n\ndef _build_network_endpoint(ip: str, port: int) -> _NetworkEndpoint:\n  return _NetworkEndpoint(\n      type=_NetworkEndpoint.IP,\n      ip_address=network_pb2.IpAddress(address_family=_get_address_family(ip)),\n      port=network_pb2.Port(port_number=port),\n  )\n\n\ndef _get_address_family(ip: str) -> _AddressFamily:\n  inet_addr = ipaddress.ip_address(ip)\n  if inet_addr.version == 4:\n    return _AddressFamily.IPV4\n  elif inet_addr.version == 6:\n    return _AddressFamily.IPV6\n  else:\n    raise ValueError(\"Unknown IP address family for IP '%s'\" % ip)\n\n\nclass FakeVulnDetector(tsunami_plugin.VulnDetector):\n  \"\"\"Fake Vulnerability detector class for testing only.\"\"\"\n\n  def __init__(\n      self,\n      http_client: HttpClient,\n      payload_generator: PayloadGenerator,\n  ):\n    self.http_client = http_client\n    self.payload_generator = payload_generator\n\n  def GetAdvisories(self) -> list[vulnerability_pb2.Vulnerability]:\n    \"\"\"Returns the advisories for this plugin.\"\"\"\n    return [\n        vulnerability_pb2.Vulnerability(\n            main_id=vulnerability_pb2.VulnerabilityId(\n                publisher='GOOGLE', value='FakeVuln1'\n            ),\n            severity=vulnerability_pb2.CRITICAL,\n            title='FakeTitle1',\n            description='FakeDescription1',\n        ),\n    ]\n\n  def GetPluginDefinition(self):\n    return tsunami_plugin.PluginDefinition(\n        info=_PluginInfo(\n            type=_PluginInfo.VULN_DETECTION,\n            name='fake',\n            version='v0.1',\n            description='fake description',\n            author='fake author',\n        ),\n        target_service_name=plugin_representation_pb2.TargetServiceName(\n            value=['fake service']\n        ),\n        target_software=plugin_representation_pb2.TargetSoftware(\n            name='fake software'\n        ),\n        for_web_service=False,\n    )\n\n  def Detect(self, target, matched_services):\n    return detection_pb2.DetectionReportList(\n        detection_reports=[\n            self._BuildFakeDetectionReport(target, matched_services[0])\n        ]\n    )\n\n  def _BuildFakeDetectionReport(self, target, network_service):\n    return detection_pb2.DetectionReport(\n        target_info=target,\n        network_service=network_service,\n        detection_timestamp=timestamp_pb2.Timestamp(nanos=1234567890),\n        detection_status=detection_pb2.VULNERABILITY_VERIFIED,\n        vulnerability=self.GetAdvisories()[0],\n    )\n\n\n# TODO(b/239628051): Add a failed VulnDetector class to test failed cases.\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "plugin_server/py/requirements.in",
    "content": "absl-py==2.1.0\naenum==3.1.15\ncertifi==2024.7.4\ncharset-normalizer==3.3.2\nglog==0.3.1\ngrpcio==1.63.0\ngrpcio-health-checking==1.63.0\ngrpcio-reflection==1.63.0\ngrpcio-tools==1.63.0\nidna==3.7\nprotobuf==5.29.6\npython-gflags==3.1.2\npyzmq==27.0.1\nrequests==2.32.4\nrequests-mock==1.12.1\nruamel.yaml==0.18.6\nruamel.yaml.clib==0.2.8\nsix==1.16.0\nurllib3==2.6.3\n"
  },
  {
    "path": "plugin_server/py/requirements.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.12\n# by the following command:\n#\n#    pip-compile --allow-unsafe --generate-hashes requirements.in\n#\nabsl-py==2.1.0 \\\n    --hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308 \\\n    --hash=sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff\n    # via -r requirements.in\naenum==3.1.15 \\\n    --hash=sha256:27b1710b9d084de6e2e695dab78fe9f269de924b51ae2850170ee7e1ca6288a5 \\\n    --hash=sha256:8cbd76cd18c4f870ff39b24284d3ea028fbe8731a58df3aa581e434c575b9559 \\\n    --hash=sha256:e0dfaeea4c2bd362144b87377e2c61d91958c5ed0b4daf89cb6f45ae23af6288\n    # via -r requirements.in\ncertifi==2024.7.4 \\\n    --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \\\n    --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90\n    # via\n    #   -r requirements.in\n    #   requests\ncharset-normalizer==3.3.2 \\\n    --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \\\n    --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \\\n    --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \\\n    --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \\\n    --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \\\n    --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \\\n    --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \\\n    --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \\\n    --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \\\n    --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \\\n    --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \\\n    --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \\\n    --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \\\n    --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \\\n    --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \\\n    --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \\\n    --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \\\n    --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \\\n    --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \\\n    --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \\\n    --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \\\n    --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \\\n    --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \\\n    --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \\\n    --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \\\n    --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \\\n    --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \\\n    --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \\\n    --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \\\n    --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \\\n    --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \\\n    --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \\\n    --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \\\n    --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \\\n    --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \\\n    --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \\\n    --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \\\n    --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \\\n    --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \\\n    --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \\\n    --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \\\n    --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \\\n    --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \\\n    --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \\\n    --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \\\n    --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \\\n    --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \\\n    --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \\\n    --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \\\n    --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \\\n    --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \\\n    --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \\\n    --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \\\n    --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \\\n    --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \\\n    --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \\\n    --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \\\n    --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \\\n    --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \\\n    --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \\\n    --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \\\n    --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \\\n    --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \\\n    --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \\\n    --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \\\n    --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \\\n    --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \\\n    --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \\\n    --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \\\n    --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \\\n    --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \\\n    --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \\\n    --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \\\n    --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \\\n    --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \\\n    --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \\\n    --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \\\n    --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \\\n    --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \\\n    --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \\\n    --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \\\n    --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \\\n    --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \\\n    --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \\\n    --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \\\n    --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \\\n    --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \\\n    --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \\\n    --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \\\n    --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561\n    # via\n    #   -r requirements.in\n    #   requests\nglog==0.3.1 \\\n    --hash=sha256:88cee83dea8bddf73db7edbf5bd697237628389ef476c0a0ecad639c606189e5 \\\n    --hash=sha256:b721edef6009eabc0b4d9f2619e153d2627a7b71a3657c8ed69f02ef7c78be97\n    # via -r requirements.in\ngrpcio==1.63.0 \\\n    --hash=sha256:01799e8649f9e94ba7db1aeb3452188048b0019dc37696b0f5ce212c87c560c3 \\\n    --hash=sha256:0697563d1d84d6985e40ec5ec596ff41b52abb3fd91ec240e8cb44a63b895094 \\\n    --hash=sha256:08e1559fd3b3b4468486b26b0af64a3904a8dbc78d8d936af9c1cf9636eb3e8b \\\n    --hash=sha256:166e5c460e5d7d4656ff9e63b13e1f6029b122104c1633d5f37eaea348d7356d \\\n    --hash=sha256:1ff737cf29b5b801619f10e59b581869e32f400159e8b12d7a97e7e3bdeee6a2 \\\n    --hash=sha256:219bb1848cd2c90348c79ed0a6b0ea51866bc7e72fa6e205e459fedab5770172 \\\n    --hash=sha256:259e11932230d70ef24a21b9fb5bb947eb4703f57865a404054400ee92f42f5d \\\n    --hash=sha256:2e93aca840c29d4ab5db93f94ed0a0ca899e241f2e8aec6334ab3575dc46125c \\\n    --hash=sha256:3a6d1f9ea965e750db7b4ee6f9fdef5fdf135abe8a249e75d84b0a3e0c668a1b \\\n    --hash=sha256:50344663068041b34a992c19c600236e7abb42d6ec32567916b87b4c8b8833b3 \\\n    --hash=sha256:56cdf96ff82e3cc90dbe8bac260352993f23e8e256e063c327b6cf9c88daf7a9 \\\n    --hash=sha256:5c039ef01516039fa39da8a8a43a95b64e288f79f42a17e6c2904a02a319b357 \\\n    --hash=sha256:6426e1fb92d006e47476d42b8f240c1d916a6d4423c5258ccc5b105e43438f61 \\\n    --hash=sha256:65bf975639a1f93bee63ca60d2e4951f1b543f498d581869922910a476ead2f5 \\\n    --hash=sha256:6a1a3642d76f887aa4009d92f71eb37809abceb3b7b5a1eec9c554a246f20e3a \\\n    --hash=sha256:6ef0ad92873672a2a3767cb827b64741c363ebaa27e7f21659e4e31f4d750280 \\\n    --hash=sha256:756fed02dacd24e8f488f295a913f250b56b98fb793f41d5b2de6c44fb762434 \\\n    --hash=sha256:75f701ff645858a2b16bc8c9fc68af215a8bb2d5a9b647448129de6e85d52bce \\\n    --hash=sha256:8064d986d3a64ba21e498b9a376cbc5d6ab2e8ab0e288d39f266f0fca169b90d \\\n    --hash=sha256:878b1d88d0137df60e6b09b74cdb73db123f9579232c8456f53e9abc4f62eb3c \\\n    --hash=sha256:8f3f6883ce54a7a5f47db43289a0a4c776487912de1a0e2cc83fdaec9685cc9f \\\n    --hash=sha256:91b73d3f1340fefa1e1716c8c1ec9930c676d6b10a3513ab6c26004cb02d8b3f \\\n    --hash=sha256:93a46794cc96c3a674cdfb59ef9ce84d46185fe9421baf2268ccb556f8f81f57 \\\n    --hash=sha256:93f45f27f516548e23e4ec3fbab21b060416007dbe768a111fc4611464cc773f \\\n    --hash=sha256:9e350cb096e5c67832e9b6e018cf8a0d2a53b2a958f6251615173165269a91b0 \\\n    --hash=sha256:a2d60cd1d58817bc5985fae6168d8b5655c4981d448d0f5b6194bbcc038090d2 \\\n    --hash=sha256:a3abfe0b0f6798dedd2e9e92e881d9acd0fdb62ae27dcbbfa7654a57e24060c0 \\\n    --hash=sha256:a44624aad77bf8ca198c55af811fd28f2b3eaf0a50ec5b57b06c034416ef2d0a \\\n    --hash=sha256:a7b19dfc74d0be7032ca1eda0ed545e582ee46cd65c162f9e9fc6b26ef827dc6 \\\n    --hash=sha256:ad2ac8903b2eae071055a927ef74121ed52d69468e91d9bcbd028bd0e554be6d \\\n    --hash=sha256:b005292369d9c1f80bf70c1db1c17c6c342da7576f1c689e8eee4fb0c256af85 \\\n    --hash=sha256:b2e44f59316716532a993ca2966636df6fbe7be4ab6f099de6815570ebe4383a \\\n    --hash=sha256:b3afbd9d6827fa6f475a4f91db55e441113f6d3eb9b7ebb8fb806e5bb6d6bd0d \\\n    --hash=sha256:b416252ac5588d9dfb8a30a191451adbf534e9ce5f56bb02cd193f12d8845b7f \\\n    --hash=sha256:b5194775fec7dc3dbd6a935102bb156cd2c35efe1685b0a46c67b927c74f0cfb \\\n    --hash=sha256:cacdef0348a08e475a721967f48206a2254a1b26ee7637638d9e081761a5ba86 \\\n    --hash=sha256:cd1e68776262dd44dedd7381b1a0ad09d9930ffb405f737d64f505eb7f77d6c7 \\\n    --hash=sha256:cdcda1156dcc41e042d1e899ba1f5c2e9f3cd7625b3d6ebfa619806a4c1aadda \\\n    --hash=sha256:cf8dae9cc0412cb86c8de5a8f3be395c5119a370f3ce2e69c8b7d46bb9872c8d \\\n    --hash=sha256:d2497769895bb03efe3187fb1888fc20e98a5f18b3d14b606167dacda5789434 \\\n    --hash=sha256:e3b77eaefc74d7eb861d3ffbdf91b50a1bb1639514ebe764c47773b833fa2d91 \\\n    --hash=sha256:e48cee31bc5f5a31fb2f3b573764bd563aaa5472342860edcc7039525b53e46a \\\n    --hash=sha256:e4cbb2100ee46d024c45920d16e888ee5d3cf47c66e316210bc236d5bebc42b3 \\\n    --hash=sha256:f28f8b2db7b86c77916829d64ab21ff49a9d8289ea1564a2b2a3a8ed9ffcccd3 \\\n    --hash=sha256:f3023e14805c61bc439fb40ca545ac3d5740ce66120a678a3c6c2c55b70343d1 \\\n    --hash=sha256:fdf348ae69c6ff484402cfdb14e18c1b0054ac2420079d575c53a60b9b2853ae\n    # via\n    #   -r requirements.in\n    #   grpcio-health-checking\n    #   grpcio-reflection\n    #   grpcio-tools\ngrpcio-health-checking==1.63.0 \\\n    --hash=sha256:43b90ad740ae6c5655b95fe2a054d1cc05b77a1c50363540963b3b750356ab50 \\\n    --hash=sha256:ebd4ea09aeb6ef314da86b784d2423705f378cbbfff7be7091bce29602614a54\n    # via -r requirements.in\ngrpcio-reflection==1.63.0 \\\n    --hash=sha256:280854ce7ca4e998df125c877c45cccb77c6acd55cd87c286ca0c6ef60eb95d5 \\\n    --hash=sha256:cb53c67f1c917a66e3864e266c1bea8d77b95e10b9a21a4bb2a60c2f4d8b2b4d\n    # via -r requirements.in\ngrpcio-tools==1.63.0 \\\n    --hash=sha256:0ca6d5623dadce66fabbd8b04d0572e35fd63b31f1ae7ea1555d662864852d33 \\\n    --hash=sha256:0f8ce3fc598886a5370f28c86f94d06ddb0d3a251101a5bb8ed9576d9f86a519 \\\n    --hash=sha256:1ab17460a2dfd3433af3120598bc18e705e3092d4d8396d3c06fe93deab19bbb \\\n    --hash=sha256:1b88be61eaa41eb4deb6b91a1e21d2a789d8567f0a973694fa27c09196f39a9a \\\n    --hash=sha256:2474cffbc8f29404f0e3a2109c0a0423211ba93fe048b144e734f601ff391fc7 \\\n    --hash=sha256:27684446c81bffcd4f20f13bf672ac7cbeaefbd270b3d867cdb58132e4b866bc \\\n    --hash=sha256:2924747142ebcbbd62acf65936fbc9694afbdfc2c6ae721461015370e27b8d6f \\\n    --hash=sha256:32247ac2d575a633aea2536840fd232d56f309bd940081d772081bd71e0626c6 \\\n    --hash=sha256:376136b9bbd16304a2e550ea0bb2b3340b720a0f623856124987845ef071d479 \\\n    --hash=sha256:3ef50fa15689f46a2c903f1c9687aa40687d67dcb0469903fff37a63e55f11cd \\\n    --hash=sha256:3f138c822090e7c87ef6a5dce0a6c4fdf68a9472e6a936b70ac7be2371184abe \\\n    --hash=sha256:409613bb694308a1945256d1d05c3ef3497f9fbf7fd68bd8bed86d80d97df334 \\\n    --hash=sha256:4374c8beefec84f682c799b8df5ac4b217c09de6d69038ce16fc12dcd862fff8 \\\n    --hash=sha256:49404876ec70bdae431eac5b1591c32c0bba4047dfd96dc6add03dbcdad5a5fc \\\n    --hash=sha256:49435413548e019921e125b178f3fd30543aa348c70775669b5ed80f0b39b393 \\\n    --hash=sha256:49af114fed0075025fe243cb3c8405c7a99f0b87f4eb7ccdaaf33ce1f55d8318 \\\n    --hash=sha256:517ed2b405793e55c527f332296ae92a3e17fdd83772f1569709f2c228acaf54 \\\n    --hash=sha256:54136ac94eabc45b1b72d5ca379e5a2753f21a654f562838c5a9b706482bc1f0 \\\n    --hash=sha256:632f78d8730d39363fc5afaf7cb5cf2f56b4e346ca11f550af74cff85e702f79 \\\n    --hash=sha256:63a975d0457b2db1ee19fe99806091c71ad22f6f3664adc8f4c95943684dc0fd \\\n    --hash=sha256:6bbf51f334452fcac422509979635f97e2c2c3e71f21480891f2ba280b4b6867 \\\n    --hash=sha256:711d9f038c18c2f637b89af70c485018ae437dff5f7d2c631ca6a1eee7563038 \\\n    --hash=sha256:744952a560fdb060a5f9d467d130fde6dbfee2abb07143c87e9b17aae3c19d5a \\\n    --hash=sha256:7cbf570f7b9badd3bd27be5e057ca466d447c1047bf80c87a53d8bcb2e87bbbe \\\n    --hash=sha256:8341846604df00cf1c0a822476d27f4c481f678601a2f0b190e3b9936f857ded \\\n    --hash=sha256:847ca8d75090d66e787576049500eb7c230a9997146d5d433da7928baf914273 \\\n    --hash=sha256:94b52c0dfb6026f69858a10ee3eadf15c343667647b5846cace82f61fe809c88 \\\n    --hash=sha256:acb5cc845942dc0f020eefbe10ad8ac6fe2f96b99c035da738c5d3026d3a5324 \\\n    --hash=sha256:b2d246eee3b2a6afe65362c22a98b0e6d805c227c2569c5616ad3bec619621dd \\\n    --hash=sha256:b5d74a30409eda2a0cdaa700da23fe3cad5d7ac47ac2d52644abe13a84047aa0 \\\n    --hash=sha256:b61682c06f0bcf2c576537819c42d5fb8eec1a0a9c05c905005200a57ff54c1f \\\n    --hash=sha256:b87750347cb024bb74d5139da01ffba9f099ad2e43ba45893dc627ec920d57ab \\\n    --hash=sha256:bc0e6af05f66b36186ad3467d46ecc0f51dc9fa366005e095f4aa7739c7bfcba \\\n    --hash=sha256:c305274aa111412f5b8858242853e56c16ebcedc25d6a49ad615fd1b3ecd5971 \\\n    --hash=sha256:c63a0f37b6b64dc31b2f1a0e5f889ae8b6bb7b7b20fe2406c1285321a7c54fdd \\\n    --hash=sha256:c759306c04e3d0b3da3bd576e3de8bcbccc31a243a85ad256fd46d3a3ed93402 \\\n    --hash=sha256:cb9a0f61cabff426eaf5c0a506de599c9f006b31947ba1159254cc291c1dbdd1 \\\n    --hash=sha256:d58a5aacee102858e49b1cc89b1ba1a020bb04f001df057e2b03fa11e6c636d1 \\\n    --hash=sha256:d7142b0162834d3a67df532744a733b0757b11056373bd489a70dc07a3f65829 \\\n    --hash=sha256:df4dc9db9763594ae6ae04b42d6fcf7f163897a072f8fc946b864c9c3f0fbab1 \\\n    --hash=sha256:e197d5de49bb024f3d0b9e1ee1a0cce9e39955e17738bfbed72b0cc506a4824c \\\n    --hash=sha256:e68d9df9134906cbab1b225b625e11a368ab01b9ff24a4546bddec705ec7fd66 \\\n    --hash=sha256:e952835e7b8f40204bceb2a96fc7bcb8b07ff45ca9d07266774bc429db1efead \\\n    --hash=sha256:f2cc0b3098ff48811ca821440e03763dcabd11158a11d9ea819c58938a9ea276 \\\n    --hash=sha256:f305a5d61613e7ea3510eab62d65d47dff5206fcbe3b2347a7c1ebc9eff23dc6 \\\n    --hash=sha256:f74a6da9db48296c3e7e34820e96744a0ea9cd58c3fa075ed206f7bb75229324\n    # via -r requirements.in\nidna==3.7 \\\n    --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \\\n    --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0\n    # via\n    #   -r requirements.in\n    #   requests\nprotobuf==5.29.6 \\\n    --hash=sha256:36ade6ff88212e91aef4e687a971a11d7d24d6948a66751abc1b3238648f5d05 \\\n    --hash=sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1 \\\n    --hash=sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86 \\\n    --hash=sha256:76e07e6567f8baf827137e8d5b8204b6c7b6488bbbff1bf0a72b383f77999c18 \\\n    --hash=sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda \\\n    --hash=sha256:831e2da16b6cc9d8f1654c041dd594eda43391affd3c03a91bea7f7f6da106d6 \\\n    --hash=sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6 \\\n    --hash=sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269 \\\n    --hash=sha256:cb4c86de9cd8a7f3a256b9744220d87b847371c6b2f10bde87768918ef33ba49 \\\n    --hash=sha256:da9ee6a5424b6b30fd5e45c5ea663aef540ca95f9ad99d1e887e819cdf9b8723 \\\n    --hash=sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9\n    # via\n    #   -r requirements.in\n    #   grpcio-health-checking\n    #   grpcio-reflection\n    #   grpcio-tools\npython-gflags==3.1.2 \\\n    --hash=sha256:40ae131e899ef68e9e14aa53ca063839c34f6a168afe622217b5b875492a1ee2\n    # via\n    #   -r requirements.in\n    #   glog\npyzmq==27.0.1 \\\n    --hash=sha256:05a94233fdde585eb70924a6e4929202a747eea6ed308a6171c4f1c715bbe39e \\\n    --hash=sha256:092f4011b26d6b0201002f439bd74b38f23f3aefcb358621bdc3b230afc9b2d5 \\\n    --hash=sha256:0ec09073ed67ae236785d543df3b322282acc0bdf6d1b748c3e81f3043b21cb5 \\\n    --hash=sha256:0f772eea55cccce7f45d6ecdd1d5049c12a77ec22404f6b892fae687faa87bee \\\n    --hash=sha256:0fc24bf45e4a454e55ef99d7f5c8b8712539200ce98533af25a5bfa954b6b390 \\\n    --hash=sha256:119ce8590409702394f959c159d048002cbed2f3c0645ec9d6a88087fc70f0f1 \\\n    --hash=sha256:1843fd0daebcf843fe6d4da53b8bdd3fc906ad3e97d25f51c3fed44436d82a49 \\\n    --hash=sha256:19dce6c93656f9c469540350d29b128cd8ba55b80b332b431b9a1e9ff74cfd01 \\\n    --hash=sha256:1c363c6dc66352331d5ad64bb838765c6692766334a6a02fdb05e76bd408ae18 \\\n    --hash=sha256:1d59dad4173dc2a111f03e59315c7bd6e73da1a9d20a84a25cf08325b0582b1a \\\n    --hash=sha256:1da8e645c655d86f0305fb4c65a0d848f461cd90ee07d21f254667287b5dbe50 \\\n    --hash=sha256:2329f0c87f0466dce45bba32b63f47018dda5ca40a0085cc5c8558fea7d9fc55 \\\n    --hash=sha256:27a78bdd384dbbe7b357af95f72efe8c494306b5ec0a03c31e2d53d6763e5307 \\\n    --hash=sha256:2852f67371918705cc18b321695f75c5d653d5d8c4a9b946c1eec4dab2bd6fdf \\\n    --hash=sha256:313a7b374e3dc64848644ca348a51004b41726f768b02e17e689f1322366a4d9 \\\n    --hash=sha256:351bf5d8ca0788ca85327fda45843b6927593ff4c807faee368cc5aaf9f809c2 \\\n    --hash=sha256:4401649bfa0a38f0f8777f8faba7cd7eb7b5b8ae2abc7542b830dd09ad4aed0d \\\n    --hash=sha256:44909aa3ed2234d69fe81e1dade7be336bcfeab106e16bdaa3318dcde4262b93 \\\n    --hash=sha256:45c3e00ce16896ace2cd770ab9057a7cf97d4613ea5f2a13f815141d8b6894b9 \\\n    --hash=sha256:45c549204bc20e7484ffd2555f6cf02e572440ecf2f3bdd60d4404b20fddf64b \\\n    --hash=sha256:497bd8af534ae55dc4ef67eebd1c149ff2a0b0f1e146db73c8b5a53d83c1a5f5 \\\n    --hash=sha256:4b9d8e26fb600d0d69cc9933e20af08552e97cc868a183d38a5c0d661e40dfbb \\\n    --hash=sha256:4bca8abc31799a6f3652d13f47e0b0e1cab76f9125f2283d085a3754f669b607 \\\n    --hash=sha256:4c3874344fd5fa6d58bb51919708048ac4cab21099f40a227173cddb76b4c20b \\\n    --hash=sha256:4f6886c59ba93ffde09b957d3e857e7950c8fe818bd5494d9b4287bc6d5bc7f1 \\\n    --hash=sha256:5268a5a9177afff53dc6d70dffe63114ba2a6e7b20d9411cc3adeba09eeda403 \\\n    --hash=sha256:544b995a6a1976fad5d7ff01409b4588f7608ccc41be72147700af91fd44875d \\\n    --hash=sha256:56a3b1853f3954ec1f0e91085f1350cc57d18f11205e4ab6e83e4b7c414120e0 \\\n    --hash=sha256:571f762aed89025ba8cdcbe355fea56889715ec06d0264fd8b6a3f3fa38154ed \\\n    --hash=sha256:57bb92abdb48467b89c2d21da1ab01a07d0745e536d62afd2e30d5acbd0092eb \\\n    --hash=sha256:58cca552567423f04d06a075f4b473e78ab5bdb906febe56bf4797633f54aa4e \\\n    --hash=sha256:64ca3c7c614aefcdd5e358ecdd41d1237c35fe1417d01ec0160e7cdb0a380edc \\\n    --hash=sha256:678e50ec112bdc6df5a83ac259a55a4ba97a8b314c325ab26b3b5b071151bc61 \\\n    --hash=sha256:696900ef6bc20bef6a242973943574f96c3f97d2183c1bd3da5eea4f559631b1 \\\n    --hash=sha256:6dcbcb34f5c9b0cefdfc71ff745459241b7d3cda5b27c7ad69d45afc0821d1e1 \\\n    --hash=sha256:6f02f30a4a6b3efe665ab13a3dd47109d80326c8fd286311d1ba9f397dc5f247 \\\n    --hash=sha256:70b719a130b81dd130a57ac0ff636dc2c0127c5b35ca5467d1b67057e3c7a4d2 \\\n    --hash=sha256:72d235d6365ca73d8ce92f7425065d70f5c1e19baa458eb3f0d570e425b73a96 \\\n    --hash=sha256:7418fb5736d0d39b3ecc6bec4ff549777988feb260f5381636d8bd321b653038 \\\n    --hash=sha256:77fed80e30fa65708546c4119840a46691290efc231f6bfb2ac2a39b52e15811 \\\n    --hash=sha256:7ebccf0d760bc92a4a7c751aeb2fef6626144aace76ee8f5a63abeb100cae87f \\\n    --hash=sha256:7fb0ee35845bef1e8c4a152d766242164e138c239e3182f558ae15cb4a891f94 \\\n    --hash=sha256:87aebf4acd7249bdff8d3df03aed4f09e67078e6762cfe0aecf8d0748ff94cde \\\n    --hash=sha256:88dc92d9eb5ea4968123e74db146d770b0c8d48f0e2bfb1dbc6c50a8edb12d64 \\\n    --hash=sha256:8c62297bc7aea2147b472ca5ca2b4389377ad82898c87cabab2a94aedd75e337 \\\n    --hash=sha256:8f617f60a8b609a13099b313e7e525e67f84ef4524b6acad396d9ff153f6e4cd \\\n    --hash=sha256:90a4da42aa322de8a3522461e3b5fe999935763b27f69a02fced40f4e3cf9682 \\\n    --hash=sha256:95594b2ceeaa94934e3e94dd7bf5f3c3659cf1a26b1fb3edcf6e42dad7e0eaf2 \\\n    --hash=sha256:9729190bd770314f5fbba42476abf6abe79a746eeda11d1d68fd56dd70e5c296 \\\n    --hash=sha256:9d16fdfd7d70a6b0ca45d36eb19f7702fa77ef6256652f17594fc9ce534c9da6 \\\n    --hash=sha256:9d7b6b90da7285642f480b48c9efd1d25302fd628237d8f6f6ee39ba6b2d2d34 \\\n    --hash=sha256:a066ea6ad6218b4c233906adf0ae67830f451ed238419c0db609310dd781fbe7 \\\n    --hash=sha256:a27fa11ebaccc099cac4309c799aa33919671a7660e29b3e465b7893bc64ec81 \\\n    --hash=sha256:a4aca06ba295aa78bec9b33ec028d1ca08744c36294338c41432b7171060c808 \\\n    --hash=sha256:af2ee67b3688b067e20fea3fe36b823a362609a1966e7e7a21883ae6da248804 \\\n    --hash=sha256:af7ebce2a1e7caf30c0bb64a845f63a69e76a2fadbc1cac47178f7bb6e657bdd \\\n    --hash=sha256:b007e5dcba684e888fbc90554cb12a2f4e492927c8c2761a80b7590209821743 \\\n    --hash=sha256:b25e72e115399a4441aad322258fa8267b873850dc7c276e3f874042728c2b45 \\\n    --hash=sha256:b978c0678cffbe8860ec9edc91200e895c29ae1ac8a7085f947f8e8864c489fb \\\n    --hash=sha256:b99ea9d330e86ce1ff7f2456b33f1bf81c43862a5590faf4ef4ed3a63504bdab \\\n    --hash=sha256:b9fd0fda730461f510cfd9a40fafa5355d65f5e3dbdd8d6dfa342b5b3f5d1949 \\\n    --hash=sha256:ba068f28028849da725ff9185c24f832ccf9207a40f9b28ac46ab7c04994bd41 \\\n    --hash=sha256:be45a895f98877271e8a0b6cf40925e0369121ce423421c20fa6d7958dc753c2 \\\n    --hash=sha256:bee5248d5ec9223545f8cc4f368c2d571477ae828c99409125c3911511d98245 \\\n    --hash=sha256:c512824360ea7490390566ce00bee880e19b526b312b25cc0bc30a0fe95cb67f \\\n    --hash=sha256:c9180d1f5b4b73e28b64e63cc6c4c097690f102aa14935a62d5dd7426a4e5b5a \\\n    --hash=sha256:c96702e1082eab62ae583d64c4e19c9b848359196697e536a0c57ae9bd165bd5 \\\n    --hash=sha256:c9d63d66059114a6756d09169c9209ffceabacb65b9cb0f66e6fc344b20b73e6 \\\n    --hash=sha256:ce181dd1a7c6c012d0efa8ab603c34b5ee9d86e570c03415bbb1b8772eeb381c \\\n    --hash=sha256:d0356a21e58c3e99248930ff73cc05b1d302ff50f41a8a47371aefb04327378a \\\n    --hash=sha256:d0b96c30be9f9387b18b18b6133c75a7b1b0065da64e150fe1feb5ebf31ece1c \\\n    --hash=sha256:d2976b7079f09f48d59dc123293ed6282fca6ef96a270f4ea0364e4e54c8e855 \\\n    --hash=sha256:d97b59cbd8a6c8b23524a8ce237ff9504d987dc07156258aa68ae06d2dd5f34d \\\n    --hash=sha256:da81512b83032ed6cdf85ca62e020b4c23dda87f1b6c26b932131222ccfdbd27 \\\n    --hash=sha256:df2c55c958d3766bdb3e9d858b911288acec09a9aab15883f384fc7180df5bed \\\n    --hash=sha256:dfb2bb5e0f7198eaacfb6796fb0330afd28f36d985a770745fba554a5903595a \\\n    --hash=sha256:e4f22d67756518d71901edf73b38dc0eb4765cce22c8fe122cc81748d425262b \\\n    --hash=sha256:e648dca28178fc879c814cf285048dd22fd1f03e1104101106505ec0eea50a4d \\\n    --hash=sha256:e971d8680003d0af6020713e52f92109b46fedb463916e988814e04c8133578a \\\n    --hash=sha256:ee16906c8025fa464bea1e48128c048d02359fb40bebe5333103228528506530 \\\n    --hash=sha256:f293a1419266e3bf3557d1f8778f9e1ffe7e6b2c8df5c9dca191caf60831eb74 \\\n    --hash=sha256:f379f11e138dfd56c3f24a04164f871a08281194dd9ddf656a278d7d080c8ad0 \\\n    --hash=sha256:f44e7ea288d022d4bf93b9e79dafcb4a7aea45a3cbeae2116792904931cefccf \\\n    --hash=sha256:f5b6133c8d313bde8bd0d123c169d22525300ff164c2189f849de495e1344577 \\\n    --hash=sha256:f65741cc06630652e82aa68ddef4986a3ab9073dd46d59f94ce5f005fa72037c \\\n    --hash=sha256:f8c3b74f1cd577a5a9253eae7ed363f88cbb345a990ca3027e9038301d47c7f4 \\\n    --hash=sha256:f96a63aecec22d3f7fdea3c6c98df9e42973f5856bb6812c3d8d78c262fee808 \\\n    --hash=sha256:f98f6b7787bd2beb1f0dde03f23a0621a0c978edf673b7d8f5e7bc039cbe1b60 \\\n    --hash=sha256:fde26267416c8478c95432c81489b53f57b0b5d24cd5c8bfaebf5bbaac4dc90c \\\n    --hash=sha256:fe632fa4501154d58dfbe1764a0495734d55f84eaf1feda4549a1f1ca76659e9 \\\n    --hash=sha256:ff3f8757570e45da7a5bedaa140489846510014f7a9d5ee9301c61f3f1b8a686 \\\n    --hash=sha256:ffe6b809a97ac6dea524b3b837d5b28743d8c2f121141056d168ff0ba8f614ef\n    # via -r requirements.in\nrequests==2.32.4 \\\n    --hash=sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c \\\n    --hash=sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422\n    # via\n    #   -r requirements.in\n    #   requests-mock\nrequests-mock==1.12.1 \\\n    --hash=sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563 \\\n    --hash=sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401\n    # via -r requirements.in\nruamel-yaml==0.18.6 \\\n    --hash=sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636 \\\n    --hash=sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b\n    # via -r requirements.in\nruamel-yaml-clib==0.2.8 \\\n    --hash=sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d \\\n    --hash=sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001 \\\n    --hash=sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462 \\\n    --hash=sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9 \\\n    --hash=sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe \\\n    --hash=sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b \\\n    --hash=sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b \\\n    --hash=sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615 \\\n    --hash=sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62 \\\n    --hash=sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15 \\\n    --hash=sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b \\\n    --hash=sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1 \\\n    --hash=sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9 \\\n    --hash=sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675 \\\n    --hash=sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899 \\\n    --hash=sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7 \\\n    --hash=sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7 \\\n    --hash=sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312 \\\n    --hash=sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa \\\n    --hash=sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91 \\\n    --hash=sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b \\\n    --hash=sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6 \\\n    --hash=sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3 \\\n    --hash=sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334 \\\n    --hash=sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5 \\\n    --hash=sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3 \\\n    --hash=sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe \\\n    --hash=sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c \\\n    --hash=sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed \\\n    --hash=sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337 \\\n    --hash=sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880 \\\n    --hash=sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f \\\n    --hash=sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d \\\n    --hash=sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248 \\\n    --hash=sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d \\\n    --hash=sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf \\\n    --hash=sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512 \\\n    --hash=sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069 \\\n    --hash=sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb \\\n    --hash=sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942 \\\n    --hash=sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d \\\n    --hash=sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31 \\\n    --hash=sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92 \\\n    --hash=sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5 \\\n    --hash=sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28 \\\n    --hash=sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d \\\n    --hash=sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1 \\\n    --hash=sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2 \\\n    --hash=sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875 \\\n    --hash=sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412\n    # via\n    #   -r requirements.in\n    #   ruamel-yaml\nsix==1.16.0 \\\n    --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \\\n    --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254\n    # via\n    #   -r requirements.in\n    #   glog\nurllib3==2.6.3 \\\n    --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \\\n    --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4\n    # via\n    #   -r requirements.in\n    #   requests\n\n# The following packages are considered to be unsafe in a requirements file:\nsetuptools==80.9.0 \\\n    --hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \\\n    --hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c\n    # via grpcio-tools\n"
  },
  {
    "path": "plugin_server/py/tsunami_plugin.py",
    "content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Interface that all Python TsunamiPlugins will need to implement to run detection.\"\"\"\nimport abc\nimport detection_pb2\nimport network_service_pb2\nimport plugin_representation_pb2\nimport reconnaissance_pb2\nimport vulnerability_pb2\n\nTargetInfo = reconnaissance_pb2.TargetInfo\nNetworkService = network_service_pb2.NetworkService\nDetectionReportList = detection_pb2.DetectionReportList\nPluginDefinition = plugin_representation_pb2.PluginDefinition\n\n\nclass TsunamiPlugin(metaclass=abc.ABCMeta):\n\n  @abc.abstractmethod\n  def GetPluginDefinition(self) -> PluginDefinition:\n    pass\n\n  @classmethod\n  def __subclasshook__(cls, subclass: abc.ABCMeta) -> bool:\n    return hasattr(subclass, 'GetPluginDefinition') and callable(\n        subclass.GetPluginDefinition\n    )\n\n\nclass VulnDetector(TsunamiPlugin):\n  \"\"\"A TsunamiPlugin that detects potential vulnerabilities on the target.\n\n  Usually a vulnerability detector takes the information about an exposed\n  network service, detects whether the service is vulnerable to a\n  specific vulnerability, and reports the detection results.\n  \"\"\"\n\n  @classmethod\n  def __init_subclass__(cls, **kwargs):\n    super().__init_subclass__(**kwargs)\n\n  @abc.abstractmethod\n  def GetAdvisories(self) -> list[vulnerability_pb2.Vulnerability]:\n    \"\"\"Returns the list of vulnerabilities detected by this plugin.\"\"\"\n\n  @abc.abstractmethod\n  def Detect(\n      self, target: TargetInfo, matched_services: list[NetworkService]\n  ) -> DetectionReportList:\n    \"\"\"Run detection logic for the target.\n\n    Args:\n      target: Information about the scanning target itself.\n      matched_services: A list of network services whose vulnerabilities could\n        be detected by this plugin.\n\n    Returns:\n      A DetectionReportList for all the vulnerabilities of the scanning target.\n    \"\"\"\n"
  },
  {
    "path": "proto/build.gradle",
    "content": "plugins {\n    id 'com.google.protobuf'\n}\n\ndescription = 'Tsunami: Protobuf Data'\n\nsourceSets {\n    main {\n        proto {\n            srcDir \"${projectDir}\"\n            exclude \"build/**\"\n        }\n    }\n}\n\nprotobuf {\n    generatedFilesBaseDir = \"${projectDir}/build/generated\"\n    protoc {\n        artifact = \"com.google.protobuf:protoc:3.25.2\"\n    }\n    plugins {\n        grpc {\n            artifact = \"io.grpc:protoc-gen-grpc-java:1.60.0\"\n        }\n    }\n    generateProtoTasks {\n        all()*.plugins { grpc {} }\n        all().configureEach {\n            task -> {\n                dependsOn(\"processResources\")\n                dependsOn(\"extractTestProto\")\n                dependsOn(\"sourcesJar\")\n            }\n        }\n    }\n}\n\nidea {\n    module {\n        sourceDirs += file(\"${projectDir}/build/generated/main/java\");\n        sourceDirs += file(\"${projectDir}/build/generated/main/grpc\");\n    }\n}\n\ndependencies {\n    implementation \"com.google.protobuf:protobuf-java:3.25.5\"\n    implementation \"io.grpc:grpc-protobuf:1.60.0\"\n    implementation \"io.grpc:grpc-stub:1.60.0\"\n    implementation \"javax.annotation:javax.annotation-api:1.3.2\"\n}\n\ngradle.projectsEvaluated {\n    tasks.withType(ProcessResources) {\n        dependsOn(\"extractProto\")\n    }\n}\n"
  },
  {
    "path": "proto/detection.proto",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Data models for describing a vulnerability detection report.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\nimport \"google/protobuf/timestamp.proto\";\nimport \"network_service.proto\";\nimport \"reconnaissance.proto\";\nimport \"vulnerability.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"DetectionProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/detection_go_proto\";\n\n// Status of the vulnerability detection result.\nenum DetectionStatus {\n  // Unspecified status.\n  DETECTION_STATUS_UNSPECIFIED = 0;\n  // Target is not vulnerable.\n  SAFE = 1;\n  // Target appears to be vulnerable (e.g. because running version is\n  // vulnerable), but couldn't be verified.\n  VULNERABILITY_PRESENT = 2;\n  // Target is vulnerable and the detector successfully verified the\n  // vulnerability.\n  VULNERABILITY_VERIFIED = 3;\n}\n\n// Full report about a detected vulnerability.\nmessage DetectionReport {\n  // Information about the scanned target.\n  TargetInfo target_info = 1;\n\n  // Information about the scanned network service.\n  NetworkService network_service = 2;\n\n  // Time when the vulnerability was detected.\n  google.protobuf.Timestamp detection_timestamp = 3;\n\n  // Status of the detection result.\n  DetectionStatus detection_status = 4;\n\n  // Full details about the detected vulnerability.\n  Vulnerability vulnerability = 5;\n}\n\nmessage DetectionReportList {\n  repeated DetectionReport detection_reports = 1;\n}\n"
  },
  {
    "path": "proto/go/detection_go_proto/detection.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing a vulnerability detection report.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: detection.proto\n\npackage detection_go_proto\n\nimport (\n\tnetwork_service_go_proto \"github.com/google/tsunami-security-scanner/proto/go/network_service_go_proto\"\n\treconnaissance_go_proto \"github.com/google/tsunami-security-scanner/proto/go/reconnaissance_go_proto\"\n\tvulnerability_go_proto \"github.com/google/tsunami-security-scanner/proto/go/vulnerability_go_proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\ttimestamppb \"google.golang.org/protobuf/types/known/timestamppb\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Status of the vulnerability detection result.\ntype DetectionStatus int32\n\nconst (\n\t// Unspecified status.\n\tDetectionStatus_DETECTION_STATUS_UNSPECIFIED DetectionStatus = 0\n\t// Target is not vulnerable.\n\tDetectionStatus_SAFE DetectionStatus = 1\n\t// Target appears to be vulnerable (e.g. because running version is\n\t// vulnerable), but couldn't be verified.\n\tDetectionStatus_VULNERABILITY_PRESENT DetectionStatus = 2\n\t// Target is vulnerable and the detector successfully verified the\n\t// vulnerability.\n\tDetectionStatus_VULNERABILITY_VERIFIED DetectionStatus = 3\n)\n\n// Enum value maps for DetectionStatus.\nvar (\n\tDetectionStatus_name = map[int32]string{\n\t\t0: \"DETECTION_STATUS_UNSPECIFIED\",\n\t\t1: \"SAFE\",\n\t\t2: \"VULNERABILITY_PRESENT\",\n\t\t3: \"VULNERABILITY_VERIFIED\",\n\t}\n\tDetectionStatus_value = map[string]int32{\n\t\t\"DETECTION_STATUS_UNSPECIFIED\": 0,\n\t\t\"SAFE\":                         1,\n\t\t\"VULNERABILITY_PRESENT\":        2,\n\t\t\"VULNERABILITY_VERIFIED\":       3,\n\t}\n)\n\nfunc (x DetectionStatus) Enum() *DetectionStatus {\n\tp := new(DetectionStatus)\n\t*p = x\n\treturn p\n}\n\nfunc (x DetectionStatus) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (DetectionStatus) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_detection_proto_enumTypes[0].Descriptor()\n}\n\nfunc (DetectionStatus) Type() protoreflect.EnumType {\n\treturn &file_detection_proto_enumTypes[0]\n}\n\nfunc (x DetectionStatus) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Full report about a detected vulnerability.\ntype DetectionReport struct {\n\tstate                         protoimpl.MessageState                   `protogen:\"opaque.v1\"`\n\txxx_hidden_TargetInfo         *reconnaissance_go_proto.TargetInfo      `protobuf:\"bytes,1,opt,name=target_info,json=targetInfo,proto3\"`\n\txxx_hidden_NetworkService     *network_service_go_proto.NetworkService `protobuf:\"bytes,2,opt,name=network_service,json=networkService,proto3\"`\n\txxx_hidden_DetectionTimestamp *timestamppb.Timestamp                   `protobuf:\"bytes,3,opt,name=detection_timestamp,json=detectionTimestamp,proto3\"`\n\txxx_hidden_DetectionStatus    DetectionStatus                          `protobuf:\"varint,4,opt,name=detection_status,json=detectionStatus,proto3,enum=tsunami.proto.DetectionStatus\"`\n\txxx_hidden_Vulnerability      *vulnerability_go_proto.Vulnerability    `protobuf:\"bytes,5,opt,name=vulnerability,proto3\"`\n\tunknownFields                 protoimpl.UnknownFields\n\tsizeCache                     protoimpl.SizeCache\n}\n\nfunc (x *DetectionReport) Reset() {\n\t*x = DetectionReport{}\n\tmi := &file_detection_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DetectionReport) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DetectionReport) ProtoMessage() {}\n\nfunc (x *DetectionReport) ProtoReflect() protoreflect.Message {\n\tmi := &file_detection_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *DetectionReport) GetTargetInfo() *reconnaissance_go_proto.TargetInfo {\n\tif x != nil {\n\t\treturn x.xxx_hidden_TargetInfo\n\t}\n\treturn nil\n}\n\nfunc (x *DetectionReport) GetNetworkService() *network_service_go_proto.NetworkService {\n\tif x != nil {\n\t\treturn x.xxx_hidden_NetworkService\n\t}\n\treturn nil\n}\n\nfunc (x *DetectionReport) GetDetectionTimestamp() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.xxx_hidden_DetectionTimestamp\n\t}\n\treturn nil\n}\n\nfunc (x *DetectionReport) GetDetectionStatus() DetectionStatus {\n\tif x != nil {\n\t\treturn x.xxx_hidden_DetectionStatus\n\t}\n\treturn DetectionStatus_DETECTION_STATUS_UNSPECIFIED\n}\n\nfunc (x *DetectionReport) GetVulnerability() *vulnerability_go_proto.Vulnerability {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Vulnerability\n\t}\n\treturn nil\n}\n\nfunc (x *DetectionReport) SetTargetInfo(v *reconnaissance_go_proto.TargetInfo) {\n\tx.xxx_hidden_TargetInfo = v\n}\n\nfunc (x *DetectionReport) SetNetworkService(v *network_service_go_proto.NetworkService) {\n\tx.xxx_hidden_NetworkService = v\n}\n\nfunc (x *DetectionReport) SetDetectionTimestamp(v *timestamppb.Timestamp) {\n\tx.xxx_hidden_DetectionTimestamp = v\n}\n\nfunc (x *DetectionReport) SetDetectionStatus(v DetectionStatus) {\n\tx.xxx_hidden_DetectionStatus = v\n}\n\nfunc (x *DetectionReport) SetVulnerability(v *vulnerability_go_proto.Vulnerability) {\n\tx.xxx_hidden_Vulnerability = v\n}\n\nfunc (x *DetectionReport) HasTargetInfo() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_TargetInfo != nil\n}\n\nfunc (x *DetectionReport) HasNetworkService() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_NetworkService != nil\n}\n\nfunc (x *DetectionReport) HasDetectionTimestamp() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_DetectionTimestamp != nil\n}\n\nfunc (x *DetectionReport) HasVulnerability() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Vulnerability != nil\n}\n\nfunc (x *DetectionReport) ClearTargetInfo() {\n\tx.xxx_hidden_TargetInfo = nil\n}\n\nfunc (x *DetectionReport) ClearNetworkService() {\n\tx.xxx_hidden_NetworkService = nil\n}\n\nfunc (x *DetectionReport) ClearDetectionTimestamp() {\n\tx.xxx_hidden_DetectionTimestamp = nil\n}\n\nfunc (x *DetectionReport) ClearVulnerability() {\n\tx.xxx_hidden_Vulnerability = nil\n}\n\ntype DetectionReport_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Information about the scanned target.\n\tTargetInfo *reconnaissance_go_proto.TargetInfo\n\t// Information about the scanned network service.\n\tNetworkService *network_service_go_proto.NetworkService\n\t// Time when the vulnerability was detected.\n\tDetectionTimestamp *timestamppb.Timestamp\n\t// Status of the detection result.\n\tDetectionStatus DetectionStatus\n\t// Full details about the detected vulnerability.\n\tVulnerability *vulnerability_go_proto.Vulnerability\n}\n\nfunc (b0 DetectionReport_builder) Build() *DetectionReport {\n\tm0 := &DetectionReport{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_TargetInfo = b.TargetInfo\n\tx.xxx_hidden_NetworkService = b.NetworkService\n\tx.xxx_hidden_DetectionTimestamp = b.DetectionTimestamp\n\tx.xxx_hidden_DetectionStatus = b.DetectionStatus\n\tx.xxx_hidden_Vulnerability = b.Vulnerability\n\treturn m0\n}\n\ntype DetectionReportList struct {\n\tstate                       protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_DetectionReports *[]*DetectionReport    `protobuf:\"bytes,1,rep,name=detection_reports,json=detectionReports,proto3\"`\n\tunknownFields               protoimpl.UnknownFields\n\tsizeCache                   protoimpl.SizeCache\n}\n\nfunc (x *DetectionReportList) Reset() {\n\t*x = DetectionReportList{}\n\tmi := &file_detection_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DetectionReportList) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DetectionReportList) ProtoMessage() {}\n\nfunc (x *DetectionReportList) ProtoReflect() protoreflect.Message {\n\tmi := &file_detection_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *DetectionReportList) GetDetectionReports() []*DetectionReport {\n\tif x != nil {\n\t\tif x.xxx_hidden_DetectionReports != nil {\n\t\t\treturn *x.xxx_hidden_DetectionReports\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *DetectionReportList) SetDetectionReports(v []*DetectionReport) {\n\tx.xxx_hidden_DetectionReports = &v\n}\n\ntype DetectionReportList_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tDetectionReports []*DetectionReport\n}\n\nfunc (b0 DetectionReportList_builder) Build() *DetectionReportList {\n\tm0 := &DetectionReportList{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_DetectionReports = &b.DetectionReports\n\treturn m0\n}\n\nvar File_detection_proto protoreflect.FileDescriptor\n\nconst file_detection_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x0fdetection.proto\\x12\\rtsunami.proto\\x1a\\x1fgoogle/protobuf/timestamp.proto\\x1a\\x15network_service.proto\\x1a\\x14reconnaissance.proto\\x1a\\x13vulnerability.proto\\\"\\xf1\\x02\\n\" +\n\t\"\\x0fDetectionReport\\x12:\\n\" +\n\t\"\\vtarget_info\\x18\\x01 \\x01(\\v2\\x19.tsunami.proto.TargetInfoR\\n\" +\n\t\"targetInfo\\x12F\\n\" +\n\t\"\\x0fnetwork_service\\x18\\x02 \\x01(\\v2\\x1d.tsunami.proto.NetworkServiceR\\x0enetworkService\\x12K\\n\" +\n\t\"\\x13detection_timestamp\\x18\\x03 \\x01(\\v2\\x1a.google.protobuf.TimestampR\\x12detectionTimestamp\\x12I\\n\" +\n\t\"\\x10detection_status\\x18\\x04 \\x01(\\x0e2\\x1e.tsunami.proto.DetectionStatusR\\x0fdetectionStatus\\x12B\\n\" +\n\t\"\\rvulnerability\\x18\\x05 \\x01(\\v2\\x1c.tsunami.proto.VulnerabilityR\\rvulnerability\\\"b\\n\" +\n\t\"\\x13DetectionReportList\\x12K\\n\" +\n\t\"\\x11detection_reports\\x18\\x01 \\x03(\\v2\\x1e.tsunami.proto.DetectionReportR\\x10detectionReports*t\\n\" +\n\t\"\\x0fDetectionStatus\\x12 \\n\" +\n\t\"\\x1cDETECTION_STATUS_UNSPECIFIED\\x10\\x00\\x12\\b\\n\" +\n\t\"\\x04SAFE\\x10\\x01\\x12\\x19\\n\" +\n\t\"\\x15VULNERABILITY_PRESENT\\x10\\x02\\x12\\x1a\\n\" +\n\t\"\\x16VULNERABILITY_VERIFIED\\x10\\x03Bu\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x0fDetectionProtosP\\x01ZFgithub.com/google/tsunami-security-scanner/proto/go/detection_go_protob\\x06proto3\"\n\nvar file_detection_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_detection_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_detection_proto_goTypes = []any{\n\t(DetectionStatus)(0),                            // 0: tsunami.proto.DetectionStatus\n\t(*DetectionReport)(nil),                         // 1: tsunami.proto.DetectionReport\n\t(*DetectionReportList)(nil),                     // 2: tsunami.proto.DetectionReportList\n\t(*reconnaissance_go_proto.TargetInfo)(nil),      // 3: tsunami.proto.TargetInfo\n\t(*network_service_go_proto.NetworkService)(nil), // 4: tsunami.proto.NetworkService\n\t(*timestamppb.Timestamp)(nil),                   // 5: google.protobuf.Timestamp\n\t(*vulnerability_go_proto.Vulnerability)(nil),    // 6: tsunami.proto.Vulnerability\n}\nvar file_detection_proto_depIdxs = []int32{\n\t3, // 0: tsunami.proto.DetectionReport.target_info:type_name -> tsunami.proto.TargetInfo\n\t4, // 1: tsunami.proto.DetectionReport.network_service:type_name -> tsunami.proto.NetworkService\n\t5, // 2: tsunami.proto.DetectionReport.detection_timestamp:type_name -> google.protobuf.Timestamp\n\t0, // 3: tsunami.proto.DetectionReport.detection_status:type_name -> tsunami.proto.DetectionStatus\n\t6, // 4: tsunami.proto.DetectionReport.vulnerability:type_name -> tsunami.proto.Vulnerability\n\t1, // 5: tsunami.proto.DetectionReportList.detection_reports:type_name -> tsunami.proto.DetectionReport\n\t6, // [6:6] is the sub-list for method output_type\n\t6, // [6:6] is the sub-list for method input_type\n\t6, // [6:6] is the sub-list for extension type_name\n\t6, // [6:6] is the sub-list for extension extendee\n\t0, // [0:6] is the sub-list for field type_name\n}\n\nfunc init() { file_detection_proto_init() }\nfunc file_detection_proto_init() {\n\tif File_detection_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_detection_proto_rawDesc), len(file_detection_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_detection_proto_goTypes,\n\t\tDependencyIndexes: file_detection_proto_depIdxs,\n\t\tEnumInfos:         file_detection_proto_enumTypes,\n\t\tMessageInfos:      file_detection_proto_msgTypes,\n\t}.Build()\n\tFile_detection_proto = out.File\n\tfile_detection_proto_goTypes = nil\n\tfile_detection_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/network_go_proto/network.pb.go",
    "content": "//\n// Copyright 2019 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing network related information.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: network.proto\n\npackage network_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// The address family of an IP address.\ntype AddressFamily int32\n\nconst (\n\tAddressFamily_ADDRESS_FAMILY_UNSPECIFIED AddressFamily = 0\n\tAddressFamily_IPV4                       AddressFamily = 4\n\tAddressFamily_IPV6                       AddressFamily = 6\n)\n\n// Enum value maps for AddressFamily.\nvar (\n\tAddressFamily_name = map[int32]string{\n\t\t0: \"ADDRESS_FAMILY_UNSPECIFIED\",\n\t\t4: \"IPV4\",\n\t\t6: \"IPV6\",\n\t}\n\tAddressFamily_value = map[string]int32{\n\t\t\"ADDRESS_FAMILY_UNSPECIFIED\": 0,\n\t\t\"IPV4\":                       4,\n\t\t\"IPV6\":                       6,\n\t}\n)\n\nfunc (x AddressFamily) Enum() *AddressFamily {\n\tp := new(AddressFamily)\n\t*p = x\n\treturn p\n}\n\nfunc (x AddressFamily) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (AddressFamily) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_network_proto_enumTypes[0].Descriptor()\n}\n\nfunc (AddressFamily) Type() protoreflect.EnumType {\n\treturn &file_network_proto_enumTypes[0]\n}\n\nfunc (x AddressFamily) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// The transport layer protocols.\ntype TransportProtocol int32\n\nconst (\n\tTransportProtocol_TRANSPORT_PROTOCOL_UNSPECIFIED TransportProtocol = 0\n\tTransportProtocol_TCP                            TransportProtocol = 1\n\tTransportProtocol_UDP                            TransportProtocol = 2\n\tTransportProtocol_SCTP                           TransportProtocol = 3\n)\n\n// Enum value maps for TransportProtocol.\nvar (\n\tTransportProtocol_name = map[int32]string{\n\t\t0: \"TRANSPORT_PROTOCOL_UNSPECIFIED\",\n\t\t1: \"TCP\",\n\t\t2: \"UDP\",\n\t\t3: \"SCTP\",\n\t}\n\tTransportProtocol_value = map[string]int32{\n\t\t\"TRANSPORT_PROTOCOL_UNSPECIFIED\": 0,\n\t\t\"TCP\":                            1,\n\t\t\"UDP\":                            2,\n\t\t\"SCTP\":                           3,\n\t}\n)\n\nfunc (x TransportProtocol) Enum() *TransportProtocol {\n\tp := new(TransportProtocol)\n\t*p = x\n\treturn p\n}\n\nfunc (x TransportProtocol) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (TransportProtocol) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_network_proto_enumTypes[1].Descriptor()\n}\n\nfunc (TransportProtocol) Type() protoreflect.EnumType {\n\treturn &file_network_proto_enumTypes[1]\n}\n\nfunc (x TransportProtocol) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\ntype NetworkEndpoint_Type int32\n\nconst (\n\tNetworkEndpoint_TYPE_UNSPECIFIED NetworkEndpoint_Type = 0\n\t// The network endpoint is represented by an IP address.\n\tNetworkEndpoint_IP NetworkEndpoint_Type = 1\n\t// The network endpoint is represented by IP address and port pair.\n\tNetworkEndpoint_IP_PORT NetworkEndpoint_Type = 2\n\t// The network endpoint is represented by a hostname.\n\tNetworkEndpoint_HOSTNAME NetworkEndpoint_Type = 3\n\t// The network endpoint is represented by a hostname and port pair.\n\tNetworkEndpoint_HOSTNAME_PORT NetworkEndpoint_Type = 4\n\t// The network endpoint is represented by an IP address and hostname.\n\tNetworkEndpoint_IP_HOSTNAME NetworkEndpoint_Type = 5\n\t// The network endpoint is represented by an IP address, hostname and port.\n\tNetworkEndpoint_IP_HOSTNAME_PORT NetworkEndpoint_Type = 6\n)\n\n// Enum value maps for NetworkEndpoint_Type.\nvar (\n\tNetworkEndpoint_Type_name = map[int32]string{\n\t\t0: \"TYPE_UNSPECIFIED\",\n\t\t1: \"IP\",\n\t\t2: \"IP_PORT\",\n\t\t3: \"HOSTNAME\",\n\t\t4: \"HOSTNAME_PORT\",\n\t\t5: \"IP_HOSTNAME\",\n\t\t6: \"IP_HOSTNAME_PORT\",\n\t}\n\tNetworkEndpoint_Type_value = map[string]int32{\n\t\t\"TYPE_UNSPECIFIED\": 0,\n\t\t\"IP\":               1,\n\t\t\"IP_PORT\":          2,\n\t\t\"HOSTNAME\":         3,\n\t\t\"HOSTNAME_PORT\":    4,\n\t\t\"IP_HOSTNAME\":      5,\n\t\t\"IP_HOSTNAME_PORT\": 6,\n\t}\n)\n\nfunc (x NetworkEndpoint_Type) Enum() *NetworkEndpoint_Type {\n\tp := new(NetworkEndpoint_Type)\n\t*p = x\n\treturn p\n}\n\nfunc (x NetworkEndpoint_Type) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (NetworkEndpoint_Type) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_network_proto_enumTypes[2].Descriptor()\n}\n\nfunc (NetworkEndpoint_Type) Type() protoreflect.EnumType {\n\treturn &file_network_proto_enumTypes[2]\n}\n\nfunc (x NetworkEndpoint_Type) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// The IP address of a networking device.\ntype IpAddress struct {\n\tstate                    protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_AddressFamily AddressFamily          `protobuf:\"varint,1,opt,name=address_family,json=addressFamily,proto3,enum=tsunami.proto.AddressFamily\"`\n\txxx_hidden_Address       string                 `protobuf:\"bytes,2,opt,name=address,proto3\"`\n\tunknownFields            protoimpl.UnknownFields\n\tsizeCache                protoimpl.SizeCache\n}\n\nfunc (x *IpAddress) Reset() {\n\t*x = IpAddress{}\n\tmi := &file_network_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *IpAddress) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IpAddress) ProtoMessage() {}\n\nfunc (x *IpAddress) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *IpAddress) GetAddressFamily() AddressFamily {\n\tif x != nil {\n\t\treturn x.xxx_hidden_AddressFamily\n\t}\n\treturn AddressFamily_ADDRESS_FAMILY_UNSPECIFIED\n}\n\nfunc (x *IpAddress) GetAddress() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Address\n\t}\n\treturn \"\"\n}\n\nfunc (x *IpAddress) SetAddressFamily(v AddressFamily) {\n\tx.xxx_hidden_AddressFamily = v\n}\n\nfunc (x *IpAddress) SetAddress(v string) {\n\tx.xxx_hidden_Address = v\n}\n\ntype IpAddress_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The family of the IP address.\n\tAddressFamily AddressFamily\n\t// A human-readable representation of the IP address, e.g. 127.0.0.1 for IPV4\n\t// and 2001:db8:0:1234:0:567:8:1 for IPV6.\n\tAddress string\n}\n\nfunc (b0 IpAddress_builder) Build() *IpAddress {\n\tm0 := &IpAddress{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_AddressFamily = b.AddressFamily\n\tx.xxx_hidden_Address = b.Address\n\treturn m0\n}\n\n// The port that a network service listens to.\ntype Port struct {\n\tstate                 protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_PortNumber uint32                 `protobuf:\"varint,1,opt,name=port_number,json=portNumber,proto3\"`\n\tunknownFields         protoimpl.UnknownFields\n\tsizeCache             protoimpl.SizeCache\n}\n\nfunc (x *Port) Reset() {\n\t*x = Port{}\n\tmi := &file_network_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Port) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Port) ProtoMessage() {}\n\nfunc (x *Port) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *Port) GetPortNumber() uint32 {\n\tif x != nil {\n\t\treturn x.xxx_hidden_PortNumber\n\t}\n\treturn 0\n}\n\nfunc (x *Port) SetPortNumber(v uint32) {\n\tx.xxx_hidden_PortNumber = v\n}\n\ntype Port_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tPortNumber uint32\n}\n\nfunc (b0 Port_builder) Build() *Port {\n\tm0 := &Port{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_PortNumber = b.PortNumber\n\treturn m0\n}\n\n// The hostname of a networking device.\ntype Hostname struct {\n\tstate           protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Name string                 `protobuf:\"bytes,1,opt,name=name,proto3\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *Hostname) Reset() {\n\t*x = Hostname{}\n\tmi := &file_network_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Hostname) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Hostname) ProtoMessage() {}\n\nfunc (x *Hostname) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *Hostname) GetName() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Hostname) SetName(v string) {\n\tx.xxx_hidden_Name = v\n}\n\ntype Hostname_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tName string\n}\n\nfunc (b0 Hostname_builder) Build() *Hostname {\n\tm0 := &Hostname{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Name = b.Name\n\treturn m0\n}\n\n// A classification of an endpoint for a network device.\ntype NetworkEndpoint struct {\n\tstate                protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Type      NetworkEndpoint_Type   `protobuf:\"varint,1,opt,name=type,proto3,enum=tsunami.proto.NetworkEndpoint_Type\"`\n\txxx_hidden_IpAddress *IpAddress             `protobuf:\"bytes,2,opt,name=ip_address,json=ipAddress,proto3\"`\n\txxx_hidden_Port      *Port                  `protobuf:\"bytes,3,opt,name=port,proto3\"`\n\txxx_hidden_Hostname  *Hostname              `protobuf:\"bytes,4,opt,name=hostname,proto3\"`\n\tunknownFields        protoimpl.UnknownFields\n\tsizeCache            protoimpl.SizeCache\n}\n\nfunc (x *NetworkEndpoint) Reset() {\n\t*x = NetworkEndpoint{}\n\tmi := &file_network_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NetworkEndpoint) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NetworkEndpoint) ProtoMessage() {}\n\nfunc (x *NetworkEndpoint) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *NetworkEndpoint) GetType() NetworkEndpoint_Type {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Type\n\t}\n\treturn NetworkEndpoint_TYPE_UNSPECIFIED\n}\n\nfunc (x *NetworkEndpoint) GetIpAddress() *IpAddress {\n\tif x != nil {\n\t\treturn x.xxx_hidden_IpAddress\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkEndpoint) GetPort() *Port {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Port\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkEndpoint) GetHostname() *Hostname {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Hostname\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkEndpoint) SetType(v NetworkEndpoint_Type) {\n\tx.xxx_hidden_Type = v\n}\n\nfunc (x *NetworkEndpoint) SetIpAddress(v *IpAddress) {\n\tx.xxx_hidden_IpAddress = v\n}\n\nfunc (x *NetworkEndpoint) SetPort(v *Port) {\n\tx.xxx_hidden_Port = v\n}\n\nfunc (x *NetworkEndpoint) SetHostname(v *Hostname) {\n\tx.xxx_hidden_Hostname = v\n}\n\nfunc (x *NetworkEndpoint) HasIpAddress() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_IpAddress != nil\n}\n\nfunc (x *NetworkEndpoint) HasPort() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Port != nil\n}\n\nfunc (x *NetworkEndpoint) HasHostname() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Hostname != nil\n}\n\nfunc (x *NetworkEndpoint) ClearIpAddress() {\n\tx.xxx_hidden_IpAddress = nil\n}\n\nfunc (x *NetworkEndpoint) ClearPort() {\n\tx.xxx_hidden_Port = nil\n}\n\nfunc (x *NetworkEndpoint) ClearHostname() {\n\tx.xxx_hidden_Hostname = nil\n}\n\ntype NetworkEndpoint_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Type of the network endpoint.\n\tType NetworkEndpoint_Type\n\t// Optional IP address of a network endpoint. Must be specified when Type is\n\t// IP or IP_PORT.\n\tIpAddress *IpAddress\n\t// Optional port of a network endpoint. Must be specified when Type is IP_PORT\n\t// or HOSTNAME_PORT.\n\tPort *Port\n\t// Optional hostname of a network endpoint. Must be specified when Type is\n\t// HOSTNAME or HOSTNAME_PORT.\n\tHostname *Hostname\n}\n\nfunc (b0 NetworkEndpoint_builder) Build() *NetworkEndpoint {\n\tm0 := &NetworkEndpoint{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Type = b.Type\n\tx.xxx_hidden_IpAddress = b.IpAddress\n\tx.xxx_hidden_Port = b.Port\n\tx.xxx_hidden_Hostname = b.Hostname\n\treturn m0\n}\n\nvar File_network_proto protoreflect.FileDescriptor\n\nconst file_network_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\rnetwork.proto\\x12\\rtsunami.proto\\\"j\\n\" +\n\t\"\\tIpAddress\\x12C\\n\" +\n\t\"\\x0eaddress_family\\x18\\x01 \\x01(\\x0e2\\x1c.tsunami.proto.AddressFamilyR\\raddressFamily\\x12\\x18\\n\" +\n\t\"\\aaddress\\x18\\x02 \\x01(\\tR\\aaddress\\\"'\\n\" +\n\t\"\\x04Port\\x12\\x1f\\n\" +\n\t\"\\vport_number\\x18\\x01 \\x01(\\rR\\n\" +\n\t\"portNumber\\\"\\x1e\\n\" +\n\t\"\\bHostname\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\\"\\xdc\\x02\\n\" +\n\t\"\\x0fNetworkEndpoint\\x127\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2#.tsunami.proto.NetworkEndpoint.TypeR\\x04type\\x127\\n\" +\n\t\"\\n\" +\n\t\"ip_address\\x18\\x02 \\x01(\\v2\\x18.tsunami.proto.IpAddressR\\tipAddress\\x12'\\n\" +\n\t\"\\x04port\\x18\\x03 \\x01(\\v2\\x13.tsunami.proto.PortR\\x04port\\x123\\n\" +\n\t\"\\bhostname\\x18\\x04 \\x01(\\v2\\x17.tsunami.proto.HostnameR\\bhostname\\\"y\\n\" +\n\t\"\\x04Type\\x12\\x14\\n\" +\n\t\"\\x10TYPE_UNSPECIFIED\\x10\\x00\\x12\\x06\\n\" +\n\t\"\\x02IP\\x10\\x01\\x12\\v\\n\" +\n\t\"\\aIP_PORT\\x10\\x02\\x12\\f\\n\" +\n\t\"\\bHOSTNAME\\x10\\x03\\x12\\x11\\n\" +\n\t\"\\rHOSTNAME_PORT\\x10\\x04\\x12\\x0f\\n\" +\n\t\"\\vIP_HOSTNAME\\x10\\x05\\x12\\x14\\n\" +\n\t\"\\x10IP_HOSTNAME_PORT\\x10\\x06*C\\n\" +\n\t\"\\rAddressFamily\\x12\\x1e\\n\" +\n\t\"\\x1aADDRESS_FAMILY_UNSPECIFIED\\x10\\x00\\x12\\b\\n\" +\n\t\"\\x04IPV4\\x10\\x04\\x12\\b\\n\" +\n\t\"\\x04IPV6\\x10\\x06*S\\n\" +\n\t\"\\x11TransportProtocol\\x12\\\"\\n\" +\n\t\"\\x1eTRANSPORT_PROTOCOL_UNSPECIFIED\\x10\\x00\\x12\\a\\n\" +\n\t\"\\x03TCP\\x10\\x01\\x12\\a\\n\" +\n\t\"\\x03UDP\\x10\\x02\\x12\\b\\n\" +\n\t\"\\x04SCTP\\x10\\x03Bq\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\rNetworkProtosP\\x01ZDgithub.com/google/tsunami-security-scanner/proto/go/network_go_protob\\x06proto3\"\n\nvar file_network_proto_enumTypes = make([]protoimpl.EnumInfo, 3)\nvar file_network_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_network_proto_goTypes = []any{\n\t(AddressFamily)(0),        // 0: tsunami.proto.AddressFamily\n\t(TransportProtocol)(0),    // 1: tsunami.proto.TransportProtocol\n\t(NetworkEndpoint_Type)(0), // 2: tsunami.proto.NetworkEndpoint.Type\n\t(*IpAddress)(nil),         // 3: tsunami.proto.IpAddress\n\t(*Port)(nil),              // 4: tsunami.proto.Port\n\t(*Hostname)(nil),          // 5: tsunami.proto.Hostname\n\t(*NetworkEndpoint)(nil),   // 6: tsunami.proto.NetworkEndpoint\n}\nvar file_network_proto_depIdxs = []int32{\n\t0, // 0: tsunami.proto.IpAddress.address_family:type_name -> tsunami.proto.AddressFamily\n\t2, // 1: tsunami.proto.NetworkEndpoint.type:type_name -> tsunami.proto.NetworkEndpoint.Type\n\t3, // 2: tsunami.proto.NetworkEndpoint.ip_address:type_name -> tsunami.proto.IpAddress\n\t4, // 3: tsunami.proto.NetworkEndpoint.port:type_name -> tsunami.proto.Port\n\t5, // 4: tsunami.proto.NetworkEndpoint.hostname:type_name -> tsunami.proto.Hostname\n\t5, // [5:5] is the sub-list for method output_type\n\t5, // [5:5] is the sub-list for method input_type\n\t5, // [5:5] is the sub-list for extension type_name\n\t5, // [5:5] is the sub-list for extension extendee\n\t0, // [0:5] is the sub-list for field type_name\n}\n\nfunc init() { file_network_proto_init() }\nfunc file_network_proto_init() {\n\tif File_network_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_network_proto_rawDesc), len(file_network_proto_rawDesc)),\n\t\t\tNumEnums:      3,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_network_proto_goTypes,\n\t\tDependencyIndexes: file_network_proto_depIdxs,\n\t\tEnumInfos:         file_network_proto_enumTypes,\n\t\tMessageInfos:      file_network_proto_msgTypes,\n\t}.Build()\n\tFile_network_proto = out.File\n\tfile_network_proto_goTypes = nil\n\tfile_network_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/network_service_go_proto/network_service.pb.go",
    "content": "//\n// Copyright 2019 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing a network service.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: network_service.proto\n\npackage network_service_go_proto\n\nimport (\n\tnetwork_go_proto \"github.com/google/tsunami-security-scanner/proto/go/network_go_proto\"\n\tsoftware_go_proto \"github.com/google/tsunami-security-scanner/proto/go/software_go_proto\"\n\tweb_crawl_go_proto \"github.com/google/tsunami-security-scanner/proto/go/web_crawl_go_proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// General information about a network service running on a target.\ntype NetworkService struct {\n\tstate                           protoimpl.MessageState             `protogen:\"opaque.v1\"`\n\txxx_hidden_NetworkEndpoint      *network_go_proto.NetworkEndpoint  `protobuf:\"bytes,1,opt,name=network_endpoint,json=networkEndpoint,proto3\"`\n\txxx_hidden_TransportProtocol    network_go_proto.TransportProtocol `protobuf:\"varint,2,opt,name=transport_protocol,json=transportProtocol,proto3,enum=tsunami.proto.TransportProtocol\"`\n\txxx_hidden_ServiceName          string                             `protobuf:\"bytes,3,opt,name=service_name,json=serviceName,proto3\"`\n\txxx_hidden_Software             *software_go_proto.Software        `protobuf:\"bytes,4,opt,name=software,proto3\"`\n\txxx_hidden_VersionSet           *software_go_proto.VersionSet      `protobuf:\"bytes,5,opt,name=version_set,json=versionSet,proto3\"`\n\txxx_hidden_Banner               []string                           `protobuf:\"bytes,6,rep,name=banner,proto3\"`\n\txxx_hidden_ServiceContext       *ServiceContext                    `protobuf:\"bytes,7,opt,name=service_context,json=serviceContext,proto3\"`\n\txxx_hidden_Cpes                 []string                           `protobuf:\"bytes,8,rep,name=cpes,proto3\"`\n\txxx_hidden_SupportedSslVersions []string                           `protobuf:\"bytes,9,rep,name=supported_ssl_versions,json=supportedSslVersions,proto3\"`\n\txxx_hidden_SupportedHttpMethods []string                           `protobuf:\"bytes,10,rep,name=supported_http_methods,json=supportedHttpMethods,proto3\"`\n\tunknownFields                   protoimpl.UnknownFields\n\tsizeCache                       protoimpl.SizeCache\n}\n\nfunc (x *NetworkService) Reset() {\n\t*x = NetworkService{}\n\tmi := &file_network_service_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NetworkService) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NetworkService) ProtoMessage() {}\n\nfunc (x *NetworkService) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_service_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *NetworkService) GetNetworkEndpoint() *network_go_proto.NetworkEndpoint {\n\tif x != nil {\n\t\treturn x.xxx_hidden_NetworkEndpoint\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetTransportProtocol() network_go_proto.TransportProtocol {\n\tif x != nil {\n\t\treturn x.xxx_hidden_TransportProtocol\n\t}\n\treturn network_go_proto.TransportProtocol(0)\n}\n\nfunc (x *NetworkService) GetServiceName() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ServiceName\n\t}\n\treturn \"\"\n}\n\nfunc (x *NetworkService) GetSoftware() *software_go_proto.Software {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Software\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetVersionSet() *software_go_proto.VersionSet {\n\tif x != nil {\n\t\treturn x.xxx_hidden_VersionSet\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetBanner() []string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Banner\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetServiceContext() *ServiceContext {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ServiceContext\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetCpes() []string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Cpes\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetSupportedSslVersions() []string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_SupportedSslVersions\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetSupportedHttpMethods() []string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_SupportedHttpMethods\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) SetNetworkEndpoint(v *network_go_proto.NetworkEndpoint) {\n\tx.xxx_hidden_NetworkEndpoint = v\n}\n\nfunc (x *NetworkService) SetTransportProtocol(v network_go_proto.TransportProtocol) {\n\tx.xxx_hidden_TransportProtocol = v\n}\n\nfunc (x *NetworkService) SetServiceName(v string) {\n\tx.xxx_hidden_ServiceName = v\n}\n\nfunc (x *NetworkService) SetSoftware(v *software_go_proto.Software) {\n\tx.xxx_hidden_Software = v\n}\n\nfunc (x *NetworkService) SetVersionSet(v *software_go_proto.VersionSet) {\n\tx.xxx_hidden_VersionSet = v\n}\n\nfunc (x *NetworkService) SetBanner(v []string) {\n\tx.xxx_hidden_Banner = v\n}\n\nfunc (x *NetworkService) SetServiceContext(v *ServiceContext) {\n\tx.xxx_hidden_ServiceContext = v\n}\n\nfunc (x *NetworkService) SetCpes(v []string) {\n\tx.xxx_hidden_Cpes = v\n}\n\nfunc (x *NetworkService) SetSupportedSslVersions(v []string) {\n\tx.xxx_hidden_SupportedSslVersions = v\n}\n\nfunc (x *NetworkService) SetSupportedHttpMethods(v []string) {\n\tx.xxx_hidden_SupportedHttpMethods = v\n}\n\nfunc (x *NetworkService) HasNetworkEndpoint() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_NetworkEndpoint != nil\n}\n\nfunc (x *NetworkService) HasSoftware() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Software != nil\n}\n\nfunc (x *NetworkService) HasVersionSet() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_VersionSet != nil\n}\n\nfunc (x *NetworkService) HasServiceContext() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_ServiceContext != nil\n}\n\nfunc (x *NetworkService) ClearNetworkEndpoint() {\n\tx.xxx_hidden_NetworkEndpoint = nil\n}\n\nfunc (x *NetworkService) ClearSoftware() {\n\tx.xxx_hidden_Software = nil\n}\n\nfunc (x *NetworkService) ClearVersionSet() {\n\tx.xxx_hidden_VersionSet = nil\n}\n\nfunc (x *NetworkService) ClearServiceContext() {\n\tx.xxx_hidden_ServiceContext = nil\n}\n\ntype NetworkService_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The network endpoint where this network service is served.\n\tNetworkEndpoint *network_go_proto.NetworkEndpoint\n\t// The transport layer protocol used by the service.\n\tTransportProtocol network_go_proto.TransportProtocol\n\t// The name of the network service, following convention in RFC6335. Examples\n\t// are like http, telnet, ssh, etc.\n\tServiceName string\n\t// The software that provides the service behind the port.\n\tSoftware *software_go_proto.Software\n\t// The complete set of versions of the software.\n\tVersionSet *software_go_proto.VersionSet\n\t// Banners generated by the service.\n\tBanner []string\n\t// Context information about this network service.\n\tServiceContext *ServiceContext\n\t// The detected Common Platform Enumeration (CPE) name for service,\n\t// in the uri binding representation, like: cpe:/a:openbsd:openssh:8.4p1\n\tCpes []string\n\t// List of supported SSL versions (e.g. TLSv1, SSLv3, ...) on the service.\n\tSupportedSslVersions []string\n\t// List of supported HTTP methods (e.g. POST, GET, ...) on the service.\n\tSupportedHttpMethods []string\n}\n\nfunc (b0 NetworkService_builder) Build() *NetworkService {\n\tm0 := &NetworkService{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_NetworkEndpoint = b.NetworkEndpoint\n\tx.xxx_hidden_TransportProtocol = b.TransportProtocol\n\tx.xxx_hidden_ServiceName = b.ServiceName\n\tx.xxx_hidden_Software = b.Software\n\tx.xxx_hidden_VersionSet = b.VersionSet\n\tx.xxx_hidden_Banner = b.Banner\n\tx.xxx_hidden_ServiceContext = b.ServiceContext\n\tx.xxx_hidden_Cpes = b.Cpes\n\tx.xxx_hidden_SupportedSslVersions = b.SupportedSslVersions\n\tx.xxx_hidden_SupportedHttpMethods = b.SupportedHttpMethods\n\treturn m0\n}\n\n// Context information about a specific network service.\ntype ServiceContext struct {\n\tstate              protoimpl.MessageState   `protogen:\"opaque.v1\"`\n\txxx_hidden_Context isServiceContext_Context `protobuf_oneof:\"context\"`\n\tunknownFields      protoimpl.UnknownFields\n\tsizeCache          protoimpl.SizeCache\n}\n\nfunc (x *ServiceContext) Reset() {\n\t*x = ServiceContext{}\n\tmi := &file_network_service_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServiceContext) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServiceContext) ProtoMessage() {}\n\nfunc (x *ServiceContext) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_service_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *ServiceContext) GetWebServiceContext() *WebServiceContext {\n\tif x != nil {\n\t\tif x, ok := x.xxx_hidden_Context.(*serviceContext_WebServiceContext); ok {\n\t\t\treturn x.WebServiceContext\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *ServiceContext) SetWebServiceContext(v *WebServiceContext) {\n\tif v == nil {\n\t\tx.xxx_hidden_Context = nil\n\t\treturn\n\t}\n\tx.xxx_hidden_Context = &serviceContext_WebServiceContext{v}\n}\n\nfunc (x *ServiceContext) HasContext() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Context != nil\n}\n\nfunc (x *ServiceContext) HasWebServiceContext() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\t_, ok := x.xxx_hidden_Context.(*serviceContext_WebServiceContext)\n\treturn ok\n}\n\nfunc (x *ServiceContext) ClearContext() {\n\tx.xxx_hidden_Context = nil\n}\n\nfunc (x *ServiceContext) ClearWebServiceContext() {\n\tif _, ok := x.xxx_hidden_Context.(*serviceContext_WebServiceContext); ok {\n\t\tx.xxx_hidden_Context = nil\n\t}\n}\n\nconst ServiceContext_Context_not_set_case case_ServiceContext_Context = 0\nconst ServiceContext_WebServiceContext_case case_ServiceContext_Context = 1\n\nfunc (x *ServiceContext) WhichContext() case_ServiceContext_Context {\n\tif x == nil {\n\t\treturn ServiceContext_Context_not_set_case\n\t}\n\tswitch x.xxx_hidden_Context.(type) {\n\tcase *serviceContext_WebServiceContext:\n\t\treturn ServiceContext_WebServiceContext_case\n\tdefault:\n\t\treturn ServiceContext_Context_not_set_case\n\t}\n}\n\ntype ServiceContext_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Fields of oneof xxx_hidden_Context:\n\tWebServiceContext *WebServiceContext\n\t// -- end of xxx_hidden_Context\n}\n\nfunc (b0 ServiceContext_builder) Build() *ServiceContext {\n\tm0 := &ServiceContext{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tif b.WebServiceContext != nil {\n\t\tx.xxx_hidden_Context = &serviceContext_WebServiceContext{b.WebServiceContext}\n\t}\n\treturn m0\n}\n\ntype case_ServiceContext_Context protoreflect.FieldNumber\n\nfunc (x case_ServiceContext_Context) String() string {\n\tmd := file_network_service_proto_msgTypes[1].Descriptor()\n\tif x == 0 {\n\t\treturn \"not set\"\n\t}\n\treturn protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))\n}\n\ntype isServiceContext_Context interface {\n\tisServiceContext_Context()\n}\n\ntype serviceContext_WebServiceContext struct {\n\tWebServiceContext *WebServiceContext `protobuf:\"bytes,1,opt,name=web_service_context,json=webServiceContext,proto3,oneof\"`\n}\n\nfunc (*serviceContext_WebServiceContext) isServiceContext_Context() {}\n\n// Context information about a web application.\n// NEXT ID: 5\ntype WebServiceContext struct {\n\tstate                      protoimpl.MessageState             `protogen:\"opaque.v1\"`\n\txxx_hidden_ApplicationRoot string                             `protobuf:\"bytes,1,opt,name=application_root,json=applicationRoot,proto3\"`\n\txxx_hidden_Software        *software_go_proto.Software        `protobuf:\"bytes,2,opt,name=software,proto3\"`\n\txxx_hidden_VersionSet      *software_go_proto.VersionSet      `protobuf:\"bytes,3,opt,name=version_set,json=versionSet,proto3\"`\n\txxx_hidden_CrawlResults    *[]*web_crawl_go_proto.CrawlResult `protobuf:\"bytes,4,rep,name=crawl_results,json=crawlResults,proto3\"`\n\tunknownFields              protoimpl.UnknownFields\n\tsizeCache                  protoimpl.SizeCache\n}\n\nfunc (x *WebServiceContext) Reset() {\n\t*x = WebServiceContext{}\n\tmi := &file_network_service_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WebServiceContext) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WebServiceContext) ProtoMessage() {}\n\nfunc (x *WebServiceContext) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_service_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *WebServiceContext) GetApplicationRoot() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ApplicationRoot\n\t}\n\treturn \"\"\n}\n\nfunc (x *WebServiceContext) GetSoftware() *software_go_proto.Software {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Software\n\t}\n\treturn nil\n}\n\nfunc (x *WebServiceContext) GetVersionSet() *software_go_proto.VersionSet {\n\tif x != nil {\n\t\treturn x.xxx_hidden_VersionSet\n\t}\n\treturn nil\n}\n\nfunc (x *WebServiceContext) GetCrawlResults() []*web_crawl_go_proto.CrawlResult {\n\tif x != nil {\n\t\tif x.xxx_hidden_CrawlResults != nil {\n\t\t\treturn *x.xxx_hidden_CrawlResults\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *WebServiceContext) SetApplicationRoot(v string) {\n\tx.xxx_hidden_ApplicationRoot = v\n}\n\nfunc (x *WebServiceContext) SetSoftware(v *software_go_proto.Software) {\n\tx.xxx_hidden_Software = v\n}\n\nfunc (x *WebServiceContext) SetVersionSet(v *software_go_proto.VersionSet) {\n\tx.xxx_hidden_VersionSet = v\n}\n\nfunc (x *WebServiceContext) SetCrawlResults(v []*web_crawl_go_proto.CrawlResult) {\n\tx.xxx_hidden_CrawlResults = &v\n}\n\nfunc (x *WebServiceContext) HasSoftware() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Software != nil\n}\n\nfunc (x *WebServiceContext) HasVersionSet() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_VersionSet != nil\n}\n\nfunc (x *WebServiceContext) ClearSoftware() {\n\tx.xxx_hidden_Software = nil\n}\n\nfunc (x *WebServiceContext) ClearVersionSet() {\n\tx.xxx_hidden_VersionSet = nil\n}\n\ntype WebServiceContext_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The root path of the hosted web application.\n\tApplicationRoot string\n\t// The web application that is serving under the application root.\n\tSoftware *software_go_proto.Software\n\t// The detected versions of the web application.\n\tVersionSet *software_go_proto.VersionSet\n\t// Fingerprinter's crawling results for this web service.\n\tCrawlResults []*web_crawl_go_proto.CrawlResult\n}\n\nfunc (b0 WebServiceContext_builder) Build() *WebServiceContext {\n\tm0 := &WebServiceContext{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_ApplicationRoot = b.ApplicationRoot\n\tx.xxx_hidden_Software = b.Software\n\tx.xxx_hidden_VersionSet = b.VersionSet\n\tx.xxx_hidden_CrawlResults = &b.CrawlResults\n\treturn m0\n}\n\nvar File_network_service_proto protoreflect.FileDescriptor\n\nconst file_network_service_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x15network_service.proto\\x12\\rtsunami.proto\\x1a\\rnetwork.proto\\x1a\\x0esoftware.proto\\x1a\\x0fweb_crawl.proto\\\"\\xa0\\x04\\n\" +\n\t\"\\x0eNetworkService\\x12I\\n\" +\n\t\"\\x10network_endpoint\\x18\\x01 \\x01(\\v2\\x1e.tsunami.proto.NetworkEndpointR\\x0fnetworkEndpoint\\x12O\\n\" +\n\t\"\\x12transport_protocol\\x18\\x02 \\x01(\\x0e2 .tsunami.proto.TransportProtocolR\\x11transportProtocol\\x12!\\n\" +\n\t\"\\fservice_name\\x18\\x03 \\x01(\\tR\\vserviceName\\x123\\n\" +\n\t\"\\bsoftware\\x18\\x04 \\x01(\\v2\\x17.tsunami.proto.SoftwareR\\bsoftware\\x12:\\n\" +\n\t\"\\vversion_set\\x18\\x05 \\x01(\\v2\\x19.tsunami.proto.VersionSetR\\n\" +\n\t\"versionSet\\x12\\x16\\n\" +\n\t\"\\x06banner\\x18\\x06 \\x03(\\tR\\x06banner\\x12F\\n\" +\n\t\"\\x0fservice_context\\x18\\a \\x01(\\v2\\x1d.tsunami.proto.ServiceContextR\\x0eserviceContext\\x12\\x12\\n\" +\n\t\"\\x04cpes\\x18\\b \\x03(\\tR\\x04cpes\\x124\\n\" +\n\t\"\\x16supported_ssl_versions\\x18\\t \\x03(\\tR\\x14supportedSslVersions\\x124\\n\" +\n\t\"\\x16supported_http_methods\\x18\\n\" +\n\t\" \\x03(\\tR\\x14supportedHttpMethods\\\"o\\n\" +\n\t\"\\x0eServiceContext\\x12R\\n\" +\n\t\"\\x13web_service_context\\x18\\x01 \\x01(\\v2 .tsunami.proto.WebServiceContextH\\x00R\\x11webServiceContextB\\t\\n\" +\n\t\"\\acontext\\\"\\xf0\\x01\\n\" +\n\t\"\\x11WebServiceContext\\x12)\\n\" +\n\t\"\\x10application_root\\x18\\x01 \\x01(\\tR\\x0fapplicationRoot\\x123\\n\" +\n\t\"\\bsoftware\\x18\\x02 \\x01(\\v2\\x17.tsunami.proto.SoftwareR\\bsoftware\\x12:\\n\" +\n\t\"\\vversion_set\\x18\\x03 \\x01(\\v2\\x19.tsunami.proto.VersionSetR\\n\" +\n\t\"versionSet\\x12?\\n\" +\n\t\"\\rcrawl_results\\x18\\x04 \\x03(\\v2\\x1a.tsunami.proto.CrawlResultR\\fcrawlResultsB\\x80\\x01\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x14NetworkServiceProtosP\\x01ZLgithub.com/google/tsunami-security-scanner/proto/go/network_service_go_protob\\x06proto3\"\n\nvar file_network_service_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_network_service_proto_goTypes = []any{\n\t(*NetworkService)(nil),                   // 0: tsunami.proto.NetworkService\n\t(*ServiceContext)(nil),                   // 1: tsunami.proto.ServiceContext\n\t(*WebServiceContext)(nil),                // 2: tsunami.proto.WebServiceContext\n\t(*network_go_proto.NetworkEndpoint)(nil), // 3: tsunami.proto.NetworkEndpoint\n\t(network_go_proto.TransportProtocol)(0),  // 4: tsunami.proto.TransportProtocol\n\t(*software_go_proto.Software)(nil),       // 5: tsunami.proto.Software\n\t(*software_go_proto.VersionSet)(nil),     // 6: tsunami.proto.VersionSet\n\t(*web_crawl_go_proto.CrawlResult)(nil),   // 7: tsunami.proto.CrawlResult\n}\nvar file_network_service_proto_depIdxs = []int32{\n\t3, // 0: tsunami.proto.NetworkService.network_endpoint:type_name -> tsunami.proto.NetworkEndpoint\n\t4, // 1: tsunami.proto.NetworkService.transport_protocol:type_name -> tsunami.proto.TransportProtocol\n\t5, // 2: tsunami.proto.NetworkService.software:type_name -> tsunami.proto.Software\n\t6, // 3: tsunami.proto.NetworkService.version_set:type_name -> tsunami.proto.VersionSet\n\t1, // 4: tsunami.proto.NetworkService.service_context:type_name -> tsunami.proto.ServiceContext\n\t2, // 5: tsunami.proto.ServiceContext.web_service_context:type_name -> tsunami.proto.WebServiceContext\n\t5, // 6: tsunami.proto.WebServiceContext.software:type_name -> tsunami.proto.Software\n\t6, // 7: tsunami.proto.WebServiceContext.version_set:type_name -> tsunami.proto.VersionSet\n\t7, // 8: tsunami.proto.WebServiceContext.crawl_results:type_name -> tsunami.proto.CrawlResult\n\t9, // [9:9] is the sub-list for method output_type\n\t9, // [9:9] is the sub-list for method input_type\n\t9, // [9:9] is the sub-list for extension type_name\n\t9, // [9:9] is the sub-list for extension extendee\n\t0, // [0:9] is the sub-list for field type_name\n}\n\nfunc init() { file_network_service_proto_init() }\nfunc file_network_service_proto_init() {\n\tif File_network_service_proto != nil {\n\t\treturn\n\t}\n\tfile_network_service_proto_msgTypes[1].OneofWrappers = []any{\n\t\t(*serviceContext_WebServiceContext)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_network_service_proto_rawDesc), len(file_network_service_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_network_service_proto_goTypes,\n\t\tDependencyIndexes: file_network_service_proto_depIdxs,\n\t\tMessageInfos:      file_network_service_proto_msgTypes,\n\t}.Build()\n\tFile_network_service_proto = out.File\n\tfile_network_service_proto_goTypes = nil\n\tfile_network_service_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/payload_generator_go_proto/payload_generator.pb.go",
    "content": "//\n// Copyright 2022 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models utilized by the Tsunami Paylaod Generator\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: payload_generator.proto\n\npackage payload_generator_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\twrapperspb \"google.golang.org/protobuf/types/known/wrapperspb\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype PayloadValidationType int32\n\nconst (\n\tPayloadValidationType_VALIDATION_TYPE_UNSPECIFIED PayloadValidationType = 0\n\tPayloadValidationType_VALIDATION_REGEX            PayloadValidationType = 1\n)\n\n// Enum value maps for PayloadValidationType.\nvar (\n\tPayloadValidationType_name = map[int32]string{\n\t\t0: \"VALIDATION_TYPE_UNSPECIFIED\",\n\t\t1: \"VALIDATION_REGEX\",\n\t}\n\tPayloadValidationType_value = map[string]int32{\n\t\t\"VALIDATION_TYPE_UNSPECIFIED\": 0,\n\t\t\"VALIDATION_REGEX\":            1,\n\t}\n)\n\nfunc (x PayloadValidationType) Enum() *PayloadValidationType {\n\tp := new(PayloadValidationType)\n\t*p = x\n\treturn p\n}\n\nfunc (x PayloadValidationType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PayloadValidationType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_payload_generator_proto_enumTypes[0].Descriptor()\n}\n\nfunc (PayloadValidationType) Type() protoreflect.EnumType {\n\treturn &file_payload_generator_proto_enumTypes[0]\n}\n\nfunc (x PayloadValidationType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// The type of vulnerability the detector is looking for\ntype PayloadGeneratorConfig_VulnerabilityType int32\n\nconst (\n\t// Unspecified vulnerability type\n\tPayloadGeneratorConfig_VULNERABILITY_TYPE_UNSPECIFIED PayloadGeneratorConfig_VulnerabilityType = 0\n\t// RCE which returns the output of the execution\n\tPayloadGeneratorConfig_REFLECTIVE_RCE PayloadGeneratorConfig_VulnerabilityType = 1\n\t// RCE which does not return the output of the execution\n\tPayloadGeneratorConfig_BLIND_RCE PayloadGeneratorConfig_VulnerabilityType = 2\n\t// Server-Side Request Forgery\n\tPayloadGeneratorConfig_SSRF PayloadGeneratorConfig_VulnerabilityType = 3\n\t// Arbitrary File Write\n\tPayloadGeneratorConfig_ARBITRARY_FILE_WRITE PayloadGeneratorConfig_VulnerabilityType = 4\n\t// RCE without output of the execution + File Read (needed to get\n\t// confirmation string)\n\tPayloadGeneratorConfig_BLIND_RCE_FILE_READ PayloadGeneratorConfig_VulnerabilityType = 5\n)\n\n// Enum value maps for PayloadGeneratorConfig_VulnerabilityType.\nvar (\n\tPayloadGeneratorConfig_VulnerabilityType_name = map[int32]string{\n\t\t0: \"VULNERABILITY_TYPE_UNSPECIFIED\",\n\t\t1: \"REFLECTIVE_RCE\",\n\t\t2: \"BLIND_RCE\",\n\t\t3: \"SSRF\",\n\t\t4: \"ARBITRARY_FILE_WRITE\",\n\t\t5: \"BLIND_RCE_FILE_READ\",\n\t}\n\tPayloadGeneratorConfig_VulnerabilityType_value = map[string]int32{\n\t\t\"VULNERABILITY_TYPE_UNSPECIFIED\": 0,\n\t\t\"REFLECTIVE_RCE\":                 1,\n\t\t\"BLIND_RCE\":                      2,\n\t\t\"SSRF\":                           3,\n\t\t\"ARBITRARY_FILE_WRITE\":           4,\n\t\t\"BLIND_RCE_FILE_READ\":            5,\n\t}\n)\n\nfunc (x PayloadGeneratorConfig_VulnerabilityType) Enum() *PayloadGeneratorConfig_VulnerabilityType {\n\tp := new(PayloadGeneratorConfig_VulnerabilityType)\n\t*p = x\n\treturn p\n}\n\nfunc (x PayloadGeneratorConfig_VulnerabilityType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PayloadGeneratorConfig_VulnerabilityType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_payload_generator_proto_enumTypes[1].Descriptor()\n}\n\nfunc (PayloadGeneratorConfig_VulnerabilityType) Type() protoreflect.EnumType {\n\treturn &file_payload_generator_proto_enumTypes[1]\n}\n\nfunc (x PayloadGeneratorConfig_VulnerabilityType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// The environment that processes the payload for execution e.g. a PHP-based\n// target likely wants a payload that is itself PHP code.\ntype PayloadGeneratorConfig_InterpretationEnvironment int32\n\nconst (\n\t// Unspecified interpretation environment type\n\tPayloadGeneratorConfig_INTERPRETATION_ENVIRONMENT_UNSPECIFIED PayloadGeneratorConfig_InterpretationEnvironment = 0\n\t// Payload is interpreted within a Linux shell environment\n\tPayloadGeneratorConfig_LINUX_SHELL PayloadGeneratorConfig_InterpretationEnvironment = 1\n\t// Payload is interpreted wihin a Java compiler context\n\tPayloadGeneratorConfig_JAVA PayloadGeneratorConfig_InterpretationEnvironment = 2\n\t// Payload is interpreted wihin a PHP VM context\n\tPayloadGeneratorConfig_PHP PayloadGeneratorConfig_InterpretationEnvironment = 3\n\t// Interpretation environment doesn't matter\n\tPayloadGeneratorConfig_INTERPRETATION_ANY PayloadGeneratorConfig_InterpretationEnvironment = 4\n\t// Payload is interpreted wihin crontab\n\tPayloadGeneratorConfig_LINUX_ROOT_CRONTAB PayloadGeneratorConfig_InterpretationEnvironment = 5\n\t// Payload is interpreted wihin a Windows shell environment\n\tPayloadGeneratorConfig_WINDOWS_SHELL PayloadGeneratorConfig_InterpretationEnvironment = 6\n\t// Payload is interpreted within a JSP shell environment\n\tPayloadGeneratorConfig_JSP PayloadGeneratorConfig_InterpretationEnvironment = 7\n)\n\n// Enum value maps for PayloadGeneratorConfig_InterpretationEnvironment.\nvar (\n\tPayloadGeneratorConfig_InterpretationEnvironment_name = map[int32]string{\n\t\t0: \"INTERPRETATION_ENVIRONMENT_UNSPECIFIED\",\n\t\t1: \"LINUX_SHELL\",\n\t\t2: \"JAVA\",\n\t\t3: \"PHP\",\n\t\t4: \"INTERPRETATION_ANY\",\n\t\t5: \"LINUX_ROOT_CRONTAB\",\n\t\t6: \"WINDOWS_SHELL\",\n\t\t7: \"JSP\",\n\t}\n\tPayloadGeneratorConfig_InterpretationEnvironment_value = map[string]int32{\n\t\t\"INTERPRETATION_ENVIRONMENT_UNSPECIFIED\": 0,\n\t\t\"LINUX_SHELL\":                            1,\n\t\t\"JAVA\":                                   2,\n\t\t\"PHP\":                                    3,\n\t\t\"INTERPRETATION_ANY\":                     4,\n\t\t\"LINUX_ROOT_CRONTAB\":                     5,\n\t\t\"WINDOWS_SHELL\":                          6,\n\t\t\"JSP\":                                    7,\n\t}\n)\n\nfunc (x PayloadGeneratorConfig_InterpretationEnvironment) Enum() *PayloadGeneratorConfig_InterpretationEnvironment {\n\tp := new(PayloadGeneratorConfig_InterpretationEnvironment)\n\t*p = x\n\treturn p\n}\n\nfunc (x PayloadGeneratorConfig_InterpretationEnvironment) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PayloadGeneratorConfig_InterpretationEnvironment) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_payload_generator_proto_enumTypes[2].Descriptor()\n}\n\nfunc (PayloadGeneratorConfig_InterpretationEnvironment) Type() protoreflect.EnumType {\n\treturn &file_payload_generator_proto_enumTypes[2]\n}\n\nfunc (x PayloadGeneratorConfig_InterpretationEnvironment) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// The actual runtime environment when the payload is run e.g. while a\n// PHP-based target wants a PHP-interpretation environment, the actual code\n// execution may happen via the Linux shell: exec(“echo \\”this is running in\n// the system.\\””).\ntype PayloadGeneratorConfig_ExecutionEnvironment int32\n\nconst (\n\t// Unspecified execution environment type\n\tPayloadGeneratorConfig_EXECUTION_ENVIRONMENT_UNSPECIFIED PayloadGeneratorConfig_ExecutionEnvironment = 0\n\t// Execute within the InterpretationEnvironment\n\tPayloadGeneratorConfig_EXEC_INTERPRETATION_ENVIRONMENT PayloadGeneratorConfig_ExecutionEnvironment = 1\n\t// Execution environment doesn't matter\n\tPayloadGeneratorConfig_EXEC_ANY PayloadGeneratorConfig_ExecutionEnvironment = 2\n)\n\n// Enum value maps for PayloadGeneratorConfig_ExecutionEnvironment.\nvar (\n\tPayloadGeneratorConfig_ExecutionEnvironment_name = map[int32]string{\n\t\t0: \"EXECUTION_ENVIRONMENT_UNSPECIFIED\",\n\t\t1: \"EXEC_INTERPRETATION_ENVIRONMENT\",\n\t\t2: \"EXEC_ANY\",\n\t}\n\tPayloadGeneratorConfig_ExecutionEnvironment_value = map[string]int32{\n\t\t\"EXECUTION_ENVIRONMENT_UNSPECIFIED\": 0,\n\t\t\"EXEC_INTERPRETATION_ENVIRONMENT\":   1,\n\t\t\"EXEC_ANY\":                          2,\n\t}\n)\n\nfunc (x PayloadGeneratorConfig_ExecutionEnvironment) Enum() *PayloadGeneratorConfig_ExecutionEnvironment {\n\tp := new(PayloadGeneratorConfig_ExecutionEnvironment)\n\t*p = x\n\treturn p\n}\n\nfunc (x PayloadGeneratorConfig_ExecutionEnvironment) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PayloadGeneratorConfig_ExecutionEnvironment) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_payload_generator_proto_enumTypes[3].Descriptor()\n}\n\nfunc (PayloadGeneratorConfig_ExecutionEnvironment) Type() protoreflect.EnumType {\n\treturn &file_payload_generator_proto_enumTypes[3]\n}\n\nfunc (x PayloadGeneratorConfig_ExecutionEnvironment) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Attributes utilized by the PayloadGenerator to select a payload\ntype PayloadGeneratorConfig struct {\n\tstate                                protoimpl.MessageState                           `protogen:\"opaque.v1\"`\n\txxx_hidden_VulnerabilityType         PayloadGeneratorConfig_VulnerabilityType         `protobuf:\"varint,2,opt,name=vulnerability_type,json=vulnerabilityType,proto3,enum=tsunami.proto.PayloadGeneratorConfig_VulnerabilityType\"`\n\txxx_hidden_InterpretationEnvironment PayloadGeneratorConfig_InterpretationEnvironment `protobuf:\"varint,3,opt,name=interpretation_environment,json=interpretationEnvironment,proto3,enum=tsunami.proto.PayloadGeneratorConfig_InterpretationEnvironment\"`\n\txxx_hidden_ExecutionEnvironment      PayloadGeneratorConfig_ExecutionEnvironment      `protobuf:\"varint,4,opt,name=execution_environment,json=executionEnvironment,proto3,enum=tsunami.proto.PayloadGeneratorConfig_ExecutionEnvironment\"`\n\tunknownFields                        protoimpl.UnknownFields\n\tsizeCache                            protoimpl.SizeCache\n}\n\nfunc (x *PayloadGeneratorConfig) Reset() {\n\t*x = PayloadGeneratorConfig{}\n\tmi := &file_payload_generator_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PayloadGeneratorConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PayloadGeneratorConfig) ProtoMessage() {}\n\nfunc (x *PayloadGeneratorConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_payload_generator_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *PayloadGeneratorConfig) GetVulnerabilityType() PayloadGeneratorConfig_VulnerabilityType {\n\tif x != nil {\n\t\treturn x.xxx_hidden_VulnerabilityType\n\t}\n\treturn PayloadGeneratorConfig_VULNERABILITY_TYPE_UNSPECIFIED\n}\n\nfunc (x *PayloadGeneratorConfig) GetInterpretationEnvironment() PayloadGeneratorConfig_InterpretationEnvironment {\n\tif x != nil {\n\t\treturn x.xxx_hidden_InterpretationEnvironment\n\t}\n\treturn PayloadGeneratorConfig_INTERPRETATION_ENVIRONMENT_UNSPECIFIED\n}\n\nfunc (x *PayloadGeneratorConfig) GetExecutionEnvironment() PayloadGeneratorConfig_ExecutionEnvironment {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ExecutionEnvironment\n\t}\n\treturn PayloadGeneratorConfig_EXECUTION_ENVIRONMENT_UNSPECIFIED\n}\n\nfunc (x *PayloadGeneratorConfig) SetVulnerabilityType(v PayloadGeneratorConfig_VulnerabilityType) {\n\tx.xxx_hidden_VulnerabilityType = v\n}\n\nfunc (x *PayloadGeneratorConfig) SetInterpretationEnvironment(v PayloadGeneratorConfig_InterpretationEnvironment) {\n\tx.xxx_hidden_InterpretationEnvironment = v\n}\n\nfunc (x *PayloadGeneratorConfig) SetExecutionEnvironment(v PayloadGeneratorConfig_ExecutionEnvironment) {\n\tx.xxx_hidden_ExecutionEnvironment = v\n}\n\ntype PayloadGeneratorConfig_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tVulnerabilityType         PayloadGeneratorConfig_VulnerabilityType\n\tInterpretationEnvironment PayloadGeneratorConfig_InterpretationEnvironment\n\tExecutionEnvironment      PayloadGeneratorConfig_ExecutionEnvironment\n}\n\nfunc (b0 PayloadGeneratorConfig_builder) Build() *PayloadGeneratorConfig {\n\tm0 := &PayloadGeneratorConfig{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_VulnerabilityType = b.VulnerabilityType\n\tx.xxx_hidden_InterpretationEnvironment = b.InterpretationEnvironment\n\tx.xxx_hidden_ExecutionEnvironment = b.ExecutionEnvironment\n\treturn m0\n}\n\n// Attributes of a payload. A detector can check these attributes to change its\n// logic based on the payload type.\ntype PayloadAttributes struct {\n\tstate                         protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_UsesCallbackServer bool                   `protobuf:\"varint,1,opt,name=uses_callback_server,json=usesCallbackServer,proto3\"`\n\tunknownFields                 protoimpl.UnknownFields\n\tsizeCache                     protoimpl.SizeCache\n}\n\nfunc (x *PayloadAttributes) Reset() {\n\t*x = PayloadAttributes{}\n\tmi := &file_payload_generator_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PayloadAttributes) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PayloadAttributes) ProtoMessage() {}\n\nfunc (x *PayloadAttributes) ProtoReflect() protoreflect.Message {\n\tmi := &file_payload_generator_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *PayloadAttributes) GetUsesCallbackServer() bool {\n\tif x != nil {\n\t\treturn x.xxx_hidden_UsesCallbackServer\n\t}\n\treturn false\n}\n\nfunc (x *PayloadAttributes) SetUsesCallbackServer(v bool) {\n\tx.xxx_hidden_UsesCallbackServer = v\n}\n\ntype PayloadAttributes_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Whether the payload uses the callback server\n\tUsesCallbackServer bool\n}\n\nfunc (b0 PayloadAttributes_builder) Build() *PayloadAttributes {\n\tm0 := &PayloadAttributes{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_UsesCallbackServer = b.UsesCallbackServer\n\treturn m0\n}\n\n// Container type for payload_definitions.yaml\ntype PayloadLibrary struct {\n\tstate               protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Payloads *[]*PayloadDefinition  `protobuf:\"bytes,1,rep,name=payloads,proto3\"`\n\tunknownFields       protoimpl.UnknownFields\n\tsizeCache           protoimpl.SizeCache\n}\n\nfunc (x *PayloadLibrary) Reset() {\n\t*x = PayloadLibrary{}\n\tmi := &file_payload_generator_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PayloadLibrary) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PayloadLibrary) ProtoMessage() {}\n\nfunc (x *PayloadLibrary) ProtoReflect() protoreflect.Message {\n\tmi := &file_payload_generator_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *PayloadLibrary) GetPayloads() []*PayloadDefinition {\n\tif x != nil {\n\t\tif x.xxx_hidden_Payloads != nil {\n\t\t\treturn *x.xxx_hidden_Payloads\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *PayloadLibrary) SetPayloads(v []*PayloadDefinition) {\n\tx.xxx_hidden_Payloads = &v\n}\n\ntype PayloadLibrary_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tPayloads []*PayloadDefinition\n}\n\nfunc (b0 PayloadLibrary_builder) Build() *PayloadLibrary {\n\tm0 := &PayloadLibrary{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Payloads = &b.Payloads\n\treturn m0\n}\n\n// Schema for each entry in payload_definitions.yaml\n// Note: this message uses StringValue and BoolValue because we validate whether\n// each payload definition in the yaml file has the correct fields present.\n// Since empty proto fields are given default values (proto fields are not\n// nullable), we use the wrapped types to check for actual presence.\ntype PayloadDefinition struct {\n\tstate                                protoimpl.MessageState                           `protogen:\"opaque.v1\"`\n\txxx_hidden_Name                      *wrapperspb.StringValue                          `protobuf:\"bytes,1,opt,name=name,proto3\"`\n\txxx_hidden_InterpretationEnvironment PayloadGeneratorConfig_InterpretationEnvironment `protobuf:\"varint,2,opt,name=interpretation_environment,json=interpretationEnvironment,proto3,enum=tsunami.proto.PayloadGeneratorConfig_InterpretationEnvironment\"`\n\txxx_hidden_ExecutionEnvironment      PayloadGeneratorConfig_ExecutionEnvironment      `protobuf:\"varint,3,opt,name=execution_environment,json=executionEnvironment,proto3,enum=tsunami.proto.PayloadGeneratorConfig_ExecutionEnvironment\"`\n\txxx_hidden_VulnerabilityType         []PayloadGeneratorConfig_VulnerabilityType       `protobuf:\"varint,4,rep,packed,name=vulnerability_type,json=vulnerabilityType,proto3,enum=tsunami.proto.PayloadGeneratorConfig_VulnerabilityType\"`\n\txxx_hidden_UsesCallbackServer        *wrapperspb.BoolValue                            `protobuf:\"bytes,5,opt,name=uses_callback_server,json=usesCallbackServer,proto3\"`\n\txxx_hidden_PayloadString             *wrapperspb.StringValue                          `protobuf:\"bytes,6,opt,name=payload_string,json=payloadString,proto3\"`\n\txxx_hidden_ValidationType            PayloadValidationType                            `protobuf:\"varint,7,opt,name=validation_type,json=validationType,proto3,enum=tsunami.proto.PayloadValidationType\"`\n\txxx_hidden_ValidationRegex           *wrapperspb.StringValue                          `protobuf:\"bytes,8,opt,name=validation_regex,json=validationRegex,proto3\"`\n\tunknownFields                        protoimpl.UnknownFields\n\tsizeCache                            protoimpl.SizeCache\n}\n\nfunc (x *PayloadDefinition) Reset() {\n\t*x = PayloadDefinition{}\n\tmi := &file_payload_generator_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PayloadDefinition) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PayloadDefinition) ProtoMessage() {}\n\nfunc (x *PayloadDefinition) ProtoReflect() protoreflect.Message {\n\tmi := &file_payload_generator_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *PayloadDefinition) GetName() *wrapperspb.StringValue {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Name\n\t}\n\treturn nil\n}\n\nfunc (x *PayloadDefinition) GetInterpretationEnvironment() PayloadGeneratorConfig_InterpretationEnvironment {\n\tif x != nil {\n\t\treturn x.xxx_hidden_InterpretationEnvironment\n\t}\n\treturn PayloadGeneratorConfig_INTERPRETATION_ENVIRONMENT_UNSPECIFIED\n}\n\nfunc (x *PayloadDefinition) GetExecutionEnvironment() PayloadGeneratorConfig_ExecutionEnvironment {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ExecutionEnvironment\n\t}\n\treturn PayloadGeneratorConfig_EXECUTION_ENVIRONMENT_UNSPECIFIED\n}\n\nfunc (x *PayloadDefinition) GetVulnerabilityType() []PayloadGeneratorConfig_VulnerabilityType {\n\tif x != nil {\n\t\treturn x.xxx_hidden_VulnerabilityType\n\t}\n\treturn nil\n}\n\nfunc (x *PayloadDefinition) GetUsesCallbackServer() *wrapperspb.BoolValue {\n\tif x != nil {\n\t\treturn x.xxx_hidden_UsesCallbackServer\n\t}\n\treturn nil\n}\n\nfunc (x *PayloadDefinition) GetPayloadString() *wrapperspb.StringValue {\n\tif x != nil {\n\t\treturn x.xxx_hidden_PayloadString\n\t}\n\treturn nil\n}\n\nfunc (x *PayloadDefinition) GetValidationType() PayloadValidationType {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ValidationType\n\t}\n\treturn PayloadValidationType_VALIDATION_TYPE_UNSPECIFIED\n}\n\nfunc (x *PayloadDefinition) GetValidationRegex() *wrapperspb.StringValue {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ValidationRegex\n\t}\n\treturn nil\n}\n\nfunc (x *PayloadDefinition) SetName(v *wrapperspb.StringValue) {\n\tx.xxx_hidden_Name = v\n}\n\nfunc (x *PayloadDefinition) SetInterpretationEnvironment(v PayloadGeneratorConfig_InterpretationEnvironment) {\n\tx.xxx_hidden_InterpretationEnvironment = v\n}\n\nfunc (x *PayloadDefinition) SetExecutionEnvironment(v PayloadGeneratorConfig_ExecutionEnvironment) {\n\tx.xxx_hidden_ExecutionEnvironment = v\n}\n\nfunc (x *PayloadDefinition) SetVulnerabilityType(v []PayloadGeneratorConfig_VulnerabilityType) {\n\tx.xxx_hidden_VulnerabilityType = v\n}\n\nfunc (x *PayloadDefinition) SetUsesCallbackServer(v *wrapperspb.BoolValue) {\n\tx.xxx_hidden_UsesCallbackServer = v\n}\n\nfunc (x *PayloadDefinition) SetPayloadString(v *wrapperspb.StringValue) {\n\tx.xxx_hidden_PayloadString = v\n}\n\nfunc (x *PayloadDefinition) SetValidationType(v PayloadValidationType) {\n\tx.xxx_hidden_ValidationType = v\n}\n\nfunc (x *PayloadDefinition) SetValidationRegex(v *wrapperspb.StringValue) {\n\tx.xxx_hidden_ValidationRegex = v\n}\n\nfunc (x *PayloadDefinition) HasName() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Name != nil\n}\n\nfunc (x *PayloadDefinition) HasUsesCallbackServer() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_UsesCallbackServer != nil\n}\n\nfunc (x *PayloadDefinition) HasPayloadString() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_PayloadString != nil\n}\n\nfunc (x *PayloadDefinition) HasValidationRegex() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_ValidationRegex != nil\n}\n\nfunc (x *PayloadDefinition) ClearName() {\n\tx.xxx_hidden_Name = nil\n}\n\nfunc (x *PayloadDefinition) ClearUsesCallbackServer() {\n\tx.xxx_hidden_UsesCallbackServer = nil\n}\n\nfunc (x *PayloadDefinition) ClearPayloadString() {\n\tx.xxx_hidden_PayloadString = nil\n}\n\nfunc (x *PayloadDefinition) ClearValidationRegex() {\n\tx.xxx_hidden_ValidationRegex = nil\n}\n\ntype PayloadDefinition_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The human-readable string to identify the payload\n\tName                      *wrapperspb.StringValue\n\tInterpretationEnvironment PayloadGeneratorConfig_InterpretationEnvironment\n\tExecutionEnvironment      PayloadGeneratorConfig_ExecutionEnvironment\n\t// All vulnerability types this payload can be used for\n\tVulnerabilityType []PayloadGeneratorConfig_VulnerabilityType\n\t// If true, payload_string must contain the $TSUNAMI_PAYLOAD_TOKEN_URL\n\t// token. Validation will automatically check against the callback server, so\n\t// the validation* fields do not need to be set.\n\tUsesCallbackServer *wrapperspb.BoolValue\n\t// The actual payload command string. The following special tokens can be\n\t// used which will cause the framework to inject dynamic content into the\n\t// command:\n\t// - $TSUNAMI_PAYLOAD_TOKEN_URL: url for the callback server\n\t// - a random string, used to reduce false positives.\n\tPayloadString *wrapperspb.StringValue\n\t// The type of validation function for determining if the payload was\n\t// executed. Currently, only REGEX is supported.\n\tValidationType PayloadValidationType\n\t// Required if validation_type == REGEX. Must be compatible with\n\t// java.util.regex.Pattern. The string will first be preprocessed before\n\t// applied as a regex, replacing any of the following tokens with the\n\t// corresponding values supplied by the framework:\n\t//   - $TSUNAMI_PAYLOAD_TOKEN_RANDOM: a random string, used to reduce false\n\t//     positives. The value is guaranteed to be the same as the value supplied\n\t//     to payload_string.\n\tValidationRegex *wrapperspb.StringValue\n}\n\nfunc (b0 PayloadDefinition_builder) Build() *PayloadDefinition {\n\tm0 := &PayloadDefinition{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Name = b.Name\n\tx.xxx_hidden_InterpretationEnvironment = b.InterpretationEnvironment\n\tx.xxx_hidden_ExecutionEnvironment = b.ExecutionEnvironment\n\tx.xxx_hidden_VulnerabilityType = b.VulnerabilityType\n\tx.xxx_hidden_UsesCallbackServer = b.UsesCallbackServer\n\tx.xxx_hidden_PayloadString = b.PayloadString\n\tx.xxx_hidden_ValidationType = b.ValidationType\n\tx.xxx_hidden_ValidationRegex = b.ValidationRegex\n\treturn m0\n}\n\nvar File_payload_generator_proto protoreflect.FileDescriptor\n\nconst file_payload_generator_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x17payload_generator.proto\\x12\\rtsunami.proto\\x1a\\x1egoogle/protobuf/wrappers.proto\\\"\\xb7\\x06\\n\" +\n\t\"\\x16PayloadGeneratorConfig\\x12f\\n\" +\n\t\"\\x12vulnerability_type\\x18\\x02 \\x01(\\x0e27.tsunami.proto.PayloadGeneratorConfig.VulnerabilityTypeR\\x11vulnerabilityType\\x12~\\n\" +\n\t\"\\x1ainterpretation_environment\\x18\\x03 \\x01(\\x0e2?.tsunami.proto.PayloadGeneratorConfig.InterpretationEnvironmentR\\x19interpretationEnvironment\\x12o\\n\" +\n\t\"\\x15execution_environment\\x18\\x04 \\x01(\\x0e2:.tsunami.proto.PayloadGeneratorConfig.ExecutionEnvironmentR\\x14executionEnvironment\\\"\\x97\\x01\\n\" +\n\t\"\\x11VulnerabilityType\\x12\\\"\\n\" +\n\t\"\\x1eVULNERABILITY_TYPE_UNSPECIFIED\\x10\\x00\\x12\\x12\\n\" +\n\t\"\\x0eREFLECTIVE_RCE\\x10\\x01\\x12\\r\\n\" +\n\t\"\\tBLIND_RCE\\x10\\x02\\x12\\b\\n\" +\n\t\"\\x04SSRF\\x10\\x03\\x12\\x18\\n\" +\n\t\"\\x14ARBITRARY_FILE_WRITE\\x10\\x04\\x12\\x17\\n\" +\n\t\"\\x13BLIND_RCE_FILE_READ\\x10\\x05\\\"\\xb7\\x01\\n\" +\n\t\"\\x19InterpretationEnvironment\\x12*\\n\" +\n\t\"&INTERPRETATION_ENVIRONMENT_UNSPECIFIED\\x10\\x00\\x12\\x0f\\n\" +\n\t\"\\vLINUX_SHELL\\x10\\x01\\x12\\b\\n\" +\n\t\"\\x04JAVA\\x10\\x02\\x12\\a\\n\" +\n\t\"\\x03PHP\\x10\\x03\\x12\\x16\\n\" +\n\t\"\\x12INTERPRETATION_ANY\\x10\\x04\\x12\\x16\\n\" +\n\t\"\\x12LINUX_ROOT_CRONTAB\\x10\\x05\\x12\\x11\\n\" +\n\t\"\\rWINDOWS_SHELL\\x10\\x06\\x12\\a\\n\" +\n\t\"\\x03JSP\\x10\\a\\\"p\\n\" +\n\t\"\\x14ExecutionEnvironment\\x12%\\n\" +\n\t\"!EXECUTION_ENVIRONMENT_UNSPECIFIED\\x10\\x00\\x12#\\n\" +\n\t\"\\x1fEXEC_INTERPRETATION_ENVIRONMENT\\x10\\x01\\x12\\f\\n\" +\n\t\"\\bEXEC_ANY\\x10\\x02\\\"E\\n\" +\n\t\"\\x11PayloadAttributes\\x120\\n\" +\n\t\"\\x14uses_callback_server\\x18\\x01 \\x01(\\bR\\x12usesCallbackServer\\\"N\\n\" +\n\t\"\\x0ePayloadLibrary\\x12<\\n\" +\n\t\"\\bpayloads\\x18\\x01 \\x03(\\v2 .tsunami.proto.PayloadDefinitionR\\bpayloads\\\"\\xc9\\x05\\n\" +\n\t\"\\x11PayloadDefinition\\x120\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\v2\\x1c.google.protobuf.StringValueR\\x04name\\x12~\\n\" +\n\t\"\\x1ainterpretation_environment\\x18\\x02 \\x01(\\x0e2?.tsunami.proto.PayloadGeneratorConfig.InterpretationEnvironmentR\\x19interpretationEnvironment\\x12o\\n\" +\n\t\"\\x15execution_environment\\x18\\x03 \\x01(\\x0e2:.tsunami.proto.PayloadGeneratorConfig.ExecutionEnvironmentR\\x14executionEnvironment\\x12f\\n\" +\n\t\"\\x12vulnerability_type\\x18\\x04 \\x03(\\x0e27.tsunami.proto.PayloadGeneratorConfig.VulnerabilityTypeR\\x11vulnerabilityType\\x12L\\n\" +\n\t\"\\x14uses_callback_server\\x18\\x05 \\x01(\\v2\\x1a.google.protobuf.BoolValueR\\x12usesCallbackServer\\x12C\\n\" +\n\t\"\\x0epayload_string\\x18\\x06 \\x01(\\v2\\x1c.google.protobuf.StringValueR\\rpayloadString\\x12M\\n\" +\n\t\"\\x0fvalidation_type\\x18\\a \\x01(\\x0e2$.tsunami.proto.PayloadValidationTypeR\\x0evalidationType\\x12G\\n\" +\n\t\"\\x10validation_regex\\x18\\b \\x01(\\v2\\x1c.google.protobuf.StringValueR\\x0fvalidationRegex*N\\n\" +\n\t\"\\x15PayloadValidationType\\x12\\x1f\\n\" +\n\t\"\\x1bVALIDATION_TYPE_UNSPECIFIED\\x10\\x00\\x12\\x14\\n\" +\n\t\"\\x10VALIDATION_REGEX\\x10\\x01B\\x84\\x01\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x16PayloadGeneratorProtosP\\x01ZNgithub.com/google/tsunami-security-scanner/proto/go/payload_generator_go_protob\\x06proto3\"\n\nvar file_payload_generator_proto_enumTypes = make([]protoimpl.EnumInfo, 4)\nvar file_payload_generator_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_payload_generator_proto_goTypes = []any{\n\t(PayloadValidationType)(0),                            // 0: tsunami.proto.PayloadValidationType\n\t(PayloadGeneratorConfig_VulnerabilityType)(0),         // 1: tsunami.proto.PayloadGeneratorConfig.VulnerabilityType\n\t(PayloadGeneratorConfig_InterpretationEnvironment)(0), // 2: tsunami.proto.PayloadGeneratorConfig.InterpretationEnvironment\n\t(PayloadGeneratorConfig_ExecutionEnvironment)(0),      // 3: tsunami.proto.PayloadGeneratorConfig.ExecutionEnvironment\n\t(*PayloadGeneratorConfig)(nil),                        // 4: tsunami.proto.PayloadGeneratorConfig\n\t(*PayloadAttributes)(nil),                             // 5: tsunami.proto.PayloadAttributes\n\t(*PayloadLibrary)(nil),                                // 6: tsunami.proto.PayloadLibrary\n\t(*PayloadDefinition)(nil),                             // 7: tsunami.proto.PayloadDefinition\n\t(*wrapperspb.StringValue)(nil),                        // 8: google.protobuf.StringValue\n\t(*wrapperspb.BoolValue)(nil),                          // 9: google.protobuf.BoolValue\n}\nvar file_payload_generator_proto_depIdxs = []int32{\n\t1,  // 0: tsunami.proto.PayloadGeneratorConfig.vulnerability_type:type_name -> tsunami.proto.PayloadGeneratorConfig.VulnerabilityType\n\t2,  // 1: tsunami.proto.PayloadGeneratorConfig.interpretation_environment:type_name -> tsunami.proto.PayloadGeneratorConfig.InterpretationEnvironment\n\t3,  // 2: tsunami.proto.PayloadGeneratorConfig.execution_environment:type_name -> tsunami.proto.PayloadGeneratorConfig.ExecutionEnvironment\n\t7,  // 3: tsunami.proto.PayloadLibrary.payloads:type_name -> tsunami.proto.PayloadDefinition\n\t8,  // 4: tsunami.proto.PayloadDefinition.name:type_name -> google.protobuf.StringValue\n\t2,  // 5: tsunami.proto.PayloadDefinition.interpretation_environment:type_name -> tsunami.proto.PayloadGeneratorConfig.InterpretationEnvironment\n\t3,  // 6: tsunami.proto.PayloadDefinition.execution_environment:type_name -> tsunami.proto.PayloadGeneratorConfig.ExecutionEnvironment\n\t1,  // 7: tsunami.proto.PayloadDefinition.vulnerability_type:type_name -> tsunami.proto.PayloadGeneratorConfig.VulnerabilityType\n\t9,  // 8: tsunami.proto.PayloadDefinition.uses_callback_server:type_name -> google.protobuf.BoolValue\n\t8,  // 9: tsunami.proto.PayloadDefinition.payload_string:type_name -> google.protobuf.StringValue\n\t0,  // 10: tsunami.proto.PayloadDefinition.validation_type:type_name -> tsunami.proto.PayloadValidationType\n\t8,  // 11: tsunami.proto.PayloadDefinition.validation_regex:type_name -> google.protobuf.StringValue\n\t12, // [12:12] is the sub-list for method output_type\n\t12, // [12:12] is the sub-list for method input_type\n\t12, // [12:12] is the sub-list for extension type_name\n\t12, // [12:12] is the sub-list for extension extendee\n\t0,  // [0:12] is the sub-list for field type_name\n}\n\nfunc init() { file_payload_generator_proto_init() }\nfunc file_payload_generator_proto_init() {\n\tif File_payload_generator_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_payload_generator_proto_rawDesc), len(file_payload_generator_proto_rawDesc)),\n\t\t\tNumEnums:      4,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_payload_generator_proto_goTypes,\n\t\tDependencyIndexes: file_payload_generator_proto_depIdxs,\n\t\tEnumInfos:         file_payload_generator_proto_enumTypes,\n\t\tMessageInfos:      file_payload_generator_proto_msgTypes,\n\t}.Build()\n\tFile_payload_generator_proto = out.File\n\tfile_payload_generator_proto_goTypes = nil\n\tfile_payload_generator_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/plugin_representation_go_proto/plugin_representation.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Representation of a tsunami plugin definition passed between language\n// servers.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: plugin_representation.proto\n\npackage plugin_representation_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype PluginInfo_PluginType int32\n\nconst (\n\t// Plugin is an unspecified type.\n\tPluginInfo_PLUGIN_TYPE_UNSPECIFIED PluginInfo_PluginType = 0\n\t// Plugin is a port scanner.\n\tPluginInfo_PORT_SCAN PluginInfo_PluginType = 1\n\t// Plugin is a service fingerprinter.\n\tPluginInfo_SERVICE_FINGERPRINT PluginInfo_PluginType = 2\n\t// Plugin is a vulnerability detector.\n\tPluginInfo_VULN_DETECTION PluginInfo_PluginType = 3\n)\n\n// Enum value maps for PluginInfo_PluginType.\nvar (\n\tPluginInfo_PluginType_name = map[int32]string{\n\t\t0: \"PLUGIN_TYPE_UNSPECIFIED\",\n\t\t1: \"PORT_SCAN\",\n\t\t2: \"SERVICE_FINGERPRINT\",\n\t\t3: \"VULN_DETECTION\",\n\t}\n\tPluginInfo_PluginType_value = map[string]int32{\n\t\t\"PLUGIN_TYPE_UNSPECIFIED\": 0,\n\t\t\"PORT_SCAN\":               1,\n\t\t\"SERVICE_FINGERPRINT\":     2,\n\t\t\"VULN_DETECTION\":          3,\n\t}\n)\n\nfunc (x PluginInfo_PluginType) Enum() *PluginInfo_PluginType {\n\tp := new(PluginInfo_PluginType)\n\t*p = x\n\treturn p\n}\n\nfunc (x PluginInfo_PluginType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PluginInfo_PluginType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_plugin_representation_proto_enumTypes[0].Descriptor()\n}\n\nfunc (PluginInfo_PluginType) Type() protoreflect.EnumType {\n\treturn &file_plugin_representation_proto_enumTypes[0]\n}\n\nfunc (x PluginInfo_PluginType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Represents a PluginDefinition placeholder.\ntype PluginDefinition struct {\n\tstate                                 protoimpl.MessageState      `protogen:\"opaque.v1\"`\n\txxx_hidden_Info                       *PluginInfo                 `protobuf:\"bytes,1,opt,name=info,proto3\"`\n\txxx_hidden_TargetServiceName          *TargetServiceName          `protobuf:\"bytes,2,opt,name=target_service_name,json=targetServiceName,proto3\"`\n\txxx_hidden_TargetSoftware             *TargetSoftware             `protobuf:\"bytes,3,opt,name=target_software,json=targetSoftware,proto3\"`\n\txxx_hidden_ForWebService              bool                        `protobuf:\"varint,4,opt,name=for_web_service,json=forWebService,proto3\"`\n\txxx_hidden_TargetOperatingSystemClass *TargetOperatingSystemClass `protobuf:\"bytes,5,opt,name=target_operating_system_class,json=targetOperatingSystemClass,proto3\"`\n\tunknownFields                         protoimpl.UnknownFields\n\tsizeCache                             protoimpl.SizeCache\n}\n\nfunc (x *PluginDefinition) Reset() {\n\t*x = PluginDefinition{}\n\tmi := &file_plugin_representation_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PluginDefinition) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PluginDefinition) ProtoMessage() {}\n\nfunc (x *PluginDefinition) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_representation_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *PluginDefinition) GetInfo() *PluginInfo {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Info\n\t}\n\treturn nil\n}\n\nfunc (x *PluginDefinition) GetTargetServiceName() *TargetServiceName {\n\tif x != nil {\n\t\treturn x.xxx_hidden_TargetServiceName\n\t}\n\treturn nil\n}\n\nfunc (x *PluginDefinition) GetTargetSoftware() *TargetSoftware {\n\tif x != nil {\n\t\treturn x.xxx_hidden_TargetSoftware\n\t}\n\treturn nil\n}\n\nfunc (x *PluginDefinition) GetForWebService() bool {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ForWebService\n\t}\n\treturn false\n}\n\nfunc (x *PluginDefinition) GetTargetOperatingSystemClass() *TargetOperatingSystemClass {\n\tif x != nil {\n\t\treturn x.xxx_hidden_TargetOperatingSystemClass\n\t}\n\treturn nil\n}\n\nfunc (x *PluginDefinition) SetInfo(v *PluginInfo) {\n\tx.xxx_hidden_Info = v\n}\n\nfunc (x *PluginDefinition) SetTargetServiceName(v *TargetServiceName) {\n\tx.xxx_hidden_TargetServiceName = v\n}\n\nfunc (x *PluginDefinition) SetTargetSoftware(v *TargetSoftware) {\n\tx.xxx_hidden_TargetSoftware = v\n}\n\nfunc (x *PluginDefinition) SetForWebService(v bool) {\n\tx.xxx_hidden_ForWebService = v\n}\n\nfunc (x *PluginDefinition) SetTargetOperatingSystemClass(v *TargetOperatingSystemClass) {\n\tx.xxx_hidden_TargetOperatingSystemClass = v\n}\n\nfunc (x *PluginDefinition) HasInfo() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Info != nil\n}\n\nfunc (x *PluginDefinition) HasTargetServiceName() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_TargetServiceName != nil\n}\n\nfunc (x *PluginDefinition) HasTargetSoftware() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_TargetSoftware != nil\n}\n\nfunc (x *PluginDefinition) HasTargetOperatingSystemClass() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_TargetOperatingSystemClass != nil\n}\n\nfunc (x *PluginDefinition) ClearInfo() {\n\tx.xxx_hidden_Info = nil\n}\n\nfunc (x *PluginDefinition) ClearTargetServiceName() {\n\tx.xxx_hidden_TargetServiceName = nil\n}\n\nfunc (x *PluginDefinition) ClearTargetSoftware() {\n\tx.xxx_hidden_TargetSoftware = nil\n}\n\nfunc (x *PluginDefinition) ClearTargetOperatingSystemClass() {\n\tx.xxx_hidden_TargetOperatingSystemClass = nil\n}\n\ntype PluginDefinition_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// PluginInfo of this definition.\n\tInfo *PluginInfo\n\t// The name of the target service.\n\tTargetServiceName *TargetServiceName\n\t// The name of the target software.\n\tTargetSoftware *TargetSoftware\n\t// If the definition is for a web service or not.\n\tForWebService bool\n\t// If the definition is for a specific operating system or not.\n\t// Note: this filter is executed within an AND condition with the other\n\t// filters. E.g. if target_service_name.value is \"http\" and\n\t// target_operating_system.osclass.family is \"Linux\" then the plugin will only\n\t// match if the service is http and the operating system is Linux.\n\tTargetOperatingSystemClass *TargetOperatingSystemClass\n}\n\nfunc (b0 PluginDefinition_builder) Build() *PluginDefinition {\n\tm0 := &PluginDefinition{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Info = b.Info\n\tx.xxx_hidden_TargetServiceName = b.TargetServiceName\n\tx.xxx_hidden_TargetSoftware = b.TargetSoftware\n\tx.xxx_hidden_ForWebService = b.ForWebService\n\tx.xxx_hidden_TargetOperatingSystemClass = b.TargetOperatingSystemClass\n\treturn m0\n}\n\n// Represents a PluginInfo annotation placeholder used by the\n// PluginDefinition proto above.\ntype PluginInfo struct {\n\tstate                  protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Type        PluginInfo_PluginType  `protobuf:\"varint,1,opt,name=type,proto3,enum=tsunami.proto.PluginInfo_PluginType\"`\n\txxx_hidden_Name        string                 `protobuf:\"bytes,2,opt,name=name,proto3\"`\n\txxx_hidden_Version     string                 `protobuf:\"bytes,3,opt,name=version,proto3\"`\n\txxx_hidden_Description string                 `protobuf:\"bytes,4,opt,name=description,proto3\"`\n\txxx_hidden_Author      string                 `protobuf:\"bytes,5,opt,name=author,proto3\"`\n\tunknownFields          protoimpl.UnknownFields\n\tsizeCache              protoimpl.SizeCache\n}\n\nfunc (x *PluginInfo) Reset() {\n\t*x = PluginInfo{}\n\tmi := &file_plugin_representation_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PluginInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PluginInfo) ProtoMessage() {}\n\nfunc (x *PluginInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_representation_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *PluginInfo) GetType() PluginInfo_PluginType {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Type\n\t}\n\treturn PluginInfo_PLUGIN_TYPE_UNSPECIFIED\n}\n\nfunc (x *PluginInfo) GetName() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *PluginInfo) GetVersion() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Version\n\t}\n\treturn \"\"\n}\n\nfunc (x *PluginInfo) GetDescription() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *PluginInfo) GetAuthor() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Author\n\t}\n\treturn \"\"\n}\n\nfunc (x *PluginInfo) SetType(v PluginInfo_PluginType) {\n\tx.xxx_hidden_Type = v\n}\n\nfunc (x *PluginInfo) SetName(v string) {\n\tx.xxx_hidden_Name = v\n}\n\nfunc (x *PluginInfo) SetVersion(v string) {\n\tx.xxx_hidden_Version = v\n}\n\nfunc (x *PluginInfo) SetDescription(v string) {\n\tx.xxx_hidden_Description = v\n}\n\nfunc (x *PluginInfo) SetAuthor(v string) {\n\tx.xxx_hidden_Author = v\n}\n\ntype PluginInfo_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Type of plugin.\n\tType PluginInfo_PluginType\n\t// Name of the plugin.\n\tName string\n\t// Version of the plugin\n\tVersion string\n\t// Description of the plugin.\n\tDescription string\n\t// Author of the plugin.\n\tAuthor string\n}\n\nfunc (b0 PluginInfo_builder) Build() *PluginInfo {\n\tm0 := &PluginInfo{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Type = b.Type\n\tx.xxx_hidden_Name = b.Name\n\tx.xxx_hidden_Version = b.Version\n\tx.xxx_hidden_Description = b.Description\n\tx.xxx_hidden_Author = b.Author\n\treturn m0\n}\n\n// Represents a ForServiceName annotation placeholder used by the\n// PluginDefinition proto above.\ntype TargetServiceName struct {\n\tstate            protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Value []string               `protobuf:\"bytes,1,rep,name=value,proto3\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *TargetServiceName) Reset() {\n\t*x = TargetServiceName{}\n\tmi := &file_plugin_representation_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TargetServiceName) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TargetServiceName) ProtoMessage() {}\n\nfunc (x *TargetServiceName) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_representation_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *TargetServiceName) GetValue() []string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Value\n\t}\n\treturn nil\n}\n\nfunc (x *TargetServiceName) SetValue(v []string) {\n\tx.xxx_hidden_Value = v\n}\n\ntype TargetServiceName_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The value of the name of the target.\n\tValue []string\n}\n\nfunc (b0 TargetServiceName_builder) Build() *TargetServiceName {\n\tm0 := &TargetServiceName{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Value = b.Value\n\treturn m0\n}\n\n// Represents a ForSoftware annotation placeholder used by the\n// PluginDefinition proto above.\ntype TargetSoftware struct {\n\tstate            protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Name  string                 `protobuf:\"bytes,1,opt,name=name,proto3\"`\n\txxx_hidden_Value []string               `protobuf:\"bytes,2,rep,name=value,proto3\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *TargetSoftware) Reset() {\n\t*x = TargetSoftware{}\n\tmi := &file_plugin_representation_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TargetSoftware) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TargetSoftware) ProtoMessage() {}\n\nfunc (x *TargetSoftware) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_representation_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *TargetSoftware) GetName() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *TargetSoftware) GetValue() []string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Value\n\t}\n\treturn nil\n}\n\nfunc (x *TargetSoftware) SetName(v string) {\n\tx.xxx_hidden_Name = v\n}\n\nfunc (x *TargetSoftware) SetValue(v []string) {\n\tx.xxx_hidden_Value = v\n}\n\ntype TargetSoftware_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The name of the target software, case insensitive.\n\tName string\n\t// Array of versions and version ranges of the target software.\n\tValue []string\n}\n\nfunc (b0 TargetSoftware_builder) Build() *TargetSoftware {\n\tm0 := &TargetSoftware{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Name = b.Name\n\tx.xxx_hidden_Value = b.Value\n\treturn m0\n}\n\n// Represents a ForOperatingSystem annotation placeholder used by the\n// PluginDefinition proto above. These values are coming directly from the\n// port scanner's output (e.g. nmap).\ntype TargetOperatingSystemClass struct {\n\tstate                  protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Vendor      []string               `protobuf:\"bytes,1,rep,name=vendor,proto3\"`\n\txxx_hidden_OsFamily    []string               `protobuf:\"bytes,2,rep,name=os_family,json=osFamily,proto3\"`\n\txxx_hidden_MinAccuracy uint32                 `protobuf:\"varint,3,opt,name=min_accuracy,json=minAccuracy,proto3\"`\n\tunknownFields          protoimpl.UnknownFields\n\tsizeCache              protoimpl.SizeCache\n}\n\nfunc (x *TargetOperatingSystemClass) Reset() {\n\t*x = TargetOperatingSystemClass{}\n\tmi := &file_plugin_representation_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TargetOperatingSystemClass) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TargetOperatingSystemClass) ProtoMessage() {}\n\nfunc (x *TargetOperatingSystemClass) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_representation_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *TargetOperatingSystemClass) GetVendor() []string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Vendor\n\t}\n\treturn nil\n}\n\nfunc (x *TargetOperatingSystemClass) GetOsFamily() []string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_OsFamily\n\t}\n\treturn nil\n}\n\nfunc (x *TargetOperatingSystemClass) GetMinAccuracy() uint32 {\n\tif x != nil {\n\t\treturn x.xxx_hidden_MinAccuracy\n\t}\n\treturn 0\n}\n\nfunc (x *TargetOperatingSystemClass) SetVendor(v []string) {\n\tx.xxx_hidden_Vendor = v\n}\n\nfunc (x *TargetOperatingSystemClass) SetOsFamily(v []string) {\n\tx.xxx_hidden_OsFamily = v\n}\n\nfunc (x *TargetOperatingSystemClass) SetMinAccuracy(v uint32) {\n\tx.xxx_hidden_MinAccuracy = v\n}\n\ntype TargetOperatingSystemClass_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The vendor of the target operating system, e.g. \"Microsoft\"\n\tVendor []string\n\t// The family of the target operating system, e.g. \"Windows\"\n\tOsFamily []string\n\t// The minimum accuracy of the target operating system, e.g. 90\n\tMinAccuracy uint32\n}\n\nfunc (b0 TargetOperatingSystemClass_builder) Build() *TargetOperatingSystemClass {\n\tm0 := &TargetOperatingSystemClass{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Vendor = b.Vendor\n\tx.xxx_hidden_OsFamily = b.OsFamily\n\tx.xxx_hidden_MinAccuracy = b.MinAccuracy\n\treturn m0\n}\n\nvar File_plugin_representation_proto protoreflect.FileDescriptor\n\nconst file_plugin_representation_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1bplugin_representation.proto\\x12\\rtsunami.proto\\\"\\xf1\\x02\\n\" +\n\t\"\\x10PluginDefinition\\x12-\\n\" +\n\t\"\\x04info\\x18\\x01 \\x01(\\v2\\x19.tsunami.proto.PluginInfoR\\x04info\\x12P\\n\" +\n\t\"\\x13target_service_name\\x18\\x02 \\x01(\\v2 .tsunami.proto.TargetServiceNameR\\x11targetServiceName\\x12F\\n\" +\n\t\"\\x0ftarget_software\\x18\\x03 \\x01(\\v2\\x1d.tsunami.proto.TargetSoftwareR\\x0etargetSoftware\\x12&\\n\" +\n\t\"\\x0ffor_web_service\\x18\\x04 \\x01(\\bR\\rforWebService\\x12l\\n\" +\n\t\"\\x1dtarget_operating_system_class\\x18\\x05 \\x01(\\v2).tsunami.proto.TargetOperatingSystemClassR\\x1atargetOperatingSystemClass\\\"\\x95\\x02\\n\" +\n\t\"\\n\" +\n\t\"PluginInfo\\x128\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2$.tsunami.proto.PluginInfo.PluginTypeR\\x04type\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x02 \\x01(\\tR\\x04name\\x12\\x18\\n\" +\n\t\"\\aversion\\x18\\x03 \\x01(\\tR\\aversion\\x12 \\n\" +\n\t\"\\vdescription\\x18\\x04 \\x01(\\tR\\vdescription\\x12\\x16\\n\" +\n\t\"\\x06author\\x18\\x05 \\x01(\\tR\\x06author\\\"e\\n\" +\n\t\"\\n\" +\n\t\"PluginType\\x12\\x1b\\n\" +\n\t\"\\x17PLUGIN_TYPE_UNSPECIFIED\\x10\\x00\\x12\\r\\n\" +\n\t\"\\tPORT_SCAN\\x10\\x01\\x12\\x17\\n\" +\n\t\"\\x13SERVICE_FINGERPRINT\\x10\\x02\\x12\\x12\\n\" +\n\t\"\\x0eVULN_DETECTION\\x10\\x03\\\")\\n\" +\n\t\"\\x11TargetServiceName\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x01 \\x03(\\tR\\x05value\\\":\\n\" +\n\t\"\\x0eTargetSoftware\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x03(\\tR\\x05value\\\"t\\n\" +\n\t\"\\x1aTargetOperatingSystemClass\\x12\\x16\\n\" +\n\t\"\\x06vendor\\x18\\x01 \\x03(\\tR\\x06vendor\\x12\\x1b\\n\" +\n\t\"\\tos_family\\x18\\x02 \\x03(\\tR\\bosFamily\\x12!\\n\" +\n\t\"\\fmin_accuracy\\x18\\x03 \\x01(\\rR\\vminAccuracyB\\x8c\\x01\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x1aPluginRepresentationProtosP\\x01ZRgithub.com/google/tsunami-security-scanner/proto/go/plugin_representation_go_protob\\x06proto3\"\n\nvar file_plugin_representation_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_plugin_representation_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_plugin_representation_proto_goTypes = []any{\n\t(PluginInfo_PluginType)(0),         // 0: tsunami.proto.PluginInfo.PluginType\n\t(*PluginDefinition)(nil),           // 1: tsunami.proto.PluginDefinition\n\t(*PluginInfo)(nil),                 // 2: tsunami.proto.PluginInfo\n\t(*TargetServiceName)(nil),          // 3: tsunami.proto.TargetServiceName\n\t(*TargetSoftware)(nil),             // 4: tsunami.proto.TargetSoftware\n\t(*TargetOperatingSystemClass)(nil), // 5: tsunami.proto.TargetOperatingSystemClass\n}\nvar file_plugin_representation_proto_depIdxs = []int32{\n\t2, // 0: tsunami.proto.PluginDefinition.info:type_name -> tsunami.proto.PluginInfo\n\t3, // 1: tsunami.proto.PluginDefinition.target_service_name:type_name -> tsunami.proto.TargetServiceName\n\t4, // 2: tsunami.proto.PluginDefinition.target_software:type_name -> tsunami.proto.TargetSoftware\n\t5, // 3: tsunami.proto.PluginDefinition.target_operating_system_class:type_name -> tsunami.proto.TargetOperatingSystemClass\n\t0, // 4: tsunami.proto.PluginInfo.type:type_name -> tsunami.proto.PluginInfo.PluginType\n\t5, // [5:5] is the sub-list for method output_type\n\t5, // [5:5] is the sub-list for method input_type\n\t5, // [5:5] is the sub-list for extension type_name\n\t5, // [5:5] is the sub-list for extension extendee\n\t0, // [0:5] is the sub-list for field type_name\n}\n\nfunc init() { file_plugin_representation_proto_init() }\nfunc file_plugin_representation_proto_init() {\n\tif File_plugin_representation_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_plugin_representation_proto_rawDesc), len(file_plugin_representation_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_plugin_representation_proto_goTypes,\n\t\tDependencyIndexes: file_plugin_representation_proto_depIdxs,\n\t\tEnumInfos:         file_plugin_representation_proto_enumTypes,\n\t\tMessageInfos:      file_plugin_representation_proto_msgTypes,\n\t}.Build()\n\tFile_plugin_representation_proto = out.File\n\tfile_plugin_representation_proto_goTypes = nil\n\tfile_plugin_representation_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/plugin_service_go_proto/plugin_service.pb.go",
    "content": "//\n// Copyright 2022 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Model for the plugin RPC service protocol.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: plugin_service.proto\n\npackage plugin_service_go_proto\n\nimport (\n\tdetection_go_proto \"github.com/google/tsunami-security-scanner/proto/go/detection_go_proto\"\n\tnetwork_service_go_proto \"github.com/google/tsunami-security-scanner/proto/go/network_service_go_proto\"\n\tplugin_representation_go_proto \"github.com/google/tsunami-security-scanner/proto/go/plugin_representation_go_proto\"\n\treconnaissance_go_proto \"github.com/google/tsunami-security-scanner/proto/go/reconnaissance_go_proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Represents a run request with all matched plugins that will need to run\n// as well as the target to run against.\ntype RunRequest struct {\n\tstate              protoimpl.MessageState              `protogen:\"opaque.v1\"`\n\txxx_hidden_Target  *reconnaissance_go_proto.TargetInfo `protobuf:\"bytes,1,opt,name=target,proto3\"`\n\txxx_hidden_Plugins *[]*MatchedPlugin                   `protobuf:\"bytes,2,rep,name=plugins,proto3\"`\n\tunknownFields      protoimpl.UnknownFields\n\tsizeCache          protoimpl.SizeCache\n}\n\nfunc (x *RunRequest) Reset() {\n\t*x = RunRequest{}\n\tmi := &file_plugin_service_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RunRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RunRequest) ProtoMessage() {}\n\nfunc (x *RunRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *RunRequest) GetTarget() *reconnaissance_go_proto.TargetInfo {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Target\n\t}\n\treturn nil\n}\n\nfunc (x *RunRequest) GetPlugins() []*MatchedPlugin {\n\tif x != nil {\n\t\tif x.xxx_hidden_Plugins != nil {\n\t\t\treturn *x.xxx_hidden_Plugins\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *RunRequest) SetTarget(v *reconnaissance_go_proto.TargetInfo) {\n\tx.xxx_hidden_Target = v\n}\n\nfunc (x *RunRequest) SetPlugins(v []*MatchedPlugin) {\n\tx.xxx_hidden_Plugins = &v\n}\n\nfunc (x *RunRequest) HasTarget() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Target != nil\n}\n\nfunc (x *RunRequest) ClearTarget() {\n\tx.xxx_hidden_Target = nil\n}\n\ntype RunRequest_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Target of the plugins.\n\tTarget *reconnaissance_go_proto.TargetInfo\n\t// All matched plugins that will need to run.\n\tPlugins []*MatchedPlugin\n}\n\nfunc (b0 RunRequest_builder) Build() *RunRequest {\n\tm0 := &RunRequest{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Target = b.Target\n\tx.xxx_hidden_Plugins = &b.Plugins\n\treturn m0\n}\n\n// Compact representation of RunRequest.\ntype RunCompactRequest struct {\n\tstate                  protoimpl.MessageState                              `protogen:\"opaque.v1\"`\n\txxx_hidden_Target      *reconnaissance_go_proto.TargetInfo                 `protobuf:\"bytes,1,opt,name=target,proto3\"`\n\txxx_hidden_Services    *[]*network_service_go_proto.NetworkService         `protobuf:\"bytes,2,rep,name=services,proto3\"`\n\txxx_hidden_Plugins     *[]*plugin_representation_go_proto.PluginDefinition `protobuf:\"bytes,3,rep,name=plugins,proto3\"`\n\txxx_hidden_ScanTargets *[]*RunCompactRequest_PluginNetworkServiceTarget    `protobuf:\"bytes,4,rep,name=scan_targets,json=scanTargets,proto3\"`\n\tunknownFields          protoimpl.UnknownFields\n\tsizeCache              protoimpl.SizeCache\n}\n\nfunc (x *RunCompactRequest) Reset() {\n\t*x = RunCompactRequest{}\n\tmi := &file_plugin_service_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RunCompactRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RunCompactRequest) ProtoMessage() {}\n\nfunc (x *RunCompactRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *RunCompactRequest) GetTarget() *reconnaissance_go_proto.TargetInfo {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Target\n\t}\n\treturn nil\n}\n\nfunc (x *RunCompactRequest) GetServices() []*network_service_go_proto.NetworkService {\n\tif x != nil {\n\t\tif x.xxx_hidden_Services != nil {\n\t\t\treturn *x.xxx_hidden_Services\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *RunCompactRequest) GetPlugins() []*plugin_representation_go_proto.PluginDefinition {\n\tif x != nil {\n\t\tif x.xxx_hidden_Plugins != nil {\n\t\t\treturn *x.xxx_hidden_Plugins\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *RunCompactRequest) GetScanTargets() []*RunCompactRequest_PluginNetworkServiceTarget {\n\tif x != nil {\n\t\tif x.xxx_hidden_ScanTargets != nil {\n\t\t\treturn *x.xxx_hidden_ScanTargets\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *RunCompactRequest) SetTarget(v *reconnaissance_go_proto.TargetInfo) {\n\tx.xxx_hidden_Target = v\n}\n\nfunc (x *RunCompactRequest) SetServices(v []*network_service_go_proto.NetworkService) {\n\tx.xxx_hidden_Services = &v\n}\n\nfunc (x *RunCompactRequest) SetPlugins(v []*plugin_representation_go_proto.PluginDefinition) {\n\tx.xxx_hidden_Plugins = &v\n}\n\nfunc (x *RunCompactRequest) SetScanTargets(v []*RunCompactRequest_PluginNetworkServiceTarget) {\n\tx.xxx_hidden_ScanTargets = &v\n}\n\nfunc (x *RunCompactRequest) HasTarget() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Target != nil\n}\n\nfunc (x *RunCompactRequest) ClearTarget() {\n\tx.xxx_hidden_Target = nil\n}\n\ntype RunCompactRequest_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Target of the plugins.\n\tTarget *reconnaissance_go_proto.TargetInfo\n\t// All network services that are targeted by some of the plugins.\n\tServices []*network_service_go_proto.NetworkService\n\t// All plugins that should be executed during the run.\n\tPlugins []*plugin_representation_go_proto.PluginDefinition\n\t// The concrete map of plugin/network service pairs that should be scanned.\n\tScanTargets []*RunCompactRequest_PluginNetworkServiceTarget\n}\n\nfunc (b0 RunCompactRequest_builder) Build() *RunCompactRequest {\n\tm0 := &RunCompactRequest{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Target = b.Target\n\tx.xxx_hidden_Services = &b.Services\n\tx.xxx_hidden_Plugins = &b.Plugins\n\tx.xxx_hidden_ScanTargets = &b.ScanTargets\n\treturn m0\n}\n\n// Represents the plugin needed to run by the language-specific server\n// as well as all the matched network services for the plugin.\ntype MatchedPlugin struct {\n\tstate               protoimpl.MessageState                           `protogen:\"opaque.v1\"`\n\txxx_hidden_Services *[]*network_service_go_proto.NetworkService      `protobuf:\"bytes,1,rep,name=services,proto3\"`\n\txxx_hidden_Plugin   *plugin_representation_go_proto.PluginDefinition `protobuf:\"bytes,2,opt,name=plugin,proto3\"`\n\tunknownFields       protoimpl.UnknownFields\n\tsizeCache           protoimpl.SizeCache\n}\n\nfunc (x *MatchedPlugin) Reset() {\n\t*x = MatchedPlugin{}\n\tmi := &file_plugin_service_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *MatchedPlugin) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*MatchedPlugin) ProtoMessage() {}\n\nfunc (x *MatchedPlugin) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *MatchedPlugin) GetServices() []*network_service_go_proto.NetworkService {\n\tif x != nil {\n\t\tif x.xxx_hidden_Services != nil {\n\t\t\treturn *x.xxx_hidden_Services\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *MatchedPlugin) GetPlugin() *plugin_representation_go_proto.PluginDefinition {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Plugin\n\t}\n\treturn nil\n}\n\nfunc (x *MatchedPlugin) SetServices(v []*network_service_go_proto.NetworkService) {\n\tx.xxx_hidden_Services = &v\n}\n\nfunc (x *MatchedPlugin) SetPlugin(v *plugin_representation_go_proto.PluginDefinition) {\n\tx.xxx_hidden_Plugin = v\n}\n\nfunc (x *MatchedPlugin) HasPlugin() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Plugin != nil\n}\n\nfunc (x *MatchedPlugin) ClearPlugin() {\n\tx.xxx_hidden_Plugin = nil\n}\n\ntype MatchedPlugin_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// All matched network services from the reconnaissance report.\n\tServices []*network_service_go_proto.NetworkService\n\t// Plugin to run.\n\tPlugin *plugin_representation_go_proto.PluginDefinition\n}\n\nfunc (b0 MatchedPlugin_builder) Build() *MatchedPlugin {\n\tm0 := &MatchedPlugin{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Services = &b.Services\n\tx.xxx_hidden_Plugin = b.Plugin\n\treturn m0\n}\n\n// Represents a run response with the only field being all DetectionReports\n// generated by the language-specific server.\ntype RunResponse struct {\n\tstate              protoimpl.MessageState                  `protogen:\"opaque.v1\"`\n\txxx_hidden_Reports *detection_go_proto.DetectionReportList `protobuf:\"bytes,1,opt,name=reports,proto3\"`\n\tunknownFields      protoimpl.UnknownFields\n\tsizeCache          protoimpl.SizeCache\n}\n\nfunc (x *RunResponse) Reset() {\n\t*x = RunResponse{}\n\tmi := &file_plugin_service_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RunResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RunResponse) ProtoMessage() {}\n\nfunc (x *RunResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *RunResponse) GetReports() *detection_go_proto.DetectionReportList {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Reports\n\t}\n\treturn nil\n}\n\nfunc (x *RunResponse) SetReports(v *detection_go_proto.DetectionReportList) {\n\tx.xxx_hidden_Reports = v\n}\n\nfunc (x *RunResponse) HasReports() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Reports != nil\n}\n\nfunc (x *RunResponse) ClearReports() {\n\tx.xxx_hidden_Reports = nil\n}\n\ntype RunResponse_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tReports *detection_go_proto.DetectionReportList\n}\n\nfunc (b0 RunResponse_builder) Build() *RunResponse {\n\tm0 := &RunResponse{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Reports = b.Reports\n\treturn m0\n}\n\n// Represents a request to list all plugins from the requested server.\ntype ListPluginsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"opaque.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListPluginsRequest) Reset() {\n\t*x = ListPluginsRequest{}\n\tmi := &file_plugin_service_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListPluginsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListPluginsRequest) ProtoMessage() {}\n\nfunc (x *ListPluginsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\ntype ListPluginsRequest_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n}\n\nfunc (b0 ListPluginsRequest_builder) Build() *ListPluginsRequest {\n\tm0 := &ListPluginsRequest{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\treturn m0\n}\n\n// Represents a response containing a list of all plugins\n// from the requested server.\ntype ListPluginsResponse struct {\n\tstate                            protoimpl.MessageState                              `protogen:\"opaque.v1\"`\n\txxx_hidden_Plugins               *[]*plugin_representation_go_proto.PluginDefinition `protobuf:\"bytes,1,rep,name=plugins,proto3\"`\n\txxx_hidden_WantCompactRunRequest bool                                                `protobuf:\"varint,2,opt,name=want_compact_run_request,json=wantCompactRunRequest,proto3\"`\n\tunknownFields                    protoimpl.UnknownFields\n\tsizeCache                        protoimpl.SizeCache\n}\n\nfunc (x *ListPluginsResponse) Reset() {\n\t*x = ListPluginsResponse{}\n\tmi := &file_plugin_service_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListPluginsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListPluginsResponse) ProtoMessage() {}\n\nfunc (x *ListPluginsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *ListPluginsResponse) GetPlugins() []*plugin_representation_go_proto.PluginDefinition {\n\tif x != nil {\n\t\tif x.xxx_hidden_Plugins != nil {\n\t\t\treturn *x.xxx_hidden_Plugins\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *ListPluginsResponse) GetWantCompactRunRequest() bool {\n\tif x != nil {\n\t\treturn x.xxx_hidden_WantCompactRunRequest\n\t}\n\treturn false\n}\n\nfunc (x *ListPluginsResponse) SetPlugins(v []*plugin_representation_go_proto.PluginDefinition) {\n\tx.xxx_hidden_Plugins = &v\n}\n\nfunc (x *ListPluginsResponse) SetWantCompactRunRequest(v bool) {\n\tx.xxx_hidden_WantCompactRunRequest = v\n}\n\ntype ListPluginsResponse_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tPlugins []*plugin_representation_go_proto.PluginDefinition\n\t// Plugin service can indicate here that it RunRequest should be compact\n\t// (compact_targets should be populated instead of MatchedPlugin plugins).\n\tWantCompactRunRequest bool\n}\n\nfunc (b0 ListPluginsResponse_builder) Build() *ListPluginsResponse {\n\tm0 := &ListPluginsResponse{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Plugins = &b.Plugins\n\tx.xxx_hidden_WantCompactRunRequest = b.WantCompactRunRequest\n\treturn m0\n}\n\n// Indexes in the following structure point to the services/plugins defined\n// below. (The order is safe, guaranteed by the proto specification: \"The\n// order of the elements with respect to each other is preserved when parsing,\n// though the ordering with respect to other fields is lost.\")\ntype RunCompactRequest_PluginNetworkServiceTarget struct {\n\tstate                   protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_PluginIndex  uint32                 `protobuf:\"varint,1,opt,name=plugin_index,json=pluginIndex,proto3\"`\n\txxx_hidden_ServiceIndex uint32                 `protobuf:\"varint,2,opt,name=service_index,json=serviceIndex,proto3\"`\n\tunknownFields           protoimpl.UnknownFields\n\tsizeCache               protoimpl.SizeCache\n}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) Reset() {\n\t*x = RunCompactRequest_PluginNetworkServiceTarget{}\n\tmi := &file_plugin_service_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RunCompactRequest_PluginNetworkServiceTarget) ProtoMessage() {}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) GetPluginIndex() uint32 {\n\tif x != nil {\n\t\treturn x.xxx_hidden_PluginIndex\n\t}\n\treturn 0\n}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) GetServiceIndex() uint32 {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ServiceIndex\n\t}\n\treturn 0\n}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) SetPluginIndex(v uint32) {\n\tx.xxx_hidden_PluginIndex = v\n}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) SetServiceIndex(v uint32) {\n\tx.xxx_hidden_ServiceIndex = v\n}\n\ntype RunCompactRequest_PluginNetworkServiceTarget_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The index of the plugin to run.\n\tPluginIndex uint32\n\t// The index of the network service to run against.\n\tServiceIndex uint32\n}\n\nfunc (b0 RunCompactRequest_PluginNetworkServiceTarget_builder) Build() *RunCompactRequest_PluginNetworkServiceTarget {\n\tm0 := &RunCompactRequest_PluginNetworkServiceTarget{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_PluginIndex = b.PluginIndex\n\tx.xxx_hidden_ServiceIndex = b.ServiceIndex\n\treturn m0\n}\n\nvar File_plugin_service_proto protoreflect.FileDescriptor\n\nconst file_plugin_service_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x14plugin_service.proto\\x12\\rtsunami.proto\\x1a\\x0fdetection.proto\\x1a\\x15network_service.proto\\x1a\\x1bplugin_representation.proto\\x1a\\x14reconnaissance.proto\\\"w\\n\" +\n\t\"\\n\" +\n\t\"RunRequest\\x121\\n\" +\n\t\"\\x06target\\x18\\x01 \\x01(\\v2\\x19.tsunami.proto.TargetInfoR\\x06target\\x126\\n\" +\n\t\"\\aplugins\\x18\\x02 \\x03(\\v2\\x1c.tsunami.proto.MatchedPluginR\\aplugins\\\"\\x82\\x03\\n\" +\n\t\"\\x11RunCompactRequest\\x121\\n\" +\n\t\"\\x06target\\x18\\x01 \\x01(\\v2\\x19.tsunami.proto.TargetInfoR\\x06target\\x129\\n\" +\n\t\"\\bservices\\x18\\x02 \\x03(\\v2\\x1d.tsunami.proto.NetworkServiceR\\bservices\\x129\\n\" +\n\t\"\\aplugins\\x18\\x03 \\x03(\\v2\\x1f.tsunami.proto.PluginDefinitionR\\aplugins\\x12^\\n\" +\n\t\"\\fscan_targets\\x18\\x04 \\x03(\\v2;.tsunami.proto.RunCompactRequest.PluginNetworkServiceTargetR\\vscanTargets\\x1ad\\n\" +\n\t\"\\x1aPluginNetworkServiceTarget\\x12!\\n\" +\n\t\"\\fplugin_index\\x18\\x01 \\x01(\\rR\\vpluginIndex\\x12#\\n\" +\n\t\"\\rservice_index\\x18\\x02 \\x01(\\rR\\fserviceIndex\\\"\\x83\\x01\\n\" +\n\t\"\\rMatchedPlugin\\x129\\n\" +\n\t\"\\bservices\\x18\\x01 \\x03(\\v2\\x1d.tsunami.proto.NetworkServiceR\\bservices\\x127\\n\" +\n\t\"\\x06plugin\\x18\\x02 \\x01(\\v2\\x1f.tsunami.proto.PluginDefinitionR\\x06plugin\\\"K\\n\" +\n\t\"\\vRunResponse\\x12<\\n\" +\n\t\"\\areports\\x18\\x01 \\x01(\\v2\\\".tsunami.proto.DetectionReportListR\\areports\\\"\\x14\\n\" +\n\t\"\\x12ListPluginsRequest\\\"\\x89\\x01\\n\" +\n\t\"\\x13ListPluginsResponse\\x129\\n\" +\n\t\"\\aplugins\\x18\\x01 \\x03(\\v2\\x1f.tsunami.proto.PluginDefinitionR\\aplugins\\x127\\n\" +\n\t\"\\x18want_compact_run_request\\x18\\x02 \\x01(\\bR\\x15wantCompactRunRequest2\\xf5\\x01\\n\" +\n\t\"\\rPluginService\\x12>\\n\" +\n\t\"\\x03Run\\x12\\x19.tsunami.proto.RunRequest\\x1a\\x1a.tsunami.proto.RunResponse\\\"\\x00\\x12L\\n\" +\n\t\"\\n\" +\n\t\"RunCompact\\x12 .tsunami.proto.RunCompactRequest\\x1a\\x1a.tsunami.proto.RunResponse\\\"\\x00\\x12V\\n\" +\n\t\"\\vListPlugins\\x12!.tsunami.proto.ListPluginsRequest\\x1a\\\".tsunami.proto.ListPluginsResponse\\\"\\x00B~\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x13PluginServiceProtosP\\x01ZKgithub.com/google/tsunami-security-scanner/proto/go/plugin_service_go_protob\\x06proto3\"\n\nvar file_plugin_service_proto_msgTypes = make([]protoimpl.MessageInfo, 7)\nvar file_plugin_service_proto_goTypes = []any{\n\t(*RunRequest)(nil),                                      // 0: tsunami.proto.RunRequest\n\t(*RunCompactRequest)(nil),                               // 1: tsunami.proto.RunCompactRequest\n\t(*MatchedPlugin)(nil),                                   // 2: tsunami.proto.MatchedPlugin\n\t(*RunResponse)(nil),                                     // 3: tsunami.proto.RunResponse\n\t(*ListPluginsRequest)(nil),                              // 4: tsunami.proto.ListPluginsRequest\n\t(*ListPluginsResponse)(nil),                             // 5: tsunami.proto.ListPluginsResponse\n\t(*RunCompactRequest_PluginNetworkServiceTarget)(nil),    // 6: tsunami.proto.RunCompactRequest.PluginNetworkServiceTarget\n\t(*reconnaissance_go_proto.TargetInfo)(nil),              // 7: tsunami.proto.TargetInfo\n\t(*network_service_go_proto.NetworkService)(nil),         // 8: tsunami.proto.NetworkService\n\t(*plugin_representation_go_proto.PluginDefinition)(nil), // 9: tsunami.proto.PluginDefinition\n\t(*detection_go_proto.DetectionReportList)(nil),          // 10: tsunami.proto.DetectionReportList\n}\nvar file_plugin_service_proto_depIdxs = []int32{\n\t7,  // 0: tsunami.proto.RunRequest.target:type_name -> tsunami.proto.TargetInfo\n\t2,  // 1: tsunami.proto.RunRequest.plugins:type_name -> tsunami.proto.MatchedPlugin\n\t7,  // 2: tsunami.proto.RunCompactRequest.target:type_name -> tsunami.proto.TargetInfo\n\t8,  // 3: tsunami.proto.RunCompactRequest.services:type_name -> tsunami.proto.NetworkService\n\t9,  // 4: tsunami.proto.RunCompactRequest.plugins:type_name -> tsunami.proto.PluginDefinition\n\t6,  // 5: tsunami.proto.RunCompactRequest.scan_targets:type_name -> tsunami.proto.RunCompactRequest.PluginNetworkServiceTarget\n\t8,  // 6: tsunami.proto.MatchedPlugin.services:type_name -> tsunami.proto.NetworkService\n\t9,  // 7: tsunami.proto.MatchedPlugin.plugin:type_name -> tsunami.proto.PluginDefinition\n\t10, // 8: tsunami.proto.RunResponse.reports:type_name -> tsunami.proto.DetectionReportList\n\t9,  // 9: tsunami.proto.ListPluginsResponse.plugins:type_name -> tsunami.proto.PluginDefinition\n\t0,  // 10: tsunami.proto.PluginService.Run:input_type -> tsunami.proto.RunRequest\n\t1,  // 11: tsunami.proto.PluginService.RunCompact:input_type -> tsunami.proto.RunCompactRequest\n\t4,  // 12: tsunami.proto.PluginService.ListPlugins:input_type -> tsunami.proto.ListPluginsRequest\n\t3,  // 13: tsunami.proto.PluginService.Run:output_type -> tsunami.proto.RunResponse\n\t3,  // 14: tsunami.proto.PluginService.RunCompact:output_type -> tsunami.proto.RunResponse\n\t5,  // 15: tsunami.proto.PluginService.ListPlugins:output_type -> tsunami.proto.ListPluginsResponse\n\t13, // [13:16] is the sub-list for method output_type\n\t10, // [10:13] is the sub-list for method input_type\n\t10, // [10:10] is the sub-list for extension type_name\n\t10, // [10:10] is the sub-list for extension extendee\n\t0,  // [0:10] is the sub-list for field type_name\n}\n\nfunc init() { file_plugin_service_proto_init() }\nfunc file_plugin_service_proto_init() {\n\tif File_plugin_service_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_plugin_service_proto_rawDesc), len(file_plugin_service_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   7,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_plugin_service_proto_goTypes,\n\t\tDependencyIndexes: file_plugin_service_proto_depIdxs,\n\t\tMessageInfos:      file_plugin_service_proto_msgTypes,\n\t}.Build()\n\tFile_plugin_service_proto = out.File\n\tfile_plugin_service_proto_goTypes = nil\n\tfile_plugin_service_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/reconnaissance_go_proto/reconnaissance.pb.go",
    "content": "//\n// Copyright 2019 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for all the reconnaissance information gathered by Tsunami.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: reconnaissance.proto\n\npackage reconnaissance_go_proto\n\nimport (\n\tnetwork_go_proto \"github.com/google/tsunami-security-scanner/proto/go/network_go_proto\"\n\tnetwork_service_go_proto \"github.com/google/tsunami-security-scanner/proto/go/network_service_go_proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Detailed information about the scanning target.\ntype TargetInfo struct {\n\tstate                             protoimpl.MessageState               `protogen:\"opaque.v1\"`\n\txxx_hidden_NetworkEndpoints       *[]*network_go_proto.NetworkEndpoint `protobuf:\"bytes,1,rep,name=network_endpoints,json=networkEndpoints,proto3\"`\n\txxx_hidden_OperatingSystemClasses *[]*OperatingSystemClass             `protobuf:\"bytes,2,rep,name=operating_system_classes,json=operatingSystemClasses,proto3\"`\n\tunknownFields                     protoimpl.UnknownFields\n\tsizeCache                         protoimpl.SizeCache\n}\n\nfunc (x *TargetInfo) Reset() {\n\t*x = TargetInfo{}\n\tmi := &file_reconnaissance_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TargetInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TargetInfo) ProtoMessage() {}\n\nfunc (x *TargetInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_reconnaissance_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *TargetInfo) GetNetworkEndpoints() []*network_go_proto.NetworkEndpoint {\n\tif x != nil {\n\t\tif x.xxx_hidden_NetworkEndpoints != nil {\n\t\t\treturn *x.xxx_hidden_NetworkEndpoints\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *TargetInfo) GetOperatingSystemClasses() []*OperatingSystemClass {\n\tif x != nil {\n\t\tif x.xxx_hidden_OperatingSystemClasses != nil {\n\t\t\treturn *x.xxx_hidden_OperatingSystemClasses\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *TargetInfo) SetNetworkEndpoints(v []*network_go_proto.NetworkEndpoint) {\n\tx.xxx_hidden_NetworkEndpoints = &v\n}\n\nfunc (x *TargetInfo) SetOperatingSystemClasses(v []*OperatingSystemClass) {\n\tx.xxx_hidden_OperatingSystemClasses = &v\n}\n\ntype TargetInfo_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// All the known network endpoints of the scanning target.\n\tNetworkEndpoints       []*network_go_proto.NetworkEndpoint\n\tOperatingSystemClasses []*OperatingSystemClass\n}\n\nfunc (b0 TargetInfo_builder) Build() *TargetInfo {\n\tm0 := &TargetInfo{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_NetworkEndpoints = &b.NetworkEndpoints\n\tx.xxx_hidden_OperatingSystemClasses = &b.OperatingSystemClasses\n\treturn m0\n}\n\n// Represents a ForOperatingSystem annotation placeholder used by the\n// PluginDefinition proto above.\n// For possible values, consult the following database:\n// https://raw.githubusercontent.com/nmap/nmap/master/nmap-os-db\ntype OperatingSystemClass struct {\n\tstate                   protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Type         string                 `protobuf:\"bytes,1,opt,name=type,proto3\"`\n\txxx_hidden_Vendor       string                 `protobuf:\"bytes,2,opt,name=vendor,proto3\"`\n\txxx_hidden_OsFamily     string                 `protobuf:\"bytes,3,opt,name=os_family,json=osFamily,proto3\"`\n\txxx_hidden_OsGeneration string                 `protobuf:\"bytes,4,opt,name=os_generation,json=osGeneration,proto3\"`\n\txxx_hidden_Accuracy     uint32                 `protobuf:\"varint,5,opt,name=accuracy,proto3\"`\n\tunknownFields           protoimpl.UnknownFields\n\tsizeCache               protoimpl.SizeCache\n}\n\nfunc (x *OperatingSystemClass) Reset() {\n\t*x = OperatingSystemClass{}\n\tmi := &file_reconnaissance_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *OperatingSystemClass) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OperatingSystemClass) ProtoMessage() {}\n\nfunc (x *OperatingSystemClass) ProtoReflect() protoreflect.Message {\n\tmi := &file_reconnaissance_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *OperatingSystemClass) GetType() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *OperatingSystemClass) GetVendor() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Vendor\n\t}\n\treturn \"\"\n}\n\nfunc (x *OperatingSystemClass) GetOsFamily() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_OsFamily\n\t}\n\treturn \"\"\n}\n\nfunc (x *OperatingSystemClass) GetOsGeneration() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_OsGeneration\n\t}\n\treturn \"\"\n}\n\nfunc (x *OperatingSystemClass) GetAccuracy() uint32 {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Accuracy\n\t}\n\treturn 0\n}\n\nfunc (x *OperatingSystemClass) SetType(v string) {\n\tx.xxx_hidden_Type = v\n}\n\nfunc (x *OperatingSystemClass) SetVendor(v string) {\n\tx.xxx_hidden_Vendor = v\n}\n\nfunc (x *OperatingSystemClass) SetOsFamily(v string) {\n\tx.xxx_hidden_OsFamily = v\n}\n\nfunc (x *OperatingSystemClass) SetOsGeneration(v string) {\n\tx.xxx_hidden_OsGeneration = v\n}\n\nfunc (x *OperatingSystemClass) SetAccuracy(v uint32) {\n\tx.xxx_hidden_Accuracy = v\n}\n\ntype OperatingSystemClass_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The type of the target operating system, e.g. \"general purpose\"\n\tType string\n\t// The vendor of the target operating system, e.g. \"Linux\"\n\tVendor string\n\t// The family of the target operating system, e.g. \"Linux\"\n\tOsFamily string\n\t// The generation of the target operating system, e.g. \"2.6.X\"\n\tOsGeneration string\n\t// The estimated accuracy of the target operating system, e.g. 90\n\tAccuracy uint32\n}\n\nfunc (b0 OperatingSystemClass_builder) Build() *OperatingSystemClass {\n\tm0 := &OperatingSystemClass{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Type = b.Type\n\tx.xxx_hidden_Vendor = b.Vendor\n\tx.xxx_hidden_OsFamily = b.OsFamily\n\tx.xxx_hidden_OsGeneration = b.OsGeneration\n\tx.xxx_hidden_Accuracy = b.Accuracy\n\treturn m0\n}\n\n// Report from a port scanner.\ntype PortScanningReport struct {\n\tstate                      protoimpl.MessageState                      `protogen:\"opaque.v1\"`\n\txxx_hidden_TargetInfo      *TargetInfo                                 `protobuf:\"bytes,1,opt,name=target_info,json=targetInfo,proto3\"`\n\txxx_hidden_NetworkServices *[]*network_service_go_proto.NetworkService `protobuf:\"bytes,2,rep,name=network_services,json=networkServices,proto3\"`\n\tunknownFields              protoimpl.UnknownFields\n\tsizeCache                  protoimpl.SizeCache\n}\n\nfunc (x *PortScanningReport) Reset() {\n\t*x = PortScanningReport{}\n\tmi := &file_reconnaissance_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PortScanningReport) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PortScanningReport) ProtoMessage() {}\n\nfunc (x *PortScanningReport) ProtoReflect() protoreflect.Message {\n\tmi := &file_reconnaissance_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *PortScanningReport) GetTargetInfo() *TargetInfo {\n\tif x != nil {\n\t\treturn x.xxx_hidden_TargetInfo\n\t}\n\treturn nil\n}\n\nfunc (x *PortScanningReport) GetNetworkServices() []*network_service_go_proto.NetworkService {\n\tif x != nil {\n\t\tif x.xxx_hidden_NetworkServices != nil {\n\t\t\treturn *x.xxx_hidden_NetworkServices\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *PortScanningReport) SetTargetInfo(v *TargetInfo) {\n\tx.xxx_hidden_TargetInfo = v\n}\n\nfunc (x *PortScanningReport) SetNetworkServices(v []*network_service_go_proto.NetworkService) {\n\tx.xxx_hidden_NetworkServices = &v\n}\n\nfunc (x *PortScanningReport) HasTargetInfo() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_TargetInfo != nil\n}\n\nfunc (x *PortScanningReport) ClearTargetInfo() {\n\tx.xxx_hidden_TargetInfo = nil\n}\n\ntype PortScanningReport_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Information about the scanning target.\n\tTargetInfo *TargetInfo\n\t// List of all the exposed network services.\n\tNetworkServices []*network_service_go_proto.NetworkService\n}\n\nfunc (b0 PortScanningReport_builder) Build() *PortScanningReport {\n\tm0 := &PortScanningReport{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_TargetInfo = b.TargetInfo\n\tx.xxx_hidden_NetworkServices = &b.NetworkServices\n\treturn m0\n}\n\n// Report from a service fingerprinter.\ntype FingerprintingReport struct {\n\tstate                      protoimpl.MessageState                      `protogen:\"opaque.v1\"`\n\txxx_hidden_NetworkServices *[]*network_service_go_proto.NetworkService `protobuf:\"bytes,3,rep,name=network_services,json=networkServices,proto3\"`\n\tunknownFields              protoimpl.UnknownFields\n\tsizeCache                  protoimpl.SizeCache\n}\n\nfunc (x *FingerprintingReport) Reset() {\n\t*x = FingerprintingReport{}\n\tmi := &file_reconnaissance_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *FingerprintingReport) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*FingerprintingReport) ProtoMessage() {}\n\nfunc (x *FingerprintingReport) ProtoReflect() protoreflect.Message {\n\tmi := &file_reconnaissance_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *FingerprintingReport) GetNetworkServices() []*network_service_go_proto.NetworkService {\n\tif x != nil {\n\t\tif x.xxx_hidden_NetworkServices != nil {\n\t\t\treturn *x.xxx_hidden_NetworkServices\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *FingerprintingReport) SetNetworkServices(v []*network_service_go_proto.NetworkService) {\n\tx.xxx_hidden_NetworkServices = &v\n}\n\ntype FingerprintingReport_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// List of all the identified network services after fingerprinting.\n\tNetworkServices []*network_service_go_proto.NetworkService\n}\n\nfunc (b0 FingerprintingReport_builder) Build() *FingerprintingReport {\n\tm0 := &FingerprintingReport{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_NetworkServices = &b.NetworkServices\n\treturn m0\n}\n\n// Full reconnaissance report about a single scanning target.\ntype ReconnaissanceReport struct {\n\tstate                      protoimpl.MessageState                      `protogen:\"opaque.v1\"`\n\txxx_hidden_TargetInfo      *TargetInfo                                 `protobuf:\"bytes,1,opt,name=target_info,json=targetInfo,proto3\"`\n\txxx_hidden_NetworkServices *[]*network_service_go_proto.NetworkService `protobuf:\"bytes,2,rep,name=network_services,json=networkServices,proto3\"`\n\tunknownFields              protoimpl.UnknownFields\n\tsizeCache                  protoimpl.SizeCache\n}\n\nfunc (x *ReconnaissanceReport) Reset() {\n\t*x = ReconnaissanceReport{}\n\tmi := &file_reconnaissance_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReconnaissanceReport) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReconnaissanceReport) ProtoMessage() {}\n\nfunc (x *ReconnaissanceReport) ProtoReflect() protoreflect.Message {\n\tmi := &file_reconnaissance_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *ReconnaissanceReport) GetTargetInfo() *TargetInfo {\n\tif x != nil {\n\t\treturn x.xxx_hidden_TargetInfo\n\t}\n\treturn nil\n}\n\nfunc (x *ReconnaissanceReport) GetNetworkServices() []*network_service_go_proto.NetworkService {\n\tif x != nil {\n\t\tif x.xxx_hidden_NetworkServices != nil {\n\t\t\treturn *x.xxx_hidden_NetworkServices\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *ReconnaissanceReport) SetTargetInfo(v *TargetInfo) {\n\tx.xxx_hidden_TargetInfo = v\n}\n\nfunc (x *ReconnaissanceReport) SetNetworkServices(v []*network_service_go_proto.NetworkService) {\n\tx.xxx_hidden_NetworkServices = &v\n}\n\nfunc (x *ReconnaissanceReport) HasTargetInfo() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_TargetInfo != nil\n}\n\nfunc (x *ReconnaissanceReport) ClearTargetInfo() {\n\tx.xxx_hidden_TargetInfo = nil\n}\n\ntype ReconnaissanceReport_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Information about the scanning target.\n\tTargetInfo *TargetInfo\n\t// All exposed network services of the scanning target.\n\tNetworkServices []*network_service_go_proto.NetworkService\n}\n\nfunc (b0 ReconnaissanceReport_builder) Build() *ReconnaissanceReport {\n\tm0 := &ReconnaissanceReport{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_TargetInfo = b.TargetInfo\n\tx.xxx_hidden_NetworkServices = &b.NetworkServices\n\treturn m0\n}\n\nvar File_reconnaissance_proto protoreflect.FileDescriptor\n\nconst file_reconnaissance_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x14reconnaissance.proto\\x12\\rtsunami.proto\\x1a\\rnetwork.proto\\x1a\\x15network_service.proto\\\"\\xb8\\x01\\n\" +\n\t\"\\n\" +\n\t\"TargetInfo\\x12K\\n\" +\n\t\"\\x11network_endpoints\\x18\\x01 \\x03(\\v2\\x1e.tsunami.proto.NetworkEndpointR\\x10networkEndpoints\\x12]\\n\" +\n\t\"\\x18operating_system_classes\\x18\\x02 \\x03(\\v2#.tsunami.proto.OperatingSystemClassR\\x16operatingSystemClasses\\\"\\xa0\\x01\\n\" +\n\t\"\\x14OperatingSystemClass\\x12\\x12\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\tR\\x04type\\x12\\x16\\n\" +\n\t\"\\x06vendor\\x18\\x02 \\x01(\\tR\\x06vendor\\x12\\x1b\\n\" +\n\t\"\\tos_family\\x18\\x03 \\x01(\\tR\\bosFamily\\x12#\\n\" +\n\t\"\\ros_generation\\x18\\x04 \\x01(\\tR\\fosGeneration\\x12\\x1a\\n\" +\n\t\"\\baccuracy\\x18\\x05 \\x01(\\rR\\baccuracy\\\"\\x9a\\x01\\n\" +\n\t\"\\x12PortScanningReport\\x12:\\n\" +\n\t\"\\vtarget_info\\x18\\x01 \\x01(\\v2\\x19.tsunami.proto.TargetInfoR\\n\" +\n\t\"targetInfo\\x12H\\n\" +\n\t\"\\x10network_services\\x18\\x02 \\x03(\\v2\\x1d.tsunami.proto.NetworkServiceR\\x0fnetworkServices\\\"`\\n\" +\n\t\"\\x14FingerprintingReport\\x12H\\n\" +\n\t\"\\x10network_services\\x18\\x03 \\x03(\\v2\\x1d.tsunami.proto.NetworkServiceR\\x0fnetworkServices\\\"\\x9c\\x01\\n\" +\n\t\"\\x14ReconnaissanceReport\\x12:\\n\" +\n\t\"\\vtarget_info\\x18\\x01 \\x01(\\v2\\x19.tsunami.proto.TargetInfoR\\n\" +\n\t\"targetInfo\\x12H\\n\" +\n\t\"\\x10network_services\\x18\\x02 \\x03(\\v2\\x1d.tsunami.proto.NetworkServiceR\\x0fnetworkServicesB\\x7f\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x14ReconnaissanceProtosP\\x01ZKgithub.com/google/tsunami-security-scanner/proto/go/reconnaissance_go_protob\\x06proto3\"\n\nvar file_reconnaissance_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_reconnaissance_proto_goTypes = []any{\n\t(*TargetInfo)(nil),                              // 0: tsunami.proto.TargetInfo\n\t(*OperatingSystemClass)(nil),                    // 1: tsunami.proto.OperatingSystemClass\n\t(*PortScanningReport)(nil),                      // 2: tsunami.proto.PortScanningReport\n\t(*FingerprintingReport)(nil),                    // 3: tsunami.proto.FingerprintingReport\n\t(*ReconnaissanceReport)(nil),                    // 4: tsunami.proto.ReconnaissanceReport\n\t(*network_go_proto.NetworkEndpoint)(nil),        // 5: tsunami.proto.NetworkEndpoint\n\t(*network_service_go_proto.NetworkService)(nil), // 6: tsunami.proto.NetworkService\n}\nvar file_reconnaissance_proto_depIdxs = []int32{\n\t5, // 0: tsunami.proto.TargetInfo.network_endpoints:type_name -> tsunami.proto.NetworkEndpoint\n\t1, // 1: tsunami.proto.TargetInfo.operating_system_classes:type_name -> tsunami.proto.OperatingSystemClass\n\t0, // 2: tsunami.proto.PortScanningReport.target_info:type_name -> tsunami.proto.TargetInfo\n\t6, // 3: tsunami.proto.PortScanningReport.network_services:type_name -> tsunami.proto.NetworkService\n\t6, // 4: tsunami.proto.FingerprintingReport.network_services:type_name -> tsunami.proto.NetworkService\n\t0, // 5: tsunami.proto.ReconnaissanceReport.target_info:type_name -> tsunami.proto.TargetInfo\n\t6, // 6: tsunami.proto.ReconnaissanceReport.network_services:type_name -> tsunami.proto.NetworkService\n\t7, // [7:7] is the sub-list for method output_type\n\t7, // [7:7] is the sub-list for method input_type\n\t7, // [7:7] is the sub-list for extension type_name\n\t7, // [7:7] is the sub-list for extension extendee\n\t0, // [0:7] is the sub-list for field type_name\n}\n\nfunc init() { file_reconnaissance_proto_init() }\nfunc file_reconnaissance_proto_init() {\n\tif File_reconnaissance_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_reconnaissance_proto_rawDesc), len(file_reconnaissance_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_reconnaissance_proto_goTypes,\n\t\tDependencyIndexes: file_reconnaissance_proto_depIdxs,\n\t\tMessageInfos:      file_reconnaissance_proto_msgTypes,\n\t}.Build()\n\tFile_reconnaissance_proto = out.File\n\tfile_reconnaissance_proto_goTypes = nil\n\tfile_reconnaissance_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/scan_results_go_proto/scan_results.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing scanning results.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: scan_results.proto\n\npackage scan_results_go_proto\n\nimport (\n\tdetection_go_proto \"github.com/google/tsunami-security-scanner/proto/go/detection_go_proto\"\n\tnetwork_service_go_proto \"github.com/google/tsunami-security-scanner/proto/go/network_service_go_proto\"\n\treconnaissance_go_proto \"github.com/google/tsunami-security-scanner/proto/go/reconnaissance_go_proto\"\n\tvulnerability_go_proto \"github.com/google/tsunami-security-scanner/proto/go/vulnerability_go_proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\tdurationpb \"google.golang.org/protobuf/types/known/durationpb\"\n\ttimestamppb \"google.golang.org/protobuf/types/known/timestamppb\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Execution status of the scan.\n// NEXT ID: 5\ntype ScanStatus int32\n\nconst (\n\t// Unspecified status.\n\tScanStatus_SCAN_STATUS_UNSPECIFIED ScanStatus = 0\n\t// Scan finished successfully.\n\tScanStatus_SUCCEEDED ScanStatus = 1\n\t// Scan finished with only a small set of selected detectors succeeded.\n\tScanStatus_PARTIALLY_SUCCEEDED ScanStatus = 4\n\t// Scan failed.\n\tScanStatus_FAILED ScanStatus = 2\n\t// Scan cancelled.\n\tScanStatus_CANCELLED ScanStatus = 3\n)\n\n// Enum value maps for ScanStatus.\nvar (\n\tScanStatus_name = map[int32]string{\n\t\t0: \"SCAN_STATUS_UNSPECIFIED\",\n\t\t1: \"SUCCEEDED\",\n\t\t4: \"PARTIALLY_SUCCEEDED\",\n\t\t2: \"FAILED\",\n\t\t3: \"CANCELLED\",\n\t}\n\tScanStatus_value = map[string]int32{\n\t\t\"SCAN_STATUS_UNSPECIFIED\": 0,\n\t\t\"SUCCEEDED\":               1,\n\t\t\"PARTIALLY_SUCCEEDED\":     4,\n\t\t\"FAILED\":                  2,\n\t\t\"CANCELLED\":               3,\n\t}\n)\n\nfunc (x ScanStatus) Enum() *ScanStatus {\n\tp := new(ScanStatus)\n\t*p = x\n\treturn p\n}\n\nfunc (x ScanStatus) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (ScanStatus) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_scan_results_proto_enumTypes[0].Descriptor()\n}\n\nfunc (ScanStatus) Type() protoreflect.EnumType {\n\treturn &file_scan_results_proto_enumTypes[0]\n}\n\nfunc (x ScanStatus) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// A single vulnerability finding for a specific service.\ntype ScanFinding struct {\n\tstate                     protoimpl.MessageState                   `protogen:\"opaque.v1\"`\n\txxx_hidden_TargetInfo     *reconnaissance_go_proto.TargetInfo      `protobuf:\"bytes,1,opt,name=target_info,json=targetInfo,proto3\"`\n\txxx_hidden_NetworkService *network_service_go_proto.NetworkService `protobuf:\"bytes,2,opt,name=network_service,json=networkService,proto3\"`\n\txxx_hidden_Vulnerability  *vulnerability_go_proto.Vulnerability    `protobuf:\"bytes,3,opt,name=vulnerability,proto3\"`\n\tunknownFields             protoimpl.UnknownFields\n\tsizeCache                 protoimpl.SizeCache\n}\n\nfunc (x *ScanFinding) Reset() {\n\t*x = ScanFinding{}\n\tmi := &file_scan_results_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ScanFinding) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ScanFinding) ProtoMessage() {}\n\nfunc (x *ScanFinding) ProtoReflect() protoreflect.Message {\n\tmi := &file_scan_results_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *ScanFinding) GetTargetInfo() *reconnaissance_go_proto.TargetInfo {\n\tif x != nil {\n\t\treturn x.xxx_hidden_TargetInfo\n\t}\n\treturn nil\n}\n\nfunc (x *ScanFinding) GetNetworkService() *network_service_go_proto.NetworkService {\n\tif x != nil {\n\t\treturn x.xxx_hidden_NetworkService\n\t}\n\treturn nil\n}\n\nfunc (x *ScanFinding) GetVulnerability() *vulnerability_go_proto.Vulnerability {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Vulnerability\n\t}\n\treturn nil\n}\n\nfunc (x *ScanFinding) SetTargetInfo(v *reconnaissance_go_proto.TargetInfo) {\n\tx.xxx_hidden_TargetInfo = v\n}\n\nfunc (x *ScanFinding) SetNetworkService(v *network_service_go_proto.NetworkService) {\n\tx.xxx_hidden_NetworkService = v\n}\n\nfunc (x *ScanFinding) SetVulnerability(v *vulnerability_go_proto.Vulnerability) {\n\tx.xxx_hidden_Vulnerability = v\n}\n\nfunc (x *ScanFinding) HasTargetInfo() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_TargetInfo != nil\n}\n\nfunc (x *ScanFinding) HasNetworkService() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_NetworkService != nil\n}\n\nfunc (x *ScanFinding) HasVulnerability() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Vulnerability != nil\n}\n\nfunc (x *ScanFinding) ClearTargetInfo() {\n\tx.xxx_hidden_TargetInfo = nil\n}\n\nfunc (x *ScanFinding) ClearNetworkService() {\n\tx.xxx_hidden_NetworkService = nil\n}\n\nfunc (x *ScanFinding) ClearVulnerability() {\n\tx.xxx_hidden_Vulnerability = nil\n}\n\ntype ScanFinding_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Information about the scanned target.\n\tTargetInfo *reconnaissance_go_proto.TargetInfo\n\t// Information about the scanned network service.\n\tNetworkService *network_service_go_proto.NetworkService\n\t// Details about the detected vulnerability.\n\tVulnerability *vulnerability_go_proto.Vulnerability\n}\n\nfunc (b0 ScanFinding_builder) Build() *ScanFinding {\n\tm0 := &ScanFinding{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_TargetInfo = b.TargetInfo\n\tx.xxx_hidden_NetworkService = b.NetworkService\n\tx.xxx_hidden_Vulnerability = b.Vulnerability\n\treturn m0\n}\n\n// Full scanning results.\n// NEXT ID: 9\ntype ScanResults struct {\n\tstate                           protoimpl.MessageState                        `protogen:\"opaque.v1\"`\n\txxx_hidden_ScanStatus           ScanStatus                                    `protobuf:\"varint,1,opt,name=scan_status,json=scanStatus,proto3,enum=tsunami.proto.ScanStatus\"`\n\txxx_hidden_StatusMessage        string                                        `protobuf:\"bytes,6,opt,name=status_message,json=statusMessage,proto3\"`\n\txxx_hidden_TargetAlive          bool                                          `protobuf:\"varint,8,opt,name=target_alive,json=targetAlive,proto3\"`\n\txxx_hidden_ScanFindings         *[]*ScanFinding                               `protobuf:\"bytes,2,rep,name=scan_findings,json=scanFindings,proto3\"`\n\txxx_hidden_ScanStartTimestamp   *timestamppb.Timestamp                        `protobuf:\"bytes,3,opt,name=scan_start_timestamp,json=scanStartTimestamp,proto3\"`\n\txxx_hidden_ScanDuration         *durationpb.Duration                          `protobuf:\"bytes,4,opt,name=scan_duration,json=scanDuration,proto3\"`\n\txxx_hidden_FullDetectionReports *FullDetectionReports                         `protobuf:\"bytes,5,opt,name=full_detection_reports,json=fullDetectionReports,proto3\"`\n\txxx_hidden_ReconnaissanceReport *reconnaissance_go_proto.ReconnaissanceReport `protobuf:\"bytes,7,opt,name=reconnaissance_report,json=reconnaissanceReport,proto3\"`\n\tunknownFields                   protoimpl.UnknownFields\n\tsizeCache                       protoimpl.SizeCache\n}\n\nfunc (x *ScanResults) Reset() {\n\t*x = ScanResults{}\n\tmi := &file_scan_results_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ScanResults) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ScanResults) ProtoMessage() {}\n\nfunc (x *ScanResults) ProtoReflect() protoreflect.Message {\n\tmi := &file_scan_results_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *ScanResults) GetScanStatus() ScanStatus {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ScanStatus\n\t}\n\treturn ScanStatus_SCAN_STATUS_UNSPECIFIED\n}\n\nfunc (x *ScanResults) GetStatusMessage() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_StatusMessage\n\t}\n\treturn \"\"\n}\n\nfunc (x *ScanResults) GetTargetAlive() bool {\n\tif x != nil {\n\t\treturn x.xxx_hidden_TargetAlive\n\t}\n\treturn false\n}\n\nfunc (x *ScanResults) GetScanFindings() []*ScanFinding {\n\tif x != nil {\n\t\tif x.xxx_hidden_ScanFindings != nil {\n\t\t\treturn *x.xxx_hidden_ScanFindings\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *ScanResults) GetScanStartTimestamp() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ScanStartTimestamp\n\t}\n\treturn nil\n}\n\nfunc (x *ScanResults) GetScanDuration() *durationpb.Duration {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ScanDuration\n\t}\n\treturn nil\n}\n\nfunc (x *ScanResults) GetFullDetectionReports() *FullDetectionReports {\n\tif x != nil {\n\t\treturn x.xxx_hidden_FullDetectionReports\n\t}\n\treturn nil\n}\n\nfunc (x *ScanResults) GetReconnaissanceReport() *reconnaissance_go_proto.ReconnaissanceReport {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ReconnaissanceReport\n\t}\n\treturn nil\n}\n\nfunc (x *ScanResults) SetScanStatus(v ScanStatus) {\n\tx.xxx_hidden_ScanStatus = v\n}\n\nfunc (x *ScanResults) SetStatusMessage(v string) {\n\tx.xxx_hidden_StatusMessage = v\n}\n\nfunc (x *ScanResults) SetTargetAlive(v bool) {\n\tx.xxx_hidden_TargetAlive = v\n}\n\nfunc (x *ScanResults) SetScanFindings(v []*ScanFinding) {\n\tx.xxx_hidden_ScanFindings = &v\n}\n\nfunc (x *ScanResults) SetScanStartTimestamp(v *timestamppb.Timestamp) {\n\tx.xxx_hidden_ScanStartTimestamp = v\n}\n\nfunc (x *ScanResults) SetScanDuration(v *durationpb.Duration) {\n\tx.xxx_hidden_ScanDuration = v\n}\n\nfunc (x *ScanResults) SetFullDetectionReports(v *FullDetectionReports) {\n\tx.xxx_hidden_FullDetectionReports = v\n}\n\nfunc (x *ScanResults) SetReconnaissanceReport(v *reconnaissance_go_proto.ReconnaissanceReport) {\n\tx.xxx_hidden_ReconnaissanceReport = v\n}\n\nfunc (x *ScanResults) HasScanStartTimestamp() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_ScanStartTimestamp != nil\n}\n\nfunc (x *ScanResults) HasScanDuration() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_ScanDuration != nil\n}\n\nfunc (x *ScanResults) HasFullDetectionReports() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_FullDetectionReports != nil\n}\n\nfunc (x *ScanResults) HasReconnaissanceReport() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_ReconnaissanceReport != nil\n}\n\nfunc (x *ScanResults) ClearScanStartTimestamp() {\n\tx.xxx_hidden_ScanStartTimestamp = nil\n}\n\nfunc (x *ScanResults) ClearScanDuration() {\n\tx.xxx_hidden_ScanDuration = nil\n}\n\nfunc (x *ScanResults) ClearFullDetectionReports() {\n\tx.xxx_hidden_FullDetectionReports = nil\n}\n\nfunc (x *ScanResults) ClearReconnaissanceReport() {\n\tx.xxx_hidden_ReconnaissanceReport = nil\n}\n\ntype ScanResults_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Status of this scan.\n\tScanStatus ScanStatus\n\t// Detailed message for the scan status.\n\tStatusMessage string\n\t// Reports whether the target was alive during the scan.\n\t// A target is considered alive if at least one network service was identified\n\t// or at least one vulnerability was detected.\n\tTargetAlive bool\n\t// All findings from this scan.\n\tScanFindings []*ScanFinding\n\t// Time when this scan was started.\n\tScanStartTimestamp *timestamppb.Timestamp\n\t// Duration of the full scan.\n\tScanDuration *durationpb.Duration\n\t// Detection reports from all triggered Tsunami detection plugins.\n\tFullDetectionReports *FullDetectionReports\n\t// Reconnaissance reports from the fingerprinting stage.\n\tReconnaissanceReport *reconnaissance_go_proto.ReconnaissanceReport\n}\n\nfunc (b0 ScanResults_builder) Build() *ScanResults {\n\tm0 := &ScanResults{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_ScanStatus = b.ScanStatus\n\tx.xxx_hidden_StatusMessage = b.StatusMessage\n\tx.xxx_hidden_TargetAlive = b.TargetAlive\n\tx.xxx_hidden_ScanFindings = &b.ScanFindings\n\tx.xxx_hidden_ScanStartTimestamp = b.ScanStartTimestamp\n\tx.xxx_hidden_ScanDuration = b.ScanDuration\n\tx.xxx_hidden_FullDetectionReports = b.FullDetectionReports\n\tx.xxx_hidden_ReconnaissanceReport = b.ReconnaissanceReport\n\treturn m0\n}\n\n// Full detection reports from all triggered Tsunami detection plugins.\ntype FullDetectionReports struct {\n\tstate                       protoimpl.MessageState                 `protogen:\"opaque.v1\"`\n\txxx_hidden_DetectionReports *[]*detection_go_proto.DetectionReport `protobuf:\"bytes,1,rep,name=detection_reports,json=detectionReports,proto3\"`\n\tunknownFields               protoimpl.UnknownFields\n\tsizeCache                   protoimpl.SizeCache\n}\n\nfunc (x *FullDetectionReports) Reset() {\n\t*x = FullDetectionReports{}\n\tmi := &file_scan_results_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *FullDetectionReports) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*FullDetectionReports) ProtoMessage() {}\n\nfunc (x *FullDetectionReports) ProtoReflect() protoreflect.Message {\n\tmi := &file_scan_results_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *FullDetectionReports) GetDetectionReports() []*detection_go_proto.DetectionReport {\n\tif x != nil {\n\t\tif x.xxx_hidden_DetectionReports != nil {\n\t\t\treturn *x.xxx_hidden_DetectionReports\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *FullDetectionReports) SetDetectionReports(v []*detection_go_proto.DetectionReport) {\n\tx.xxx_hidden_DetectionReports = &v\n}\n\ntype FullDetectionReports_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tDetectionReports []*detection_go_proto.DetectionReport\n}\n\nfunc (b0 FullDetectionReports_builder) Build() *FullDetectionReports {\n\tm0 := &FullDetectionReports{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_DetectionReports = &b.DetectionReports\n\treturn m0\n}\n\nvar File_scan_results_proto protoreflect.FileDescriptor\n\nconst file_scan_results_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x12scan_results.proto\\x12\\rtsunami.proto\\x1a\\x1fgoogle/protobuf/timestamp.proto\\x1a\\x1egoogle/protobuf/duration.proto\\x1a\\x0fdetection.proto\\x1a\\x15network_service.proto\\x1a\\x14reconnaissance.proto\\x1a\\x13vulnerability.proto\\\"\\xd5\\x01\\n\" +\n\t\"\\vScanFinding\\x12:\\n\" +\n\t\"\\vtarget_info\\x18\\x01 \\x01(\\v2\\x19.tsunami.proto.TargetInfoR\\n\" +\n\t\"targetInfo\\x12F\\n\" +\n\t\"\\x0fnetwork_service\\x18\\x02 \\x01(\\v2\\x1d.tsunami.proto.NetworkServiceR\\x0enetworkService\\x12B\\n\" +\n\t\"\\rvulnerability\\x18\\x03 \\x01(\\v2\\x1c.tsunami.proto.VulnerabilityR\\rvulnerability\\\"\\x97\\x04\\n\" +\n\t\"\\vScanResults\\x12:\\n\" +\n\t\"\\vscan_status\\x18\\x01 \\x01(\\x0e2\\x19.tsunami.proto.ScanStatusR\\n\" +\n\t\"scanStatus\\x12%\\n\" +\n\t\"\\x0estatus_message\\x18\\x06 \\x01(\\tR\\rstatusMessage\\x12!\\n\" +\n\t\"\\ftarget_alive\\x18\\b \\x01(\\bR\\vtargetAlive\\x12?\\n\" +\n\t\"\\rscan_findings\\x18\\x02 \\x03(\\v2\\x1a.tsunami.proto.ScanFindingR\\fscanFindings\\x12L\\n\" +\n\t\"\\x14scan_start_timestamp\\x18\\x03 \\x01(\\v2\\x1a.google.protobuf.TimestampR\\x12scanStartTimestamp\\x12>\\n\" +\n\t\"\\rscan_duration\\x18\\x04 \\x01(\\v2\\x19.google.protobuf.DurationR\\fscanDuration\\x12Y\\n\" +\n\t\"\\x16full_detection_reports\\x18\\x05 \\x01(\\v2#.tsunami.proto.FullDetectionReportsR\\x14fullDetectionReports\\x12X\\n\" +\n\t\"\\x15reconnaissance_report\\x18\\a \\x01(\\v2#.tsunami.proto.ReconnaissanceReportR\\x14reconnaissanceReport\\\"c\\n\" +\n\t\"\\x14FullDetectionReports\\x12K\\n\" +\n\t\"\\x11detection_reports\\x18\\x01 \\x03(\\v2\\x1e.tsunami.proto.DetectionReportR\\x10detectionReports*l\\n\" +\n\t\"\\n\" +\n\t\"ScanStatus\\x12\\x1b\\n\" +\n\t\"\\x17SCAN_STATUS_UNSPECIFIED\\x10\\x00\\x12\\r\\n\" +\n\t\"\\tSUCCEEDED\\x10\\x01\\x12\\x17\\n\" +\n\t\"\\x13PARTIALLY_SUCCEEDED\\x10\\x04\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06FAILED\\x10\\x02\\x12\\r\\n\" +\n\t\"\\tCANCELLED\\x10\\x03Bz\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x11ScanResultsProtosP\\x01ZIgithub.com/google/tsunami-security-scanner/proto/go/scan_results_go_protob\\x06proto3\"\n\nvar file_scan_results_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_scan_results_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_scan_results_proto_goTypes = []any{\n\t(ScanStatus)(0),                                      // 0: tsunami.proto.ScanStatus\n\t(*ScanFinding)(nil),                                  // 1: tsunami.proto.ScanFinding\n\t(*ScanResults)(nil),                                  // 2: tsunami.proto.ScanResults\n\t(*FullDetectionReports)(nil),                         // 3: tsunami.proto.FullDetectionReports\n\t(*reconnaissance_go_proto.TargetInfo)(nil),           // 4: tsunami.proto.TargetInfo\n\t(*network_service_go_proto.NetworkService)(nil),      // 5: tsunami.proto.NetworkService\n\t(*vulnerability_go_proto.Vulnerability)(nil),         // 6: tsunami.proto.Vulnerability\n\t(*timestamppb.Timestamp)(nil),                        // 7: google.protobuf.Timestamp\n\t(*durationpb.Duration)(nil),                          // 8: google.protobuf.Duration\n\t(*reconnaissance_go_proto.ReconnaissanceReport)(nil), // 9: tsunami.proto.ReconnaissanceReport\n\t(*detection_go_proto.DetectionReport)(nil),           // 10: tsunami.proto.DetectionReport\n}\nvar file_scan_results_proto_depIdxs = []int32{\n\t4,  // 0: tsunami.proto.ScanFinding.target_info:type_name -> tsunami.proto.TargetInfo\n\t5,  // 1: tsunami.proto.ScanFinding.network_service:type_name -> tsunami.proto.NetworkService\n\t6,  // 2: tsunami.proto.ScanFinding.vulnerability:type_name -> tsunami.proto.Vulnerability\n\t0,  // 3: tsunami.proto.ScanResults.scan_status:type_name -> tsunami.proto.ScanStatus\n\t1,  // 4: tsunami.proto.ScanResults.scan_findings:type_name -> tsunami.proto.ScanFinding\n\t7,  // 5: tsunami.proto.ScanResults.scan_start_timestamp:type_name -> google.protobuf.Timestamp\n\t8,  // 6: tsunami.proto.ScanResults.scan_duration:type_name -> google.protobuf.Duration\n\t3,  // 7: tsunami.proto.ScanResults.full_detection_reports:type_name -> tsunami.proto.FullDetectionReports\n\t9,  // 8: tsunami.proto.ScanResults.reconnaissance_report:type_name -> tsunami.proto.ReconnaissanceReport\n\t10, // 9: tsunami.proto.FullDetectionReports.detection_reports:type_name -> tsunami.proto.DetectionReport\n\t10, // [10:10] is the sub-list for method output_type\n\t10, // [10:10] is the sub-list for method input_type\n\t10, // [10:10] is the sub-list for extension type_name\n\t10, // [10:10] is the sub-list for extension extendee\n\t0,  // [0:10] is the sub-list for field type_name\n}\n\nfunc init() { file_scan_results_proto_init() }\nfunc file_scan_results_proto_init() {\n\tif File_scan_results_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_scan_results_proto_rawDesc), len(file_scan_results_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_scan_results_proto_goTypes,\n\t\tDependencyIndexes: file_scan_results_proto_depIdxs,\n\t\tEnumInfos:         file_scan_results_proto_enumTypes,\n\t\tMessageInfos:      file_scan_results_proto_msgTypes,\n\t}.Build()\n\tFile_scan_results_proto = out.File\n\tfile_scan_results_proto_goTypes = nil\n\tfile_scan_results_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/scan_target_go_proto/scan_target.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing a scanning target.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: scan_target.proto\n\npackage scan_target_go_proto\n\nimport (\n\tnetwork_go_proto \"github.com/google/tsunami-security-scanner/proto/go/network_go_proto\"\n\tnetwork_service_go_proto \"github.com/google/tsunami-security-scanner/proto/go/network_service_go_proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// The information about a scan target.\ntype ScanTarget struct {\n\tstate             protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Target isScanTarget_Target    `protobuf_oneof:\"target\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *ScanTarget) Reset() {\n\t*x = ScanTarget{}\n\tmi := &file_scan_target_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ScanTarget) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ScanTarget) ProtoMessage() {}\n\nfunc (x *ScanTarget) ProtoReflect() protoreflect.Message {\n\tmi := &file_scan_target_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *ScanTarget) GetNetworkEndpoint() *network_go_proto.NetworkEndpoint {\n\tif x != nil {\n\t\tif x, ok := x.xxx_hidden_Target.(*scanTarget_NetworkEndpoint); ok {\n\t\t\treturn x.NetworkEndpoint\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *ScanTarget) GetNetworkService() *network_service_go_proto.NetworkService {\n\tif x != nil {\n\t\tif x, ok := x.xxx_hidden_Target.(*scanTarget_NetworkService); ok {\n\t\t\treturn x.NetworkService\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *ScanTarget) SetNetworkEndpoint(v *network_go_proto.NetworkEndpoint) {\n\tif v == nil {\n\t\tx.xxx_hidden_Target = nil\n\t\treturn\n\t}\n\tx.xxx_hidden_Target = &scanTarget_NetworkEndpoint{v}\n}\n\nfunc (x *ScanTarget) SetNetworkService(v *network_service_go_proto.NetworkService) {\n\tif v == nil {\n\t\tx.xxx_hidden_Target = nil\n\t\treturn\n\t}\n\tx.xxx_hidden_Target = &scanTarget_NetworkService{v}\n}\n\nfunc (x *ScanTarget) HasTarget() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Target != nil\n}\n\nfunc (x *ScanTarget) HasNetworkEndpoint() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\t_, ok := x.xxx_hidden_Target.(*scanTarget_NetworkEndpoint)\n\treturn ok\n}\n\nfunc (x *ScanTarget) HasNetworkService() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\t_, ok := x.xxx_hidden_Target.(*scanTarget_NetworkService)\n\treturn ok\n}\n\nfunc (x *ScanTarget) ClearTarget() {\n\tx.xxx_hidden_Target = nil\n}\n\nfunc (x *ScanTarget) ClearNetworkEndpoint() {\n\tif _, ok := x.xxx_hidden_Target.(*scanTarget_NetworkEndpoint); ok {\n\t\tx.xxx_hidden_Target = nil\n\t}\n}\n\nfunc (x *ScanTarget) ClearNetworkService() {\n\tif _, ok := x.xxx_hidden_Target.(*scanTarget_NetworkService); ok {\n\t\tx.xxx_hidden_Target = nil\n\t}\n}\n\nconst ScanTarget_Target_not_set_case case_ScanTarget_Target = 0\nconst ScanTarget_NetworkEndpoint_case case_ScanTarget_Target = 1\nconst ScanTarget_NetworkService_case case_ScanTarget_Target = 2\n\nfunc (x *ScanTarget) WhichTarget() case_ScanTarget_Target {\n\tif x == nil {\n\t\treturn ScanTarget_Target_not_set_case\n\t}\n\tswitch x.xxx_hidden_Target.(type) {\n\tcase *scanTarget_NetworkEndpoint:\n\t\treturn ScanTarget_NetworkEndpoint_case\n\tcase *scanTarget_NetworkService:\n\t\treturn ScanTarget_NetworkService_case\n\tdefault:\n\t\treturn ScanTarget_Target_not_set_case\n\t}\n}\n\ntype ScanTarget_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Fields of oneof xxx_hidden_Target:\n\t// The network endpoint to be scanned.\n\tNetworkEndpoint *network_go_proto.NetworkEndpoint\n\t// The network service to be scanned.\n\tNetworkService *network_service_go_proto.NetworkService\n\t// -- end of xxx_hidden_Target\n}\n\nfunc (b0 ScanTarget_builder) Build() *ScanTarget {\n\tm0 := &ScanTarget{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tif b.NetworkEndpoint != nil {\n\t\tx.xxx_hidden_Target = &scanTarget_NetworkEndpoint{b.NetworkEndpoint}\n\t}\n\tif b.NetworkService != nil {\n\t\tx.xxx_hidden_Target = &scanTarget_NetworkService{b.NetworkService}\n\t}\n\treturn m0\n}\n\ntype case_ScanTarget_Target protoreflect.FieldNumber\n\nfunc (x case_ScanTarget_Target) String() string {\n\tmd := file_scan_target_proto_msgTypes[0].Descriptor()\n\tif x == 0 {\n\t\treturn \"not set\"\n\t}\n\treturn protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))\n}\n\ntype isScanTarget_Target interface {\n\tisScanTarget_Target()\n}\n\ntype scanTarget_NetworkEndpoint struct {\n\t// The network endpoint to be scanned.\n\tNetworkEndpoint *network_go_proto.NetworkEndpoint `protobuf:\"bytes,1,opt,name=network_endpoint,json=networkEndpoint,proto3,oneof\"`\n}\n\ntype scanTarget_NetworkService struct {\n\t// The network service to be scanned.\n\tNetworkService *network_service_go_proto.NetworkService `protobuf:\"bytes,2,opt,name=network_service,json=networkService,proto3,oneof\"`\n}\n\nfunc (*scanTarget_NetworkEndpoint) isScanTarget_Target() {}\n\nfunc (*scanTarget_NetworkService) isScanTarget_Target() {}\n\nvar File_scan_target_proto protoreflect.FileDescriptor\n\nconst file_scan_target_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x11scan_target.proto\\x12\\rtsunami.proto\\x1a\\rnetwork.proto\\x1a\\x15network_service.proto\\\"\\xad\\x01\\n\" +\n\t\"\\n\" +\n\t\"ScanTarget\\x12K\\n\" +\n\t\"\\x10network_endpoint\\x18\\x01 \\x01(\\v2\\x1e.tsunami.proto.NetworkEndpointH\\x00R\\x0fnetworkEndpoint\\x12H\\n\" +\n\t\"\\x0fnetwork_service\\x18\\x02 \\x01(\\v2\\x1d.tsunami.proto.NetworkServiceH\\x00R\\x0enetworkServiceB\\b\\n\" +\n\t\"\\x06targetBx\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x10ScanTargetProtosP\\x01ZHgithub.com/google/tsunami-security-scanner/proto/go/scan_target_go_protob\\x06proto3\"\n\nvar file_scan_target_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_scan_target_proto_goTypes = []any{\n\t(*ScanTarget)(nil),                              // 0: tsunami.proto.ScanTarget\n\t(*network_go_proto.NetworkEndpoint)(nil),        // 1: tsunami.proto.NetworkEndpoint\n\t(*network_service_go_proto.NetworkService)(nil), // 2: tsunami.proto.NetworkService\n}\nvar file_scan_target_proto_depIdxs = []int32{\n\t1, // 0: tsunami.proto.ScanTarget.network_endpoint:type_name -> tsunami.proto.NetworkEndpoint\n\t2, // 1: tsunami.proto.ScanTarget.network_service:type_name -> tsunami.proto.NetworkService\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_scan_target_proto_init() }\nfunc file_scan_target_proto_init() {\n\tif File_scan_target_proto != nil {\n\t\treturn\n\t}\n\tfile_scan_target_proto_msgTypes[0].OneofWrappers = []any{\n\t\t(*scanTarget_NetworkEndpoint)(nil),\n\t\t(*scanTarget_NetworkService)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_scan_target_proto_rawDesc), len(file_scan_target_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_scan_target_proto_goTypes,\n\t\tDependencyIndexes: file_scan_target_proto_depIdxs,\n\t\tMessageInfos:      file_scan_target_proto_msgTypes,\n\t}.Build()\n\tFile_scan_target_proto = out.File\n\tfile_scan_target_proto_goTypes = nil\n\tfile_scan_target_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/software_go_proto/software.pb.go",
    "content": "//\n// Copyright 2019 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing a software.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: software.proto\n\npackage software_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Type of the Version message, identifying an ordinary software version or a\n// sentinel MINIMUM/MAXIMUM version. See comments below for what is a sentinel\n// version.\ntype Version_VersionType int32\n\nconst (\n\tVersion_VERSION_TYPE_UNSPECIFIED Version_VersionType = 0\n\t// A normal software version.\n\tVersion_NORMAL Version_VersionType = 1\n\t// A sentinel version representing negative infinity, i.e. MINIMUM version\n\t// is less than any NORMAL and MAXIMUM versions.\n\tVersion_MINIMUM Version_VersionType = 2\n\t// A sentinel version representing positive infinity, i.e. MAXIMUM version\n\t// is greater than any NORMAL and MINIMUM versions.\n\tVersion_MAXIMUM Version_VersionType = 3\n)\n\n// Enum value maps for Version_VersionType.\nvar (\n\tVersion_VersionType_name = map[int32]string{\n\t\t0: \"VERSION_TYPE_UNSPECIFIED\",\n\t\t1: \"NORMAL\",\n\t\t2: \"MINIMUM\",\n\t\t3: \"MAXIMUM\",\n\t}\n\tVersion_VersionType_value = map[string]int32{\n\t\t\"VERSION_TYPE_UNSPECIFIED\": 0,\n\t\t\"NORMAL\":                   1,\n\t\t\"MINIMUM\":                  2,\n\t\t\"MAXIMUM\":                  3,\n\t}\n)\n\nfunc (x Version_VersionType) Enum() *Version_VersionType {\n\tp := new(Version_VersionType)\n\t*p = x\n\treturn p\n}\n\nfunc (x Version_VersionType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Version_VersionType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_software_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Version_VersionType) Type() protoreflect.EnumType {\n\treturn &file_software_proto_enumTypes[0]\n}\n\nfunc (x Version_VersionType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Whether the range endpoint is inclusive or exclusive.\ntype VersionRange_Inclusiveness int32\n\nconst (\n\tVersionRange_INCLUSIVENESS_UNSPECIFIED VersionRange_Inclusiveness = 0\n\tVersionRange_INCLUSIVE                 VersionRange_Inclusiveness = 1\n\tVersionRange_EXCLUSIVE                 VersionRange_Inclusiveness = 2\n)\n\n// Enum value maps for VersionRange_Inclusiveness.\nvar (\n\tVersionRange_Inclusiveness_name = map[int32]string{\n\t\t0: \"INCLUSIVENESS_UNSPECIFIED\",\n\t\t1: \"INCLUSIVE\",\n\t\t2: \"EXCLUSIVE\",\n\t}\n\tVersionRange_Inclusiveness_value = map[string]int32{\n\t\t\"INCLUSIVENESS_UNSPECIFIED\": 0,\n\t\t\"INCLUSIVE\":                 1,\n\t\t\"EXCLUSIVE\":                 2,\n\t}\n)\n\nfunc (x VersionRange_Inclusiveness) Enum() *VersionRange_Inclusiveness {\n\tp := new(VersionRange_Inclusiveness)\n\t*p = x\n\treturn p\n}\n\nfunc (x VersionRange_Inclusiveness) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (VersionRange_Inclusiveness) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_software_proto_enumTypes[1].Descriptor()\n}\n\nfunc (VersionRange_Inclusiveness) Type() protoreflect.EnumType {\n\treturn &file_software_proto_enumTypes[1]\n}\n\nfunc (x VersionRange_Inclusiveness) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// The exact version of a software.\ntype Version struct {\n\tstate                        protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Type              Version_VersionType    `protobuf:\"varint,1,opt,name=type,proto3,enum=tsunami.proto.Version_VersionType\"`\n\txxx_hidden_FullVersionString string                 `protobuf:\"bytes,2,opt,name=full_version_string,json=fullVersionString,proto3\"`\n\tunknownFields                protoimpl.UnknownFields\n\tsizeCache                    protoimpl.SizeCache\n}\n\nfunc (x *Version) Reset() {\n\t*x = Version{}\n\tmi := &file_software_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Version) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Version) ProtoMessage() {}\n\nfunc (x *Version) ProtoReflect() protoreflect.Message {\n\tmi := &file_software_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *Version) GetType() Version_VersionType {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Type\n\t}\n\treturn Version_VERSION_TYPE_UNSPECIFIED\n}\n\nfunc (x *Version) GetFullVersionString() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_FullVersionString\n\t}\n\treturn \"\"\n}\n\nfunc (x *Version) SetType(v Version_VersionType) {\n\tx.xxx_hidden_Type = v\n}\n\nfunc (x *Version) SetFullVersionString(v string) {\n\tx.xxx_hidden_FullVersionString = v\n}\n\ntype Version_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Distinguishes between sentinel MIN/MAX versions and normal versions.\n\tType Version_VersionType\n\t// Human readable version number, e.g. 1.0.3. This is set only when type is\n\t// NORMAL. Tsunami uses raw string to represent a version number instead of\n\t// any structured messages in order to handle different kinds of version\n\t// schemes. Tsunami will tokenize this version string and store tokens\n\t// internally. When performing version comparisons, Tsunami follows the\n\t// precedence defined by Semantic Versioning (semver.org). More details can be\n\t// found in Tsunami's internal Version class.\n\tFullVersionString string\n}\n\nfunc (b0 Version_builder) Build() *Version {\n\tm0 := &Version{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Type = b.Type\n\tx.xxx_hidden_FullVersionString = b.FullVersionString\n\treturn m0\n}\n\n// An inclusive range of versions for a software.\ntype VersionRange struct {\n\tstate                              protoimpl.MessageState     `protogen:\"opaque.v1\"`\n\txxx_hidden_MinVersion              *Version                   `protobuf:\"bytes,1,opt,name=min_version,json=minVersion,proto3\"`\n\txxx_hidden_MinVersionInclusiveness VersionRange_Inclusiveness `protobuf:\"varint,2,opt,name=min_version_inclusiveness,json=minVersionInclusiveness,proto3,enum=tsunami.proto.VersionRange_Inclusiveness\"`\n\txxx_hidden_MaxVersion              *Version                   `protobuf:\"bytes,3,opt,name=max_version,json=maxVersion,proto3\"`\n\txxx_hidden_MaxVersionInclusiveness VersionRange_Inclusiveness `protobuf:\"varint,4,opt,name=max_version_inclusiveness,json=maxVersionInclusiveness,proto3,enum=tsunami.proto.VersionRange_Inclusiveness\"`\n\tunknownFields                      protoimpl.UnknownFields\n\tsizeCache                          protoimpl.SizeCache\n}\n\nfunc (x *VersionRange) Reset() {\n\t*x = VersionRange{}\n\tmi := &file_software_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VersionRange) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VersionRange) ProtoMessage() {}\n\nfunc (x *VersionRange) ProtoReflect() protoreflect.Message {\n\tmi := &file_software_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *VersionRange) GetMinVersion() *Version {\n\tif x != nil {\n\t\treturn x.xxx_hidden_MinVersion\n\t}\n\treturn nil\n}\n\nfunc (x *VersionRange) GetMinVersionInclusiveness() VersionRange_Inclusiveness {\n\tif x != nil {\n\t\treturn x.xxx_hidden_MinVersionInclusiveness\n\t}\n\treturn VersionRange_INCLUSIVENESS_UNSPECIFIED\n}\n\nfunc (x *VersionRange) GetMaxVersion() *Version {\n\tif x != nil {\n\t\treturn x.xxx_hidden_MaxVersion\n\t}\n\treturn nil\n}\n\nfunc (x *VersionRange) GetMaxVersionInclusiveness() VersionRange_Inclusiveness {\n\tif x != nil {\n\t\treturn x.xxx_hidden_MaxVersionInclusiveness\n\t}\n\treturn VersionRange_INCLUSIVENESS_UNSPECIFIED\n}\n\nfunc (x *VersionRange) SetMinVersion(v *Version) {\n\tx.xxx_hidden_MinVersion = v\n}\n\nfunc (x *VersionRange) SetMinVersionInclusiveness(v VersionRange_Inclusiveness) {\n\tx.xxx_hidden_MinVersionInclusiveness = v\n}\n\nfunc (x *VersionRange) SetMaxVersion(v *Version) {\n\tx.xxx_hidden_MaxVersion = v\n}\n\nfunc (x *VersionRange) SetMaxVersionInclusiveness(v VersionRange_Inclusiveness) {\n\tx.xxx_hidden_MaxVersionInclusiveness = v\n}\n\nfunc (x *VersionRange) HasMinVersion() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_MinVersion != nil\n}\n\nfunc (x *VersionRange) HasMaxVersion() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_MaxVersion != nil\n}\n\nfunc (x *VersionRange) ClearMinVersion() {\n\tx.xxx_hidden_MinVersion = nil\n}\n\nfunc (x *VersionRange) ClearMaxVersion() {\n\tx.xxx_hidden_MaxVersion = nil\n}\n\ntype VersionRange_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Minimum version that belongs in the range.\n\tMinVersion *Version\n\t// Inclusiveness of the min_version. When min_version points to negative\n\t// infinity, this value will always be EXCLUSIVE to matching the\n\t// representation of (-inf, 1.0]. Note that negative infinity version should\n\t// ***NOT*** be compared with a version range as it is just a bogus sentinel\n\t// version without any meaning.\n\tMinVersionInclusiveness VersionRange_Inclusiveness\n\t// Maximum version that belongs in the range.\n\tMaxVersion *Version\n\t// Inclusiveness of the max_version. When max_version points to positive\n\t// infinity, this value will always be EXCLUSIVE to matching the\n\t// representation of [1.0, inf). Note that positive infinity version should\n\t// ***NOT*** be compared with a version range as it is just a bogus sentinel\n\t// version without any meaning.\n\tMaxVersionInclusiveness VersionRange_Inclusiveness\n}\n\nfunc (b0 VersionRange_builder) Build() *VersionRange {\n\tm0 := &VersionRange{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_MinVersion = b.MinVersion\n\tx.xxx_hidden_MinVersionInclusiveness = b.MinVersionInclusiveness\n\tx.xxx_hidden_MaxVersion = b.MaxVersion\n\tx.xxx_hidden_MaxVersionInclusiveness = b.MaxVersionInclusiveness\n\treturn m0\n}\n\n// A set of Versions and VersionRanges that completely describes a set of\n// software releases, e.g. {3.9.1, 3.9.3, [4.7.1, 4.7.8], 4.8}\ntype VersionSet struct {\n\tstate                    protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Versions      *[]*Version            `protobuf:\"bytes,1,rep,name=versions,proto3\"`\n\txxx_hidden_VersionRanges *[]*VersionRange       `protobuf:\"bytes,2,rep,name=version_ranges,json=versionRanges,proto3\"`\n\tunknownFields            protoimpl.UnknownFields\n\tsizeCache                protoimpl.SizeCache\n}\n\nfunc (x *VersionSet) Reset() {\n\t*x = VersionSet{}\n\tmi := &file_software_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VersionSet) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VersionSet) ProtoMessage() {}\n\nfunc (x *VersionSet) ProtoReflect() protoreflect.Message {\n\tmi := &file_software_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *VersionSet) GetVersions() []*Version {\n\tif x != nil {\n\t\tif x.xxx_hidden_Versions != nil {\n\t\t\treturn *x.xxx_hidden_Versions\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *VersionSet) GetVersionRanges() []*VersionRange {\n\tif x != nil {\n\t\tif x.xxx_hidden_VersionRanges != nil {\n\t\t\treturn *x.xxx_hidden_VersionRanges\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *VersionSet) SetVersions(v []*Version) {\n\tx.xxx_hidden_Versions = &v\n}\n\nfunc (x *VersionSet) SetVersionRanges(v []*VersionRange) {\n\tx.xxx_hidden_VersionRanges = &v\n}\n\ntype VersionSet_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tVersions      []*Version\n\tVersionRanges []*VersionRange\n}\n\nfunc (b0 VersionSet_builder) Build() *VersionSet {\n\tm0 := &VersionSet{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Versions = &b.Versions\n\tx.xxx_hidden_VersionRanges = &b.VersionRanges\n\treturn m0\n}\n\n// A structured description about a software.\ntype Software struct {\n\tstate           protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Name string                 `protobuf:\"bytes,1,opt,name=name,proto3\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *Software) Reset() {\n\t*x = Software{}\n\tmi := &file_software_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Software) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Software) ProtoMessage() {}\n\nfunc (x *Software) ProtoReflect() protoreflect.Message {\n\tmi := &file_software_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *Software) GetName() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Software) SetName(v string) {\n\tx.xxx_hidden_Name = v\n}\n\ntype Software_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The name of this software.\n\tName string\n}\n\nfunc (b0 Software_builder) Build() *Software {\n\tm0 := &Software{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Name = b.Name\n\treturn m0\n}\n\nvar File_software_proto protoreflect.FileDescriptor\n\nconst file_software_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x0esoftware.proto\\x12\\rtsunami.proto\\\"\\xc4\\x01\\n\" +\n\t\"\\aVersion\\x126\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2\\\".tsunami.proto.Version.VersionTypeR\\x04type\\x12.\\n\" +\n\t\"\\x13full_version_string\\x18\\x02 \\x01(\\tR\\x11fullVersionString\\\"Q\\n\" +\n\t\"\\vVersionType\\x12\\x1c\\n\" +\n\t\"\\x18VERSION_TYPE_UNSPECIFIED\\x10\\x00\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06NORMAL\\x10\\x01\\x12\\v\\n\" +\n\t\"\\aMINIMUM\\x10\\x02\\x12\\v\\n\" +\n\t\"\\aMAXIMUM\\x10\\x03\\\"\\x9c\\x03\\n\" +\n\t\"\\fVersionRange\\x127\\n\" +\n\t\"\\vmin_version\\x18\\x01 \\x01(\\v2\\x16.tsunami.proto.VersionR\\n\" +\n\t\"minVersion\\x12e\\n\" +\n\t\"\\x19min_version_inclusiveness\\x18\\x02 \\x01(\\x0e2).tsunami.proto.VersionRange.InclusivenessR\\x17minVersionInclusiveness\\x127\\n\" +\n\t\"\\vmax_version\\x18\\x03 \\x01(\\v2\\x16.tsunami.proto.VersionR\\n\" +\n\t\"maxVersion\\x12e\\n\" +\n\t\"\\x19max_version_inclusiveness\\x18\\x04 \\x01(\\x0e2).tsunami.proto.VersionRange.InclusivenessR\\x17maxVersionInclusiveness\\\"L\\n\" +\n\t\"\\rInclusiveness\\x12\\x1d\\n\" +\n\t\"\\x19INCLUSIVENESS_UNSPECIFIED\\x10\\x00\\x12\\r\\n\" +\n\t\"\\tINCLUSIVE\\x10\\x01\\x12\\r\\n\" +\n\t\"\\tEXCLUSIVE\\x10\\x02\\\"\\x84\\x01\\n\" +\n\t\"\\n\" +\n\t\"VersionSet\\x122\\n\" +\n\t\"\\bversions\\x18\\x01 \\x03(\\v2\\x16.tsunami.proto.VersionR\\bversions\\x12B\\n\" +\n\t\"\\x0eversion_ranges\\x18\\x02 \\x03(\\v2\\x1b.tsunami.proto.VersionRangeR\\rversionRanges\\\"\\x1e\\n\" +\n\t\"\\bSoftware\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04nameBs\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x0eSoftwareProtosP\\x01ZEgithub.com/google/tsunami-security-scanner/proto/go/software_go_protob\\x06proto3\"\n\nvar file_software_proto_enumTypes = make([]protoimpl.EnumInfo, 2)\nvar file_software_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_software_proto_goTypes = []any{\n\t(Version_VersionType)(0),        // 0: tsunami.proto.Version.VersionType\n\t(VersionRange_Inclusiveness)(0), // 1: tsunami.proto.VersionRange.Inclusiveness\n\t(*Version)(nil),                 // 2: tsunami.proto.Version\n\t(*VersionRange)(nil),            // 3: tsunami.proto.VersionRange\n\t(*VersionSet)(nil),              // 4: tsunami.proto.VersionSet\n\t(*Software)(nil),                // 5: tsunami.proto.Software\n}\nvar file_software_proto_depIdxs = []int32{\n\t0, // 0: tsunami.proto.Version.type:type_name -> tsunami.proto.Version.VersionType\n\t2, // 1: tsunami.proto.VersionRange.min_version:type_name -> tsunami.proto.Version\n\t1, // 2: tsunami.proto.VersionRange.min_version_inclusiveness:type_name -> tsunami.proto.VersionRange.Inclusiveness\n\t2, // 3: tsunami.proto.VersionRange.max_version:type_name -> tsunami.proto.Version\n\t1, // 4: tsunami.proto.VersionRange.max_version_inclusiveness:type_name -> tsunami.proto.VersionRange.Inclusiveness\n\t2, // 5: tsunami.proto.VersionSet.versions:type_name -> tsunami.proto.Version\n\t3, // 6: tsunami.proto.VersionSet.version_ranges:type_name -> tsunami.proto.VersionRange\n\t7, // [7:7] is the sub-list for method output_type\n\t7, // [7:7] is the sub-list for method input_type\n\t7, // [7:7] is the sub-list for extension type_name\n\t7, // [7:7] is the sub-list for extension extendee\n\t0, // [0:7] is the sub-list for field type_name\n}\n\nfunc init() { file_software_proto_init() }\nfunc file_software_proto_init() {\n\tif File_software_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_software_proto_rawDesc), len(file_software_proto_rawDesc)),\n\t\t\tNumEnums:      2,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_software_proto_goTypes,\n\t\tDependencyIndexes: file_software_proto_depIdxs,\n\t\tEnumInfos:         file_software_proto_enumTypes,\n\t\tMessageInfos:      file_software_proto_msgTypes,\n\t}.Build()\n\tFile_software_proto = out.File\n\tfile_software_proto_goTypes = nil\n\tfile_software_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/vulnerability_go_proto/vulnerability.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing a vulnerability.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: vulnerability.proto\n\npackage vulnerability_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Severity of a vulnerability.\ntype Severity int32\n\nconst (\n\t// Unspecified severity.\n\tSeverity_SEVERITY_UNSPECIFIED Severity = 0\n\t// Minimal severity.\n\tSeverity_MINIMAL Severity = 1\n\t// Low severity.\n\tSeverity_LOW Severity = 2\n\t// Medium severity.\n\tSeverity_MEDIUM Severity = 3\n\t// High severity.\n\tSeverity_HIGH Severity = 4\n\t// Critical severity.\n\tSeverity_CRITICAL Severity = 5\n)\n\n// Enum value maps for Severity.\nvar (\n\tSeverity_name = map[int32]string{\n\t\t0: \"SEVERITY_UNSPECIFIED\",\n\t\t1: \"MINIMAL\",\n\t\t2: \"LOW\",\n\t\t3: \"MEDIUM\",\n\t\t4: \"HIGH\",\n\t\t5: \"CRITICAL\",\n\t}\n\tSeverity_value = map[string]int32{\n\t\t\"SEVERITY_UNSPECIFIED\": 0,\n\t\t\"MINIMAL\":              1,\n\t\t\"LOW\":                  2,\n\t\t\"MEDIUM\":               3,\n\t\t\"HIGH\":                 4,\n\t\t\"CRITICAL\":             5,\n\t}\n)\n\nfunc (x Severity) Enum() *Severity {\n\tp := new(Severity)\n\t*p = x\n\treturn p\n}\n\nfunc (x Severity) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Severity) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_vulnerability_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Severity) Type() protoreflect.EnumType {\n\treturn &file_vulnerability_proto_enumTypes[0]\n}\n\nfunc (x Severity) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// The identifier that uniquely identifies this vulnerability.\ntype VulnerabilityId struct {\n\tstate                protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Publisher string                 `protobuf:\"bytes,1,opt,name=publisher,proto3\"`\n\txxx_hidden_Value     string                 `protobuf:\"bytes,2,opt,name=value,proto3\"`\n\txxx_hidden_Link      string                 `protobuf:\"bytes,3,opt,name=link,proto3\"`\n\tunknownFields        protoimpl.UnknownFields\n\tsizeCache            protoimpl.SizeCache\n}\n\nfunc (x *VulnerabilityId) Reset() {\n\t*x = VulnerabilityId{}\n\tmi := &file_vulnerability_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VulnerabilityId) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VulnerabilityId) ProtoMessage() {}\n\nfunc (x *VulnerabilityId) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *VulnerabilityId) GetPublisher() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Publisher\n\t}\n\treturn \"\"\n}\n\nfunc (x *VulnerabilityId) GetValue() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Value\n\t}\n\treturn \"\"\n}\n\nfunc (x *VulnerabilityId) GetLink() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *VulnerabilityId) SetPublisher(v string) {\n\tx.xxx_hidden_Publisher = v\n}\n\nfunc (x *VulnerabilityId) SetValue(v string) {\n\tx.xxx_hidden_Value = v\n}\n\nfunc (x *VulnerabilityId) SetLink(v string) {\n\tx.xxx_hidden_Link = v\n}\n\ntype VulnerabilityId_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Entity that published this identifier.\n\tPublisher string\n\t// Publisher assigned unique identifier.\n\tValue string\n\t// Optional. URL for details about this vulnerability.\n\tLink string\n}\n\nfunc (b0 VulnerabilityId_builder) Build() *VulnerabilityId {\n\tm0 := &VulnerabilityId{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Publisher = b.Publisher\n\tx.xxx_hidden_Value = b.Value\n\tx.xxx_hidden_Link = b.Link\n\treturn m0\n}\n\n// Message that represents one single vulnerability detected by Tsunami.\ntype Vulnerability struct {\n\tstate                        protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_MainId            *VulnerabilityId       `protobuf:\"bytes,1,opt,name=main_id,json=mainId,proto3\"`\n\txxx_hidden_RelatedId         *[]*VulnerabilityId    `protobuf:\"bytes,2,rep,name=related_id,json=relatedId,proto3\"`\n\txxx_hidden_Severity          Severity               `protobuf:\"varint,3,opt,name=severity,proto3,enum=tsunami.proto.Severity\"`\n\txxx_hidden_Title             string                 `protobuf:\"bytes,4,opt,name=title,proto3\"`\n\txxx_hidden_Description       string                 `protobuf:\"bytes,5,opt,name=description,proto3\"`\n\txxx_hidden_Recommendation    string                 `protobuf:\"bytes,6,opt,name=recommendation,proto3\"`\n\txxx_hidden_CvssV2            string                 `protobuf:\"bytes,7,opt,name=cvss_v2,json=cvssV2,proto3\"`\n\txxx_hidden_CvssV3            string                 `protobuf:\"bytes,8,opt,name=cvss_v3,json=cvssV3,proto3\"`\n\txxx_hidden_AdditionalDetails *[]*AdditionalDetail   `protobuf:\"bytes,9,rep,name=additional_details,json=additionalDetails,proto3\"`\n\tunknownFields                protoimpl.UnknownFields\n\tsizeCache                    protoimpl.SizeCache\n}\n\nfunc (x *Vulnerability) Reset() {\n\t*x = Vulnerability{}\n\tmi := &file_vulnerability_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Vulnerability) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Vulnerability) ProtoMessage() {}\n\nfunc (x *Vulnerability) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *Vulnerability) GetMainId() *VulnerabilityId {\n\tif x != nil {\n\t\treturn x.xxx_hidden_MainId\n\t}\n\treturn nil\n}\n\nfunc (x *Vulnerability) GetRelatedId() []*VulnerabilityId {\n\tif x != nil {\n\t\tif x.xxx_hidden_RelatedId != nil {\n\t\t\treturn *x.xxx_hidden_RelatedId\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *Vulnerability) GetSeverity() Severity {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Severity\n\t}\n\treturn Severity_SEVERITY_UNSPECIFIED\n}\n\nfunc (x *Vulnerability) GetTitle() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Title\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vulnerability) GetDescription() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vulnerability) GetRecommendation() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Recommendation\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vulnerability) GetCvssV2() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_CvssV2\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vulnerability) GetCvssV3() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_CvssV3\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vulnerability) GetAdditionalDetails() []*AdditionalDetail {\n\tif x != nil {\n\t\tif x.xxx_hidden_AdditionalDetails != nil {\n\t\t\treturn *x.xxx_hidden_AdditionalDetails\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *Vulnerability) SetMainId(v *VulnerabilityId) {\n\tx.xxx_hidden_MainId = v\n}\n\nfunc (x *Vulnerability) SetRelatedId(v []*VulnerabilityId) {\n\tx.xxx_hidden_RelatedId = &v\n}\n\nfunc (x *Vulnerability) SetSeverity(v Severity) {\n\tx.xxx_hidden_Severity = v\n}\n\nfunc (x *Vulnerability) SetTitle(v string) {\n\tx.xxx_hidden_Title = v\n}\n\nfunc (x *Vulnerability) SetDescription(v string) {\n\tx.xxx_hidden_Description = v\n}\n\nfunc (x *Vulnerability) SetRecommendation(v string) {\n\tx.xxx_hidden_Recommendation = v\n}\n\nfunc (x *Vulnerability) SetCvssV2(v string) {\n\tx.xxx_hidden_CvssV2 = v\n}\n\nfunc (x *Vulnerability) SetCvssV3(v string) {\n\tx.xxx_hidden_CvssV3 = v\n}\n\nfunc (x *Vulnerability) SetAdditionalDetails(v []*AdditionalDetail) {\n\tx.xxx_hidden_AdditionalDetails = &v\n}\n\nfunc (x *Vulnerability) HasMainId() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_MainId != nil\n}\n\nfunc (x *Vulnerability) ClearMainId() {\n\tx.xxx_hidden_MainId = nil\n}\n\ntype Vulnerability_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The main identifier for this vulnerability, usually a publicly known\n\t// identifier like CVEs and such. If not publicly known, users are expected to\n\t// assign an id on their own.\n\tMainId *VulnerabilityId\n\t// Any related identifiers about this vulnerability, e.g. a CWE weakness.\n\tRelatedId []*VulnerabilityId\n\t// Severity of this vulnerability.\n\tSeverity Severity\n\t// Terse but descriptive sentence about this vulnerability.\n\t// For example: \"Default Password (0p3nm35h) for 'root' Account.\".\n\tTitle string\n\t// Verbose description of this vulnerability.\n\tDescription string\n\t// Optional. Verbose recommended solution(s).\n\tRecommendation string\n\t// Optional. The CVSS v2 score of this vulnerability.\n\tCvssV2 string\n\t// Optional. The CVSS v3 score of this vulnerability.\n\tCvssV3 string\n\t// Any additional technical details about this vulnerability.\n\tAdditionalDetails []*AdditionalDetail\n}\n\nfunc (b0 Vulnerability_builder) Build() *Vulnerability {\n\tm0 := &Vulnerability{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_MainId = b.MainId\n\tx.xxx_hidden_RelatedId = &b.RelatedId\n\tx.xxx_hidden_Severity = b.Severity\n\tx.xxx_hidden_Title = b.Title\n\tx.xxx_hidden_Description = b.Description\n\tx.xxx_hidden_Recommendation = b.Recommendation\n\tx.xxx_hidden_CvssV2 = b.CvssV2\n\tx.xxx_hidden_CvssV3 = b.CvssV3\n\tx.xxx_hidden_AdditionalDetails = &b.AdditionalDetails\n\treturn m0\n}\n\n// A list of vulnerabilities. Used mostly for export purposes.\ntype VulnerabilityList struct {\n\tstate                      protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Vulnerabilities *[]*Vulnerability      `protobuf:\"bytes,1,rep,name=vulnerabilities,proto3\"`\n\tunknownFields              protoimpl.UnknownFields\n\tsizeCache                  protoimpl.SizeCache\n}\n\nfunc (x *VulnerabilityList) Reset() {\n\t*x = VulnerabilityList{}\n\tmi := &file_vulnerability_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VulnerabilityList) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VulnerabilityList) ProtoMessage() {}\n\nfunc (x *VulnerabilityList) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *VulnerabilityList) GetVulnerabilities() []*Vulnerability {\n\tif x != nil {\n\t\tif x.xxx_hidden_Vulnerabilities != nil {\n\t\t\treturn *x.xxx_hidden_Vulnerabilities\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *VulnerabilityList) SetVulnerabilities(v []*Vulnerability) {\n\tx.xxx_hidden_Vulnerabilities = &v\n}\n\ntype VulnerabilityList_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tVulnerabilities []*Vulnerability\n}\n\nfunc (b0 VulnerabilityList_builder) Build() *VulnerabilityList {\n\tm0 := &VulnerabilityList{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Vulnerabilities = &b.Vulnerabilities\n\treturn m0\n}\n\n// Additional details regarding a vulnerability can be stored here. Prefers to\n// use the existing structured data when possible, otherwise store the raw data\n// as a blob.\ntype AdditionalDetail struct {\n\tstate                  protoimpl.MessageState    `protogen:\"opaque.v1\"`\n\txxx_hidden_Description string                    `protobuf:\"bytes,1,opt,name=description,proto3\"`\n\txxx_hidden_Detail      isAdditionalDetail_Detail `protobuf_oneof:\"detail\"`\n\tunknownFields          protoimpl.UnknownFields\n\tsizeCache              protoimpl.SizeCache\n}\n\nfunc (x *AdditionalDetail) Reset() {\n\t*x = AdditionalDetail{}\n\tmi := &file_vulnerability_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AdditionalDetail) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AdditionalDetail) ProtoMessage() {}\n\nfunc (x *AdditionalDetail) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *AdditionalDetail) GetDescription() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *AdditionalDetail) GetBlobData() *BlobData {\n\tif x != nil {\n\t\tif x, ok := x.xxx_hidden_Detail.(*additionalDetail_BlobData); ok {\n\t\t\treturn x.BlobData\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *AdditionalDetail) GetTextData() *TextData {\n\tif x != nil {\n\t\tif x, ok := x.xxx_hidden_Detail.(*additionalDetail_TextData); ok {\n\t\t\treturn x.TextData\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *AdditionalDetail) GetCredential() *Credential {\n\tif x != nil {\n\t\tif x, ok := x.xxx_hidden_Detail.(*additionalDetail_Credential); ok {\n\t\t\treturn x.Credential\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *AdditionalDetail) GetCredentials() *Credentials {\n\tif x != nil {\n\t\tif x, ok := x.xxx_hidden_Detail.(*additionalDetail_Credentials); ok {\n\t\t\treturn x.Credentials\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *AdditionalDetail) SetDescription(v string) {\n\tx.xxx_hidden_Description = v\n}\n\nfunc (x *AdditionalDetail) SetBlobData(v *BlobData) {\n\tif v == nil {\n\t\tx.xxx_hidden_Detail = nil\n\t\treturn\n\t}\n\tx.xxx_hidden_Detail = &additionalDetail_BlobData{v}\n}\n\nfunc (x *AdditionalDetail) SetTextData(v *TextData) {\n\tif v == nil {\n\t\tx.xxx_hidden_Detail = nil\n\t\treturn\n\t}\n\tx.xxx_hidden_Detail = &additionalDetail_TextData{v}\n}\n\nfunc (x *AdditionalDetail) SetCredential(v *Credential) {\n\tif v == nil {\n\t\tx.xxx_hidden_Detail = nil\n\t\treturn\n\t}\n\tx.xxx_hidden_Detail = &additionalDetail_Credential{v}\n}\n\nfunc (x *AdditionalDetail) SetCredentials(v *Credentials) {\n\tif v == nil {\n\t\tx.xxx_hidden_Detail = nil\n\t\treturn\n\t}\n\tx.xxx_hidden_Detail = &additionalDetail_Credentials{v}\n}\n\nfunc (x *AdditionalDetail) HasDetail() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_Detail != nil\n}\n\nfunc (x *AdditionalDetail) HasBlobData() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\t_, ok := x.xxx_hidden_Detail.(*additionalDetail_BlobData)\n\treturn ok\n}\n\nfunc (x *AdditionalDetail) HasTextData() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\t_, ok := x.xxx_hidden_Detail.(*additionalDetail_TextData)\n\treturn ok\n}\n\nfunc (x *AdditionalDetail) HasCredential() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\t_, ok := x.xxx_hidden_Detail.(*additionalDetail_Credential)\n\treturn ok\n}\n\nfunc (x *AdditionalDetail) HasCredentials() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\t_, ok := x.xxx_hidden_Detail.(*additionalDetail_Credentials)\n\treturn ok\n}\n\nfunc (x *AdditionalDetail) ClearDetail() {\n\tx.xxx_hidden_Detail = nil\n}\n\nfunc (x *AdditionalDetail) ClearBlobData() {\n\tif _, ok := x.xxx_hidden_Detail.(*additionalDetail_BlobData); ok {\n\t\tx.xxx_hidden_Detail = nil\n\t}\n}\n\nfunc (x *AdditionalDetail) ClearTextData() {\n\tif _, ok := x.xxx_hidden_Detail.(*additionalDetail_TextData); ok {\n\t\tx.xxx_hidden_Detail = nil\n\t}\n}\n\nfunc (x *AdditionalDetail) ClearCredential() {\n\tif _, ok := x.xxx_hidden_Detail.(*additionalDetail_Credential); ok {\n\t\tx.xxx_hidden_Detail = nil\n\t}\n}\n\nfunc (x *AdditionalDetail) ClearCredentials() {\n\tif _, ok := x.xxx_hidden_Detail.(*additionalDetail_Credentials); ok {\n\t\tx.xxx_hidden_Detail = nil\n\t}\n}\n\nconst AdditionalDetail_Detail_not_set_case case_AdditionalDetail_Detail = 0\nconst AdditionalDetail_BlobData_case case_AdditionalDetail_Detail = 2\nconst AdditionalDetail_TextData_case case_AdditionalDetail_Detail = 3\nconst AdditionalDetail_Credential_case case_AdditionalDetail_Detail = 4\nconst AdditionalDetail_Credentials_case case_AdditionalDetail_Detail = 5\n\nfunc (x *AdditionalDetail) WhichDetail() case_AdditionalDetail_Detail {\n\tif x == nil {\n\t\treturn AdditionalDetail_Detail_not_set_case\n\t}\n\tswitch x.xxx_hidden_Detail.(type) {\n\tcase *additionalDetail_BlobData:\n\t\treturn AdditionalDetail_BlobData_case\n\tcase *additionalDetail_TextData:\n\t\treturn AdditionalDetail_TextData_case\n\tcase *additionalDetail_Credential:\n\t\treturn AdditionalDetail_Credential_case\n\tcase *additionalDetail_Credentials:\n\t\treturn AdditionalDetail_Credentials_case\n\tdefault:\n\t\treturn AdditionalDetail_Detail_not_set_case\n\t}\n}\n\ntype AdditionalDetail_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tDescription string\n\t// Fields of oneof xxx_hidden_Detail:\n\tBlobData    *BlobData\n\tTextData    *TextData\n\tCredential  *Credential\n\tCredentials *Credentials\n\t// -- end of xxx_hidden_Detail\n}\n\nfunc (b0 AdditionalDetail_builder) Build() *AdditionalDetail {\n\tm0 := &AdditionalDetail{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Description = b.Description\n\tif b.BlobData != nil {\n\t\tx.xxx_hidden_Detail = &additionalDetail_BlobData{b.BlobData}\n\t}\n\tif b.TextData != nil {\n\t\tx.xxx_hidden_Detail = &additionalDetail_TextData{b.TextData}\n\t}\n\tif b.Credential != nil {\n\t\tx.xxx_hidden_Detail = &additionalDetail_Credential{b.Credential}\n\t}\n\tif b.Credentials != nil {\n\t\tx.xxx_hidden_Detail = &additionalDetail_Credentials{b.Credentials}\n\t}\n\treturn m0\n}\n\ntype case_AdditionalDetail_Detail protoreflect.FieldNumber\n\nfunc (x case_AdditionalDetail_Detail) String() string {\n\tmd := file_vulnerability_proto_msgTypes[3].Descriptor()\n\tif x == 0 {\n\t\treturn \"not set\"\n\t}\n\treturn protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x))\n}\n\ntype isAdditionalDetail_Detail interface {\n\tisAdditionalDetail_Detail()\n}\n\ntype additionalDetail_BlobData struct {\n\tBlobData *BlobData `protobuf:\"bytes,2,opt,name=blob_data,json=blobData,proto3,oneof\"`\n}\n\ntype additionalDetail_TextData struct {\n\tTextData *TextData `protobuf:\"bytes,3,opt,name=text_data,json=textData,proto3,oneof\"`\n}\n\ntype additionalDetail_Credential struct {\n\tCredential *Credential `protobuf:\"bytes,4,opt,name=credential,proto3,oneof\"`\n}\n\ntype additionalDetail_Credentials struct {\n\tCredentials *Credentials `protobuf:\"bytes,5,opt,name=credentials,proto3,oneof\"`\n}\n\nfunc (*additionalDetail_BlobData) isAdditionalDetail_Detail() {}\n\nfunc (*additionalDetail_TextData) isAdditionalDetail_Detail() {}\n\nfunc (*additionalDetail_Credential) isAdditionalDetail_Detail() {}\n\nfunc (*additionalDetail_Credentials) isAdditionalDetail_Detail() {}\n\n// A piece of arbitrary binary data.\ntype BlobData struct {\n\tstate           protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Data []byte                 `protobuf:\"bytes,1,opt,name=data,proto3\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *BlobData) Reset() {\n\t*x = BlobData{}\n\tmi := &file_vulnerability_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *BlobData) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*BlobData) ProtoMessage() {}\n\nfunc (x *BlobData) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *BlobData) GetData() []byte {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Data\n\t}\n\treturn nil\n}\n\nfunc (x *BlobData) SetData(v []byte) {\n\tif v == nil {\n\t\tv = []byte{}\n\t}\n\tx.xxx_hidden_Data = v\n}\n\ntype BlobData_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tData []byte\n}\n\nfunc (b0 BlobData_builder) Build() *BlobData {\n\tm0 := &BlobData{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Data = b.Data\n\treturn m0\n}\n\n// A piece of arbitrary UTF-8 encoded text data.\ntype TextData struct {\n\tstate           protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Text string                 `protobuf:\"bytes,1,opt,name=text,proto3\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *TextData) Reset() {\n\t*x = TextData{}\n\tmi := &file_vulnerability_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TextData) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TextData) ProtoMessage() {}\n\nfunc (x *TextData) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *TextData) GetText() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Text\n\t}\n\treturn \"\"\n}\n\nfunc (x *TextData) SetText(v string) {\n\tx.xxx_hidden_Text = v\n}\n\ntype TextData_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tText string\n}\n\nfunc (b0 TextData_builder) Build() *TextData {\n\tm0 := &TextData{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Text = b.Text\n\treturn m0\n}\n\n// Credential for a vulnerable network service.\ntype Credential struct {\n\tstate               protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Username string                 `protobuf:\"bytes,1,opt,name=username,proto3\"`\n\txxx_hidden_Password string                 `protobuf:\"bytes,2,opt,name=password,proto3\"`\n\tunknownFields       protoimpl.UnknownFields\n\tsizeCache           protoimpl.SizeCache\n}\n\nfunc (x *Credential) Reset() {\n\t*x = Credential{}\n\tmi := &file_vulnerability_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Credential) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Credential) ProtoMessage() {}\n\nfunc (x *Credential) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *Credential) GetUsername() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *Credential) GetPassword() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Password\n\t}\n\treturn \"\"\n}\n\nfunc (x *Credential) SetUsername(v string) {\n\tx.xxx_hidden_Username = v\n}\n\nfunc (x *Credential) SetPassword(v string) {\n\tx.xxx_hidden_Password = v\n}\n\ntype Credential_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tUsername string\n\tPassword string\n}\n\nfunc (b0 Credential_builder) Build() *Credential {\n\tm0 := &Credential{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Username = b.Username\n\tx.xxx_hidden_Password = b.Password\n\treturn m0\n}\n\n// A set of credentials for a vulnerable network service.\ntype Credentials struct {\n\tstate                 protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Credential *[]*Credential         `protobuf:\"bytes,1,rep,name=credential,proto3\"`\n\tunknownFields         protoimpl.UnknownFields\n\tsizeCache             protoimpl.SizeCache\n}\n\nfunc (x *Credentials) Reset() {\n\t*x = Credentials{}\n\tmi := &file_vulnerability_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Credentials) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Credentials) ProtoMessage() {}\n\nfunc (x *Credentials) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *Credentials) GetCredential() []*Credential {\n\tif x != nil {\n\t\tif x.xxx_hidden_Credential != nil {\n\t\t\treturn *x.xxx_hidden_Credential\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *Credentials) SetCredential(v []*Credential) {\n\tx.xxx_hidden_Credential = &v\n}\n\ntype Credentials_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tCredential []*Credential\n}\n\nfunc (b0 Credentials_builder) Build() *Credentials {\n\tm0 := &Credentials{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Credential = &b.Credential\n\treturn m0\n}\n\nvar File_vulnerability_proto protoreflect.FileDescriptor\n\nconst file_vulnerability_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x13vulnerability.proto\\x12\\rtsunami.proto\\\"Y\\n\" +\n\t\"\\x0fVulnerabilityId\\x12\\x1c\\n\" +\n\t\"\\tpublisher\\x18\\x01 \\x01(\\tR\\tpublisher\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value\\x12\\x12\\n\" +\n\t\"\\x04link\\x18\\x03 \\x01(\\tR\\x04link\\\"\\x9e\\x03\\n\" +\n\t\"\\rVulnerability\\x127\\n\" +\n\t\"\\amain_id\\x18\\x01 \\x01(\\v2\\x1e.tsunami.proto.VulnerabilityIdR\\x06mainId\\x12=\\n\" +\n\t\"\\n\" +\n\t\"related_id\\x18\\x02 \\x03(\\v2\\x1e.tsunami.proto.VulnerabilityIdR\\trelatedId\\x123\\n\" +\n\t\"\\bseverity\\x18\\x03 \\x01(\\x0e2\\x17.tsunami.proto.SeverityR\\bseverity\\x12\\x14\\n\" +\n\t\"\\x05title\\x18\\x04 \\x01(\\tR\\x05title\\x12 \\n\" +\n\t\"\\vdescription\\x18\\x05 \\x01(\\tR\\vdescription\\x12&\\n\" +\n\t\"\\x0erecommendation\\x18\\x06 \\x01(\\tR\\x0erecommendation\\x12\\x17\\n\" +\n\t\"\\acvss_v2\\x18\\a \\x01(\\tR\\x06cvssV2\\x12\\x17\\n\" +\n\t\"\\acvss_v3\\x18\\b \\x01(\\tR\\x06cvssV3\\x12N\\n\" +\n\t\"\\x12additional_details\\x18\\t \\x03(\\v2\\x1f.tsunami.proto.AdditionalDetailR\\x11additionalDetails\\\"[\\n\" +\n\t\"\\x11VulnerabilityList\\x12F\\n\" +\n\t\"\\x0fvulnerabilities\\x18\\x01 \\x03(\\v2\\x1c.tsunami.proto.VulnerabilityR\\x0fvulnerabilities\\\"\\xab\\x02\\n\" +\n\t\"\\x10AdditionalDetail\\x12 \\n\" +\n\t\"\\vdescription\\x18\\x01 \\x01(\\tR\\vdescription\\x126\\n\" +\n\t\"\\tblob_data\\x18\\x02 \\x01(\\v2\\x17.tsunami.proto.BlobDataH\\x00R\\bblobData\\x126\\n\" +\n\t\"\\ttext_data\\x18\\x03 \\x01(\\v2\\x17.tsunami.proto.TextDataH\\x00R\\btextData\\x12;\\n\" +\n\t\"\\n\" +\n\t\"credential\\x18\\x04 \\x01(\\v2\\x19.tsunami.proto.CredentialH\\x00R\\n\" +\n\t\"credential\\x12>\\n\" +\n\t\"\\vcredentials\\x18\\x05 \\x01(\\v2\\x1a.tsunami.proto.CredentialsH\\x00R\\vcredentialsB\\b\\n\" +\n\t\"\\x06detail\\\"\\x1e\\n\" +\n\t\"\\bBlobData\\x12\\x12\\n\" +\n\t\"\\x04data\\x18\\x01 \\x01(\\fR\\x04data\\\"\\x1e\\n\" +\n\t\"\\bTextData\\x12\\x12\\n\" +\n\t\"\\x04text\\x18\\x01 \\x01(\\tR\\x04text\\\"D\\n\" +\n\t\"\\n\" +\n\t\"Credential\\x12\\x1a\\n\" +\n\t\"\\busername\\x18\\x01 \\x01(\\tR\\busername\\x12\\x1a\\n\" +\n\t\"\\bpassword\\x18\\x02 \\x01(\\tR\\bpassword\\\"H\\n\" +\n\t\"\\vCredentials\\x129\\n\" +\n\t\"\\n\" +\n\t\"credential\\x18\\x01 \\x03(\\v2\\x19.tsunami.proto.CredentialR\\n\" +\n\t\"credential*^\\n\" +\n\t\"\\bSeverity\\x12\\x18\\n\" +\n\t\"\\x14SEVERITY_UNSPECIFIED\\x10\\x00\\x12\\v\\n\" +\n\t\"\\aMINIMAL\\x10\\x01\\x12\\a\\n\" +\n\t\"\\x03LOW\\x10\\x02\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06MEDIUM\\x10\\x03\\x12\\b\\n\" +\n\t\"\\x04HIGH\\x10\\x04\\x12\\f\\n\" +\n\t\"\\bCRITICAL\\x10\\x05B}\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x13VulnerabilityProtosP\\x01ZJgithub.com/google/tsunami-security-scanner/proto/go/vulnerability_go_protob\\x06proto3\"\n\nvar file_vulnerability_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_vulnerability_proto_msgTypes = make([]protoimpl.MessageInfo, 8)\nvar file_vulnerability_proto_goTypes = []any{\n\t(Severity)(0),             // 0: tsunami.proto.Severity\n\t(*VulnerabilityId)(nil),   // 1: tsunami.proto.VulnerabilityId\n\t(*Vulnerability)(nil),     // 2: tsunami.proto.Vulnerability\n\t(*VulnerabilityList)(nil), // 3: tsunami.proto.VulnerabilityList\n\t(*AdditionalDetail)(nil),  // 4: tsunami.proto.AdditionalDetail\n\t(*BlobData)(nil),          // 5: tsunami.proto.BlobData\n\t(*TextData)(nil),          // 6: tsunami.proto.TextData\n\t(*Credential)(nil),        // 7: tsunami.proto.Credential\n\t(*Credentials)(nil),       // 8: tsunami.proto.Credentials\n}\nvar file_vulnerability_proto_depIdxs = []int32{\n\t1,  // 0: tsunami.proto.Vulnerability.main_id:type_name -> tsunami.proto.VulnerabilityId\n\t1,  // 1: tsunami.proto.Vulnerability.related_id:type_name -> tsunami.proto.VulnerabilityId\n\t0,  // 2: tsunami.proto.Vulnerability.severity:type_name -> tsunami.proto.Severity\n\t4,  // 3: tsunami.proto.Vulnerability.additional_details:type_name -> tsunami.proto.AdditionalDetail\n\t2,  // 4: tsunami.proto.VulnerabilityList.vulnerabilities:type_name -> tsunami.proto.Vulnerability\n\t5,  // 5: tsunami.proto.AdditionalDetail.blob_data:type_name -> tsunami.proto.BlobData\n\t6,  // 6: tsunami.proto.AdditionalDetail.text_data:type_name -> tsunami.proto.TextData\n\t7,  // 7: tsunami.proto.AdditionalDetail.credential:type_name -> tsunami.proto.Credential\n\t8,  // 8: tsunami.proto.AdditionalDetail.credentials:type_name -> tsunami.proto.Credentials\n\t7,  // 9: tsunami.proto.Credentials.credential:type_name -> tsunami.proto.Credential\n\t10, // [10:10] is the sub-list for method output_type\n\t10, // [10:10] is the sub-list for method input_type\n\t10, // [10:10] is the sub-list for extension type_name\n\t10, // [10:10] is the sub-list for extension extendee\n\t0,  // [0:10] is the sub-list for field type_name\n}\n\nfunc init() { file_vulnerability_proto_init() }\nfunc file_vulnerability_proto_init() {\n\tif File_vulnerability_proto != nil {\n\t\treturn\n\t}\n\tfile_vulnerability_proto_msgTypes[3].OneofWrappers = []any{\n\t\t(*additionalDetail_BlobData)(nil),\n\t\t(*additionalDetail_TextData)(nil),\n\t\t(*additionalDetail_Credential)(nil),\n\t\t(*additionalDetail_Credentials)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_vulnerability_proto_rawDesc), len(file_vulnerability_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   8,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_vulnerability_proto_goTypes,\n\t\tDependencyIndexes: file_vulnerability_proto_depIdxs,\n\t\tEnumInfos:         file_vulnerability_proto_enumTypes,\n\t\tMessageInfos:      file_vulnerability_proto_msgTypes,\n\t}.Build()\n\tFile_vulnerability_proto = out.File\n\tfile_vulnerability_proto_goTypes = nil\n\tfile_vulnerability_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/go/web_crawl_go_proto/web_crawl.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for the web crawler.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v3.21.12\n// source: web_crawl.proto\n\npackage web_crawl_go_proto\n\nimport (\n\tnetwork_go_proto \"github.com/google/tsunami-security-scanner/proto/go/network_go_proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// The type of content stored in the CrawlResult.\ntype CrawlContentType int32\n\nconst (\n\tCrawlContentType_CONTENT_TYPE_UNSPECIFIED CrawlContentType = 0\n\tCrawlContentType_CONTENT_TYPE_RAW         CrawlContentType = 1\n\tCrawlContentType_CONTENT_TYPE_HASH        CrawlContentType = 2\n)\n\n// Enum value maps for CrawlContentType.\nvar (\n\tCrawlContentType_name = map[int32]string{\n\t\t0: \"CONTENT_TYPE_UNSPECIFIED\",\n\t\t1: \"CONTENT_TYPE_RAW\",\n\t\t2: \"CONTENT_TYPE_HASH\",\n\t}\n\tCrawlContentType_value = map[string]int32{\n\t\t\"CONTENT_TYPE_UNSPECIFIED\": 0,\n\t\t\"CONTENT_TYPE_RAW\":         1,\n\t\t\"CONTENT_TYPE_HASH\":        2,\n\t}\n)\n\nfunc (x CrawlContentType) Enum() *CrawlContentType {\n\tp := new(CrawlContentType)\n\t*p = x\n\treturn p\n}\n\nfunc (x CrawlContentType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (CrawlContentType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_web_crawl_proto_enumTypes[0].Descriptor()\n}\n\nfunc (CrawlContentType) Type() protoreflect.EnumType {\n\treturn &file_web_crawl_proto_enumTypes[0]\n}\n\nfunc (x CrawlContentType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Next ID: 7\ntype CrawlConfig struct {\n\tstate                              protoimpl.MessageState            `protogen:\"opaque.v1\"`\n\txxx_hidden_SeedingUrls             []string                          `protobuf:\"bytes,1,rep,name=seeding_urls,json=seedingUrls,proto3\"`\n\txxx_hidden_MaxDepth                int32                             `protobuf:\"varint,2,opt,name=max_depth,json=maxDepth,proto3\"`\n\txxx_hidden_Scopes                  *[]*CrawlConfig_Scope             `protobuf:\"bytes,3,rep,name=scopes,proto3\"`\n\txxx_hidden_ShouldEnforceScopeCheck bool                              `protobuf:\"varint,5,opt,name=should_enforce_scope_check,json=shouldEnforceScopeCheck,proto3\"`\n\txxx_hidden_NetworkEndpoint         *network_go_proto.NetworkEndpoint `protobuf:\"bytes,6,opt,name=network_endpoint,json=networkEndpoint,proto3\"`\n\tunknownFields                      protoimpl.UnknownFields\n\tsizeCache                          protoimpl.SizeCache\n}\n\nfunc (x *CrawlConfig) Reset() {\n\t*x = CrawlConfig{}\n\tmi := &file_web_crawl_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CrawlConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CrawlConfig) ProtoMessage() {}\n\nfunc (x *CrawlConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_web_crawl_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *CrawlConfig) GetSeedingUrls() []string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_SeedingUrls\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlConfig) GetMaxDepth() int32 {\n\tif x != nil {\n\t\treturn x.xxx_hidden_MaxDepth\n\t}\n\treturn 0\n}\n\nfunc (x *CrawlConfig) GetScopes() []*CrawlConfig_Scope {\n\tif x != nil {\n\t\tif x.xxx_hidden_Scopes != nil {\n\t\t\treturn *x.xxx_hidden_Scopes\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlConfig) GetShouldEnforceScopeCheck() bool {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ShouldEnforceScopeCheck\n\t}\n\treturn false\n}\n\nfunc (x *CrawlConfig) GetNetworkEndpoint() *network_go_proto.NetworkEndpoint {\n\tif x != nil {\n\t\treturn x.xxx_hidden_NetworkEndpoint\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlConfig) SetSeedingUrls(v []string) {\n\tx.xxx_hidden_SeedingUrls = v\n}\n\nfunc (x *CrawlConfig) SetMaxDepth(v int32) {\n\tx.xxx_hidden_MaxDepth = v\n}\n\nfunc (x *CrawlConfig) SetScopes(v []*CrawlConfig_Scope) {\n\tx.xxx_hidden_Scopes = &v\n}\n\nfunc (x *CrawlConfig) SetShouldEnforceScopeCheck(v bool) {\n\tx.xxx_hidden_ShouldEnforceScopeCheck = v\n}\n\nfunc (x *CrawlConfig) SetNetworkEndpoint(v *network_go_proto.NetworkEndpoint) {\n\tx.xxx_hidden_NetworkEndpoint = v\n}\n\nfunc (x *CrawlConfig) HasNetworkEndpoint() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_NetworkEndpoint != nil\n}\n\nfunc (x *CrawlConfig) ClearNetworkEndpoint() {\n\tx.xxx_hidden_NetworkEndpoint = nil\n}\n\ntype CrawlConfig_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// Starting points of a web crawl.\n\t// Required.\n\tSeedingUrls []string\n\t// The maximum depth of a web crawl.\n\t// Required.\n\tMaxDepth int32\n\t// Allowed crawling scopes.\n\t// Optional. When empty, scopes are autogenerated from seeding_urls.\n\tScopes []*CrawlConfig_Scope\n\t// Whether crawling scope check should be enforced.\n\t// Optional.\n\tShouldEnforceScopeCheck bool\n\t// The network endpoint to be crawled.\n\t// Required.\n\tNetworkEndpoint *network_go_proto.NetworkEndpoint\n}\n\nfunc (b0 CrawlConfig_builder) Build() *CrawlConfig {\n\tm0 := &CrawlConfig{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_SeedingUrls = b.SeedingUrls\n\tx.xxx_hidden_MaxDepth = b.MaxDepth\n\tx.xxx_hidden_Scopes = &b.Scopes\n\tx.xxx_hidden_ShouldEnforceScopeCheck = b.ShouldEnforceScopeCheck\n\tx.xxx_hidden_NetworkEndpoint = b.NetworkEndpoint\n\treturn m0\n}\n\ntype CrawlTarget struct {\n\tstate                      protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Url             string                 `protobuf:\"bytes,1,opt,name=url,proto3\"`\n\txxx_hidden_HttpMethod      string                 `protobuf:\"bytes,2,opt,name=http_method,json=httpMethod,proto3\"`\n\txxx_hidden_HttpRequestBody []byte                 `protobuf:\"bytes,3,opt,name=http_request_body,json=httpRequestBody,proto3\"`\n\tunknownFields              protoimpl.UnknownFields\n\tsizeCache                  protoimpl.SizeCache\n}\n\nfunc (x *CrawlTarget) Reset() {\n\t*x = CrawlTarget{}\n\tmi := &file_web_crawl_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CrawlTarget) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CrawlTarget) ProtoMessage() {}\n\nfunc (x *CrawlTarget) ProtoReflect() protoreflect.Message {\n\tmi := &file_web_crawl_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *CrawlTarget) GetUrl() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Url\n\t}\n\treturn \"\"\n}\n\nfunc (x *CrawlTarget) GetHttpMethod() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_HttpMethod\n\t}\n\treturn \"\"\n}\n\nfunc (x *CrawlTarget) GetHttpRequestBody() []byte {\n\tif x != nil {\n\t\treturn x.xxx_hidden_HttpRequestBody\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlTarget) SetUrl(v string) {\n\tx.xxx_hidden_Url = v\n}\n\nfunc (x *CrawlTarget) SetHttpMethod(v string) {\n\tx.xxx_hidden_HttpMethod = v\n}\n\nfunc (x *CrawlTarget) SetHttpRequestBody(v []byte) {\n\tif v == nil {\n\t\tv = []byte{}\n\t}\n\tx.xxx_hidden_HttpRequestBody = v\n}\n\ntype CrawlTarget_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The URL pointing to the document.\n\tUrl string\n\t// HTTP method to reach the url. Value must be in all upper case, like \"GET\".\n\tHttpMethod string\n\t// An optional HTTP request body sent to the crawl URL.\n\tHttpRequestBody []byte\n}\n\nfunc (b0 CrawlTarget_builder) Build() *CrawlTarget {\n\tm0 := &CrawlTarget{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Url = b.Url\n\tx.xxx_hidden_HttpMethod = b.HttpMethod\n\tx.xxx_hidden_HttpRequestBody = b.HttpRequestBody\n\treturn m0\n}\n\n// Represents an HTTP header.\ntype HttpHeader struct {\n\tstate            protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Key   string                 `protobuf:\"bytes,1,opt,name=key,proto3\"`\n\txxx_hidden_Value string                 `protobuf:\"bytes,2,opt,name=value,proto3\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *HttpHeader) Reset() {\n\t*x = HttpHeader{}\n\tmi := &file_web_crawl_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *HttpHeader) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HttpHeader) ProtoMessage() {}\n\nfunc (x *HttpHeader) ProtoReflect() protoreflect.Message {\n\tmi := &file_web_crawl_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *HttpHeader) GetKey() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *HttpHeader) GetValue() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Value\n\t}\n\treturn \"\"\n}\n\nfunc (x *HttpHeader) SetKey(v string) {\n\tx.xxx_hidden_Key = v\n}\n\nfunc (x *HttpHeader) SetValue(v string) {\n\tx.xxx_hidden_Value = v\n}\n\ntype HttpHeader_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\tKey   string\n\tValue string\n}\n\nfunc (b0 HttpHeader_builder) Build() *HttpHeader {\n\tm0 := &HttpHeader{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Key = b.Key\n\tx.xxx_hidden_Value = b.Value\n\treturn m0\n}\n\ntype CrawlResult struct {\n\tstate                       protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_CrawlTarget      *CrawlTarget           `protobuf:\"bytes,1,opt,name=crawl_target,json=crawlTarget,proto3\"`\n\txxx_hidden_CrawlDepth       int32                  `protobuf:\"varint,2,opt,name=crawl_depth,json=crawlDepth,proto3\"`\n\txxx_hidden_ResponseCode     int32                  `protobuf:\"varint,3,opt,name=response_code,json=responseCode,proto3\"`\n\txxx_hidden_ContentType      string                 `protobuf:\"bytes,4,opt,name=content_type,json=contentType,proto3\"`\n\txxx_hidden_Content          []byte                 `protobuf:\"bytes,5,opt,name=content,proto3\"`\n\txxx_hidden_ResponseHeaders  *[]*HttpHeader         `protobuf:\"bytes,6,rep,name=response_headers,json=responseHeaders,proto3\"`\n\txxx_hidden_CrawlContentType CrawlContentType       `protobuf:\"varint,7,opt,name=crawl_content_type,json=crawlContentType,proto3,enum=tsunami.proto.CrawlContentType\"`\n\tunknownFields               protoimpl.UnknownFields\n\tsizeCache                   protoimpl.SizeCache\n}\n\nfunc (x *CrawlResult) Reset() {\n\t*x = CrawlResult{}\n\tmi := &file_web_crawl_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CrawlResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CrawlResult) ProtoMessage() {}\n\nfunc (x *CrawlResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_web_crawl_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *CrawlResult) GetCrawlTarget() *CrawlTarget {\n\tif x != nil {\n\t\treturn x.xxx_hidden_CrawlTarget\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlResult) GetCrawlDepth() int32 {\n\tif x != nil {\n\t\treturn x.xxx_hidden_CrawlDepth\n\t}\n\treturn 0\n}\n\nfunc (x *CrawlResult) GetResponseCode() int32 {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ResponseCode\n\t}\n\treturn 0\n}\n\nfunc (x *CrawlResult) GetContentType() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_ContentType\n\t}\n\treturn \"\"\n}\n\nfunc (x *CrawlResult) GetContent() []byte {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Content\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlResult) GetResponseHeaders() []*HttpHeader {\n\tif x != nil {\n\t\tif x.xxx_hidden_ResponseHeaders != nil {\n\t\t\treturn *x.xxx_hidden_ResponseHeaders\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlResult) GetCrawlContentType() CrawlContentType {\n\tif x != nil {\n\t\treturn x.xxx_hidden_CrawlContentType\n\t}\n\treturn CrawlContentType_CONTENT_TYPE_UNSPECIFIED\n}\n\nfunc (x *CrawlResult) SetCrawlTarget(v *CrawlTarget) {\n\tx.xxx_hidden_CrawlTarget = v\n}\n\nfunc (x *CrawlResult) SetCrawlDepth(v int32) {\n\tx.xxx_hidden_CrawlDepth = v\n}\n\nfunc (x *CrawlResult) SetResponseCode(v int32) {\n\tx.xxx_hidden_ResponseCode = v\n}\n\nfunc (x *CrawlResult) SetContentType(v string) {\n\tx.xxx_hidden_ContentType = v\n}\n\nfunc (x *CrawlResult) SetContent(v []byte) {\n\tif v == nil {\n\t\tv = []byte{}\n\t}\n\tx.xxx_hidden_Content = v\n}\n\nfunc (x *CrawlResult) SetResponseHeaders(v []*HttpHeader) {\n\tx.xxx_hidden_ResponseHeaders = &v\n}\n\nfunc (x *CrawlResult) SetCrawlContentType(v CrawlContentType) {\n\tx.xxx_hidden_CrawlContentType = v\n}\n\nfunc (x *CrawlResult) HasCrawlTarget() bool {\n\tif x == nil {\n\t\treturn false\n\t}\n\treturn x.xxx_hidden_CrawlTarget != nil\n}\n\nfunc (x *CrawlResult) ClearCrawlTarget() {\n\tx.xxx_hidden_CrawlTarget = nil\n}\n\ntype CrawlResult_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The target visited by the crawler.\n\tCrawlTarget *CrawlTarget\n\t// Depth at which the target was visited.\n\tCrawlDepth int32\n\t// Response code from the crawled target.\n\tResponseCode int32\n\t// Content type of the resource served at the crawl target.\n\tContentType string\n\t// The content of the resource served at the crawl target.\n\tContent []byte\n\t// Http headers of the response\n\tResponseHeaders []*HttpHeader\n\t// The type of content stored in the crawl_results. By default, the whole\n\t// response body is stored (RAW). But some configuration can request storing\n\t// only a hash of the response body (HASH).\n\tCrawlContentType CrawlContentType\n}\n\nfunc (b0 CrawlResult_builder) Build() *CrawlResult {\n\tm0 := &CrawlResult{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_CrawlTarget = b.CrawlTarget\n\tx.xxx_hidden_CrawlDepth = b.CrawlDepth\n\tx.xxx_hidden_ResponseCode = b.ResponseCode\n\tx.xxx_hidden_ContentType = b.ContentType\n\tx.xxx_hidden_Content = b.Content\n\tx.xxx_hidden_ResponseHeaders = &b.ResponseHeaders\n\tx.xxx_hidden_CrawlContentType = b.CrawlContentType\n\treturn m0\n}\n\n// The crawler should only interact with web resources under certain scopes.\ntype CrawlConfig_Scope struct {\n\tstate             protoimpl.MessageState `protogen:\"opaque.v1\"`\n\txxx_hidden_Domain string                 `protobuf:\"bytes,1,opt,name=domain,proto3\"`\n\txxx_hidden_Path   string                 `protobuf:\"bytes,2,opt,name=path,proto3\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *CrawlConfig_Scope) Reset() {\n\t*x = CrawlConfig_Scope{}\n\tmi := &file_web_crawl_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CrawlConfig_Scope) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CrawlConfig_Scope) ProtoMessage() {}\n\nfunc (x *CrawlConfig_Scope) ProtoReflect() protoreflect.Message {\n\tmi := &file_web_crawl_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\nfunc (x *CrawlConfig_Scope) GetDomain() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Domain\n\t}\n\treturn \"\"\n}\n\nfunc (x *CrawlConfig_Scope) GetPath() string {\n\tif x != nil {\n\t\treturn x.xxx_hidden_Path\n\t}\n\treturn \"\"\n}\n\nfunc (x *CrawlConfig_Scope) SetDomain(v string) {\n\tx.xxx_hidden_Domain = v\n}\n\nfunc (x *CrawlConfig_Scope) SetPath(v string) {\n\tx.xxx_hidden_Path = v\n}\n\ntype CrawlConfig_Scope_builder struct {\n\t_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.\n\n\t// The domain of the scope, only URLs that are on the same domain or a\n\t// subdomain will be admitted for crawling. Domain might include a port.\n\t// Required.\n\tDomain string\n\t// The path of the scope, only URLs that are under the same path will be\n\t// admitted for crawling.\n\t// Optional. When empty, all URLs under the same domain are allowed,\n\t// regardless of the paths.\n\tPath string\n}\n\nfunc (b0 CrawlConfig_Scope_builder) Build() *CrawlConfig_Scope {\n\tm0 := &CrawlConfig_Scope{}\n\tb, x := &b0, m0\n\t_, _ = b, x\n\tx.xxx_hidden_Domain = b.Domain\n\tx.xxx_hidden_Path = b.Path\n\treturn m0\n}\n\nvar File_web_crawl_proto protoreflect.FileDescriptor\n\nconst file_web_crawl_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x0fweb_crawl.proto\\x12\\rtsunami.proto\\x1a\\rnetwork.proto\\\"\\xca\\x02\\n\" +\n\t\"\\vCrawlConfig\\x12!\\n\" +\n\t\"\\fseeding_urls\\x18\\x01 \\x03(\\tR\\vseedingUrls\\x12\\x1b\\n\" +\n\t\"\\tmax_depth\\x18\\x02 \\x01(\\x05R\\bmaxDepth\\x128\\n\" +\n\t\"\\x06scopes\\x18\\x03 \\x03(\\v2 .tsunami.proto.CrawlConfig.ScopeR\\x06scopes\\x12;\\n\" +\n\t\"\\x1ashould_enforce_scope_check\\x18\\x05 \\x01(\\bR\\x17shouldEnforceScopeCheck\\x12I\\n\" +\n\t\"\\x10network_endpoint\\x18\\x06 \\x01(\\v2\\x1e.tsunami.proto.NetworkEndpointR\\x0fnetworkEndpoint\\x1a3\\n\" +\n\t\"\\x05Scope\\x12\\x16\\n\" +\n\t\"\\x06domain\\x18\\x01 \\x01(\\tR\\x06domain\\x12\\x12\\n\" +\n\t\"\\x04path\\x18\\x02 \\x01(\\tR\\x04pathJ\\x04\\b\\x04\\x10\\x05\\\"l\\n\" +\n\t\"\\vCrawlTarget\\x12\\x10\\n\" +\n\t\"\\x03url\\x18\\x01 \\x01(\\tR\\x03url\\x12\\x1f\\n\" +\n\t\"\\vhttp_method\\x18\\x02 \\x01(\\tR\\n\" +\n\t\"httpMethod\\x12*\\n\" +\n\t\"\\x11http_request_body\\x18\\x03 \\x01(\\fR\\x0fhttpRequestBody\\\"4\\n\" +\n\t\"\\n\" +\n\t\"HttpHeader\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value\\\"\\xe4\\x02\\n\" +\n\t\"\\vCrawlResult\\x12=\\n\" +\n\t\"\\fcrawl_target\\x18\\x01 \\x01(\\v2\\x1a.tsunami.proto.CrawlTargetR\\vcrawlTarget\\x12\\x1f\\n\" +\n\t\"\\vcrawl_depth\\x18\\x02 \\x01(\\x05R\\n\" +\n\t\"crawlDepth\\x12#\\n\" +\n\t\"\\rresponse_code\\x18\\x03 \\x01(\\x05R\\fresponseCode\\x12!\\n\" +\n\t\"\\fcontent_type\\x18\\x04 \\x01(\\tR\\vcontentType\\x12\\x18\\n\" +\n\t\"\\acontent\\x18\\x05 \\x01(\\fR\\acontent\\x12D\\n\" +\n\t\"\\x10response_headers\\x18\\x06 \\x03(\\v2\\x19.tsunami.proto.HttpHeaderR\\x0fresponseHeaders\\x12M\\n\" +\n\t\"\\x12crawl_content_type\\x18\\a \\x01(\\x0e2\\x1f.tsunami.proto.CrawlContentTypeR\\x10crawlContentType*]\\n\" +\n\t\"\\x10CrawlContentType\\x12\\x1c\\n\" +\n\t\"\\x18CONTENT_TYPE_UNSPECIFIED\\x10\\x00\\x12\\x14\\n\" +\n\t\"\\x10CONTENT_TYPE_RAW\\x10\\x01\\x12\\x15\\n\" +\n\t\"\\x11CONTENT_TYPE_HASH\\x10\\x02Bt\\n\" +\n\t\"\\x18com.google.tsunami.protoB\\x0eWebCrawlProtosP\\x01ZFgithub.com/google/tsunami-security-scanner/proto/go/web_crawl_go_protob\\x06proto3\"\n\nvar file_web_crawl_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_web_crawl_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_web_crawl_proto_goTypes = []any{\n\t(CrawlContentType)(0),                    // 0: tsunami.proto.CrawlContentType\n\t(*CrawlConfig)(nil),                      // 1: tsunami.proto.CrawlConfig\n\t(*CrawlTarget)(nil),                      // 2: tsunami.proto.CrawlTarget\n\t(*HttpHeader)(nil),                       // 3: tsunami.proto.HttpHeader\n\t(*CrawlResult)(nil),                      // 4: tsunami.proto.CrawlResult\n\t(*CrawlConfig_Scope)(nil),                // 5: tsunami.proto.CrawlConfig.Scope\n\t(*network_go_proto.NetworkEndpoint)(nil), // 6: tsunami.proto.NetworkEndpoint\n}\nvar file_web_crawl_proto_depIdxs = []int32{\n\t5, // 0: tsunami.proto.CrawlConfig.scopes:type_name -> tsunami.proto.CrawlConfig.Scope\n\t6, // 1: tsunami.proto.CrawlConfig.network_endpoint:type_name -> tsunami.proto.NetworkEndpoint\n\t2, // 2: tsunami.proto.CrawlResult.crawl_target:type_name -> tsunami.proto.CrawlTarget\n\t3, // 3: tsunami.proto.CrawlResult.response_headers:type_name -> tsunami.proto.HttpHeader\n\t0, // 4: tsunami.proto.CrawlResult.crawl_content_type:type_name -> tsunami.proto.CrawlContentType\n\t5, // [5:5] is the sub-list for method output_type\n\t5, // [5:5] is the sub-list for method input_type\n\t5, // [5:5] is the sub-list for extension type_name\n\t5, // [5:5] is the sub-list for extension extendee\n\t0, // [0:5] is the sub-list for field type_name\n}\n\nfunc init() { file_web_crawl_proto_init() }\nfunc file_web_crawl_proto_init() {\n\tif File_web_crawl_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_web_crawl_proto_rawDesc), len(file_web_crawl_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_web_crawl_proto_goTypes,\n\t\tDependencyIndexes: file_web_crawl_proto_depIdxs,\n\t\tEnumInfos:         file_web_crawl_proto_enumTypes,\n\t\tMessageInfos:      file_web_crawl_proto_msgTypes,\n\t}.Build()\n\tFile_web_crawl_proto = out.File\n\tfile_web_crawl_proto_goTypes = nil\n\tfile_web_crawl_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/network.proto",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Data models for describing network related information.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\noption java_multiple_files = true;\noption java_outer_classname = \"NetworkProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/network_go_proto\";\n\n// The address family of an IP address.\nenum AddressFamily {\n  ADDRESS_FAMILY_UNSPECIFIED = 0;\n  IPV4 = 4;\n  IPV6 = 6;\n}\n\n// The IP address of a networking device.\nmessage IpAddress {\n  // The family of the IP address.\n  AddressFamily address_family = 1;\n\n  // A human-readable representation of the IP address, e.g. 127.0.0.1 for IPV4\n  // and 2001:db8:0:1234:0:567:8:1 for IPV6.\n  string address = 2;\n}\n\n// The port that a network service listens to.\nmessage Port {\n  uint32 port_number = 1;\n}\n\n// The hostname of a networking device.\nmessage Hostname {\n  string name = 1;\n}\n\n// A classification of an endpoint for a network device.\nmessage NetworkEndpoint {\n  enum Type {\n    TYPE_UNSPECIFIED = 0;\n\n    // The network endpoint is represented by an IP address.\n    IP = 1;\n\n    // The network endpoint is represented by IP address and port pair.\n    IP_PORT = 2;\n\n    // The network endpoint is represented by a hostname.\n    HOSTNAME = 3;\n\n    // The network endpoint is represented by a hostname and port pair.\n    HOSTNAME_PORT = 4;\n\n    // The network endpoint is represented by an IP address and hostname.\n    IP_HOSTNAME = 5;\n\n    // The network endpoint is represented by an IP address, hostname and port.\n    IP_HOSTNAME_PORT = 6;\n  }\n\n  // Type of the network endpoint.\n  Type type = 1;\n\n  // Optional IP address of a network endpoint. Must be specified when Type is\n  // IP or IP_PORT.\n  IpAddress ip_address = 2;\n\n  // Optional port of a network endpoint. Must be specified when Type is IP_PORT\n  // or HOSTNAME_PORT.\n  Port port = 3;\n\n  // Optional hostname of a network endpoint. Must be specified when Type is\n  // HOSTNAME or HOSTNAME_PORT.\n  Hostname hostname = 4;\n}\n\n// The transport layer protocols.\nenum TransportProtocol {\n  TRANSPORT_PROTOCOL_UNSPECIFIED = 0;\n\n  TCP = 1;\n  UDP = 2;\n  SCTP = 3;\n}\n"
  },
  {
    "path": "proto/network_service.proto",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Data models for describing a network service.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\nimport \"network.proto\";\nimport \"software.proto\";\nimport \"web_crawl.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"NetworkServiceProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/network_service_go_proto\";\n\n// General information about a network service running on a target.\nmessage NetworkService {\n  // The network endpoint where this network service is served.\n  NetworkEndpoint network_endpoint = 1;\n\n  // The transport layer protocol used by the service.\n  TransportProtocol transport_protocol = 2;\n\n  // The name of the network service, following convention in RFC6335. Examples\n  // are like http, telnet, ssh, etc.\n  string service_name = 3;\n\n  // The software that provides the service behind the port.\n  Software software = 4;\n\n  // The complete set of versions of the software.\n  VersionSet version_set = 5;\n\n  // Banners generated by the service.\n  repeated string banner = 6;\n\n  // Context information about this network service.\n  ServiceContext service_context = 7;\n\n  // The detected Common Platform Enumeration (CPE) name for service,\n  // in the uri binding representation, like: cpe:/a:openbsd:openssh:8.4p1\n  repeated string cpes = 8;\n\n  // List of supported SSL versions (e.g. TLSv1, SSLv3, ...) on the service.\n  repeated string supported_ssl_versions = 9;\n\n  // List of supported HTTP methods (e.g. POST, GET, ...) on the service.\n  repeated string supported_http_methods = 10;\n}\n\n// Context information about a specific network service.\nmessage ServiceContext {\n  oneof context {\n    WebServiceContext web_service_context = 1;\n  }\n}\n\n// Context information about a web application.\n// NEXT ID: 5\nmessage WebServiceContext {\n  // The root path of the hosted web application.\n  string application_root = 1;\n\n  // The web application that is serving under the application root.\n  Software software = 2;\n\n  // The detected versions of the web application.\n  VersionSet version_set = 3;\n\n  // Fingerprinter's crawling results for this web service.\n  repeated CrawlResult crawl_results = 4;\n}\n"
  },
  {
    "path": "proto/payload_generator.proto",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Data models utilized by the Tsunami Paylaod Generator\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\nimport 'google/protobuf/wrappers.proto';\n\noption java_multiple_files = true;\noption java_outer_classname = \"PayloadGeneratorProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/payload_generator_go_proto\";\n\n// Attributes utilized by the PayloadGenerator to select a payload\nmessage PayloadGeneratorConfig {\n  // The type of vulnerability the detector is looking for\n  enum VulnerabilityType {\n    // Unspecified vulnerability type\n    VULNERABILITY_TYPE_UNSPECIFIED = 0;\n    // RCE which returns the output of the execution\n    REFLECTIVE_RCE = 1;\n    // RCE which does not return the output of the execution\n    BLIND_RCE = 2;\n    // Server-Side Request Forgery\n    SSRF = 3;\n    // Arbitrary File Write\n    ARBITRARY_FILE_WRITE = 4;\n    // RCE without output of the execution + File Read (needed to get\n    // confirmation string)\n    BLIND_RCE_FILE_READ = 5;\n  }\n\n  // The environment that processes the payload for execution e.g. a PHP-based\n  // target likely wants a payload that is itself PHP code.\n  enum InterpretationEnvironment {\n    // Unspecified interpretation environment type\n    INTERPRETATION_ENVIRONMENT_UNSPECIFIED = 0;\n    // Payload is interpreted within a Linux shell environment\n    LINUX_SHELL = 1;\n    // Payload is interpreted wihin a Java compiler context\n    JAVA = 2;\n    // Payload is interpreted wihin a PHP VM context\n    PHP = 3;\n    // Interpretation environment doesn't matter\n    INTERPRETATION_ANY = 4;\n    // Payload is interpreted wihin crontab\n    LINUX_ROOT_CRONTAB = 5;\n    // Payload is interpreted wihin a Windows shell environment\n    WINDOWS_SHELL = 6;\n    // Payload is interpreted within a JSP shell environment\n    JSP = 7;\n  }\n\n  // The actual runtime environment when the payload is run e.g. while a\n  // PHP-based target wants a PHP-interpretation environment, the actual code\n  // execution may happen via the Linux shell: exec(“echo \\”this is running in\n  // the system.\\””).\n  enum ExecutionEnvironment {\n    // Unspecified execution environment type\n    EXECUTION_ENVIRONMENT_UNSPECIFIED = 0;\n    // Execute within the InterpretationEnvironment\n    EXEC_INTERPRETATION_ENVIRONMENT = 1;\n    // Execution environment doesn't matter\n    EXEC_ANY = 2;\n  }\n\n  VulnerabilityType vulnerability_type = 2;\n\n  InterpretationEnvironment interpretation_environment = 3;\n\n  ExecutionEnvironment execution_environment = 4;\n}\n\n// Attributes of a payload. A detector can check these attributes to change its\n// logic based on the payload type.\nmessage PayloadAttributes {\n  // Whether the payload uses the callback server\n  bool uses_callback_server = 1;\n}\n\nenum PayloadValidationType {\n  VALIDATION_TYPE_UNSPECIFIED = 0;\n\n  VALIDATION_REGEX = 1;\n}\n\n// Container type for payload_definitions.yaml\nmessage PayloadLibrary {\n  repeated PayloadDefinition payloads = 1;\n}\n\n// Schema for each entry in payload_definitions.yaml\n// Note: this message uses StringValue and BoolValue because we validate whether\n// each payload definition in the yaml file has the correct fields present.\n// Since empty proto fields are given default values (proto fields are not\n// nullable), we use the wrapped types to check for actual presence.\nmessage PayloadDefinition {\n  // The human-readable string to identify the payload\n  google.protobuf.StringValue name = 1;\n\n  PayloadGeneratorConfig.InterpretationEnvironment interpretation_environment =\n      2;\n  PayloadGeneratorConfig.ExecutionEnvironment execution_environment = 3;\n\n  // All vulnerability types this payload can be used for\n  repeated PayloadGeneratorConfig.VulnerabilityType vulnerability_type = 4;\n\n  // If true, payload_string must contain the $TSUNAMI_PAYLOAD_TOKEN_URL\n  // token. Validation will automatically check against the callback server, so\n  // the validation* fields do not need to be set.\n  google.protobuf.BoolValue uses_callback_server = 5;\n\n  // The actual payload command string. The following special tokens can be\n  // used which will cause the framework to inject dynamic content into the\n  // command:\n  // - $TSUNAMI_PAYLOAD_TOKEN_URL: url for the callback server\n  // - a random string, used to reduce false positives.\n  google.protobuf.StringValue payload_string = 6;\n\n  // The type of validation function for determining if the payload was\n  // executed. Currently, only REGEX is supported.\n  PayloadValidationType validation_type = 7;\n\n  // Required if validation_type == REGEX. Must be compatible with\n  // java.util.regex.Pattern. The string will first be preprocessed before\n  // applied as a regex, replacing any of the following tokens with the\n  // corresponding values supplied by the framework:\n  // - $TSUNAMI_PAYLOAD_TOKEN_RANDOM: a random string, used to reduce false\n  //   positives. The value is guaranteed to be the same as the value supplied\n  //   to payload_string.\n  google.protobuf.StringValue validation_regex = 8;\n}\n"
  },
  {
    "path": "proto/plugin_representation.proto",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Representation of a tsunami plugin definition passed between language\n// servers.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\noption java_multiple_files = true;\noption java_outer_classname = \"PluginRepresentationProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/plugin_representation_go_proto\";\n\n// Represents a PluginDefinition placeholder.\nmessage PluginDefinition {\n  // PluginInfo of this definition.\n  PluginInfo info = 1;\n\n  // The name of the target service.\n  TargetServiceName target_service_name = 2;\n\n  // The name of the target software.\n  TargetSoftware target_software = 3;\n\n  // If the definition is for a web service or not.\n  bool for_web_service = 4;\n\n  // If the definition is for a specific operating system or not.\n  // Note: this filter is executed within an AND condition with the other\n  // filters. E.g. if target_service_name.value is \"http\" and\n  // target_operating_system.osclass.family is \"Linux\" then the plugin will only\n  // match if the service is http and the operating system is Linux.\n  TargetOperatingSystemClass target_operating_system_class = 5;\n}\n\n// Represents a PluginInfo annotation placeholder used by the\n// PluginDefinition proto above.\nmessage PluginInfo {\n  enum PluginType {\n    // Plugin is an unspecified type.\n    PLUGIN_TYPE_UNSPECIFIED = 0;\n    // Plugin is a port scanner.\n    PORT_SCAN = 1;\n    // Plugin is a service fingerprinter.\n    SERVICE_FINGERPRINT = 2;\n    // Plugin is a vulnerability detector.\n    VULN_DETECTION = 3;\n  }\n\n  // Type of plugin.\n  PluginType type = 1;\n\n  // Name of the plugin.\n  string name = 2;\n\n  // Version of the plugin\n  string version = 3;\n\n  // Description of the plugin.\n  string description = 4;\n\n  // Author of the plugin.\n  string author = 5;\n}\n\n// Represents a ForServiceName annotation placeholder used by the\n// PluginDefinition proto above.\nmessage TargetServiceName {\n  // The value of the name of the target.\n  repeated string value = 1;\n}\n\n// Represents a ForSoftware annotation placeholder used by the\n// PluginDefinition proto above.\nmessage TargetSoftware {\n  // The name of the target software, case insensitive.\n  string name = 1;\n\n  // Array of versions and version ranges of the target software.\n  repeated string value = 2;\n}\n\n// Represents a ForOperatingSystem annotation placeholder used by the\n// PluginDefinition proto above. These values are coming directly from the\n// port scanner's output (e.g. nmap).\nmessage TargetOperatingSystemClass {\n  // The vendor of the target operating system, e.g. \"Microsoft\"\n  repeated string vendor = 1;\n\n  // The family of the target operating system, e.g. \"Windows\"\n  repeated string os_family = 2;\n\n  // The minimum accuracy of the target operating system, e.g. 90\n  uint32 min_accuracy = 3;\n}\n"
  },
  {
    "path": "proto/plugin_service.proto",
    "content": "/*\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Model for the plugin RPC service protocol.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\nimport \"detection.proto\";\nimport \"network_service.proto\";\nimport \"plugin_representation.proto\";\nimport \"reconnaissance.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"PluginServiceProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/plugin_service_go_proto\";\n\n// Represents a run request with all matched plugins that will need to run\n// as well as the target to run against.\nmessage RunRequest {\n  // Target of the plugins.\n  TargetInfo target = 1;\n  // All matched plugins that will need to run.\n  repeated MatchedPlugin plugins = 2;\n}\n\n// Compact representation of RunRequest.\nmessage RunCompactRequest {\n  // Target of the plugins.\n  TargetInfo target = 1;\n\n  // Indexes in the following structure point to the services/plugins defined\n  // below. (The order is safe, guaranteed by the proto specification: \"The\n  // order of the elements with respect to each other is preserved when parsing,\n  // though the ordering with respect to other fields is lost.\")\n  message PluginNetworkServiceTarget {\n    // The index of the plugin to run.\n    uint32 plugin_index = 1;\n    // The index of the network service to run against.\n    uint32 service_index = 2;\n  }\n  // All network services that are targeted by some of the plugins.\n  repeated NetworkService services = 2;\n  // All plugins that should be executed during the run.\n  repeated PluginDefinition plugins = 3;\n  // The concrete map of plugin/network service pairs that should be scanned.\n  repeated PluginNetworkServiceTarget scan_targets = 4;\n}\n\n// Represents the plugin needed to run by the language-specific server\n// as well as all the matched network services for the plugin.\nmessage MatchedPlugin {\n  // All matched network services from the reconnaissance report.\n  repeated NetworkService services = 1;\n  // Plugin to run.\n  PluginDefinition plugin = 2;\n}\n\n// Represents a run response with the only field being all DetectionReports\n// generated by the language-specific server.\nmessage RunResponse {\n  DetectionReportList reports = 1;\n}\n\n// Represents a request to list all plugins from the requested server.\nmessage ListPluginsRequest {}\n\n// Represents a response containing a list of all plugins\n// from the requested server.\nmessage ListPluginsResponse {\n  repeated PluginDefinition plugins = 1;\n\n  // Plugin service can indicate here that it RunRequest should be compact\n  // (compact_targets should be populated instead of MatchedPlugin plugins).\n  bool want_compact_run_request = 2;\n}\n\n// Represents the plugin service, two RPCs for running plugins\n// and listing plugins, respectively.\nservice PluginService {\n  // Performs a run request to run all language plugins specified by the\n  // request.\n  rpc Run(RunRequest) returns (RunResponse) {}\n  // Performs a run request to run all language plugins specified by the\n  // compact representation of the request. This is useful, when hundreds of\n  // plugins are to be run against many different NetworkServices.\n  // The language server must set `want_compact_run_request` so that the\n  // Tsunami CLI knows to invoke this method instead of `Run`.\n  rpc RunCompact(RunCompactRequest) returns (RunResponse) {}\n  // Sends a request to list all plugins from the respective language server.\n  rpc ListPlugins(ListPluginsRequest) returns (ListPluginsResponse) {}\n}\n"
  },
  {
    "path": "proto/reconnaissance.proto",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Data models for all the reconnaissance information gathered by Tsunami.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\nimport \"network.proto\";\nimport \"network_service.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"ReconnaissanceProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/reconnaissance_go_proto\";\n\n// Detailed information about the scanning target.\nmessage TargetInfo {\n  // All the known network endpoints of the scanning target.\n  repeated NetworkEndpoint network_endpoints = 1;\n\n  // TODO(magl): add more information about the scanning target, like OSes,\n  // architectures, firewalls etc.\n\n  repeated OperatingSystemClass operating_system_classes = 2;\n}\n\n// Represents a ForOperatingSystem annotation placeholder used by the\n// PluginDefinition proto above.\n// For possible values, consult the following database:\n// https://raw.githubusercontent.com/nmap/nmap/master/nmap-os-db\nmessage OperatingSystemClass {\n  // The type of the target operating system, e.g. \"general purpose\"\n  string type = 1;\n\n  // The vendor of the target operating system, e.g. \"Linux\"\n  string vendor = 2;\n\n  // The family of the target operating system, e.g. \"Linux\"\n  string os_family = 3;\n\n  // The generation of the target operating system, e.g. \"2.6.X\"\n  string os_generation = 4;\n\n  // The estimated accuracy of the target operating system, e.g. 90\n  uint32 accuracy = 5;\n}\n\n// Report from a port scanner.\nmessage PortScanningReport {\n  // Information about the scanning target.\n  TargetInfo target_info = 1;\n\n  // List of all the exposed network services.\n  repeated NetworkService network_services = 2;\n}\n\n// Report from a service fingerprinter.\nmessage FingerprintingReport {\n  // List of all the identified network services after fingerprinting.\n  repeated NetworkService network_services = 3;\n}\n\n// Full reconnaissance report about a single scanning target.\nmessage ReconnaissanceReport {\n  // Information about the scanning target.\n  TargetInfo target_info = 1;\n\n  // All exposed network services of the scanning target.\n  repeated NetworkService network_services = 2;\n}\n"
  },
  {
    "path": "proto/scan_results.proto",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Data models for describing scanning results.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\nimport \"google/protobuf/timestamp.proto\";\nimport \"google/protobuf/duration.proto\";\nimport \"detection.proto\";\nimport \"network_service.proto\";\nimport \"reconnaissance.proto\";\nimport \"vulnerability.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"ScanResultsProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/scan_results_go_proto\";\n\n// Execution status of the scan.\n// NEXT ID: 5\nenum ScanStatus {\n  // Unspecified status.\n  SCAN_STATUS_UNSPECIFIED = 0;\n  // Scan finished successfully.\n  SUCCEEDED = 1;\n  // Scan finished with only a small set of selected detectors succeeded.\n  PARTIALLY_SUCCEEDED = 4;\n  // Scan failed.\n  FAILED = 2;\n  // Scan cancelled.\n  CANCELLED = 3;\n}\n\n// A single vulnerability finding for a specific service.\nmessage ScanFinding {\n  // Information about the scanned target.\n  TargetInfo target_info = 1;\n\n  // Information about the scanned network service.\n  NetworkService network_service = 2;\n\n  // Details about the detected vulnerability.\n  Vulnerability vulnerability = 3;\n}\n\n// Full scanning results.\n// NEXT ID: 9\nmessage ScanResults {\n  // Status of this scan.\n  ScanStatus scan_status = 1;\n\n  // Detailed message for the scan status.\n  string status_message = 6;\n\n  // Reports whether the target was alive during the scan.\n  // A target is considered alive if at least one network service was identified\n  // or at least one vulnerability was detected.\n  bool target_alive = 8;\n\n  // All findings from this scan.\n  repeated ScanFinding scan_findings = 2;\n\n  // Time when this scan was started.\n  google.protobuf.Timestamp scan_start_timestamp = 3;\n\n  // Duration of the full scan.\n  google.protobuf.Duration scan_duration = 4;\n\n  // Detection reports from all triggered Tsunami detection plugins.\n  FullDetectionReports full_detection_reports = 5;\n\n  // Reconnaissance reports from the fingerprinting stage.\n  ReconnaissanceReport reconnaissance_report = 7;\n}\n\n// Full detection reports from all triggered Tsunami detection plugins.\nmessage FullDetectionReports {\n  repeated DetectionReport detection_reports = 1;\n}\n"
  },
  {
    "path": "proto/scan_target.proto",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Data models for describing a scanning target.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\nimport \"network.proto\";\nimport \"network_service.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"ScanTargetProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/scan_target_go_proto\";\n\n// The information about a scan target.\nmessage ScanTarget {\n  oneof target {\n    // The network endpoint to be scanned.\n    NetworkEndpoint network_endpoint = 1;\n    // The network service to be scanned.\n    NetworkService network_service = 2;\n  }\n}\n"
  },
  {
    "path": "proto/software.proto",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Data models for describing a software.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\noption java_multiple_files = true;\noption java_outer_classname = \"SoftwareProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/software_go_proto\";\n\n// The exact version of a software.\nmessage Version {\n  // Type of the Version message, identifying an ordinary software version or a\n  // sentinel MINIMUM/MAXIMUM version. See comments below for what is a sentinel\n  // version.\n  enum VersionType {\n    VERSION_TYPE_UNSPECIFIED = 0;\n\n    // A normal software version.\n    NORMAL = 1;\n    // A sentinel version representing negative infinity, i.e. MINIMUM version\n    // is less than any NORMAL and MAXIMUM versions.\n    MINIMUM = 2;\n    // A sentinel version representing positive infinity, i.e. MAXIMUM version\n    // is greater than any NORMAL and MINIMUM versions.\n    MAXIMUM = 3;\n  }\n\n  // Distinguishes between sentinel MIN/MAX versions and normal versions.\n  VersionType type = 1;\n\n  // Human readable version number, e.g. 1.0.3. This is set only when type is\n  // NORMAL. Tsunami uses raw string to represent a version number instead of\n  // any structured messages in order to handle different kinds of version\n  // schemes. Tsunami will tokenize this version string and store tokens\n  // internally. When performing version comparisons, Tsunami follows the\n  // precedence defined by Semantic Versioning (semver.org). More details can be\n  // found in Tsunami's internal Version class.\n  string full_version_string = 2;\n}\n\n// An inclusive range of versions for a software.\nmessage VersionRange {\n  // Whether the range endpoint is inclusive or exclusive.\n  enum Inclusiveness {\n    INCLUSIVENESS_UNSPECIFIED = 0;\n    INCLUSIVE = 1;\n    EXCLUSIVE = 2;\n  }\n\n  // Minimum version that belongs in the range.\n  Version min_version = 1;\n\n  // Inclusiveness of the min_version. When min_version points to negative\n  // infinity, this value will always be EXCLUSIVE to matching the\n  // representation of (-inf, 1.0]. Note that negative infinity version should\n  // ***NOT*** be compared with a version range as it is just a bogus sentinel\n  // version without any meaning.\n  Inclusiveness min_version_inclusiveness = 2;\n\n  // Maximum version that belongs in the range.\n  Version max_version = 3;\n\n  // Inclusiveness of the max_version. When max_version points to positive\n  // infinity, this value will always be EXCLUSIVE to matching the\n  // representation of [1.0, inf). Note that positive infinity version should\n  // ***NOT*** be compared with a version range as it is just a bogus sentinel\n  // version without any meaning.\n  Inclusiveness max_version_inclusiveness = 4;\n}\n\n// A set of Versions and VersionRanges that completely describes a set of\n// software releases, e.g. {3.9.1, 3.9.3, [4.7.1, 4.7.8], 4.8}\nmessage VersionSet {\n  repeated Version versions = 1;\n  repeated VersionRange version_ranges = 2;\n}\n\n// A structured description about a software.\nmessage Software {\n  // The name of this software.\n  string name = 1;\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/detection.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing a vulnerability detection report.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: detection.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\ttimestamppb \"google.golang.org/protobuf/types/known/timestamppb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Status of the vulnerability detection result.\ntype DetectionStatus int32\n\nconst (\n\t// Unspecified status.\n\tDetectionStatus_DETECTION_STATUS_UNSPECIFIED DetectionStatus = 0\n\t// Target is not vulnerable.\n\tDetectionStatus_SAFE DetectionStatus = 1\n\t// Target appears to be vulnerable (e.g. because running version is\n\t// vulnerable), but couldn't be verified.\n\tDetectionStatus_VULNERABILITY_PRESENT DetectionStatus = 2\n\t// Target is vulnerable and the detector successfully verified the\n\t// vulnerability.\n\tDetectionStatus_VULNERABILITY_VERIFIED DetectionStatus = 3\n)\n\n// Enum value maps for DetectionStatus.\nvar (\n\tDetectionStatus_name = map[int32]string{\n\t\t0: \"DETECTION_STATUS_UNSPECIFIED\",\n\t\t1: \"SAFE\",\n\t\t2: \"VULNERABILITY_PRESENT\",\n\t\t3: \"VULNERABILITY_VERIFIED\",\n\t}\n\tDetectionStatus_value = map[string]int32{\n\t\t\"DETECTION_STATUS_UNSPECIFIED\": 0,\n\t\t\"SAFE\":                         1,\n\t\t\"VULNERABILITY_PRESENT\":        2,\n\t\t\"VULNERABILITY_VERIFIED\":       3,\n\t}\n)\n\nfunc (x DetectionStatus) Enum() *DetectionStatus {\n\tp := new(DetectionStatus)\n\t*p = x\n\treturn p\n}\n\nfunc (x DetectionStatus) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (DetectionStatus) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_detection_proto_enumTypes[0].Descriptor()\n}\n\nfunc (DetectionStatus) Type() protoreflect.EnumType {\n\treturn &file_detection_proto_enumTypes[0]\n}\n\nfunc (x DetectionStatus) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use DetectionStatus.Descriptor instead.\nfunc (DetectionStatus) EnumDescriptor() ([]byte, []int) {\n\treturn file_detection_proto_rawDescGZIP(), []int{0}\n}\n\n// Full report about a detected vulnerability.\ntype DetectionReport struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Information about the scanned target.\n\tTargetInfo *TargetInfo `protobuf:\"bytes,1,opt,name=target_info,json=targetInfo,proto3\" json:\"target_info,omitempty\"`\n\t// Information about the scanned network service.\n\tNetworkService *NetworkService `protobuf:\"bytes,2,opt,name=network_service,json=networkService,proto3\" json:\"network_service,omitempty\"`\n\t// Time when the vulnerability was detected.\n\tDetectionTimestamp *timestamppb.Timestamp `protobuf:\"bytes,3,opt,name=detection_timestamp,json=detectionTimestamp,proto3\" json:\"detection_timestamp,omitempty\"`\n\t// Status of the detection result.\n\tDetectionStatus DetectionStatus `protobuf:\"varint,4,opt,name=detection_status,json=detectionStatus,proto3,enum=tsunami.proto.DetectionStatus\" json:\"detection_status,omitempty\"`\n\t// Full details about the detected vulnerability.\n\tVulnerability *Vulnerability `protobuf:\"bytes,5,opt,name=vulnerability,proto3\" json:\"vulnerability,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DetectionReport) Reset() {\n\t*x = DetectionReport{}\n\tmi := &file_detection_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DetectionReport) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DetectionReport) ProtoMessage() {}\n\nfunc (x *DetectionReport) ProtoReflect() protoreflect.Message {\n\tmi := &file_detection_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DetectionReport.ProtoReflect.Descriptor instead.\nfunc (*DetectionReport) Descriptor() ([]byte, []int) {\n\treturn file_detection_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *DetectionReport) GetTargetInfo() *TargetInfo {\n\tif x != nil {\n\t\treturn x.TargetInfo\n\t}\n\treturn nil\n}\n\nfunc (x *DetectionReport) GetNetworkService() *NetworkService {\n\tif x != nil {\n\t\treturn x.NetworkService\n\t}\n\treturn nil\n}\n\nfunc (x *DetectionReport) GetDetectionTimestamp() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.DetectionTimestamp\n\t}\n\treturn nil\n}\n\nfunc (x *DetectionReport) GetDetectionStatus() DetectionStatus {\n\tif x != nil {\n\t\treturn x.DetectionStatus\n\t}\n\treturn DetectionStatus_DETECTION_STATUS_UNSPECIFIED\n}\n\nfunc (x *DetectionReport) GetVulnerability() *Vulnerability {\n\tif x != nil {\n\t\treturn x.Vulnerability\n\t}\n\treturn nil\n}\n\ntype DetectionReportList struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tDetectionReports []*DetectionReport     `protobuf:\"bytes,1,rep,name=detection_reports,json=detectionReports,proto3\" json:\"detection_reports,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *DetectionReportList) Reset() {\n\t*x = DetectionReportList{}\n\tmi := &file_detection_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DetectionReportList) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DetectionReportList) ProtoMessage() {}\n\nfunc (x *DetectionReportList) ProtoReflect() protoreflect.Message {\n\tmi := &file_detection_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DetectionReportList.ProtoReflect.Descriptor instead.\nfunc (*DetectionReportList) Descriptor() ([]byte, []int) {\n\treturn file_detection_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *DetectionReportList) GetDetectionReports() []*DetectionReport {\n\tif x != nil {\n\t\treturn x.DetectionReports\n\t}\n\treturn nil\n}\n\nvar File_detection_proto protoreflect.FileDescriptor\n\nvar file_detection_proto_rawDesc = string([]byte{\n\t0x0a, 0x0f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x12, 0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,\n\t0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x1a, 0x15, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e,\n\t0x61, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x13,\n\t0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x22, 0xf1, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3a, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65,\n\t0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x72,\n\t0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49,\n\t0x6e, 0x66, 0x6f, 0x12, 0x46, 0x0a, 0x0f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73,\n\t0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x74,\n\t0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x0e, 0x6e, 0x65, 0x74,\n\t0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4b, 0x0a, 0x13, 0x64,\n\t0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,\n\t0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,\n\t0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,\n\t0x74, 0x61, 0x6d, 0x70, 0x52, 0x12, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54,\n\t0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x49, 0x0a, 0x10, 0x64, 0x65, 0x74, 0x65,\n\t0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74,\n\t0x75, 0x73, 0x52, 0x0f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61,\n\t0x74, 0x75, 0x73, 0x12, 0x42, 0x0a, 0x0d, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69,\n\t0x6c, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x73, 0x75,\n\t0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65,\n\t0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0d, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72,\n\t0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x22, 0x62, 0x0a, 0x13, 0x44, 0x65, 0x74, 0x65, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x4b,\n\t0x0a, 0x11, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x70, 0x6f,\n\t0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x73, 0x75, 0x6e,\n\t0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x10, 0x64, 0x65, 0x74, 0x65, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2a, 0x74, 0x0a, 0x0f, 0x44,\n\t0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20,\n\t0x0a, 0x1c, 0x44, 0x45, 0x54, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54,\n\t0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,\n\t0x12, 0x08, 0x0a, 0x04, 0x53, 0x41, 0x46, 0x45, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x56, 0x55,\n\t0x4c, 0x4e, 0x45, 0x52, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x50, 0x52, 0x45, 0x53,\n\t0x45, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x55, 0x4c, 0x4e, 0x45, 0x52, 0x41,\n\t0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,\n\t0x03, 0x42, 0x70, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,\n\t0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x0f, 0x44,\n\t0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50, 0x01,\n\t0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f,\n\t0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75,\n\t0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_detection_proto_rawDescOnce sync.Once\n\tfile_detection_proto_rawDescData []byte\n)\n\nfunc file_detection_proto_rawDescGZIP() []byte {\n\tfile_detection_proto_rawDescOnce.Do(func() {\n\t\tfile_detection_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_detection_proto_rawDesc), len(file_detection_proto_rawDesc)))\n\t})\n\treturn file_detection_proto_rawDescData\n}\n\nvar file_detection_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_detection_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_detection_proto_goTypes = []any{\n\t(DetectionStatus)(0),          // 0: tsunami.proto.DetectionStatus\n\t(*DetectionReport)(nil),       // 1: tsunami.proto.DetectionReport\n\t(*DetectionReportList)(nil),   // 2: tsunami.proto.DetectionReportList\n\t(*TargetInfo)(nil),            // 3: tsunami.proto.TargetInfo\n\t(*NetworkService)(nil),        // 4: tsunami.proto.NetworkService\n\t(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp\n\t(*Vulnerability)(nil),         // 6: tsunami.proto.Vulnerability\n}\nvar file_detection_proto_depIdxs = []int32{\n\t3, // 0: tsunami.proto.DetectionReport.target_info:type_name -> tsunami.proto.TargetInfo\n\t4, // 1: tsunami.proto.DetectionReport.network_service:type_name -> tsunami.proto.NetworkService\n\t5, // 2: tsunami.proto.DetectionReport.detection_timestamp:type_name -> google.protobuf.Timestamp\n\t0, // 3: tsunami.proto.DetectionReport.detection_status:type_name -> tsunami.proto.DetectionStatus\n\t6, // 4: tsunami.proto.DetectionReport.vulnerability:type_name -> tsunami.proto.Vulnerability\n\t1, // 5: tsunami.proto.DetectionReportList.detection_reports:type_name -> tsunami.proto.DetectionReport\n\t6, // [6:6] is the sub-list for method output_type\n\t6, // [6:6] is the sub-list for method input_type\n\t6, // [6:6] is the sub-list for extension type_name\n\t6, // [6:6] is the sub-list for extension extendee\n\t0, // [0:6] is the sub-list for field type_name\n}\n\nfunc init() { file_detection_proto_init() }\nfunc file_detection_proto_init() {\n\tif File_detection_proto != nil {\n\t\treturn\n\t}\n\tfile_network_service_proto_init()\n\tfile_reconnaissance_proto_init()\n\tfile_vulnerability_proto_init()\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_detection_proto_rawDesc), len(file_detection_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_detection_proto_goTypes,\n\t\tDependencyIndexes: file_detection_proto_depIdxs,\n\t\tEnumInfos:         file_detection_proto_enumTypes,\n\t\tMessageInfos:      file_detection_proto_msgTypes,\n\t}.Build()\n\tFile_detection_proto = out.File\n\tfile_detection_proto_goTypes = nil\n\tfile_detection_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/network.pb.go",
    "content": "//\n// Copyright 2019 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing network related information.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: network.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// The address family of an IP address.\ntype AddressFamily int32\n\nconst (\n\tAddressFamily_ADDRESS_FAMILY_UNSPECIFIED AddressFamily = 0\n\tAddressFamily_IPV4                       AddressFamily = 4\n\tAddressFamily_IPV6                       AddressFamily = 6\n)\n\n// Enum value maps for AddressFamily.\nvar (\n\tAddressFamily_name = map[int32]string{\n\t\t0: \"ADDRESS_FAMILY_UNSPECIFIED\",\n\t\t4: \"IPV4\",\n\t\t6: \"IPV6\",\n\t}\n\tAddressFamily_value = map[string]int32{\n\t\t\"ADDRESS_FAMILY_UNSPECIFIED\": 0,\n\t\t\"IPV4\":                       4,\n\t\t\"IPV6\":                       6,\n\t}\n)\n\nfunc (x AddressFamily) Enum() *AddressFamily {\n\tp := new(AddressFamily)\n\t*p = x\n\treturn p\n}\n\nfunc (x AddressFamily) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (AddressFamily) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_network_proto_enumTypes[0].Descriptor()\n}\n\nfunc (AddressFamily) Type() protoreflect.EnumType {\n\treturn &file_network_proto_enumTypes[0]\n}\n\nfunc (x AddressFamily) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use AddressFamily.Descriptor instead.\nfunc (AddressFamily) EnumDescriptor() ([]byte, []int) {\n\treturn file_network_proto_rawDescGZIP(), []int{0}\n}\n\n// The transport layer protocols.\ntype TransportProtocol int32\n\nconst (\n\tTransportProtocol_TRANSPORT_PROTOCOL_UNSPECIFIED TransportProtocol = 0\n\tTransportProtocol_TCP                            TransportProtocol = 1\n\tTransportProtocol_UDP                            TransportProtocol = 2\n\tTransportProtocol_SCTP                           TransportProtocol = 3\n)\n\n// Enum value maps for TransportProtocol.\nvar (\n\tTransportProtocol_name = map[int32]string{\n\t\t0: \"TRANSPORT_PROTOCOL_UNSPECIFIED\",\n\t\t1: \"TCP\",\n\t\t2: \"UDP\",\n\t\t3: \"SCTP\",\n\t}\n\tTransportProtocol_value = map[string]int32{\n\t\t\"TRANSPORT_PROTOCOL_UNSPECIFIED\": 0,\n\t\t\"TCP\":                            1,\n\t\t\"UDP\":                            2,\n\t\t\"SCTP\":                           3,\n\t}\n)\n\nfunc (x TransportProtocol) Enum() *TransportProtocol {\n\tp := new(TransportProtocol)\n\t*p = x\n\treturn p\n}\n\nfunc (x TransportProtocol) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (TransportProtocol) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_network_proto_enumTypes[1].Descriptor()\n}\n\nfunc (TransportProtocol) Type() protoreflect.EnumType {\n\treturn &file_network_proto_enumTypes[1]\n}\n\nfunc (x TransportProtocol) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use TransportProtocol.Descriptor instead.\nfunc (TransportProtocol) EnumDescriptor() ([]byte, []int) {\n\treturn file_network_proto_rawDescGZIP(), []int{1}\n}\n\ntype NetworkEndpoint_Type int32\n\nconst (\n\tNetworkEndpoint_TYPE_UNSPECIFIED NetworkEndpoint_Type = 0\n\t// The network endpoint is represented by an IP address.\n\tNetworkEndpoint_IP NetworkEndpoint_Type = 1\n\t// The network endpoint is represented by IP address and port pair.\n\tNetworkEndpoint_IP_PORT NetworkEndpoint_Type = 2\n\t// The network endpoint is represented by a hostname.\n\tNetworkEndpoint_HOSTNAME NetworkEndpoint_Type = 3\n\t// The network endpoint is represented by a hostname and port pair.\n\tNetworkEndpoint_HOSTNAME_PORT NetworkEndpoint_Type = 4\n\t// The network endpoint is represented by an IP address and hostname.\n\tNetworkEndpoint_IP_HOSTNAME NetworkEndpoint_Type = 5\n\t// The network endpoint is represented by an IP address, hostname and port.\n\tNetworkEndpoint_IP_HOSTNAME_PORT NetworkEndpoint_Type = 6\n)\n\n// Enum value maps for NetworkEndpoint_Type.\nvar (\n\tNetworkEndpoint_Type_name = map[int32]string{\n\t\t0: \"TYPE_UNSPECIFIED\",\n\t\t1: \"IP\",\n\t\t2: \"IP_PORT\",\n\t\t3: \"HOSTNAME\",\n\t\t4: \"HOSTNAME_PORT\",\n\t\t5: \"IP_HOSTNAME\",\n\t\t6: \"IP_HOSTNAME_PORT\",\n\t}\n\tNetworkEndpoint_Type_value = map[string]int32{\n\t\t\"TYPE_UNSPECIFIED\": 0,\n\t\t\"IP\":               1,\n\t\t\"IP_PORT\":          2,\n\t\t\"HOSTNAME\":         3,\n\t\t\"HOSTNAME_PORT\":    4,\n\t\t\"IP_HOSTNAME\":      5,\n\t\t\"IP_HOSTNAME_PORT\": 6,\n\t}\n)\n\nfunc (x NetworkEndpoint_Type) Enum() *NetworkEndpoint_Type {\n\tp := new(NetworkEndpoint_Type)\n\t*p = x\n\treturn p\n}\n\nfunc (x NetworkEndpoint_Type) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (NetworkEndpoint_Type) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_network_proto_enumTypes[2].Descriptor()\n}\n\nfunc (NetworkEndpoint_Type) Type() protoreflect.EnumType {\n\treturn &file_network_proto_enumTypes[2]\n}\n\nfunc (x NetworkEndpoint_Type) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use NetworkEndpoint_Type.Descriptor instead.\nfunc (NetworkEndpoint_Type) EnumDescriptor() ([]byte, []int) {\n\treturn file_network_proto_rawDescGZIP(), []int{3, 0}\n}\n\n// The IP address of a networking device.\ntype IpAddress struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The family of the IP address.\n\tAddressFamily AddressFamily `protobuf:\"varint,1,opt,name=address_family,json=addressFamily,proto3,enum=tsunami.proto.AddressFamily\" json:\"address_family,omitempty\"`\n\t// A human-readable representation of the IP address, e.g. 127.0.0.1 for IPV4\n\t// and 2001:db8:0:1234:0:567:8:1 for IPV6.\n\tAddress       string `protobuf:\"bytes,2,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *IpAddress) Reset() {\n\t*x = IpAddress{}\n\tmi := &file_network_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *IpAddress) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IpAddress) ProtoMessage() {}\n\nfunc (x *IpAddress) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use IpAddress.ProtoReflect.Descriptor instead.\nfunc (*IpAddress) Descriptor() ([]byte, []int) {\n\treturn file_network_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *IpAddress) GetAddressFamily() AddressFamily {\n\tif x != nil {\n\t\treturn x.AddressFamily\n\t}\n\treturn AddressFamily_ADDRESS_FAMILY_UNSPECIFIED\n}\n\nfunc (x *IpAddress) GetAddress() string {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn \"\"\n}\n\n// The port that a network service listens to.\ntype Port struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPortNumber    uint32                 `protobuf:\"varint,1,opt,name=port_number,json=portNumber,proto3\" json:\"port_number,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Port) Reset() {\n\t*x = Port{}\n\tmi := &file_network_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Port) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Port) ProtoMessage() {}\n\nfunc (x *Port) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Port.ProtoReflect.Descriptor instead.\nfunc (*Port) Descriptor() ([]byte, []int) {\n\treturn file_network_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Port) GetPortNumber() uint32 {\n\tif x != nil {\n\t\treturn x.PortNumber\n\t}\n\treturn 0\n}\n\n// The hostname of a networking device.\ntype Hostname struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Hostname) Reset() {\n\t*x = Hostname{}\n\tmi := &file_network_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Hostname) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Hostname) ProtoMessage() {}\n\nfunc (x *Hostname) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Hostname.ProtoReflect.Descriptor instead.\nfunc (*Hostname) Descriptor() ([]byte, []int) {\n\treturn file_network_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *Hostname) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\n// A classification of an endpoint for a network device.\ntype NetworkEndpoint struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Type of the network endpoint.\n\tType NetworkEndpoint_Type `protobuf:\"varint,1,opt,name=type,proto3,enum=tsunami.proto.NetworkEndpoint_Type\" json:\"type,omitempty\"`\n\t// Optional IP address of a network endpoint. Must be specified when Type is\n\t// IP or IP_PORT.\n\tIpAddress *IpAddress `protobuf:\"bytes,2,opt,name=ip_address,json=ipAddress,proto3\" json:\"ip_address,omitempty\"`\n\t// Optional port of a network endpoint. Must be specified when Type is IP_PORT\n\t// or HOSTNAME_PORT.\n\tPort *Port `protobuf:\"bytes,3,opt,name=port,proto3\" json:\"port,omitempty\"`\n\t// Optional hostname of a network endpoint. Must be specified when Type is\n\t// HOSTNAME or HOSTNAME_PORT.\n\tHostname      *Hostname `protobuf:\"bytes,4,opt,name=hostname,proto3\" json:\"hostname,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *NetworkEndpoint) Reset() {\n\t*x = NetworkEndpoint{}\n\tmi := &file_network_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NetworkEndpoint) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NetworkEndpoint) ProtoMessage() {}\n\nfunc (x *NetworkEndpoint) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NetworkEndpoint.ProtoReflect.Descriptor instead.\nfunc (*NetworkEndpoint) Descriptor() ([]byte, []int) {\n\treturn file_network_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *NetworkEndpoint) GetType() NetworkEndpoint_Type {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn NetworkEndpoint_TYPE_UNSPECIFIED\n}\n\nfunc (x *NetworkEndpoint) GetIpAddress() *IpAddress {\n\tif x != nil {\n\t\treturn x.IpAddress\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkEndpoint) GetPort() *Port {\n\tif x != nil {\n\t\treturn x.Port\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkEndpoint) GetHostname() *Hostname {\n\tif x != nil {\n\t\treturn x.Hostname\n\t}\n\treturn nil\n}\n\nvar File_network_proto protoreflect.FileDescriptor\n\nvar file_network_proto_rawDesc = string([]byte{\n\t0x0a, 0x0d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,\n\t0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6a,\n\t0x0a, 0x09, 0x49, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x43, 0x0a, 0x0e, 0x61,\n\t0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x46, 0x61, 0x6d, 0x69, 0x6c,\n\t0x79, 0x52, 0x0d, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79,\n\t0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x27, 0x0a, 0x04, 0x50, 0x6f,\n\t0x72, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65,\n\t0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x75, 0x6d,\n\t0x62, 0x65, 0x72, 0x22, 0x1e, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12,\n\t0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,\n\t0x61, 0x6d, 0x65, 0x22, 0xdc, 0x02, 0x0a, 0x0f, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45,\n\t0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x6e, 0x64,\n\t0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,\n\t0x12, 0x37, 0x0a, 0x0a, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x09,\n\t0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x27, 0x0a, 0x04, 0x70, 0x6f, 0x72,\n\t0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d,\n\t0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x04, 0x70, 0x6f,\n\t0x72, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, 0x68,\n\t0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x79, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,\n\t0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,\n\t0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a,\n\t0x07, 0x49, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x48, 0x4f,\n\t0x53, 0x54, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x48, 0x4f, 0x53, 0x54,\n\t0x4e, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, 0x49,\n\t0x50, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x05, 0x12, 0x14, 0x0a, 0x10,\n\t0x49, 0x50, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x4e, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x4f, 0x52, 0x54,\n\t0x10, 0x06, 0x2a, 0x43, 0x0a, 0x0d, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x46, 0x61, 0x6d,\n\t0x69, 0x6c, 0x79, 0x12, 0x1e, 0x0a, 0x1a, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x46,\n\t0x41, 0x4d, 0x49, 0x4c, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,\n\t0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x50, 0x56, 0x34, 0x10, 0x04, 0x12, 0x08, 0x0a,\n\t0x04, 0x49, 0x50, 0x56, 0x36, 0x10, 0x06, 0x2a, 0x53, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73,\n\t0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x22, 0x0a, 0x1e,\n\t0x54, 0x52, 0x41, 0x4e, 0x53, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43,\n\t0x4f, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,\n\t0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50,\n\t0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x43, 0x54, 0x50, 0x10, 0x03, 0x42, 0x6e, 0x0a, 0x18,\n\t0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61,\n\t0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x0d, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,\n\t0x6b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75,\n\t0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75,\n\t0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63,\n\t0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x73, 0x75, 0x6e,\n\t0x61, 0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_network_proto_rawDescOnce sync.Once\n\tfile_network_proto_rawDescData []byte\n)\n\nfunc file_network_proto_rawDescGZIP() []byte {\n\tfile_network_proto_rawDescOnce.Do(func() {\n\t\tfile_network_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_network_proto_rawDesc), len(file_network_proto_rawDesc)))\n\t})\n\treturn file_network_proto_rawDescData\n}\n\nvar file_network_proto_enumTypes = make([]protoimpl.EnumInfo, 3)\nvar file_network_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_network_proto_goTypes = []any{\n\t(AddressFamily)(0),        // 0: tsunami.proto.AddressFamily\n\t(TransportProtocol)(0),    // 1: tsunami.proto.TransportProtocol\n\t(NetworkEndpoint_Type)(0), // 2: tsunami.proto.NetworkEndpoint.Type\n\t(*IpAddress)(nil),         // 3: tsunami.proto.IpAddress\n\t(*Port)(nil),              // 4: tsunami.proto.Port\n\t(*Hostname)(nil),          // 5: tsunami.proto.Hostname\n\t(*NetworkEndpoint)(nil),   // 6: tsunami.proto.NetworkEndpoint\n}\nvar file_network_proto_depIdxs = []int32{\n\t0, // 0: tsunami.proto.IpAddress.address_family:type_name -> tsunami.proto.AddressFamily\n\t2, // 1: tsunami.proto.NetworkEndpoint.type:type_name -> tsunami.proto.NetworkEndpoint.Type\n\t3, // 2: tsunami.proto.NetworkEndpoint.ip_address:type_name -> tsunami.proto.IpAddress\n\t4, // 3: tsunami.proto.NetworkEndpoint.port:type_name -> tsunami.proto.Port\n\t5, // 4: tsunami.proto.NetworkEndpoint.hostname:type_name -> tsunami.proto.Hostname\n\t5, // [5:5] is the sub-list for method output_type\n\t5, // [5:5] is the sub-list for method input_type\n\t5, // [5:5] is the sub-list for extension type_name\n\t5, // [5:5] is the sub-list for extension extendee\n\t0, // [0:5] is the sub-list for field type_name\n}\n\nfunc init() { file_network_proto_init() }\nfunc file_network_proto_init() {\n\tif File_network_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_network_proto_rawDesc), len(file_network_proto_rawDesc)),\n\t\t\tNumEnums:      3,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_network_proto_goTypes,\n\t\tDependencyIndexes: file_network_proto_depIdxs,\n\t\tEnumInfos:         file_network_proto_enumTypes,\n\t\tMessageInfos:      file_network_proto_msgTypes,\n\t}.Build()\n\tFile_network_proto = out.File\n\tfile_network_proto_goTypes = nil\n\tfile_network_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/network_service.pb.go",
    "content": "//\n// Copyright 2019 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing a network service.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: network_service.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// General information about a network service running on a target.\ntype NetworkService struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The network endpoint where this network service is served.\n\tNetworkEndpoint *NetworkEndpoint `protobuf:\"bytes,1,opt,name=network_endpoint,json=networkEndpoint,proto3\" json:\"network_endpoint,omitempty\"`\n\t// The transport layer protocol used by the service.\n\tTransportProtocol TransportProtocol `protobuf:\"varint,2,opt,name=transport_protocol,json=transportProtocol,proto3,enum=tsunami.proto.TransportProtocol\" json:\"transport_protocol,omitempty\"`\n\t// The name of the network service, following convention in RFC6335. Examples\n\t// are like http, telnet, ssh, etc.\n\tServiceName string `protobuf:\"bytes,3,opt,name=service_name,json=serviceName,proto3\" json:\"service_name,omitempty\"`\n\t// The software that provides the service behind the port.\n\tSoftware *Software `protobuf:\"bytes,4,opt,name=software,proto3\" json:\"software,omitempty\"`\n\t// The complete set of versions of the software.\n\tVersionSet *VersionSet `protobuf:\"bytes,5,opt,name=version_set,json=versionSet,proto3\" json:\"version_set,omitempty\"`\n\t// Banners generated by the service.\n\tBanner []string `protobuf:\"bytes,6,rep,name=banner,proto3\" json:\"banner,omitempty\"`\n\t// Context information about this network service.\n\tServiceContext *ServiceContext `protobuf:\"bytes,7,opt,name=service_context,json=serviceContext,proto3\" json:\"service_context,omitempty\"`\n\t// The detected Common Platform Enumeration (CPE) name for service,\n\t// in the uri binding representation, like: cpe:/a:openbsd:openssh:8.4p1\n\tCpes []string `protobuf:\"bytes,8,rep,name=cpes,proto3\" json:\"cpes,omitempty\"`\n\t// List of supported SSL versions (e.g. TLSv1, SSLv3, ...) on the service.\n\tSupportedSslVersions []string `protobuf:\"bytes,9,rep,name=supported_ssl_versions,json=supportedSslVersions,proto3\" json:\"supported_ssl_versions,omitempty\"`\n\t// List of supported HTTP methods (e.g. POST, GET, ...) on the service.\n\tSupportedHttpMethods []string `protobuf:\"bytes,10,rep,name=supported_http_methods,json=supportedHttpMethods,proto3\" json:\"supported_http_methods,omitempty\"`\n\tunknownFields        protoimpl.UnknownFields\n\tsizeCache            protoimpl.SizeCache\n}\n\nfunc (x *NetworkService) Reset() {\n\t*x = NetworkService{}\n\tmi := &file_network_service_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NetworkService) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NetworkService) ProtoMessage() {}\n\nfunc (x *NetworkService) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_service_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NetworkService.ProtoReflect.Descriptor instead.\nfunc (*NetworkService) Descriptor() ([]byte, []int) {\n\treturn file_network_service_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *NetworkService) GetNetworkEndpoint() *NetworkEndpoint {\n\tif x != nil {\n\t\treturn x.NetworkEndpoint\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetTransportProtocol() TransportProtocol {\n\tif x != nil {\n\t\treturn x.TransportProtocol\n\t}\n\treturn TransportProtocol_TRANSPORT_PROTOCOL_UNSPECIFIED\n}\n\nfunc (x *NetworkService) GetServiceName() string {\n\tif x != nil {\n\t\treturn x.ServiceName\n\t}\n\treturn \"\"\n}\n\nfunc (x *NetworkService) GetSoftware() *Software {\n\tif x != nil {\n\t\treturn x.Software\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetVersionSet() *VersionSet {\n\tif x != nil {\n\t\treturn x.VersionSet\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetBanner() []string {\n\tif x != nil {\n\t\treturn x.Banner\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetServiceContext() *ServiceContext {\n\tif x != nil {\n\t\treturn x.ServiceContext\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetCpes() []string {\n\tif x != nil {\n\t\treturn x.Cpes\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetSupportedSslVersions() []string {\n\tif x != nil {\n\t\treturn x.SupportedSslVersions\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkService) GetSupportedHttpMethods() []string {\n\tif x != nil {\n\t\treturn x.SupportedHttpMethods\n\t}\n\treturn nil\n}\n\n// Context information about a specific network service.\ntype ServiceContext struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Types that are valid to be assigned to Context:\n\t//\n\t//\t*ServiceContext_WebServiceContext\n\tContext       isServiceContext_Context `protobuf_oneof:\"context\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ServiceContext) Reset() {\n\t*x = ServiceContext{}\n\tmi := &file_network_service_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServiceContext) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServiceContext) ProtoMessage() {}\n\nfunc (x *ServiceContext) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_service_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServiceContext.ProtoReflect.Descriptor instead.\nfunc (*ServiceContext) Descriptor() ([]byte, []int) {\n\treturn file_network_service_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *ServiceContext) GetContext() isServiceContext_Context {\n\tif x != nil {\n\t\treturn x.Context\n\t}\n\treturn nil\n}\n\nfunc (x *ServiceContext) GetWebServiceContext() *WebServiceContext {\n\tif x != nil {\n\t\tif x, ok := x.Context.(*ServiceContext_WebServiceContext); ok {\n\t\t\treturn x.WebServiceContext\n\t\t}\n\t}\n\treturn nil\n}\n\ntype isServiceContext_Context interface {\n\tisServiceContext_Context()\n}\n\ntype ServiceContext_WebServiceContext struct {\n\tWebServiceContext *WebServiceContext `protobuf:\"bytes,1,opt,name=web_service_context,json=webServiceContext,proto3,oneof\"`\n}\n\nfunc (*ServiceContext_WebServiceContext) isServiceContext_Context() {}\n\n// Context information about a web application.\n// NEXT ID: 5\ntype WebServiceContext struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The root path of the hosted web application.\n\tApplicationRoot string `protobuf:\"bytes,1,opt,name=application_root,json=applicationRoot,proto3\" json:\"application_root,omitempty\"`\n\t// The web application that is serving under the application root.\n\tSoftware *Software `protobuf:\"bytes,2,opt,name=software,proto3\" json:\"software,omitempty\"`\n\t// The detected versions of the web application.\n\tVersionSet *VersionSet `protobuf:\"bytes,3,opt,name=version_set,json=versionSet,proto3\" json:\"version_set,omitempty\"`\n\t// Fingerprinter's crawling results for this web service.\n\tCrawlResults  []*CrawlResult `protobuf:\"bytes,4,rep,name=crawl_results,json=crawlResults,proto3\" json:\"crawl_results,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WebServiceContext) Reset() {\n\t*x = WebServiceContext{}\n\tmi := &file_network_service_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WebServiceContext) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WebServiceContext) ProtoMessage() {}\n\nfunc (x *WebServiceContext) ProtoReflect() protoreflect.Message {\n\tmi := &file_network_service_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WebServiceContext.ProtoReflect.Descriptor instead.\nfunc (*WebServiceContext) Descriptor() ([]byte, []int) {\n\treturn file_network_service_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *WebServiceContext) GetApplicationRoot() string {\n\tif x != nil {\n\t\treturn x.ApplicationRoot\n\t}\n\treturn \"\"\n}\n\nfunc (x *WebServiceContext) GetSoftware() *Software {\n\tif x != nil {\n\t\treturn x.Software\n\t}\n\treturn nil\n}\n\nfunc (x *WebServiceContext) GetVersionSet() *VersionSet {\n\tif x != nil {\n\t\treturn x.VersionSet\n\t}\n\treturn nil\n}\n\nfunc (x *WebServiceContext) GetCrawlResults() []*CrawlResult {\n\tif x != nil {\n\t\treturn x.CrawlResults\n\t}\n\treturn nil\n}\n\nvar File_network_service_proto protoreflect.FileDescriptor\n\nvar file_network_service_proto_rawDesc = string([]byte{\n\t0x0a, 0x15, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,\n\t0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0e, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x77, 0x65, 0x62, 0x5f, 0x63, 0x72, 0x61, 0x77, 0x6c,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa0, 0x04, 0x0a, 0x0e, 0x4e, 0x65, 0x74, 0x77, 0x6f,\n\t0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x10, 0x6e, 0x65, 0x74,\n\t0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x6e, 0x64, 0x70, 0x6f,\n\t0x69, 0x6e, 0x74, 0x52, 0x0f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x6e, 0x64, 0x70,\n\t0x6f, 0x69, 0x6e, 0x74, 0x12, 0x4f, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,\n\t0x74, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,\n\t0x32, 0x20, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,\n\t0x6f, 0x6c, 0x52, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f,\n\t0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72,\n\t0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x33, 0x0a, 0x08, 0x73, 0x6f, 0x66, 0x74,\n\t0x77, 0x61, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x73, 0x75,\n\t0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x6f, 0x66, 0x74, 0x77,\n\t0x61, 0x72, 0x65, 0x52, 0x08, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x12, 0x3a, 0x0a,\n\t0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x52, 0x0a, 0x76,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x61, 0x6e,\n\t0x6e, 0x65, 0x72, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x62, 0x61, 0x6e, 0x6e, 0x65,\n\t0x72, 0x12, 0x46, 0x0a, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e,\n\t0x74, 0x65, 0x78, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x73, 0x75,\n\t0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x70, 0x65,\n\t0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x63, 0x70, 0x65, 0x73, 0x12, 0x34, 0x0a,\n\t0x16, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x73, 0x6c, 0x5f, 0x76,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x73,\n\t0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x73, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69,\n\t0x6f, 0x6e, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64,\n\t0x5f, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x0a, 0x20,\n\t0x03, 0x28, 0x09, 0x52, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x48, 0x74,\n\t0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0x6f, 0x0a, 0x0e, 0x53, 0x65, 0x72,\n\t0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x52, 0x0a, 0x13, 0x77,\n\t0x65, 0x62, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65,\n\t0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61,\n\t0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x65, 0x62, 0x53, 0x65, 0x72, 0x76,\n\t0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x48, 0x00, 0x52, 0x11, 0x77, 0x65,\n\t0x62, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x42,\n\t0x09, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xf0, 0x01, 0x0a, 0x11, 0x57,\n\t0x65, 0x62, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,\n\t0x12, 0x29, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,\n\t0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x70, 0x70, 0x6c,\n\t0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x73,\n\t0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e,\n\t0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x6f,\n\t0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x52, 0x08, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65,\n\t0x12, 0x3a, 0x0a, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x18,\n\t0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74,\n\t0x52, 0x0a, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x12, 0x3f, 0x0a, 0x0d,\n\t0x63, 0x72, 0x61, 0x77, 0x6c, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20,\n\t0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x61, 0x77, 0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52,\n\t0x0c, 0x63, 0x72, 0x61, 0x77, 0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x75, 0x0a,\n\t0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e,\n\t0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x14, 0x4e, 0x65, 0x74, 0x77, 0x6f,\n\t0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50,\n\t0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f,\n\t0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63,\n\t0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_network_service_proto_rawDescOnce sync.Once\n\tfile_network_service_proto_rawDescData []byte\n)\n\nfunc file_network_service_proto_rawDescGZIP() []byte {\n\tfile_network_service_proto_rawDescOnce.Do(func() {\n\t\tfile_network_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_network_service_proto_rawDesc), len(file_network_service_proto_rawDesc)))\n\t})\n\treturn file_network_service_proto_rawDescData\n}\n\nvar file_network_service_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_network_service_proto_goTypes = []any{\n\t(*NetworkService)(nil),    // 0: tsunami.proto.NetworkService\n\t(*ServiceContext)(nil),    // 1: tsunami.proto.ServiceContext\n\t(*WebServiceContext)(nil), // 2: tsunami.proto.WebServiceContext\n\t(*NetworkEndpoint)(nil),   // 3: tsunami.proto.NetworkEndpoint\n\t(TransportProtocol)(0),    // 4: tsunami.proto.TransportProtocol\n\t(*Software)(nil),          // 5: tsunami.proto.Software\n\t(*VersionSet)(nil),        // 6: tsunami.proto.VersionSet\n\t(*CrawlResult)(nil),       // 7: tsunami.proto.CrawlResult\n}\nvar file_network_service_proto_depIdxs = []int32{\n\t3, // 0: tsunami.proto.NetworkService.network_endpoint:type_name -> tsunami.proto.NetworkEndpoint\n\t4, // 1: tsunami.proto.NetworkService.transport_protocol:type_name -> tsunami.proto.TransportProtocol\n\t5, // 2: tsunami.proto.NetworkService.software:type_name -> tsunami.proto.Software\n\t6, // 3: tsunami.proto.NetworkService.version_set:type_name -> tsunami.proto.VersionSet\n\t1, // 4: tsunami.proto.NetworkService.service_context:type_name -> tsunami.proto.ServiceContext\n\t2, // 5: tsunami.proto.ServiceContext.web_service_context:type_name -> tsunami.proto.WebServiceContext\n\t5, // 6: tsunami.proto.WebServiceContext.software:type_name -> tsunami.proto.Software\n\t6, // 7: tsunami.proto.WebServiceContext.version_set:type_name -> tsunami.proto.VersionSet\n\t7, // 8: tsunami.proto.WebServiceContext.crawl_results:type_name -> tsunami.proto.CrawlResult\n\t9, // [9:9] is the sub-list for method output_type\n\t9, // [9:9] is the sub-list for method input_type\n\t9, // [9:9] is the sub-list for extension type_name\n\t9, // [9:9] is the sub-list for extension extendee\n\t0, // [0:9] is the sub-list for field type_name\n}\n\nfunc init() { file_network_service_proto_init() }\nfunc file_network_service_proto_init() {\n\tif File_network_service_proto != nil {\n\t\treturn\n\t}\n\tfile_network_proto_init()\n\tfile_software_proto_init()\n\tfile_web_crawl_proto_init()\n\tfile_network_service_proto_msgTypes[1].OneofWrappers = []any{\n\t\t(*ServiceContext_WebServiceContext)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_network_service_proto_rawDesc), len(file_network_service_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_network_service_proto_goTypes,\n\t\tDependencyIndexes: file_network_service_proto_depIdxs,\n\t\tMessageInfos:      file_network_service_proto_msgTypes,\n\t}.Build()\n\tFile_network_service_proto = out.File\n\tfile_network_service_proto_goTypes = nil\n\tfile_network_service_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/payload_generator.pb.go",
    "content": "//\n// Copyright 2022 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models utilized by the Tsunami Paylaod Generator\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: payload_generator.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\twrapperspb \"google.golang.org/protobuf/types/known/wrapperspb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype PayloadValidationType int32\n\nconst (\n\tPayloadValidationType_VALIDATION_TYPE_UNSPECIFIED PayloadValidationType = 0\n\tPayloadValidationType_VALIDATION_REGEX            PayloadValidationType = 1\n)\n\n// Enum value maps for PayloadValidationType.\nvar (\n\tPayloadValidationType_name = map[int32]string{\n\t\t0: \"VALIDATION_TYPE_UNSPECIFIED\",\n\t\t1: \"VALIDATION_REGEX\",\n\t}\n\tPayloadValidationType_value = map[string]int32{\n\t\t\"VALIDATION_TYPE_UNSPECIFIED\": 0,\n\t\t\"VALIDATION_REGEX\":            1,\n\t}\n)\n\nfunc (x PayloadValidationType) Enum() *PayloadValidationType {\n\tp := new(PayloadValidationType)\n\t*p = x\n\treturn p\n}\n\nfunc (x PayloadValidationType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PayloadValidationType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_payload_generator_proto_enumTypes[0].Descriptor()\n}\n\nfunc (PayloadValidationType) Type() protoreflect.EnumType {\n\treturn &file_payload_generator_proto_enumTypes[0]\n}\n\nfunc (x PayloadValidationType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use PayloadValidationType.Descriptor instead.\nfunc (PayloadValidationType) EnumDescriptor() ([]byte, []int) {\n\treturn file_payload_generator_proto_rawDescGZIP(), []int{0}\n}\n\n// The type of vulnerability the detector is looking for\ntype PayloadGeneratorConfig_VulnerabilityType int32\n\nconst (\n\t// Unspecified vulnerability type\n\tPayloadGeneratorConfig_VULNERABILITY_TYPE_UNSPECIFIED PayloadGeneratorConfig_VulnerabilityType = 0\n\t// RCE which returns the output of the execution\n\tPayloadGeneratorConfig_REFLECTIVE_RCE PayloadGeneratorConfig_VulnerabilityType = 1\n\t// RCE which does not return the output of the execution\n\tPayloadGeneratorConfig_BLIND_RCE PayloadGeneratorConfig_VulnerabilityType = 2\n\t// Server-Side Request Forgery\n\tPayloadGeneratorConfig_SSRF PayloadGeneratorConfig_VulnerabilityType = 3\n\t// Arbitrary File Write\n\tPayloadGeneratorConfig_ARBITRARY_FILE_WRITE PayloadGeneratorConfig_VulnerabilityType = 4\n\t// RCE without output of the execution + File Read (needed to get\n\t// confirmation string)\n\tPayloadGeneratorConfig_BLIND_RCE_FILE_READ PayloadGeneratorConfig_VulnerabilityType = 5\n)\n\n// Enum value maps for PayloadGeneratorConfig_VulnerabilityType.\nvar (\n\tPayloadGeneratorConfig_VulnerabilityType_name = map[int32]string{\n\t\t0: \"VULNERABILITY_TYPE_UNSPECIFIED\",\n\t\t1: \"REFLECTIVE_RCE\",\n\t\t2: \"BLIND_RCE\",\n\t\t3: \"SSRF\",\n\t\t4: \"ARBITRARY_FILE_WRITE\",\n\t\t5: \"BLIND_RCE_FILE_READ\",\n\t}\n\tPayloadGeneratorConfig_VulnerabilityType_value = map[string]int32{\n\t\t\"VULNERABILITY_TYPE_UNSPECIFIED\": 0,\n\t\t\"REFLECTIVE_RCE\":                 1,\n\t\t\"BLIND_RCE\":                      2,\n\t\t\"SSRF\":                           3,\n\t\t\"ARBITRARY_FILE_WRITE\":           4,\n\t\t\"BLIND_RCE_FILE_READ\":            5,\n\t}\n)\n\nfunc (x PayloadGeneratorConfig_VulnerabilityType) Enum() *PayloadGeneratorConfig_VulnerabilityType {\n\tp := new(PayloadGeneratorConfig_VulnerabilityType)\n\t*p = x\n\treturn p\n}\n\nfunc (x PayloadGeneratorConfig_VulnerabilityType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PayloadGeneratorConfig_VulnerabilityType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_payload_generator_proto_enumTypes[1].Descriptor()\n}\n\nfunc (PayloadGeneratorConfig_VulnerabilityType) Type() protoreflect.EnumType {\n\treturn &file_payload_generator_proto_enumTypes[1]\n}\n\nfunc (x PayloadGeneratorConfig_VulnerabilityType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use PayloadGeneratorConfig_VulnerabilityType.Descriptor instead.\nfunc (PayloadGeneratorConfig_VulnerabilityType) EnumDescriptor() ([]byte, []int) {\n\treturn file_payload_generator_proto_rawDescGZIP(), []int{0, 0}\n}\n\n// The environment that processes the payload for execution e.g. a PHP-based\n// target likely wants a payload that is itself PHP code.\ntype PayloadGeneratorConfig_InterpretationEnvironment int32\n\nconst (\n\t// Unspecified interpretation environment type\n\tPayloadGeneratorConfig_INTERPRETATION_ENVIRONMENT_UNSPECIFIED PayloadGeneratorConfig_InterpretationEnvironment = 0\n\t// Payload is interpreted within a Linux shell environment\n\tPayloadGeneratorConfig_LINUX_SHELL PayloadGeneratorConfig_InterpretationEnvironment = 1\n\t// Payload is interpreted wihin a Java compiler context\n\tPayloadGeneratorConfig_JAVA PayloadGeneratorConfig_InterpretationEnvironment = 2\n\t// Payload is interpreted wihin a PHP VM context\n\tPayloadGeneratorConfig_PHP PayloadGeneratorConfig_InterpretationEnvironment = 3\n\t// Interpretation environment doesn't matter\n\tPayloadGeneratorConfig_INTERPRETATION_ANY PayloadGeneratorConfig_InterpretationEnvironment = 4\n\t// Payload is interpreted wihin crontab\n\tPayloadGeneratorConfig_LINUX_ROOT_CRONTAB PayloadGeneratorConfig_InterpretationEnvironment = 5\n\t// Payload is interpreted wihin a Windows shell environment\n\tPayloadGeneratorConfig_WINDOWS_SHELL PayloadGeneratorConfig_InterpretationEnvironment = 6\n\t// Payload is interpreted within a JSP shell environment\n\tPayloadGeneratorConfig_JSP PayloadGeneratorConfig_InterpretationEnvironment = 7\n)\n\n// Enum value maps for PayloadGeneratorConfig_InterpretationEnvironment.\nvar (\n\tPayloadGeneratorConfig_InterpretationEnvironment_name = map[int32]string{\n\t\t0: \"INTERPRETATION_ENVIRONMENT_UNSPECIFIED\",\n\t\t1: \"LINUX_SHELL\",\n\t\t2: \"JAVA\",\n\t\t3: \"PHP\",\n\t\t4: \"INTERPRETATION_ANY\",\n\t\t5: \"LINUX_ROOT_CRONTAB\",\n\t\t6: \"WINDOWS_SHELL\",\n\t\t7: \"JSP\",\n\t}\n\tPayloadGeneratorConfig_InterpretationEnvironment_value = map[string]int32{\n\t\t\"INTERPRETATION_ENVIRONMENT_UNSPECIFIED\": 0,\n\t\t\"LINUX_SHELL\":                            1,\n\t\t\"JAVA\":                                   2,\n\t\t\"PHP\":                                    3,\n\t\t\"INTERPRETATION_ANY\":                     4,\n\t\t\"LINUX_ROOT_CRONTAB\":                     5,\n\t\t\"WINDOWS_SHELL\":                          6,\n\t\t\"JSP\":                                    7,\n\t}\n)\n\nfunc (x PayloadGeneratorConfig_InterpretationEnvironment) Enum() *PayloadGeneratorConfig_InterpretationEnvironment {\n\tp := new(PayloadGeneratorConfig_InterpretationEnvironment)\n\t*p = x\n\treturn p\n}\n\nfunc (x PayloadGeneratorConfig_InterpretationEnvironment) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PayloadGeneratorConfig_InterpretationEnvironment) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_payload_generator_proto_enumTypes[2].Descriptor()\n}\n\nfunc (PayloadGeneratorConfig_InterpretationEnvironment) Type() protoreflect.EnumType {\n\treturn &file_payload_generator_proto_enumTypes[2]\n}\n\nfunc (x PayloadGeneratorConfig_InterpretationEnvironment) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use PayloadGeneratorConfig_InterpretationEnvironment.Descriptor instead.\nfunc (PayloadGeneratorConfig_InterpretationEnvironment) EnumDescriptor() ([]byte, []int) {\n\treturn file_payload_generator_proto_rawDescGZIP(), []int{0, 1}\n}\n\n// The actual runtime environment when the payload is run e.g. while a\n// PHP-based target wants a PHP-interpretation environment, the actual code\n// execution may happen via the Linux shell: exec(“echo \\”this is running in\n// the system.\\””).\ntype PayloadGeneratorConfig_ExecutionEnvironment int32\n\nconst (\n\t// Unspecified execution environment type\n\tPayloadGeneratorConfig_EXECUTION_ENVIRONMENT_UNSPECIFIED PayloadGeneratorConfig_ExecutionEnvironment = 0\n\t// Execute within the InterpretationEnvironment\n\tPayloadGeneratorConfig_EXEC_INTERPRETATION_ENVIRONMENT PayloadGeneratorConfig_ExecutionEnvironment = 1\n\t// Execution environment doesn't matter\n\tPayloadGeneratorConfig_EXEC_ANY PayloadGeneratorConfig_ExecutionEnvironment = 2\n)\n\n// Enum value maps for PayloadGeneratorConfig_ExecutionEnvironment.\nvar (\n\tPayloadGeneratorConfig_ExecutionEnvironment_name = map[int32]string{\n\t\t0: \"EXECUTION_ENVIRONMENT_UNSPECIFIED\",\n\t\t1: \"EXEC_INTERPRETATION_ENVIRONMENT\",\n\t\t2: \"EXEC_ANY\",\n\t}\n\tPayloadGeneratorConfig_ExecutionEnvironment_value = map[string]int32{\n\t\t\"EXECUTION_ENVIRONMENT_UNSPECIFIED\": 0,\n\t\t\"EXEC_INTERPRETATION_ENVIRONMENT\":   1,\n\t\t\"EXEC_ANY\":                          2,\n\t}\n)\n\nfunc (x PayloadGeneratorConfig_ExecutionEnvironment) Enum() *PayloadGeneratorConfig_ExecutionEnvironment {\n\tp := new(PayloadGeneratorConfig_ExecutionEnvironment)\n\t*p = x\n\treturn p\n}\n\nfunc (x PayloadGeneratorConfig_ExecutionEnvironment) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PayloadGeneratorConfig_ExecutionEnvironment) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_payload_generator_proto_enumTypes[3].Descriptor()\n}\n\nfunc (PayloadGeneratorConfig_ExecutionEnvironment) Type() protoreflect.EnumType {\n\treturn &file_payload_generator_proto_enumTypes[3]\n}\n\nfunc (x PayloadGeneratorConfig_ExecutionEnvironment) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use PayloadGeneratorConfig_ExecutionEnvironment.Descriptor instead.\nfunc (PayloadGeneratorConfig_ExecutionEnvironment) EnumDescriptor() ([]byte, []int) {\n\treturn file_payload_generator_proto_rawDescGZIP(), []int{0, 2}\n}\n\n// Attributes utilized by the PayloadGenerator to select a payload\ntype PayloadGeneratorConfig struct {\n\tstate                     protoimpl.MessageState                           `protogen:\"open.v1\"`\n\tVulnerabilityType         PayloadGeneratorConfig_VulnerabilityType         `protobuf:\"varint,2,opt,name=vulnerability_type,json=vulnerabilityType,proto3,enum=tsunami.proto.PayloadGeneratorConfig_VulnerabilityType\" json:\"vulnerability_type,omitempty\"`\n\tInterpretationEnvironment PayloadGeneratorConfig_InterpretationEnvironment `protobuf:\"varint,3,opt,name=interpretation_environment,json=interpretationEnvironment,proto3,enum=tsunami.proto.PayloadGeneratorConfig_InterpretationEnvironment\" json:\"interpretation_environment,omitempty\"`\n\tExecutionEnvironment      PayloadGeneratorConfig_ExecutionEnvironment      `protobuf:\"varint,4,opt,name=execution_environment,json=executionEnvironment,proto3,enum=tsunami.proto.PayloadGeneratorConfig_ExecutionEnvironment\" json:\"execution_environment,omitempty\"`\n\tunknownFields             protoimpl.UnknownFields\n\tsizeCache                 protoimpl.SizeCache\n}\n\nfunc (x *PayloadGeneratorConfig) Reset() {\n\t*x = PayloadGeneratorConfig{}\n\tmi := &file_payload_generator_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PayloadGeneratorConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PayloadGeneratorConfig) ProtoMessage() {}\n\nfunc (x *PayloadGeneratorConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_payload_generator_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PayloadGeneratorConfig.ProtoReflect.Descriptor instead.\nfunc (*PayloadGeneratorConfig) Descriptor() ([]byte, []int) {\n\treturn file_payload_generator_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *PayloadGeneratorConfig) GetVulnerabilityType() PayloadGeneratorConfig_VulnerabilityType {\n\tif x != nil {\n\t\treturn x.VulnerabilityType\n\t}\n\treturn PayloadGeneratorConfig_VULNERABILITY_TYPE_UNSPECIFIED\n}\n\nfunc (x *PayloadGeneratorConfig) GetInterpretationEnvironment() PayloadGeneratorConfig_InterpretationEnvironment {\n\tif x != nil {\n\t\treturn x.InterpretationEnvironment\n\t}\n\treturn PayloadGeneratorConfig_INTERPRETATION_ENVIRONMENT_UNSPECIFIED\n}\n\nfunc (x *PayloadGeneratorConfig) GetExecutionEnvironment() PayloadGeneratorConfig_ExecutionEnvironment {\n\tif x != nil {\n\t\treturn x.ExecutionEnvironment\n\t}\n\treturn PayloadGeneratorConfig_EXECUTION_ENVIRONMENT_UNSPECIFIED\n}\n\n// Attributes of a payload. A detector can check these attributes to change its\n// logic based on the payload type.\ntype PayloadAttributes struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Whether the payload uses the callback server\n\tUsesCallbackServer bool `protobuf:\"varint,1,opt,name=uses_callback_server,json=usesCallbackServer,proto3\" json:\"uses_callback_server,omitempty\"`\n\tunknownFields      protoimpl.UnknownFields\n\tsizeCache          protoimpl.SizeCache\n}\n\nfunc (x *PayloadAttributes) Reset() {\n\t*x = PayloadAttributes{}\n\tmi := &file_payload_generator_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PayloadAttributes) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PayloadAttributes) ProtoMessage() {}\n\nfunc (x *PayloadAttributes) ProtoReflect() protoreflect.Message {\n\tmi := &file_payload_generator_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PayloadAttributes.ProtoReflect.Descriptor instead.\nfunc (*PayloadAttributes) Descriptor() ([]byte, []int) {\n\treturn file_payload_generator_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *PayloadAttributes) GetUsesCallbackServer() bool {\n\tif x != nil {\n\t\treturn x.UsesCallbackServer\n\t}\n\treturn false\n}\n\n// Container type for payload_definitions.yaml\ntype PayloadLibrary struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPayloads      []*PayloadDefinition   `protobuf:\"bytes,1,rep,name=payloads,proto3\" json:\"payloads,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PayloadLibrary) Reset() {\n\t*x = PayloadLibrary{}\n\tmi := &file_payload_generator_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PayloadLibrary) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PayloadLibrary) ProtoMessage() {}\n\nfunc (x *PayloadLibrary) ProtoReflect() protoreflect.Message {\n\tmi := &file_payload_generator_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PayloadLibrary.ProtoReflect.Descriptor instead.\nfunc (*PayloadLibrary) Descriptor() ([]byte, []int) {\n\treturn file_payload_generator_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *PayloadLibrary) GetPayloads() []*PayloadDefinition {\n\tif x != nil {\n\t\treturn x.Payloads\n\t}\n\treturn nil\n}\n\n// Schema for each entry in payload_definitions.yaml\n// Note: this message uses StringValue and BoolValue because we validate whether\n// each payload definition in the yaml file has the correct fields present.\n// Since empty proto fields are given default values (proto fields are not\n// nullable), we use the wrapped types to check for actual presence.\ntype PayloadDefinition struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The human-readable string to identify the payload\n\tName                      *wrapperspb.StringValue                          `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tInterpretationEnvironment PayloadGeneratorConfig_InterpretationEnvironment `protobuf:\"varint,2,opt,name=interpretation_environment,json=interpretationEnvironment,proto3,enum=tsunami.proto.PayloadGeneratorConfig_InterpretationEnvironment\" json:\"interpretation_environment,omitempty\"`\n\tExecutionEnvironment      PayloadGeneratorConfig_ExecutionEnvironment      `protobuf:\"varint,3,opt,name=execution_environment,json=executionEnvironment,proto3,enum=tsunami.proto.PayloadGeneratorConfig_ExecutionEnvironment\" json:\"execution_environment,omitempty\"`\n\t// All vulnerability types this payload can be used for\n\tVulnerabilityType []PayloadGeneratorConfig_VulnerabilityType `protobuf:\"varint,4,rep,packed,name=vulnerability_type,json=vulnerabilityType,proto3,enum=tsunami.proto.PayloadGeneratorConfig_VulnerabilityType\" json:\"vulnerability_type,omitempty\"`\n\t// If true, payload_string must contain the $TSUNAMI_PAYLOAD_TOKEN_URL\n\t// token. Validation will automatically check against the callback server, so\n\t// the validation* fields do not need to be set.\n\tUsesCallbackServer *wrapperspb.BoolValue `protobuf:\"bytes,5,opt,name=uses_callback_server,json=usesCallbackServer,proto3\" json:\"uses_callback_server,omitempty\"`\n\t// The actual payload command string. The following special tokens can be\n\t// used which will cause the framework to inject dynamic content into the\n\t// command:\n\t// - $TSUNAMI_PAYLOAD_TOKEN_URL: url for the callback server\n\t// - a random string, used to reduce false positives.\n\tPayloadString *wrapperspb.StringValue `protobuf:\"bytes,6,opt,name=payload_string,json=payloadString,proto3\" json:\"payload_string,omitempty\"`\n\t// The type of validation function for determining if the payload was\n\t// executed. Currently, only REGEX is supported.\n\tValidationType PayloadValidationType `protobuf:\"varint,7,opt,name=validation_type,json=validationType,proto3,enum=tsunami.proto.PayloadValidationType\" json:\"validation_type,omitempty\"`\n\t// Required if validation_type == REGEX. Must be compatible with\n\t// java.util.regex.Pattern. The string will first be preprocessed before\n\t// applied as a regex, replacing any of the following tokens with the\n\t// corresponding values supplied by the framework:\n\t//   - $TSUNAMI_PAYLOAD_TOKEN_RANDOM: a random string, used to reduce false\n\t//     positives. The value is guaranteed to be the same as the value supplied\n\t//     to payload_string.\n\tValidationRegex *wrapperspb.StringValue `protobuf:\"bytes,8,opt,name=validation_regex,json=validationRegex,proto3\" json:\"validation_regex,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *PayloadDefinition) Reset() {\n\t*x = PayloadDefinition{}\n\tmi := &file_payload_generator_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PayloadDefinition) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PayloadDefinition) ProtoMessage() {}\n\nfunc (x *PayloadDefinition) ProtoReflect() protoreflect.Message {\n\tmi := &file_payload_generator_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PayloadDefinition.ProtoReflect.Descriptor instead.\nfunc (*PayloadDefinition) Descriptor() ([]byte, []int) {\n\treturn file_payload_generator_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *PayloadDefinition) GetName() *wrapperspb.StringValue {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn nil\n}\n\nfunc (x *PayloadDefinition) GetInterpretationEnvironment() PayloadGeneratorConfig_InterpretationEnvironment {\n\tif x != nil {\n\t\treturn x.InterpretationEnvironment\n\t}\n\treturn PayloadGeneratorConfig_INTERPRETATION_ENVIRONMENT_UNSPECIFIED\n}\n\nfunc (x *PayloadDefinition) GetExecutionEnvironment() PayloadGeneratorConfig_ExecutionEnvironment {\n\tif x != nil {\n\t\treturn x.ExecutionEnvironment\n\t}\n\treturn PayloadGeneratorConfig_EXECUTION_ENVIRONMENT_UNSPECIFIED\n}\n\nfunc (x *PayloadDefinition) GetVulnerabilityType() []PayloadGeneratorConfig_VulnerabilityType {\n\tif x != nil {\n\t\treturn x.VulnerabilityType\n\t}\n\treturn nil\n}\n\nfunc (x *PayloadDefinition) GetUsesCallbackServer() *wrapperspb.BoolValue {\n\tif x != nil {\n\t\treturn x.UsesCallbackServer\n\t}\n\treturn nil\n}\n\nfunc (x *PayloadDefinition) GetPayloadString() *wrapperspb.StringValue {\n\tif x != nil {\n\t\treturn x.PayloadString\n\t}\n\treturn nil\n}\n\nfunc (x *PayloadDefinition) GetValidationType() PayloadValidationType {\n\tif x != nil {\n\t\treturn x.ValidationType\n\t}\n\treturn PayloadValidationType_VALIDATION_TYPE_UNSPECIFIED\n}\n\nfunc (x *PayloadDefinition) GetValidationRegex() *wrapperspb.StringValue {\n\tif x != nil {\n\t\treturn x.ValidationRegex\n\t}\n\treturn nil\n}\n\nvar File_payload_generator_proto protoreflect.FileDescriptor\n\nvar file_payload_generator_proto_rawDesc = string([]byte{\n\t0x0a, 0x17, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,\n\t0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61,\n\t0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65,\n\t0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb7, 0x06, 0x0a, 0x16, 0x50, 0x61, 0x79,\n\t0x6c, 0x6f, 0x61, 0x64, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e,\n\t0x66, 0x69, 0x67, 0x12, 0x66, 0x0a, 0x12, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69,\n\t0x6c, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,\n\t0x37, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,\n\t0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72,\n\t0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69,\n\t0x6c, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x11, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72,\n\t0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x7e, 0x0a, 0x1a, 0x69,\n\t0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e,\n\t0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32,\n\t0x3f, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,\n\t0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72,\n\t0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74,\n\t0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74,\n\t0x52, 0x19, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x6f, 0x0a, 0x15, 0x65,\n\t0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e,\n\t0x6d, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3a, 0x2e, 0x74, 0x73, 0x75,\n\t0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f,\n\t0x61, 0x64, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,\n\t0x67, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72,\n\t0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x14, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f,\n\t0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x97, 0x01, 0x0a,\n\t0x11, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x54, 0x79,\n\t0x70, 0x65, 0x12, 0x22, 0x0a, 0x1e, 0x56, 0x55, 0x4c, 0x4e, 0x45, 0x52, 0x41, 0x42, 0x49, 0x4c,\n\t0x49, 0x54, 0x59, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,\n\t0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x46, 0x4c, 0x45, 0x43,\n\t0x54, 0x49, 0x56, 0x45, 0x5f, 0x52, 0x43, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x42, 0x4c,\n\t0x49, 0x4e, 0x44, 0x5f, 0x52, 0x43, 0x45, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x53, 0x52,\n\t0x46, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, 0x41, 0x52, 0x59,\n\t0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x10, 0x04, 0x12, 0x17, 0x0a,\n\t0x13, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x5f, 0x52, 0x43, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f,\n\t0x52, 0x45, 0x41, 0x44, 0x10, 0x05, 0x22, 0xb7, 0x01, 0x0a, 0x19, 0x49, 0x6e, 0x74, 0x65, 0x72,\n\t0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e,\n\t0x6d, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x26, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x50, 0x52, 0x45,\n\t0x54, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x4e, 0x56, 0x49, 0x52, 0x4f, 0x4e, 0x4d, 0x45,\n\t0x4e, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,\n\t0x12, 0x0f, 0x0a, 0x0b, 0x4c, 0x49, 0x4e, 0x55, 0x58, 0x5f, 0x53, 0x48, 0x45, 0x4c, 0x4c, 0x10,\n\t0x01, 0x12, 0x08, 0x0a, 0x04, 0x4a, 0x41, 0x56, 0x41, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x50,\n\t0x48, 0x50, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x50, 0x52, 0x45,\n\t0x54, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4e, 0x59, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12,\n\t0x4c, 0x49, 0x4e, 0x55, 0x58, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x43, 0x52, 0x4f, 0x4e, 0x54,\n\t0x41, 0x42, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x5f,\n\t0x53, 0x48, 0x45, 0x4c, 0x4c, 0x10, 0x06, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x53, 0x50, 0x10, 0x07,\n\t0x22, 0x70, 0x0a, 0x14, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x76,\n\t0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x21, 0x45, 0x58, 0x45, 0x43,\n\t0x55, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x4e, 0x56, 0x49, 0x52, 0x4f, 0x4e, 0x4d, 0x45, 0x4e,\n\t0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,\n\t0x23, 0x0a, 0x1f, 0x45, 0x58, 0x45, 0x43, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x50, 0x52, 0x45,\n\t0x54, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x4e, 0x56, 0x49, 0x52, 0x4f, 0x4e, 0x4d, 0x45,\n\t0x4e, 0x54, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x45, 0x43, 0x5f, 0x41, 0x4e, 0x59,\n\t0x10, 0x02, 0x22, 0x45, 0x0a, 0x11, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x74, 0x74,\n\t0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x73, 0x5f,\n\t0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x75, 0x73, 0x65, 0x73, 0x43, 0x61, 0x6c, 0x6c, 0x62,\n\t0x61, 0x63, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x4e, 0x0a, 0x0e, 0x50, 0x61, 0x79,\n\t0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x3c, 0x0a, 0x08, 0x70,\n\t0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e,\n\t0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61,\n\t0x79, 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52,\n\t0x08, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x22, 0xc9, 0x05, 0x0a, 0x11, 0x50, 0x61,\n\t0x79, 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12,\n\t0x30, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e,\n\t0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,\n\t0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x12, 0x7e, 0x0a, 0x1a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3f, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x47, 0x65, 0x6e,\n\t0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6e, 0x74,\n\t0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72,\n\t0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x19, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65,\n\t0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e,\n\t0x74, 0x12, 0x6f, 0x0a, 0x15, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65,\n\t0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e,\n\t0x32, 0x3a, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f,\n\t0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f,\n\t0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x14, 0x65, 0x78,\n\t0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65,\n\t0x6e, 0x74, 0x12, 0x66, 0x0a, 0x12, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c,\n\t0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x37,\n\t0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50,\n\t0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x43,\n\t0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c,\n\t0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x11, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61,\n\t0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4c, 0x0a, 0x14, 0x75, 0x73,\n\t0x65, 0x73, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76,\n\t0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,\n\t0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56,\n\t0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, 0x73, 0x65, 0x73, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61,\n\t0x63, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x43, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x6c,\n\t0x6f, 0x61, 0x64, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,\n\t0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0d,\n\t0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x4d, 0x0a,\n\t0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65,\n\t0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x56, 0x61,\n\t0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x76, 0x61,\n\t0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x47, 0x0a, 0x10,\n\t0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78,\n\t0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56,\n\t0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x52, 0x65, 0x67, 0x65, 0x78, 0x2a, 0x4e, 0x0a, 0x15, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,\n\t0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f,\n\t0x0a, 0x1b, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50,\n\t0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,\n\t0x14, 0x0a, 0x10, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45,\n\t0x47, 0x45, 0x58, 0x10, 0x01, 0x42, 0x77, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f,\n\t0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x42, 0x16, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61,\n\t0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74,\n\t0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d,\n\t0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x73,\n\t0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_payload_generator_proto_rawDescOnce sync.Once\n\tfile_payload_generator_proto_rawDescData []byte\n)\n\nfunc file_payload_generator_proto_rawDescGZIP() []byte {\n\tfile_payload_generator_proto_rawDescOnce.Do(func() {\n\t\tfile_payload_generator_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_payload_generator_proto_rawDesc), len(file_payload_generator_proto_rawDesc)))\n\t})\n\treturn file_payload_generator_proto_rawDescData\n}\n\nvar file_payload_generator_proto_enumTypes = make([]protoimpl.EnumInfo, 4)\nvar file_payload_generator_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_payload_generator_proto_goTypes = []any{\n\t(PayloadValidationType)(0),                            // 0: tsunami.proto.PayloadValidationType\n\t(PayloadGeneratorConfig_VulnerabilityType)(0),         // 1: tsunami.proto.PayloadGeneratorConfig.VulnerabilityType\n\t(PayloadGeneratorConfig_InterpretationEnvironment)(0), // 2: tsunami.proto.PayloadGeneratorConfig.InterpretationEnvironment\n\t(PayloadGeneratorConfig_ExecutionEnvironment)(0),      // 3: tsunami.proto.PayloadGeneratorConfig.ExecutionEnvironment\n\t(*PayloadGeneratorConfig)(nil),                        // 4: tsunami.proto.PayloadGeneratorConfig\n\t(*PayloadAttributes)(nil),                             // 5: tsunami.proto.PayloadAttributes\n\t(*PayloadLibrary)(nil),                                // 6: tsunami.proto.PayloadLibrary\n\t(*PayloadDefinition)(nil),                             // 7: tsunami.proto.PayloadDefinition\n\t(*wrapperspb.StringValue)(nil),                        // 8: google.protobuf.StringValue\n\t(*wrapperspb.BoolValue)(nil),                          // 9: google.protobuf.BoolValue\n}\nvar file_payload_generator_proto_depIdxs = []int32{\n\t1,  // 0: tsunami.proto.PayloadGeneratorConfig.vulnerability_type:type_name -> tsunami.proto.PayloadGeneratorConfig.VulnerabilityType\n\t2,  // 1: tsunami.proto.PayloadGeneratorConfig.interpretation_environment:type_name -> tsunami.proto.PayloadGeneratorConfig.InterpretationEnvironment\n\t3,  // 2: tsunami.proto.PayloadGeneratorConfig.execution_environment:type_name -> tsunami.proto.PayloadGeneratorConfig.ExecutionEnvironment\n\t7,  // 3: tsunami.proto.PayloadLibrary.payloads:type_name -> tsunami.proto.PayloadDefinition\n\t8,  // 4: tsunami.proto.PayloadDefinition.name:type_name -> google.protobuf.StringValue\n\t2,  // 5: tsunami.proto.PayloadDefinition.interpretation_environment:type_name -> tsunami.proto.PayloadGeneratorConfig.InterpretationEnvironment\n\t3,  // 6: tsunami.proto.PayloadDefinition.execution_environment:type_name -> tsunami.proto.PayloadGeneratorConfig.ExecutionEnvironment\n\t1,  // 7: tsunami.proto.PayloadDefinition.vulnerability_type:type_name -> tsunami.proto.PayloadGeneratorConfig.VulnerabilityType\n\t9,  // 8: tsunami.proto.PayloadDefinition.uses_callback_server:type_name -> google.protobuf.BoolValue\n\t8,  // 9: tsunami.proto.PayloadDefinition.payload_string:type_name -> google.protobuf.StringValue\n\t0,  // 10: tsunami.proto.PayloadDefinition.validation_type:type_name -> tsunami.proto.PayloadValidationType\n\t8,  // 11: tsunami.proto.PayloadDefinition.validation_regex:type_name -> google.protobuf.StringValue\n\t12, // [12:12] is the sub-list for method output_type\n\t12, // [12:12] is the sub-list for method input_type\n\t12, // [12:12] is the sub-list for extension type_name\n\t12, // [12:12] is the sub-list for extension extendee\n\t0,  // [0:12] is the sub-list for field type_name\n}\n\nfunc init() { file_payload_generator_proto_init() }\nfunc file_payload_generator_proto_init() {\n\tif File_payload_generator_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_payload_generator_proto_rawDesc), len(file_payload_generator_proto_rawDesc)),\n\t\t\tNumEnums:      4,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_payload_generator_proto_goTypes,\n\t\tDependencyIndexes: file_payload_generator_proto_depIdxs,\n\t\tEnumInfos:         file_payload_generator_proto_enumTypes,\n\t\tMessageInfos:      file_payload_generator_proto_msgTypes,\n\t}.Build()\n\tFile_payload_generator_proto = out.File\n\tfile_payload_generator_proto_goTypes = nil\n\tfile_payload_generator_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/plugin_representation.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Representation of a tsunami plugin definition passed between language\n// servers.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: plugin_representation.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype PluginInfo_PluginType int32\n\nconst (\n\t// Plugin is an unspecified type.\n\tPluginInfo_PLUGIN_TYPE_UNSPECIFIED PluginInfo_PluginType = 0\n\t// Plugin is a port scanner.\n\tPluginInfo_PORT_SCAN PluginInfo_PluginType = 1\n\t// Plugin is a service fingerprinter.\n\tPluginInfo_SERVICE_FINGERPRINT PluginInfo_PluginType = 2\n\t// Plugin is a vulnerability detector.\n\tPluginInfo_VULN_DETECTION PluginInfo_PluginType = 3\n)\n\n// Enum value maps for PluginInfo_PluginType.\nvar (\n\tPluginInfo_PluginType_name = map[int32]string{\n\t\t0: \"PLUGIN_TYPE_UNSPECIFIED\",\n\t\t1: \"PORT_SCAN\",\n\t\t2: \"SERVICE_FINGERPRINT\",\n\t\t3: \"VULN_DETECTION\",\n\t}\n\tPluginInfo_PluginType_value = map[string]int32{\n\t\t\"PLUGIN_TYPE_UNSPECIFIED\": 0,\n\t\t\"PORT_SCAN\":               1,\n\t\t\"SERVICE_FINGERPRINT\":     2,\n\t\t\"VULN_DETECTION\":          3,\n\t}\n)\n\nfunc (x PluginInfo_PluginType) Enum() *PluginInfo_PluginType {\n\tp := new(PluginInfo_PluginType)\n\t*p = x\n\treturn p\n}\n\nfunc (x PluginInfo_PluginType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PluginInfo_PluginType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_plugin_representation_proto_enumTypes[0].Descriptor()\n}\n\nfunc (PluginInfo_PluginType) Type() protoreflect.EnumType {\n\treturn &file_plugin_representation_proto_enumTypes[0]\n}\n\nfunc (x PluginInfo_PluginType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use PluginInfo_PluginType.Descriptor instead.\nfunc (PluginInfo_PluginType) EnumDescriptor() ([]byte, []int) {\n\treturn file_plugin_representation_proto_rawDescGZIP(), []int{1, 0}\n}\n\n// Represents a PluginDefinition placeholder.\ntype PluginDefinition struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// PluginInfo of this definition.\n\tInfo *PluginInfo `protobuf:\"bytes,1,opt,name=info,proto3\" json:\"info,omitempty\"`\n\t// The name of the target service.\n\tTargetServiceName *TargetServiceName `protobuf:\"bytes,2,opt,name=target_service_name,json=targetServiceName,proto3\" json:\"target_service_name,omitempty\"`\n\t// The name of the target software.\n\tTargetSoftware *TargetSoftware `protobuf:\"bytes,3,opt,name=target_software,json=targetSoftware,proto3\" json:\"target_software,omitempty\"`\n\t// If the definition is for a web service or not.\n\tForWebService bool `protobuf:\"varint,4,opt,name=for_web_service,json=forWebService,proto3\" json:\"for_web_service,omitempty\"`\n\t// If the definition is for a specific operating system or not.\n\t// Note: this filter is executed within an AND condition with the other\n\t// filters. E.g. if target_service_name.value is \"http\" and\n\t// target_operating_system.osclass.family is \"Linux\" then the plugin will only\n\t// match if the service is http and the operating system is Linux.\n\tTargetOperatingSystemClass *TargetOperatingSystemClass `protobuf:\"bytes,5,opt,name=target_operating_system_class,json=targetOperatingSystemClass,proto3\" json:\"target_operating_system_class,omitempty\"`\n\tunknownFields              protoimpl.UnknownFields\n\tsizeCache                  protoimpl.SizeCache\n}\n\nfunc (x *PluginDefinition) Reset() {\n\t*x = PluginDefinition{}\n\tmi := &file_plugin_representation_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PluginDefinition) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PluginDefinition) ProtoMessage() {}\n\nfunc (x *PluginDefinition) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_representation_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PluginDefinition.ProtoReflect.Descriptor instead.\nfunc (*PluginDefinition) Descriptor() ([]byte, []int) {\n\treturn file_plugin_representation_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *PluginDefinition) GetInfo() *PluginInfo {\n\tif x != nil {\n\t\treturn x.Info\n\t}\n\treturn nil\n}\n\nfunc (x *PluginDefinition) GetTargetServiceName() *TargetServiceName {\n\tif x != nil {\n\t\treturn x.TargetServiceName\n\t}\n\treturn nil\n}\n\nfunc (x *PluginDefinition) GetTargetSoftware() *TargetSoftware {\n\tif x != nil {\n\t\treturn x.TargetSoftware\n\t}\n\treturn nil\n}\n\nfunc (x *PluginDefinition) GetForWebService() bool {\n\tif x != nil {\n\t\treturn x.ForWebService\n\t}\n\treturn false\n}\n\nfunc (x *PluginDefinition) GetTargetOperatingSystemClass() *TargetOperatingSystemClass {\n\tif x != nil {\n\t\treturn x.TargetOperatingSystemClass\n\t}\n\treturn nil\n}\n\n// Represents a PluginInfo annotation placeholder used by the\n// PluginDefinition proto above.\ntype PluginInfo struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Type of plugin.\n\tType PluginInfo_PluginType `protobuf:\"varint,1,opt,name=type,proto3,enum=tsunami.proto.PluginInfo_PluginType\" json:\"type,omitempty\"`\n\t// Name of the plugin.\n\tName string `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Version of the plugin\n\tVersion string `protobuf:\"bytes,3,opt,name=version,proto3\" json:\"version,omitempty\"`\n\t// Description of the plugin.\n\tDescription string `protobuf:\"bytes,4,opt,name=description,proto3\" json:\"description,omitempty\"`\n\t// Author of the plugin.\n\tAuthor        string `protobuf:\"bytes,5,opt,name=author,proto3\" json:\"author,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PluginInfo) Reset() {\n\t*x = PluginInfo{}\n\tmi := &file_plugin_representation_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PluginInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PluginInfo) ProtoMessage() {}\n\nfunc (x *PluginInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_representation_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PluginInfo.ProtoReflect.Descriptor instead.\nfunc (*PluginInfo) Descriptor() ([]byte, []int) {\n\treturn file_plugin_representation_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *PluginInfo) GetType() PluginInfo_PluginType {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn PluginInfo_PLUGIN_TYPE_UNSPECIFIED\n}\n\nfunc (x *PluginInfo) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *PluginInfo) GetVersion() string {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn \"\"\n}\n\nfunc (x *PluginInfo) GetDescription() string {\n\tif x != nil {\n\t\treturn x.Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *PluginInfo) GetAuthor() string {\n\tif x != nil {\n\t\treturn x.Author\n\t}\n\treturn \"\"\n}\n\n// Represents a ForServiceName annotation placeholder used by the\n// PluginDefinition proto above.\ntype TargetServiceName struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The value of the name of the target.\n\tValue         []string `protobuf:\"bytes,1,rep,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TargetServiceName) Reset() {\n\t*x = TargetServiceName{}\n\tmi := &file_plugin_representation_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TargetServiceName) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TargetServiceName) ProtoMessage() {}\n\nfunc (x *TargetServiceName) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_representation_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TargetServiceName.ProtoReflect.Descriptor instead.\nfunc (*TargetServiceName) Descriptor() ([]byte, []int) {\n\treturn file_plugin_representation_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *TargetServiceName) GetValue() []string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn nil\n}\n\n// Represents a ForSoftware annotation placeholder used by the\n// PluginDefinition proto above.\ntype TargetSoftware struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The name of the target software, case insensitive.\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Array of versions and version ranges of the target software.\n\tValue         []string `protobuf:\"bytes,2,rep,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TargetSoftware) Reset() {\n\t*x = TargetSoftware{}\n\tmi := &file_plugin_representation_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TargetSoftware) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TargetSoftware) ProtoMessage() {}\n\nfunc (x *TargetSoftware) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_representation_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TargetSoftware.ProtoReflect.Descriptor instead.\nfunc (*TargetSoftware) Descriptor() ([]byte, []int) {\n\treturn file_plugin_representation_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *TargetSoftware) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *TargetSoftware) GetValue() []string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn nil\n}\n\n// Represents a ForOperatingSystem annotation placeholder used by the\n// PluginDefinition proto above. These values are coming directly from the\n// port scanner's output (e.g. nmap).\ntype TargetOperatingSystemClass struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The vendor of the target operating system, e.g. \"Microsoft\"\n\tVendor []string `protobuf:\"bytes,1,rep,name=vendor,proto3\" json:\"vendor,omitempty\"`\n\t// The family of the target operating system, e.g. \"Windows\"\n\tOsFamily []string `protobuf:\"bytes,2,rep,name=os_family,json=osFamily,proto3\" json:\"os_family,omitempty\"`\n\t// The minimum accuracy of the target operating system, e.g. 90\n\tMinAccuracy   uint32 `protobuf:\"varint,3,opt,name=min_accuracy,json=minAccuracy,proto3\" json:\"min_accuracy,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TargetOperatingSystemClass) Reset() {\n\t*x = TargetOperatingSystemClass{}\n\tmi := &file_plugin_representation_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TargetOperatingSystemClass) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TargetOperatingSystemClass) ProtoMessage() {}\n\nfunc (x *TargetOperatingSystemClass) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_representation_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TargetOperatingSystemClass.ProtoReflect.Descriptor instead.\nfunc (*TargetOperatingSystemClass) Descriptor() ([]byte, []int) {\n\treturn file_plugin_representation_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *TargetOperatingSystemClass) GetVendor() []string {\n\tif x != nil {\n\t\treturn x.Vendor\n\t}\n\treturn nil\n}\n\nfunc (x *TargetOperatingSystemClass) GetOsFamily() []string {\n\tif x != nil {\n\t\treturn x.OsFamily\n\t}\n\treturn nil\n}\n\nfunc (x *TargetOperatingSystemClass) GetMinAccuracy() uint32 {\n\tif x != nil {\n\t\treturn x.MinAccuracy\n\t}\n\treturn 0\n}\n\nvar File_plugin_representation_proto protoreflect.FileDescriptor\n\nvar file_plugin_representation_proto_rawDesc = string([]byte{\n\t0x0a, 0x1b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65,\n\t0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf1, 0x02, 0x0a,\n\t0x10, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f,\n\t0x6e, 0x12, 0x2d, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x19, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,\n\t0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f,\n\t0x12, 0x50, 0x0a, 0x13, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e,\n\t0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61,\n\t0x72, 0x67, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52,\n\t0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61,\n\t0x6d, 0x65, 0x12, 0x46, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x6f, 0x66,\n\t0x74, 0x77, 0x61, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x73,\n\t0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x72, 0x67,\n\t0x65, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67,\n\t0x65, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x6f,\n\t0x72, 0x5f, 0x77, 0x65, 0x62, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x0d, 0x66, 0x6f, 0x72, 0x57, 0x65, 0x62, 0x53, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x12, 0x6c, 0x0a, 0x1d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6f, 0x70, 0x65,\n\t0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6c,\n\t0x61, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x73, 0x75, 0x6e,\n\t0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74,\n\t0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43,\n\t0x6c, 0x61, 0x73, 0x73, 0x52, 0x1a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72,\n\t0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43, 0x6c, 0x61, 0x73, 0x73,\n\t0x22, 0x95, 0x02, 0x0a, 0x0a, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12,\n\t0x38, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e,\n\t0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x6c,\n\t0x75, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54,\n\t0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a,\n\t0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,\n\t0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,\n\t0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65,\n\t0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74,\n\t0x68, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f,\n\t0x72, 0x22, 0x65, 0x0a, 0x0a, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12,\n\t0x1b, 0x0a, 0x17, 0x50, 0x4c, 0x55, 0x47, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55,\n\t0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09,\n\t0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x43, 0x41, 0x4e, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x53,\n\t0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x47, 0x45, 0x52, 0x50, 0x52, 0x49,\n\t0x4e, 0x54, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x56, 0x55, 0x4c, 0x4e, 0x5f, 0x44, 0x45, 0x54,\n\t0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x22, 0x29, 0x0a, 0x11, 0x54, 0x61, 0x72, 0x67,\n\t0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a,\n\t0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,\n\t0x6c, 0x75, 0x65, 0x22, 0x3a, 0x0a, 0x0e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x6f, 0x66,\n\t0x74, 0x77, 0x61, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,\n\t0x75, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,\n\t0x74, 0x0a, 0x1a, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,\n\t0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x16, 0x0a,\n\t0x06, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76,\n\t0x65, 0x6e, 0x64, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x73, 0x5f, 0x66, 0x61, 0x6d, 0x69,\n\t0x6c, 0x79, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x73, 0x46, 0x61, 0x6d, 0x69,\n\t0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x61, 0x63, 0x63, 0x75, 0x72, 0x61,\n\t0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x41, 0x63, 0x63,\n\t0x75, 0x72, 0x61, 0x63, 0x79, 0x42, 0x7b, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f,\n\t0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x42, 0x1a, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65,\n\t0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50, 0x01, 0x5a,\n\t0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67,\n\t0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72,\n\t0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_plugin_representation_proto_rawDescOnce sync.Once\n\tfile_plugin_representation_proto_rawDescData []byte\n)\n\nfunc file_plugin_representation_proto_rawDescGZIP() []byte {\n\tfile_plugin_representation_proto_rawDescOnce.Do(func() {\n\t\tfile_plugin_representation_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_plugin_representation_proto_rawDesc), len(file_plugin_representation_proto_rawDesc)))\n\t})\n\treturn file_plugin_representation_proto_rawDescData\n}\n\nvar file_plugin_representation_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_plugin_representation_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_plugin_representation_proto_goTypes = []any{\n\t(PluginInfo_PluginType)(0),         // 0: tsunami.proto.PluginInfo.PluginType\n\t(*PluginDefinition)(nil),           // 1: tsunami.proto.PluginDefinition\n\t(*PluginInfo)(nil),                 // 2: tsunami.proto.PluginInfo\n\t(*TargetServiceName)(nil),          // 3: tsunami.proto.TargetServiceName\n\t(*TargetSoftware)(nil),             // 4: tsunami.proto.TargetSoftware\n\t(*TargetOperatingSystemClass)(nil), // 5: tsunami.proto.TargetOperatingSystemClass\n}\nvar file_plugin_representation_proto_depIdxs = []int32{\n\t2, // 0: tsunami.proto.PluginDefinition.info:type_name -> tsunami.proto.PluginInfo\n\t3, // 1: tsunami.proto.PluginDefinition.target_service_name:type_name -> tsunami.proto.TargetServiceName\n\t4, // 2: tsunami.proto.PluginDefinition.target_software:type_name -> tsunami.proto.TargetSoftware\n\t5, // 3: tsunami.proto.PluginDefinition.target_operating_system_class:type_name -> tsunami.proto.TargetOperatingSystemClass\n\t0, // 4: tsunami.proto.PluginInfo.type:type_name -> tsunami.proto.PluginInfo.PluginType\n\t5, // [5:5] is the sub-list for method output_type\n\t5, // [5:5] is the sub-list for method input_type\n\t5, // [5:5] is the sub-list for extension type_name\n\t5, // [5:5] is the sub-list for extension extendee\n\t0, // [0:5] is the sub-list for field type_name\n}\n\nfunc init() { file_plugin_representation_proto_init() }\nfunc file_plugin_representation_proto_init() {\n\tif File_plugin_representation_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_plugin_representation_proto_rawDesc), len(file_plugin_representation_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_plugin_representation_proto_goTypes,\n\t\tDependencyIndexes: file_plugin_representation_proto_depIdxs,\n\t\tEnumInfos:         file_plugin_representation_proto_enumTypes,\n\t\tMessageInfos:      file_plugin_representation_proto_msgTypes,\n\t}.Build()\n\tFile_plugin_representation_proto = out.File\n\tfile_plugin_representation_proto_goTypes = nil\n\tfile_plugin_representation_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/plugin_service.pb.go",
    "content": "//\n// Copyright 2022 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Model for the plugin RPC service protocol.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: plugin_service.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Represents a run request with all matched plugins that will need to run\n// as well as the target to run against.\ntype RunRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Target of the plugins.\n\tTarget *TargetInfo `protobuf:\"bytes,1,opt,name=target,proto3\" json:\"target,omitempty\"`\n\t// All matched plugins that will need to run.\n\tPlugins       []*MatchedPlugin `protobuf:\"bytes,2,rep,name=plugins,proto3\" json:\"plugins,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RunRequest) Reset() {\n\t*x = RunRequest{}\n\tmi := &file_plugin_service_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RunRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RunRequest) ProtoMessage() {}\n\nfunc (x *RunRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RunRequest.ProtoReflect.Descriptor instead.\nfunc (*RunRequest) Descriptor() ([]byte, []int) {\n\treturn file_plugin_service_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *RunRequest) GetTarget() *TargetInfo {\n\tif x != nil {\n\t\treturn x.Target\n\t}\n\treturn nil\n}\n\nfunc (x *RunRequest) GetPlugins() []*MatchedPlugin {\n\tif x != nil {\n\t\treturn x.Plugins\n\t}\n\treturn nil\n}\n\n// Compact representation of RunRequest.\ntype RunCompactRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Target of the plugins.\n\tTarget *TargetInfo `protobuf:\"bytes,1,opt,name=target,proto3\" json:\"target,omitempty\"`\n\t// All network services that are targeted by some of the plugins.\n\tServices []*NetworkService `protobuf:\"bytes,2,rep,name=services,proto3\" json:\"services,omitempty\"`\n\t// All plugins that should be executed during the run.\n\tPlugins []*PluginDefinition `protobuf:\"bytes,3,rep,name=plugins,proto3\" json:\"plugins,omitempty\"`\n\t// The concrete map of plugin/network service pairs that should be scanned.\n\tScanTargets   []*RunCompactRequest_PluginNetworkServiceTarget `protobuf:\"bytes,4,rep,name=scan_targets,json=scanTargets,proto3\" json:\"scan_targets,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RunCompactRequest) Reset() {\n\t*x = RunCompactRequest{}\n\tmi := &file_plugin_service_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RunCompactRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RunCompactRequest) ProtoMessage() {}\n\nfunc (x *RunCompactRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RunCompactRequest.ProtoReflect.Descriptor instead.\nfunc (*RunCompactRequest) Descriptor() ([]byte, []int) {\n\treturn file_plugin_service_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *RunCompactRequest) GetTarget() *TargetInfo {\n\tif x != nil {\n\t\treturn x.Target\n\t}\n\treturn nil\n}\n\nfunc (x *RunCompactRequest) GetServices() []*NetworkService {\n\tif x != nil {\n\t\treturn x.Services\n\t}\n\treturn nil\n}\n\nfunc (x *RunCompactRequest) GetPlugins() []*PluginDefinition {\n\tif x != nil {\n\t\treturn x.Plugins\n\t}\n\treturn nil\n}\n\nfunc (x *RunCompactRequest) GetScanTargets() []*RunCompactRequest_PluginNetworkServiceTarget {\n\tif x != nil {\n\t\treturn x.ScanTargets\n\t}\n\treturn nil\n}\n\n// Represents the plugin needed to run by the language-specific server\n// as well as all the matched network services for the plugin.\ntype MatchedPlugin struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// All matched network services from the reconnaissance report.\n\tServices []*NetworkService `protobuf:\"bytes,1,rep,name=services,proto3\" json:\"services,omitempty\"`\n\t// Plugin to run.\n\tPlugin        *PluginDefinition `protobuf:\"bytes,2,opt,name=plugin,proto3\" json:\"plugin,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *MatchedPlugin) Reset() {\n\t*x = MatchedPlugin{}\n\tmi := &file_plugin_service_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *MatchedPlugin) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*MatchedPlugin) ProtoMessage() {}\n\nfunc (x *MatchedPlugin) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use MatchedPlugin.ProtoReflect.Descriptor instead.\nfunc (*MatchedPlugin) Descriptor() ([]byte, []int) {\n\treturn file_plugin_service_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *MatchedPlugin) GetServices() []*NetworkService {\n\tif x != nil {\n\t\treturn x.Services\n\t}\n\treturn nil\n}\n\nfunc (x *MatchedPlugin) GetPlugin() *PluginDefinition {\n\tif x != nil {\n\t\treturn x.Plugin\n\t}\n\treturn nil\n}\n\n// Represents a run response with the only field being all DetectionReports\n// generated by the language-specific server.\ntype RunResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tReports       *DetectionReportList   `protobuf:\"bytes,1,opt,name=reports,proto3\" json:\"reports,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RunResponse) Reset() {\n\t*x = RunResponse{}\n\tmi := &file_plugin_service_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RunResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RunResponse) ProtoMessage() {}\n\nfunc (x *RunResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RunResponse.ProtoReflect.Descriptor instead.\nfunc (*RunResponse) Descriptor() ([]byte, []int) {\n\treturn file_plugin_service_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *RunResponse) GetReports() *DetectionReportList {\n\tif x != nil {\n\t\treturn x.Reports\n\t}\n\treturn nil\n}\n\n// Represents a request to list all plugins from the requested server.\ntype ListPluginsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListPluginsRequest) Reset() {\n\t*x = ListPluginsRequest{}\n\tmi := &file_plugin_service_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListPluginsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListPluginsRequest) ProtoMessage() {}\n\nfunc (x *ListPluginsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListPluginsRequest.ProtoReflect.Descriptor instead.\nfunc (*ListPluginsRequest) Descriptor() ([]byte, []int) {\n\treturn file_plugin_service_proto_rawDescGZIP(), []int{4}\n}\n\n// Represents a response containing a list of all plugins\n// from the requested server.\ntype ListPluginsResponse struct {\n\tstate   protoimpl.MessageState `protogen:\"open.v1\"`\n\tPlugins []*PluginDefinition    `protobuf:\"bytes,1,rep,name=plugins,proto3\" json:\"plugins,omitempty\"`\n\t// Plugin service can indicate here that it RunRequest should be compact\n\t// (compact_targets should be populated instead of MatchedPlugin plugins).\n\tWantCompactRunRequest bool `protobuf:\"varint,2,opt,name=want_compact_run_request,json=wantCompactRunRequest,proto3\" json:\"want_compact_run_request,omitempty\"`\n\tunknownFields         protoimpl.UnknownFields\n\tsizeCache             protoimpl.SizeCache\n}\n\nfunc (x *ListPluginsResponse) Reset() {\n\t*x = ListPluginsResponse{}\n\tmi := &file_plugin_service_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListPluginsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListPluginsResponse) ProtoMessage() {}\n\nfunc (x *ListPluginsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListPluginsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListPluginsResponse) Descriptor() ([]byte, []int) {\n\treturn file_plugin_service_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *ListPluginsResponse) GetPlugins() []*PluginDefinition {\n\tif x != nil {\n\t\treturn x.Plugins\n\t}\n\treturn nil\n}\n\nfunc (x *ListPluginsResponse) GetWantCompactRunRequest() bool {\n\tif x != nil {\n\t\treturn x.WantCompactRunRequest\n\t}\n\treturn false\n}\n\n// Indexes in the following structure point to the services/plugins defined\n// below. (The order is safe, guaranteed by the proto specification: \"The\n// order of the elements with respect to each other is preserved when parsing,\n// though the ordering with respect to other fields is lost.\")\ntype RunCompactRequest_PluginNetworkServiceTarget struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The index of the plugin to run.\n\tPluginIndex uint32 `protobuf:\"varint,1,opt,name=plugin_index,json=pluginIndex,proto3\" json:\"plugin_index,omitempty\"`\n\t// The index of the network service to run against.\n\tServiceIndex  uint32 `protobuf:\"varint,2,opt,name=service_index,json=serviceIndex,proto3\" json:\"service_index,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) Reset() {\n\t*x = RunCompactRequest_PluginNetworkServiceTarget{}\n\tmi := &file_plugin_service_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RunCompactRequest_PluginNetworkServiceTarget) ProtoMessage() {}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) ProtoReflect() protoreflect.Message {\n\tmi := &file_plugin_service_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RunCompactRequest_PluginNetworkServiceTarget.ProtoReflect.Descriptor instead.\nfunc (*RunCompactRequest_PluginNetworkServiceTarget) Descriptor() ([]byte, []int) {\n\treturn file_plugin_service_proto_rawDescGZIP(), []int{1, 0}\n}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) GetPluginIndex() uint32 {\n\tif x != nil {\n\t\treturn x.PluginIndex\n\t}\n\treturn 0\n}\n\nfunc (x *RunCompactRequest_PluginNetworkServiceTarget) GetServiceIndex() uint32 {\n\tif x != nil {\n\t\treturn x.ServiceIndex\n\t}\n\treturn 0\n}\n\nvar File_plugin_service_proto protoreflect.FileDescriptor\n\nvar file_plugin_service_proto_rawDesc = string([]byte{\n\t0x0a, 0x14, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f,\n\t0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x70,\n\t0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61,\n\t0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x72, 0x65, 0x63, 0x6f,\n\t0x6e, 0x6e, 0x61, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x22, 0x77, 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31,\n\t0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19,\n\t0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54,\n\t0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65,\n\t0x74, 0x12, 0x36, 0x0a, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,\n\t0x52, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x22, 0x82, 0x03, 0x0a, 0x11, 0x52, 0x75,\n\t0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,\n\t0x31, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x19, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,\n\t0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67,\n\t0x65, 0x74, 0x12, 0x39, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x02,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76,\n\t0x69, 0x63, 0x65, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x39, 0x0a,\n\t0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f,\n\t0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50,\n\t0x6c, 0x75, 0x67, 0x69, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52,\n\t0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x12, 0x5e, 0x0a, 0x0c, 0x73, 0x63, 0x61, 0x6e,\n\t0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b,\n\t0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52,\n\t0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x0b, 0x73, 0x63, 0x61,\n\t0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x1a, 0x64, 0x0a, 0x1a, 0x50, 0x6c, 0x75, 0x67,\n\t0x69, 0x6e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e,\n\t0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x6c,\n\t0x75, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x72,\n\t0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,\n\t0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x83,\n\t0x01, 0x0a, 0x0d, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,\n\t0x12, 0x39, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,\n\t0x65, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, 0x70,\n\t0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x73,\n\t0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x6c, 0x75, 0x67,\n\t0x69, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x70, 0x6c,\n\t0x75, 0x67, 0x69, 0x6e, 0x22, 0x4b, 0x0a, 0x0b, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,\n\t0x70, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74,\n\t0x73, 0x22, 0x14, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x89, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74,\n\t0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,\n\t0x39, 0x0a, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,\n\t0x32, 0x1f, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f,\n\t0x6e, 0x52, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x77, 0x61,\n\t0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x72,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x77, 0x61,\n\t0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x32, 0xf5, 0x01, 0x0a, 0x0d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x03, 0x52, 0x75, 0x6e, 0x12, 0x19, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x75, 0x6e,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d,\n\t0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x70,\n\t0x61, 0x63, 0x74, 0x12, 0x20, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x22, 0x00, 0x12, 0x56, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69,\n\t0x6e, 0x73, 0x12, 0x21, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,\n\t0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x74, 0x0a, 0x18, 0x63,\n\t0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d,\n\t0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x13, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53,\n\t0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50, 0x01, 0x5a, 0x41,\n\t0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c,\n\t0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69,\n\t0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_plugin_service_proto_rawDescOnce sync.Once\n\tfile_plugin_service_proto_rawDescData []byte\n)\n\nfunc file_plugin_service_proto_rawDescGZIP() []byte {\n\tfile_plugin_service_proto_rawDescOnce.Do(func() {\n\t\tfile_plugin_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_plugin_service_proto_rawDesc), len(file_plugin_service_proto_rawDesc)))\n\t})\n\treturn file_plugin_service_proto_rawDescData\n}\n\nvar file_plugin_service_proto_msgTypes = make([]protoimpl.MessageInfo, 7)\nvar file_plugin_service_proto_goTypes = []any{\n\t(*RunRequest)(nil),                                   // 0: tsunami.proto.RunRequest\n\t(*RunCompactRequest)(nil),                            // 1: tsunami.proto.RunCompactRequest\n\t(*MatchedPlugin)(nil),                                // 2: tsunami.proto.MatchedPlugin\n\t(*RunResponse)(nil),                                  // 3: tsunami.proto.RunResponse\n\t(*ListPluginsRequest)(nil),                           // 4: tsunami.proto.ListPluginsRequest\n\t(*ListPluginsResponse)(nil),                          // 5: tsunami.proto.ListPluginsResponse\n\t(*RunCompactRequest_PluginNetworkServiceTarget)(nil), // 6: tsunami.proto.RunCompactRequest.PluginNetworkServiceTarget\n\t(*TargetInfo)(nil),                                   // 7: tsunami.proto.TargetInfo\n\t(*NetworkService)(nil),                               // 8: tsunami.proto.NetworkService\n\t(*PluginDefinition)(nil),                             // 9: tsunami.proto.PluginDefinition\n\t(*DetectionReportList)(nil),                          // 10: tsunami.proto.DetectionReportList\n}\nvar file_plugin_service_proto_depIdxs = []int32{\n\t7,  // 0: tsunami.proto.RunRequest.target:type_name -> tsunami.proto.TargetInfo\n\t2,  // 1: tsunami.proto.RunRequest.plugins:type_name -> tsunami.proto.MatchedPlugin\n\t7,  // 2: tsunami.proto.RunCompactRequest.target:type_name -> tsunami.proto.TargetInfo\n\t8,  // 3: tsunami.proto.RunCompactRequest.services:type_name -> tsunami.proto.NetworkService\n\t9,  // 4: tsunami.proto.RunCompactRequest.plugins:type_name -> tsunami.proto.PluginDefinition\n\t6,  // 5: tsunami.proto.RunCompactRequest.scan_targets:type_name -> tsunami.proto.RunCompactRequest.PluginNetworkServiceTarget\n\t8,  // 6: tsunami.proto.MatchedPlugin.services:type_name -> tsunami.proto.NetworkService\n\t9,  // 7: tsunami.proto.MatchedPlugin.plugin:type_name -> tsunami.proto.PluginDefinition\n\t10, // 8: tsunami.proto.RunResponse.reports:type_name -> tsunami.proto.DetectionReportList\n\t9,  // 9: tsunami.proto.ListPluginsResponse.plugins:type_name -> tsunami.proto.PluginDefinition\n\t0,  // 10: tsunami.proto.PluginService.Run:input_type -> tsunami.proto.RunRequest\n\t1,  // 11: tsunami.proto.PluginService.RunCompact:input_type -> tsunami.proto.RunCompactRequest\n\t4,  // 12: tsunami.proto.PluginService.ListPlugins:input_type -> tsunami.proto.ListPluginsRequest\n\t3,  // 13: tsunami.proto.PluginService.Run:output_type -> tsunami.proto.RunResponse\n\t3,  // 14: tsunami.proto.PluginService.RunCompact:output_type -> tsunami.proto.RunResponse\n\t5,  // 15: tsunami.proto.PluginService.ListPlugins:output_type -> tsunami.proto.ListPluginsResponse\n\t13, // [13:16] is the sub-list for method output_type\n\t10, // [10:13] is the sub-list for method input_type\n\t10, // [10:10] is the sub-list for extension type_name\n\t10, // [10:10] is the sub-list for extension extendee\n\t0,  // [0:10] is the sub-list for field type_name\n}\n\nfunc init() { file_plugin_service_proto_init() }\nfunc file_plugin_service_proto_init() {\n\tif File_plugin_service_proto != nil {\n\t\treturn\n\t}\n\tfile_detection_proto_init()\n\tfile_network_service_proto_init()\n\tfile_plugin_representation_proto_init()\n\tfile_reconnaissance_proto_init()\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_plugin_service_proto_rawDesc), len(file_plugin_service_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   7,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_plugin_service_proto_goTypes,\n\t\tDependencyIndexes: file_plugin_service_proto_depIdxs,\n\t\tMessageInfos:      file_plugin_service_proto_msgTypes,\n\t}.Build()\n\tFile_plugin_service_proto = out.File\n\tfile_plugin_service_proto_goTypes = nil\n\tfile_plugin_service_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/reconnaissance.pb.go",
    "content": "//\n// Copyright 2019 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for all the reconnaissance information gathered by Tsunami.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: reconnaissance.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Detailed information about the scanning target.\ntype TargetInfo struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// All the known network endpoints of the scanning target.\n\tNetworkEndpoints       []*NetworkEndpoint      `protobuf:\"bytes,1,rep,name=network_endpoints,json=networkEndpoints,proto3\" json:\"network_endpoints,omitempty\"`\n\tOperatingSystemClasses []*OperatingSystemClass `protobuf:\"bytes,2,rep,name=operating_system_classes,json=operatingSystemClasses,proto3\" json:\"operating_system_classes,omitempty\"`\n\tunknownFields          protoimpl.UnknownFields\n\tsizeCache              protoimpl.SizeCache\n}\n\nfunc (x *TargetInfo) Reset() {\n\t*x = TargetInfo{}\n\tmi := &file_reconnaissance_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TargetInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TargetInfo) ProtoMessage() {}\n\nfunc (x *TargetInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_reconnaissance_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TargetInfo.ProtoReflect.Descriptor instead.\nfunc (*TargetInfo) Descriptor() ([]byte, []int) {\n\treturn file_reconnaissance_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *TargetInfo) GetNetworkEndpoints() []*NetworkEndpoint {\n\tif x != nil {\n\t\treturn x.NetworkEndpoints\n\t}\n\treturn nil\n}\n\nfunc (x *TargetInfo) GetOperatingSystemClasses() []*OperatingSystemClass {\n\tif x != nil {\n\t\treturn x.OperatingSystemClasses\n\t}\n\treturn nil\n}\n\n// Represents a ForOperatingSystem annotation placeholder used by the\n// PluginDefinition proto above.\n// For possible values, consult the following database:\n// https://raw.githubusercontent.com/nmap/nmap/master/nmap-os-db\ntype OperatingSystemClass struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The type of the target operating system, e.g. \"general purpose\"\n\tType string `protobuf:\"bytes,1,opt,name=type,proto3\" json:\"type,omitempty\"`\n\t// The vendor of the target operating system, e.g. \"Linux\"\n\tVendor string `protobuf:\"bytes,2,opt,name=vendor,proto3\" json:\"vendor,omitempty\"`\n\t// The family of the target operating system, e.g. \"Linux\"\n\tOsFamily string `protobuf:\"bytes,3,opt,name=os_family,json=osFamily,proto3\" json:\"os_family,omitempty\"`\n\t// The generation of the target operating system, e.g. \"2.6.X\"\n\tOsGeneration string `protobuf:\"bytes,4,opt,name=os_generation,json=osGeneration,proto3\" json:\"os_generation,omitempty\"`\n\t// The estimated accuracy of the target operating system, e.g. 90\n\tAccuracy      uint32 `protobuf:\"varint,5,opt,name=accuracy,proto3\" json:\"accuracy,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *OperatingSystemClass) Reset() {\n\t*x = OperatingSystemClass{}\n\tmi := &file_reconnaissance_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *OperatingSystemClass) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OperatingSystemClass) ProtoMessage() {}\n\nfunc (x *OperatingSystemClass) ProtoReflect() protoreflect.Message {\n\tmi := &file_reconnaissance_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OperatingSystemClass.ProtoReflect.Descriptor instead.\nfunc (*OperatingSystemClass) Descriptor() ([]byte, []int) {\n\treturn file_reconnaissance_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *OperatingSystemClass) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *OperatingSystemClass) GetVendor() string {\n\tif x != nil {\n\t\treturn x.Vendor\n\t}\n\treturn \"\"\n}\n\nfunc (x *OperatingSystemClass) GetOsFamily() string {\n\tif x != nil {\n\t\treturn x.OsFamily\n\t}\n\treturn \"\"\n}\n\nfunc (x *OperatingSystemClass) GetOsGeneration() string {\n\tif x != nil {\n\t\treturn x.OsGeneration\n\t}\n\treturn \"\"\n}\n\nfunc (x *OperatingSystemClass) GetAccuracy() uint32 {\n\tif x != nil {\n\t\treturn x.Accuracy\n\t}\n\treturn 0\n}\n\n// Report from a port scanner.\ntype PortScanningReport struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Information about the scanning target.\n\tTargetInfo *TargetInfo `protobuf:\"bytes,1,opt,name=target_info,json=targetInfo,proto3\" json:\"target_info,omitempty\"`\n\t// List of all the exposed network services.\n\tNetworkServices []*NetworkService `protobuf:\"bytes,2,rep,name=network_services,json=networkServices,proto3\" json:\"network_services,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *PortScanningReport) Reset() {\n\t*x = PortScanningReport{}\n\tmi := &file_reconnaissance_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PortScanningReport) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PortScanningReport) ProtoMessage() {}\n\nfunc (x *PortScanningReport) ProtoReflect() protoreflect.Message {\n\tmi := &file_reconnaissance_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PortScanningReport.ProtoReflect.Descriptor instead.\nfunc (*PortScanningReport) Descriptor() ([]byte, []int) {\n\treturn file_reconnaissance_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *PortScanningReport) GetTargetInfo() *TargetInfo {\n\tif x != nil {\n\t\treturn x.TargetInfo\n\t}\n\treturn nil\n}\n\nfunc (x *PortScanningReport) GetNetworkServices() []*NetworkService {\n\tif x != nil {\n\t\treturn x.NetworkServices\n\t}\n\treturn nil\n}\n\n// Report from a service fingerprinter.\ntype FingerprintingReport struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// List of all the identified network services after fingerprinting.\n\tNetworkServices []*NetworkService `protobuf:\"bytes,3,rep,name=network_services,json=networkServices,proto3\" json:\"network_services,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *FingerprintingReport) Reset() {\n\t*x = FingerprintingReport{}\n\tmi := &file_reconnaissance_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *FingerprintingReport) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*FingerprintingReport) ProtoMessage() {}\n\nfunc (x *FingerprintingReport) ProtoReflect() protoreflect.Message {\n\tmi := &file_reconnaissance_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use FingerprintingReport.ProtoReflect.Descriptor instead.\nfunc (*FingerprintingReport) Descriptor() ([]byte, []int) {\n\treturn file_reconnaissance_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *FingerprintingReport) GetNetworkServices() []*NetworkService {\n\tif x != nil {\n\t\treturn x.NetworkServices\n\t}\n\treturn nil\n}\n\n// Full reconnaissance report about a single scanning target.\ntype ReconnaissanceReport struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Information about the scanning target.\n\tTargetInfo *TargetInfo `protobuf:\"bytes,1,opt,name=target_info,json=targetInfo,proto3\" json:\"target_info,omitempty\"`\n\t// All exposed network services of the scanning target.\n\tNetworkServices []*NetworkService `protobuf:\"bytes,2,rep,name=network_services,json=networkServices,proto3\" json:\"network_services,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *ReconnaissanceReport) Reset() {\n\t*x = ReconnaissanceReport{}\n\tmi := &file_reconnaissance_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReconnaissanceReport) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReconnaissanceReport) ProtoMessage() {}\n\nfunc (x *ReconnaissanceReport) ProtoReflect() protoreflect.Message {\n\tmi := &file_reconnaissance_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReconnaissanceReport.ProtoReflect.Descriptor instead.\nfunc (*ReconnaissanceReport) Descriptor() ([]byte, []int) {\n\treturn file_reconnaissance_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *ReconnaissanceReport) GetTargetInfo() *TargetInfo {\n\tif x != nil {\n\t\treturn x.TargetInfo\n\t}\n\treturn nil\n}\n\nfunc (x *ReconnaissanceReport) GetNetworkServices() []*NetworkService {\n\tif x != nil {\n\t\treturn x.NetworkServices\n\t}\n\treturn nil\n}\n\nvar File_reconnaissance_proto protoreflect.FileDescriptor\n\nvar file_reconnaissance_proto_rawDesc = string([]byte{\n\t0x0a, 0x14, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x61, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x63, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb8, 0x01, 0x0a, 0x0a,\n\t0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4b, 0x0a, 0x11, 0x6e, 0x65,\n\t0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18,\n\t0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x6e, 0x64,\n\t0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x10, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x6e,\n\t0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x5d, 0x0a, 0x18, 0x6f, 0x70, 0x65, 0x72, 0x61,\n\t0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6c, 0x61, 0x73,\n\t0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x73, 0x75, 0x6e,\n\t0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74,\n\t0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x52, 0x16,\n\t0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43,\n\t0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x22, 0xa0, 0x01, 0x0a, 0x14, 0x4f, 0x70, 0x65, 0x72, 0x61,\n\t0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12,\n\t0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74,\n\t0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x06, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6f,\n\t0x73, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,\n\t0x6f, 0x73, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x73, 0x5f, 0x67,\n\t0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0c, 0x6f, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a,\n\t0x08, 0x61, 0x63, 0x63, 0x75, 0x72, 0x61, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52,\n\t0x08, 0x61, 0x63, 0x63, 0x75, 0x72, 0x61, 0x63, 0x79, 0x22, 0x9a, 0x01, 0x0a, 0x12, 0x50, 0x6f,\n\t0x72, 0x74, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,\n\t0x12, 0x3a, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f,\n\t0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x10,\n\t0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,\n\t0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x0f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x60, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72,\n\t0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x48,\n\t0x0a, 0x10, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,\n\t0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61,\n\t0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,\n\t0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x0f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,\n\t0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x9c, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x63,\n\t0x6f, 0x6e, 0x6e, 0x61, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72,\n\t0x74, 0x12, 0x3a, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x6e, 0x66,\n\t0x6f, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x48, 0x0a,\n\t0x10, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d,\n\t0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53,\n\t0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x0f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53,\n\t0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x42, 0x75, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x67,\n\t0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x42, 0x14, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x61, 0x69, 0x73, 0x73, 0x61,\n\t0x6e, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74,\n\t0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d,\n\t0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x73,\n\t0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_reconnaissance_proto_rawDescOnce sync.Once\n\tfile_reconnaissance_proto_rawDescData []byte\n)\n\nfunc file_reconnaissance_proto_rawDescGZIP() []byte {\n\tfile_reconnaissance_proto_rawDescOnce.Do(func() {\n\t\tfile_reconnaissance_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_reconnaissance_proto_rawDesc), len(file_reconnaissance_proto_rawDesc)))\n\t})\n\treturn file_reconnaissance_proto_rawDescData\n}\n\nvar file_reconnaissance_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_reconnaissance_proto_goTypes = []any{\n\t(*TargetInfo)(nil),           // 0: tsunami.proto.TargetInfo\n\t(*OperatingSystemClass)(nil), // 1: tsunami.proto.OperatingSystemClass\n\t(*PortScanningReport)(nil),   // 2: tsunami.proto.PortScanningReport\n\t(*FingerprintingReport)(nil), // 3: tsunami.proto.FingerprintingReport\n\t(*ReconnaissanceReport)(nil), // 4: tsunami.proto.ReconnaissanceReport\n\t(*NetworkEndpoint)(nil),      // 5: tsunami.proto.NetworkEndpoint\n\t(*NetworkService)(nil),       // 6: tsunami.proto.NetworkService\n}\nvar file_reconnaissance_proto_depIdxs = []int32{\n\t5, // 0: tsunami.proto.TargetInfo.network_endpoints:type_name -> tsunami.proto.NetworkEndpoint\n\t1, // 1: tsunami.proto.TargetInfo.operating_system_classes:type_name -> tsunami.proto.OperatingSystemClass\n\t0, // 2: tsunami.proto.PortScanningReport.target_info:type_name -> tsunami.proto.TargetInfo\n\t6, // 3: tsunami.proto.PortScanningReport.network_services:type_name -> tsunami.proto.NetworkService\n\t6, // 4: tsunami.proto.FingerprintingReport.network_services:type_name -> tsunami.proto.NetworkService\n\t0, // 5: tsunami.proto.ReconnaissanceReport.target_info:type_name -> tsunami.proto.TargetInfo\n\t6, // 6: tsunami.proto.ReconnaissanceReport.network_services:type_name -> tsunami.proto.NetworkService\n\t7, // [7:7] is the sub-list for method output_type\n\t7, // [7:7] is the sub-list for method input_type\n\t7, // [7:7] is the sub-list for extension type_name\n\t7, // [7:7] is the sub-list for extension extendee\n\t0, // [0:7] is the sub-list for field type_name\n}\n\nfunc init() { file_reconnaissance_proto_init() }\nfunc file_reconnaissance_proto_init() {\n\tif File_reconnaissance_proto != nil {\n\t\treturn\n\t}\n\tfile_network_proto_init()\n\tfile_network_service_proto_init()\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_reconnaissance_proto_rawDesc), len(file_reconnaissance_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_reconnaissance_proto_goTypes,\n\t\tDependencyIndexes: file_reconnaissance_proto_depIdxs,\n\t\tMessageInfos:      file_reconnaissance_proto_msgTypes,\n\t}.Build()\n\tFile_reconnaissance_proto = out.File\n\tfile_reconnaissance_proto_goTypes = nil\n\tfile_reconnaissance_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/scan_results.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing scanning results.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: scan_results.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\tdurationpb \"google.golang.org/protobuf/types/known/durationpb\"\n\ttimestamppb \"google.golang.org/protobuf/types/known/timestamppb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Execution status of the scan.\n// NEXT ID: 5\ntype ScanStatus int32\n\nconst (\n\t// Unspecified status.\n\tScanStatus_SCAN_STATUS_UNSPECIFIED ScanStatus = 0\n\t// Scan finished successfully.\n\tScanStatus_SUCCEEDED ScanStatus = 1\n\t// Scan finished with only a small set of selected detectors succeeded.\n\tScanStatus_PARTIALLY_SUCCEEDED ScanStatus = 4\n\t// Scan failed.\n\tScanStatus_FAILED ScanStatus = 2\n\t// Scan cancelled.\n\tScanStatus_CANCELLED ScanStatus = 3\n)\n\n// Enum value maps for ScanStatus.\nvar (\n\tScanStatus_name = map[int32]string{\n\t\t0: \"SCAN_STATUS_UNSPECIFIED\",\n\t\t1: \"SUCCEEDED\",\n\t\t4: \"PARTIALLY_SUCCEEDED\",\n\t\t2: \"FAILED\",\n\t\t3: \"CANCELLED\",\n\t}\n\tScanStatus_value = map[string]int32{\n\t\t\"SCAN_STATUS_UNSPECIFIED\": 0,\n\t\t\"SUCCEEDED\":               1,\n\t\t\"PARTIALLY_SUCCEEDED\":     4,\n\t\t\"FAILED\":                  2,\n\t\t\"CANCELLED\":               3,\n\t}\n)\n\nfunc (x ScanStatus) Enum() *ScanStatus {\n\tp := new(ScanStatus)\n\t*p = x\n\treturn p\n}\n\nfunc (x ScanStatus) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (ScanStatus) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_scan_results_proto_enumTypes[0].Descriptor()\n}\n\nfunc (ScanStatus) Type() protoreflect.EnumType {\n\treturn &file_scan_results_proto_enumTypes[0]\n}\n\nfunc (x ScanStatus) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use ScanStatus.Descriptor instead.\nfunc (ScanStatus) EnumDescriptor() ([]byte, []int) {\n\treturn file_scan_results_proto_rawDescGZIP(), []int{0}\n}\n\n// A single vulnerability finding for a specific service.\ntype ScanFinding struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Information about the scanned target.\n\tTargetInfo *TargetInfo `protobuf:\"bytes,1,opt,name=target_info,json=targetInfo,proto3\" json:\"target_info,omitempty\"`\n\t// Information about the scanned network service.\n\tNetworkService *NetworkService `protobuf:\"bytes,2,opt,name=network_service,json=networkService,proto3\" json:\"network_service,omitempty\"`\n\t// Details about the detected vulnerability.\n\tVulnerability *Vulnerability `protobuf:\"bytes,3,opt,name=vulnerability,proto3\" json:\"vulnerability,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ScanFinding) Reset() {\n\t*x = ScanFinding{}\n\tmi := &file_scan_results_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ScanFinding) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ScanFinding) ProtoMessage() {}\n\nfunc (x *ScanFinding) ProtoReflect() protoreflect.Message {\n\tmi := &file_scan_results_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ScanFinding.ProtoReflect.Descriptor instead.\nfunc (*ScanFinding) Descriptor() ([]byte, []int) {\n\treturn file_scan_results_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ScanFinding) GetTargetInfo() *TargetInfo {\n\tif x != nil {\n\t\treturn x.TargetInfo\n\t}\n\treturn nil\n}\n\nfunc (x *ScanFinding) GetNetworkService() *NetworkService {\n\tif x != nil {\n\t\treturn x.NetworkService\n\t}\n\treturn nil\n}\n\nfunc (x *ScanFinding) GetVulnerability() *Vulnerability {\n\tif x != nil {\n\t\treturn x.Vulnerability\n\t}\n\treturn nil\n}\n\n// Full scanning results.\n// NEXT ID: 9\ntype ScanResults struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Status of this scan.\n\tScanStatus ScanStatus `protobuf:\"varint,1,opt,name=scan_status,json=scanStatus,proto3,enum=tsunami.proto.ScanStatus\" json:\"scan_status,omitempty\"`\n\t// Detailed message for the scan status.\n\tStatusMessage string `protobuf:\"bytes,6,opt,name=status_message,json=statusMessage,proto3\" json:\"status_message,omitempty\"`\n\t// Reports whether the target was alive during the scan.\n\t// A target is considered alive if at least one network service was identified\n\t// or at least one vulnerability was detected.\n\tTargetAlive bool `protobuf:\"varint,8,opt,name=target_alive,json=targetAlive,proto3\" json:\"target_alive,omitempty\"`\n\t// All findings from this scan.\n\tScanFindings []*ScanFinding `protobuf:\"bytes,2,rep,name=scan_findings,json=scanFindings,proto3\" json:\"scan_findings,omitempty\"`\n\t// Time when this scan was started.\n\tScanStartTimestamp *timestamppb.Timestamp `protobuf:\"bytes,3,opt,name=scan_start_timestamp,json=scanStartTimestamp,proto3\" json:\"scan_start_timestamp,omitempty\"`\n\t// Duration of the full scan.\n\tScanDuration *durationpb.Duration `protobuf:\"bytes,4,opt,name=scan_duration,json=scanDuration,proto3\" json:\"scan_duration,omitempty\"`\n\t// Detection reports from all triggered Tsunami detection plugins.\n\tFullDetectionReports *FullDetectionReports `protobuf:\"bytes,5,opt,name=full_detection_reports,json=fullDetectionReports,proto3\" json:\"full_detection_reports,omitempty\"`\n\t// Reconnaissance reports from the fingerprinting stage.\n\tReconnaissanceReport *ReconnaissanceReport `protobuf:\"bytes,7,opt,name=reconnaissance_report,json=reconnaissanceReport,proto3\" json:\"reconnaissance_report,omitempty\"`\n\tunknownFields        protoimpl.UnknownFields\n\tsizeCache            protoimpl.SizeCache\n}\n\nfunc (x *ScanResults) Reset() {\n\t*x = ScanResults{}\n\tmi := &file_scan_results_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ScanResults) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ScanResults) ProtoMessage() {}\n\nfunc (x *ScanResults) ProtoReflect() protoreflect.Message {\n\tmi := &file_scan_results_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ScanResults.ProtoReflect.Descriptor instead.\nfunc (*ScanResults) Descriptor() ([]byte, []int) {\n\treturn file_scan_results_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *ScanResults) GetScanStatus() ScanStatus {\n\tif x != nil {\n\t\treturn x.ScanStatus\n\t}\n\treturn ScanStatus_SCAN_STATUS_UNSPECIFIED\n}\n\nfunc (x *ScanResults) GetStatusMessage() string {\n\tif x != nil {\n\t\treturn x.StatusMessage\n\t}\n\treturn \"\"\n}\n\nfunc (x *ScanResults) GetTargetAlive() bool {\n\tif x != nil {\n\t\treturn x.TargetAlive\n\t}\n\treturn false\n}\n\nfunc (x *ScanResults) GetScanFindings() []*ScanFinding {\n\tif x != nil {\n\t\treturn x.ScanFindings\n\t}\n\treturn nil\n}\n\nfunc (x *ScanResults) GetScanStartTimestamp() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.ScanStartTimestamp\n\t}\n\treturn nil\n}\n\nfunc (x *ScanResults) GetScanDuration() *durationpb.Duration {\n\tif x != nil {\n\t\treturn x.ScanDuration\n\t}\n\treturn nil\n}\n\nfunc (x *ScanResults) GetFullDetectionReports() *FullDetectionReports {\n\tif x != nil {\n\t\treturn x.FullDetectionReports\n\t}\n\treturn nil\n}\n\nfunc (x *ScanResults) GetReconnaissanceReport() *ReconnaissanceReport {\n\tif x != nil {\n\t\treturn x.ReconnaissanceReport\n\t}\n\treturn nil\n}\n\n// Full detection reports from all triggered Tsunami detection plugins.\ntype FullDetectionReports struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tDetectionReports []*DetectionReport     `protobuf:\"bytes,1,rep,name=detection_reports,json=detectionReports,proto3\" json:\"detection_reports,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *FullDetectionReports) Reset() {\n\t*x = FullDetectionReports{}\n\tmi := &file_scan_results_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *FullDetectionReports) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*FullDetectionReports) ProtoMessage() {}\n\nfunc (x *FullDetectionReports) ProtoReflect() protoreflect.Message {\n\tmi := &file_scan_results_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use FullDetectionReports.ProtoReflect.Descriptor instead.\nfunc (*FullDetectionReports) Descriptor() ([]byte, []int) {\n\treturn file_scan_results_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *FullDetectionReports) GetDetectionReports() []*DetectionReport {\n\tif x != nil {\n\t\treturn x.DetectionReports\n\t}\n\treturn nil\n}\n\nvar File_scan_results_proto protoreflect.FileDescriptor\n\nvar file_scan_results_proto_rawDesc = string([]byte{\n\t0x0a, 0x12, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73,\n\t0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x72, 0x65,\n\t0x63, 0x6f, 0x6e, 0x6e, 0x61, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x1a, 0x13, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74,\n\t0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd5, 0x01, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e,\n\t0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3a, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65,\n\t0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x72,\n\t0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49,\n\t0x6e, 0x66, 0x6f, 0x12, 0x46, 0x0a, 0x0f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73,\n\t0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x74,\n\t0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x0e, 0x6e, 0x65, 0x74,\n\t0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x42, 0x0a, 0x0d, 0x76,\n\t0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79,\n\t0x52, 0x0d, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x22,\n\t0x97, 0x04, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12,\n\t0x3a, 0x0a, 0x0b, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,\n\t0x0a, 0x73, 0x63, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73,\n\t0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4d, 0x65, 0x73, 0x73, 0x61,\n\t0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69,\n\t0x76, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,\n\t0x41, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x3f, 0x0a, 0x0d, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x66, 0x69,\n\t0x6e, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x63, 0x61,\n\t0x6e, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x0c, 0x73, 0x63, 0x61, 0x6e, 0x46, 0x69,\n\t0x6e, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4c, 0x0a, 0x14, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x73,\n\t0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,\n\t0x52, 0x12, 0x73, 0x63, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73,\n\t0x74, 0x61, 0x6d, 0x70, 0x12, 0x3e, 0x0a, 0x0d, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x64, 0x75, 0x72,\n\t0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f,\n\t0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75,\n\t0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x63, 0x61, 0x6e, 0x44, 0x75, 0x72, 0x61,\n\t0x74, 0x69, 0x6f, 0x6e, 0x12, 0x59, 0x0a, 0x16, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x64, 0x65, 0x74,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x05,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x14, 0x66, 0x75, 0x6c, 0x6c, 0x44,\n\t0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12,\n\t0x58, 0x0a, 0x15, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x61, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x63,\n\t0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23,\n\t0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52,\n\t0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x61, 0x69, 0x73, 0x73, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x70,\n\t0x6f, 0x72, 0x74, 0x52, 0x14, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x61, 0x69, 0x73, 0x73, 0x61,\n\t0x6e, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x63, 0x0a, 0x14, 0x46, 0x75, 0x6c,\n\t0x6c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,\n\t0x73, 0x12, 0x4b, 0x0a, 0x11, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72,\n\t0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x65, 0x74,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x10, 0x64, 0x65,\n\t0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2a, 0x6c,\n\t0x0a, 0x0a, 0x53, 0x63, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x17,\n\t0x53, 0x43, 0x41, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50,\n\t0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43,\n\t0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x41, 0x52, 0x54,\n\t0x49, 0x41, 0x4c, 0x4c, 0x59, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10,\n\t0x04, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a,\n\t0x09, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x42, 0x72, 0x0a, 0x18,\n\t0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61,\n\t0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x11, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65,\n\t0x73, 0x75, 0x6c, 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50, 0x01, 0x5a, 0x41, 0x67,\n\t0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,\n\t0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,\n\t0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_scan_results_proto_rawDescOnce sync.Once\n\tfile_scan_results_proto_rawDescData []byte\n)\n\nfunc file_scan_results_proto_rawDescGZIP() []byte {\n\tfile_scan_results_proto_rawDescOnce.Do(func() {\n\t\tfile_scan_results_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_scan_results_proto_rawDesc), len(file_scan_results_proto_rawDesc)))\n\t})\n\treturn file_scan_results_proto_rawDescData\n}\n\nvar file_scan_results_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_scan_results_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_scan_results_proto_goTypes = []any{\n\t(ScanStatus)(0),               // 0: tsunami.proto.ScanStatus\n\t(*ScanFinding)(nil),           // 1: tsunami.proto.ScanFinding\n\t(*ScanResults)(nil),           // 2: tsunami.proto.ScanResults\n\t(*FullDetectionReports)(nil),  // 3: tsunami.proto.FullDetectionReports\n\t(*TargetInfo)(nil),            // 4: tsunami.proto.TargetInfo\n\t(*NetworkService)(nil),        // 5: tsunami.proto.NetworkService\n\t(*Vulnerability)(nil),         // 6: tsunami.proto.Vulnerability\n\t(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp\n\t(*durationpb.Duration)(nil),   // 8: google.protobuf.Duration\n\t(*ReconnaissanceReport)(nil),  // 9: tsunami.proto.ReconnaissanceReport\n\t(*DetectionReport)(nil),       // 10: tsunami.proto.DetectionReport\n}\nvar file_scan_results_proto_depIdxs = []int32{\n\t4,  // 0: tsunami.proto.ScanFinding.target_info:type_name -> tsunami.proto.TargetInfo\n\t5,  // 1: tsunami.proto.ScanFinding.network_service:type_name -> tsunami.proto.NetworkService\n\t6,  // 2: tsunami.proto.ScanFinding.vulnerability:type_name -> tsunami.proto.Vulnerability\n\t0,  // 3: tsunami.proto.ScanResults.scan_status:type_name -> tsunami.proto.ScanStatus\n\t1,  // 4: tsunami.proto.ScanResults.scan_findings:type_name -> tsunami.proto.ScanFinding\n\t7,  // 5: tsunami.proto.ScanResults.scan_start_timestamp:type_name -> google.protobuf.Timestamp\n\t8,  // 6: tsunami.proto.ScanResults.scan_duration:type_name -> google.protobuf.Duration\n\t3,  // 7: tsunami.proto.ScanResults.full_detection_reports:type_name -> tsunami.proto.FullDetectionReports\n\t9,  // 8: tsunami.proto.ScanResults.reconnaissance_report:type_name -> tsunami.proto.ReconnaissanceReport\n\t10, // 9: tsunami.proto.FullDetectionReports.detection_reports:type_name -> tsunami.proto.DetectionReport\n\t10, // [10:10] is the sub-list for method output_type\n\t10, // [10:10] is the sub-list for method input_type\n\t10, // [10:10] is the sub-list for extension type_name\n\t10, // [10:10] is the sub-list for extension extendee\n\t0,  // [0:10] is the sub-list for field type_name\n}\n\nfunc init() { file_scan_results_proto_init() }\nfunc file_scan_results_proto_init() {\n\tif File_scan_results_proto != nil {\n\t\treturn\n\t}\n\tfile_detection_proto_init()\n\tfile_network_service_proto_init()\n\tfile_reconnaissance_proto_init()\n\tfile_vulnerability_proto_init()\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_scan_results_proto_rawDesc), len(file_scan_results_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_scan_results_proto_goTypes,\n\t\tDependencyIndexes: file_scan_results_proto_depIdxs,\n\t\tEnumInfos:         file_scan_results_proto_enumTypes,\n\t\tMessageInfos:      file_scan_results_proto_msgTypes,\n\t}.Build()\n\tFile_scan_results_proto = out.File\n\tfile_scan_results_proto_goTypes = nil\n\tfile_scan_results_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/scan_target.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing a scanning target.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: scan_target.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// The information about a scan target.\ntype ScanTarget struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Types that are valid to be assigned to Target:\n\t//\n\t//\t*ScanTarget_NetworkEndpoint\n\t//\t*ScanTarget_NetworkService\n\tTarget        isScanTarget_Target `protobuf_oneof:\"target\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ScanTarget) Reset() {\n\t*x = ScanTarget{}\n\tmi := &file_scan_target_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ScanTarget) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ScanTarget) ProtoMessage() {}\n\nfunc (x *ScanTarget) ProtoReflect() protoreflect.Message {\n\tmi := &file_scan_target_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ScanTarget.ProtoReflect.Descriptor instead.\nfunc (*ScanTarget) Descriptor() ([]byte, []int) {\n\treturn file_scan_target_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ScanTarget) GetTarget() isScanTarget_Target {\n\tif x != nil {\n\t\treturn x.Target\n\t}\n\treturn nil\n}\n\nfunc (x *ScanTarget) GetNetworkEndpoint() *NetworkEndpoint {\n\tif x != nil {\n\t\tif x, ok := x.Target.(*ScanTarget_NetworkEndpoint); ok {\n\t\t\treturn x.NetworkEndpoint\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *ScanTarget) GetNetworkService() *NetworkService {\n\tif x != nil {\n\t\tif x, ok := x.Target.(*ScanTarget_NetworkService); ok {\n\t\t\treturn x.NetworkService\n\t\t}\n\t}\n\treturn nil\n}\n\ntype isScanTarget_Target interface {\n\tisScanTarget_Target()\n}\n\ntype ScanTarget_NetworkEndpoint struct {\n\t// The network endpoint to be scanned.\n\tNetworkEndpoint *NetworkEndpoint `protobuf:\"bytes,1,opt,name=network_endpoint,json=networkEndpoint,proto3,oneof\"`\n}\n\ntype ScanTarget_NetworkService struct {\n\t// The network service to be scanned.\n\tNetworkService *NetworkService `protobuf:\"bytes,2,opt,name=network_service,json=networkService,proto3,oneof\"`\n}\n\nfunc (*ScanTarget_NetworkEndpoint) isScanTarget_Target() {}\n\nfunc (*ScanTarget_NetworkService) isScanTarget_Target() {}\n\nvar File_scan_target_proto protoreflect.FileDescriptor\n\nvar file_scan_target_proto_rawDesc = string([]byte{\n\t0x0a, 0x11, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x1a, 0x0d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x1a, 0x15, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xad, 0x01, 0x0a, 0x0a, 0x53, 0x63, 0x61,\n\t0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x4b, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x77, 0x6f,\n\t0x72, 0x6b, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e,\n\t0x74, 0x48, 0x00, 0x52, 0x0f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x6e, 0x64, 0x70,\n\t0x6f, 0x69, 0x6e, 0x74, 0x12, 0x48, 0x0a, 0x0f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f,\n\t0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e,\n\t0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65,\n\t0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0e,\n\t0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x08,\n\t0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x42, 0x71, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e,\n\t0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x42, 0x10, 0x53, 0x63, 0x61, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74,\n\t0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,\n\t0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e,\n\t0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61,\n\t0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61,\n\t0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_scan_target_proto_rawDescOnce sync.Once\n\tfile_scan_target_proto_rawDescData []byte\n)\n\nfunc file_scan_target_proto_rawDescGZIP() []byte {\n\tfile_scan_target_proto_rawDescOnce.Do(func() {\n\t\tfile_scan_target_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_scan_target_proto_rawDesc), len(file_scan_target_proto_rawDesc)))\n\t})\n\treturn file_scan_target_proto_rawDescData\n}\n\nvar file_scan_target_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_scan_target_proto_goTypes = []any{\n\t(*ScanTarget)(nil),      // 0: tsunami.proto.ScanTarget\n\t(*NetworkEndpoint)(nil), // 1: tsunami.proto.NetworkEndpoint\n\t(*NetworkService)(nil),  // 2: tsunami.proto.NetworkService\n}\nvar file_scan_target_proto_depIdxs = []int32{\n\t1, // 0: tsunami.proto.ScanTarget.network_endpoint:type_name -> tsunami.proto.NetworkEndpoint\n\t2, // 1: tsunami.proto.ScanTarget.network_service:type_name -> tsunami.proto.NetworkService\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_scan_target_proto_init() }\nfunc file_scan_target_proto_init() {\n\tif File_scan_target_proto != nil {\n\t\treturn\n\t}\n\tfile_network_proto_init()\n\tfile_network_service_proto_init()\n\tfile_scan_target_proto_msgTypes[0].OneofWrappers = []any{\n\t\t(*ScanTarget_NetworkEndpoint)(nil),\n\t\t(*ScanTarget_NetworkService)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_scan_target_proto_rawDesc), len(file_scan_target_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_scan_target_proto_goTypes,\n\t\tDependencyIndexes: file_scan_target_proto_depIdxs,\n\t\tMessageInfos:      file_scan_target_proto_msgTypes,\n\t}.Build()\n\tFile_scan_target_proto = out.File\n\tfile_scan_target_proto_goTypes = nil\n\tfile_scan_target_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/software.pb.go",
    "content": "//\n// Copyright 2019 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing a software.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: software.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Type of the Version message, identifying an ordinary software version or a\n// sentinel MINIMUM/MAXIMUM version. See comments below for what is a sentinel\n// version.\ntype Version_VersionType int32\n\nconst (\n\tVersion_VERSION_TYPE_UNSPECIFIED Version_VersionType = 0\n\t// A normal software version.\n\tVersion_NORMAL Version_VersionType = 1\n\t// A sentinel version representing negative infinity, i.e. MINIMUM version\n\t// is less than any NORMAL and MAXIMUM versions.\n\tVersion_MINIMUM Version_VersionType = 2\n\t// A sentinel version representing positive infinity, i.e. MAXIMUM version\n\t// is greater than any NORMAL and MINIMUM versions.\n\tVersion_MAXIMUM Version_VersionType = 3\n)\n\n// Enum value maps for Version_VersionType.\nvar (\n\tVersion_VersionType_name = map[int32]string{\n\t\t0: \"VERSION_TYPE_UNSPECIFIED\",\n\t\t1: \"NORMAL\",\n\t\t2: \"MINIMUM\",\n\t\t3: \"MAXIMUM\",\n\t}\n\tVersion_VersionType_value = map[string]int32{\n\t\t\"VERSION_TYPE_UNSPECIFIED\": 0,\n\t\t\"NORMAL\":                   1,\n\t\t\"MINIMUM\":                  2,\n\t\t\"MAXIMUM\":                  3,\n\t}\n)\n\nfunc (x Version_VersionType) Enum() *Version_VersionType {\n\tp := new(Version_VersionType)\n\t*p = x\n\treturn p\n}\n\nfunc (x Version_VersionType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Version_VersionType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_software_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Version_VersionType) Type() protoreflect.EnumType {\n\treturn &file_software_proto_enumTypes[0]\n}\n\nfunc (x Version_VersionType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Version_VersionType.Descriptor instead.\nfunc (Version_VersionType) EnumDescriptor() ([]byte, []int) {\n\treturn file_software_proto_rawDescGZIP(), []int{0, 0}\n}\n\n// Whether the range endpoint is inclusive or exclusive.\ntype VersionRange_Inclusiveness int32\n\nconst (\n\tVersionRange_INCLUSIVENESS_UNSPECIFIED VersionRange_Inclusiveness = 0\n\tVersionRange_INCLUSIVE                 VersionRange_Inclusiveness = 1\n\tVersionRange_EXCLUSIVE                 VersionRange_Inclusiveness = 2\n)\n\n// Enum value maps for VersionRange_Inclusiveness.\nvar (\n\tVersionRange_Inclusiveness_name = map[int32]string{\n\t\t0: \"INCLUSIVENESS_UNSPECIFIED\",\n\t\t1: \"INCLUSIVE\",\n\t\t2: \"EXCLUSIVE\",\n\t}\n\tVersionRange_Inclusiveness_value = map[string]int32{\n\t\t\"INCLUSIVENESS_UNSPECIFIED\": 0,\n\t\t\"INCLUSIVE\":                 1,\n\t\t\"EXCLUSIVE\":                 2,\n\t}\n)\n\nfunc (x VersionRange_Inclusiveness) Enum() *VersionRange_Inclusiveness {\n\tp := new(VersionRange_Inclusiveness)\n\t*p = x\n\treturn p\n}\n\nfunc (x VersionRange_Inclusiveness) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (VersionRange_Inclusiveness) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_software_proto_enumTypes[1].Descriptor()\n}\n\nfunc (VersionRange_Inclusiveness) Type() protoreflect.EnumType {\n\treturn &file_software_proto_enumTypes[1]\n}\n\nfunc (x VersionRange_Inclusiveness) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use VersionRange_Inclusiveness.Descriptor instead.\nfunc (VersionRange_Inclusiveness) EnumDescriptor() ([]byte, []int) {\n\treturn file_software_proto_rawDescGZIP(), []int{1, 0}\n}\n\n// The exact version of a software.\ntype Version struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Distinguishes between sentinel MIN/MAX versions and normal versions.\n\tType Version_VersionType `protobuf:\"varint,1,opt,name=type,proto3,enum=tsunami.proto.Version_VersionType\" json:\"type,omitempty\"`\n\t// Human readable version number, e.g. 1.0.3. This is set only when type is\n\t// NORMAL. Tsunami uses raw string to represent a version number instead of\n\t// any structured messages in order to handle different kinds of version\n\t// schemes. Tsunami will tokenize this version string and store tokens\n\t// internally. When performing version comparisons, Tsunami follows the\n\t// precedence defined by Semantic Versioning (semver.org). More details can be\n\t// found in Tsunami's internal Version class.\n\tFullVersionString string `protobuf:\"bytes,2,opt,name=full_version_string,json=fullVersionString,proto3\" json:\"full_version_string,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *Version) Reset() {\n\t*x = Version{}\n\tmi := &file_software_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Version) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Version) ProtoMessage() {}\n\nfunc (x *Version) ProtoReflect() protoreflect.Message {\n\tmi := &file_software_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Version.ProtoReflect.Descriptor instead.\nfunc (*Version) Descriptor() ([]byte, []int) {\n\treturn file_software_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Version) GetType() Version_VersionType {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn Version_VERSION_TYPE_UNSPECIFIED\n}\n\nfunc (x *Version) GetFullVersionString() string {\n\tif x != nil {\n\t\treturn x.FullVersionString\n\t}\n\treturn \"\"\n}\n\n// An inclusive range of versions for a software.\ntype VersionRange struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Minimum version that belongs in the range.\n\tMinVersion *Version `protobuf:\"bytes,1,opt,name=min_version,json=minVersion,proto3\" json:\"min_version,omitempty\"`\n\t// Inclusiveness of the min_version. When min_version points to negative\n\t// infinity, this value will always be EXCLUSIVE to matching the\n\t// representation of (-inf, 1.0]. Note that negative infinity version should\n\t// ***NOT*** be compared with a version range as it is just a bogus sentinel\n\t// version without any meaning.\n\tMinVersionInclusiveness VersionRange_Inclusiveness `protobuf:\"varint,2,opt,name=min_version_inclusiveness,json=minVersionInclusiveness,proto3,enum=tsunami.proto.VersionRange_Inclusiveness\" json:\"min_version_inclusiveness,omitempty\"`\n\t// Maximum version that belongs in the range.\n\tMaxVersion *Version `protobuf:\"bytes,3,opt,name=max_version,json=maxVersion,proto3\" json:\"max_version,omitempty\"`\n\t// Inclusiveness of the max_version. When max_version points to positive\n\t// infinity, this value will always be EXCLUSIVE to matching the\n\t// representation of [1.0, inf). Note that positive infinity version should\n\t// ***NOT*** be compared with a version range as it is just a bogus sentinel\n\t// version without any meaning.\n\tMaxVersionInclusiveness VersionRange_Inclusiveness `protobuf:\"varint,4,opt,name=max_version_inclusiveness,json=maxVersionInclusiveness,proto3,enum=tsunami.proto.VersionRange_Inclusiveness\" json:\"max_version_inclusiveness,omitempty\"`\n\tunknownFields           protoimpl.UnknownFields\n\tsizeCache               protoimpl.SizeCache\n}\n\nfunc (x *VersionRange) Reset() {\n\t*x = VersionRange{}\n\tmi := &file_software_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VersionRange) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VersionRange) ProtoMessage() {}\n\nfunc (x *VersionRange) ProtoReflect() protoreflect.Message {\n\tmi := &file_software_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VersionRange.ProtoReflect.Descriptor instead.\nfunc (*VersionRange) Descriptor() ([]byte, []int) {\n\treturn file_software_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *VersionRange) GetMinVersion() *Version {\n\tif x != nil {\n\t\treturn x.MinVersion\n\t}\n\treturn nil\n}\n\nfunc (x *VersionRange) GetMinVersionInclusiveness() VersionRange_Inclusiveness {\n\tif x != nil {\n\t\treturn x.MinVersionInclusiveness\n\t}\n\treturn VersionRange_INCLUSIVENESS_UNSPECIFIED\n}\n\nfunc (x *VersionRange) GetMaxVersion() *Version {\n\tif x != nil {\n\t\treturn x.MaxVersion\n\t}\n\treturn nil\n}\n\nfunc (x *VersionRange) GetMaxVersionInclusiveness() VersionRange_Inclusiveness {\n\tif x != nil {\n\t\treturn x.MaxVersionInclusiveness\n\t}\n\treturn VersionRange_INCLUSIVENESS_UNSPECIFIED\n}\n\n// A set of Versions and VersionRanges that completely describes a set of\n// software releases, e.g. {3.9.1, 3.9.3, [4.7.1, 4.7.8], 4.8}\ntype VersionSet struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tVersions      []*Version             `protobuf:\"bytes,1,rep,name=versions,proto3\" json:\"versions,omitempty\"`\n\tVersionRanges []*VersionRange        `protobuf:\"bytes,2,rep,name=version_ranges,json=versionRanges,proto3\" json:\"version_ranges,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VersionSet) Reset() {\n\t*x = VersionSet{}\n\tmi := &file_software_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VersionSet) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VersionSet) ProtoMessage() {}\n\nfunc (x *VersionSet) ProtoReflect() protoreflect.Message {\n\tmi := &file_software_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VersionSet.ProtoReflect.Descriptor instead.\nfunc (*VersionSet) Descriptor() ([]byte, []int) {\n\treturn file_software_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *VersionSet) GetVersions() []*Version {\n\tif x != nil {\n\t\treturn x.Versions\n\t}\n\treturn nil\n}\n\nfunc (x *VersionSet) GetVersionRanges() []*VersionRange {\n\tif x != nil {\n\t\treturn x.VersionRanges\n\t}\n\treturn nil\n}\n\n// A structured description about a software.\ntype Software struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The name of this software.\n\tName          string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Software) Reset() {\n\t*x = Software{}\n\tmi := &file_software_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Software) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Software) ProtoMessage() {}\n\nfunc (x *Software) ProtoReflect() protoreflect.Message {\n\tmi := &file_software_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Software.ProtoReflect.Descriptor instead.\nfunc (*Software) Descriptor() ([]byte, []int) {\n\treturn file_software_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *Software) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nvar File_software_proto protoreflect.FileDescriptor\n\nvar file_software_proto_rawDesc = string([]byte{\n\t0x0a, 0x0e, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x12, 0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,\n\t0xc4, 0x01, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x04, 0x74,\n\t0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x73, 0x75, 0x6e,\n\t0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,\n\t0x6e, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,\n\t0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73,\n\t0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x11, 0x66, 0x75, 0x6c, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72,\n\t0x69, 0x6e, 0x67, 0x22, 0x51, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79,\n\t0x70, 0x65, 0x12, 0x1c, 0x0a, 0x18, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59,\n\t0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,\n\t0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07,\n\t0x4d, 0x49, 0x4e, 0x49, 0x4d, 0x55, 0x4d, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x41, 0x58,\n\t0x49, 0x4d, 0x55, 0x4d, 0x10, 0x03, 0x22, 0x9c, 0x03, 0x0a, 0x0c, 0x56, 0x65, 0x72, 0x73, 0x69,\n\t0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x5f, 0x76,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72,\n\t0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,\n\t0x12, 0x65, 0x0a, 0x19, 0x6d, 0x69, 0x6e, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x6e, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65,\n\t0x2e, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x6e, 0x65, 0x73, 0x73, 0x52, 0x17,\n\t0x6d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73,\n\t0x69, 0x76, 0x65, 0x6e, 0x65, 0x73, 0x73, 0x12, 0x37, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x76,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72,\n\t0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,\n\t0x12, 0x65, 0x0a, 0x19, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x6e, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65,\n\t0x2e, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x6e, 0x65, 0x73, 0x73, 0x52, 0x17,\n\t0x6d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73,\n\t0x69, 0x76, 0x65, 0x6e, 0x65, 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x0d, 0x49, 0x6e, 0x63, 0x6c, 0x75,\n\t0x73, 0x69, 0x76, 0x65, 0x6e, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x19, 0x49, 0x4e, 0x43, 0x4c,\n\t0x55, 0x53, 0x49, 0x56, 0x45, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43,\n\t0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x43, 0x4c, 0x55,\n\t0x53, 0x49, 0x56, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x58, 0x43, 0x4c, 0x55, 0x53,\n\t0x49, 0x56, 0x45, 0x10, 0x02, 0x22, 0x84, 0x01, 0x0a, 0x0a, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,\n\t0x6e, 0x53, 0x65, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73,\n\t0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08,\n\t0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x76, 0x65, 0x72, 0x73,\n\t0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,\n\t0x32, 0x1b, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x76,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x22, 0x1e, 0x0a, 0x08,\n\t0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x6f, 0x0a, 0x18,\n\t0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61,\n\t0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x0e, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61,\n\t0x72, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68,\n\t0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73,\n\t0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73,\n\t0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x73, 0x75,\n\t0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_software_proto_rawDescOnce sync.Once\n\tfile_software_proto_rawDescData []byte\n)\n\nfunc file_software_proto_rawDescGZIP() []byte {\n\tfile_software_proto_rawDescOnce.Do(func() {\n\t\tfile_software_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_software_proto_rawDesc), len(file_software_proto_rawDesc)))\n\t})\n\treturn file_software_proto_rawDescData\n}\n\nvar file_software_proto_enumTypes = make([]protoimpl.EnumInfo, 2)\nvar file_software_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_software_proto_goTypes = []any{\n\t(Version_VersionType)(0),        // 0: tsunami.proto.Version.VersionType\n\t(VersionRange_Inclusiveness)(0), // 1: tsunami.proto.VersionRange.Inclusiveness\n\t(*Version)(nil),                 // 2: tsunami.proto.Version\n\t(*VersionRange)(nil),            // 3: tsunami.proto.VersionRange\n\t(*VersionSet)(nil),              // 4: tsunami.proto.VersionSet\n\t(*Software)(nil),                // 5: tsunami.proto.Software\n}\nvar file_software_proto_depIdxs = []int32{\n\t0, // 0: tsunami.proto.Version.type:type_name -> tsunami.proto.Version.VersionType\n\t2, // 1: tsunami.proto.VersionRange.min_version:type_name -> tsunami.proto.Version\n\t1, // 2: tsunami.proto.VersionRange.min_version_inclusiveness:type_name -> tsunami.proto.VersionRange.Inclusiveness\n\t2, // 3: tsunami.proto.VersionRange.max_version:type_name -> tsunami.proto.Version\n\t1, // 4: tsunami.proto.VersionRange.max_version_inclusiveness:type_name -> tsunami.proto.VersionRange.Inclusiveness\n\t2, // 5: tsunami.proto.VersionSet.versions:type_name -> tsunami.proto.Version\n\t3, // 6: tsunami.proto.VersionSet.version_ranges:type_name -> tsunami.proto.VersionRange\n\t7, // [7:7] is the sub-list for method output_type\n\t7, // [7:7] is the sub-list for method input_type\n\t7, // [7:7] is the sub-list for extension type_name\n\t7, // [7:7] is the sub-list for extension extendee\n\t0, // [0:7] is the sub-list for field type_name\n}\n\nfunc init() { file_software_proto_init() }\nfunc file_software_proto_init() {\n\tif File_software_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_software_proto_rawDesc), len(file_software_proto_rawDesc)),\n\t\t\tNumEnums:      2,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_software_proto_goTypes,\n\t\tDependencyIndexes: file_software_proto_depIdxs,\n\t\tEnumInfos:         file_software_proto_enumTypes,\n\t\tMessageInfos:      file_software_proto_msgTypes,\n\t}.Build()\n\tFile_software_proto = out.File\n\tfile_software_proto_goTypes = nil\n\tfile_software_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/vulnerability.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for describing a vulnerability.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: vulnerability.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Severity of a vulnerability.\ntype Severity int32\n\nconst (\n\t// Unspecified severity.\n\tSeverity_SEVERITY_UNSPECIFIED Severity = 0\n\t// Minimal severity.\n\tSeverity_MINIMAL Severity = 1\n\t// Low severity.\n\tSeverity_LOW Severity = 2\n\t// Medium severity.\n\tSeverity_MEDIUM Severity = 3\n\t// High severity.\n\tSeverity_HIGH Severity = 4\n\t// Critical severity.\n\tSeverity_CRITICAL Severity = 5\n)\n\n// Enum value maps for Severity.\nvar (\n\tSeverity_name = map[int32]string{\n\t\t0: \"SEVERITY_UNSPECIFIED\",\n\t\t1: \"MINIMAL\",\n\t\t2: \"LOW\",\n\t\t3: \"MEDIUM\",\n\t\t4: \"HIGH\",\n\t\t5: \"CRITICAL\",\n\t}\n\tSeverity_value = map[string]int32{\n\t\t\"SEVERITY_UNSPECIFIED\": 0,\n\t\t\"MINIMAL\":              1,\n\t\t\"LOW\":                  2,\n\t\t\"MEDIUM\":               3,\n\t\t\"HIGH\":                 4,\n\t\t\"CRITICAL\":             5,\n\t}\n)\n\nfunc (x Severity) Enum() *Severity {\n\tp := new(Severity)\n\t*p = x\n\treturn p\n}\n\nfunc (x Severity) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Severity) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_vulnerability_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Severity) Type() protoreflect.EnumType {\n\treturn &file_vulnerability_proto_enumTypes[0]\n}\n\nfunc (x Severity) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Severity.Descriptor instead.\nfunc (Severity) EnumDescriptor() ([]byte, []int) {\n\treturn file_vulnerability_proto_rawDescGZIP(), []int{0}\n}\n\n// The identifier that uniquely identifies this vulnerability.\ntype VulnerabilityId struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Entity that published this identifier.\n\tPublisher string `protobuf:\"bytes,1,opt,name=publisher,proto3\" json:\"publisher,omitempty\"`\n\t// Publisher assigned unique identifier.\n\tValue string `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\t// Optional. URL for details about this vulnerability.\n\tLink          string `protobuf:\"bytes,3,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VulnerabilityId) Reset() {\n\t*x = VulnerabilityId{}\n\tmi := &file_vulnerability_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VulnerabilityId) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VulnerabilityId) ProtoMessage() {}\n\nfunc (x *VulnerabilityId) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VulnerabilityId.ProtoReflect.Descriptor instead.\nfunc (*VulnerabilityId) Descriptor() ([]byte, []int) {\n\treturn file_vulnerability_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *VulnerabilityId) GetPublisher() string {\n\tif x != nil {\n\t\treturn x.Publisher\n\t}\n\treturn \"\"\n}\n\nfunc (x *VulnerabilityId) GetValue() string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn \"\"\n}\n\nfunc (x *VulnerabilityId) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\n// Message that represents one single vulnerability detected by Tsunami.\ntype Vulnerability struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The main identifier for this vulnerability, usually a publicly known\n\t// identifier like CVEs and such. If not publicly known, users are expected to\n\t// assign an id on their own.\n\tMainId *VulnerabilityId `protobuf:\"bytes,1,opt,name=main_id,json=mainId,proto3\" json:\"main_id,omitempty\"`\n\t// Any related identifiers about this vulnerability, e.g. a CWE weakness.\n\tRelatedId []*VulnerabilityId `protobuf:\"bytes,2,rep,name=related_id,json=relatedId,proto3\" json:\"related_id,omitempty\"`\n\t// Severity of this vulnerability.\n\tSeverity Severity `protobuf:\"varint,3,opt,name=severity,proto3,enum=tsunami.proto.Severity\" json:\"severity,omitempty\"`\n\t// Terse but descriptive sentence about this vulnerability.\n\t// For example: \"Default Password (0p3nm35h) for 'root' Account.\".\n\tTitle string `protobuf:\"bytes,4,opt,name=title,proto3\" json:\"title,omitempty\"`\n\t// Verbose description of this vulnerability.\n\tDescription string `protobuf:\"bytes,5,opt,name=description,proto3\" json:\"description,omitempty\"`\n\t// Optional. Verbose recommended solution(s).\n\tRecommendation string `protobuf:\"bytes,6,opt,name=recommendation,proto3\" json:\"recommendation,omitempty\"`\n\t// Optional. The CVSS v2 score of this vulnerability.\n\tCvssV2 string `protobuf:\"bytes,7,opt,name=cvss_v2,json=cvssV2,proto3\" json:\"cvss_v2,omitempty\"`\n\t// Optional. The CVSS v3 score of this vulnerability.\n\tCvssV3 string `protobuf:\"bytes,8,opt,name=cvss_v3,json=cvssV3,proto3\" json:\"cvss_v3,omitempty\"`\n\t// Any additional technical details about this vulnerability.\n\tAdditionalDetails []*AdditionalDetail `protobuf:\"bytes,9,rep,name=additional_details,json=additionalDetails,proto3\" json:\"additional_details,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *Vulnerability) Reset() {\n\t*x = Vulnerability{}\n\tmi := &file_vulnerability_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Vulnerability) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Vulnerability) ProtoMessage() {}\n\nfunc (x *Vulnerability) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Vulnerability.ProtoReflect.Descriptor instead.\nfunc (*Vulnerability) Descriptor() ([]byte, []int) {\n\treturn file_vulnerability_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Vulnerability) GetMainId() *VulnerabilityId {\n\tif x != nil {\n\t\treturn x.MainId\n\t}\n\treturn nil\n}\n\nfunc (x *Vulnerability) GetRelatedId() []*VulnerabilityId {\n\tif x != nil {\n\t\treturn x.RelatedId\n\t}\n\treturn nil\n}\n\nfunc (x *Vulnerability) GetSeverity() Severity {\n\tif x != nil {\n\t\treturn x.Severity\n\t}\n\treturn Severity_SEVERITY_UNSPECIFIED\n}\n\nfunc (x *Vulnerability) GetTitle() string {\n\tif x != nil {\n\t\treturn x.Title\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vulnerability) GetDescription() string {\n\tif x != nil {\n\t\treturn x.Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vulnerability) GetRecommendation() string {\n\tif x != nil {\n\t\treturn x.Recommendation\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vulnerability) GetCvssV2() string {\n\tif x != nil {\n\t\treturn x.CvssV2\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vulnerability) GetCvssV3() string {\n\tif x != nil {\n\t\treturn x.CvssV3\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vulnerability) GetAdditionalDetails() []*AdditionalDetail {\n\tif x != nil {\n\t\treturn x.AdditionalDetails\n\t}\n\treturn nil\n}\n\n// Additional details regarding a vulnerability can be stored here. Prefers to\n// use the existing structured data when possible, otherwise store the raw data\n// as a blob.\ntype AdditionalDetail struct {\n\tstate       protoimpl.MessageState `protogen:\"open.v1\"`\n\tDescription string                 `protobuf:\"bytes,1,opt,name=description,proto3\" json:\"description,omitempty\"`\n\t// Types that are valid to be assigned to Detail:\n\t//\n\t//\t*AdditionalDetail_BlobData\n\t//\t*AdditionalDetail_TextData\n\t//\t*AdditionalDetail_Credential\n\t//\t*AdditionalDetail_Credentials\n\tDetail        isAdditionalDetail_Detail `protobuf_oneof:\"detail\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AdditionalDetail) Reset() {\n\t*x = AdditionalDetail{}\n\tmi := &file_vulnerability_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AdditionalDetail) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AdditionalDetail) ProtoMessage() {}\n\nfunc (x *AdditionalDetail) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AdditionalDetail.ProtoReflect.Descriptor instead.\nfunc (*AdditionalDetail) Descriptor() ([]byte, []int) {\n\treturn file_vulnerability_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *AdditionalDetail) GetDescription() string {\n\tif x != nil {\n\t\treturn x.Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *AdditionalDetail) GetDetail() isAdditionalDetail_Detail {\n\tif x != nil {\n\t\treturn x.Detail\n\t}\n\treturn nil\n}\n\nfunc (x *AdditionalDetail) GetBlobData() *BlobData {\n\tif x != nil {\n\t\tif x, ok := x.Detail.(*AdditionalDetail_BlobData); ok {\n\t\t\treturn x.BlobData\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *AdditionalDetail) GetTextData() *TextData {\n\tif x != nil {\n\t\tif x, ok := x.Detail.(*AdditionalDetail_TextData); ok {\n\t\t\treturn x.TextData\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *AdditionalDetail) GetCredential() *Credential {\n\tif x != nil {\n\t\tif x, ok := x.Detail.(*AdditionalDetail_Credential); ok {\n\t\t\treturn x.Credential\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *AdditionalDetail) GetCredentials() *Credentials {\n\tif x != nil {\n\t\tif x, ok := x.Detail.(*AdditionalDetail_Credentials); ok {\n\t\t\treturn x.Credentials\n\t\t}\n\t}\n\treturn nil\n}\n\ntype isAdditionalDetail_Detail interface {\n\tisAdditionalDetail_Detail()\n}\n\ntype AdditionalDetail_BlobData struct {\n\tBlobData *BlobData `protobuf:\"bytes,2,opt,name=blob_data,json=blobData,proto3,oneof\"`\n}\n\ntype AdditionalDetail_TextData struct {\n\tTextData *TextData `protobuf:\"bytes,3,opt,name=text_data,json=textData,proto3,oneof\"`\n}\n\ntype AdditionalDetail_Credential struct {\n\tCredential *Credential `protobuf:\"bytes,4,opt,name=credential,proto3,oneof\"`\n}\n\ntype AdditionalDetail_Credentials struct {\n\tCredentials *Credentials `protobuf:\"bytes,5,opt,name=credentials,proto3,oneof\"`\n}\n\nfunc (*AdditionalDetail_BlobData) isAdditionalDetail_Detail() {}\n\nfunc (*AdditionalDetail_TextData) isAdditionalDetail_Detail() {}\n\nfunc (*AdditionalDetail_Credential) isAdditionalDetail_Detail() {}\n\nfunc (*AdditionalDetail_Credentials) isAdditionalDetail_Detail() {}\n\n// A piece of arbitrary binary data.\ntype BlobData struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tData          []byte                 `protobuf:\"bytes,1,opt,name=data,proto3\" json:\"data,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *BlobData) Reset() {\n\t*x = BlobData{}\n\tmi := &file_vulnerability_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *BlobData) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*BlobData) ProtoMessage() {}\n\nfunc (x *BlobData) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use BlobData.ProtoReflect.Descriptor instead.\nfunc (*BlobData) Descriptor() ([]byte, []int) {\n\treturn file_vulnerability_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *BlobData) GetData() []byte {\n\tif x != nil {\n\t\treturn x.Data\n\t}\n\treturn nil\n}\n\n// A piece of arbitrary UTF-8 encoded text data.\ntype TextData struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tText          string                 `protobuf:\"bytes,1,opt,name=text,proto3\" json:\"text,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TextData) Reset() {\n\t*x = TextData{}\n\tmi := &file_vulnerability_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TextData) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TextData) ProtoMessage() {}\n\nfunc (x *TextData) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TextData.ProtoReflect.Descriptor instead.\nfunc (*TextData) Descriptor() ([]byte, []int) {\n\treturn file_vulnerability_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *TextData) GetText() string {\n\tif x != nil {\n\t\treturn x.Text\n\t}\n\treturn \"\"\n}\n\n// Credential for a vulnerable network service.\ntype Credential struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUsername      string                 `protobuf:\"bytes,1,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tPassword      string                 `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Credential) Reset() {\n\t*x = Credential{}\n\tmi := &file_vulnerability_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Credential) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Credential) ProtoMessage() {}\n\nfunc (x *Credential) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Credential.ProtoReflect.Descriptor instead.\nfunc (*Credential) Descriptor() ([]byte, []int) {\n\treturn file_vulnerability_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *Credential) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *Credential) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\n// A set of credentials for a vulnerable network service.\ntype Credentials struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tCredential    []*Credential          `protobuf:\"bytes,1,rep,name=credential,proto3\" json:\"credential,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Credentials) Reset() {\n\t*x = Credentials{}\n\tmi := &file_vulnerability_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Credentials) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Credentials) ProtoMessage() {}\n\nfunc (x *Credentials) ProtoReflect() protoreflect.Message {\n\tmi := &file_vulnerability_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Credentials.ProtoReflect.Descriptor instead.\nfunc (*Credentials) Descriptor() ([]byte, []int) {\n\treturn file_vulnerability_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *Credentials) GetCredential() []*Credential {\n\tif x != nil {\n\t\treturn x.Credential\n\t}\n\treturn nil\n}\n\nvar File_vulnerability_proto protoreflect.FileDescriptor\n\nvar file_vulnerability_proto_rawDesc = string([]byte{\n\t0x0a, 0x13, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x22, 0x59, 0x0a, 0x0f, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62,\n\t0x69, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69,\n\t0x73, 0x68, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c,\n\t0x69, 0x73, 0x68, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22,\n\t0x9e, 0x03, 0x0a, 0x0d, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74,\n\t0x79, 0x12, 0x37, 0x0a, 0x07, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79,\n\t0x49, 0x64, 0x52, 0x06, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x3d, 0x0a, 0x0a, 0x72, 0x65,\n\t0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e,\n\t0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56,\n\t0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x64, 0x52, 0x09,\n\t0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x08, 0x73, 0x65, 0x76,\n\t0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x74, 0x73,\n\t0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x76, 0x65,\n\t0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14,\n\t0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74,\n\t0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,\n\t0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,\n\t0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d,\n\t0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e,\n\t0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17,\n\t0x0a, 0x07, 0x63, 0x76, 0x73, 0x73, 0x5f, 0x76, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x06, 0x63, 0x76, 0x73, 0x73, 0x56, 0x32, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x76, 0x73, 0x73, 0x5f,\n\t0x76, 0x33, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x76, 0x73, 0x73, 0x56, 0x33,\n\t0x12, 0x4e, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x64,\n\t0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x64, 0x64,\n\t0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x11, 0x61,\n\t0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,\n\t0x22, 0xab, 0x02, 0x0a, 0x10, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44,\n\t0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,\n\t0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63,\n\t0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x62, 0x5f,\n\t0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x73, 0x75,\n\t0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x44,\n\t0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x62, 0x44, 0x61, 0x74, 0x61, 0x12,\n\t0x36, 0x0a, 0x09, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x08, 0x74,\n\t0x65, 0x78, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x3b, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65,\n\t0x6e, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x73,\n\t0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x64,\n\t0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x61, 0x6c, 0x12, 0x3e, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,\n\t0x61, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x73, 0x75, 0x6e,\n\t0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x61, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,\n\t0x69, 0x61, 0x6c, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x22, 0x1e,\n\t0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x62, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61,\n\t0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x1e,\n\t0x0a, 0x08, 0x54, 0x65, 0x78, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65,\n\t0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x44,\n\t0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1a, 0x0a, 0x08,\n\t0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,\n\t0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73,\n\t0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73,\n\t0x77, 0x6f, 0x72, 0x64, 0x22, 0x48, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,\n\t0x61, 0x6c, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,\n\t0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d,\n\t0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,\n\t0x61, 0x6c, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2a, 0x5e,\n\t0x0a, 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x45,\n\t0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,\n\t0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x49, 0x4e, 0x49, 0x4d, 0x41, 0x4c, 0x10,\n\t0x01, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x45,\n\t0x44, 0x49, 0x55, 0x4d, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x49, 0x47, 0x48, 0x10, 0x04,\n\t0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x05, 0x42, 0x74,\n\t0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75,\n\t0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x13, 0x56, 0x75, 0x6c, 0x6e,\n\t0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x50,\n\t0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f,\n\t0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63,\n\t0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x67, 0x6f, 0x5f, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_vulnerability_proto_rawDescOnce sync.Once\n\tfile_vulnerability_proto_rawDescData []byte\n)\n\nfunc file_vulnerability_proto_rawDescGZIP() []byte {\n\tfile_vulnerability_proto_rawDescOnce.Do(func() {\n\t\tfile_vulnerability_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_vulnerability_proto_rawDesc), len(file_vulnerability_proto_rawDesc)))\n\t})\n\treturn file_vulnerability_proto_rawDescData\n}\n\nvar file_vulnerability_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_vulnerability_proto_msgTypes = make([]protoimpl.MessageInfo, 7)\nvar file_vulnerability_proto_goTypes = []any{\n\t(Severity)(0),            // 0: tsunami.proto.Severity\n\t(*VulnerabilityId)(nil),  // 1: tsunami.proto.VulnerabilityId\n\t(*Vulnerability)(nil),    // 2: tsunami.proto.Vulnerability\n\t(*AdditionalDetail)(nil), // 3: tsunami.proto.AdditionalDetail\n\t(*BlobData)(nil),         // 4: tsunami.proto.BlobData\n\t(*TextData)(nil),         // 5: tsunami.proto.TextData\n\t(*Credential)(nil),       // 6: tsunami.proto.Credential\n\t(*Credentials)(nil),      // 7: tsunami.proto.Credentials\n}\nvar file_vulnerability_proto_depIdxs = []int32{\n\t1, // 0: tsunami.proto.Vulnerability.main_id:type_name -> tsunami.proto.VulnerabilityId\n\t1, // 1: tsunami.proto.Vulnerability.related_id:type_name -> tsunami.proto.VulnerabilityId\n\t0, // 2: tsunami.proto.Vulnerability.severity:type_name -> tsunami.proto.Severity\n\t3, // 3: tsunami.proto.Vulnerability.additional_details:type_name -> tsunami.proto.AdditionalDetail\n\t4, // 4: tsunami.proto.AdditionalDetail.blob_data:type_name -> tsunami.proto.BlobData\n\t5, // 5: tsunami.proto.AdditionalDetail.text_data:type_name -> tsunami.proto.TextData\n\t6, // 6: tsunami.proto.AdditionalDetail.credential:type_name -> tsunami.proto.Credential\n\t7, // 7: tsunami.proto.AdditionalDetail.credentials:type_name -> tsunami.proto.Credentials\n\t6, // 8: tsunami.proto.Credentials.credential:type_name -> tsunami.proto.Credential\n\t9, // [9:9] is the sub-list for method output_type\n\t9, // [9:9] is the sub-list for method input_type\n\t9, // [9:9] is the sub-list for extension type_name\n\t9, // [9:9] is the sub-list for extension extendee\n\t0, // [0:9] is the sub-list for field type_name\n}\n\nfunc init() { file_vulnerability_proto_init() }\nfunc file_vulnerability_proto_init() {\n\tif File_vulnerability_proto != nil {\n\t\treturn\n\t}\n\tfile_vulnerability_proto_msgTypes[2].OneofWrappers = []any{\n\t\t(*AdditionalDetail_BlobData)(nil),\n\t\t(*AdditionalDetail_TextData)(nil),\n\t\t(*AdditionalDetail_Credential)(nil),\n\t\t(*AdditionalDetail_Credentials)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_vulnerability_proto_rawDesc), len(file_vulnerability_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   7,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_vulnerability_proto_goTypes,\n\t\tDependencyIndexes: file_vulnerability_proto_depIdxs,\n\t\tEnumInfos:         file_vulnerability_proto_enumTypes,\n\t\tMessageInfos:      file_vulnerability_proto_msgTypes,\n\t}.Build()\n\tFile_vulnerability_proto = out.File\n\tfile_vulnerability_proto_goTypes = nil\n\tfile_vulnerability_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/tsunami_go_proto/web_crawl.pb.go",
    "content": "//\n// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Data models for the web crawler.\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v3.21.12\n// source: web_crawl.proto\n\npackage tsunami_go_proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Next ID: 7\ntype CrawlConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Starting points of a web crawl.\n\t// Required.\n\tSeedingUrls []string `protobuf:\"bytes,1,rep,name=seeding_urls,json=seedingUrls,proto3\" json:\"seeding_urls,omitempty\"`\n\t// The maximum depth of a web crawl.\n\t// Required.\n\tMaxDepth int32 `protobuf:\"varint,2,opt,name=max_depth,json=maxDepth,proto3\" json:\"max_depth,omitempty\"`\n\t// Allowed crawling scopes.\n\t// Optional. When empty, scopes are autogenerated from seeding_urls.\n\tScopes []*CrawlConfig_Scope `protobuf:\"bytes,3,rep,name=scopes,proto3\" json:\"scopes,omitempty\"`\n\t// Whether crawling scope check should be enforced.\n\t// Optional.\n\tShouldEnforceScopeCheck bool `protobuf:\"varint,5,opt,name=should_enforce_scope_check,json=shouldEnforceScopeCheck,proto3\" json:\"should_enforce_scope_check,omitempty\"`\n\t// The network endpoint to be crawled.\n\t// Required.\n\tNetworkEndpoint *NetworkEndpoint `protobuf:\"bytes,6,opt,name=network_endpoint,json=networkEndpoint,proto3\" json:\"network_endpoint,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *CrawlConfig) Reset() {\n\t*x = CrawlConfig{}\n\tmi := &file_web_crawl_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CrawlConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CrawlConfig) ProtoMessage() {}\n\nfunc (x *CrawlConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_web_crawl_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CrawlConfig.ProtoReflect.Descriptor instead.\nfunc (*CrawlConfig) Descriptor() ([]byte, []int) {\n\treturn file_web_crawl_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *CrawlConfig) GetSeedingUrls() []string {\n\tif x != nil {\n\t\treturn x.SeedingUrls\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlConfig) GetMaxDepth() int32 {\n\tif x != nil {\n\t\treturn x.MaxDepth\n\t}\n\treturn 0\n}\n\nfunc (x *CrawlConfig) GetScopes() []*CrawlConfig_Scope {\n\tif x != nil {\n\t\treturn x.Scopes\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlConfig) GetShouldEnforceScopeCheck() bool {\n\tif x != nil {\n\t\treturn x.ShouldEnforceScopeCheck\n\t}\n\treturn false\n}\n\nfunc (x *CrawlConfig) GetNetworkEndpoint() *NetworkEndpoint {\n\tif x != nil {\n\t\treturn x.NetworkEndpoint\n\t}\n\treturn nil\n}\n\ntype CrawlTarget struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The URL pointing to the document.\n\tUrl string `protobuf:\"bytes,1,opt,name=url,proto3\" json:\"url,omitempty\"`\n\t// HTTP method to reach the url. Value must be in all upper case, like \"GET\".\n\tHttpMethod string `protobuf:\"bytes,2,opt,name=http_method,json=httpMethod,proto3\" json:\"http_method,omitempty\"`\n\t// An optional HTTP request body sent to the crawl URL.\n\tHttpRequestBody []byte `protobuf:\"bytes,3,opt,name=http_request_body,json=httpRequestBody,proto3\" json:\"http_request_body,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *CrawlTarget) Reset() {\n\t*x = CrawlTarget{}\n\tmi := &file_web_crawl_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CrawlTarget) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CrawlTarget) ProtoMessage() {}\n\nfunc (x *CrawlTarget) ProtoReflect() protoreflect.Message {\n\tmi := &file_web_crawl_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CrawlTarget.ProtoReflect.Descriptor instead.\nfunc (*CrawlTarget) Descriptor() ([]byte, []int) {\n\treturn file_web_crawl_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *CrawlTarget) GetUrl() string {\n\tif x != nil {\n\t\treturn x.Url\n\t}\n\treturn \"\"\n}\n\nfunc (x *CrawlTarget) GetHttpMethod() string {\n\tif x != nil {\n\t\treturn x.HttpMethod\n\t}\n\treturn \"\"\n}\n\nfunc (x *CrawlTarget) GetHttpRequestBody() []byte {\n\tif x != nil {\n\t\treturn x.HttpRequestBody\n\t}\n\treturn nil\n}\n\n// Represents an HTTP header.\ntype HttpHeader struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tKey           string                 `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tValue         string                 `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *HttpHeader) Reset() {\n\t*x = HttpHeader{}\n\tmi := &file_web_crawl_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *HttpHeader) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HttpHeader) ProtoMessage() {}\n\nfunc (x *HttpHeader) ProtoReflect() protoreflect.Message {\n\tmi := &file_web_crawl_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use HttpHeader.ProtoReflect.Descriptor instead.\nfunc (*HttpHeader) Descriptor() ([]byte, []int) {\n\treturn file_web_crawl_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *HttpHeader) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *HttpHeader) GetValue() string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn \"\"\n}\n\ntype CrawlResult struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The target visited by the crawler.\n\tCrawlTarget *CrawlTarget `protobuf:\"bytes,1,opt,name=crawl_target,json=crawlTarget,proto3\" json:\"crawl_target,omitempty\"`\n\t// Depth at which the target was visited.\n\tCrawlDepth int32 `protobuf:\"varint,2,opt,name=crawl_depth,json=crawlDepth,proto3\" json:\"crawl_depth,omitempty\"`\n\t// Response code from the crawled target.\n\tResponseCode int32 `protobuf:\"varint,3,opt,name=response_code,json=responseCode,proto3\" json:\"response_code,omitempty\"`\n\t// Content type of the resource served at the crawl target.\n\tContentType string `protobuf:\"bytes,4,opt,name=content_type,json=contentType,proto3\" json:\"content_type,omitempty\"`\n\t// The content of the resource served at the crawl target.\n\tContent []byte `protobuf:\"bytes,5,opt,name=content,proto3\" json:\"content,omitempty\"`\n\t// Http headers of the response\n\tResponseHeaders []*HttpHeader `protobuf:\"bytes,6,rep,name=response_headers,json=responseHeaders,proto3\" json:\"response_headers,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *CrawlResult) Reset() {\n\t*x = CrawlResult{}\n\tmi := &file_web_crawl_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CrawlResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CrawlResult) ProtoMessage() {}\n\nfunc (x *CrawlResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_web_crawl_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CrawlResult.ProtoReflect.Descriptor instead.\nfunc (*CrawlResult) Descriptor() ([]byte, []int) {\n\treturn file_web_crawl_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *CrawlResult) GetCrawlTarget() *CrawlTarget {\n\tif x != nil {\n\t\treturn x.CrawlTarget\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlResult) GetCrawlDepth() int32 {\n\tif x != nil {\n\t\treturn x.CrawlDepth\n\t}\n\treturn 0\n}\n\nfunc (x *CrawlResult) GetResponseCode() int32 {\n\tif x != nil {\n\t\treturn x.ResponseCode\n\t}\n\treturn 0\n}\n\nfunc (x *CrawlResult) GetContentType() string {\n\tif x != nil {\n\t\treturn x.ContentType\n\t}\n\treturn \"\"\n}\n\nfunc (x *CrawlResult) GetContent() []byte {\n\tif x != nil {\n\t\treturn x.Content\n\t}\n\treturn nil\n}\n\nfunc (x *CrawlResult) GetResponseHeaders() []*HttpHeader {\n\tif x != nil {\n\t\treturn x.ResponseHeaders\n\t}\n\treturn nil\n}\n\n// The crawler should only interact with web resources under certain scopes.\ntype CrawlConfig_Scope struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The domain of the scope, only URLs that are on the same domain or a\n\t// subdomain will be admitted for crawling. Domain might include a port.\n\t// Required.\n\tDomain string `protobuf:\"bytes,1,opt,name=domain,proto3\" json:\"domain,omitempty\"`\n\t// The path of the scope, only URLs that are under the same path will be\n\t// admitted for crawling.\n\t// Optional. When empty, all URLs under the same domain are allowed,\n\t// regardless of the paths.\n\tPath          string `protobuf:\"bytes,2,opt,name=path,proto3\" json:\"path,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CrawlConfig_Scope) Reset() {\n\t*x = CrawlConfig_Scope{}\n\tmi := &file_web_crawl_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CrawlConfig_Scope) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CrawlConfig_Scope) ProtoMessage() {}\n\nfunc (x *CrawlConfig_Scope) ProtoReflect() protoreflect.Message {\n\tmi := &file_web_crawl_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CrawlConfig_Scope.ProtoReflect.Descriptor instead.\nfunc (*CrawlConfig_Scope) Descriptor() ([]byte, []int) {\n\treturn file_web_crawl_proto_rawDescGZIP(), []int{0, 0}\n}\n\nfunc (x *CrawlConfig_Scope) GetDomain() string {\n\tif x != nil {\n\t\treturn x.Domain\n\t}\n\treturn \"\"\n}\n\nfunc (x *CrawlConfig_Scope) GetPath() string {\n\tif x != nil {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\nvar File_web_crawl_proto protoreflect.FileDescriptor\n\nvar file_web_crawl_proto_rawDesc = string([]byte{\n\t0x0a, 0x0f, 0x77, 0x65, 0x62, 0x5f, 0x63, 0x72, 0x61, 0x77, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x12, 0x0d, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x1a, 0x0d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,\n\t0xca, 0x02, 0x0a, 0x0b, 0x43, 0x72, 0x61, 0x77, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,\n\t0x21, 0x0a, 0x0c, 0x73, 0x65, 0x65, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x72, 0x6c, 0x73, 0x18,\n\t0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x65, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x72,\n\t0x6c, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x44, 0x65, 0x70, 0x74, 0x68, 0x12,\n\t0x38, 0x0a, 0x06, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32,\n\t0x20, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,\n\t0x43, 0x72, 0x61, 0x77, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x63, 0x6f, 0x70,\n\t0x65, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x73, 0x68, 0x6f,\n\t0x75, 0x6c, 0x64, 0x5f, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x63, 0x6f, 0x70,\n\t0x65, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x73,\n\t0x68, 0x6f, 0x75, 0x6c, 0x64, 0x45, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x53, 0x63, 0x6f, 0x70,\n\t0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x49, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72,\n\t0x6b, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x1e, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,\n\t0x52, 0x0f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e,\n\t0x74, 0x1a, 0x33, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f,\n\t0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61,\n\t0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0x6c, 0x0a, 0x0b,\n\t0x43, 0x72, 0x61, 0x77, 0x6c, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75,\n\t0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1f, 0x0a,\n\t0x0b, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x2a,\n\t0x0a, 0x11, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62,\n\t0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x68, 0x74, 0x74, 0x70, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x34, 0x0a, 0x0a, 0x48, 0x74,\n\t0x74, 0x70, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,\n\t0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,\n\t0x22, 0x95, 0x02, 0x0a, 0x0b, 0x43, 0x72, 0x61, 0x77, 0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,\n\t0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x72, 0x61, 0x77, 0x6c, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x61, 0x77, 0x6c, 0x54, 0x61, 0x72, 0x67,\n\t0x65, 0x74, 0x52, 0x0b, 0x63, 0x72, 0x61, 0x77, 0x6c, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12,\n\t0x1f, 0x0a, 0x0b, 0x63, 0x72, 0x61, 0x77, 0x6c, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x72, 0x61, 0x77, 0x6c, 0x44, 0x65, 0x70, 0x74, 0x68,\n\t0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x64,\n\t0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,\n\t0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e,\n\t0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74,\n\t0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65,\n\t0x6e, 0x74, 0x12, 0x44, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x68,\n\t0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74,\n\t0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x74, 0x74,\n\t0x70, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x42, 0x6f, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e,\n\t0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x42, 0x0e, 0x57, 0x65, 0x62, 0x43, 0x72, 0x61, 0x77, 0x6c, 0x50, 0x72,\n\t0x6f, 0x74, 0x6f, 0x73, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,\n\t0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d,\n\t0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e,\n\t0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69,\n\t0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x33,\n})\n\nvar (\n\tfile_web_crawl_proto_rawDescOnce sync.Once\n\tfile_web_crawl_proto_rawDescData []byte\n)\n\nfunc file_web_crawl_proto_rawDescGZIP() []byte {\n\tfile_web_crawl_proto_rawDescOnce.Do(func() {\n\t\tfile_web_crawl_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_web_crawl_proto_rawDesc), len(file_web_crawl_proto_rawDesc)))\n\t})\n\treturn file_web_crawl_proto_rawDescData\n}\n\nvar file_web_crawl_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_web_crawl_proto_goTypes = []any{\n\t(*CrawlConfig)(nil),       // 0: tsunami.proto.CrawlConfig\n\t(*CrawlTarget)(nil),       // 1: tsunami.proto.CrawlTarget\n\t(*HttpHeader)(nil),        // 2: tsunami.proto.HttpHeader\n\t(*CrawlResult)(nil),       // 3: tsunami.proto.CrawlResult\n\t(*CrawlConfig_Scope)(nil), // 4: tsunami.proto.CrawlConfig.Scope\n\t(*NetworkEndpoint)(nil),   // 5: tsunami.proto.NetworkEndpoint\n}\nvar file_web_crawl_proto_depIdxs = []int32{\n\t4, // 0: tsunami.proto.CrawlConfig.scopes:type_name -> tsunami.proto.CrawlConfig.Scope\n\t5, // 1: tsunami.proto.CrawlConfig.network_endpoint:type_name -> tsunami.proto.NetworkEndpoint\n\t1, // 2: tsunami.proto.CrawlResult.crawl_target:type_name -> tsunami.proto.CrawlTarget\n\t2, // 3: tsunami.proto.CrawlResult.response_headers:type_name -> tsunami.proto.HttpHeader\n\t4, // [4:4] is the sub-list for method output_type\n\t4, // [4:4] is the sub-list for method input_type\n\t4, // [4:4] is the sub-list for extension type_name\n\t4, // [4:4] is the sub-list for extension extendee\n\t0, // [0:4] is the sub-list for field type_name\n}\n\nfunc init() { file_web_crawl_proto_init() }\nfunc file_web_crawl_proto_init() {\n\tif File_web_crawl_proto != nil {\n\t\treturn\n\t}\n\tfile_network_proto_init()\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_web_crawl_proto_rawDesc), len(file_web_crawl_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_web_crawl_proto_goTypes,\n\t\tDependencyIndexes: file_web_crawl_proto_depIdxs,\n\t\tMessageInfos:      file_web_crawl_proto_msgTypes,\n\t}.Build()\n\tFile_web_crawl_proto = out.File\n\tfile_web_crawl_proto_goTypes = nil\n\tfile_web_crawl_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/vulnerability.proto",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Data models for describing a vulnerability.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\noption java_multiple_files = true;\noption java_outer_classname = \"VulnerabilityProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/vulnerability_go_proto\";\n\n// Severity of a vulnerability.\nenum Severity {\n  // Unspecified severity.\n  SEVERITY_UNSPECIFIED = 0;\n  // Minimal severity.\n  MINIMAL = 1;\n  // Low severity.\n  LOW = 2;\n  // Medium severity.\n  MEDIUM = 3;\n  // High severity.\n  HIGH = 4;\n  // Critical severity.\n  CRITICAL = 5;\n}\n\n// The identifier that uniquely identifies this vulnerability.\nmessage VulnerabilityId {\n  // Entity that published this identifier.\n  string publisher = 1;\n\n  // Publisher assigned unique identifier.\n  string value = 2;\n\n  // Optional. URL for details about this vulnerability.\n  string link = 3;\n}\n\n// Message that represents one single vulnerability detected by Tsunami.\nmessage Vulnerability {\n  // The main identifier for this vulnerability, usually a publicly known\n  // identifier like CVEs and such. If not publicly known, users are expected to\n  // assign an id on their own.\n  VulnerabilityId main_id = 1;\n\n  // Any related identifiers about this vulnerability, e.g. a CWE weakness.\n  repeated VulnerabilityId related_id = 2;\n\n  // Severity of this vulnerability.\n  Severity severity = 3;\n\n  // Terse but descriptive sentence about this vulnerability.\n  // For example: \"Default Password (0p3nm35h) for 'root' Account.\".\n  string title = 4;\n\n  // Verbose description of this vulnerability.\n  string description = 5;\n\n  // Optional. Verbose recommended solution(s).\n  string recommendation = 6;\n\n  // Optional. The CVSS v2 score of this vulnerability.\n  string cvss_v2 = 7;\n\n  // Optional. The CVSS v3 score of this vulnerability.\n  string cvss_v3 = 8;\n\n  // Any additional technical details about this vulnerability.\n  repeated AdditionalDetail additional_details = 9;\n}\n\n// A list of vulnerabilities. Used mostly for export purposes.\nmessage VulnerabilityList {\n  repeated Vulnerability vulnerabilities = 1;\n}\n\n// Additional details regarding a vulnerability can be stored here. Prefers to\n// use the existing structured data when possible, otherwise store the raw data\n// as a blob.\nmessage AdditionalDetail {\n  string description = 1;\n\n  oneof detail {\n    BlobData blob_data = 2;\n    TextData text_data = 3;\n    Credential credential = 4;\n    Credentials credentials = 5;\n  }\n}\n\n// A piece of arbitrary binary data.\nmessage BlobData {\n  bytes data = 1;\n}\n\n// A piece of arbitrary UTF-8 encoded text data.\nmessage TextData {\n  string text = 1;\n}\n\n// Credential for a vulnerable network service.\nmessage Credential {\n  string username = 1;\n  string password = 2\n      ;\n}\n\n// A set of credentials for a vulnerable network service.\nmessage Credentials {\n  repeated Credential credential = 1;\n}\n"
  },
  {
    "path": "proto/web_crawl.proto",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Data models for the web crawler.\nsyntax = \"proto3\";\n\npackage tsunami.proto;\n\nimport \"network.proto\";\n\noption java_multiple_files = true;\noption java_outer_classname = \"WebCrawlProtos\";\noption java_package = \"com.google.tsunami.proto\";\noption go_package = \"github.com/google/tsunami-security-scanner/proto/go/web_crawl_go_proto\";\n\n// Next ID: 7\nmessage CrawlConfig {\n  // The crawler should only interact with web resources under certain scopes.\n  message Scope {\n    // The domain of the scope, only URLs that are on the same domain or a\n    // subdomain will be admitted for crawling. Domain might include a port.\n    // Required.\n    string domain = 1;\n\n    // The path of the scope, only URLs that are under the same path will be\n    // admitted for crawling.\n    // Optional. When empty, all URLs under the same domain are allowed,\n    // regardless of the paths.\n    string path = 2;\n  }\n\n  // Starting points of a web crawl.\n  // Required.\n  repeated string seeding_urls = 1;\n\n  // The maximum depth of a web crawl.\n  // Required.\n  int32 max_depth = 2;\n\n  // Allowed crawling scopes.\n  // Optional. When empty, scopes are autogenerated from seeding_urls.\n  repeated Scope scopes = 3;\n\n  // Whether crawling scope check should be enforced.\n  // Optional.\n  bool should_enforce_scope_check = 5;\n\n  // The network endpoint to be crawled.\n  // Required.\n  NetworkEndpoint network_endpoint = 6;\n\n  reserved 4;\n}\n\nmessage CrawlTarget {\n  // The URL pointing to the document.\n  string url = 1;\n\n  // HTTP method to reach the url. Value must be in all upper case, like \"GET\".\n  string http_method = 2;\n\n  // An optional HTTP request body sent to the crawl URL.\n  bytes http_request_body = 3;\n}\n\n// Represents an HTTP header.\nmessage HttpHeader {\n  string key = 1;\n  string value = 2;\n}\n\n// The type of content stored in the CrawlResult.\nenum CrawlContentType {\n  CONTENT_TYPE_UNSPECIFIED = 0;\n  CONTENT_TYPE_RAW = 1;\n  CONTENT_TYPE_HASH = 2;\n}\n\nmessage CrawlResult {\n  // The target visited by the crawler.\n  CrawlTarget crawl_target = 1;\n\n  // Depth at which the target was visited.\n  int32 crawl_depth = 2;\n\n  // Response code from the crawled target.\n  int32 response_code = 3;\n\n  // Content type of the resource served at the crawl target.\n  string content_type = 4;\n\n  // The content of the resource served at the crawl target.\n  bytes content = 5;\n\n  // Http headers of the response\n  repeated HttpHeader response_headers = 6;\n\n  // The type of content stored in the crawl_results. By default, the whole\n  // response body is stored (RAW). But some configuration can request storing\n  // only a hash of the response body (HASH).\n  CrawlContentType crawl_content_type = 7;\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "pluginManagement {\n    plugins {\n        id 'com.github.johnrengelman.shadow' version '5.2.0'\n        id 'com.google.protobuf' version '0.8.12'\n        id 'net.ltgt.errorprone' version '1.1.1'\n    }\n    repositories {\n        gradlePluginPortal()\n    }\n}\n\nrootProject.name = 'tsunami'\ninclude ':tsunami-common'\ninclude ':tsunami-main'\ninclude ':tsunami-plugin'\ninclude ':tsunami-proto'\ninclude ':tsunami-workflow'\n\nproject(':tsunami-common').projectDir = \"$rootDir/common\" as File\nproject(':tsunami-main').projectDir = \"$rootDir/main\" as File\nproject(':tsunami-plugin').projectDir = \"$rootDir/plugin\" as File\nproject(':tsunami-proto').projectDir = \"$rootDir/proto\" as File\nproject(':tsunami-workflow').projectDir = \"$rootDir/workflow\" as File\n\ndef tcsRepository = System.getenv(\"GITREPO_TSUNAMI_TCS\") ?: \"https://github.com/google/tsunami-security-scanner-callback-server.git\"\n\nsourceControl {\n    gitRepository(\"${tcsRepository}\") {\n        producesModule(\"com.google.tsunami:tcs-common\")\n        producesModule(\"com.google.tsunami:tcs-proto\")\n    }\n}\n"
  },
  {
    "path": "tsunami.yaml",
    "content": "# This is an example YAML config file for Tsunami security scanner.\n# TODO: add examples for all available configuration options.\n\n# Socket factory configuration for TCP connections.\n# These settings ensure all sockets created by plugins have proper timeouts,\n# preventing plugins from hanging indefinitely when servers don't respond.\ncommon:\n  net:\n    socket:\n      # Timeout in seconds for establishing TCP connections (default: 10)\n      connect_timeout_seconds: 10\n      # Timeout in seconds for read operations on sockets (default: 30)\n      read_timeout_seconds: 30\n      # Whether to trust all SSL certificates (default: true)\n      trust_all_certificates: true\n"
  },
  {
    "path": "tsunami_tcs.yaml",
    "content": "plugin:\n  callbackserver:\n    callback_address: \"127.0.0.1\"  # Running callback server locally\n    callback_port: 8881            # Make sure to match with ones configured in tcs_config.yaml\n    polling_uri: \"http://127.0.0.1:8880\"\ncommon:\n  net:\n    http:\n      trust_all_certificates: true\n      connect_timeout_seconds: 60\n\n"
  },
  {
    "path": "workflow/README.md",
    "content": "# Tsunami Workflow Module\n\n## Overview\n\nThis module defines basic workflows for network scanning.\n"
  },
  {
    "path": "workflow/build.gradle",
    "content": "description = 'Tsunami: Workflow'\n\ndependencies {\n    implementation project(':tsunami-common')\n    implementation project(':tsunami-plugin')\n    implementation project(':tsunami-proto')\n\n    implementation \"com.google.flogger:flogger:0.9\"\n    implementation \"com.google.flogger:google-extensions:0.9\"\n    implementation \"com.google.guava:guava:33.0.0-jre\"\n    implementation \"com.google.protobuf:protobuf-java-util:3.25.5\"\n    implementation \"com.google.protobuf:protobuf-java:3.25.5\"\n    implementation \"javax.inject:javax.inject:1\"\n\n    testImplementation \"com.google.guava:guava-testlib:33.0.0-jre\"\n    testImplementation \"com.google.truth:truth:1.4.4\"\n    testImplementation \"com.google.truth.extensions:truth-java8-extension:1.4.4\"\n    testImplementation \"com.google.truth.extensions:truth-proto-extension:1.4.4\"\n    testImplementation \"junit:junit:4.13.2\"\n}\n\ntasks.named(\"javadoc\") {\n    dependsOn(\":tsunami-plugin:shadowJar\")\n    dependsOn(\":tsunami-proto:shadowJar\")\n}\n\ntasks.named(\"shadowJar\") {\n    dependsOn(\":tsunami-proto:shadowJar\")\n    dependsOn(\":tsunami-plugin:shadowJar\")\n}\n\ntasks.named(\"compileTestJava\") {\n    dependsOn(\":tsunami-plugin:shadowJar\")\n    dependsOn(\":tsunami-proto:shadowJar\")\n}\n\ntasks.named(\"compileJava\") {\n    dependsOn(\":tsunami-plugin:shadowJar\")\n}\n\ntasks.named('compileJava') {\n    dependsOn(':tsunami-proto:shadowJar')\n}\n\n"
  },
  {
    "path": "workflow/src/main/java/com/google/tsunami/workflow/AdvisoriesWorkflow.java",
    "content": "/*\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.workflow;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.collect.ImmutableList.toImmutableList;\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.protobuf.TextFormat;\nimport com.google.tsunami.plugin.PluginManager;\nimport com.google.tsunami.proto.VulnerabilityList;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport javax.inject.Inject;\n\n/** Workflow for dumping advisories. */\npublic final class AdvisoriesWorkflow {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  private final PluginManager pluginManager;\n\n  @Inject\n  AdvisoriesWorkflow(PluginManager pluginManager) {\n    this.pluginManager = checkNotNull(pluginManager);\n  }\n\n  public Void run(String path) {\n    logger.atInfo().log(\"Dumping advisories to %s\", path);\n\n    var vulnerabilityList = buildVulnerabilityList();\n    var vulnerabilityText = TextFormat.printer().printToString(vulnerabilityList);\n\n    try (PrintWriter writer = new PrintWriter(path, UTF_8.name())) {\n      writer.write(vulnerabilityText);\n    } catch (IOException e) {\n      logger.atSevere().withCause(e).log(\"Failed to dump advisories to %s\", path);\n    }\n\n    return null;\n  }\n\n  private VulnerabilityList buildVulnerabilityList() {\n    var vulnerabilities =\n        pluginManager.getAllVulnDetectors().stream()\n            .flatMap(plugin -> plugin.getAdvisories().stream())\n            .collect(toImmutableList());\n\n    return VulnerabilityList.newBuilder().addAllVulnerabilities(vulnerabilities).build();\n  }\n}\n"
  },
  {
    "path": "workflow/src/main/java/com/google/tsunami/workflow/DefaultScanningWorkflow.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.workflow;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.collect.ImmutableList.toImmutableList;\nimport static com.google.common.util.concurrent.Futures.immediateFailedFuture;\nimport static com.google.common.util.concurrent.Futures.immediateFuture;\nimport static com.google.common.util.concurrent.MoreExecutors.directExecutor;\n\nimport com.google.common.base.Joiner;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Lists;\nimport com.google.common.flogger.GoogleLogger;\nimport com.google.common.util.concurrent.FluentFuture;\nimport com.google.common.util.concurrent.Futures;\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.protobuf.util.Durations;\nimport com.google.protobuf.util.Timestamps;\nimport com.google.tsunami.common.TsunamiException;\nimport com.google.tsunami.common.time.UtcClock;\nimport com.google.tsunami.plugin.LanguageServerException;\nimport com.google.tsunami.plugin.PluginExecutionException;\nimport com.google.tsunami.plugin.PluginExecutionResult;\nimport com.google.tsunami.plugin.PluginExecutor;\nimport com.google.tsunami.plugin.PluginExecutor.PluginExecutorConfig;\nimport com.google.tsunami.plugin.PluginManager;\nimport com.google.tsunami.plugin.PluginManager.PluginMatchingResult;\nimport com.google.tsunami.plugin.PortScanner;\nimport com.google.tsunami.plugin.ServiceFingerprinter;\nimport com.google.tsunami.plugin.VulnDetector;\nimport com.google.tsunami.proto.DetectionReport;\nimport com.google.tsunami.proto.DetectionReportList;\nimport com.google.tsunami.proto.DetectionStatus;\nimport com.google.tsunami.proto.FingerprintingReport;\nimport com.google.tsunami.proto.FullDetectionReports;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.PortScanningReport;\nimport com.google.tsunami.proto.ReconnaissanceReport;\nimport com.google.tsunami.proto.ScanFinding;\nimport com.google.tsunami.proto.ScanResults;\nimport com.google.tsunami.proto.ScanStatus;\nimport com.google.tsunami.proto.ScanTarget;\nimport com.google.tsunami.proto.TargetInfo;\nimport java.time.Clock;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutionException;\nimport javax.inject.Inject;\nimport javax.inject.Provider;\n\n/**\n * Default scanning workflow for Tsunami.\n *\n * <p>This workflow is intended to be invoked by Tsunami's command line tool. One {@link\n * DefaultScanningWorkflow} object will be created for one target IP or hostname, and scans for\n * different IP / hostname target will be executed in isolated processes.\n *\n * <p>Tsunami performs the network scanning in the following steps:\n *\n * <ol>\n *   <li>Port scanning using NMap.\n *   <li>If target serves any web application, web fingerprinting to determine the exposed\n *       application.\n *   <li>Vulnerability detection by matching the identified network services to corresponding\n *       detectors.\n * </ol>\n */\n// TODO(b/145315535): provide a cleaner API to avoid executing the same workflow on different scan\n// target.\npublic final class DefaultScanningWorkflow {\n  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();\n\n  private final PluginManager pluginManager;\n  private final Clock clock;\n  private final Provider<PluginExecutor> pluginExecutorProvider;\n\n  private Instant scanStartTimestamp;\n  private ExecutionTracer executionTracer;\n\n  @Inject\n  public DefaultScanningWorkflow(\n      PluginManager pluginManager,\n      @UtcClock Clock clock,\n      Provider<PluginExecutor> pluginExecutorProvider) {\n    this.pluginManager = checkNotNull(pluginManager);\n    this.clock = checkNotNull(clock);\n    this.pluginExecutorProvider = checkNotNull(pluginExecutorProvider);\n  }\n\n  public ExecutionTracer getExecutionTracer() {\n    return executionTracer;\n  }\n\n  /**\n   * Performs the scanning workflow in blocking manner.\n   *\n   * @param scanTarget the IP or hostname target to be scanned\n   * @return The result of the scanning workflow.\n   * @throws ExecutionException if the scanning workflow execution thread failed.\n   * @throws InterruptedException if interrupted during scanning workflow execution.\n   */\n  public ScanResults run(ScanTarget scanTarget) throws ExecutionException, InterruptedException {\n    return runAsync(scanTarget).get();\n  }\n\n  /**\n   * Performs the scanning workflow asynchronously.\n   *\n   * @param scanTarget the IP or hostname or uri target to be scanned\n   * @return A {@link ListenableFuture} over the result of the scanning workflow.\n   */\n  public ListenableFuture<ScanResults> runAsync(ScanTarget scanTarget) {\n    checkNotNull(scanTarget);\n    scanStartTimestamp = Instant.now(clock);\n    executionTracer = ExecutionTracer.startWorkflow();\n    logger.atInfo().log(\"Staring Tsunami scanning workflow.\");\n    FluentFuture<ReconnaissanceReport> reconnaissanceReport;\n\n    if (scanTarget.hasNetworkService()) {\n      PortScanningReport portScanningReport = buildUriPortScanningReport(scanTarget);\n      reconnaissanceReport = FluentFuture.from(fingerprintNetworkServices(portScanningReport));\n    } else {\n      reconnaissanceReport =\n          FluentFuture.from(scanPorts(scanTarget))\n              .transformAsync(this::fingerprintNetworkServices, directExecutor());\n    }\n    return reconnaissanceReport\n        .transformAsync(this::detectVulnerabilities, directExecutor())\n        // Unfortunately FluentFuture doesn't support future peeking.\n        .transform(\n            scanResults -> {\n              logger.atInfo().log(\"%s\", executionTracer.buildLoggableExecutionTrace(scanResults));\n              return scanResults;\n            },\n            directExecutor())\n        // Execution errors are handled and reported back in the ScanResults.\n        .catching(PluginExecutionException.class, this::onExecutionError, directExecutor())\n        .catching(LanguageServerException.class, this::onExecutionError, directExecutor())\n        .catching(ScanningWorkflowException.class, this::onExecutionError, directExecutor());\n  }\n\n  private PortScanningReport buildUriPortScanningReport(ScanTarget scanTarget) {\n\n    Optional<PluginMatchingResult<PortScanner>> matchedPortScanner = pluginManager.getPortScanner();\n    executionTracer.startPortScanning(ImmutableList.of(matchedPortScanner.get()));\n\n    NetworkService networkService = scanTarget.getNetworkService();\n\n    return PortScanningReport.newBuilder()\n        .setTargetInfo(\n            TargetInfo.newBuilder().addNetworkEndpoints(networkService.getNetworkEndpoint()))\n        .addNetworkServices(networkService)\n        .build();\n  }\n\n  private ScanResults onExecutionError(TsunamiException exception) {\n    logger.atSevere().withCause(exception).log(\"Tsunami scan failed, aborting workflow!!!\");\n    return buildScanResultForFailure(exception);\n  }\n\n  private ListenableFuture<PortScanningReport> scanPorts(ScanTarget scanTarget)\n      throws ScanningWorkflowException {\n    Optional<PluginMatchingResult<PortScanner>> matchedPortScanner = pluginManager.getPortScanner();\n    if (!matchedPortScanner.isPresent()) {\n      return immediateFailedFuture(\n          new ScanningWorkflowException(\"At least one PortScanner plugin is required\"));\n    }\n\n    PluginExecutorConfig<PortScanningReport> executorConfig =\n        PluginExecutorConfig.<PortScanningReport>builder()\n            .setMatchedPlugin(matchedPortScanner.get())\n            .setPluginExecutionLogic(\n                () -> matchedPortScanner.get().tsunamiPlugin().scan(scanTarget))\n            .build();\n    executionTracer.startPortScanning(ImmutableList.of(matchedPortScanner.get()));\n    logger.atInfo().log(\"Starting port scanning phase of the scanning workflow.\");\n    return FluentFuture.from(pluginExecutorProvider.get().executeAsync(executorConfig))\n        .transformAsync(\n            pluginExecutionResult ->\n                pluginExecutionResult.isSucceeded()\n                    ? immediateFuture(pluginExecutionResult.resultData().get())\n                    : immediateFailedFuture(pluginExecutionResult.exception().get()),\n            directExecutor());\n  }\n\n  private ListenableFuture<ReconnaissanceReport> fingerprintNetworkServices(\n      PortScanningReport portScanningReport) {\n    checkNotNull(portScanningReport);\n\n    // For each network service, find matching fingerprinting plugin, otherwise directly add to\n    // ReconnaissanceReport.\n    TargetInfo targetInfo = portScanningReport.getTargetInfo();\n    List<PluginMatchingResult<ServiceFingerprinter>> matchedFingerprinters = Lists.newArrayList();\n    List<NetworkService> networkServicesToKeep = Lists.newArrayList();\n    for (NetworkService networkService : portScanningReport.getNetworkServicesList()) {\n      Optional<PluginMatchingResult<ServiceFingerprinter>> matchedFingerprinter =\n          pluginManager.getServiceFingerprinter(networkService);\n      if (matchedFingerprinter.isPresent()) {\n        matchedFingerprinters.add(matchedFingerprinter.get());\n      } else {\n        networkServicesToKeep.add(networkService);\n      }\n    }\n\n    executionTracer.startServiceFingerprinting(ImmutableList.copyOf(matchedFingerprinters));\n    logger.atInfo().log(\n        \"Port scanning phase done, moving to service fingerprinting phase with '%d'\"\n            + \" fingerprinter(s) selected.\",\n        matchedFingerprinters.size());\n\n    // Execute matched fingerprinters asynchronously.\n    ImmutableList<ListenableFuture<PluginExecutionResult<FingerprintingReport>>>\n        fingerprintingResultFutures =\n            matchedFingerprinters.stream()\n                .map(fingerprinter -> buildFingerprinterExecutorConfig(targetInfo, fingerprinter))\n                .map(executorConfig -> pluginExecutorProvider.get().executeAsync(executorConfig))\n                .collect(toImmutableList());\n    return FluentFuture.from(Futures.successfulAsList(fingerprintingResultFutures))\n        .transform(\n            executionResults ->\n                ReconnaissanceReport.newBuilder()\n                    .setTargetInfo(targetInfo)\n                    .addAllNetworkServices(networkServicesToKeep)\n                    .addAllNetworkServices(getFingerprintedServices(executionResults))\n                    .build(),\n            directExecutor());\n  }\n\n  private static PluginExecutorConfig<FingerprintingReport> buildFingerprinterExecutorConfig(\n      TargetInfo targetInfo, PluginMatchingResult<ServiceFingerprinter> fingerprinter) {\n    return PluginExecutorConfig.<FingerprintingReport>builder()\n        .setMatchedPlugin(fingerprinter)\n        .setPluginExecutionLogic(\n            () ->\n                fingerprinter\n                    .tsunamiPlugin()\n                    .fingerprint(targetInfo, fingerprinter.matchedServices().get(0)))\n        .build();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private static ImmutableList<NetworkService> getFingerprintedServices(\n      Collection<PluginExecutionResult<FingerprintingReport>> executionResults) {\n    return executionResults.stream()\n        .flatMap(\n            result ->\n                result.isSucceeded()\n                    ? result.resultData().get().getNetworkServicesList().stream()\n                    : ((List<NetworkService>)\n                            result.executorConfig().matchedPlugin().matchedServices())\n                        .stream())\n        .collect(toImmutableList());\n  }\n\n  private ListenableFuture<ScanResults> detectVulnerabilities(\n      ReconnaissanceReport reconnaissanceReport) {\n    checkNotNull(reconnaissanceReport);\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> matchedVulnDetectors =\n        pluginManager.getVulnDetectors(reconnaissanceReport);\n    executionTracer.startVulnerabilityDetecting(matchedVulnDetectors);\n    logger.atInfo().log(\"Service fingerprinting phase done, moving to vuln detection phase.\");\n\n    ImmutableList<ListenableFuture<PluginExecutionResult<DetectionReportList>>>\n        detectionResultFutures =\n            matchedVulnDetectors.stream()\n                .map(\n                    matchedVulnDetector ->\n                        PluginExecutorConfig.<DetectionReportList>builder()\n                            .setMatchedPlugin(matchedVulnDetector)\n                            .setPluginExecutionLogic(\n                                () ->\n                                    matchedVulnDetector\n                                        .tsunamiPlugin()\n                                        .detect(\n                                            reconnaissanceReport.getTargetInfo(),\n                                            matchedVulnDetector.matchedServices()))\n                            .build())\n                .map(\n                    vulnDetectorExecutorConfig ->\n                        pluginExecutorProvider.get().executeAsync(vulnDetectorExecutorConfig))\n                .collect(toImmutableList());\n    return FluentFuture.from(Futures.successfulAsList(detectionResultFutures))\n        .transform(\n            detectionResult -> generateScanResults(detectionResult, reconnaissanceReport),\n            directExecutor());\n  }\n\n  private ScanResults generateScanResults(\n      Collection<PluginExecutionResult<DetectionReportList>> detectionResults,\n      ReconnaissanceReport reconnaissanceReport) {\n    executionTracer.setDone();\n    logger.atInfo().log(\"Tsunami scanning workflow done. Generating scan results.\");\n\n    ImmutableList<DetectionReport> succeededDetectionReports =\n        detectionResults.stream()\n            .filter(PluginExecutionResult::isSucceeded)\n            .flatMap(\n                detectionResult ->\n                    detectionResult.resultData().get().getDetectionReportsList().stream())\n            .collect(toImmutableList());\n    ImmutableList<String> failedPlugins =\n        detectionResults.stream()\n            .filter(executionResult -> !executionResult.isSucceeded())\n            .map(executionResult -> executionResult.executorConfig().matchedPlugin().pluginId())\n            .collect(toImmutableList());\n\n    ScanStatus scanStatus;\n    String statusMessage = \"\";\n    if (failedPlugins.isEmpty()) {\n      scanStatus = ScanStatus.SUCCEEDED;\n    } else if (failedPlugins.size() == detectionResults.size()) {\n      scanStatus = ScanStatus.FAILED;\n      statusMessage = \"All VulnDetectors failed.\";\n    } else {\n      scanStatus = ScanStatus.PARTIALLY_SUCCEEDED;\n      statusMessage = \"Failed plugins:\\n\" + Joiner.on(\"\\n\").join(failedPlugins);\n    }\n\n    boolean targetAlive = false;\n    if (reconnaissanceReport.getNetworkServicesCount() > 0 || !detectionResults.isEmpty()) {\n      targetAlive = true;\n    }\n\n    return ScanResults.newBuilder()\n        .setScanStatus(scanStatus)\n        .setStatusMessage(statusMessage)\n        .setTargetAlive(targetAlive)\n        .addAllScanFindings(\n            succeededDetectionReports.stream()\n                .filter(\n                    detectionReport ->\n                        detectionReport\n                                .getDetectionStatus()\n                                .equals(DetectionStatus.VULNERABILITY_VERIFIED)\n                            || detectionReport\n                                .getDetectionStatus()\n                                .equals(DetectionStatus.VULNERABILITY_PRESENT))\n                .map(\n                    detectionReport ->\n                        ScanFinding.newBuilder()\n                            .setTargetInfo(detectionReport.getTargetInfo())\n                            .setNetworkService(detectionReport.getNetworkService())\n                            .setVulnerability(detectionReport.getVulnerability())\n                            .build())\n                .collect(toImmutableList()))\n        .setScanStartTimestamp(Timestamps.fromMillis(scanStartTimestamp.toEpochMilli()))\n        .setScanDuration(\n            Durations.fromMillis(\n                Duration.between(scanStartTimestamp, Instant.now(clock)).toMillis()))\n        .setFullDetectionReports(\n            FullDetectionReports.newBuilder().addAllDetectionReports(succeededDetectionReports))\n        .setReconnaissanceReport(reconnaissanceReport)\n        .build();\n  }\n\n  private ScanResults buildScanResultForFailure(TsunamiException exception) {\n    executionTracer.forceDone();\n    return ScanResults.newBuilder()\n        .setScanStatus(ScanStatus.FAILED)\n        .setStatusMessage(exception.getMessage())\n        .setScanStartTimestamp(Timestamps.fromMillis(scanStartTimestamp.toEpochMilli()))\n        .setScanDuration(\n            Durations.fromMillis(\n                Duration.between(scanStartTimestamp, Instant.now(clock)).toMillis()))\n        .build();\n  }\n}\n"
  },
  {
    "path": "workflow/src/main/java/com/google/tsunami/workflow/ExecutionStage.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.workflow;\n\n/** Defines the scan execution stages. */\npublic enum ExecutionStage {\n  START,\n  PORT_SCANNING,\n  SERVICE_FINGERPRINTING,\n  VULNERABILITY_DETECTING,\n  DONE\n}\n"
  },
  {
    "path": "workflow/src/main/java/com/google/tsunami/workflow/ExecutionTracer.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.workflow;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static com.google.common.base.Preconditions.checkState;\nimport static com.google.common.collect.ImmutableList.toImmutableList;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Joiner;\nimport com.google.common.base.Stopwatch;\nimport com.google.common.collect.ImmutableList;\nimport com.google.tsunami.plugin.PluginManager.PluginMatchingResult;\nimport com.google.tsunami.plugin.PortScanner;\nimport com.google.tsunami.plugin.ServiceFingerprinter;\nimport com.google.tsunami.plugin.TsunamiPlugin;\nimport com.google.tsunami.plugin.VulnDetector;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.ScanResults;\nimport java.time.Duration;\n\n/** Traces the execution of the default scanning workflow. */\nfinal class ExecutionTracer {\n  private static final Joiner PLUGIN_INFO_JOINER = Joiner.on(\"\\n    \");\n  private static final Joiner NETWORK_SERVICE_JOINER = Joiner.on(\", \");\n\n  private final Stopwatch portScanningTimer;\n  private final Stopwatch serviceFingerprintingTimer;\n  private final Stopwatch vulnerabilityDetectingTimer;\n\n  private ExecutionStage currentExecutionStage;\n  private ImmutableList<PluginMatchingResult<PortScanner>> selectedPortScanners;\n  private ImmutableList<PluginMatchingResult<ServiceFingerprinter>> selectedServiceFingerprinters;\n  private ImmutableList<PluginMatchingResult<VulnDetector>> selectedVulnDetectors;\n\n  private ExecutionTracer() {\n    this(Stopwatch.createUnstarted(), Stopwatch.createUnstarted(), Stopwatch.createUnstarted());\n  }\n\n  @VisibleForTesting\n  ExecutionTracer(\n      Stopwatch portScanningTimer,\n      Stopwatch serviceFingerprintingTimer,\n      Stopwatch vulnerabilityDetectingTimer) {\n    this.currentExecutionStage = ExecutionStage.START;\n    this.portScanningTimer = checkNotNull(portScanningTimer);\n    this.serviceFingerprintingTimer = checkNotNull(serviceFingerprintingTimer);\n    this.vulnerabilityDetectingTimer = checkNotNull(vulnerabilityDetectingTimer);\n  }\n\n  static ExecutionTracer startWorkflow() {\n    return new ExecutionTracer();\n  }\n\n  ExecutionStage getCurrentExecutionStage() {\n    return this.currentExecutionStage;\n  }\n\n  void startPortScanning(ImmutableList<PluginMatchingResult<PortScanner>> selectedPortScanners) {\n    checkState(currentExecutionStage.equals(ExecutionStage.START));\n\n    this.portScanningTimer.start();\n    this.currentExecutionStage = ExecutionStage.PORT_SCANNING;\n    this.selectedPortScanners = checkNotNull(selectedPortScanners);\n  }\n\n  void startServiceFingerprinting(\n      ImmutableList<PluginMatchingResult<ServiceFingerprinter>> selectedServiceFingerprinters) {\n    checkState(currentExecutionStage.equals(ExecutionStage.PORT_SCANNING));\n    checkState(portScanningTimer.isRunning());\n\n    this.portScanningTimer.stop();\n    this.serviceFingerprintingTimer.start();\n    this.currentExecutionStage = ExecutionStage.SERVICE_FINGERPRINTING;\n    this.selectedServiceFingerprinters = checkNotNull(selectedServiceFingerprinters);\n  }\n\n  void startVulnerabilityDetecting(\n      ImmutableList<PluginMatchingResult<VulnDetector>> selectedVulnDetectors) {\n    checkState(currentExecutionStage.equals(ExecutionStage.SERVICE_FINGERPRINTING));\n    checkState(serviceFingerprintingTimer.isRunning());\n\n    this.serviceFingerprintingTimer.stop();\n    this.vulnerabilityDetectingTimer.start();\n    this.currentExecutionStage = ExecutionStage.VULNERABILITY_DETECTING;\n    this.selectedVulnDetectors = checkNotNull(selectedVulnDetectors);\n  }\n\n  void setDone() {\n    checkState(currentExecutionStage.equals(ExecutionStage.VULNERABILITY_DETECTING));\n    checkState(!portScanningTimer.isRunning());\n    checkState(!serviceFingerprintingTimer.isRunning());\n    checkState(vulnerabilityDetectingTimer.isRunning());\n\n    this.vulnerabilityDetectingTimer.stop();\n    this.currentExecutionStage = ExecutionStage.DONE;\n  }\n\n  void forceDone() {\n    if (portScanningTimer.isRunning()) {\n      portScanningTimer.stop();\n    }\n    if (serviceFingerprintingTimer.isRunning()) {\n      serviceFingerprintingTimer.stop();\n    }\n    if (vulnerabilityDetectingTimer.isRunning()) {\n      vulnerabilityDetectingTimer.stop();\n    }\n\n    this.currentExecutionStage = ExecutionStage.DONE;\n  }\n\n  boolean isDone() {\n    return currentExecutionStage.equals(ExecutionStage.DONE);\n  }\n\n  Duration getPortScanningStageRuntime() {\n    return portScanningTimer.elapsed();\n  }\n\n  Duration getServiceFingerprintingStageRuntime() {\n    return serviceFingerprintingTimer.elapsed();\n  }\n\n  Duration getVulnerabilityDetectingStageRuntime() {\n    return vulnerabilityDetectingTimer.elapsed();\n  }\n\n  ImmutableList<PluginMatchingResult<PortScanner>> getSelectedPortScanners() {\n    return selectedPortScanners;\n  }\n\n  ImmutableList<PluginMatchingResult<ServiceFingerprinter>> getSelectedServiceFingerprinters() {\n    return selectedServiceFingerprinters;\n  }\n\n  ImmutableList<PluginMatchingResult<VulnDetector>> getSelectedVulnDetectors() {\n    return selectedVulnDetectors;\n  }\n\n  String buildLoggableExecutionTrace(ScanResults scanResults) {\n    checkState(isDone());\n    return new StringBuilder(\"Tsunami scanning workflow traces:\\n\")\n        // Port scanning phase.\n        .append(\n            String.format(\n                \"  Port scanning phase (%s) with %d plugin(s):\\n    \",\n                portScanningTimer, selectedPortScanners.size()))\n        .append(\n            PLUGIN_INFO_JOINER.join(\n                selectedPortScanners.stream()\n                    .map(ExecutionTracer::buildPluginInfoMessage)\n                    .collect(toImmutableList())))\n        // Service fingerprinting phase.\n        .append(\n            String.format(\n                \"\\n  Service fingerprinting phase (%s) with %d plugin(s):\\n    \",\n                serviceFingerprintingTimer, selectedServiceFingerprinters.size()))\n        .append(\n            PLUGIN_INFO_JOINER.join(\n                selectedServiceFingerprinters.stream()\n                    .map(ExecutionTracer::buildPluginInfoMessage)\n                    .collect(toImmutableList())))\n        // Vuln detection phase.\n        .append(\n            String.format(\n                \"\\n  Vuln detection phase (%s) with %d plugin(s):\\n    \",\n                vulnerabilityDetectingTimer, selectedVulnDetectors.size()))\n        .append(\n            PLUGIN_INFO_JOINER.join(\n                selectedVulnDetectors.stream()\n                    .map(ExecutionTracer::buildPluginInfoMessage)\n                    .collect(toImmutableList())))\n        .append(\n            String.format(\n                \"\\n  # of detected vulnerability: %d.\", scanResults.getScanFindingsCount()))\n        .toString();\n  }\n\n  static <T extends TsunamiPlugin> String buildPluginInfoMessage(\n      PluginMatchingResult<T> pluginMatchingResult) {\n    // TODO(b/145315535): add execution time once plugin execution logic is moved to a dedicated\n    // plugin executor.\n    StringBuilder pluginInfoBuilder = new StringBuilder(pluginMatchingResult.pluginId());\n    if (!pluginMatchingResult.matchedServices().isEmpty()) {\n      pluginInfoBuilder\n          .append(\" was selected for the following services: \")\n          .append(\n              NETWORK_SERVICE_JOINER.join(\n                  pluginMatchingResult.matchedServices().stream()\n                      .map(ExecutionTracer::formatNetworkService)\n                      .collect(toImmutableList())));\n    }\n    return pluginInfoBuilder.toString();\n  }\n\n  static String formatNetworkService(NetworkService networkService) {\n    return String.format(\n        \"%s (%s, port %d)\",\n        networkService.getServiceName(),\n        networkService.getTransportProtocol(),\n        networkService.getNetworkEndpoint().getPort().getPortNumber());\n  }\n}\n"
  },
  {
    "path": "workflow/src/main/java/com/google/tsunami/workflow/ScanningWorkflowException.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.workflow;\n\nimport com.google.tsunami.common.ErrorCode;\nimport com.google.tsunami.common.TsunamiException;\n\n/** Signals a well known and recognized error occurred while executing the scanning workflow. */\npublic class ScanningWorkflowException extends TsunamiException {\n  public ScanningWorkflowException(String message) {\n    super(ErrorCode.WORKFLOW_ERROR, message);\n  }\n}\n"
  },
  {
    "path": "workflow/src/test/java/com/google/tsunami/workflow/DefaultScanningWorkflowTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.workflow;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.tsunami.common.data.NetworkEndpointUtils.forIp;\nimport static com.google.tsunami.common.data.NetworkServiceUtils.buildUriNetworkService;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.inject.Guice;\nimport com.google.inject.Injector;\nimport com.google.tsunami.common.net.http.HttpClientModule;\nimport com.google.tsunami.common.time.testing.FakeUtcClockModule;\nimport com.google.tsunami.plugin.payload.PayloadGeneratorModule;\nimport com.google.tsunami.plugin.testing.FailedPortScannerBootstrapModule;\nimport com.google.tsunami.plugin.testing.FailedRemoteVulnDetectorBootstrapModule;\nimport com.google.tsunami.plugin.testing.FailedServiceFingerprinterBootstrapModule;\nimport com.google.tsunami.plugin.testing.FailedVulnDetectorBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakePluginExecutionModule;\nimport com.google.tsunami.plugin.testing.FakePortScanner;\nimport com.google.tsunami.plugin.testing.FakePortScannerBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakePortScannerBootstrapModule2;\nimport com.google.tsunami.plugin.testing.FakePortScannerBootstrapModuleEmpty;\nimport com.google.tsunami.plugin.testing.FakeRemoteVulnDetector;\nimport com.google.tsunami.plugin.testing.FakeRemoteVulnDetectorBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakeServiceFingerprinter;\nimport com.google.tsunami.plugin.testing.FakeServiceFingerprinterBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakeVulnDetector;\nimport com.google.tsunami.plugin.testing.FakeVulnDetector2;\nimport com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModule2;\nimport com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModuleEmpty;\nimport com.google.tsunami.proto.ScanResults;\nimport com.google.tsunami.proto.ScanStatus;\nimport com.google.tsunami.proto.ScanTarget;\nimport java.security.SecureRandom;\nimport java.util.concurrent.ExecutionException;\nimport javax.inject.Inject;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link DefaultScanningWorkflow}. */\n@RunWith(JUnit4.class)\npublic final class DefaultScanningWorkflowTest {\n  @Inject private DefaultScanningWorkflow scanningWorkflow;\n\n  @Before\n  public void setUp() {\n    Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakeUtcClockModule(),\n            new FakePluginExecutionModule(),\n            new FakePortScannerBootstrapModule(),\n            new FakePortScannerBootstrapModule2(),\n            new FakeServiceFingerprinterBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule2(),\n            new FakeRemoteVulnDetectorBootstrapModule())\n        .injectMembers(this);\n  }\n\n  @Test\n  public void run_whenAllRequiredPluginsInstalled_executesScanningWorkflow()\n      throws InterruptedException, ExecutionException {\n    ScanResults scanResults = scanningWorkflow.run(buildScanTarget());\n    ExecutionTracer executionTracer = scanningWorkflow.getExecutionTracer();\n\n    assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);\n    assertThat(executionTracer.isDone()).isTrue();\n    assertThat(executionTracer.getSelectedPortScanners()).hasSize(1);\n    assertThat(executionTracer.getSelectedPortScanners().get(0).tsunamiPlugin().getClass())\n        .isEqualTo(FakePortScanner.class);\n    assertThat(executionTracer.getSelectedServiceFingerprinters()).hasSize(1);\n    assertThat(executionTracer.getSelectedServiceFingerprinters().get(0).tsunamiPlugin().getClass())\n        .isEqualTo(FakeServiceFingerprinter.class);\n    assertThat(\n            executionTracer.getSelectedVulnDetectors().stream()\n                .map(selectedVulnDetector -> selectedVulnDetector.tsunamiPlugin().getClass()))\n        .containsExactlyElementsIn(\n            ImmutableList.of(\n                FakeVulnDetector.class, FakeVulnDetector2.class, FakeRemoteVulnDetector.class));\n  }\n\n  @Test\n  public void run_whenUriScanTarget_executesScanningWorkflow()\n      throws InterruptedException, ExecutionException {\n    ScanResults scanResults = scanningWorkflow.run(buildUriScanTarget());\n    ExecutionTracer executionTracer = scanningWorkflow.getExecutionTracer();\n\n    assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);\n    assertThat(executionTracer.isDone()).isTrue();\n    assertThat(\n            executionTracer.getSelectedVulnDetectors().stream()\n                .map(selectedVulnDetector -> selectedVulnDetector.tsunamiPlugin().getClass()))\n        .containsExactlyElementsIn(\n            ImmutableList.of(\n                FakeVulnDetector.class, FakeVulnDetector2.class, FakeRemoteVulnDetector.class));\n    assertThat(scanResults.getScanFindings(0).getNetworkService().getServiceName())\n        .isEqualTo(\"https\");\n    assertThat(\n            scanResults\n                .getScanFindings(0)\n                .getNetworkService()\n                .getServiceContext()\n                .getWebServiceContext()\n                .getApplicationRoot())\n        .isEqualTo(\"/function1\");\n    assertThat(\n            scanResults\n                .getScanFindings(0)\n                .getNetworkService()\n                .getNetworkEndpoint()\n                .getPort()\n                .getPortNumber())\n        .isEqualTo(443);\n    assertThat(\n            scanResults\n                .getScanFindings(0)\n                .getNetworkService()\n                .getNetworkEndpoint()\n                .getHostname()\n                .getName())\n        .isEqualTo(\"localhost\");\n  }\n\n  // TODO(b/145315535): add default output for the fake plugins and test the output of the workflow.\n\n  @Test\n  public void run_whenNoPortScannerInstalled_returnsFailedScanResult()\n      throws ExecutionException, InterruptedException {\n    Injector injector =\n        Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakeUtcClockModule(),\n            new FakePluginExecutionModule(),\n            new FakeServiceFingerprinterBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule());\n    scanningWorkflow = injector.getInstance(DefaultScanningWorkflow.class);\n\n    ScanResults scanResults = scanningWorkflow.run(buildScanTarget());\n\n    assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.FAILED);\n    assertThat(scanResults.getStatusMessage())\n        .contains(\"At least one PortScanner plugin is required\");\n    assertThat(scanResults.getScanFindingsList()).isEmpty();\n  }\n\n  @Test\n  public void run_whenNoFingerprinterInstalled_executesScanningWorkflow()\n      throws InterruptedException, ExecutionException {\n    Injector injector =\n        Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakeUtcClockModule(),\n            new FakePluginExecutionModule(),\n            new FakePortScannerBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule2());\n    scanningWorkflow = injector.getInstance(DefaultScanningWorkflow.class);\n\n    ScanResults scanResults = scanningWorkflow.run(buildScanTarget());\n    ExecutionTracer executionTracer = scanningWorkflow.getExecutionTracer();\n\n    assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);\n    assertThat(executionTracer.isDone()).isTrue();\n    assertThat(executionTracer.getSelectedPortScanners()).hasSize(1);\n    assertThat(executionTracer.getSelectedPortScanners().get(0).tsunamiPlugin().getClass())\n        .isEqualTo(FakePortScanner.class);\n    assertThat(executionTracer.getSelectedServiceFingerprinters()).isEmpty();\n    assertThat(\n            executionTracer.getSelectedVulnDetectors().stream()\n                .map(selectedVulnDetector -> selectedVulnDetector.tsunamiPlugin().getClass()))\n        .containsExactlyElementsIn(\n            ImmutableList.of(FakeVulnDetector.class, FakeVulnDetector2.class));\n  }\n\n  @Test\n  public void run_whenPortScannerFailed_returnsFailedScanResult()\n      throws ExecutionException, InterruptedException {\n    Injector injector =\n        Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakeUtcClockModule(),\n            new FakePluginExecutionModule(),\n            new FailedPortScannerBootstrapModule(),\n            new FakeServiceFingerprinterBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule());\n    scanningWorkflow = injector.getInstance(DefaultScanningWorkflow.class);\n\n    ScanResults scanResults = scanningWorkflow.run(buildScanTarget());\n\n    assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.FAILED);\n    assertThat(scanResults.getStatusMessage())\n        .contains(\"Plugin execution error on '/fake/PORT_SCAN/FailedPortScanner/v0.1'\");\n    assertThat(scanResults.getScanFindingsList()).isEmpty();\n  }\n\n  @Test\n  public void run_whenServiceFingerprinterFailed_reusesNetworkServicesFromPortScan()\n      throws ExecutionException, InterruptedException {\n    Injector injector =\n        Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakeUtcClockModule(),\n            new FakePluginExecutionModule(),\n            new FakePortScannerBootstrapModule(),\n            new FailedServiceFingerprinterBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule2());\n    scanningWorkflow = injector.getInstance(DefaultScanningWorkflow.class);\n\n    ScanResults scanResults = scanningWorkflow.run(buildScanTarget());\n\n    assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);\n    assertThat(scanResults.getTargetAlive()).isTrue();\n    assertThat(scanResults.getReconnaissanceReport().getNetworkServicesList())\n        .containsExactly(\n            FakePortScanner.getFakeNetworkService(buildScanTarget().getNetworkEndpoint()));\n  }\n\n  @Test\n  public void run_whenNoPortOrVulnReported_returnsHostDown()\n      throws ExecutionException, InterruptedException {\n    Injector injector =\n        Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakeUtcClockModule(),\n            new FakePluginExecutionModule(),\n            new FakePortScannerBootstrapModuleEmpty(),\n            new FakeVulnDetectorBootstrapModuleEmpty());\n    scanningWorkflow = injector.getInstance(DefaultScanningWorkflow.class);\n\n    ScanResults scanResults = scanningWorkflow.run(buildScanTarget());\n\n    assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);\n    assertThat(scanResults.getReconnaissanceReport().getNetworkServicesList()).isEmpty();\n    assertThat(scanResults.getScanFindingsList()).isEmpty();\n    assertThat(scanResults.getTargetAlive()).isFalse();\n  }\n\n  @Test\n  public void run_whenServiceFingerprinterSucceeded_fillsReconnaissanceReportWithFingerprintResult()\n      throws ExecutionException, InterruptedException {\n    Injector injector =\n        Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakeUtcClockModule(),\n            new FakePluginExecutionModule(),\n            new FakePortScannerBootstrapModule(),\n            new FakeServiceFingerprinterBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule2());\n    scanningWorkflow = injector.getInstance(DefaultScanningWorkflow.class);\n\n    ScanResults scanResults = scanningWorkflow.run(buildScanTarget());\n\n    assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);\n    assertThat(scanResults.getTargetAlive()).isTrue();\n    assertThat(scanResults.getReconnaissanceReport().getNetworkServicesList())\n        .containsExactly(\n            FakeServiceFingerprinter.addWebServiceContext(\n                FakePortScanner.getFakeNetworkService(buildScanTarget().getNetworkEndpoint())));\n  }\n\n  @Test\n  public void run_whenSomeVulnDetectorFailed_returnsPartiallySucceededScanResult()\n      throws ExecutionException, InterruptedException {\n    Injector injector =\n        Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakeUtcClockModule(),\n            new FakePluginExecutionModule(),\n            new FakePortScannerBootstrapModule(),\n            new FakeServiceFingerprinterBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule(),\n            new FakeRemoteVulnDetectorBootstrapModule(),\n            new FailedVulnDetectorBootstrapModule(),\n            new FailedRemoteVulnDetectorBootstrapModule());\n    scanningWorkflow = injector.getInstance(DefaultScanningWorkflow.class);\n\n    ScanResults scanResults = scanningWorkflow.run(buildScanTarget());\n\n    assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.PARTIALLY_SUCCEEDED);\n    assertThat(scanResults.getStatusMessage())\n        .contains(\n            \"Failed plugins:\\n\"\n                + \"/fake/VULN_DETECTION/FailedVulnDetector/v0.1\\n\"\n                + \"/fake/REMOTE_VULN_DETECTION/FailedRemoteVulnDetector/v0.1\");\n    assertThat(scanResults.getScanFindingsList()).hasSize(2);\n  }\n\n  @Test\n  public void run_whenAllVulnDetectorFailed_returnsFailedScanResult()\n      throws ExecutionException, InterruptedException {\n    Injector injector =\n        Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakeUtcClockModule(),\n            new FakePluginExecutionModule(),\n            new FakePortScannerBootstrapModule(),\n            new FakeServiceFingerprinterBootstrapModule(),\n            new FailedVulnDetectorBootstrapModule(),\n            new FailedRemoteVulnDetectorBootstrapModule());\n    scanningWorkflow = injector.getInstance(DefaultScanningWorkflow.class);\n\n    ScanResults scanResults = scanningWorkflow.run(buildScanTarget());\n\n    assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.FAILED);\n    assertThat(scanResults.getStatusMessage()).contains(\"All VulnDetectors failed\");\n    assertThat(scanResults.getScanFindingsList()).isEmpty();\n  }\n\n  @Test\n  public void run_whenNullScanTarget_throwsNullPointerException() {\n    Injector injector =\n        Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakeUtcClockModule(),\n            new FakePluginExecutionModule(),\n            new FakeServiceFingerprinterBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule());\n    scanningWorkflow = injector.getInstance(DefaultScanningWorkflow.class);\n\n    assertThrows(NullPointerException.class, () -> scanningWorkflow.run(null));\n  }\n\n  private static ScanTarget buildScanTarget() {\n    return ScanTarget.newBuilder().setNetworkEndpoint(forIp(\"1.2.3.4\")).build();\n  }\n\n  private static ScanTarget buildUriScanTarget() {\n    return ScanTarget.newBuilder()\n        .setNetworkService(buildUriNetworkService(\"https://localhost/function1\"))\n        .build();\n  }\n}\n"
  },
  {
    "path": "workflow/src/test/java/com/google/tsunami/workflow/ExecutionTracerTest.java",
    "content": "/*\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.google.tsunami.workflow;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.tsunami.common.data.NetworkEndpointUtils.forIpAndPort;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.base.Stopwatch;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.testing.FakeTicker;\nimport com.google.inject.Guice;\nimport com.google.tsunami.common.net.http.HttpClientModule;\nimport com.google.tsunami.plugin.PluginManager;\nimport com.google.tsunami.plugin.PluginManager.PluginMatchingResult;\nimport com.google.tsunami.plugin.PortScanner;\nimport com.google.tsunami.plugin.VulnDetector;\nimport com.google.tsunami.plugin.payload.PayloadGeneratorModule;\nimport com.google.tsunami.plugin.testing.FakePortScannerBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakeServiceFingerprinterBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModule;\nimport com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModule2;\nimport com.google.tsunami.proto.NetworkService;\nimport com.google.tsunami.proto.ReconnaissanceReport;\nimport com.google.tsunami.proto.ScanFinding;\nimport com.google.tsunami.proto.ScanResults;\nimport com.google.tsunami.proto.TransportProtocol;\nimport java.security.SecureRandom;\nimport java.time.Duration;\nimport javax.inject.Inject;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/** Tests for {@link ExecutionTracer}. */\n@RunWith(JUnit4.class)\npublic final class ExecutionTracerTest {\n  private static final Duration TICK_DURATION = Duration.ofSeconds(1);\n  private final FakeTicker ticker = new FakeTicker().setAutoIncrementStep(TICK_DURATION);\n  private final Stopwatch portScanningTimer = Stopwatch.createUnstarted(ticker);\n  private final Stopwatch serviceFingerprintingTimer = Stopwatch.createUnstarted(ticker);\n  private final Stopwatch vulnerabilityDetectingTimer = Stopwatch.createUnstarted(ticker);\n\n  @Inject PluginManager pluginManager;\n\n  @Before\n  public void setUp() {\n    Guice.createInjector(\n            new HttpClientModule.Builder().build(),\n            new PayloadGeneratorModule(new SecureRandom()),\n            new FakePortScannerBootstrapModule(),\n            new FakePortScannerBootstrapModule(),\n            new FakeServiceFingerprinterBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule(),\n            new FakeVulnDetectorBootstrapModule2())\n        .injectMembers(this);\n  }\n\n  @Test\n  public void startWorkflow_always_createExecutionTracerAtStartStage() {\n    assertThat(ExecutionTracer.startWorkflow().getCurrentExecutionStage())\n        .isEqualTo(ExecutionStage.START);\n  }\n\n  @Test\n  public void startPortScanning_always_setsExecutionTracerToCorrectState() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n    ImmutableList<PluginMatchingResult<PortScanner>> installedPortScanners =\n        pluginManager.getPortScanners();\n\n    executionTracer.startPortScanning(installedPortScanners);\n\n    assertThat(portScanningTimer.isRunning()).isTrue();\n    assertThat(serviceFingerprintingTimer.isRunning()).isFalse();\n    assertThat(vulnerabilityDetectingTimer.isRunning()).isFalse();\n    assertThat(executionTracer.getCurrentExecutionStage()).isEqualTo(ExecutionStage.PORT_SCANNING);\n    assertThat(executionTracer.getSelectedPortScanners())\n        .containsExactlyElementsIn(installedPortScanners);\n    assertThat(executionTracer.getPortScanningStageRuntime()).isEqualTo(TICK_DURATION);\n  }\n\n  @Test\n  public void startPortScanning_whenExecutionStageIsNotStart_throwsException() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n    ImmutableList<PluginMatchingResult<PortScanner>> installedPortScanners =\n        pluginManager.getPortScanners();\n    executionTracer.startPortScanning(installedPortScanners);\n\n    assertThrows(\n        IllegalStateException.class,\n        () -> executionTracer.startPortScanning(installedPortScanners));\n  }\n\n  @Test\n  public void startServiceFingerprinting_always_setsExecutionTracerToCorrectState() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n    executionTracer.startPortScanning(pluginManager.getPortScanners());\n\n    // TODO(b/145315535): fill service fingerprinter data when plugin manager has the interface.\n    executionTracer.startServiceFingerprinting(ImmutableList.of());\n\n    assertThat(portScanningTimer.isRunning()).isFalse();\n    assertThat(serviceFingerprintingTimer.isRunning()).isTrue();\n    assertThat(vulnerabilityDetectingTimer.isRunning()).isFalse();\n    assertThat(executionTracer.getCurrentExecutionStage())\n        .isEqualTo(ExecutionStage.SERVICE_FINGERPRINTING);\n    assertThat(executionTracer.getSelectedServiceFingerprinters()).isEmpty();\n    assertThat(executionTracer.getServiceFingerprintingStageRuntime()).isEqualTo(TICK_DURATION);\n  }\n\n  @Test\n  public void startServiceFingerprinting_whenStageNotPortScanning_throwsException() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n\n    assertThrows(\n        IllegalStateException.class,\n        () -> executionTracer.startServiceFingerprinting(ImmutableList.of()));\n  }\n\n  @Test\n  public void startServiceFingerprinting_whenPortScanningTimerNotRunning_throwsException() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n    executionTracer.startPortScanning(pluginManager.getPortScanners());\n    portScanningTimer.stop();\n\n    assertThrows(\n        IllegalStateException.class,\n        () -> executionTracer.startServiceFingerprinting(ImmutableList.of()));\n  }\n\n  @Test\n  public void startVulnerabilityDetecting_always_setsExecutionTracerToCorrectState() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n    executionTracer.startPortScanning(pluginManager.getPortScanners());\n    executionTracer.startServiceFingerprinting(ImmutableList.of());\n\n    ImmutableList<PluginMatchingResult<VulnDetector>> installedVulnDetectors =\n        pluginManager.getVulnDetectors(ReconnaissanceReport.getDefaultInstance());\n    executionTracer.startVulnerabilityDetecting(installedVulnDetectors);\n\n    assertThat(portScanningTimer.isRunning()).isFalse();\n    assertThat(serviceFingerprintingTimer.isRunning()).isFalse();\n    assertThat(vulnerabilityDetectingTimer.isRunning()).isTrue();\n    assertThat(executionTracer.getCurrentExecutionStage())\n        .isEqualTo(ExecutionStage.VULNERABILITY_DETECTING);\n    assertThat(executionTracer.getSelectedVulnDetectors())\n        .containsExactlyElementsIn(installedVulnDetectors);\n    assertThat(executionTracer.getVulnerabilityDetectingStageRuntime()).isEqualTo(TICK_DURATION);\n  }\n\n  @Test\n  public void startVulnerabilityDetecting_whenStageNotFingerprinting_throwsException() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n\n    assertThrows(\n        IllegalStateException.class,\n        () ->\n            executionTracer.startVulnerabilityDetecting(\n                pluginManager.getVulnDetectors(ReconnaissanceReport.getDefaultInstance())));\n  }\n\n  @Test\n  public void startVulnerabilityDetecting_whenFingerprintingTimerNotRunning_throwsException() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n    executionTracer.startPortScanning(pluginManager.getPortScanners());\n    executionTracer.startServiceFingerprinting(ImmutableList.of());\n    serviceFingerprintingTimer.stop();\n\n    assertThrows(\n        IllegalStateException.class,\n        () ->\n            executionTracer.startVulnerabilityDetecting(\n                pluginManager.getVulnDetectors(ReconnaissanceReport.getDefaultInstance())));\n  }\n\n  @Test\n  public void setDone_always_setsExecutionTracerToCorrectState() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n    executionTracer.startPortScanning(pluginManager.getPortScanners());\n    executionTracer.startServiceFingerprinting(ImmutableList.of());\n    executionTracer.startVulnerabilityDetecting(\n        pluginManager.getVulnDetectors(ReconnaissanceReport.getDefaultInstance()));\n\n    executionTracer.setDone();\n\n    assertThat(portScanningTimer.isRunning()).isFalse();\n    assertThat(serviceFingerprintingTimer.isRunning()).isFalse();\n    assertThat(vulnerabilityDetectingTimer.isRunning()).isFalse();\n    assertThat(executionTracer.isDone()).isTrue();\n  }\n\n  @Test\n  public void setDone_whenStageNotVulnerabilityDetecting_throwsException() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n\n    assertThrows(IllegalStateException.class, executionTracer::setDone);\n  }\n\n  @Test\n  public void setDone_whenVulnerabilityDetectingTimerNotRunning_throwsException() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n    executionTracer.startPortScanning(pluginManager.getPortScanners());\n    executionTracer.startServiceFingerprinting(ImmutableList.of());\n    executionTracer.startVulnerabilityDetecting(\n        pluginManager.getVulnDetectors(ReconnaissanceReport.getDefaultInstance()));\n    vulnerabilityDetectingTimer.stop();\n\n    assertThrows(IllegalStateException.class, executionTracer::setDone);\n  }\n\n  @Test\n  public void forceDone_always_stopsAllTimerAndSetDoneStatus() {\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n    executionTracer.startPortScanning(pluginManager.getPortScanners());\n\n    executionTracer.forceDone();\n\n    assertThat(portScanningTimer.isRunning()).isFalse();\n    assertThat(serviceFingerprintingTimer.isRunning()).isFalse();\n    assertThat(vulnerabilityDetectingTimer.isRunning()).isFalse();\n    assertThat(executionTracer.isDone()).isTrue();\n  }\n\n  @Test\n  public void buildLoggableExecutionTrace_always_generatesExpectedMessage() {\n    ReconnaissanceReport reconnaissanceReport =\n        ReconnaissanceReport.newBuilder()\n            .addNetworkServices(\n                NetworkService.newBuilder()\n                    .setNetworkEndpoint(forIpAndPort(\"1.1.1.1\", 80))\n                    .setTransportProtocol(TransportProtocol.TCP)\n                    .setServiceName(\"http\")\n                    .build())\n            .addNetworkServices(\n                NetworkService.newBuilder()\n                    .setNetworkEndpoint(forIpAndPort(\"1.1.1.1\", 22))\n                    .setTransportProtocol(TransportProtocol.TCP)\n                    .setServiceName(\"ssh\")\n                    .build())\n            .build();\n    ExecutionTracer executionTracer =\n        new ExecutionTracer(\n            portScanningTimer, serviceFingerprintingTimer, vulnerabilityDetectingTimer);\n    executionTracer.startPortScanning(pluginManager.getPortScanners());\n    executionTracer.startServiceFingerprinting(ImmutableList.of());\n    executionTracer.startVulnerabilityDetecting(\n        pluginManager.getVulnDetectors(reconnaissanceReport));\n    executionTracer.setDone();\n\n    String message =\n        executionTracer.buildLoggableExecutionTrace(\n            ScanResults.newBuilder().addScanFindings(ScanFinding.getDefaultInstance()).build());\n\n    assertThat(message)\n        .isEqualTo(\n            \"Tsunami scanning workflow traces:\\n\"\n                + \"  Port scanning phase (1.000 s) with 1 plugin(s):\\n\"\n                + \"    /fake/PORT_SCAN/FakePortScanner/v0.1\\n\"\n                + \"  Service fingerprinting phase (1.000 s) with 0 plugin(s):\\n\"\n                + \"    \\n\"\n                + \"  Vuln detection phase (1.000 s) with 2 plugin(s):\\n\"\n                + \"    /fake/VULN_DETECTION/FakeVulnDetector/v0.1 was selected for the following\"\n                + \" services: http (TCP, port 80), ssh (TCP, port 22)\\n\"\n                + \"    /fake/VULN_DETECTION/FakeVulnDetector2/v0.1 was selected for the following\"\n                + \" services: http (TCP, port 80), ssh (TCP, port 22)\\n\"\n                + \"  # of detected vulnerability: 1.\");\n  }\n}\n"
  }
]